Compare commits
338 Commits
mineclone5
...
master
Author | SHA1 | Date |
---|---|---|
Nils Dagsson Moskopp | 46394e7e1f | |
Lizzy Fleckenstein | b0aceae73d | |
NO11 | 9200c3fe44 | |
NO11 | 903d1777be | |
Lizzy Fleckenstein | 6ca5799565 | |
Lizzy Fleckenstein | 30528b0a2c | |
NO11 | 9919011aca | |
NO11 | c757e98b4f | |
NO11 | e2b22a62ce | |
NO11 | a9804879e2 | |
NO11 | d2a03ec0b9 | |
NO11 | 7ef6613f09 | |
NO11 | d27109d807 | |
iliekprogrammar | 79e1e2fa8f | |
NO11 | fdaaf0ec49 | |
NO11 | add97d39a6 | |
NO11 | bcb6251d20 | |
NO11 | 1483c45cc9 | |
NO11 | 642b9db035 | |
NO11 | d7e59f6a35 | |
NO11 | d4da855570 | |
NO11 | 30dd505985 | |
NO11 | ae8068cca6 | |
iliekprogrammar | bd14918543 | |
iliekprogrammar | 66c1b4a95f | |
iliekprogrammar | 96c4fb60d8 | |
NO11 | 5515e2baa1 | |
NO11 | be2e4614d0 | |
NO11 | 7ed964756e | |
NO11 | 0e5ad8637d | |
NO11 | 0cc0a7e01d | |
NO11 | 490e40d042 | |
NO11 | f233fb5d30 | |
NO11 | 9e4e7f0695 | |
NO11 | 2a08f31435 | |
iliekprogrammar | 740f7583ef | |
iliekprogrammar | 0564121183 | |
iliekprogrammar | bca5033fb2 | |
Dieter44 | 03d22852ee | |
Dieter44 | 10a5f8c4bf | |
Dieter44 | 68810a2a74 | |
Dieter44 | 0b6467d679 | |
NO11 | 8979230c42 | |
NO11 | fafa47d643 | |
iliekprogrammar | f61143758e | |
iliekprogrammar | fa22ec4dd0 | |
Dieter44 | 017bf705e9 | |
Lizzy Fleckenstein | a7bc460fae | |
Dieter44 | e82d21040c | |
iliekprogrammar | 0491b814dd | |
Dieter44 | 2f05388541 | |
Dieter44 | 976f522b9d | |
NO11 | 51dffc6c53 | |
NO11 | 30ce6f8a77 | |
Lizzy Fleckenstein | a34ae040c8 | |
Lizzy Fleckenstein | a410a7fabe | |
Lizzy Fleckenstein | ea46c8741b | |
NO11 | 5b52deaa8a | |
NO11 | 7bbc1e9951 | |
Lizzy Fleckenstein | 0dd780ee2a | |
NO11 | 1b259f928b | |
Dieter44 | d0d60804a3 | |
Dieter44 | d1d11f9740 | |
Nils Dagsson Moskopp | 4926c0560d | |
Nils Dagsson Moskopp | 23ca11c8e1 | |
Elias Fleckenstein | 88e084cbca | |
Lizzy Fleckenstein | be86b603f8 | |
iliekprogrammar | f4803a35c4 | |
Lizzy Fleckenstein | ce4c0ed4c1 | |
Lizzy Fleckenstein | 4a3a8841cd | |
Lizzy Fleckenstein | 649f481b51 | |
Lizzy Fleckenstein | b937b38b1c | |
Lizzy Fleckenstein | f9e7f58492 | |
Lizzy Fleckenstein | a77e79d985 | |
Lizzy Fleckenstein | 24ca8252a9 | |
Lizzy Fleckenstein | c6e9d763d6 | |
Lizzy Fleckenstein | 61d0dc8182 | |
Lizzy Fleckenstein | 46d1dd42d4 | |
Lizzy Fleckenstein | 39f66eb4a0 | |
Lizzy Fleckenstein | 18dd1cabd0 | |
Lizzy Fleckenstein | e70161501f | |
Lizzy Fleckenstein | bb6fe65aa5 | |
Lizzy Fleckenstein | cdf6533e0a | |
Lizzy Fleckenstein | d690797011 | |
Lizzy Fleckenstein | 6000c29171 | |
Lizzy Fleckenstein | faff9316e0 | |
Lizzy Fleckenstein | 7c1777c53a | |
Lizzy Fleckenstein | c2f0f0297b | |
Lizzy Fleckenstein | 6473494cbc | |
Lizzy Fleckenstein | 4d93e13f80 | |
Lizzy Fleckenstein | 47340386e2 | |
Lizzy Fleckenstein | 25f467c8ab | |
Elias Fleckenstein | c17fc81d1f | |
Lizzy Fleckenstein | 8f9f5cc936 | |
Elias Fleckenstein | 4af5d8e5e8 | |
Lizzy Fleckenstein | 643ded06da | |
Lizzy Fleckenstein | 970988cb39 | |
Lizzy Fleckenstein | 34f329a9d5 | |
Lizzy Fleckenstein | a4e73886d5 | |
Lizzy Fleckenstein | 148575a05b | |
iliekprogrammar | 0baffeced3 | |
Nils Dagsson Moskopp | 2607d40f1f | |
Artem Arbatsky | db696d0e2b | |
NO11 | f09923ded9 | |
NO11 | 0b9cbdb20a | |
NO11 | c52fda154b | |
NO11 | 28b7304211 | |
NO11 | 1518eb81cb | |
NO11 | 4e0bb0d120 | |
NO11 | 5b37f56005 | |
NO11 | aaee3b6de7 | |
NO11 | 6025a3d7d4 | |
Lizzy Fleckenstein | bbdd8f55eb | |
Lizzy Fleckenstein | 6fd8ff8865 | |
Lizzy Fleckenstein | 11e364b3ec | |
Lizzy Fleckenstein | 4db9952a84 | |
Lizzy Fleckenstein | fb2a501a9c | |
Lizzy Fleckenstein | 938911e7e3 | |
Lizzy Fleckenstein | c1934c4f3a | |
Lizzy Fleckenstein | 61dccfb9e5 | |
Lizzy Fleckenstein | 90796ec7b4 | |
Lizzy Fleckenstein | d30e014233 | |
NO11 | 19689dd857 | |
NO11 | b78e7b3e4d | |
Lizzy Fleckenstein | 835076ea4b | |
Lizzy Fleckenstein | 797da20fa7 | |
Lizzy Fleckenstein | c315d155e1 | |
Lizzy Fleckenstein | 0c567c7921 | |
Lizzy Fleckenstein | 64ebdd0f18 | |
Lizzy Fleckenstein | 1bd972bff7 | |
Lizzy Fleckenstein | ea0f52763c | |
Lizzy Fleckenstein | 70425e9f30 | |
Lizzy Fleckenstein | a0789e72f0 | |
Lizzy Fleckenstein | e341b2a6fe | |
Lizzy Fleckenstein | 4e8e6fbb51 | |
Lizzy Fleckenstein | efbe3c93b4 | |
Lizzy Fleckenstein | 0369465630 | |
Lizzy Fleckenstein | 30f7c638f3 | |
NO11 | eccba76732 | |
Lizzy Fleckenstein | f3d16d264c | |
Lizzy Fleckenstein | da5e703675 | |
Lizzy Fleckenstein | 6466061796 | |
Lizzy Fleckenstein | a80438d58e | |
Lizzy Fleckenstein | 47fbb0c176 | |
Lizzy Fleckenstein | fba30eccd6 | |
Lizzy Fleckenstein | 6e7827902c | |
Lizzy Fleckenstein | a877c615a5 | |
Lizzy Fleckenstein | 7f43ba6e36 | |
Lizzy Fleckenstein | 5edf27ac88 | |
Lizzy Fleckenstein | c998788435 | |
Lizzy Fleckenstein | 7707c3132c | |
Lizzy Fleckenstein | 756d28e2c6 | |
Lizzy Fleckenstein | 3a422e3afc | |
Lizzy Fleckenstein | 362de4c920 | |
Lizzy Fleckenstein | 6580bcab5a | |
Lizzy Fleckenstein | a6def5e9bb | |
NO11 | 6d22262c53 | |
NO11 | 7489010152 | |
Lizzy Fleckenstein | d224236388 | |
Lizzy Fleckenstein | 27f35fe422 | |
Lizzy Fleckenstein | 4b1606eaee | |
Lizzy Fleckenstein | 4776487f83 | |
Lizzy Fleckenstein | 664244d25c | |
Lizzy Fleckenstein | b9999195ec | |
Elias Fleckenstein | b18e077ba3 | |
Elias Fleckenstein | f3693138c8 | |
Elias Fleckenstein | 6e94550a12 | |
NO11 | d85f143fde | |
NO11 | dafe860e56 | |
Lizzy Fleckenstein | 832e634e3b | |
Lizzy Fleckenstein | 5c55ddff1e | |
Lizzy Fleckenstein | a28bfebb9b | |
Lizzy Fleckenstein | e5c5a78553 | |
epCode | 1c458a2e72 | |
NO11 | 94e1d82b45 | |
NO11 | 9ccf8de606 | |
NO11 | 19b5a5aac7 | |
NO11 | 6803d22dbd | |
NO11 | 0584d16569 | |
NO11 | 03829dd518 | |
NO11 | 72ea9069bd | |
NO11 | 9188467a6a | |
NO11 | fe91d7f3e0 | |
NO11 | 463fe2af5f | |
NO11 | e4af02ea52 | |
NO11 | 8e3f9d2169 | |
NO11 | a410d17306 | |
AFCMS | e9437e9e1e | |
AFCMS | e40ed91674 | |
NO11 | 16c73c1cb6 | |
NO11 | 03be736566 | |
NO11 | 8697b38d60 | |
NO11 | e83438e42c | |
NO11 | 71f448537c | |
NO11 | df1d8077e6 | |
NO11 | 89e90b13eb | |
AFCMS | b0cf07a020 | |
AFCMS | e36a8c5acf | |
AFCMS | b2af00db22 | |
AFCMS | e8134345d4 | |
AFCMS | e0c7e938de | |
AFCMS | 3e54acce9d | |
AFCMS | 47fff66a41 | |
ztianyang | ac8383dd4b | |
NO11 | c6003398b5 | |
NO11 | f10d579d9c | |
NO11 | fed43586f2 | |
NO11 | 18a83fa5d7 | |
NO11 | 0e15accada | |
NO11 | 4194a76db0 | |
Lizzy Fleckenstein | 27e4bd6d09 | |
Nils Dagsson Moskopp | 256de2bc36 | |
sfan5 | 693a5317ef | |
Nils Dagsson Moskopp | 95c4d6472b | |
Wuzzy | 1c192f4fbb | |
NO11 | c22c2063ad | |
NO11 | 410b7c7844 | |
NO11 | 9f9799d96e | |
NO11 | cd84c472dc | |
NO11 | 0bc9ab9233 | |
NO11 | 89ff666a6a | |
NO11 | 1a5339e907 | |
NO11 | aa930dc2b8 | |
NO11 | 79463738f6 | |
NO11 | 79d8593d34 | |
NO11 | dc8436fdf9 | |
NO11 | f41cea71fd | |
NO11 | 5fedd914fb | |
Elias Fleckenstein | a8d09338a9 | |
NO11 | 872e3e74d3 | |
NO11 | bf62eb33fa | |
NO11 | 0da7b3fbda | |
Elias Fleckenstein | 3669321828 | |
NO11 | ca086109bf | |
NO11 | 6c6d40eb9d | |
NO11 | 2b322a451f | |
NO11 | f0af15fcd8 | |
AFCMS | af8e88f44c | |
AFCMS | 2d1a43396f | |
AFCMS | df4b8e64cc | |
AFCMS | 40898d3e9d | |
AFCMS | 58a292a4f3 | |
AFCMS | fe62189019 | |
NO11 | 4802b610c3 | |
NO11 | cb55c36863 | |
NO11 | f8dcf05670 | |
NO11 | 7e0bb036f4 | |
NO11 | 664c238137 | |
Elias Fleckenstein | efab5a1cf5 | |
NO11 | d76a7daf2d | |
Emily | 5bb57a81ad | |
Emily | df0c1f1dd1 | |
NO11 | 5c563d6ffd | |
NO11 | 4aabd7d9e7 | |
Elias Fleckenstein | 88f253bf66 | |
NO11 | 289ba826ba | |
NO11 | 4846076c8f | |
NO11 | 5c5c405ccf | |
NO11 | 65d33b935a | |
NO11 | c05e57efb1 | |
NO11 | e44e9eaf62 | |
NO11 | 09a68443cd | |
NO11 | 75b425ffd7 | |
NO11 | 0a9ea7e46a | |
NO11 | a44d9643ae | |
AFCMS | a47630035d | |
AFCMS | 667ef5cad5 | |
AFCMS | fef23d0b6f | |
NO11 | a0d52010bf | |
AFCMS | 44063bca93 | |
AFCMS | e58bb6d859 | |
NO11 | 31d3ea8a87 | |
AFCMS | 13536baed8 | |
AFCMS | 6069bb4a38 | |
AFCMS | c31c852a6e | |
AFCMS | b5f7ae5458 | |
AFCMS | 9254bab971 | |
AFCMS | 999b82c94a | |
NO11 | a83dd8548f | |
Tianyang Zhang | 559cf85c94 | |
NO11 | 96e8e6a86f | |
NO11 | 801d9a2571 | |
NO11 | f2a4d6bd56 | |
NO11 | 48166625d4 | |
NO11 | a2e0cf83bc | |
AFCMS | b364faa7c7 | |
Tianyang Zhang | 60fcafeee4 | |
SmallJoker | 9d9e213012 | |
AFCMS | 49bde37a5e | |
AFCMS | dc17cc91a3 | |
AFCMS | 873a1e73dc | |
AFCMS | 8fff20eec9 | |
AFCMS | ec6086d8e6 | |
AFCMS | 6d7aafe0d4 | |
AFCMS | 91adfcbb1d | |
AFCMS | 8958aa7b12 | |
AFCMS | cf5703d528 | |
AFCMS | 88e59d3592 | |
AFCMS | cd08df175c | |
AFCMS | ca277b6769 | |
AFCMS | b0127fc1c3 | |
AFCMS | d26b1b1402 | |
NO11 | 549bdeb6e9 | |
NO11 | ecfb615c89 | |
NO11 | 5ceb48fcb1 | |
1F616EMO | d2f7d31360 | |
AFCMS | 8e931e92f5 | |
AFCMS | cbfec5c5c8 | |
NO11 | 509568b4b0 | |
NO11 | 1471521709 | |
AFCMS | 2624343c30 | |
AFCMS | 30e2e0d70a | |
AFCMS | 40f4287ff2 | |
AFCMS | f70dbb8cfa | |
NO11 | ee2fa60cae | |
NO11 | 0795862c5c | |
NO11 | 99ccd9ea4c | |
AFCMS | 2603c4768b | |
AFCMS | c642f9e2f7 | |
NO11 | 1e0ffed26b | |
NO11 | ee21a24fb6 | |
AFCMS | 881f0b0a71 | |
AFCMS | a21b14e707 | |
AFCMS | b9fd1ac227 | |
AFCMS | 7e64470f70 | |
AFCMS | c8102838cb | |
AFCMS | 6b53dda79b | |
AFCMS | 640b0dc485 | |
NO11 | 75e263debc | |
NO11 | a65db15b5c | |
AFCMS | 0119793d7a | |
AFCMS | 91ac70cf28 | |
AFCMS | 17202115fa | |
AFCMS | 5d65c8a3aa | |
AFCMS | e9f38c6b90 | |
talamh | 2c434495c1 | |
talamh | e6b61a1551 | |
talamh | f1229c5401 |
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
eliasfleckenstein@web.de.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
443
CONTRIBUTING.md
|
@ -2,104 +2,413 @@
|
|||
So you want to contribute to MineClone2?
|
||||
Wow, thank you! :-)
|
||||
|
||||
But first, some things to note:
|
||||
MineClone2 is maintained by Nicu and Fleckenstein. If you have any
|
||||
problems or questions, contact us (See Links section below).
|
||||
|
||||
MineClone 2's development target is to make a free software clone of Minecraft,
|
||||
***version 1.12***, ***PC edition***, *** + Optifine features supported by the Minetest Engine ***.
|
||||
You can help with MineClone2's development in many different ways,
|
||||
whether you're a programmer or not.
|
||||
|
||||
MineClone 2 is maintained by three persons. Namely, kay27, EliasFleckenstein and jordan4ibanez. You can find us
|
||||
in the Minetest forums (forums.minetest.net), in IRC in the #mineclone2
|
||||
channel on irc.freenode.net. And finally, you can send e-mails to
|
||||
<eliasfleckenstein@web.de> or <kay27@bk.ru>.
|
||||
## MineClone2's development target is to...
|
||||
- Crucially, create a stable, moddable, free/libre clone of Minecraft
|
||||
based on the Minetest engine with polished features, usable in both
|
||||
singleplayer and multiplayer. Currently, most of **Minecraft Java
|
||||
Edition 1.12.2** features are already implemented and polishing existing
|
||||
features are prioritized over new feature requests.
|
||||
- With lessened priority yet strictly, implement features targetting
|
||||
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
|
||||
by the Minetest Engine). This means features in parity with the listed
|
||||
Minecraft experiences are prioritized over those that don't fulfill this
|
||||
scope.
|
||||
- Optionally, create a performant experience that will run relatively
|
||||
well on really low spec computers. Unfortunately, due to Minecraft's
|
||||
mechanisms and Minetest engine's limitations along with a very small
|
||||
playerbase on low spec computers, optimizations are hard to investigate.
|
||||
|
||||
By sending us patches or asking us to include your changes in this game,
|
||||
you agree that they fall under the terms of the LGPLv2.1, which basically
|
||||
means they will become part of a free software.
|
||||
## Links
|
||||
* [Mesehub](https://git.minetest.land/MineClone2/MineClone2)
|
||||
* [Discord](https://discord.gg/xE4z8EEpDC)
|
||||
* [YouTube](https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A)
|
||||
* [IRC](https://web.libera.chat/#mineclone2)
|
||||
* [Matrix](https://app.element.io/#/room/#mc2:matrix.org)
|
||||
* [Reddit](https://www.reddit.com/r/MineClone2/)
|
||||
* [Minetest forums](https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* [ContentDB](https://content.minetest.net/packages/wuzzy/mineclone2/)
|
||||
* [OpenCollective](https://opencollective.com/mineclone2)
|
||||
|
||||
## The suggested workflow
|
||||
We don't **dictate** your workflow, but in order to work with us in an efficient
|
||||
way, you can follow these suggestions:
|
||||
## Using git
|
||||
MineClone2 is developed using the version control system
|
||||
[git](https://git-scm.com/). If you want to contribute code to the
|
||||
project, it is **highly recommended** that you learn the git basics.
|
||||
For non-programmers and people who do not plan to contribute code to
|
||||
MineClone2, git is not required. However, git is a tool that will be
|
||||
referenced frequently because of its usefulness. As such, it is valuable
|
||||
in learning how git works and its terminology. It can also help you
|
||||
keeping your game updated, and easily test pull requests.
|
||||
|
||||
For small and medium changes:
|
||||
## How you can help as a non-programmer
|
||||
|
||||
* Fork the repository
|
||||
As someone who does not know how to write programs in Lua or does not
|
||||
know how to use the Minetest API, you can still help us out a lot. For
|
||||
example, by opening an issue in the
|
||||
[Issue tracker](https://git.minetest.land/MineClone2/MineClone2/issues),
|
||||
you can report a bug or request a feature.
|
||||
|
||||
### Rules about both bugs and feature requests
|
||||
* Stay polite towards the developers and anyone else involved in the
|
||||
discussion.
|
||||
* Choose a descriptive title (e.g. not just "crash", "bug" or "question"
|
||||
).
|
||||
* Please write in plain, understandable English. It will be easier to
|
||||
communicate.
|
||||
* Please start the issue title with a capital letter.
|
||||
* Always check the currently opened issues before creating a new one.
|
||||
Don't report bugs that have already been reported or request features
|
||||
that already have been requested.
|
||||
* If you know about Minetest's inner workings, please think about
|
||||
whether the bug / the feature that you are reporting / requesting is
|
||||
actually an issue with Minetest itself, and if it is, head to the
|
||||
[Minetest issue tracker](https://github.com/minetest/minetest/issues)
|
||||
instead.
|
||||
* If you need any help regarding creating a Mesehub account or opening
|
||||
an issue, feel free to ask on the Discord / Matrix server or the IRC
|
||||
channel.
|
||||
|
||||
### Reporting bugs
|
||||
* A bug is an unintended behavior or, in the worst case, a crash.
|
||||
However, it is not a bug if you believe something is missing in the
|
||||
game. In this case, please read "Requesting features"
|
||||
* If you report a crash, always include the error message. If you play
|
||||
in singleplayer, post a screenshot of the message that Minetest showed
|
||||
when the crash happened (or copy the message into your issue). If you
|
||||
are a server admin, you can find error messages in the log file of the
|
||||
server.
|
||||
* Tell us which MineClone2 and Minetest versions you are using.
|
||||
* Tell us how to reproduce the problem: What you were doing to trigger
|
||||
the bug, e.g. before the crash happened or what causes the faulty
|
||||
behavior.
|
||||
|
||||
### Requesting features
|
||||
* Ensure the requested feature fulfills our development targets and
|
||||
goals.
|
||||
* Begging or excessive attention seeking does not help us in the
|
||||
slightest, and may very well disrupt MineClone2 development. It's better
|
||||
to put that energy into helping or researching the feature in question.
|
||||
After all, we're just volunteers working on our spare time.
|
||||
* Ensure the requested feature has not been implemented in MineClone2
|
||||
latest or development versions.
|
||||
|
||||
### Testing code
|
||||
If you want to help us with speeding up MineClone2 development and
|
||||
making the game more stable, a great way to do that is by testing out
|
||||
new features from contributors. For most new things that get into the
|
||||
game, a pull request is created. A pull request is essentially a
|
||||
programmer saying "Look, I modified the game, please apply my changes
|
||||
to the upstream version of the game". However, every programmer makes
|
||||
mistakes sometimes, some of which are hard to spot. You can help by
|
||||
downloading this modified version of the game and trying it out - then
|
||||
tell us if the code works as expected without any issues. Ideally, you
|
||||
would report issues will pull requests similar to when you were
|
||||
reporting bugs that are the mainline (See Reporting bugs section). You
|
||||
can find currently open pull requests here:
|
||||
<https://git.minetest.land/MineClone2/MineClone2/pulls>. Note that pull
|
||||
requests that start with a `WIP:` are not done yet, and therefore might
|
||||
not work, so it's not very useful to try them out yet.
|
||||
|
||||
### Contributing assets
|
||||
Due to license problems, MineClone2 unfortunately cannot use
|
||||
Minecraft's assets, therefore we are always looking for asset
|
||||
contributions. To contribute assets, it can be useful to learn git
|
||||
basics and read the section for Programmers of this document, however
|
||||
this is not required. It's also a good idea to join the Discord server
|
||||
(or alternatively IRC or Matrix).
|
||||
|
||||
#### Textures
|
||||
For textures we use the Pixel Perfection texture pack. This is mostly
|
||||
enough; however in some cases - e.g. for newer Minecraft features, it's
|
||||
useful to have texture artists around. If you want to make such
|
||||
contributions, join our Discord server. Demands for textures will be
|
||||
communicated there.
|
||||
|
||||
#### Sounds
|
||||
MineClone2 currently does not have a consistent way to handle sounds.
|
||||
The sounds in the game come from different sources, like the SnowZone
|
||||
resource pack or minetest_game. Unfortunately, MineClone2 does not play
|
||||
a sound in every situation you would get one in Minecraft. Any help with
|
||||
sounds is greatly appreciated, however if you add new sounds you should
|
||||
probably work together with a programmer, to write the code to actually
|
||||
play these sounds in game.
|
||||
|
||||
#### 3D Models
|
||||
Most of the 3D Models in MineClone2 come from
|
||||
[22i's repository](https://github.com/22i/minecraft-voxel-blender-models).
|
||||
Similar to the textures, we need people that can make 3D Models with
|
||||
Blender on demand. Many of the models have to be patched, some new
|
||||
animations have to be added etc.
|
||||
|
||||
#### Crediting
|
||||
Asset contributions will be credited in their own respective sections in
|
||||
CREDITS.md. If you have commited the results yourself, you will also be
|
||||
credited in the Contributors section.
|
||||
|
||||
### Contributing Translations
|
||||
|
||||
#### Workflow
|
||||
To add/update support for your language to MineClone2, you should take
|
||||
the steps documented in the section for Programmers, add/update the
|
||||
translation files of the mods that you want to update. You can add
|
||||
support for all mods, just some of them or only one mod; you can update
|
||||
the translation file entirely or only partly; basically any effort is
|
||||
valued. If your changes are small, you can also send them to developers
|
||||
via E-Mail, Discord, IRC or Matrix - they will credit you appropriately.
|
||||
|
||||
#### Things to note
|
||||
You can use the script at `tools/check_translate_files.py` to compare
|
||||
the translation files for the language you are working on with the
|
||||
template files, to see what is missing and what is out of date with
|
||||
the template file. However, template files are often incomplete and/or
|
||||
out of date, sometimes they don't match the code. You can update the
|
||||
translation files if that is required, you can also modify the code in
|
||||
your translation PR if it's related to translation. You can also work on
|
||||
multiple languages at the same time in one PR.
|
||||
|
||||
#### Crediting
|
||||
Translation contributions will be credited in their own in CREDITS.md.
|
||||
If you have commited the results yourself, you will also be credited in
|
||||
the Contributors section.
|
||||
|
||||
### Profiling
|
||||
If you own a server, a great way to help us improve MineClone2's code
|
||||
is by giving us profiler results. Profiler results give us detailed
|
||||
information about the game's performance and let us know places to
|
||||
investigate optimization issues. This way we can make the game faster.
|
||||
|
||||
#### Using Minetest's profiler
|
||||
Minetest has a built in profiler. Simply set `profiler.load = true` in
|
||||
your configuration file and restart the server. After running the server
|
||||
for some time, just run `/profiler save` in chat - then you will find a
|
||||
file in the world directory containing the results. Open a new issue and
|
||||
upload the file. You can name the issue "<Server name> profiler
|
||||
results".
|
||||
|
||||
### Let us know your opinion
|
||||
It is always encouraged to actively contribute to issue discussions on
|
||||
MeseHub, let us know what you think about a topic and help us make
|
||||
decisions. Also, note that a lot of discussion takes place on the
|
||||
Discord server, so it's definitely worth checking it out.
|
||||
|
||||
### Funding
|
||||
You can help pay for our infrastructure (Mesehub) by donating to our
|
||||
OpenCollective link (See Links section).
|
||||
|
||||
### Crediting
|
||||
If you opened or have contributed to an issue, you receive the
|
||||
`Community` role on our Discord (after asking for it).
|
||||
OpenCollective Funders are credited in their own section in
|
||||
`CREDITS.md` and receive a special role "Funder" on our discord (unless
|
||||
they have made their donation Incognito).
|
||||
|
||||
## How you can help as a programmer
|
||||
(Almost) all the MineClone2 development is done using pull requests.
|
||||
|
||||
### Recommended workflow
|
||||
* Fork the repository (in case you have not already)
|
||||
* Do your change in a new branch
|
||||
* Create a pull request to get your changes merged into master
|
||||
* Keep your pull request up to date by regularly merging upstream. It is
|
||||
imperative that conflicts are resolved prior to merging the pull
|
||||
request.
|
||||
* After the pull request got merged, you can delete the branch
|
||||
|
||||
For small changes, sending us a patch is also good.
|
||||
### Discuss first
|
||||
If you feel like a problem needs to fixed or you want to make a new
|
||||
feature, you could start writing the code right away and notifying us
|
||||
when you're done, but it never hurts to discuss things first. If there
|
||||
is no issue on the topic, open one. If there is an issue, tell us that
|
||||
you'd like to take care of it, to avoid duplicate work.
|
||||
|
||||
For big changes: Same as above, but consider notifying us first to avoid
|
||||
duplicate work and possible tears of rejection. ;-)
|
||||
### Don't hesitate to ask for help
|
||||
We appreciate any contributing effort to MineClone2. If you are a
|
||||
relatively new programmer, you can reach us on Discord, Matrix or IRC
|
||||
for questions about git, Lua, Minetest API, MineClone2 codebase or
|
||||
anything related to MineClone2. We can help you avoid writing code that
|
||||
would be deemed inadequate, or help you become familiar with MineClone2
|
||||
better, or assist you use development tools.
|
||||
|
||||
For trusted people, we might give them direct commit access to this
|
||||
repository. In this case, you obviously don't need to fork, but you still
|
||||
need to show your contributions align with the project goals. We still
|
||||
reserve the right to revert everything that we don't like.
|
||||
For bigger changes, we strongly recommend to use feature branches and
|
||||
discuss with me first.
|
||||
### Maintain your own code, even if already got merged
|
||||
Sometimes, your code may cause crashes or bugs - we try to avoid such
|
||||
scenarios by testing every time before merging it, but if your merged
|
||||
work causes problems, we ask you fix the issues as soon as possible.
|
||||
|
||||
If your code causes bugs and crashes, it is your responsibility to fix them as soon as possible.
|
||||
### Changing Gameplay
|
||||
Pull Requests that change gameplay have to be properly researched and
|
||||
need to state their sources. These PRs also need Fleckenstein's approval
|
||||
before they are merged.
|
||||
You can use these sources:
|
||||
|
||||
We mostly use plain merging rather than rebasing or squash merging.
|
||||
* Minecraft code (Name the source file and line, however DONT post any
|
||||
proprietary code). You can use
|
||||
[MCP](https://minecraft.fandom.com/wiki/Programs_and_editors/Mod_Coder_Pack)
|
||||
to decompile Minecraft or look at
|
||||
[Minestorm](https://github.com/Minestom/Minestom) code.
|
||||
* Testing things inside of Minecraft (Attach screenshots / video footage
|
||||
of the results)
|
||||
* [Official Minecraft Wiki](https://minecraft.fandom.com/wiki/Minecraft_Wiki)
|
||||
(Include a link to the specific page you used)
|
||||
|
||||
Your commit names should be relatively descriptive, e.g. when saying "Fix #issueid", the commit message should also contain the title of the issue.
|
||||
### Stick to our guidelines
|
||||
|
||||
Contributors will be credited in `CREDITS.md`.
|
||||
#### Git Guidelines
|
||||
* We use merge rather than rebase or squash merge
|
||||
* We don't use git submodules.
|
||||
* Your commit names should be relatively descriptive, e.g. when saying
|
||||
"Fix #issueid", the commit message should also contain the title of the
|
||||
issue.
|
||||
* Try to keep your commits as atomic as possible (advise, but completely
|
||||
optional)
|
||||
|
||||
## Code Style
|
||||
#### Code Guidelines
|
||||
* Each mod must provide `mod.conf`.
|
||||
* Mod names are snake case, and newly added mods start with `mcl_`, e.g.
|
||||
`mcl_core`, `mcl_farming`, `mcl_monster_eggs`. Keep in mind Minetest
|
||||
does not support capital letters in mod names.
|
||||
* To export functions, store them inside a global table named like the
|
||||
mod, e.g.
|
||||
|
||||
Each mod must provide `mod.conf`.
|
||||
Each mod which add API functions should store functions inside a global table named like the mod.
|
||||
Public functions should not use self references but rather just access the table directly.
|
||||
Functions should be defined in this way:
|
||||
```
|
||||
function mcl_xyz.stuff(param) end
|
||||
```
|
||||
Insteed of this way:
|
||||
```
|
||||
mcl_xyz.stuff = function(param) end
|
||||
```
|
||||
Indentation must be unified, more likely with tabs.
|
||||
```lua
|
||||
mcl_example = {}
|
||||
|
||||
function mcl_example.do_something()
|
||||
-- ...
|
||||
end
|
||||
|
||||
Time sensitive mods should make a local copy of most used API functions to improve performances.
|
||||
```
|
||||
local vector = vector
|
||||
local get_node = minetest.get_node
|
||||
```
|
||||
|
||||
* Public functions should not use self references but rather just access
|
||||
the table directly, e.g.
|
||||
|
||||
## Features > 1.12
|
||||
```lua
|
||||
-- bad
|
||||
function mcl_example:do_something()
|
||||
end
|
||||
|
||||
If you want to make a feature that was added in a Minecraft version later than 1.12, you should fork MineClone5 (mineclone5 branch in the repository) and add your changes to this.
|
||||
-- good
|
||||
function mcl_example.do_something()
|
||||
end
|
||||
```
|
||||
|
||||
## What we accept
|
||||
* Use modern Minetest API, e.g. no usage of `minetest.env`
|
||||
* Tabs should be used for indent, spaces for alignment, e.g.
|
||||
|
||||
* Every MC features up to version 1.12 JE.
|
||||
* Every already finished and working good features from versions above (only when making a MineClone5 PR / Contribution).
|
||||
* Except features which couldn't be done easily and bugfree because of Minetest engine limitations. Eg. we CAN extend world boundaries by playing with map chunks, just teleporting player onto next layer after 31000 , but it would cost too much (time, code, bugs, performance, stability, etc).
|
||||
* Some features, approved by the rest of the community, I mean maybe some voting and really missing any negative feedback.
|
||||
```lua
|
||||
|
||||
## What we reject
|
||||
-- use tabs for indent
|
||||
|
||||
* Any features which cause critical bugs, sending them to rework/fix or trying to fix immediately.
|
||||
* Some small portions of big entirely missing features which just definitely break gamplay balance give nothing useful
|
||||
* Controversial features, which some people support while others do not should be discussed well, with publishing forum announcements, at least during the week. In case if there are still doubts - send them into the mod.
|
||||
for i = 1, 10 do
|
||||
if i % 3 == 0 then
|
||||
print(i)
|
||||
end
|
||||
end
|
||||
|
||||
## Reporting bugs
|
||||
Report all bugs and missing Minecraft features here:
|
||||
-- use tabs for indent and spaces to align things
|
||||
|
||||
<https://git.minetest.land/MineClone2/MineClone2/issues>
|
||||
some_table = {
|
||||
{"a string", 5},
|
||||
{"a very much longer string", 10},
|
||||
}
|
||||
```
|
||||
|
||||
## Direct discussion
|
||||
We have an IRC channel! Join us on #mineclone2 in freenode.net.
|
||||
* Use double quotes for strings, e.g. `"asdf"` rather than `'asdf'`
|
||||
* Use snake_case rather than CamelCase, e.g. `my_function` rather than
|
||||
`MyFunction`
|
||||
* Don't declare functions as an assignment, e.g.
|
||||
|
||||
<ircs://irc.freenode.net:6697/#mineclone2>
|
||||
```lua
|
||||
-- bad
|
||||
local some_local_func = function()
|
||||
-- ...
|
||||
end
|
||||
|
||||
## Creating releases
|
||||
my_mod.some_func = function()
|
||||
-- ...
|
||||
end
|
||||
|
||||
-- good
|
||||
local function some_local_func()
|
||||
-- ...
|
||||
end
|
||||
|
||||
function my_mod.some_func()
|
||||
-- ...
|
||||
end
|
||||
```
|
||||
|
||||
### Developer status
|
||||
Active and trusted contributors are often granted write access to the
|
||||
MineClone2 repository.
|
||||
|
||||
#### Developer responsibilities
|
||||
- You should not push things directly to
|
||||
MineClone2 master - rather, do your work on a branch on your private
|
||||
repository, then create a pull request. This way other people can review
|
||||
your changes and make sure they work before they get merged.
|
||||
- Merge PRs only when they have recieved the necessary feedback and have
|
||||
been tested by at least two different people (including the author of
|
||||
the pull request), to avoid crashes or the introduction of new bugs.
|
||||
- You may also be assigned to issues or pull
|
||||
requests as a developer. In this case it is your responsibility to fix
|
||||
the issue / review and merge the pull request when it is ready. You can
|
||||
also unassign yourself from the issue / PR if you have no time or don't
|
||||
want to take care of it for some other reason. After all, everyone is a
|
||||
volunteer and we can't expect you to do work that you are not interested
|
||||
in. **The important thing is that you make sure to inform us if you
|
||||
won't take care of something that has been assigned to you.**
|
||||
- Please assign yourself to something that you want to work on to avoid
|
||||
duplicate work.
|
||||
- As a developer, it should be easy to reach you about your work. You
|
||||
should be in at least one of the public MineClone2 discussion rooms -
|
||||
preferrably Discord, but if you really don't like Discord, Matrix
|
||||
or IRC are fine too.
|
||||
|
||||
### Maintainer status
|
||||
Maintainers carry the main responsibility for the project.
|
||||
|
||||
#### Maintainer responsibilities
|
||||
- Making sure issues are addressed and pull requests are reviewed and
|
||||
merged, by assigning either themselves or Developers to issues / PRs
|
||||
- Making releases
|
||||
- Making sure guidelines are kept
|
||||
- Making project decisions based on community feedback
|
||||
- Granting/revoking developer access
|
||||
- Enforcing the code of conduct (See CODE_OF_CONDUCT.md)
|
||||
- Moderating official community spaces (See Links section)
|
||||
- Resolving conflicts and problems within the community
|
||||
|
||||
#### Current maintainers
|
||||
* Fleckenstein - responsible for gameplay review, publishing releases,
|
||||
technical guidelines and issue/PR delegation
|
||||
* Nicu - responsible for community related issues
|
||||
|
||||
#### Release process
|
||||
* Run `tools/generate_ingame_credits.lua` to update the ingame credits
|
||||
from `CREDITS.md` and commit the result (if anything changed)
|
||||
* Launch MineClone2 to make sure it still runs
|
||||
* Update the version number in README.md
|
||||
* Use `git tag <version number>` to tag the latest commit with the version number
|
||||
* Push to repo (don't forget `--tags`!)
|
||||
* Update ContentDB (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Update first post in forum thread (https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* Use `git tag <version number>` to tag the latest commit with the
|
||||
version number
|
||||
* Push to repository (don't forget `--tags`!)
|
||||
* Update ContentDB
|
||||
(https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Update first post in forum thread
|
||||
(https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* Post release announcement and changelog in forums
|
||||
|
||||
### Licensing
|
||||
By asking us to include your changes in this game, you agree that they
|
||||
fall under the terms of the GPLv3, which basically means they will
|
||||
become part of a free/libre software.
|
||||
|
||||
### Crediting
|
||||
Contributors, Developers and Maintainers will be credited in
|
||||
`CREDITS.md`. If you make your first time contribution, please add
|
||||
yourself to this file. There are also Discord roles for Contributors,
|
||||
Developers and Maintainers.
|
||||
|
|
31
CREDITS.md
|
@ -8,8 +8,8 @@
|
|||
|
||||
## Maintainers
|
||||
* Fleckenstein
|
||||
* Nicu
|
||||
* kay27
|
||||
* jordan4ibanez
|
||||
|
||||
## Developers
|
||||
* bzoss
|
||||
|
@ -19,10 +19,11 @@
|
|||
* iliekprogrammar
|
||||
* MysticTempest
|
||||
* Rootyjr
|
||||
* Nicu
|
||||
* aligator
|
||||
* Code-Sploit
|
||||
* NO11
|
||||
* cora
|
||||
* jordan4ibanez
|
||||
|
||||
## Contributors
|
||||
* Laurent Rocher
|
||||
|
@ -48,8 +49,25 @@
|
|||
* dBeans
|
||||
* nickolas360
|
||||
* yutyo
|
||||
* ztianyang
|
||||
* Tianyang Zhang
|
||||
* j45
|
||||
* Marcin Serwin
|
||||
* erlehmann
|
||||
* E
|
||||
* Benjamin Schötz
|
||||
* Doloment
|
||||
* Sydney Gems
|
||||
* talamh
|
||||
* Emily2255
|
||||
* Emojigit
|
||||
* FinishedFragment
|
||||
* sfan5
|
||||
* Blue Blancmange
|
||||
* Jared Moody
|
||||
* SmallJoker
|
||||
* Sven792
|
||||
* aldum
|
||||
* Dieter44
|
||||
|
||||
## MineClone5
|
||||
* kay27
|
||||
|
@ -74,7 +92,6 @@
|
|||
* Rochambeau
|
||||
* rubenwardy
|
||||
* stu
|
||||
* jordan4ibanez
|
||||
* 4aiman
|
||||
* Kahrl
|
||||
* Krock
|
||||
|
@ -103,6 +120,7 @@
|
|||
* xMrVizzy
|
||||
* yutyo
|
||||
* NO11
|
||||
* kay27
|
||||
|
||||
## Translations
|
||||
* Wuzzy
|
||||
|
@ -110,6 +128,11 @@
|
|||
* wuniversales
|
||||
* kay27
|
||||
* pitchum
|
||||
* todoporlalibertad
|
||||
* Marcin Serwin
|
||||
|
||||
## Funders
|
||||
* 40W
|
||||
|
||||
## Special thanks
|
||||
* celeron55 for creating Minetest
|
||||
|
|
31
README.md
|
@ -85,21 +85,24 @@ The MineClone2 repository is hosted at Mesehub. To contribute or report issues,
|
|||
* Matrix: <https://app.element.io/#/room/#mc2:matrix.org>
|
||||
* Reddit: <https://www.reddit.com/r/MineClone2/>
|
||||
* Minetest forums: <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* ContentDB: <https://content.minetest.net/packages/wuzzy/mineclone2/>
|
||||
* OpenCollective: <https://opencollective.com/mineclone2>
|
||||
|
||||
## Project description
|
||||
The main goal of **MineClone 2** is to be a clone of Minecraft and to be released as free software.
|
||||
|
||||
* **Target of development: Minecraft, PC Edition, version 1.12** (later known as “Java Edition”)
|
||||
* MineClone2 also includes Optifine features supported by the Minetest
|
||||
* In general, Minecraft is aimed to be cloned as good as possible
|
||||
* Cloning the gameplay has highest priority
|
||||
* MineClone 2 will use different assets, but with a similar style
|
||||
* Limitations found in Minetest will be documented in the course of development
|
||||
* Features of later Minecraft versions are collected in the mineclone5 branch
|
||||
|
||||
## Using features from newer versions of Minecraft
|
||||
For > 1.12 features, checkout MineClone5. It includes features from newer Minecraft versions.
|
||||
Download it here: https://git.minetest.land/MineClone2/MineClone2/src/branch/mineclone5
|
||||
## Target
|
||||
- Crucially, create a stable, moddable, free/libre clone of Minecraft
|
||||
based on the Minetest engine with polished features, usable in both
|
||||
singleplayer and multiplayer. Currently, most of **Minecraft Java
|
||||
Edition 1.12.2** features are already implemented and polishing existing
|
||||
features are prioritized over new feature requests.
|
||||
- With lessened priority yet strictly, implement features targetting
|
||||
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
|
||||
by the Minetest Engine). This means features in parity with the listed
|
||||
Minecraft experiences are prioritized over those that don't fulfill this
|
||||
scope.
|
||||
- Optionally, create a performant experience that will run relatively
|
||||
well on really low spec computers. Unfortunately, due to Minecraft's
|
||||
mechanisms and Minetest engine's limitations along with a very small
|
||||
playerbase on low spec computers, optimizations are hard to investigate.
|
||||
|
||||
## Completion status
|
||||
This game is currently in **beta** stage.
|
||||
|
|
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 154 B |
After Width: | Height: | Size: 155 B |
After Width: | Height: | Size: 165 B |
|
@ -1,5 +1,27 @@
|
|||
mcl_util = {}
|
||||
|
||||
-- Updates all values in t using values from to*.
|
||||
function table.update(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Updates nil values in t using values from to*.
|
||||
function table.update_nil(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
if t[k] == nil then
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Based on minetest.rotate_and_place
|
||||
|
||||
--[[
|
||||
|
@ -538,3 +560,12 @@ function mcl_util.get_object_name(object)
|
|||
return luaentity.nametag and luaentity.nametag ~= "" and luaentity.nametag or luaentity.description or luaentity.name
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.replace_mob(obj, mob)
|
||||
local rot = obj:get_yaw()
|
||||
local pos = obj:get_pos()
|
||||
obj:remove()
|
||||
obj = minetest.add_entity(pos, mob)
|
||||
obj:set_yaw(rot)
|
||||
return obj
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ Params:
|
|||
|
||||
* pos: position
|
||||
|
||||
## mcl_worlds.y_to_layer(y)
|
||||
## mcl_worlds.y_to_layer(y)
|
||||
This function is used to calculate the minetest y layer and dimension of the given <y> minecraft layer.
|
||||
Mainly used for ore generation.
|
||||
Takes an Y coordinate as input and returns:
|
||||
|
|
|
@ -38,18 +38,32 @@ function image:encode_header()
|
|||
self.data = self.data
|
||||
.. string.char(0) -- image id
|
||||
.. string.char(0) -- color map type
|
||||
.. string.char(2) -- image type (uncompressed true-color image = 2)
|
||||
.. string.char(10) -- image type (RLE RGB = 10)
|
||||
self:encode_colormap_spec() -- color map specification
|
||||
self:encode_image_spec() -- image specification
|
||||
end
|
||||
|
||||
function image:encode_data()
|
||||
local current_pixel = ''
|
||||
local previous_pixel = ''
|
||||
local count = 1
|
||||
local packets = {}
|
||||
local rle_packet = ''
|
||||
for _, row in ipairs(self.pixels) do
|
||||
for _, pixel in ipairs(row) do
|
||||
self.data = self.data
|
||||
.. string.char(pixel[3], pixel[2], pixel[1])
|
||||
current_pixel = string.char(pixel[3], pixel[2], pixel[1])
|
||||
if current_pixel ~= previous_pixel or count == 128 then
|
||||
packets[#packets +1] = rle_packet
|
||||
count = 1
|
||||
previous_pixel = current_pixel
|
||||
else
|
||||
count = count + 1
|
||||
end
|
||||
rle_packet = string.char(128 + count - 1) .. current_pixel
|
||||
end
|
||||
end
|
||||
packets[#packets +1] = rle_packet
|
||||
self.data = self.data .. table.concat(packets)
|
||||
end
|
||||
|
||||
function image:encode_footer()
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
--Dripping Water Mod
|
||||
--by kddekadenz
|
||||
|
||||
local math = math
|
||||
|
||||
-- License of code, textures & sounds: CC0
|
||||
|
||||
--Drop entities
|
||||
|
||||
--water
|
||||
|
||||
local water_tex = "default_water_source_animated.png^[verticalframe:16:0"
|
||||
minetest.register_entity("drippingwater:drop_water", {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.025,-0.05,-0.025,0.025,-0.01,0.025},
|
||||
pointable = false,
|
||||
visual = "cube",
|
||||
visual_size = {x=0.05, y=0.1},
|
||||
textures = {water_tex, water_tex, water_tex, water_tex, water_tex, water_tex},
|
||||
spritediv = {x=1, y=1},
|
||||
initial_sprite_basepos = {x=0, y=0},
|
||||
static_save = false,
|
||||
on_activate = function(self, staticdata)
|
||||
self.object:set_sprite({x=0,y=0}, 1, 1, true)
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1,222)
|
||||
local ownpos = self.object:get_pos()
|
||||
if k==1 then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y +0.5, z=ownpos.z}).name == "air" then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y -0.5, z=ownpos.z}).name ~= "air" then
|
||||
self.object:remove()
|
||||
minetest.sound_play({name="drippingwater_drip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
--lava
|
||||
|
||||
local lava_tex = "default_lava_source_animated.png^[verticalframe:16:0"
|
||||
minetest.register_entity("drippingwater:drop_lava", {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.025,-0.05,-0.025,0.025,-0.01,0.025},
|
||||
glow = math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3),
|
||||
pointable = false,
|
||||
visual = "cube",
|
||||
visual_size = {x=0.05, y=0.1},
|
||||
textures = {lava_tex, lava_tex, lava_tex, lava_tex, lava_tex, lava_tex},
|
||||
spritediv = {x=1, y=1},
|
||||
initial_sprite_basepos = {x=0, y=0},
|
||||
static_save = false,
|
||||
on_activate = function(self, staticdata)
|
||||
self.object:set_sprite({x=0,y=0}, 1, 0, true)
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1,222)
|
||||
local ownpos = self.object:get_pos()
|
||||
if k == 1 then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y +0.5, z=ownpos.z}).name == "air" then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y -0.5, z=ownpos.z}).name ~= "air" then
|
||||
self.object:remove()
|
||||
minetest.sound_play({name="drippingwater_lavadrip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
|
||||
--Create drop
|
||||
|
||||
minetest.register_abm({
|
||||
label = "Create water drops",
|
||||
nodenames = {"group:opaque", "group:leaves"},
|
||||
neighbors = {"group:water"},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name, "water") ~= 0
|
||||
and minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
|
||||
local i = math.random(-45,45) / 100
|
||||
minetest.add_entity({x=pos.x + i, y=pos.y - 0.501, z=pos.z + i}, "drippingwater:drop_water")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--Create lava drop
|
||||
|
||||
minetest.register_abm({
|
||||
label = "Create lava drops",
|
||||
nodenames = {"group:opaque"},
|
||||
neighbors = {"group:lava"},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name, "lava") ~= 0
|
||||
and minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
|
||||
local i = math.random(-45,45) / 100
|
||||
minetest.add_entity({x=pos.x + i, y=pos.y - 0.501, z=pos.z + i}, "drippingwater:drop_lava")
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -84,7 +84,7 @@ local function attach_object(self, obj)
|
|||
end
|
||||
end, name)
|
||||
obj:set_look_horizontal(yaw)
|
||||
mcl_tmp_message.message(obj, S("Sneak to dismount"))
|
||||
mcl_title.set(obj, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
else
|
||||
obj:get_luaentity()._old_visual_size = visual_size
|
||||
end
|
||||
|
@ -115,7 +115,7 @@ local boat = {
|
|||
collisionbox = {-0.5, -0.35, -0.5, 0.5, 0.3, 0.5},
|
||||
visual = "mesh",
|
||||
mesh = "mcl_boats_boat.b3d",
|
||||
textures = {"mcl_boats_texture_oak_boat.png"},
|
||||
textures = {"mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png"},
|
||||
visual_size = boat_visual_size,
|
||||
hp_max = boat_max_hp,
|
||||
damage_texture_modifier = "^[colorize:white:0",
|
||||
|
@ -148,6 +148,11 @@ function boat.on_activate(self, staticdata, dtime_s)
|
|||
self._v = data.v
|
||||
self._last_v = self._v
|
||||
self._itemstring = data.itemstring
|
||||
|
||||
while #data.textures < 5 do
|
||||
table.insert(data.textures, data.textures[1])
|
||||
end
|
||||
|
||||
self.object:set_properties({textures = data.textures})
|
||||
end
|
||||
end
|
||||
|
@ -337,7 +342,8 @@ function boat.on_step(self, dtime, moveresult)
|
|||
self.object:get_velocity().y)
|
||||
else
|
||||
p.y = p.y + 1
|
||||
if is_water(p) then
|
||||
local is_obsidian_boat = self.object:get_luaentity()._itemstring == "mcl_boats:boat_obsidian"
|
||||
if is_water(p) or is_obsidian_boat then
|
||||
-- Inside water: Slowly sink
|
||||
local y = self.object:get_velocity().y
|
||||
y = y - 0.01
|
||||
|
@ -377,13 +383,13 @@ end
|
|||
-- Register one entity for all boat types
|
||||
minetest.register_entity("mcl_boats:boat", boat)
|
||||
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat") }
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat") }
|
||||
local craftstuffs = {}
|
||||
if minetest.get_modpath("mcl_core") then
|
||||
craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood" }
|
||||
craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian" }
|
||||
end
|
||||
local images = { "oak", "spruce", "birch", "jungle", "acacia", "dark_oak" }
|
||||
local images = { "oak", "spruce", "birch", "jungle", "acacia", "dark_oak", "obsidian" }
|
||||
|
||||
for b=1, #boat_ids do
|
||||
local itemstring = "mcl_boats:"..boat_ids[b]
|
||||
|
@ -434,8 +440,9 @@ for b=1, #boat_ids do
|
|||
pos = vector.add(pos, vector.multiply(dir, boat_y_offset_ground))
|
||||
end
|
||||
local boat = minetest.add_entity(pos, "mcl_boats:boat")
|
||||
local texture = "mcl_boats_texture_"..images[b].."_boat.png"
|
||||
boat:get_luaentity()._itemstring = itemstring
|
||||
boat:set_properties({textures = { "mcl_boats_texture_"..images[b].."_boat.png" }})
|
||||
boat:set_properties({textures = { texture, texture, texture, texture, texture }})
|
||||
boat:set_yaw(placer:get_look_horizontal())
|
||||
if not minetest.is_creative_enabled(placer:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
|
|
|
@ -6,6 +6,7 @@ Boats are used to travel on the surface of water.=Les bateaux sont utilisés pou
|
|||
Dark Oak Boat=Bateau en Chêne Noir
|
||||
Jungle Boat=Bateau en Acajou
|
||||
Oak Boat=Bateau en Chêne
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item.=Faites un clic droit sur une source d'eau pour placer le bateau. Faites un clic droit sur le bateau pour y entrer. Utilisez [Gauche] et [Droite] pour diriger, [Avant] pour accélérer et [Arrière] pour ralentir ou reculer. Cliquez de nouveau avec le bouton droit sur le bateau pour le quitter, frappez le bateau pour le faire tomber en tant qu'objet.
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=Faites un clic droit sur une source d'eau pour placer le bateau. Faites un clic droit sur le bateau pour y entrer. Utilisez [Gauche] et [Droite] pour diriger, [Avant] pour accélérer et [Arrière] pour ralentir ou reculer. Utilisez [Sneak] pour le quitter, frappez le bateau pour le faire tomber en tant qu'objet.
|
||||
Spruce Boat=Bateau en Sapin
|
||||
Water vehicle=Véhicule aquatique
|
||||
Sneak to dismount=
|
|
@ -1,7 +1,7 @@
|
|||
name = mcl_boats
|
||||
author = PilzAdam
|
||||
description = Adds drivable boats.
|
||||
depends = mcl_player, flowlib
|
||||
depends = mcl_player, flowlib, mcl_title
|
||||
optional_depends = mcl_core, doc_identifier
|
||||
|
||||
|
||||
|
|
After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 535 B |
|
@ -67,14 +67,9 @@ function mcl_burning.set_on_fire(obj, burn_time)
|
|||
end
|
||||
|
||||
if not storage.burn_time or burn_time >= storage.burn_time then
|
||||
if obj:is_player() and not storage.fire_hud_id then
|
||||
storage.fire_hud_id = obj:hud_add({
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.5, y = 0.5},
|
||||
scale = {x = -100, y = -100},
|
||||
text = "mcl_burning_entity_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. 1,
|
||||
z_index = 1000,
|
||||
})
|
||||
if obj:is_player() then
|
||||
mcl_burning.channels[obj]:send_all(tostring(mcl_burning.animation_frames))
|
||||
mcl_burning.channels[obj]:send_all("start")
|
||||
end
|
||||
storage.burn_time = burn_time
|
||||
storage.fire_damage_timer = 0
|
||||
|
@ -95,7 +90,6 @@ function mcl_burning.set_on_fire(obj, burn_time)
|
|||
fire_entity:set_properties({visual_size = size})
|
||||
fire_entity:set_attach(obj, "", offset, {x = 0, y = 0, z = 0})
|
||||
local fire_luaentity = fire_entity:get_luaentity()
|
||||
fire_luaentity:update_frame(obj, storage)
|
||||
|
||||
for _, other in pairs(minetest.get_objects_inside_radius(fire_entity:get_pos(), 0)) do
|
||||
local other_luaentity = other:get_luaentity()
|
||||
|
@ -111,9 +105,7 @@ function mcl_burning.extinguish(obj)
|
|||
if mcl_burning.is_burning(obj) then
|
||||
local storage = mcl_burning.get_storage(obj)
|
||||
if obj:is_player() then
|
||||
if storage.fire_hud_id then
|
||||
obj:hud_remove(storage.fire_hud_id)
|
||||
end
|
||||
mcl_burning.channels[obj]:send_all("stop")
|
||||
mcl_burning.storage[obj] = {}
|
||||
else
|
||||
storage.burn_time = nil
|
||||
|
|
|
@ -7,6 +7,7 @@ local get_item_group = minetest.get_item_group
|
|||
|
||||
mcl_burning = {
|
||||
storage = {},
|
||||
channels = {},
|
||||
animation_frames = tonumber(minetest.settings:get("fire_animation_frames")) or 8
|
||||
}
|
||||
|
||||
|
@ -43,23 +44,22 @@ minetest.register_on_respawnplayer(function(player)
|
|||
mcl_burning.extinguish(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local storage
|
||||
|
||||
local burn_data = player:get_meta():get_string("mcl_burning:data")
|
||||
if burn_data == "" then
|
||||
storage = {}
|
||||
else
|
||||
storage = minetest.deserialize(burn_data)
|
||||
function mcl_burning.init_player(player)
|
||||
local meta = player:get_meta()
|
||||
-- NOTE: mcl_burning:data may be "return nil" (which deserialize into nil) for reasons unknown.
|
||||
if meta:get_string("mcl_burning:data"):find("return nil", 1, true) then
|
||||
minetest.log("warning", "[mcl_burning] 'mcl_burning:data' player meta field is invalid! Please report this bug")
|
||||
end
|
||||
mcl_burning.storage[player] = meta:contains("mcl_burning:data") and minetest.deserialize(meta:get_string("mcl_burning:data")) or {}
|
||||
mcl_burning.channels[player] = minetest.mod_channel_join("mcl_burning:" .. player:get_player_name())
|
||||
end
|
||||
|
||||
mcl_burning.storage[player] = storage
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
mcl_burning.init_player(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local storage = mcl_burning.storage[player]
|
||||
storage.fire_hud_id = nil
|
||||
player:get_meta():set_string("mcl_burning:data", minetest.serialize(storage))
|
||||
player:get_meta():set_string("mcl_burning:data", minetest.serialize(mcl_burning.storage[player]))
|
||||
mcl_burning.storage[player] = nil
|
||||
end)
|
||||
|
||||
|
@ -68,27 +68,28 @@ minetest.register_entity("mcl_burning:fire", {
|
|||
initial_properties = {
|
||||
physical = false,
|
||||
collisionbox = {0, 0, 0, 0, 0, 0},
|
||||
visual = "cube",
|
||||
visual = "upright_sprite",
|
||||
textures = {
|
||||
name = "mcl_burning_entity_flame_animated.png",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 1.0,
|
||||
},
|
||||
},
|
||||
spritediv = {x = 1, y = mcl_burning.animation_frames},
|
||||
pointable = false,
|
||||
glow = -1,
|
||||
backface_culling = false,
|
||||
},
|
||||
animation_frame = 0,
|
||||
animation_timer = 0,
|
||||
on_step = function(self, dtime)
|
||||
local parent, storage = self:sanity_check()
|
||||
|
||||
if parent then
|
||||
self.animation_timer = self.animation_timer + dtime
|
||||
if self.animation_timer >= 0.1 then
|
||||
self.animation_timer = 0
|
||||
self.animation_frame = self.animation_frame + 1
|
||||
if self.animation_frame > mcl_burning.animation_frames - 1 then
|
||||
self.animation_frame = 0
|
||||
end
|
||||
self:update_frame(parent, storage)
|
||||
end
|
||||
else
|
||||
on_activate = function(self)
|
||||
self.object:set_sprite({x = 0, y = 0}, mcl_burning.animation_frames, 1.0 / mcl_burning.animation_frames)
|
||||
end,
|
||||
on_step = function(self)
|
||||
if not self:sanity_check() then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
|
@ -96,23 +97,15 @@ minetest.register_entity("mcl_burning:fire", {
|
|||
local parent = self.object:get_attach()
|
||||
|
||||
if not parent then
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
local storage = mcl_burning.get_storage(parent)
|
||||
|
||||
if not storage or not storage.burn_time then
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
return parent, storage
|
||||
end,
|
||||
update_frame = function(self, parent, storage)
|
||||
local frame_overlay = "^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. self.animation_frame
|
||||
local fire_texture = "mcl_burning_entity_flame_animated.png" .. frame_overlay
|
||||
self.object:set_properties({textures = {"blank.png", "blank.png", fire_texture, fire_texture, fire_texture, fire_texture}})
|
||||
if parent:is_player() then
|
||||
parent:hud_change(storage.fire_hud_id, "text", "mcl_burning_hud_flame_animated.png" .. frame_overlay)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
-- Dripping Water Mod
|
||||
-- by kddekadenz
|
||||
|
||||
local math = math
|
||||
|
||||
-- License of code, textures & sounds: CC0
|
||||
|
||||
local function register_drop(liquid, glow, sound, nodes)
|
||||
minetest.register_entity("mcl_dripping:drop_" .. liquid, {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.01, 0.01, -0.01, 0.01, 0.01, 0.01},
|
||||
glow = glow,
|
||||
pointable = false,
|
||||
visual = "sprite",
|
||||
visual_size = {x = 0.1, y = 0.1},
|
||||
textures = {""},
|
||||
spritediv = {x = 1, y = 1},
|
||||
initial_sprite_basepos = {x = 0, y = 0},
|
||||
static_save = false,
|
||||
_dropped = false,
|
||||
on_activate = function(self)
|
||||
self.object:set_properties({
|
||||
textures = {"[combine:2x2:" .. -math.random(1, 16) .. "," .. -math.random(1, 16) .. "=default_" .. liquid .. "_source_animated.png"}
|
||||
})
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1, 222)
|
||||
local ownpos = self.object:get_pos()
|
||||
if k == 1 then
|
||||
self.object:set_acceleration(vector.new(0, -5, 0))
|
||||
end
|
||||
if minetest.get_node(vector.offset(ownpos, 0, 0.5, 0)).name == "air" then
|
||||
self.object:set_acceleration(vector.new(0, -5, 0))
|
||||
end
|
||||
if minetest.get_node(vector.offset(ownpos, 0, -0.1, 0)).name ~= "air" then
|
||||
local ent = self.object:get_luaentity()
|
||||
if not ent._dropped then
|
||||
ent._dropped = true
|
||||
minetest.sound_play({name = "drippingwater_" .. sound .. "drip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
if k < 3 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
minetest.register_abm({
|
||||
label = "Create drops",
|
||||
nodenames = nodes,
|
||||
neighbors = {"group:" .. liquid},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node(vector.offset(pos, 0, 1, 0)).name, liquid) ~= 0
|
||||
and minetest.get_node(vector.offset(pos, 0, -1, 0)).name == "air" then
|
||||
local x, z = math.random(-45, 45) / 100, math.random(-45, 45) / 100
|
||||
minetest.add_entity(vector.offset(pos, x, -0.520, z), "mcl_dripping:drop_" .. liquid)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
register_drop("water", 1, "", {"group:opaque", "group:leaves"})
|
||||
register_drop("lava", math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3), "lava", {"group:opaque"})
|
|
@ -1,4 +1,4 @@
|
|||
name = drippingwater
|
||||
name = mcl_dripping
|
||||
author = kddekadenz
|
||||
description = Drops are generated rarely under solid nodes
|
||||
depends = mcl_core
|
|
@ -1,12 +1,12 @@
|
|||
Dripping Water Mod
|
||||
Dripping Mod
|
||||
by kddekadenz
|
||||
|
||||
modified for MineClone 2 by Wuzzy
|
||||
modified for MineClone 2 by Wuzzy and NO11
|
||||
|
||||
|
||||
Installing instructions:
|
||||
|
||||
1. Copy the drippingwater mod folder into games/gamemode/mods
|
||||
1. Copy the mcl_dripping mod folder into games/gamemode/mods
|
||||
|
||||
2. Start game and enjoy :)
|
||||
|
|
@ -290,10 +290,10 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
end
|
||||
end
|
||||
|
||||
if digger and mcl_experience.throw_experience and not silk_touch_drop then
|
||||
if digger and mcl_experience.throw_xp and not silk_touch_drop then
|
||||
local experience_amount = minetest.get_item_group(dug_node.name,"xp")
|
||||
if experience_amount > 0 then
|
||||
mcl_experience.throw_experience(pos, experience_amount)
|
||||
mcl_experience.throw_xp(pos, experience_amount)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -480,7 +480,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
return minetest.serialize({
|
||||
local data = minetest.serialize({
|
||||
itemstring = self.itemstring,
|
||||
always_collect = self.always_collect,
|
||||
age = self.age,
|
||||
|
@ -488,6 +488,39 @@ minetest.register_entity(":__builtin:item", {
|
|||
_flowing = self._flowing,
|
||||
_removed = self._removed,
|
||||
})
|
||||
-- sfan5 guessed that the biggest serializable item
|
||||
-- entity would have a size of 65530 bytes. This has
|
||||
-- been experimentally verified to be still too large.
|
||||
--
|
||||
-- anon5 has calculated that the biggest serializable
|
||||
-- item entity has a size of exactly 65487 bytes:
|
||||
--
|
||||
-- 1. serializeString16 can handle max. 65535 bytes.
|
||||
-- 2. The following engine metadata is always saved:
|
||||
-- • 1 byte (version)
|
||||
-- • 2 byte (length prefix)
|
||||
-- • 14 byte “__builtin:item”
|
||||
-- • 4 byte (length prefix)
|
||||
-- • 2 byte (health)
|
||||
-- • 3 × 4 byte = 12 byte (position)
|
||||
-- • 4 byte (yaw)
|
||||
-- • 1 byte (version 2)
|
||||
-- • 2 × 4 byte = 8 byte (pitch and roll)
|
||||
-- 3. This leaves 65487 bytes for the serialization.
|
||||
if #data > 65487 then -- would crash the engine
|
||||
local stack = ItemStack(self.itemstring)
|
||||
stack:get_meta():from_table(nil)
|
||||
self.itemstring = stack:to_string()
|
||||
minetest.log(
|
||||
"warning",
|
||||
"Overlong item entity metadata removed: “" ..
|
||||
self.itemstring ..
|
||||
"” had serialized length of " ..
|
||||
#data
|
||||
)
|
||||
return self:get_staticdata()
|
||||
end
|
||||
return data
|
||||
end,
|
||||
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
|
@ -575,7 +608,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
return true
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
on_step = function(self, dtime, moveresult)
|
||||
if self._removed then
|
||||
self.object:set_properties({
|
||||
physical = false
|
||||
|
@ -642,6 +675,18 @@ minetest.register_entity(":__builtin:item", {
|
|||
end
|
||||
end
|
||||
|
||||
-- Destroy item when it collides with a cactus
|
||||
if moveresult and moveresult.collides then
|
||||
for _, collision in pairs(moveresult.collisions) do
|
||||
local pos = collision.node_pos
|
||||
if collision.type == "node" and minetest.get_node(pos).name == "mcl_core:cactus" then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Push item out when stuck inside solid opaque node
|
||||
if def and def.walkable and def.groups and def.groups.opaque == 1 then
|
||||
local shootdir
|
||||
|
|
|
@ -198,7 +198,20 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
else
|
||||
self._last_float_check = self._last_float_check + dtime
|
||||
end
|
||||
local pos, rou_pos, node
|
||||
|
||||
local pos, rou_pos, node = self.object:get_pos()
|
||||
local r = 0.6
|
||||
for _, node_pos in pairs({{r, 0}, {0, r}, {-r, 0}, {0, -r}}) do
|
||||
if minetest.get_node(vector.offset(pos, node_pos[1], 0, node_pos[2])).name == "mcl_core:cactus" then
|
||||
detach_driver(self)
|
||||
for d = 1, #drop do
|
||||
minetest.add_item(pos, drop[d])
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Drop minecart if it isn't on a rail anymore
|
||||
if self._last_float_check >= mcl_minecarts.check_float_time then
|
||||
pos = self.object:get_pos()
|
||||
|
@ -646,7 +659,7 @@ register_minecart(
|
|||
if player then
|
||||
mcl_player.player_set_animation(player, "sit" , 30)
|
||||
player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
|
||||
mcl_tmp_message.message(clicker, S("Sneak to dismount"))
|
||||
mcl_title.set(clicker, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
end
|
||||
end, name)
|
||||
end
|
||||
|
|
|
@ -33,3 +33,4 @@ Activates minecarts when powered=Active les wagonnets lorsqu'il est alimenté
|
|||
Emits redstone power when a minecart is detected=Émet de l'énergie redstone lorsqu'un wagonnet est détecté
|
||||
Vehicle for fast travel on rails=Véhicule pour voyager rapidement sur rails
|
||||
Can be ignited by tools or powered activator rail=Peut être allumé par des outils ou un rail d'activation motorisé
|
||||
Sneak to dismount=
|
|
@ -1,6 +1,6 @@
|
|||
name = mcl_minecarts
|
||||
author = Krock
|
||||
description = Minecarts are vehicles to move players quickly on rails.
|
||||
depends = mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons
|
||||
optional_depends = doc_identifier, mcl_wip
|
||||
|
||||
|
|
|
@ -122,7 +122,10 @@ mobs.death_logic = function(self, dtime)
|
|||
if self.death_animation_timer >= 1.25 then
|
||||
item_drop(self,false,1)
|
||||
mobs.death_effect(self)
|
||||
mcl_experience.throw_experience(self.object:get_pos(), math_random(self.xp_min, self.xp_max))
|
||||
mcl_experience.throw_xp(self.object:get_pos(), math_random(self.xp_min, self.xp_max))
|
||||
if self.on_die then
|
||||
self.on_die(self, self.object:get_pos())
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
},
|
||||
makes_footstep_sound = false,
|
||||
walk_velocity = 1.05,
|
||||
run_velocity = 3.25,
|
||||
run_velocity = 2.1,
|
||||
runaway_from = { "mobs_mc:ocelot", "mobs_mc:cat" },
|
||||
attack_type = "explode",
|
||||
eye_height = 1.25,
|
||||
|
@ -47,8 +47,8 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
--explosion_radius = 3,
|
||||
--explosion_damage_radius = 6,
|
||||
--explosiontimer_reset_radius = 6,
|
||||
reach = 1.5,
|
||||
defuse_reach = 4,
|
||||
reach = 3,
|
||||
defuse_reach = 5.2,
|
||||
explosion_timer = 0.3,
|
||||
allow_fuse_reset = true,
|
||||
stop_to_explode = true,
|
||||
|
@ -95,7 +95,8 @@ mobs:register_mob("mobs_mc:creeper", {
|
|||
if self._forced_explosion_countdown_timer then
|
||||
self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime
|
||||
if self._forced_explosion_countdown_timer <= 0 then
|
||||
mobs:boom(self, mcl_util.get_object_center(self.object), self.explosion_strength)
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { griefing = mobs_griefing, drop_chance = 1.0}, self.object)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
@ -151,6 +152,7 @@ mobs:register_mob("mobs_mc:creeper_charged", {
|
|||
description = S("Charged Creeper"),
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
hostile = true,
|
||||
hp_min = 20,
|
||||
hp_max = 20,
|
||||
xp_min = 5,
|
||||
|
@ -186,8 +188,8 @@ mobs:register_mob("mobs_mc:creeper_charged", {
|
|||
--explosion_radius = 3,
|
||||
--explosion_damage_radius = 6,
|
||||
--explosiontimer_reset_radius = 3,
|
||||
reach = 1.5,
|
||||
defuse_reach = 4,
|
||||
reach = 3,
|
||||
defuse_reach = 5.2,
|
||||
explosion_timer = 0.3,
|
||||
allow_fuse_reset = true,
|
||||
stop_to_explode = true,
|
||||
|
@ -219,7 +221,8 @@ mobs:register_mob("mobs_mc:creeper_charged", {
|
|||
if self._forced_explosion_countdown_timer then
|
||||
self._forced_explosion_countdown_timer = self._forced_explosion_countdown_timer - dtime
|
||||
if self._forced_explosion_countdown_timer <= 0 then
|
||||
mobs:boom(self, mcl_util.get_object_center(self.object), self.explosion_strength)
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { griefing = mobs_griefing, drop_chance = 1.0}, self.object)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
|
|
@ -103,7 +103,7 @@ mobs:register_mob("mobs_mc:enderdragon", {
|
|||
mcl_portals.spawn_gateway_portal()
|
||||
mcl_structures.call_struct(self._portal_pos, "end_exit_portal_open")
|
||||
if self._initial then
|
||||
mcl_experience.throw_experience(pos, 11500) -- 500 + 11500 = 12000
|
||||
mcl_experience.throw_xp(pos, 11500) -- 500 + 11500 = 12000
|
||||
minetest.set_node(vector.add(self._portal_pos, vector.new(3, 5, 3)), {name = mobs_mc.items.dragon_egg})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,7 @@ Pig=
|
|||
Polar Bear=
|
||||
Rabbit=
|
||||
Killer Bunny=
|
||||
The Killer Bunny=
|
||||
Sheep=
|
||||
Shulker=
|
||||
Silverfish=
|
||||
|
|
|
@ -233,4 +233,4 @@ mobs:spawn(spawn_grass)
|
|||
mobs:register_egg("mobs_mc:rabbit", S("Rabbit"), "mobs_mc_spawn_icon_rabbit.png", 0)
|
||||
|
||||
-- Note: This spawn egg does not exist in Minecraft
|
||||
mobs:register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "mobs_mc_spawn_icon_rabbit.png^[colorize:#FF0000:192", 0) -- TODO: Update inventory image
|
||||
mobs:register_egg("mobs_mc:killer_bunny", S("Killer Bunny"), "mobs_mc_spawn_icon_rabbit_caerbannog.png", 0)
|
||||
|
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,31 @@
|
|||
# lightning
|
||||
Lightning mod for MineClone2 with the following API:
|
||||
|
||||
## lightning.register_on_strike(function(pos, pos2, objects))
|
||||
Custom function called when a lightning strikes.
|
||||
|
||||
* `pos`: impact position
|
||||
* `pos2`: rounded node position where fire is placed
|
||||
* `objects`: table with ObjectRefs of all objects within a radius of 3.5 around pos2
|
||||
|
||||
## lightning.strike(pos)
|
||||
Let a lightning strike.
|
||||
|
||||
* `pos`: optional, if not given a random pos will be chosen
|
||||
* `returns`: bool - success if a strike happened
|
||||
|
||||
|
||||
### Examples:
|
||||
|
||||
```
|
||||
lightning.register_on_strike(function(pos, pos2, objects)
|
||||
for _, obj in pairs(objects) do
|
||||
obj:remove()
|
||||
end
|
||||
minetest.add_entity(pos, "mobs_mc:sheep")
|
||||
end)
|
||||
|
||||
minetest.register_on_respawnplayer(function(player)
|
||||
lightning.strike(player:get_pos())
|
||||
end)
|
||||
```
|
|
@ -31,6 +31,7 @@ lightning = {
|
|||
size = 100,
|
||||
-- disable this to stop lightning mod from striking
|
||||
auto = true,
|
||||
on_strike_functions = {},
|
||||
}
|
||||
|
||||
local rng = PcgRandom(32321123312123)
|
||||
|
@ -54,6 +55,18 @@ end
|
|||
|
||||
minetest.register_globalstep(revertsky)
|
||||
|
||||
-- lightning strike API
|
||||
|
||||
-- See API.md
|
||||
--[[
|
||||
lightning.register_on_strike(function(pos, pos2, objects)
|
||||
-- code
|
||||
end)
|
||||
]]
|
||||
function lightning.register_on_strike(func)
|
||||
table.insert(lightning.on_strike_functions, func)
|
||||
end
|
||||
|
||||
-- select a random strike point, midpoint
|
||||
local function choose_pos(pos)
|
||||
if not pos then
|
||||
|
@ -94,7 +107,6 @@ local function choose_pos(pos)
|
|||
return pos, pos2
|
||||
end
|
||||
|
||||
-- lightning strike API
|
||||
-- * pos: optional, if not given a random pos will be chosen
|
||||
-- * returns: bool - success if a strike happened
|
||||
function lightning.strike(pos)
|
||||
|
@ -108,21 +120,28 @@ function lightning.strike(pos)
|
|||
if not pos then
|
||||
return false
|
||||
end
|
||||
local objects = get_objects_inside_radius(pos2, 3.5)
|
||||
if lightning.on_strike_functions then
|
||||
for _, func in pairs(lightning.on_strike_functions) do
|
||||
func(pos, pos2, objects)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
lightning.register_on_strike(function(pos, pos2, objects)
|
||||
local particle_pos = vector.offset(pos2, 0, (lightning.size / 2) + 0.5, 0)
|
||||
local particle_size = lightning.size * 10
|
||||
local time = 0.2
|
||||
add_particlespawner({
|
||||
amount = 1,
|
||||
time = 0.2,
|
||||
time = time,
|
||||
-- make it hit the top of a block exactly with the bottom
|
||||
minpos = {x = pos2.x, y = pos2.y + (lightning.size / 2) + 1/2, z = pos2.z },
|
||||
maxpos = {x = pos2.x, y = pos2.y + (lightning.size / 2) + 1/2, z = pos2.z },
|
||||
minvel = {x = 0, y = 0, z = 0},
|
||||
maxvel = {x = 0, y = 0, z = 0},
|
||||
minacc = {x = 0, y = 0, z = 0},
|
||||
maxacc = {x = 0, y = 0, z = 0},
|
||||
minexptime = 0.2,
|
||||
maxexptime = 0.2,
|
||||
minsize = lightning.size * 10,
|
||||
maxsize = lightning.size * 10,
|
||||
minpos = particle_pos,
|
||||
maxpos = particle_pos,
|
||||
minexptime = time,
|
||||
maxexptime = time,
|
||||
minsize = particle_size,
|
||||
maxsize = particle_size,
|
||||
collisiondetection = true,
|
||||
vertical = true,
|
||||
-- to make it appear hitting the node that will get set on fire, make sure
|
||||
|
@ -135,18 +154,14 @@ function lightning.strike(pos)
|
|||
sound_play({ name = "lightning_thunder", gain = 10 }, { pos = pos, max_hear_distance = 500 }, true)
|
||||
|
||||
-- damage nearby objects, transform mobs
|
||||
-- TODO: use an API insteed of hardcoding this behaviour
|
||||
local objs = get_objects_inside_radius(pos2, 3.5)
|
||||
for o=1, #objs do
|
||||
local obj = objs[o]
|
||||
for _, obj in pairs(objects) do
|
||||
local lua = obj:get_luaentity()
|
||||
-- pig → zombie pigman (no damage)
|
||||
if lua and lua._on_strike then
|
||||
lua._on_strike(lua, pos, pos2, objects)
|
||||
end
|
||||
-- remove this when mob API is done
|
||||
if lua and lua.name == "mobs_mc:pig" then
|
||||
local rot = obj:get_yaw()
|
||||
obj:remove()
|
||||
obj = add_entity(pos2, "mobs_mc:pigman")
|
||||
obj:set_yaw(rot)
|
||||
-- mooshroom: toggle color red/brown (no damage)
|
||||
mcl_util.replace_mob(obj, "mobs_mc:pigman")
|
||||
elseif lua and lua.name == "mobs_mc:mooshroom" then
|
||||
if lua.base_texture[1] == "mobs_mc_mooshroom.png" then
|
||||
lua.base_texture = { "mobs_mc_mooshroom_brown.png", "mobs_mc_mushroom_brown.png" }
|
||||
|
@ -154,23 +169,10 @@ function lightning.strike(pos)
|
|||
lua.base_texture = { "mobs_mc_mooshroom.png", "mobs_mc_mushroom_red.png" }
|
||||
end
|
||||
obj:set_properties({ textures = lua.base_texture })
|
||||
-- villager → witch (no damage)
|
||||
--elseif lua and lua.name == "mobs_mc:villager" then
|
||||
-- Witches are incomplete, this code is unused
|
||||
-- TODO: Enable this code when witches are working.
|
||||
--[[
|
||||
local rot = obj:get_yaw()
|
||||
obj:remove()
|
||||
obj = minetest.add_entity(pos2, "mobs_mc:witch")
|
||||
obj:set_yaw(rot)
|
||||
]]
|
||||
-- charged creeper
|
||||
elseif lua and lua.name == "mobs_mc:villager" then
|
||||
mcl_util.replace_mob(obj, "mobs_mc:witch")
|
||||
elseif lua and lua.name == "mobs_mc:creeper" then
|
||||
local rot = obj:get_yaw()
|
||||
obj:remove()
|
||||
obj = add_entity(pos2, "mobs_mc:creeper_charged")
|
||||
obj:set_yaw(rot)
|
||||
-- Other objects: Just damage
|
||||
mcl_util.replace_mob(obj, "mobs_mc:creeper_charged")
|
||||
else
|
||||
mcl_util.deal_damage(obj, 5, { type = "lightning_bolt" })
|
||||
end
|
||||
|
@ -213,7 +215,9 @@ function lightning.strike(pos)
|
|||
posadd = { x=math.cos(angle),y=0,z=math.sin(angle) }
|
||||
posadd = vector.normalize(posadd)
|
||||
local mob = add_entity(vector.add(pos2, posadd), "mobs_mc:skeleton")
|
||||
if mob then
|
||||
mob:set_yaw(angle-math.pi/2)
|
||||
end
|
||||
angle = angle + (math.pi*2) / 3
|
||||
end
|
||||
|
||||
|
@ -223,8 +227,7 @@ function lightning.strike(pos)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
-- if other mods disable auto lightning during initialization, don't trigger the first lightning.
|
||||
after(5, function(dtime)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Using it as fuel turns it into: @1.=L'utiliser comme combustible le transforme en : @1.
|
||||
@1 seconds=@1 secondes
|
||||
# Item count times item name
|
||||
%@1×@2=%@1×@
|
||||
@1×@2=@1×@
|
||||
# Itemname (25%)
|
||||
@1 (@2%)=@1 (@2%)
|
||||
# Itemname (<0.5%)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# mcl_item_id
|
||||
Show the item ID of an item in the description.
|
||||
With this API, you can register a different name space than "mineclone" for your mod.
|
||||
|
||||
## mcl_item_id.set_mod_namespace(modname, namespace)
|
||||
Set a name space for all items in a mod.
|
||||
|
||||
* param1: the modname
|
||||
* param2: (optional) string of the desired name space, if nil, it is the name of the mod
|
||||
|
||||
## mcl_item_id.get_mod_namespace(modname)
|
||||
Get the name space of a mod registered with mcl_item_id.set_mod_namespace(modname, namespace).
|
||||
|
||||
* param1: the modname
|
||||
|
||||
### Examples:
|
||||
|
||||
The name of the mod is "mod" which registered an item called "mod:itemname".
|
||||
|
||||
* mcl_item_id.set_mod_namespace("mod", "mymod") will show "mymod:itemname" in the description of "mod:itemname"
|
||||
* mcl_item_id.set_mod_namespace(minetest.get_current_modname()) will show "mod:itemname" in the description of "mod:itemname"
|
||||
* mcl_item_id.get_mod_namespace(minetest.get_current_modname()) will return "mod"
|
||||
|
||||
(If no namespace is set by a mod, mcl_item_id.get_mod_namespace(minetest.get_current_modname()) will return "mineclone")
|
|
@ -0,0 +1,62 @@
|
|||
mcl_item_id = {
|
||||
mod_namespaces = {},
|
||||
}
|
||||
|
||||
local game = "mineclone"
|
||||
|
||||
function mcl_item_id.set_mod_namespace(modname, namespace)
|
||||
local namespace = namespace or modname
|
||||
mcl_item_id.mod_namespaces[modname] = namespace
|
||||
end
|
||||
|
||||
function mcl_item_id.get_mod_namespace(modname)
|
||||
local namespace = mcl_item_id.mod_namespaces[modname]
|
||||
if namespace then
|
||||
return namespace
|
||||
else
|
||||
return game
|
||||
end
|
||||
end
|
||||
|
||||
local same_id = {
|
||||
enchanting = { "table" },
|
||||
experience = { "bottle" },
|
||||
heads = { "skeleton", "zombie", "creeper", "wither_skeleton" },
|
||||
mobitems = { "rabbit", "chicken" },
|
||||
walls = {
|
||||
"andesite", "brick", "cobble", "diorite", "endbricks",
|
||||
"granite", "mossycobble", "netherbrick", "prismarine",
|
||||
"rednetherbrick", "redsandstone", "sandstone",
|
||||
"stonebrick", "stonebrickmossy",
|
||||
},
|
||||
wool = {
|
||||
"black", "blue", "brown", "cyan", "green",
|
||||
"grey", "light_blue", "lime", "magenta", "orange",
|
||||
"pink", "purple", "red", "silver", "white", "yellow",
|
||||
},
|
||||
}
|
||||
|
||||
tt.register_snippet(function(itemstring)
|
||||
local def = minetest.registered_items[itemstring]
|
||||
local item_split = itemstring:find(":")
|
||||
local id_string = itemstring:sub(item_split)
|
||||
local id_modname = itemstring:sub(1, item_split - 1)
|
||||
local new_id = game .. id_string
|
||||
local mod_namespace = mcl_item_id.get_mod_namespace(id_modname)
|
||||
for mod, ids in pairs(same_id) do
|
||||
for _, id in pairs(ids) do
|
||||
if itemstring == "mcl_" .. mod .. ":" .. id then
|
||||
new_id = game .. ":" .. id .. "_" .. mod:gsub("s", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
if mod_namespace ~= game then
|
||||
new_id = mod_namespace .. id_string
|
||||
end
|
||||
if mod_namespace ~= id_modname then
|
||||
minetest.register_alias_force(new_id, itemstring)
|
||||
end
|
||||
if minetest.settings:get_bool("mcl_item_id_debug", false) then
|
||||
return new_id, "#555555"
|
||||
end
|
||||
end)
|
|
@ -0,0 +1,3 @@
|
|||
name = mcl_item_id
|
||||
author = NO11
|
||||
depends = tt
|
|
@ -45,3 +45,4 @@ Mining durability: @1=Grabehaltbarkeit: @1
|
|||
Block breaking strength: @1=Blockbruchstärke: @1
|
||||
@1 uses=@1 Verwendungen
|
||||
Unlimited uses=Unbegrenzte Verwendungen
|
||||
Durability: @1=Haltbarkeit: @1
|
||||
|
|
|
@ -45,3 +45,4 @@ Mining durability: @1=
|
|||
Block breaking strength: @1=
|
||||
@1 uses=
|
||||
Unlimited uses=
|
||||
Durability: @1=
|
||||
|
|
|
@ -107,3 +107,8 @@ tt.register_snippet(function(itemstring)
|
|||
end
|
||||
end)
|
||||
|
||||
tt.register_snippet(function(itemstring, _, itemstack)
|
||||
if itemstring:sub(1, 23) == "mcl_fishing:fishing_rod" or itemstring:sub(1, 12) == "mcl_bows:bow" then
|
||||
return S("Durability: @1", S("@1 uses", mcl_util.calculate_durability(itemstack or ItemStack(itemstring))))
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -425,6 +425,7 @@ function hb.hide_hudbar(player, identifier)
|
|||
local name = player:get_player_name()
|
||||
local hudtable = hb.get_hudtable(identifier)
|
||||
if hudtable == nil then return false end
|
||||
if hudtable.hudstate[name].hidden == true then return true end
|
||||
if hb.settings.bar_type == "progress_bar" then
|
||||
if hudtable.hudids[name].icon then
|
||||
player:hud_change(hudtable.hudids[name].icon, "scale", {x=0,y=0})
|
||||
|
@ -443,6 +444,7 @@ function hb.unhide_hudbar(player, identifier)
|
|||
local name = player:get_player_name()
|
||||
local hudtable = hb.get_hudtable(identifier)
|
||||
if hudtable == nil then return false end
|
||||
if hudtable.hudstate[name].hidden == false then return true end
|
||||
local value = hudtable.hudstate[name].value
|
||||
local max = hudtable.hudstate[name].max
|
||||
if hb.settings.bar_type == "progress_bar" then
|
||||
|
|
|
@ -3,123 +3,8 @@ local S = minetest.get_translator(modname)
|
|||
|
||||
mcl_credits = {
|
||||
players = {},
|
||||
}
|
||||
|
||||
mcl_credits.description = S("A faithful Open Source clone of Minecraft")
|
||||
|
||||
-- Sub-lists are sorted by number of commits, but the list should not be rearranged (-> new contributors are just added at the end of the list)
|
||||
mcl_credits.people = {
|
||||
{ S("Creator of MineClone"), 0x0A9400, {
|
||||
"davedevils",
|
||||
}},
|
||||
{ S("Creator of MineClone2"), 0xFBF837, {
|
||||
"Wuzzy",
|
||||
}},
|
||||
{ S("Maintainers"), 0xFF51D5, {
|
||||
"Fleckenstein",
|
||||
"kay27",
|
||||
"oilboi",
|
||||
}},
|
||||
{ S("Developers"), 0xF84355, {
|
||||
"bzoss",
|
||||
"AFCMS",
|
||||
"epCode",
|
||||
"ryvnf",
|
||||
"iliekprogrammar",
|
||||
"MysticTempest",
|
||||
"Rootyjr",
|
||||
"Nicu",
|
||||
"aligator",
|
||||
"Code-Sploit",
|
||||
"NO11",
|
||||
}},
|
||||
{ S("Contributors"), 0x52FF00, {
|
||||
"Laurent Rocher",
|
||||
"HimbeerserverDE",
|
||||
"TechDudie",
|
||||
"Alexander Minges",
|
||||
"ArTee3",
|
||||
"ZeDique la Ruleta",
|
||||
"pitchum",
|
||||
"wuniversales",
|
||||
"Bu-Gee",
|
||||
"David McMackins II",
|
||||
"Nicholas Niro",
|
||||
"Wouters Dorian",
|
||||
"Blue Blancmange",
|
||||
"Jared Moody",
|
||||
"Li0n",
|
||||
"Midgard",
|
||||
"Saku Laesvuori",
|
||||
"Yukitty",
|
||||
"ZedekThePD",
|
||||
"aldum",
|
||||
"dBeans",
|
||||
"nickolas360",
|
||||
"yutyo",
|
||||
"ztianyang",
|
||||
"j45",
|
||||
}},
|
||||
{"MineClone5", 0xA60014, {
|
||||
"kay27",
|
||||
"Debiankaios",
|
||||
"epCode",
|
||||
"NO11",
|
||||
"j45",
|
||||
}},
|
||||
{ S("Original Mod Authors"), 0x343434, {
|
||||
"Wuzzy",
|
||||
"Fleckenstein",
|
||||
"BlockMen",
|
||||
"TenPlus1",
|
||||
"PilzAdam",
|
||||
"ryvnf",
|
||||
"stujones11",
|
||||
"Arcelmi",
|
||||
"celeron55",
|
||||
"maikerumine",
|
||||
"GunshipPenguin",
|
||||
"Qwertymine3",
|
||||
"Rochambeau",
|
||||
"rubenwardy",
|
||||
"stu",
|
||||
"oilboi",
|
||||
"4aiman",
|
||||
"Kahrl",
|
||||
"Krock",
|
||||
"UgnilJoZ",
|
||||
"lordfingle",
|
||||
"22i",
|
||||
"bzoss",
|
||||
"kilbith",
|
||||
"xeranas",
|
||||
"kddekadenz",
|
||||
"sofar",
|
||||
"4Evergreen4",
|
||||
"jordan4ibanez",
|
||||
"paramat",
|
||||
}},
|
||||
{ S("3D Models"), 0x0019FF, {
|
||||
"22i",
|
||||
"tobyplowy",
|
||||
"epCode",
|
||||
}},
|
||||
{ S("Textures"), 0xFF9705, {
|
||||
"XSSheep",
|
||||
"Wuzzy",
|
||||
"kingoscargames",
|
||||
"leorockway",
|
||||
"xMrVizzy",
|
||||
"yutyo",
|
||||
"NO11",
|
||||
}},
|
||||
{ S("Translations"), 0x00FF60, {
|
||||
"Wuzzy",
|
||||
"Rocher Laurent",
|
||||
"wuniversales",
|
||||
"kay27",
|
||||
"pitchum",
|
||||
}},
|
||||
description = S("A faithful Open Source clone of Minecraft"),
|
||||
people = dofile(minetest.get_modpath(modname) .. "/people.lua"),
|
||||
}
|
||||
|
||||
local function add_hud_element(def, huds, y)
|
||||
|
|
|
@ -7,6 +7,7 @@ Creator of MineClone2=Schöpfer von MineClone2
|
|||
Developers=Entwickler
|
||||
Jump to speed up (additionally sprint)=Springen, um zu beschleunigen (zusätzlich sprinten)
|
||||
Maintainers=Betreuer
|
||||
MineClone5=MineClone5
|
||||
Original Mod Authors=Original-Mod-Autoren
|
||||
Sneak to skip=Schleichen zum Überspringen
|
||||
Textures=Texturen
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# textdomain: mcl_credits
|
||||
3D Models=
|
||||
A faithful Open Source clone of Minecraft=
|
||||
Contributors=
|
||||
Creator of MineClone=
|
||||
Creator of MineClone2=
|
||||
Developers=
|
||||
Jump to speed up (additionally sprint)=
|
||||
Maintainers=
|
||||
MineClone5=
|
||||
Original Mod Authors=
|
||||
Sneak to skip=
|
||||
Textures=
|
||||
Translations=
|
|
@ -0,0 +1,14 @@
|
|||
# textdomain: mcl_credits
|
||||
3D Models=Modèles 3D
|
||||
A faithful Open Source clone of Minecraft=Un clone open source de Minecraft
|
||||
Contributors=Contributeurs
|
||||
Creator of MineClone=Créateur de MineClone
|
||||
Creator of MineClone2=Créateur de MineClone2
|
||||
Developers=Développeurs
|
||||
Jump to speed up (additionally sprint)=Saut pour accélérer (peut être combiné avec sprint)
|
||||
Maintainers=Mainteneurs
|
||||
MineClone5=MineClone5
|
||||
Original Mod Authors=Auteurs des mods originaux
|
||||
Sneak to skip=Shift pour passer
|
||||
Textures=Textures
|
||||
Translations=Traductions
|
|
@ -0,0 +1,14 @@
|
|||
# textdomain: mcl_credits
|
||||
3D Models=
|
||||
A faithful Open Source clone of Minecraft=
|
||||
Contributors=
|
||||
Creator of MineClone=
|
||||
Creator of MineClone2=
|
||||
Developers=
|
||||
Jump to speed up (additionally sprint)=
|
||||
Maintainers=
|
||||
MineClone5=
|
||||
Original Mod Authors=
|
||||
Sneak to skip=
|
||||
Textures=
|
||||
Translations=
|
|
@ -0,0 +1,14 @@
|
|||
# textdomain: mcl_credits
|
||||
3D Models=
|
||||
A faithful Open Source clone of Minecraft=
|
||||
Contributors=
|
||||
Creator of MineClone=
|
||||
Creator of MineClone2=
|
||||
Developers=
|
||||
Jump to speed up (additionally sprint)=
|
||||
Maintainers=
|
||||
MineClone5=
|
||||
Original Mod Authors=
|
||||
Sneak to skip=
|
||||
Textures=
|
||||
Translations=
|
|
@ -0,0 +1,144 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
return {
|
||||
{S("Creator of MineClone"), 0x0A9400, {
|
||||
"davedevils",
|
||||
}},
|
||||
{S("Creator of MineClone2"), 0xFBF837, {
|
||||
"Wuzzy",
|
||||
}},
|
||||
{S("Maintainers"), 0xFF51D5, {
|
||||
"Fleckenstein",
|
||||
"Nicu",
|
||||
"kay27",
|
||||
}},
|
||||
{S("Developers"), 0xF84355, {
|
||||
"bzoss",
|
||||
"AFCMS",
|
||||
"epCode",
|
||||
"ryvnf",
|
||||
"iliekprogrammar",
|
||||
"MysticTempest",
|
||||
"Rootyjr",
|
||||
"aligator",
|
||||
"Code-Sploit",
|
||||
"NO11",
|
||||
"cora",
|
||||
"jordan4ibanez",
|
||||
}},
|
||||
{S("Contributors"), 0x52FF00, {
|
||||
"Laurent Rocher",
|
||||
"HimbeerserverDE",
|
||||
"TechDudie",
|
||||
"Alexander Minges",
|
||||
"ArTee3",
|
||||
"ZeDique la Ruleta",
|
||||
"pitchum",
|
||||
"wuniversales",
|
||||
"Bu-Gee",
|
||||
"David McMackins II",
|
||||
"Nicholas Niro",
|
||||
"Wouters Dorian",
|
||||
"Blue Blancmange",
|
||||
"Jared Moody",
|
||||
"Li0n",
|
||||
"Midgard",
|
||||
"Saku Laesvuori",
|
||||
"Yukitty",
|
||||
"ZedekThePD",
|
||||
"aldum",
|
||||
"dBeans",
|
||||
"nickolas360",
|
||||
"yutyo",
|
||||
"Tianyang Zhang",
|
||||
"j45",
|
||||
"Marcin Serwin",
|
||||
"erlehmann",
|
||||
"E",
|
||||
"Benjamin Schötz",
|
||||
"Doloment",
|
||||
"Sydney Gems",
|
||||
"talamh",
|
||||
"Emily2255",
|
||||
"Emojigit",
|
||||
"FinishedFragment",
|
||||
"sfan5",
|
||||
"Blue Blancmange",
|
||||
"Jared Moody",
|
||||
"SmallJoker",
|
||||
"Sven792",
|
||||
"aldum",
|
||||
}},
|
||||
{S("MineClone5"), 0xA60014, {
|
||||
"kay27",
|
||||
"Debiankaios",
|
||||
"epCode",
|
||||
"NO11",
|
||||
"j45",
|
||||
}},
|
||||
{S("Original Mod Authors"), 0x343434, {
|
||||
"Wuzzy",
|
||||
"Fleckenstein",
|
||||
"BlockMen",
|
||||
"TenPlus1",
|
||||
"PilzAdam",
|
||||
"ryvnf",
|
||||
"stujones11",
|
||||
"Arcelmi",
|
||||
"celeron55",
|
||||
"maikerumine",
|
||||
"GunshipPenguin",
|
||||
"Qwertymine3",
|
||||
"Rochambeau",
|
||||
"rubenwardy",
|
||||
"stu",
|
||||
"4aiman",
|
||||
"Kahrl",
|
||||
"Krock",
|
||||
"UgnilJoZ",
|
||||
"lordfingle",
|
||||
"22i",
|
||||
"bzoss",
|
||||
"kilbith",
|
||||
"xeranas",
|
||||
"kddekadenz",
|
||||
"sofar",
|
||||
"4Evergreen4",
|
||||
"jordan4ibanez",
|
||||
"paramat",
|
||||
}},
|
||||
{S("3D Models"), 0x0019FF, {
|
||||
"22i",
|
||||
"tobyplowy",
|
||||
"epCode",
|
||||
}},
|
||||
{S("Textures"), 0xFF9705, {
|
||||
"XSSheep",
|
||||
"Wuzzy",
|
||||
"kingoscargames",
|
||||
"leorockway",
|
||||
"xMrVizzy",
|
||||
"yutyo",
|
||||
"NO11",
|
||||
"kay27",
|
||||
}},
|
||||
{S("Translations"), 0x00FF60, {
|
||||
"Wuzzy",
|
||||
"Rocher Laurent",
|
||||
"wuniversales",
|
||||
"kay27",
|
||||
"pitchum",
|
||||
"todoporlalibertad",
|
||||
"Marcin Serwin",
|
||||
}},
|
||||
{S("Funders"), 0xF7FF00, {
|
||||
"40W",
|
||||
}},
|
||||
{S("Special thanks"), 0x00E9FF, {
|
||||
"celeron55 for creating Minetest",
|
||||
"Jordach for the jukebox music compilation from Big Freaking Dig",
|
||||
"The workaholics who spent way too much time writing for the Minecraft Wiki. It's an invaluable resource for creating this game",
|
||||
"Notch and Jeb for being the major forces behind Minecraft",
|
||||
}},
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
minetest.register_entity("mcl_experience:bottle",{
|
||||
textures = {"mcl_experience_bottle.png"},
|
||||
hp_max = 1,
|
||||
visual_size = {x = 0.35, y = 0.35},
|
||||
collisionbox = {-0.1, -0.1, -0.1, 0.1, 0.1, 0.1},
|
||||
pointable = false,
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local n = node.name
|
||||
if n ~= "air" and n ~= "mcl_portals:portal" and n ~= "mcl_portals:portal_end" and minetest.get_item_group(n, "liquid") == 0 then
|
||||
minetest.sound_play("mcl_potions_breaking_glass", {pos = pos, max_hear_distance = 16, gain = 1})
|
||||
mcl_experience.throw_xp(pos, math.random(3, 11))
|
||||
minetest.add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.1,
|
||||
minpos = vector.add(pos, vector.new(-0.1, 0.5, -0.1)),
|
||||
maxpos = vector.add(pos, vector.new( 0.1, 0.6, 0.1)),
|
||||
minvel = vector.new(-2, 0, -2),
|
||||
maxvel = vector.new( 2, 2, 2),
|
||||
minacc = vector.new(0, 0, 0),
|
||||
maxacc = vector.new(0, 0, 0),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 1.25,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = true,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_effect.png^[colorize:blue:127",
|
||||
})
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local function throw_xp_bottle(pos, dir, velocity)
|
||||
minetest.sound_play("mcl_throwing_throw", {pos = pos, gain = 0.4, max_hear_distance = 16}, true)
|
||||
local obj = minetest.add_entity(pos, "mcl_experience:bottle")
|
||||
obj:set_velocity(vector.multiply(dir, velocity))
|
||||
local acceleration = vector.multiply(dir, -3)
|
||||
acceleration.y = -9.81
|
||||
obj:set_acceleration(acceleration)
|
||||
end
|
||||
|
||||
minetest.register_craftitem("mcl_experience:bottle", {
|
||||
description = "Bottle o' Enchanting",
|
||||
inventory_image = "mcl_experience_bottle.png",
|
||||
wield_image = "mcl_experience_bottle.png",
|
||||
stack_max = 64,
|
||||
on_use = function(itemstack, placer, pointed_thing)
|
||||
throw_xp_bottle(vector.add(placer:get_pos(), vector.new(0, 1.5, 0)), placer:get_look_dir(), 10)
|
||||
if not minetest.is_creative_enabled(placer:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end,
|
||||
_on_dispense = function(_, pos, _, _, dir)
|
||||
throw_xp_bottle(vector.add(pos, vector.multiply(dir, 0.51)), dir, 10)
|
||||
end
|
||||
})
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
minetest.register_chatcommand("xp", {
|
||||
params = S("[[<player>] <xp>]"),
|
||||
description = S("Gives a player some XP"),
|
||||
privs = {server=true},
|
||||
func = function(name, params)
|
||||
local player, xp = nil, 1000
|
||||
local P, i = {}, 0
|
||||
for str in string.gmatch(params, "([^ ]+)") do
|
||||
i = i + 1
|
||||
P[i] = str
|
||||
end
|
||||
if i > 2 then
|
||||
return false, S("Error: Too many parameters!")
|
||||
end
|
||||
if i > 0 then
|
||||
xp = tonumber(P[i])
|
||||
end
|
||||
if i < 2 then
|
||||
player = minetest.get_player_by_name(name)
|
||||
end
|
||||
if i == 2 then
|
||||
player = minetest.get_player_by_name(P[1])
|
||||
end
|
||||
|
||||
if not xp then
|
||||
return false, S("Error: Incorrect value of XP")
|
||||
end
|
||||
|
||||
if not player then
|
||||
return false, S("Error: Player not found")
|
||||
end
|
||||
|
||||
mcl_experience.add_xp(player, xp)
|
||||
|
||||
return true, S("Added @1 XP to @2, total: @3, experience level: @4", tostring(xp), player:get_player_name(), tostring(mcl_experience.get_xp(player)), tostring(mcl_experience.get_level(player)))
|
||||
end,
|
||||
})
|
|
@ -1,203 +1,25 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
mcl_experience = {}
|
||||
|
||||
local vector = vector
|
||||
local math = math
|
||||
local string = string
|
||||
|
||||
local pool = {}
|
||||
local registered_nodes
|
||||
local max_xp = 2^31-1
|
||||
local max_orb_age = 300 -- seconds
|
||||
|
||||
local gravity = {x = 0, y = -((tonumber(minetest.settings:get("movement_gravity"))) or 9.81), z = 0}
|
||||
local size_min, size_max = 20, 59 -- percents
|
||||
local delta_size = size_max - size_min
|
||||
local size_to_xp = {
|
||||
{-32768, 2}, -- 1
|
||||
{ 3, 6}, -- 2
|
||||
{ 7, 16}, -- 3
|
||||
{ 17, 36}, -- 4
|
||||
{ 37, 72}, -- 5
|
||||
{ 73, 148}, -- 6
|
||||
{ 149, 306}, -- 7
|
||||
{ 307, 616}, -- 8
|
||||
{ 617, 1236}, -- 9
|
||||
{ 1237, 2476}, --10
|
||||
{ 2477, 32767} --11
|
||||
mcl_experience = {
|
||||
on_add_xp = {},
|
||||
}
|
||||
|
||||
local function xp_to_size(xp)
|
||||
local i, l = 1, #size_to_xp
|
||||
while (xp > size_to_xp[i][1]) and (i < l) do
|
||||
i = i + 1
|
||||
end
|
||||
return ((i-1) / (l-1) * delta_size + size_min)/100
|
||||
end
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
registered_nodes = minetest.registered_nodes
|
||||
end)
|
||||
dofile(modpath .. "/command.lua")
|
||||
dofile(modpath .. "/orb.lua")
|
||||
dofile(modpath .. "/bottle.lua")
|
||||
|
||||
local function load_data(player)
|
||||
local name = player:get_player_name()
|
||||
pool[name] = {}
|
||||
local temp_pool = pool[name]
|
||||
local meta = player:get_meta()
|
||||
temp_pool.xp = meta:get_int("xp") or 0
|
||||
temp_pool.level = mcl_experience.xp_to_level(temp_pool.xp)
|
||||
temp_pool.bar, temp_pool.bar_step, temp_pool.xp_next_level = mcl_experience.xp_to_bar(temp_pool.xp, temp_pool.level)
|
||||
temp_pool.last_time= minetest.get_us_time()/1000000
|
||||
end
|
||||
-- local storage
|
||||
|
||||
-- saves data to be utilized on next login
|
||||
local function save_data(player)
|
||||
local name = player:get_player_name()
|
||||
local temp_pool = pool[name]
|
||||
local meta = player:get_meta()
|
||||
meta:set_int("xp", temp_pool.xp)
|
||||
pool[name] = nil
|
||||
end
|
||||
local hud_bars = {}
|
||||
local hud_levels = {}
|
||||
local caches = {}
|
||||
|
||||
local player_huds = {} -- the list of players hud lists (3d array)
|
||||
hud_manager = {} -- hud manager class
|
||||
-- helpers
|
||||
|
||||
-- terminate the player's list on leave
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
player_huds[name] = nil
|
||||
end)
|
||||
|
||||
-- create instance of new hud
|
||||
function hud_manager.add_hud(player,hud_name,def)
|
||||
local name = player:get_player_name()
|
||||
if minetest.is_creative_enabled(name) then
|
||||
return
|
||||
end
|
||||
local local_hud = player:hud_add({
|
||||
hud_elem_type = def.hud_elem_type,
|
||||
position = def.position,
|
||||
text = def.text,
|
||||
text2 = def.text2,
|
||||
number = def.number,
|
||||
item = def.item,
|
||||
direction = def.direction,
|
||||
size = def.size,
|
||||
offset = def.offset,
|
||||
z_index = def.z_index,
|
||||
alignment = def.alignment,
|
||||
scale = def.scale,
|
||||
})
|
||||
-- create new 3d array here
|
||||
-- depends.txt is not needed
|
||||
-- with it here
|
||||
if not player_huds[name] then
|
||||
player_huds[name] = {}
|
||||
end
|
||||
|
||||
player_huds[name][hud_name] = local_hud
|
||||
end
|
||||
|
||||
-- delete instance of hud
|
||||
function hud_manager.remove_hud(player,hud_name)
|
||||
local name = player:get_player_name()
|
||||
if player_huds[name] and player_huds[name][hud_name] then
|
||||
player:hud_remove(player_huds[name][hud_name])
|
||||
player_huds[name][hud_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- change element of hud
|
||||
function hud_manager.change_hud(data)
|
||||
local name = data.player:get_player_name()
|
||||
if player_huds[name] and player_huds[name][data.hud_name] then
|
||||
data.player:hud_change(player_huds[name][data.hud_name], data.element, data.data)
|
||||
end
|
||||
end
|
||||
|
||||
-- gets if hud exists
|
||||
function hud_manager.hud_exists(player,hud_name)
|
||||
local name = player:get_player_name()
|
||||
if player_huds[name] and player_huds[name][hud_name] then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
-------------------
|
||||
|
||||
-- saves specific users data for when they relog
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
save_data(player)
|
||||
end)
|
||||
|
||||
-- is used for shutdowns to save all data
|
||||
local function save_all()
|
||||
for name,_ in pairs(pool) do
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
save_data(player)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- save all data to mod storage on shutdown
|
||||
minetest.register_on_shutdown(function()
|
||||
save_all()
|
||||
end)
|
||||
|
||||
|
||||
function mcl_experience.get_player_xp_level(player)
|
||||
local name = player:get_player_name()
|
||||
return pool[name].level
|
||||
end
|
||||
|
||||
function mcl_experience.set_player_xp_level(player,level)
|
||||
local name = player:get_player_name()
|
||||
if level == pool[name].level then
|
||||
return
|
||||
end
|
||||
pool[name].level = level
|
||||
pool[name].xp, pool[name].bar_step, pool[name].xp_next_level = mcl_experience.bar_to_xp(pool[name].bar, level)
|
||||
hud_manager.change_hud({player = player, hud_name = "xp_level", element = "text", data = tostring(level)})
|
||||
-- we may don't update the bar
|
||||
end
|
||||
|
||||
local name
|
||||
local temp_pool
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
|
||||
load_data(player)
|
||||
|
||||
name = player:get_player_name()
|
||||
temp_pool = pool[name]
|
||||
|
||||
hud_manager.add_hud(player,"experience_bar",
|
||||
{
|
||||
hud_elem_type = "image",
|
||||
name = "experience bar",
|
||||
text = "experience_bar_background.png^[lowpart:" .. math.floor(temp_pool.bar / 36 * 100) .. ":experience_bar.png^[transformR270",
|
||||
position = {x=0.5, y=1},
|
||||
offset = {x = (-9 * 28) - 3, y = -(48 + 24 + 16 - 5)},
|
||||
scale = {x = 2.8, y = 3.0},
|
||||
alignment = { x = 1, y = 1 },
|
||||
z_index = 11,
|
||||
})
|
||||
|
||||
hud_manager.add_hud(player,"xp_level",
|
||||
{
|
||||
hud_elem_type = "text", position = {x=0.5, y=1},
|
||||
name = "xp_level", text = tostring(temp_pool.level),
|
||||
number = 0x80FF20,
|
||||
offset = {x = 0, y = -(48 + 24 + 24)},
|
||||
z_index = 12,
|
||||
})
|
||||
end)
|
||||
|
||||
function mcl_experience.xp_to_level(xp)
|
||||
local function xp_to_level(xp)
|
||||
local xp = xp or 0
|
||||
local a, b, c, D
|
||||
|
||||
if xp > 1507 then
|
||||
a, b, c = 4.5, -162.5, 2220 - xp
|
||||
elseif xp > 352 then
|
||||
|
@ -205,437 +27,201 @@ function mcl_experience.xp_to_level(xp)
|
|||
else
|
||||
a, b, c = 1, 6, -xp
|
||||
end
|
||||
|
||||
D = b * b - 4 * a * c
|
||||
|
||||
if D == 0 then
|
||||
return math.floor(-b / 2 / a)
|
||||
elseif D > 0 then
|
||||
local v1, v2 = -b / 2 / a, math.sqrt(D) / 2 / a
|
||||
return math.floor((math.max(v1-v2, v1+v2)))
|
||||
return math.floor(math.max(v1 - v2, v1 + v2))
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function mcl_experience.level_to_xp(level)
|
||||
if (level >= 1 and level <= 16) then
|
||||
local function level_to_xp(level)
|
||||
if level >= 1 and level <= 16 then
|
||||
return math.floor(math.pow(level, 2) + 6 * level)
|
||||
elseif (level >= 17 and level <= 31) then
|
||||
elseif level >= 17 and level <= 31 then
|
||||
return math.floor(2.5 * math.pow(level, 2) - 40.5 * level + 360)
|
||||
elseif level >= 32 then
|
||||
return math.floor(4.5 * math.pow(level, 2) - 162.5 * level + 2220);
|
||||
return math.floor(4.5 * math.pow(level, 2) - 162.5 * level + 2220)
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
function mcl_experience.xp_to_bar(xp, level)
|
||||
local level = level or mcl_experience.xp_to_level(xp)
|
||||
local xp_this_level = mcl_experience.level_to_xp(level)
|
||||
local xp_next_level = mcl_experience.level_to_xp(level+1)
|
||||
local bar_step = 36 / (xp_next_level-xp_this_level)
|
||||
local bar = (xp-xp_this_level) * bar_step
|
||||
return bar, bar_step, xp_next_level
|
||||
local function calculate_bounds(level)
|
||||
return level_to_xp(level), level_to_xp(level + 1)
|
||||
end
|
||||
|
||||
function mcl_experience.bar_to_xp(bar, level)
|
||||
local xp_this_level = mcl_experience.level_to_xp(level)
|
||||
local xp_next_level = mcl_experience.level_to_xp(level+1)
|
||||
local bar_step = 36 / (xp_next_level-xp_this_level)
|
||||
local xp = xp_this_level + math.floor(bar/36*(xp_next_level-xp_this_level))
|
||||
return xp, bar_step, xp_next_level
|
||||
local function xp_to_bar(xp, level)
|
||||
local xp_min, xp_max = calculate_bounds(level)
|
||||
|
||||
return (xp - xp_min) / (xp_max - xp_min)
|
||||
end
|
||||
|
||||
function mcl_experience.add_experience(player, experience)
|
||||
local function bar_to_xp(bar, level)
|
||||
local xp_min, xp_max = calculate_bounds(level)
|
||||
|
||||
return xp_min + bar * (xp_max - xp_min)
|
||||
end
|
||||
|
||||
local function get_time()
|
||||
return minetest.get_us_time() / 1000000
|
||||
end
|
||||
|
||||
-- api
|
||||
|
||||
function mcl_experience.get_level(player)
|
||||
return caches[player].level
|
||||
end
|
||||
|
||||
function mcl_experience.set_level(player, level)
|
||||
local cache = caches[player]
|
||||
|
||||
if level ~= cache.level then
|
||||
mcl_experience.set_xp(player, math.floor(bar_to_xp(xp_to_bar(mcl_experience.get_xp(player), cache.level), level)))
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_experience.get_xp(player)
|
||||
return player:get_meta():get_int("xp")
|
||||
end
|
||||
|
||||
function mcl_experience.set_xp(player, xp)
|
||||
player:get_meta():set_int("xp", xp)
|
||||
|
||||
mcl_experience.update(player)
|
||||
end
|
||||
|
||||
function mcl_experience.add_xp(player, xp)
|
||||
for _, cb in ipairs(mcl_experience.on_add_xp) do
|
||||
xp = cb.func(player, xp) or xp
|
||||
|
||||
if xp == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local cache = caches[player]
|
||||
local old_level = cache.level
|
||||
|
||||
mcl_experience.set_xp(player, mcl_experience.get_xp(player) + xp)
|
||||
|
||||
local current_time = get_time()
|
||||
|
||||
if current_time - cache.last_time > 0.01 then
|
||||
local name = player:get_player_name()
|
||||
local temp_pool = pool[name]
|
||||
|
||||
local inv = player:get_inventory()
|
||||
local candidates = {
|
||||
{list = "main", index = player:get_wield_index()},
|
||||
{list = "armor", index = 2},
|
||||
{list = "armor", index = 3},
|
||||
{list = "armor", index = 4},
|
||||
{list = "armor", index = 5},
|
||||
}
|
||||
local final_candidates = {}
|
||||
for _, can in ipairs(candidates) do
|
||||
local stack = inv:get_stack(can.list, can.index)
|
||||
local wear = stack:get_wear()
|
||||
if mcl_enchanting.has_enchantment(stack, "mending") and wear > 0 then
|
||||
can.stack = stack
|
||||
can.wear = wear
|
||||
table.insert(final_candidates, can)
|
||||
end
|
||||
end
|
||||
if #final_candidates > 0 then
|
||||
local can = final_candidates[math.random(#final_candidates)]
|
||||
local stack, list, index, wear = can.stack, can.list, can.index, can.wear
|
||||
local uses = mcl_util.calculate_durability(stack)
|
||||
local multiplier = 2 * 65535 / uses
|
||||
local repair = experience * multiplier
|
||||
local new_wear = wear - repair
|
||||
if new_wear < 0 then
|
||||
experience = math.floor(-new_wear / multiplier + 0.5)
|
||||
new_wear = 0
|
||||
else
|
||||
experience = 0
|
||||
end
|
||||
stack:set_wear(math.floor(new_wear))
|
||||
inv:set_stack(list, index, stack)
|
||||
end
|
||||
|
||||
local old_bar, old_xp, old_level = temp_pool.bar, temp_pool.xp, temp_pool.level
|
||||
temp_pool.xp = math.min(math.max(temp_pool.xp + experience, 0), max_xp)
|
||||
|
||||
if (temp_pool.xp < temp_pool.xp_next_level) and (temp_pool.xp >= old_xp) then
|
||||
temp_pool.bar = temp_pool.bar + temp_pool.bar_step * experience
|
||||
else
|
||||
temp_pool.level = mcl_experience.xp_to_level(temp_pool.xp)
|
||||
temp_pool.bar, temp_pool.bar_step, temp_pool.xp_next_level = mcl_experience.xp_to_bar(temp_pool.xp, temp_pool.level)
|
||||
end
|
||||
|
||||
if old_bar ~= temp_pool.bar then
|
||||
hud_manager.change_hud({player = player, hud_name = "experience_bar", element = "text", data = "experience_bar_background.png^[lowpart:" .. math.floor(temp_pool.bar / 36 * 100) .. ":experience_bar.png^[transformR270",})
|
||||
end
|
||||
|
||||
if experience > 0 and minetest.get_us_time()/1000000 - temp_pool.last_time > 0.01 then
|
||||
if old_level ~= temp_pool.level then
|
||||
minetest.sound_play("level_up",{gain=0.2,to_player = name})
|
||||
temp_pool.last_time = minetest.get_us_time()/1000000 + 0.2
|
||||
else
|
||||
minetest.sound_play("experience",{gain=0.1,to_player = name,pitch=math.random(75,99)/100})
|
||||
temp_pool.last_time = minetest.get_us_time()/1000000
|
||||
end
|
||||
end
|
||||
|
||||
if old_level ~= temp_pool.level then
|
||||
hud_manager.change_hud({player = player, hud_name = "xp_level", element = "text", data = tostring(temp_pool.level)})
|
||||
end
|
||||
end
|
||||
|
||||
--reset player level
|
||||
local name
|
||||
local temp_pool
|
||||
local xp_amount
|
||||
minetest.register_on_dieplayer(function(player)
|
||||
if minetest.settings:get_bool("mcl_keepInventory", false) then
|
||||
return
|
||||
end
|
||||
|
||||
name = player:get_player_name()
|
||||
temp_pool = pool[name]
|
||||
xp_amount = temp_pool.xp
|
||||
|
||||
temp_pool.xp = 0
|
||||
temp_pool.level = 0
|
||||
temp_pool.bar, temp_pool.bar_step, temp_pool.xp_next_level = mcl_experience.xp_to_bar(temp_pool.xp, temp_pool.level)
|
||||
|
||||
hud_manager.change_hud({player = player, hud_name = "xp_level", element = "text", data = tostring(temp_pool.level)})
|
||||
hud_manager.change_hud({player = player, hud_name = "experience_bar", element = "text", data = "experience_bar_background.png^[lowpart:" .. math.floor(temp_pool.bar / 36 * 100) .. ":experience_bar.png^[transformR270",})
|
||||
|
||||
mcl_experience.throw_experience(player:get_pos(), xp_amount)
|
||||
end)
|
||||
|
||||
local collector, pos, pos2
|
||||
local direction, distance, player_velocity, goal
|
||||
local currentvel, acceleration, multiplier, velocity
|
||||
local node, vel, def
|
||||
local is_moving, is_slippery, slippery, slip_factor
|
||||
local size
|
||||
local function xp_step(self, dtime)
|
||||
--if item set to be collected then only execute go to player
|
||||
if self.collected == true then
|
||||
if not self.collector then
|
||||
self.collected = false
|
||||
return
|
||||
end
|
||||
collector = minetest.get_player_by_name(self.collector)
|
||||
if collector and collector:get_hp() > 0 and vector.distance(self.object:get_pos(),collector:get_pos()) < 7.25 then
|
||||
self.object:set_acceleration(vector.new(0,0,0))
|
||||
self.disable_physics(self)
|
||||
--get the variables
|
||||
pos = self.object:get_pos()
|
||||
pos2 = collector:get_pos()
|
||||
|
||||
player_velocity = collector:get_velocity() or collector:get_player_velocity()
|
||||
|
||||
pos2.y = pos2.y + 0.8
|
||||
|
||||
direction = vector.direction(pos,pos2)
|
||||
distance = vector.distance(pos2,pos)
|
||||
multiplier = distance
|
||||
if multiplier < 1 then
|
||||
multiplier = 1
|
||||
end
|
||||
goal = vector.multiply(direction,multiplier)
|
||||
currentvel = self.object:get_velocity()
|
||||
|
||||
if distance > 1 then
|
||||
multiplier = 20 - distance
|
||||
velocity = vector.multiply(direction,multiplier)
|
||||
goal = velocity
|
||||
acceleration = vector.new(goal.x-currentvel.x,goal.y-currentvel.y,goal.z-currentvel.z)
|
||||
self.object:add_velocity(vector.add(acceleration,player_velocity))
|
||||
elseif distance < 0.8 then
|
||||
mcl_experience.add_experience(collector, self._xp)
|
||||
self.object:remove()
|
||||
end
|
||||
return
|
||||
else
|
||||
self.collector = nil
|
||||
self.enable_physics(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self.age = self.age + dtime
|
||||
if self.age > max_orb_age then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
pos = self.object:get_pos()
|
||||
|
||||
if pos then
|
||||
node = minetest.get_node_or_nil({
|
||||
x = pos.x,
|
||||
y = pos.y -0.25,
|
||||
z = pos.z
|
||||
if old_level == cache.level then
|
||||
minetest.sound_play("mcl_experience", {
|
||||
to_player = name,
|
||||
gain = 0.1,
|
||||
pitch = math.random(75, 99) / 100,
|
||||
})
|
||||
|
||||
cache.last_time = current_time
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove nodes in 'ignore'
|
||||
if node and node.name == "ignore" then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
if not self.physical_state then
|
||||
return -- Don't do anything
|
||||
end
|
||||
|
||||
-- Slide on slippery nodes
|
||||
vel = self.object:get_velocity()
|
||||
def = node and registered_nodes[node.name]
|
||||
is_moving = (def and not def.walkable) or
|
||||
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
|
||||
is_slippery = false
|
||||
|
||||
if def and def.walkable then
|
||||
slippery = minetest.get_item_group(node.name, "slippery")
|
||||
is_slippery = slippery ~= 0
|
||||
if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
|
||||
-- Horizontal deceleration
|
||||
slip_factor = 4.0 / (slippery + 4)
|
||||
self.object:set_acceleration({
|
||||
x = -vel.x * slip_factor,
|
||||
y = 0,
|
||||
z = -vel.z * slip_factor
|
||||
minetest.sound_play("mcl_experience_level_up", {
|
||||
to_player = name,
|
||||
gain = 0.2,
|
||||
})
|
||||
elseif vel.y == 0 then
|
||||
is_moving = false
|
||||
|
||||
cache.last_time = current_time + 0.2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.moving_state == is_moving and self.slippery_state == is_slippery then
|
||||
-- Do not update anything until the moving state changes
|
||||
return
|
||||
function mcl_experience.throw_xp(pos, total_xp)
|
||||
local i, j = 0, 0
|
||||
|
||||
while i < total_xp and j < 100 do
|
||||
local xp = math.min(math.random(1, math.min(32767, total_xp - math.floor(i / 2))), total_xp - i)
|
||||
local obj = minetest.add_entity(pos, "mcl_experience:orb", tostring(xp))
|
||||
|
||||
if not obj then
|
||||
return false
|
||||
end
|
||||
|
||||
self.moving_state = is_moving
|
||||
self.slippery_state = is_slippery
|
||||
|
||||
if is_moving then
|
||||
self.object:set_acceleration(gravity)
|
||||
else
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
self.object:set_velocity({x = 0, y = 0, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_experience:orb", {
|
||||
initial_properties = {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
|
||||
visual = "sprite",
|
||||
visual_size = {x = 0.4, y = 0.4},
|
||||
textures = {name="experience_orb.png", animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0}},
|
||||
spritediv = {x = 1, y = 14},
|
||||
initial_sprite_basepos = {x = 0, y = 0},
|
||||
is_visible = true,
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
},
|
||||
moving_state = true,
|
||||
slippery_state = false,
|
||||
physical_state = true,
|
||||
-- Item expiry
|
||||
age = 0,
|
||||
-- Pushing item out of solid nodes
|
||||
force_out = nil,
|
||||
force_out_start = nil,
|
||||
--Collection Variables
|
||||
collectable = false,
|
||||
try_timer = 0,
|
||||
collected = false,
|
||||
delete_timer = 0,
|
||||
radius = 4,
|
||||
|
||||
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
self.object:set_velocity(vector.new(
|
||||
obj:set_velocity(vector.new(
|
||||
math.random(-2, 2) * math.random(),
|
||||
math.random( 2, 5),
|
||||
math.random(-2, 2) * math.random()
|
||||
))
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
self.object:set_velocity({x = 0, y = 2, z = 0})
|
||||
self.object:set_acceleration(gravity)
|
||||
local xp = tonumber(staticdata)
|
||||
self._xp = xp
|
||||
size = xp_to_size(xp)
|
||||
self.object:set_properties({
|
||||
visual_size = {x = size, y = size},
|
||||
glow = 14,
|
||||
})
|
||||
self.object:set_sprite({x=1,y=math.random(1,14)}, 14, 0.05, false)
|
||||
end,
|
||||
|
||||
enable_physics = function(self)
|
||||
if not self.physical_state then
|
||||
self.physical_state = true
|
||||
self.object:set_properties({physical = true})
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration(gravity)
|
||||
end
|
||||
end,
|
||||
|
||||
disable_physics = function(self)
|
||||
if self.physical_state then
|
||||
self.physical_state = false
|
||||
self.object:set_properties({physical = false})
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
end
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
xp_step(self, dtime)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("xp", {
|
||||
params = S("[[<player>] <xp>]"),
|
||||
description = S("Gives a player some XP"),
|
||||
privs = {server=true},
|
||||
func = function(name, params)
|
||||
local player, xp = nil, 1000
|
||||
local P, i = {}, 0
|
||||
for str in string.gmatch(params, "([^ ]+)") do
|
||||
i = i + 1
|
||||
P[i] = str
|
||||
end
|
||||
if i > 2 then
|
||||
return false, S("Error: Too many parameters!")
|
||||
end
|
||||
if i > 0 then
|
||||
xp = tonumber(P[i])
|
||||
end
|
||||
if i < 2 then
|
||||
player = minetest.get_player_by_name(name)
|
||||
end
|
||||
if i == 2 then
|
||||
player = minetest.get_player_by_name(P[1])
|
||||
end
|
||||
if not xp then
|
||||
return false, S("Error: Incorrect value of XP")
|
||||
end
|
||||
if not player then
|
||||
return false, S("Error: Player not found")
|
||||
end
|
||||
mcl_experience.add_experience(player, xp)
|
||||
local playername = player:get_player_name()
|
||||
minetest.chat_send_player(name, S("Added @1 XP to @2, total: @3, experience level: @4", tostring(xp), playername, tostring(pool[playername].xp), tostring(pool[playername].level)))
|
||||
end,
|
||||
})
|
||||
|
||||
function mcl_experience.throw_experience(pos, amount)
|
||||
local i, j = 0, 0
|
||||
local obj, xp
|
||||
while i < amount and j < 100 do
|
||||
xp = math.min(math.random(1, math.min(32767, amount-math.floor(i/2))), amount-i)
|
||||
obj = minetest.add_entity(pos, "mcl_experience:orb", tostring(xp))
|
||||
if not obj then
|
||||
return false
|
||||
end
|
||||
obj:set_velocity({
|
||||
x=math.random(-2,2)*math.random(),
|
||||
y=math.random(2,5),
|
||||
z=math.random(-2,2)*math.random()
|
||||
})
|
||||
i = i + xp
|
||||
j = j + 1
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_experience:bottle",{
|
||||
textures = {"mcl_experience_bottle.png"},
|
||||
hp_max = 1,
|
||||
visual_size = {x = 0.35, y = 0.35},
|
||||
collisionbox = {-0.1, -0.1, -0.1, 0.1, 0.1, 0.1},
|
||||
pointable = false,
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(pos)
|
||||
local n = node.name
|
||||
if n ~= "air" and n ~= "mcl_portals:portal" and n ~= "mcl_portals:portal_end" and minetest.get_item_group(n, "liquid") == 0 then
|
||||
minetest.sound_play("mcl_potions_breaking_glass", {pos = pos, max_hear_distance = 16, gain = 1})
|
||||
mcl_experience.throw_experience(pos, math.random(3, 11))
|
||||
minetest.add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.1,
|
||||
minpos = vector.add(pos, vector.new(-0.1, 0.5, -0.1)),
|
||||
maxpos = vector.add(pos, vector.new( 0.1, 0.6, 0.1)),
|
||||
minvel = vector.new(-2, 0, -2),
|
||||
maxvel = vector.new( 2, 2, 2),
|
||||
minacc = vector.new(0, 0, 0),
|
||||
maxacc = vector.new(0, 0, 0),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 1.25,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = true,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_effect.png^[colorize:blue:127",
|
||||
})
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
})
|
||||
function mcl_experience.update(player)
|
||||
local xp = mcl_experience.get_xp(player)
|
||||
local cache = caches[player]
|
||||
|
||||
local function throw_xp_bottle(pos, dir, velocity)
|
||||
minetest.sound_play("mcl_throwing_throw", {pos = pos, gain = 0.4, max_hear_distance = 16}, true)
|
||||
local obj = minetest.add_entity(pos, "mcl_experience:bottle")
|
||||
obj:set_velocity(vector.multiply(dir, velocity))
|
||||
local acceleration = vector.multiply(dir, -3)
|
||||
acceleration.y = -9.81
|
||||
obj:set_acceleration(acceleration)
|
||||
cache.level = xp_to_level(xp)
|
||||
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
player:hud_change(hud_bars[player], "text", "mcl_experience_bar_background.png^[lowpart:"
|
||||
.. math.floor(math.floor(xp_to_bar(xp, cache.level) * 18) / 18 * 100)
|
||||
.. ":mcl_experience_bar.png^[transformR270"
|
||||
)
|
||||
|
||||
if cache.level == 0 then
|
||||
player:hud_change(hud_levels[player], "text", "")
|
||||
else
|
||||
player:hud_change(hud_levels[player], "text", tostring(cache.level))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_craftitem("mcl_experience:bottle", {
|
||||
description = "Bottle o' Enchanting",
|
||||
inventory_image = "mcl_experience_bottle.png",
|
||||
wield_image = "mcl_experience_bottle.png",
|
||||
stack_max = 64,
|
||||
on_use = function(itemstack, placer, pointed_thing)
|
||||
throw_xp_bottle(vector.add(placer:get_pos(), vector.new(0, 1.5, 0)), placer:get_look_dir(), 10)
|
||||
if not minetest.is_creative_enabled(placer:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end,
|
||||
_on_dispense = function(_, pos, _, _, dir)
|
||||
throw_xp_bottle(vector.add(pos, vector.multiply(dir, 0.51)), dir, 10)
|
||||
function mcl_experience.register_on_add_xp(func, priority)
|
||||
table.insert(mcl_experience.on_add_xp, {func = func, priority = priority or 0})
|
||||
end
|
||||
|
||||
-- callbacks
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
caches[player] = {
|
||||
last_time = get_time(),
|
||||
}
|
||||
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
hud_bars[player] = player:hud_add({
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.5, y = 1},
|
||||
offset = {x = (-9 * 28) - 3, y = -(48 + 24 + 16 - 5)},
|
||||
scale = {x = 2.8, y = 3.0},
|
||||
alignment = {x = 1, y = 1},
|
||||
z_index = 11,
|
||||
})
|
||||
|
||||
hud_levels[player] = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = {x = 0.5, y = 1},
|
||||
number = 0x80FF20,
|
||||
offset = {x = 0, y = -(48 + 24 + 24)},
|
||||
z_index = 12,
|
||||
})
|
||||
end
|
||||
|
||||
mcl_experience.update(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
hud_bars[player] = nil
|
||||
hud_levels[player] = nil
|
||||
caches[player] = nil
|
||||
end)
|
||||
|
||||
minetest.register_on_dieplayer(function(player)
|
||||
if not minetest.settings:get_bool("mcl_keepInventory", false) then
|
||||
mcl_experience.throw_xp(player:get_pos(), mcl_experience.get_xp(player))
|
||||
mcl_experience.set_xp(player, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
table.sort(mcl_experience.on_add_xp, function(a, b) return a.priority < b.priority end)
|
||||
end)
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
local size_min, size_max = 20, 59
|
||||
local delta_size = size_max - size_min
|
||||
|
||||
local size_to_xp = {
|
||||
{-32768, 2}, -- 1
|
||||
{ 3, 6}, -- 2
|
||||
{ 7, 16}, -- 3
|
||||
{ 17, 36}, -- 4
|
||||
{ 37, 72}, -- 5
|
||||
{ 73, 148}, -- 6
|
||||
{ 149, 306}, -- 7
|
||||
{ 307, 616}, -- 8
|
||||
{ 617, 1236}, -- 9
|
||||
{ 1237, 2476}, -- 10
|
||||
{ 2477, 32767} -- 11
|
||||
}
|
||||
|
||||
local function xp_to_size(xp)
|
||||
local i, l = 1, #size_to_xp
|
||||
|
||||
while xp > size_to_xp[i][1] and i < l do
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return ((i - 1) / (l - 1) * delta_size + size_min) / 100
|
||||
end
|
||||
|
||||
local max_orb_age = 300 -- seconds
|
||||
local gravity = vector.new(0, -((tonumber(minetest.settings:get("movement_gravity"))) or 9.81), 0)
|
||||
|
||||
local collector, pos, pos2
|
||||
local direction, distance, player_velocity, goal
|
||||
local currentvel, acceleration, multiplier, velocity
|
||||
local node, vel, def
|
||||
local is_moving, is_slippery, slippery, slip_factor
|
||||
local size
|
||||
local function xp_step(self, dtime)
|
||||
--if item set to be collected then only execute go to player
|
||||
if self.collected == true then
|
||||
if not self.collector then
|
||||
self.collected = false
|
||||
return
|
||||
end
|
||||
collector = minetest.get_player_by_name(self.collector)
|
||||
if collector and collector:get_hp() > 0 and vector.distance(self.object:get_pos(),collector:get_pos()) < 7.25 then
|
||||
self.object:set_acceleration(vector.new(0,0,0))
|
||||
self.disable_physics(self)
|
||||
--get the variables
|
||||
pos = self.object:get_pos()
|
||||
pos2 = collector:get_pos()
|
||||
|
||||
player_velocity = collector:get_velocity() or collector:get_player_velocity()
|
||||
|
||||
pos2.y = pos2.y + 0.8
|
||||
|
||||
direction = vector.direction(pos,pos2)
|
||||
distance = vector.distance(pos2,pos)
|
||||
multiplier = distance
|
||||
if multiplier < 1 then
|
||||
multiplier = 1
|
||||
end
|
||||
goal = vector.multiply(direction,multiplier)
|
||||
currentvel = self.object:get_velocity()
|
||||
|
||||
if distance > 1 then
|
||||
multiplier = 20 - distance
|
||||
velocity = vector.multiply(direction,multiplier)
|
||||
goal = velocity
|
||||
acceleration = vector.new(goal.x-currentvel.x,goal.y-currentvel.y,goal.z-currentvel.z)
|
||||
self.object:add_velocity(vector.add(acceleration,player_velocity))
|
||||
elseif distance < 0.8 then
|
||||
mcl_experience.add_xp(collector, self._xp)
|
||||
self.object:remove()
|
||||
end
|
||||
return
|
||||
else
|
||||
self.collector = nil
|
||||
self.enable_physics(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self.age = self.age + dtime
|
||||
if self.age > max_orb_age then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
pos = self.object:get_pos()
|
||||
|
||||
if pos then
|
||||
node = minetest.get_node_or_nil({
|
||||
x = pos.x,
|
||||
y = pos.y -0.25,
|
||||
z = pos.z
|
||||
})
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove nodes in 'ignore'
|
||||
if node and node.name == "ignore" then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
if not self.physical_state then
|
||||
return -- Don't do anything
|
||||
end
|
||||
|
||||
-- Slide on slippery nodes
|
||||
vel = self.object:get_velocity()
|
||||
def = node and minetest.registered_nodes[node.name]
|
||||
is_moving = (def and not def.walkable) or
|
||||
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
|
||||
is_slippery = false
|
||||
|
||||
if def and def.walkable then
|
||||
slippery = minetest.get_item_group(node.name, "slippery")
|
||||
is_slippery = slippery ~= 0
|
||||
if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
|
||||
-- Horizontal deceleration
|
||||
slip_factor = 4.0 / (slippery + 4)
|
||||
self.object:set_acceleration({
|
||||
x = -vel.x * slip_factor,
|
||||
y = 0,
|
||||
z = -vel.z * slip_factor
|
||||
})
|
||||
elseif vel.y == 0 then
|
||||
is_moving = false
|
||||
end
|
||||
end
|
||||
|
||||
if self.moving_state == is_moving and self.slippery_state == is_slippery then
|
||||
-- Do not update anything until the moving state changes
|
||||
return
|
||||
end
|
||||
|
||||
self.moving_state = is_moving
|
||||
self.slippery_state = is_slippery
|
||||
|
||||
if is_moving then
|
||||
self.object:set_acceleration(gravity)
|
||||
else
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
self.object:set_velocity({x = 0, y = 0, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_experience:orb", {
|
||||
initial_properties = {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
|
||||
visual = "sprite",
|
||||
visual_size = {x = 0.4, y = 0.4},
|
||||
textures = {name="mcl_experience_orb.png", animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0}},
|
||||
spritediv = {x = 1, y = 14},
|
||||
initial_sprite_basepos = {x = 0, y = 0},
|
||||
is_visible = true,
|
||||
pointable = false,
|
||||
static_save = false,
|
||||
},
|
||||
moving_state = true,
|
||||
slippery_state = false,
|
||||
physical_state = true,
|
||||
-- Item expiry
|
||||
age = 0,
|
||||
-- Pushing item out of solid nodes
|
||||
force_out = nil,
|
||||
force_out_start = nil,
|
||||
--Collection Variables
|
||||
collectable = false,
|
||||
try_timer = 0,
|
||||
collected = false,
|
||||
delete_timer = 0,
|
||||
radius = 4,
|
||||
|
||||
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
self.object:set_velocity(vector.new(
|
||||
math.random(-2,2)*math.random(),
|
||||
math.random(2,5),
|
||||
math.random(-2,2)*math.random()
|
||||
))
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
self.object:set_velocity({x = 0, y = 2, z = 0})
|
||||
self.object:set_acceleration(gravity)
|
||||
local xp = tonumber(staticdata)
|
||||
self._xp = xp
|
||||
size = xp_to_size(xp)
|
||||
self.object:set_properties({
|
||||
visual_size = {x = size, y = size},
|
||||
glow = 14,
|
||||
})
|
||||
self.object:set_sprite({x=1,y=math.random(1,14)}, 14, 0.05, false)
|
||||
end,
|
||||
|
||||
enable_physics = function(self)
|
||||
if not self.physical_state then
|
||||
self.physical_state = true
|
||||
self.object:set_properties({physical = true})
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration(gravity)
|
||||
end
|
||||
end,
|
||||
|
||||
disable_physics = function(self)
|
||||
if self.physical_state then
|
||||
self.physical_state = false
|
||||
self.object:set_properties({physical = false})
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
end
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
xp_step(self, dtime)
|
||||
end,
|
||||
})
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
|
@ -1,5 +1,6 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local F = minetest.formspec_escape
|
||||
local C = minetest.colorize
|
||||
|
||||
-- Prepare player info table
|
||||
local players = {}
|
||||
|
@ -289,6 +290,19 @@ filtername["inv"] = S("Survival Inventory")
|
|||
bg["default"] = dark_bg
|
||||
end]]
|
||||
|
||||
local function get_stack_size(player)
|
||||
return player:get_meta():get_int("mcl_inventory:switch_stack")
|
||||
end
|
||||
|
||||
local function set_stack_size(player, n)
|
||||
player:get_meta():set_int("mcl_inventory:switch_stack", n)
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function (player)
|
||||
if get_stack_size(player) == 0 then
|
||||
set_stack_size(player, 64)
|
||||
end
|
||||
end)
|
||||
|
||||
function mcl_inventory.set_creative_formspec(player, start_i, pagenum, inv_size, show, page, filter)
|
||||
--reset_menu_item_bg()
|
||||
|
@ -349,6 +363,8 @@ function mcl_inventory.set_creative_formspec(player, start_i, pagenum, inv_size,
|
|||
armor_slot_imgs = armor_slot_imgs .. "image[5.5,2.75;1,1;mcl_inventory_empty_armor_slot_boots.png]"
|
||||
end
|
||||
|
||||
local stack_size = get_stack_size(player)
|
||||
|
||||
-- Survival inventory slots
|
||||
main_list = "list[current_player;main;0,3.75;9,3;9]"..
|
||||
mcl_formspec.get_itemslot_bg(0,3.75,9,3)..
|
||||
|
@ -376,7 +392,11 @@ function mcl_inventory.set_creative_formspec(player, start_i, pagenum, inv_size,
|
|||
-- achievements button
|
||||
"image_button[9,4;1,1;mcl_achievements_button.png;__mcl_achievements;]"..
|
||||
--"style_type[image_button;border=;bgimg=;bgimg_pressed=]"..
|
||||
"tooltip[__mcl_achievements;"..F(S("Achievements")).."]"
|
||||
"tooltip[__mcl_achievements;"..F(S("Achievements")).."]"..
|
||||
-- switch stack size button
|
||||
"image_button[9,5;1,1;default_apple.png;__switch_stack;]"..
|
||||
"label[9.4,5.4;".. F(C("#FFFFFF", stack_size ~= 1 and stack_size or "")) .."]"..
|
||||
"tooltip[__switch_stack;"..F(S("Switch stack size")).."]"
|
||||
|
||||
-- For shortcuts
|
||||
listrings = listrings ..
|
||||
|
@ -417,8 +437,7 @@ function mcl_inventory.set_creative_formspec(player, start_i, pagenum, inv_size,
|
|||
return
|
||||
"style["..this_tab..";border=false;bgimg=;bgimg_pressed=]"..
|
||||
"item_image_button[" .. boffset[this_tab] ..";1,1;"..tab_icon[this_tab]..";"..this_tab..";]"..
|
||||
"image[" .. offset[this_tab] .. ";1.5,1.44;" .. bg_img .. "]" ..
|
||||
"image[" .. boffset[this_tab] .. ";1,1;crafting_creative_marker.png]"
|
||||
"image[" .. offset[this_tab] .. ";1.5,1.44;" .. bg_img .. "]"
|
||||
end
|
||||
local caption = ""
|
||||
if name ~= "inv" and filtername[name] then
|
||||
|
@ -545,6 +564,12 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||
elseif fields.search and not fields.creative_next and not fields.creative_prev then
|
||||
set_inv_search(string.lower(fields.search),player)
|
||||
page = "nix"
|
||||
elseif fields.__switch_stack then
|
||||
local switch = 1
|
||||
if get_stack_size(player) == 1 then
|
||||
switch = 64
|
||||
end
|
||||
set_stack_size(player, switch)
|
||||
end
|
||||
|
||||
if page then
|
||||
|
@ -668,3 +693,11 @@ minetest.register_on_joinplayer(function(player)
|
|||
init(player)
|
||||
mcl_inventory.set_creative_formspec(player, 0, 1, nil, false, "nix", "")
|
||||
end)
|
||||
|
||||
minetest.register_on_player_inventory_action(function(player, action, inventory, inventory_info)
|
||||
if minetest.is_creative_enabled(player:get_player_name()) and get_stack_size(player) == 64 and action == "put" and inventory_info.listname == "main" then
|
||||
local stack = inventory_info.stack
|
||||
stack:set_count(stack:get_stack_max())
|
||||
player:get_inventory():set_stack("main", inventory_info.index, stack)
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# mcl_title
|
||||
|
||||
Allow mods to show messages in the hud of players.
|
||||
|
||||
## mcl_title.set(player, type, data)
|
||||
|
||||
Show a hud message of `type` to player `player` with `data` as params.
|
||||
|
||||
The element will stay for the per-player param `stay` or `data.stay` (in gametick which is 1/20 second).
|
||||
|
||||
Here is a usage exemple:
|
||||
|
||||
```lua
|
||||
--show a title in the HUD with minecraft color "gold"
|
||||
mcl_title.set(player, "title", {text="dummy text", color="gold"})
|
||||
|
||||
--show a subtitle in the HUD with hex color "#612D2D"
|
||||
mcl_title.set(player, "subtitle", {text="dummy subtitle", color="#612D2D"})
|
||||
|
||||
--show an actionbar in the HUD (above the hotbar) with minecraft color "red"
|
||||
mcl_title.set(player, "subtitle", {text="dummy actionbar", color="red"})
|
||||
|
||||
--show a title in the HUD with minecraft color "gold" staying for 3 seconds (override stay setting)
|
||||
mcl_title.set(player, "title", {text="dummy text", color="gold", stay=60})
|
||||
```
|
||||
|
||||
## mcl_title.remove(player, type)
|
||||
|
||||
Hide HUD element of type `type` for player `player`.
|
||||
|
||||
## mcl_title.clear(player)
|
||||
|
||||
Remove every title/subtitle/actionbar from a player.
|
||||
Basicaly run `mcl_title.remove(player, type)` for every type.
|
||||
|
||||
## mcl_title.params_set(player, params)
|
||||
|
||||
Allow mods to set `stay` and upcomming `fadeIn`/`fadeOut` params.
|
||||
|
||||
```lua
|
||||
mcl_title.params_set(player, {stay = 600}) --elements with no 'data.stay' field will stay during 30s (600/20)
|
||||
```
|
||||
|
||||
## mcl_title.params_get(player)
|
||||
|
||||
Get `stay` and upcomming `fadeIn` and `fadeOut` params of a player as a table.
|
||||
|
||||
```lua
|
||||
mcl_title.params_get(player)
|
||||
```
|
|
@ -0,0 +1,236 @@
|
|||
--Based on:
|
||||
--https://www.digminecraft.com/game_commands/title_command.php
|
||||
--https://youtu.be/oVrtQRO2hpY
|
||||
|
||||
--TODO: use SSCSM to reduce lag and network trafic (just send modchannel messages)
|
||||
--TODO: fadeIn and fadeOut animation (needs engine change: SSCSM or native support)
|
||||
--TODO: allow obfuscating text (needs engine change: SSCSM or native support)
|
||||
--TODO: allow colorizing and styling of part of the text (NEEDS ENGINE CHANGE!!!)
|
||||
--TODO: exactly mc like layout
|
||||
|
||||
--Note that the table storing timeouts use playername as index insteed of player objects (faster)
|
||||
--This is intended in order to speedup the process of removing HUD elements the the timeout is up
|
||||
|
||||
local huds_idx = {}
|
||||
|
||||
local hud_hide_timeouts = {}
|
||||
|
||||
hud_hide_timeouts.title = {}
|
||||
hud_hide_timeouts.subtitle = {}
|
||||
hud_hide_timeouts.actionbar = {}
|
||||
|
||||
huds_idx.title = {}
|
||||
huds_idx.subtitle = {}
|
||||
huds_idx.actionbar = {}
|
||||
|
||||
mcl_title = {}
|
||||
mcl_title.defaults = {fadein = 10, stay = 70, fadeout = 20}
|
||||
mcl_title.layout = {}
|
||||
mcl_title.layout.title = {position = {x = 0.5, y = 0.5}, alignment = {x = 0, y = -1.3}, size = 7}
|
||||
mcl_title.layout.subtitle = {position = {x = 0.5, y = 0.5}, alignment = {x = 0, y = 1.7}, size = 4}
|
||||
mcl_title.layout.actionbar = {position = {x = 0.5, y = 1}, alignment = {x = 0, y = 0}, size = 1}
|
||||
|
||||
local get_color = mcl_util.get_color
|
||||
|
||||
--local string = string
|
||||
local pairs = pairs
|
||||
|
||||
local function gametick_to_secondes(gametick)
|
||||
if gametick then
|
||||
return gametick / 20
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--https://github.com/minetest/minetest/blob/b3b075ea02034306256b486dd45410aa765f035a/doc/lua_api.txt#L8477
|
||||
--[[
|
||||
local function style_to_bits(bold, italic)
|
||||
if bold then
|
||||
if italic then
|
||||
return 3
|
||||
else
|
||||
return 1
|
||||
end
|
||||
else
|
||||
if italic then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
--PARAMS SYSTEM
|
||||
local player_params = {}
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
--local playername = player:get_player_name()
|
||||
player_params[player] = {
|
||||
stay = mcl_title.defaults.stay,
|
||||
--fadeIn = mcl_title.defaults.fadein,
|
||||
--fadeOut = mcl_title.defaults.fadeout,
|
||||
}
|
||||
local _, hex_color = get_color("white")
|
||||
huds_idx.title[player] = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = mcl_title.layout.title.position,
|
||||
alignment = mcl_title.layout.title.alignment,
|
||||
text = "",
|
||||
--style = 0,
|
||||
size = {x = mcl_title.layout.title.size},
|
||||
number = hex_color,
|
||||
z_index = 100,
|
||||
})
|
||||
huds_idx.subtitle[player] = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = mcl_title.layout.subtitle.position,
|
||||
alignment = mcl_title.layout.subtitle.alignment,
|
||||
text = "",
|
||||
--style = 0,
|
||||
size = {x = mcl_title.layout.subtitle.size},
|
||||
number = hex_color,
|
||||
z_index = 100,
|
||||
})
|
||||
huds_idx.actionbar[player] = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = mcl_title.layout.actionbar.position,
|
||||
offset = {x = 0, y = -210},
|
||||
alignment = mcl_title.layout.actionbar.alignment,
|
||||
--style = 0,
|
||||
text = "",
|
||||
size = {x = mcl_title.layout.actionbar.size},
|
||||
number = hex_color,
|
||||
z_index = 100,
|
||||
})
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local playername = player:get_player_name()
|
||||
|
||||
--remove player params from the list
|
||||
player_params[player] = nil
|
||||
|
||||
--remove HUD idx from the list (HUD elements are removed by the engine)
|
||||
huds_idx.title[player] = nil
|
||||
huds_idx.subtitle[player] = nil
|
||||
huds_idx.actionbar[player] = nil
|
||||
|
||||
--remove timers from list
|
||||
hud_hide_timeouts.title[playername] = nil
|
||||
hud_hide_timeouts.subtitle[playername] = nil
|
||||
hud_hide_timeouts.actionbar[playername] = nil
|
||||
end)
|
||||
|
||||
function mcl_title.params_set(player, data)
|
||||
player_params[player] = {
|
||||
stay = data.stay or mcl_title.defaults.stay,
|
||||
--fadeIn = data.fadeIn or mcl_title.defaults.fadein,
|
||||
--fadeOut = data.fadeOut or mcl_title.defaults.fadeout,
|
||||
}
|
||||
end
|
||||
|
||||
function mcl_title.params_get(player)
|
||||
return player_params[player]
|
||||
end
|
||||
|
||||
--API FUNCTIONS
|
||||
|
||||
function mcl_title.set(player, type, data)
|
||||
if not data.color then
|
||||
data.color = "white"
|
||||
end
|
||||
local _, hex_color = get_color(data.color)
|
||||
if not hex_color then
|
||||
return false
|
||||
end
|
||||
|
||||
player:hud_change(huds_idx[type][player], "text", data.text)
|
||||
player:hud_change(huds_idx[type][player], "number", hex_color)
|
||||
|
||||
--apply bold and italic
|
||||
--player:hud_change(huds_idx[type][player], "style", style_to_bits(data.bold, data.italic))
|
||||
|
||||
hud_hide_timeouts[type][player:get_player_name()] = gametick_to_secondes(data.stay) or gametick_to_secondes(mcl_title.params_get(player).stay)
|
||||
return true
|
||||
end
|
||||
|
||||
function mcl_title.remove(player, type)
|
||||
if player then
|
||||
player:hud_change(huds_idx[type][player], "text", "")
|
||||
--player:hud_change(huds_idx[type][player], "style", 0) --no styling
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_title.clear(player)
|
||||
mcl_title.remove(player, "title")
|
||||
mcl_title.remove(player, "subtitle")
|
||||
mcl_title.remove(player, "actionbar")
|
||||
end
|
||||
|
||||
minetest.register_on_dieplayer(function(player)
|
||||
mcl_title.clear(player)
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
local new_timeouts = {
|
||||
title = {},
|
||||
subtitle = {},
|
||||
actionbar = {},
|
||||
}
|
||||
for element, content in pairs(hud_hide_timeouts) do
|
||||
for name, timeout in pairs(content) do
|
||||
timeout = timeout - dtime
|
||||
if timeout <= 0 then
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.remove(player, element)
|
||||
else
|
||||
new_timeouts[element][name] = timeout
|
||||
end
|
||||
end
|
||||
end
|
||||
hud_hide_timeouts = new_timeouts
|
||||
end)
|
||||
|
||||
|
||||
--DEBUG STUFF!!
|
||||
--[[
|
||||
minetest.register_chatcommand("title", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.set(player, "title", {text=param, color="gold", bold=true, italic=true})
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("subtitle", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.set(player, "subtitle", {text=param, color="gold"})
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("actionbar", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.set(player, "actionbar", {text=param, color="gold"})
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("timeout", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.params_set(player, {stay = 600})
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("all", {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
mcl_title.params_set(player, {stay = 600})
|
||||
mcl_title.set(player, "title", {text=param, color="gold"})
|
||||
mcl_title.set(player, "subtitle", {text=param, color="gold"})
|
||||
mcl_title.set(player, "actionbar", {text=param, color="gold"})
|
||||
end,
|
||||
})
|
||||
]]
|
|
@ -0,0 +1,4 @@
|
|||
name = mcl_title
|
||||
description = Add an API to add in HUD title
|
||||
depends = mcl_colors
|
||||
author = AFCMS
|
|
@ -1,7 +0,0 @@
|
|||
# mcl_temp_message
|
||||
|
||||
Allow mods to show short messages in the hud of players.
|
||||
|
||||
## mcl_tmp_message.message(player, message)
|
||||
|
||||
Show above the hotbar a hud message <message> to player <player>.
|
|
@ -1,44 +0,0 @@
|
|||
mcl_tmp_message = {}
|
||||
|
||||
local huds = {}
|
||||
local hud_hide_timeouts = {}
|
||||
|
||||
function mcl_tmp_message.message(player, message)
|
||||
local name = player:get_player_name()
|
||||
player:hud_change(huds[name], "text", message)
|
||||
hud_hide_timeouts[name] = 3
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
huds[player:get_player_name()] = player:hud_add({
|
||||
hud_elem_type = "text",
|
||||
position = {x=0.5, y=1},
|
||||
offset = {x = 0, y = -210},
|
||||
alignment = {x=0, y=0},
|
||||
number = 0xFFFFFF ,
|
||||
text = "",
|
||||
z_index = 100,
|
||||
})
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name = player:get_player_name()
|
||||
huds[name] = nil
|
||||
hud_hide_timeouts[name] = nil
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
local new_timeouts = {}
|
||||
for name, timeout in pairs(hud_hide_timeouts) do
|
||||
timeout = timeout - dtime
|
||||
if timeout <= 0 then
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
player:hud_change(huds[name], "text", "")
|
||||
end
|
||||
else
|
||||
new_timeouts[name] = timeout
|
||||
end
|
||||
end
|
||||
hud_hide_timeouts = new_timeouts
|
||||
end)
|
|
@ -1,3 +0,0 @@
|
|||
name = mcl_tmp_message
|
||||
author = Fleckenstein
|
||||
description = A simple API to show a temporary message to a player
|
|
@ -168,6 +168,56 @@ local dispenserdef = {
|
|||
end
|
||||
|
||||
inv:set_stack("main", stack_id, stack)
|
||||
|
||||
-- Use shears on sheeps
|
||||
elseif igroups.shears then
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(droppos, 1)) do
|
||||
local entity = obj:get_luaentity()
|
||||
if entity and not entity.child and not entity.gotten then
|
||||
local entname = entity.name
|
||||
local pos = obj:get_pos()
|
||||
local used, texture = false
|
||||
if entname == "mobs_mc:sheep" then
|
||||
minetest.add_item(pos, entity.drops[2].name .. " " .. math.random(1, 3))
|
||||
if not entity.color then
|
||||
entity.color = "unicolor_white"
|
||||
end
|
||||
entity.base_texture = { "blank.png", "mobs_mc_sheep.png" }
|
||||
texture = entity.base_texture
|
||||
entity.drops = {
|
||||
{ name = mobs_mc.items.mutton_raw, chance = 1, min = 1, max = 2 },
|
||||
}
|
||||
used = true
|
||||
elseif entname == "mobs_mc:snowman" then
|
||||
texture = {
|
||||
"mobs_mc_snowman.png",
|
||||
"blank.png", "blank.png",
|
||||
"blank.png", "blank.png",
|
||||
"blank.png", "blank.png",
|
||||
}
|
||||
used = true
|
||||
elseif entname == "mobs_mc:mooshroom" then
|
||||
local droppos = vector.offset(pos, 0, 1.4, 0)
|
||||
if entity.base_texture[1] == "mobs_mc_mooshroom_brown.png" then
|
||||
minetest.add_item(droppos, mobs_mc.items.mushroom_brown .. " 5")
|
||||
else
|
||||
minetest.add_item(droppos, mobs_mc.items.mushroom_red .. " 5")
|
||||
end
|
||||
obj = mcl_util.replace_mob(obj, "mobs_mc:cow")
|
||||
entity = obj:get_luaentity()
|
||||
used = true
|
||||
end
|
||||
if used then
|
||||
obj:set_properties({ textures = texture })
|
||||
entity.gotten = true
|
||||
minetest.sound_play("mcl_tools_shears_cut", { pos = pos }, true)
|
||||
stack:add_wear(65535 / stackdef._mcl_diggroups.shearsy.uses)
|
||||
inv:set_stack("main", stack_id, stack)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawn Egg
|
||||
elseif igroups.spawn_egg then
|
||||
-- Spawn mob
|
||||
|
|
|
@ -53,6 +53,15 @@ local function get_consumed_materials(tool, material)
|
|||
return materials_used
|
||||
end
|
||||
|
||||
local function contains(table, value)
|
||||
for _, i in pairs(table) do
|
||||
if i == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Given 2 input stacks, tells you which is the tool and which is the material.
|
||||
-- Returns ("tool", input1, input2) if input1 is tool and input2 is material.
|
||||
-- Returns ("material", input2, input1) if input1 is material and input2 is tool.
|
||||
|
@ -60,9 +69,15 @@ end
|
|||
local function distinguish_tool_and_material(input1, input2)
|
||||
local def1 = input1:get_definition()
|
||||
local def2 = input2:get_definition()
|
||||
if def1.type == "tool" and def1._repair_material then
|
||||
local r1 = def1._repair_material
|
||||
local r2 = def2._repair_material
|
||||
if def1.type == "tool" and r1 and type(r1) == "table" and contains(r1, input2) then
|
||||
return "tool", input1, input2
|
||||
elseif def2.type == "tool" and def2._repair_material then
|
||||
elseif def2.type == "tool" and r2 and type(r2) == "table" and contains(r2, input1) then
|
||||
return "material", input2, input1
|
||||
elseif def1.type == "tool" and r1 then
|
||||
return "tool", input1, input2
|
||||
elseif def2.type == "tool" and r2 then
|
||||
return "material", input2, input1
|
||||
else
|
||||
return nil
|
||||
|
@ -121,12 +136,29 @@ local function update_anvil_slots(meta)
|
|||
local distinguished, tool, material = distinguish_tool_and_material(input1, input2)
|
||||
if distinguished then
|
||||
local tooldef = tool:get_definition()
|
||||
local repair = tooldef._repair_material
|
||||
local has_correct_material = false
|
||||
if string.sub(tooldef._repair_material, 1, 6) == "group:" then
|
||||
has_correct_material = minetest.get_item_group(material:get_name(), string.sub(tooldef._repair_material, 7)) ~= 0
|
||||
elseif material:get_name() == tooldef._repair_material then
|
||||
local material_name = material:get_name()
|
||||
if type(repair) == "string" then
|
||||
if string.sub(repair, 1, 6) == "group:" then
|
||||
has_correct_material = minetest.get_item_group(material_name, string.sub(repair, 7)) ~= 0
|
||||
elseif material_name == repair then
|
||||
has_correct_material = true
|
||||
end
|
||||
else
|
||||
if contains(repair, material_name) then
|
||||
has_correct_material = true
|
||||
else
|
||||
for _, r in pairs(repair) do
|
||||
if string.sub(r, 1, 6) == "group:" then
|
||||
if minetest.get_item_group(material_name, string.sub(r, 7)) ~= 0 then
|
||||
has_correct_material = true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if has_correct_material and tool:get_wear() > 0 then
|
||||
local materials_used = get_consumed_materials(tool, material)
|
||||
local new_wear = calculate_repair(tool:get_wear(), MAX_WEAR, MATERIAL_TOOL_REPAIR_BOOST[materials_used])
|
||||
|
@ -284,6 +316,12 @@ local function damage_anvil_by_falling(pos, distance)
|
|||
end
|
||||
end
|
||||
|
||||
local anvilbox = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -8 / 16, -8 / 16, -6 / 16, 8 / 16, 8 / 16, 6 / 16 },
|
||||
},
|
||||
}
|
||||
local anvildef = {
|
||||
groups = {pickaxey=1, falling_node=1, falling_node_damage=1, crush_after_fall=1, deco_block=1, anvil=1},
|
||||
tiles = {"mcl_anvils_anvil_top_damaged_0.png^[transformR90", "mcl_anvils_anvil_base.png", "mcl_anvils_anvil_side.png"},
|
||||
|
@ -297,11 +335,14 @@ local anvildef = {
|
|||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, 2/16, -5/16, 8/16, 8/16, 5/16}, -- top
|
||||
{-5/16, -4/16, -2/16, 5/16, 5/16, 2/16}, -- middle
|
||||
{-8/16, -8/16, -5/16, 8/16, -4/16, 5/16}, -- base
|
||||
{ -6 / 16, -8 / 16, -6 / 16, 6 / 16, -4 / 16, 6 / 16 },
|
||||
{ -5 / 16, -4 / 16, -4 / 16, 5 / 16, -3 / 16, 4 / 16 },
|
||||
{ -4 / 16, -3 / 16, -2 / 16, 4 / 16, 2 / 16, 2 / 16 },
|
||||
{ -8 / 16, 2 / 16, -5 / 16, 8 / 16, 8 / 16, 5 / 16 },
|
||||
}
|
||||
},
|
||||
selection_box = anvilbox,
|
||||
collision_box = anvilbox,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
_mcl_blast_resistance = 1200,
|
||||
_mcl_hardness = 5,
|
||||
|
|
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 209 B After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 5.7 KiB |
|
@ -1,5 +1,5 @@
|
|||
name = mcl_armor
|
||||
author = stu
|
||||
description = Adds craftable armor that is visible to other players.
|
||||
depends = mcl_core, mcl_player, mcl_enchanting
|
||||
depends = mcl_core, mcl_player, mcl_enchanting, mcl_damage
|
||||
optional_depends = mcl_fire, ethereal, bakedclay
|
||||
|
|
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 409 B |
|
@ -119,8 +119,7 @@ local patterns = {
|
|||
|
||||
name = N("@1 Thing Charge"),
|
||||
type = "shapeless",
|
||||
-- TODO: Replace with enchanted golden apple
|
||||
{ e, "mcl_core:apple_gold", d },
|
||||
{ e, "mcl_core:apple_gold_enchanted", d },
|
||||
},
|
||||
["rhombus"] = {
|
||||
name = N("@1 Lozenge"),
|
||||
|
|
|
@ -12,15 +12,7 @@ Authors of media (textures)
|
|||
BlockMen (CC BY-SA 3.0)
|
||||
|
||||
This mod adds a bed to Minetest which allows to skip the night.
|
||||
To sleep, rightclick the bed. If playing in singleplayer mode the night gets skipped
|
||||
immediately. If playing multiplayer you get shown how many other players are in bed too,
|
||||
if all players are sleeping the night gets skipped. The night skip can be forced if more
|
||||
than 50% of the players are lying in bed and use this option.
|
||||
|
||||
Another feature is a controlled respawning. If you have slept in bed (not just lying in
|
||||
it) your respawn point is set to the beds location and you will respawn there after
|
||||
To sleep, rightclick the bed.
|
||||
Another feature is a controlled respawning. If you have slept in bed your respawn point is set to the beds location and you will respawn there after
|
||||
death.
|
||||
You can disable the respawn at beds by setting "enable_bed_respawn = false" in
|
||||
minetest.conf.
|
||||
You can disable the night skip feature by setting "enable_bed_night_skip = false" in
|
||||
minetest.conf or by using the /set command in-game.
|
||||
Use the mcl_playersSleepingPercentage setting to enable/disable night skipping or set a percentage of how many players need to sleep to skip the night.
|
|
@ -14,39 +14,34 @@ local worlds_mod = minetest.get_modpath("mcl_worlds")
|
|||
|
||||
local function get_look_yaw(pos)
|
||||
local n = minetest.get_node(pos)
|
||||
if n.param2 == 1 then
|
||||
return math.pi / 2, n.param2
|
||||
elseif n.param2 == 3 then
|
||||
return -math.pi / 2, n.param2
|
||||
elseif n.param2 == 0 then
|
||||
return math.pi, n.param2
|
||||
local param = n.param2
|
||||
if param == 1 then
|
||||
return math.pi / 2, param
|
||||
elseif param == 3 then
|
||||
return -math.pi / 2, param
|
||||
elseif param == 0 then
|
||||
return math.pi, param
|
||||
else
|
||||
return 0, n.param2
|
||||
return 0, param
|
||||
end
|
||||
end
|
||||
|
||||
local function players_in_bed_setting()
|
||||
return tonumber(minetest.settings:get("mcl_playersSleepingPercentage")) or 100
|
||||
end
|
||||
|
||||
local function is_night_skip_enabled()
|
||||
local enable_night_skip = minetest.settings:get_bool("enable_bed_night_skip")
|
||||
if enable_night_skip == nil then
|
||||
enable_night_skip = true
|
||||
end
|
||||
return enable_night_skip
|
||||
return players_in_bed_setting() <= 100
|
||||
end
|
||||
|
||||
local function check_in_beds(players)
|
||||
local in_bed = mcl_beds.player
|
||||
if not players then
|
||||
players = minetest.get_connected_players()
|
||||
end
|
||||
|
||||
for n, player in pairs(players) do
|
||||
local name = player:get_player_name()
|
||||
if not in_bed[name] then
|
||||
if player_in_bed <= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return #players > 0
|
||||
return players_in_bed_setting() <= (player_in_bed * 100) / #players
|
||||
end
|
||||
|
||||
-- These monsters do not prevent sleep
|
||||
|
@ -198,7 +193,7 @@ end
|
|||
local function update_formspecs(finished, ges)
|
||||
local ges = ges or #minetest.get_connected_players()
|
||||
local form_n = "size[12,5;true]"
|
||||
local all_in_bed = ges == player_in_bed
|
||||
local all_in_bed = players_in_bed_setting() <= (player_in_bed * 100) / ges
|
||||
local night_skip = is_night_skip_enabled()
|
||||
local button_leave = "button_exit[4,3;4,0.75;leave;"..F(S("Leave bed")).."]"
|
||||
local button_abort = "button_exit[4,3;4,0.75;leave;"..F(S("Abort sleep")).."]"
|
||||
|
@ -221,7 +216,13 @@ local function update_formspecs(finished, ges)
|
|||
form_n = form_n .. bg_sleep
|
||||
form_n = form_n .. button_abort
|
||||
else
|
||||
text = text .. "\n" .. S("You will fall asleep when all players are in bed.")
|
||||
local comment = "You will fall asleep when "
|
||||
if players_in_bed_setting() == 100 then
|
||||
comment = S(comment .. "all players are in bed.")
|
||||
else
|
||||
comment = S(comment .. "@1% of all players are in bed.", players_in_bed_setting())
|
||||
end
|
||||
text = text .. "\n" .. comment
|
||||
form_n = form_n .. bg_presleep
|
||||
form_n = form_n .. button_leave
|
||||
end
|
||||
|
@ -330,7 +331,7 @@ function mcl_beds.on_rightclick(pos, player, is_top)
|
|||
message = select(2, lay_down(player, ppos, other))
|
||||
end
|
||||
if message then
|
||||
mcl_tmp_message.message(player, message)
|
||||
mcl_title.set(player, "actionbar", {text=message, color="white", stay=60})
|
||||
end
|
||||
else
|
||||
lay_down(player, nil, nil, false)
|
||||
|
@ -349,7 +350,6 @@ function mcl_beds.on_rightclick(pos, player, is_top)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
-- Callbacks
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local meta = player:get_meta()
|
||||
|
|
|
@ -37,5 +37,6 @@ Players in bed: @1/@2=Spieler im Bett: @1/@2
|
|||
Note: Night skip is disabled.=Anmerkung: Überspringen der Nacht deaktiviert.
|
||||
You're sleeping.=Sie schlafen.
|
||||
You will fall asleep when all players are in bed.=Sie werden einschlafen, wenn alle Spieler im Bett sind.
|
||||
You will fall asleep when @1% of all players are in bed.=Sie werden einschlafen, wenn @1% der Spieler im Bett sind.
|
||||
You're in bed.=Sie sind im Bett.
|
||||
Allows you to sleep=Zum Einschafen
|
||||
|
|
|
@ -37,5 +37,6 @@ Players in bed: @1/@2=
|
|||
Note: Night skip is disabled.=
|
||||
You're sleeping.=
|
||||
You will fall asleep when all players are in bed.=
|
||||
You will fall asleep when @1% of all players are in bed.=
|
||||
You're in bed.=
|
||||
Allows you to sleep=
|
||||
|
|
|
@ -43,7 +43,7 @@ S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there'
|
|||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
_doc_items_usagehelp = S("To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it."),
|
||||
inventory_image = "mcl_bows_arrow_inv.png",
|
||||
groups = { ammo=1, ammo_bow=1, ammo_bow_regular=1 },
|
||||
groups = { ammo=1, ammo_bow=1, ammo_bow_regular=1, ammo_crossbow=1 },
|
||||
_on_dispense = function(itemstack, dispenserpos, droppos, dropnode, dropdir)
|
||||
-- Shoot arrow
|
||||
local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
|
||||
|
@ -324,8 +324,10 @@ function ARROW_ENTITY.on_step(self, dtime)
|
|||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
if self._piercing == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
mcl_bows_s = {}
|
||||
|
||||
-- local arrows = {
|
||||
-- ["mcl_bows:arrow"] = "mcl_bows:arrow_entity",
|
||||
-- }
|
||||
|
||||
local GRAVITY = 9.81
|
||||
local BOW_DURABILITY = 385
|
||||
|
||||
-- Charging time in microseconds
|
||||
local _BOW_CHARGE_TIME_HALF = 350000 -- bow level 1
|
||||
local _BOW_CHARGE_TIME_FULL = 900000 -- bow level 2 (full charge)
|
||||
|
||||
local BOW_CHARGE_TIME_HALF = 350000 -- bow level 1
|
||||
local BOW_CHARGE_TIME_FULL = 900000 -- bow level 2 (full charge)
|
||||
|
||||
-- Factor to multiply with player speed while player uses bow
|
||||
-- This emulates the sneak speed.
|
||||
local PLAYER_USE_CROSSBOW_SPEED = tonumber(minetest.settings:get("movement_speed_crouch")) / tonumber(minetest.settings:get("movement_speed_walk"))
|
||||
|
||||
-- TODO: Use Minecraft speed (ca. 53 m/s)
|
||||
-- Currently nerfed because at full speed the arrow would easily get out of the range of the loaded map.
|
||||
local BOW_MAX_SPEED = 68
|
||||
|
||||
local function play_load_sound(id, pos)
|
||||
minetest.sound_play("mcl_bows_crossbow_drawback_"..id, {pos=pos, max_hear_distance=12}, true)
|
||||
end
|
||||
|
||||
--[[ Store the charging state of each player.
|
||||
keys: player name
|
||||
value:
|
||||
nil = not charging or player not existing
|
||||
number: currently charging, the number is the time from minetest.get_us_time
|
||||
in which the charging has started
|
||||
]]
|
||||
local bow_load = {}
|
||||
|
||||
-- Another player table, this one stores the wield index of the bow being charged
|
||||
local bow_index = {}
|
||||
|
||||
function mcl_bows_s.shoot_arrow_crossbow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, crossbow_stack, collectable)
|
||||
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
|
||||
if power == nil then
|
||||
power = BOW_MAX_SPEED --19
|
||||
end
|
||||
if damage == nil then
|
||||
damage = 3
|
||||
end
|
||||
local knockback
|
||||
if crossbow_stack then
|
||||
local enchantments = mcl_enchanting.get_enchantments(crossbow_stack)
|
||||
if enchantments.piercing then
|
||||
obj:get_luaentity()._piercing = 1 * enchantments.piercing
|
||||
else
|
||||
obj:get_luaentity()._piercing = 0
|
||||
end
|
||||
end
|
||||
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
|
||||
obj:set_acceleration({x=0, y=-GRAVITY, z=0})
|
||||
obj:set_yaw(yaw-math.pi/2)
|
||||
local le = obj:get_luaentity()
|
||||
le._shooter = shooter
|
||||
le._source_object = shooter
|
||||
le._damage = damage
|
||||
le._is_critical = is_critical
|
||||
le._startpos = pos
|
||||
le._knockback = knockback
|
||||
le._collectable = collectable
|
||||
minetest.sound_play("mcl_bows_crossbow_shoot", {pos=pos, max_hear_distance=16}, true)
|
||||
if shooter and shooter:is_player() then
|
||||
if obj:get_luaentity().player == "" then
|
||||
obj:get_luaentity().player = shooter
|
||||
end
|
||||
obj:get_luaentity().node = shooter:get_inventory():get_stack("main", 1):get_name()
|
||||
end
|
||||
return obj
|
||||
end
|
||||
|
||||
local function get_arrow(player)
|
||||
local inv = player:get_inventory()
|
||||
local arrow_stack, arrow_stack_id
|
||||
for i=1, inv:get_size("main") do
|
||||
local it = inv:get_stack("main", i)
|
||||
if not it:is_empty() and minetest.get_item_group(it:get_name(), "ammo_crossbow") ~= 0 then
|
||||
arrow_stack = it
|
||||
arrow_stack_id = i
|
||||
break
|
||||
end
|
||||
end
|
||||
return arrow_stack, arrow_stack_id
|
||||
end
|
||||
|
||||
local function player_shoot_arrow(wielditem, player, power, damage, is_critical)
|
||||
local has_multishot_enchantment = mcl_enchanting.has_enchantment(player:get_wielded_item(), "multishot")
|
||||
local arrow_itemstring = wielditem:get_meta():get("arrow")
|
||||
|
||||
if not arrow_itemstring then
|
||||
return false
|
||||
end
|
||||
|
||||
local playerpos = player:get_pos()
|
||||
local dir = player:get_look_dir()
|
||||
local yaw = player:get_look_horizontal()
|
||||
|
||||
if has_multishot_enchantment then
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, {x=dir.x, y=dir.y, z=dir.z + .2}, yaw, player, power, damage, is_critical, player:get_wielded_item(), false)
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, {x=dir.x, y=dir.y, z=dir.z - .2}, yaw, player, power, damage, is_critical, player:get_wielded_item(), false)
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, dir, yaw, player, power, damage, is_critical, player:get_wielded_item(), true)
|
||||
else
|
||||
mcl_bows_s.shoot_arrow_crossbow(arrow_itemstring, {x=playerpos.x,y=playerpos.y+1.5,z=playerpos.z}, dir, yaw, player, power, damage, is_critical, player:get_wielded_item(), true)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Bow item, uncharged state
|
||||
minetest.register_tool("mcl_bows:crossbow", {
|
||||
description = S("Corssbow"),
|
||||
_tt_help = S("Launches arrows"),
|
||||
_doc_items_longdesc = S("Bows are ranged weapons to shoot arrows at your foes.").."\n"..
|
||||
S("The speed and damage of the arrow increases the longer you charge. The regular damage of the arrow is between 1 and 9. At full charge, there's also a 20% of a critical hit, dealing 10 damage instead."),
|
||||
_doc_items_usagehelp = S("To use the bow, you first need to have at least one arrow anywhere in your inventory (unless in Creative Mode). Hold down the right mouse button to charge, release to shoot."),
|
||||
_doc_items_durability = BOW_DURABILITY,
|
||||
inventory_image = "mcl_bows_crossbow.png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 4,
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if player and not player:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, player, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
on_secondary_use = function(itemstack)
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
minetest.register_tool("mcl_bows:crossbow_loaded", {
|
||||
description = S("Corssbow"),
|
||||
_tt_help = S("Launches arrows"),
|
||||
_doc_items_longdesc = S("Corssbow are ranged weapons to shoot arrows at your foes.").."\n"..
|
||||
S("The speed and damage of the arrow increases the longer you charge. The regular damage of the arrow is between 1 and 9. At full charge, there's also a 20% of a critical hit, dealing 10 damage instead."),
|
||||
_doc_items_usagehelp = S("To use the corssbow, you first need to have at least one arrow anywhere in your inventory (unless in Creative Mode). Hold down the right mouse button to charge, release to load an arrow into the chamber, then to shoot press left mouse."),
|
||||
_doc_items_durability = BOW_DURABILITY,
|
||||
inventory_image = "mcl_bows_crossbow_3.png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 4,
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_place = function(itemstack, player, pointed_thing)
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if player and not player:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, player, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
on_secondary_use = function(itemstack)
|
||||
itemstack:get_meta():set_string("active", "true")
|
||||
return itemstack
|
||||
end,
|
||||
groups = {weapon=1,weapon_ranged=1,crossbow=1,enchantability=1},
|
||||
_mcl_uses = 326,
|
||||
})
|
||||
|
||||
-- Iterates through player inventory and resets all the bows in "charging" state back to their original stage
|
||||
local function reset_bows(player)
|
||||
local inv = player:get_inventory()
|
||||
local list = inv:get_list("main")
|
||||
for place, stack in pairs(list) do
|
||||
if stack:get_name() == "mcl_bows:crossbow" or stack:get_name() == "mcl_bows:crossbow_enchanted" then
|
||||
stack:get_meta():set_string("active", "")
|
||||
elseif stack:get_name()=="mcl_bows:crossbow_0" or stack:get_name()=="mcl_bows:crossbow_1" or stack:get_name()=="mcl_bows:crossbow_2" then
|
||||
stack:set_name("mcl_bows:crossbow")
|
||||
stack:get_meta():set_string("active", "")
|
||||
list[place] = stack
|
||||
elseif stack:get_name()=="mcl_bows:crossbow_0_enchanted" or stack:get_name()=="mcl_bows:crossbow_1_enchanted" or stack:get_name()=="mcl_bows:crossbow_2_enchanted" then
|
||||
stack:set_name("mcl_bows:crossbow_enchanted")
|
||||
stack:get_meta():set_string("active", "")
|
||||
list[place] = stack
|
||||
end
|
||||
end
|
||||
inv:set_list("main", list)
|
||||
end
|
||||
|
||||
-- Resets the bow charging state and player speed. To be used when the player is no longer charging the bow
|
||||
local function reset_bow_state(player, also_reset_bows)
|
||||
bow_load[player:get_player_name()] = nil
|
||||
bow_index[player:get_player_name()] = nil
|
||||
if minetest.get_modpath("playerphysics") then
|
||||
playerphysics.remove_physics_factor(player, "speed", "mcl_bows:use_crossbow")
|
||||
end
|
||||
if also_reset_bows then
|
||||
reset_bows(player)
|
||||
end
|
||||
end
|
||||
|
||||
-- Bow in charging state
|
||||
for level=0, 2 do
|
||||
minetest.register_tool("mcl_bows:crossbow_"..level, {
|
||||
description = S("Crossbow"),
|
||||
_doc_items_create_entry = false,
|
||||
inventory_image = "mcl_bows_crossbow_"..level..".png",
|
||||
wield_scale = mcl_vars.tool_wield_scale,
|
||||
stack_max = 1,
|
||||
range = 0, -- Pointing range to 0 to prevent punching with bow :D
|
||||
groups = {not_in_creative_inventory=1, not_in_craft_guide=1, bow=1, enchantability=1},
|
||||
-- Trick to disable digging as well
|
||||
on_use = function() return end,
|
||||
on_drop = function(itemstack, dropper, pos)
|
||||
reset_bow_state(dropper)
|
||||
itemstack:get_meta():set_string("active", "")
|
||||
if mcl_enchanting.is_enchanted(itemstack:get_name()) then
|
||||
itemstack:set_name("mcl_bows:crossbow_enchanted")
|
||||
else
|
||||
itemstack:set_name("mcl_bows:crossbow")
|
||||
end
|
||||
minetest.item_drop(itemstack, dropper, pos)
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
end,
|
||||
-- Prevent accidental interaction with itemframes and other nodes
|
||||
on_place = function(itemstack)
|
||||
return itemstack
|
||||
end,
|
||||
_mcl_uses = 385,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
controls.register_on_release(function(player, key, time)
|
||||
if key~="RMB" then return end
|
||||
--local inv = minetest.get_inventory({type="player", name=player:get_player_name()})
|
||||
local wielditem = player:get_wielded_item()
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_2" and get_arrow(player) or wielditem:get_name()=="mcl_bows:crossbow_2" and minetest.is_creative_enabled(player:get_player_name()) or wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" and get_arrow(player) or wielditem:get_name()=="mcl_bows:crossbow_2_enchanted" and minetest.is_creative_enabled(player:get_player_name()) then
|
||||
local arrow_stack, arrow_stack_id = get_arrow(player)
|
||||
local arrow_itemstring
|
||||
|
||||
if minetest.is_creative_enabled(player:get_player_name()) then
|
||||
if arrow_stack then
|
||||
arrow_itemstring = arrow_stack:get_name()
|
||||
else
|
||||
arrow_itemstring = "mcl_bows:arrow"
|
||||
end
|
||||
else
|
||||
arrow_itemstring = arrow_stack:get_name()
|
||||
arrow_stack:take_item()
|
||||
player:get_inventory():set_stack("main", arrow_stack_id, arrow_stack)
|
||||
end
|
||||
|
||||
wielditem:get_meta():set_string("arrow", arrow_itemstring)
|
||||
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_2" then
|
||||
wielditem:set_name("mcl_bows:crossbow_loaded")
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow_loaded_enchanted")
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
minetest.sound_play("mcl_bows_crossbow_load", {pos=player:get_pos(), max_hear_distance=16}, true)
|
||||
else
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end)
|
||||
|
||||
controls.register_on_press(function(player, key, time)
|
||||
if key~="LMB" then return end
|
||||
local wielditem = player:get_wielded_item()
|
||||
if wielditem:get_name()=="mcl_bows:crossbow_loaded" or wielditem:get_name()=="mcl_bows:crossbow_loaded_enchanted" then
|
||||
local enchanted = mcl_enchanting.is_enchanted(wielditem:get_name())
|
||||
local speed, damage
|
||||
local p_load = bow_load[player:get_player_name()]
|
||||
local charge
|
||||
-- Type sanity check
|
||||
if type(p_load) == "number" then
|
||||
charge = minetest.get_us_time() - p_load
|
||||
else
|
||||
-- In case something goes wrong ...
|
||||
-- Just assume minimum charge.
|
||||
charge = 0
|
||||
minetest.log("warning", "[mcl_bows] Player "..player:get_player_name().." fires arrow with non-numeric bow_load!")
|
||||
end
|
||||
charge = math.max(math.min(charge, BOW_CHARGE_TIME_FULL), 0)
|
||||
|
||||
local charge_ratio = charge / BOW_CHARGE_TIME_FULL
|
||||
charge_ratio = math.max(math.min(charge_ratio, 1), 0)
|
||||
|
||||
-- Calculate damage and speed
|
||||
-- Fully charged
|
||||
local is_critical = false
|
||||
speed = BOW_MAX_SPEED
|
||||
local r = math.random(1,5)
|
||||
if r == 1 then
|
||||
-- 20% chance for critical hit
|
||||
damage = 10
|
||||
is_critical = true
|
||||
else
|
||||
damage = 9
|
||||
end
|
||||
|
||||
local has_shot = player_shoot_arrow(wielditem, player, speed, damage, is_critical)
|
||||
|
||||
if enchanted then
|
||||
wielditem:set_name("mcl_bows:crossbow_enchanted")
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow")
|
||||
end
|
||||
|
||||
if has_shot and not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
local durability = BOW_DURABILITY
|
||||
local unbreaking = mcl_enchanting.get_enchantment(wielditem, "unbreaking")
|
||||
local multishot = mcl_enchanting.get_enchantment(wielditem, "multishot")
|
||||
if unbreaking > 0 then
|
||||
durability = durability * (unbreaking + 1)
|
||||
end
|
||||
if multishot then
|
||||
durability = durability / 3
|
||||
end
|
||||
wielditem:add_wear(65535/durability)
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end)
|
||||
|
||||
controls.register_on_hold(function(player, key, time)
|
||||
local name = player:get_player_name()
|
||||
local creative = minetest.is_creative_enabled(name)
|
||||
if key ~= "RMB" then
|
||||
return
|
||||
end
|
||||
--local inv = minetest.get_inventory({type="player", name=name})
|
||||
local wielditem = player:get_wielded_item()
|
||||
local enchantments = mcl_enchanting.get_enchantments(wielditem)
|
||||
if enchantments.quick_charge then
|
||||
BOW_CHARGE_TIME_HALF = _BOW_CHARGE_TIME_HALF - (enchantments.quick_charge * 0.13 * 1000000 * .5)
|
||||
BOW_CHARGE_TIME_FULL = _BOW_CHARGE_TIME_FULL - (enchantments.quick_charge * 0.13 * 1000000)
|
||||
else
|
||||
BOW_CHARGE_TIME_HALF = _BOW_CHARGE_TIME_HALF
|
||||
BOW_CHARGE_TIME_FULL = _BOW_CHARGE_TIME_FULL
|
||||
end
|
||||
|
||||
if bow_load[name] == nil and (wielditem:get_name()=="mcl_bows:crossbow" or wielditem:get_name()=="mcl_bows:crossbow_enchanted") and wielditem:get_meta():get("active") and (creative or get_arrow(player)) then
|
||||
local enchanted = mcl_enchanting.is_enchanted(wielditem:get_name())
|
||||
if enchanted then
|
||||
wielditem:set_name("mcl_bows:crossbow_0_enchanted")
|
||||
play_load_sound(0, player:get_pos())
|
||||
else
|
||||
wielditem:set_name("mcl_bows:crossbow_0")
|
||||
play_load_sound(0, player:get_pos())
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
if minetest.get_modpath("playerphysics") then
|
||||
-- Slow player down when using bow
|
||||
playerphysics.add_physics_factor(player, "speed", "mcl_bows:use_crossbow", PLAYER_USE_CROSSBOW_SPEED)
|
||||
end
|
||||
bow_load[name] = minetest.get_us_time()
|
||||
bow_index[name] = player:get_wield_index()
|
||||
else
|
||||
if player:get_wield_index() == bow_index[name] then
|
||||
if type(bow_load[name]) == "number" then
|
||||
if wielditem:get_name() == "mcl_bows:crossbow_0" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_HALF then
|
||||
wielditem:set_name("mcl_bows:crossbow_1")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_0_enchanted" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_HALF then
|
||||
wielditem:set_name("mcl_bows:crossbow_1_enchanted")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_1" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_FULL then
|
||||
wielditem:set_name("mcl_bows:crossbow_2")
|
||||
play_load_sound(2, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_1_enchanted" and minetest.get_us_time() - bow_load[name] >= BOW_CHARGE_TIME_FULL then
|
||||
wielditem:set_name("mcl_bows:crossbow_2_enchanted")
|
||||
play_load_sound(2, player:get_pos())
|
||||
end
|
||||
else
|
||||
if wielditem:get_name() == "mcl_bows:crossbow_0" or wielditem:get_name() == "mcl_bows:crossbow_1" or wielditem:get_name() == "mcl_bows:crossbow_2" then
|
||||
wielditem:set_name("mcl_bows:crossbow")
|
||||
play_load_sound(1, player:get_pos())
|
||||
elseif wielditem:get_name() == "mcl_bows:crossbow_0_enchanted" or wielditem:get_name() == "mcl_bows:crossbow_1_enchanted" or wielditem:get_name() == "mcl_bows:crossbow_2_enchanted" then
|
||||
wielditem:set_name("mcl_bows:crossbow_enchanted")
|
||||
play_load_sound(1, player:get_pos())
|
||||
end
|
||||
end
|
||||
player:set_wielded_item(wielditem)
|
||||
else
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
local wielditem = player:get_wielded_item()
|
||||
local wieldindex = player:get_wield_index()
|
||||
--local controls = player:get_player_control()
|
||||
if type(bow_load[name]) == "number" and ((wielditem:get_name()~="mcl_bows:crossbow_0" and wielditem:get_name()~="mcl_bows:crossbow_1" and wielditem:get_name()~="mcl_bows:crossbow_2" and wielditem:get_name()~="mcl_bows:crossbow_0_enchanted" and wielditem:get_name()~="mcl_bows:crossbow_1_enchanted" and wielditem:get_name()~="mcl_bows:crossbow_2_enchanted") or wieldindex ~= bow_index[name]) then
|
||||
reset_bow_state(player, true)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
reset_bows(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
reset_bow_state(player, true)
|
||||
end)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
output = "mcl_bows:crossbow",
|
||||
recipe = {
|
||||
{"mcl_core:stick", "mcl_core:iron_ingot", "mcl_core:stick"},
|
||||
{"mcl_mobitems:string", "mcl_bows:arrow", "mcl_mobitems:string"},
|
||||
{"", "mcl_core:stick", ""},
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
minetest.register_craft({
|
||||
type = "fuel",
|
||||
recipe = "group:bow",
|
||||
burntime = 15,
|
||||
})
|
||||
|
||||
-- Add entry aliases for the Help
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_0")
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_1")
|
||||
doc.add_entry_alias("tools", "mcl_bows:crossbow", "tools", "mcl_bows:crossbow_2")
|
||||
end
|
|
@ -1,5 +1,11 @@
|
|||
--Bow
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/arrow.lua")
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/bow.lua")
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/rocket.lua")
|
||||
|
||||
--Crossbow
|
||||
dofile(minetest.get_modpath("mcl_bows") .. "/crossbow.lua")
|
||||
|
||||
--Compatiblility with older MineClone worlds
|
||||
minetest.register_alias("mcl_throwing:bow", "mcl_bows:bow")
|
||||
minetest.register_alias("mcl_throwing:arrow", "mcl_bows:arrow")
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl None
|
||||
Ns 500
|
||||
Ka 0.8 0.8 0.8
|
||||
Kd 0.8 0.8 0.8
|
||||
Ks 0.8 0.8 0.8
|
||||
d 1
|
||||
illum 2
|
|
@ -0,0 +1,706 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local math = math
|
||||
local vector = vector
|
||||
|
||||
-- Time in seconds after which a stuck arrow is deleted
|
||||
local ARROW_TIMEOUT = 1
|
||||
-- Time after which stuck arrow is rechecked for being stuck
|
||||
local STUCK_RECHECK_TIME = 0.1
|
||||
|
||||
--local GRAVITY = 9.81
|
||||
|
||||
local YAW_OFFSET = -math.pi/2
|
||||
|
||||
local function dir_to_pitch(dir)
|
||||
--local dir2 = vector.normalize(dir)
|
||||
local xz = math.abs(dir.x) + math.abs(dir.z)
|
||||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
local function random_arrow_positions(positions, placement)
|
||||
if positions == "x" then
|
||||
return math.random(-4, 4)
|
||||
elseif positions == "y" then
|
||||
return math.random(0, 10)
|
||||
end
|
||||
if placement == "front" and positions == "z" then
|
||||
return 3
|
||||
elseif placement == "back" and positions == "z" then
|
||||
return -3
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function damage_explosion(self, damagemulitplier)
|
||||
mcl_explosions.explode(self.object:get_pos(), 3, {})
|
||||
local objects = minetest.get_objects_inside_radius(self.object:get_pos(), 8)
|
||||
for _,obj in pairs(objects) do
|
||||
if obj:is_player() then
|
||||
mcl_util.deal_damage(obj, damagemulitplier - vector.distance(self.object:get_pos(), obj:get_pos()), {type = "explosion"})
|
||||
elseif obj:get_luaentity()._cmi_is_mob then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=damagemulitplier - vector.distance(self.object:get_pos(), obj:get_pos())},
|
||||
}, self.object:get_velocity())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function particle_explosion(self)
|
||||
local particle_pattern = math.random(1, 3)
|
||||
local fpitch = 0
|
||||
local true_type = ""
|
||||
local type = math.random(1,2)
|
||||
local size = math.random(1,3)
|
||||
local colors = {"red", "yellow", "blue", "green", "white"}
|
||||
local this_colors = {colors[math.random(#colors)], colors[math.random(#colors)], colors[math.random(#colors)]}
|
||||
|
||||
if size == 1 then
|
||||
fpitch = math.random(200, 300)
|
||||
elseif size == 2 then
|
||||
fpitch = math.random(100, 130)
|
||||
else
|
||||
fpitch = math.random(60, 70)
|
||||
end
|
||||
|
||||
if type == 1 then
|
||||
true_type = "Popper"
|
||||
else
|
||||
true_type = "Floof"
|
||||
end
|
||||
|
||||
if type == 1 then
|
||||
minetest.sound_play("mcl_bows_firework", {
|
||||
pos = self.object:get_pos(),
|
||||
max_hear_distance = 100,
|
||||
gain = 3.0,
|
||||
pitch = fpitch/100
|
||||
}, true)
|
||||
else
|
||||
minetest.sound_play("mcl_bows_firework_soft", {
|
||||
pos = self.object:get_pos(),
|
||||
max_hear_distance = 100,
|
||||
gain = 4.0,
|
||||
pitch = fpitch/100
|
||||
}, true)
|
||||
end
|
||||
|
||||
if particle_pattern == 1 then
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-7 * size,-7 * size,-7 * size),
|
||||
maxvel = vector.new(7 * size,7 * size,7 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 100 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-14 * size,-14 * size,-14 * size),
|
||||
maxvel = vector.new(14 * size,14 * size,14 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
elseif particle_pattern == 2 then
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 240 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-5 * size,-5 * size,-5 * size),
|
||||
maxvel = vector.new(5 * size,5 * size,5 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 500 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-2 * size,-2 * size,-2 * size),
|
||||
maxvel = vector.new(2 * size,2 * size,2 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 350 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-3 * size,-3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size / 2,
|
||||
maxexptime = .9 * size / 2,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
elseif particle_pattern == 3 then
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 400 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-6 * size,-4 * size,-6 * size),
|
||||
maxvel = vector.new(6 * size,4 * size,6 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[1]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 120 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-8 * size,6 * size,-8 * size),
|
||||
maxvel = vector.new(8 * size,6 * size,8 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[2]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
minetest.add_particlespawner({
|
||||
amount = 130 * size,
|
||||
time = 0.0001,
|
||||
minpos = self.object:get_pos(),
|
||||
maxpos = self.object:get_pos(),
|
||||
minvel = vector.new(-3 * size,3 * size,-3 * size),
|
||||
maxvel = vector.new(3 * size,3 * size,3 * size),
|
||||
minexptime = .6 * size,
|
||||
maxexptime = .9 * size,
|
||||
minsize = 2 * size,
|
||||
maxsize = 3 * size,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_firework_"..this_colors[3]..".png",
|
||||
glow = 14,
|
||||
})
|
||||
end
|
||||
|
||||
return size
|
||||
|
||||
end
|
||||
|
||||
local mod_awards = minetest.get_modpath("awards") and minetest.get_modpath("mcl_achievements")
|
||||
local mod_button = minetest.get_modpath("mesecons_button")
|
||||
|
||||
minetest.register_craftitem("mcl_bows:rocket", {
|
||||
description = S("Arrow"),
|
||||
_tt_help = S("Ammunition").."\n"..S("Damage from bow: 1-10").."\n"..S("Damage from dispenser: 3"),
|
||||
_doc_items_longdesc = S("Arrows are ammunition for bows and dispensers.").."\n"..
|
||||
S("An arrow fired from a bow has a regular damage of 1-9. At full charge, there's a 20% chance of a critical hit dealing 10 damage instead. An arrow fired from a dispenser always deals 3 damage.").."\n"..
|
||||
S("Arrows might get stuck on solid blocks and can be retrieved again. They are also capable of pushing wooden buttons."),
|
||||
_doc_items_usagehelp = S("To use arrows as ammunition for a bow, just put them anywhere in your inventory, they will be used up automatically. To use arrows as ammunition for a dispenser, place them in the dispenser's inventory. To retrieve an arrow that sticks in a block, simply walk close to it."),
|
||||
inventory_image = "mcl_bows_rocket.png",
|
||||
groups = { ammo=1, ammo_crossbow=1, ammo_bow_regular=1 },
|
||||
_on_dispense = function(itemstack, dispenserpos, droppos, dropnode, dropdir)
|
||||
-- Shoot arrow
|
||||
local shootpos = vector.add(dispenserpos, vector.multiply(dropdir, 0.51))
|
||||
local yaw = math.atan2(dropdir.z, dropdir.x) + YAW_OFFSET
|
||||
mcl_bows.shoot_arrow(itemstack:get_name(), shootpos, dropdir, yaw, nil, 19, 3)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
|
||||
local ARROW_ENTITY={
|
||||
physical = true,
|
||||
pointable = false,
|
||||
visual = "mesh",
|
||||
mesh = "mcl_bows_rocket.obj",
|
||||
visual_size = {x=2.5, y=2.5},
|
||||
textures = {"mcl_bows_rocket.png"},
|
||||
collisionbox = {-0.19, -0.125, -0.19, 0.19, 0.125, 0.19},
|
||||
collide_with_objects = false,
|
||||
_fire_damage_resistant = true,
|
||||
|
||||
_lastpos={},
|
||||
_startpos=nil,
|
||||
_damage=1, -- Damage on impact
|
||||
_is_critical=false, -- Whether this arrow would deal critical damage
|
||||
_stuck=false, -- Whether arrow is stuck
|
||||
_fuse=nil,-- Amount of time (in seconds) the arrow has been stuck so far
|
||||
_fuserechecktimer=nil,-- An additional timer for periodically re-checking the stuck status of an arrow
|
||||
_stuckin=nil, --Position of node in which arow is stuck.
|
||||
_shooter=nil, -- ObjectRef of player or mob who shot it
|
||||
_is_arrow = true,
|
||||
|
||||
_viscosity=0, -- Viscosity of node the arrow is currently in
|
||||
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
|
||||
}
|
||||
|
||||
-- Destroy arrow entity self at pos and drops it as an item
|
||||
local function spawn_item(self, pos)
|
||||
if not minetest.is_creative_enabled("") then
|
||||
local item = minetest.add_item(pos, "mcl_bows:rocket")
|
||||
item:set_velocity({x=0, y=0, z=0})
|
||||
item:set_yaw(self.object:get_yaw())
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
local function damage_particles(pos, is_critical)
|
||||
if is_critical then
|
||||
minetest.add_particlespawner({
|
||||
amount = 15,
|
||||
time = 0.1,
|
||||
minpos = {x=pos.x-0.5, y=pos.y-0.5, z=pos.z-0.5},
|
||||
maxpos = {x=pos.x+0.5, y=pos.y+0.5, z=pos.z+0.5},
|
||||
minvel = {x=-0.1, y=-0.1, z=-0.1},
|
||||
maxvel = {x=0.1, y=0.1, z=0.1},
|
||||
minacc = {x=0, y=0, z=0},
|
||||
maxacc = {x=0, y=0, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 2,
|
||||
minsize = 1.5,
|
||||
maxsize = 1.5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_step(self, dtime)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
|
||||
self._time_in_air = self._time_in_air + .001
|
||||
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
local dpos = table.copy(pos) -- digital pos
|
||||
dpos = vector.round(dpos)
|
||||
local node = minetest.get_node(dpos)
|
||||
|
||||
if not self._fuse then
|
||||
self._fuse = 0
|
||||
end
|
||||
if not self._fuserechecktimer then
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
|
||||
self._fuse = self._fuse + dtime
|
||||
self._fuserechecktimer = self._fuserechecktimer + dtime
|
||||
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
self._stuck = true
|
||||
end
|
||||
if self._stuck then
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
-- Drop arrow as item when it is no longer stuck
|
||||
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
|
||||
if self._fuserechecktimer > STUCK_RECHECK_TIME then
|
||||
local stuckin_def
|
||||
if self._stuckin then
|
||||
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
|
||||
end
|
||||
-- TODO: In MC, arrow just falls down without turning into an item
|
||||
if stuckin_def and stuckin_def.walkable == false then
|
||||
spawn_item(self, pos)
|
||||
return
|
||||
end
|
||||
self._fuserechecktimer = 0
|
||||
end
|
||||
-- Pickup arrow if player is nearby (not in Creative Mode)
|
||||
local objects = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,obj in ipairs(objects) do
|
||||
if obj:is_player() then
|
||||
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
if obj:get_inventory():room_for_item("main", "mcl_bows:rocket") then
|
||||
obj:get_inventory():add_item("main", "mcl_bows:rocket")
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
max_hear_distance = 16,
|
||||
gain = 1.0,
|
||||
}, true)
|
||||
end
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
|
||||
else
|
||||
|
||||
if self._in_player == false then
|
||||
minetest.add_particlespawner({
|
||||
amount = 1,
|
||||
time = .0001,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = vector.new(-0.1,-0.1,-0.1),
|
||||
maxvel = vector.new(0.1,0.1,0.1),
|
||||
minexptime = 0.5,
|
||||
maxexptime = 0.5,
|
||||
minsize = 2,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_bows_rocket_particle.png",
|
||||
glow = 1,
|
||||
})
|
||||
end
|
||||
-- We just check for any hurtable objects nearby.
|
||||
-- The radius of 3 is fairly liberal, but anything lower than than will cause
|
||||
-- arrow to hilariously go through mobs often.
|
||||
-- TODO: Implement an ACTUAL collision detection (engine support needed).
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1.5)
|
||||
local closest_object
|
||||
local closest_distance
|
||||
|
||||
if self._deflection_cooloff > 0 then
|
||||
self._deflection_cooloff = self._deflection_cooloff - dtime
|
||||
end
|
||||
|
||||
-- Iterate through all objects and remember the closest attackable object
|
||||
for k, obj in pairs(objs) do
|
||||
local ok = false
|
||||
-- Arrows can only damage players and mobs
|
||||
if obj:is_player() then
|
||||
ok = true
|
||||
elseif obj:get_luaentity() then
|
||||
if (obj:get_luaentity()._cmi_is_mob or obj:get_luaentity()._hittable_by_projectile) then
|
||||
ok = true
|
||||
end
|
||||
end
|
||||
|
||||
if ok then
|
||||
local dist = vector.distance(pos, obj:get_pos())
|
||||
if not closest_object or not closest_distance then
|
||||
closest_object = obj
|
||||
closest_distance = dist
|
||||
elseif dist < closest_distance then
|
||||
closest_object = obj
|
||||
closest_distance = dist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If an attackable object was found, we will damage the closest one only
|
||||
|
||||
if closest_object then
|
||||
local obj = closest_object
|
||||
local is_player = obj:is_player()
|
||||
local lua = obj:get_luaentity()
|
||||
if obj == self._shooter and self._time_in_air > 1.02 or obj ~= self._shooter and (is_player or (lua and (lua._cmi_is_mob or lua._hittable_by_projectile))) then
|
||||
if obj:get_hp() > 0 then
|
||||
-- Check if there is no solid node between arrow and object
|
||||
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
|
||||
for pointed_thing in ray do
|
||||
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
|
||||
-- Target reached! We can proceed now.
|
||||
break
|
||||
elseif pointed_thing.type == "node" then
|
||||
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if (not def) or def.walkable then
|
||||
-- There's a node in the way. Delete arrow without damage
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Punch target object but avoid hurting enderman.
|
||||
if not lua or lua.name ~= "mobs_mc:enderman" then
|
||||
if self._in_player == false then
|
||||
damage_particles(self.object:get_pos(), self._is_critical)
|
||||
end
|
||||
if mcl_burning.is_burning(self.object) then
|
||||
mcl_burning.set_on_fire(obj, 5)
|
||||
end
|
||||
if self._in_player == false then
|
||||
obj:punch(self.object, 1.0, {
|
||||
full_punch_interval=1.0,
|
||||
damage_groups={fleshy=self._damage},
|
||||
}, self.object:get_velocity())
|
||||
if obj:is_player() then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if is_player then
|
||||
if self._shooter and self._shooter:is_player() and self._in_player == false then
|
||||
-- “Ding” sound for hitting another player
|
||||
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
|
||||
end
|
||||
end
|
||||
|
||||
if lua then
|
||||
local entity_name = lua.name
|
||||
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
|
||||
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
|
||||
-- TODO: This achievement should be given for the kill, not just a hit
|
||||
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
|
||||
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
|
||||
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
|
||||
end
|
||||
end
|
||||
end
|
||||
if self._in_player == false then
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
end
|
||||
end
|
||||
if not obj:is_player() then
|
||||
mcl_burning.extinguish(self.object)
|
||||
if self._piercing == 0 then
|
||||
local eploded_particle = particle_explosion(self)
|
||||
damage_explosion(self, eploded_particle * 17)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for node collision
|
||||
if self._lastpos.x~=nil and not self._stuck then
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
local vel = self.object:get_velocity()
|
||||
-- Arrow has stopped in one axis, so it probably hit something.
|
||||
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
|
||||
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
|
||||
-- Check for the node to which the arrow is pointing
|
||||
local dir
|
||||
if math.abs(vel.y) < 0.00001 then
|
||||
if self._lastpos.y < pos.y then
|
||||
dir = {x=0, y=1, z=0}
|
||||
else
|
||||
dir = {x=0, y=-1, z=0}
|
||||
end
|
||||
else
|
||||
dir = minetest.facedir_to_dir(minetest.dir_to_facedir(minetest.yaw_to_dir(self.object:get_yaw()-YAW_OFFSET)))
|
||||
end
|
||||
self._stuckin = vector.add(dpos, dir)
|
||||
local snode = minetest.get_node(self._stuckin)
|
||||
local sdef = minetest.registered_nodes[snode.name]
|
||||
|
||||
-- If node is non-walkable, unknown or ignore, don't make arrow stuck.
|
||||
-- This causes a deflection in the engine.
|
||||
if not sdef or sdef.walkable == false or snode.name == "ignore" then
|
||||
self._stuckin = nil
|
||||
if self._deflection_cooloff <= 0 then
|
||||
-- Lose 1/3 of velocity on deflection
|
||||
local newvel = vector.multiply(vel, 0.6667)
|
||||
|
||||
self.object:set_velocity(newvel)
|
||||
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
|
||||
self._deflection_cooloff = 1.0
|
||||
end
|
||||
else
|
||||
|
||||
-- Node was walkable, make arrow stuck
|
||||
self._stuck = true
|
||||
self._fuserechecktimer = 0
|
||||
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
|
||||
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
|
||||
|
||||
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
|
||||
tnt.ignite(self._stuckin)
|
||||
end
|
||||
|
||||
-- Push the button! Push, push, push the button!
|
||||
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
|
||||
local bdir = minetest.wallmounted_to_dir(node.param2)
|
||||
-- Check the button orientation
|
||||
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
|
||||
mesecon.push_button(dpos, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif (def and def.liquidtype ~= "none") then
|
||||
-- Slow down arrow in liquids
|
||||
local v = def.liquid_viscosity
|
||||
if not v then
|
||||
v = 0
|
||||
end
|
||||
--local old_v = self._viscosity
|
||||
self._viscosity = v
|
||||
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
|
||||
if math.abs(vel.x) > 0.001 then
|
||||
vel.x = vel.x * vpenalty
|
||||
end
|
||||
if math.abs(vel.z) > 0.001 then
|
||||
vel.z = vel.z * vpenalty
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update yaw
|
||||
if not self._stuck then
|
||||
local vel = self.object:get_velocity()
|
||||
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
|
||||
local pitch = dir_to_pitch(vel)
|
||||
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
|
||||
end
|
||||
|
||||
-- Update internal variable
|
||||
self._lastpos={x=pos.x, y=pos.y, z=pos.z}
|
||||
end
|
||||
|
||||
-- Force recheck of stuck arrows when punched.
|
||||
-- Otherwise, punching has no effect.
|
||||
function ARROW_ENTITY.on_punch(self)
|
||||
if self._stuck then
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
end
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.get_staticdata(self)
|
||||
local out = {
|
||||
lastpos = self._lastpos,
|
||||
startpos = self._startpos,
|
||||
damage = self._damage,
|
||||
is_critical = self._is_critical,
|
||||
stuck = self._stuck,
|
||||
stuckin = self._stuckin,
|
||||
}
|
||||
if self._stuck then
|
||||
-- If _fuse is missing for some reason, assume the maximum
|
||||
if not self._fuse then
|
||||
self._fuse = ARROW_TIMEOUT
|
||||
end
|
||||
out.stuckstarttime = minetest.get_gametime() - self._fuse
|
||||
end
|
||||
if self._shooter and self._shooter:is_player() then
|
||||
out.shootername = self._shooter:get_player_name()
|
||||
end
|
||||
return minetest.serialize(out)
|
||||
end
|
||||
|
||||
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
|
||||
self._time_in_air = 1.0
|
||||
self._in_player = false
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if data then
|
||||
self._stuck = data.stuck
|
||||
if data.stuck then
|
||||
if data.stuckstarttime then
|
||||
-- First, check if the stuck arrow is aleady past its life timer.
|
||||
-- If yes, delete it.
|
||||
self._fuse = minetest.get_gametime() - data.stuckstarttime
|
||||
if self._fuse > ARROW_TIMEOUT then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self._fuse = 2
|
||||
-- Perform a stuck recheck on the next step.
|
||||
self._fuserechecktimer = STUCK_RECHECK_TIME
|
||||
|
||||
self._stuckin = data.stuckin
|
||||
end
|
||||
|
||||
-- Get the remaining arrow state
|
||||
self._lastpos = data.lastpos
|
||||
self._startpos = data.startpos
|
||||
self._damage = data.damage
|
||||
self._is_critical = data.is_critical
|
||||
if data.shootername then
|
||||
local shooter = minetest.get_player_by_name(data.shootername)
|
||||
if shooter and shooter:is_player() then
|
||||
self._shooter = shooter
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
end
|
||||
|
||||
minetest.register_entity("mcl_bows:rocket_entity", ARROW_ENTITY)
|
||||
|
||||
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
|
||||
minetest.register_craft({
|
||||
output = "mcl_bows:rocket 1",
|
||||
recipe = {
|
||||
{"mcl_core:paper"},
|
||||
{"mcl_fireworks:rocket_2"},
|
||||
{"mcl_bows:arrow"},
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object("mcl_bows:rocket_entity", "craftitems", "mcl_bows:rocket")
|
||||
end
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.3 KiB |