Merge pull request 'master' (#12) from MineClone2/MineClone2:master into master
Reviewed-on: https://git.minetest.land/NO11/MineClone2/pulls/12
128
CODE_OF_CONDUCT.md
Normal file
@ -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.
|
445
CONTRIBUTING.md
@ -1,105 +1,414 @@
|
||||
# Contributing to MineClone 2
|
||||
So you want to contribute to MineClone 2?
|
||||
# Contributing to MineClone2
|
||||
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:
|
||||
```lua
|
||||
function mcl_xyz.stuff(param) end
|
||||
```
|
||||
Insteed of this way:
|
||||
```lua
|
||||
mcl_xyz.stuff = function(param) end
|
||||
```
|
||||
Indentation must be unified, more likely with tabs.
|
||||
mcl_example = {}
|
||||
|
||||
function mcl_example.do_something()
|
||||
-- ...
|
||||
end
|
||||
|
||||
Time sensitive mods should make a local copy of most used API functions to improve performances.
|
||||
```lua
|
||||
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
|
||||
|
39
README.md
@ -1,4 +1,4 @@
|
||||
# MineClone 2
|
||||
# MineClone2
|
||||
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
|
||||
Developed by many people. Not developed or endorsed by Mojang AB.
|
||||
|
||||
@ -69,9 +69,9 @@ an explanation.
|
||||
This game requires [Minetest](http://minetest.net) to run (version 5.3.0 or
|
||||
later). So you need to install Minetest first. Only stable versions of Minetest
|
||||
are officially supported.
|
||||
There is no support for running MineClone 2 in development versions of Minetest.
|
||||
There is no support for running MineClone2 in development versions of Minetest.
|
||||
|
||||
To install MineClone 2 (if you haven't already), move this directory into the
|
||||
To install MineClone2 (if you haven't already), move this directory into the
|
||||
“games” directory of your Minetest data directory. Consult the help of
|
||||
Minetest to learn more.
|
||||
|
||||
@ -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.
|
||||
@ -186,7 +189,7 @@ Technical differences from Minecraft:
|
||||
* Different engine (Minetest)
|
||||
* Different easter eggs
|
||||
|
||||
… and finally, MineClone 2 is free software (“free” as in “freedom”)!
|
||||
… and finally, MineClone2 is free software (“free” as in “freedom”)!
|
||||
|
||||
## Other readme files
|
||||
|
||||
|
BIN
mods/CORE/mcl_particles/textures/mcl_particles_totem1.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
mods/CORE/mcl_particles/textures/mcl_particles_totem2.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
mods/CORE/mcl_particles/textures/mcl_particles_totem3.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
mods/CORE/mcl_particles/textures/mcl_particles_totem4.png
Normal file
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
|
||||
|
||||
--[[
|
||||
|
@ -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:
|
||||
@ -78,4 +78,4 @@ Table containing all function registered with mcl_worlds.register_on_dimension_c
|
||||
Notify this mod of a dimension change of <player> to <dimension>
|
||||
|
||||
* player: player, player who changed the dimension
|
||||
* dimension: string, new dimension ("overworld", "nether", "end", "void")
|
||||
* dimension: string, new dimension ("overworld", "nether", "end", "void")
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
@ -434,8 +439,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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
@ -155,4 +158,4 @@ mobs.death_logic = function(self, dtime)
|
||||
if self.pause_timer <= 0 then
|
||||
mobs.set_velocity(self,0)
|
||||
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
|
||||
|
@ -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)
|
||||
@ -243,7 +128,7 @@ minetest.register_globalstep(function(dtime)
|
||||
y = y - 5
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if y > -100 then
|
||||
if id == huds.icon then
|
||||
y = math.max(400, y)
|
||||
|
144
mods/HUD/mcl_credits/people.lua
Normal file
@ -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",
|
||||
}},
|
||||
}
|
63
mods/HUD/mcl_experience/bottle.lua
Normal file
@ -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
|
||||
})
|
||||
|
39
mods/HUD/mcl_experience/command.lua
Normal file
@ -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,641 +1,227 @@
|
||||
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
|
||||
a, b, c = 4.5, -162.5, 2220 - xp
|
||||
elseif xp > 352 then
|
||||
a, b, c = 2.5, -40.5, 360-xp
|
||||
a, b, c = 2.5, -40.5, 360 - xp
|
||||
else
|
||||
a, b, c = 1, 6, -xp
|
||||
end
|
||||
D = b*b-4*a*c
|
||||
|
||||
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(-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))
|
||||
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 name = player:get_player_name()
|
||||
local temp_pool = pool[name]
|
||||
local function bar_to_xp(bar, level)
|
||||
local xp_min, xp_max = calculate_bounds(level)
|
||||
|
||||
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
|
||||
return xp_min + bar * (xp_max - xp_min)
|
||||
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)
|
||||
local function get_time()
|
||||
return minetest.get_us_time() / 1000000
|
||||
end
|
||||
|
||||
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
|
||||
-- api
|
||||
|
||||
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
|
||||
function mcl_experience.get_level(player)
|
||||
return caches[player].level
|
||||
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
|
||||
function mcl_experience.set_level(player, level)
|
||||
local cache = caches[player]
|
||||
|
||||
if old_level ~= temp_pool.level then
|
||||
hud_manager.change_hud({player = player, hud_name = "xp_level", element = "text", data = tostring(temp_pool.level)})
|
||||
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
|
||||
|
||||
--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
|
||||
function mcl_experience.get_xp(player)
|
||||
return player:get_meta():get_int("xp")
|
||||
end
|
||||
|
||||
name = player:get_player_name()
|
||||
temp_pool = pool[name]
|
||||
xp_amount = temp_pool.xp
|
||||
function mcl_experience.set_xp(player, xp)
|
||||
player:get_meta():set_int("xp", 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)
|
||||
mcl_experience.update(player)
|
||||
end
|
||||
|
||||
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",})
|
||||
function mcl_experience.add_xp(player, xp)
|
||||
for _, cb in ipairs(mcl_experience.on_add_xp) do
|
||||
xp = cb.func(player, xp) or xp
|
||||
|
||||
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)
|
||||
if xp == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local cache = caches[player]
|
||||
local old_level = cache.level
|
||||
|
||||
self.age = self.age + dtime
|
||||
if self.age > max_orb_age then
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
mcl_experience.set_xp(player, mcl_experience.get_xp(player) + xp)
|
||||
|
||||
pos = self.object:get_pos()
|
||||
local current_time = get_time()
|
||||
|
||||
if pos then
|
||||
node = minetest.get_node_or_nil({
|
||||
x = pos.x,
|
||||
y = pos.y -0.25,
|
||||
z = pos.z
|
||||
})
|
||||
else
|
||||
return
|
||||
end
|
||||
if current_time - cache.last_time > 0.01 then
|
||||
local name = player:get_player_name()
|
||||
|
||||
-- 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
|
||||
if old_level == cache.level then
|
||||
minetest.sound_play("mcl_experience", {
|
||||
to_player = name,
|
||||
gain = 0.1,
|
||||
pitch = math.random(75, 99) / 100,
|
||||
})
|
||||
elseif vel.y == 0 then
|
||||
is_moving = false
|
||||
|
||||
cache.last_time = current_time
|
||||
else
|
||||
minetest.sound_play("mcl_experience_level_up", {
|
||||
to_player = name,
|
||||
gain = 0.2,
|
||||
})
|
||||
|
||||
cache.last_time = current_time + 0.2
|
||||
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="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,
|
||||
})
|
||||
|
||||
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)
|
||||
function mcl_experience.throw_xp(pos, total_xp)
|
||||
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))
|
||||
|
||||
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
|
||||
obj:set_velocity({
|
||||
x=math.random(-2,2)*math.random(),
|
||||
y=math.random(2,5),
|
||||
z=math.random(-2,2)*math.random()
|
||||
})
|
||||
|
||||
obj:set_velocity(vector.new(
|
||||
math.random(-2, 2) * math.random(),
|
||||
math.random( 2, 5),
|
||||
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)
|
||||
|
220
mods/HUD/mcl_experience/orb.lua
Normal file
@ -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 |
@ -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"),
|
||||
|
@ -773,8 +773,7 @@ end
|
||||
|
||||
local grass_spread_randomizer = PseudoRandom(minetest.get_mapgen_setting("seed"))
|
||||
|
||||
-- Return appropriate grass block node for pos
|
||||
function mcl_core.get_grass_block_type(pos)
|
||||
function mcl_core.get_grass_palette_index(pos)
|
||||
local biome_data = minetest.get_biome_data(pos)
|
||||
local index = 0
|
||||
if biome_data then
|
||||
@ -785,7 +784,12 @@ function mcl_core.get_grass_block_type(pos)
|
||||
index = reg_biome._mcl_palette_index
|
||||
end
|
||||
end
|
||||
return {name="mcl_core:dirt_with_grass", param2=index}
|
||||
return index
|
||||
end
|
||||
|
||||
-- Return appropriate grass block node for pos
|
||||
function mcl_core.get_grass_block_type(pos)
|
||||
return {name = "mcl_core:dirt_with_grass", param2 = mcl_core.get_grass_palette_index(pos)}
|
||||
end
|
||||
|
||||
------------------------------
|
||||
|
@ -365,7 +365,7 @@ minetest.register_node("mcl_core:dirt_with_grass", {
|
||||
overlay_tiles = {"mcl_core_grass_block_top.png", "", {name="mcl_core_grass_block_side_overlay.png", tileable_vertical=false}},
|
||||
palette = "mcl_core_palette_grass.png",
|
||||
palette_index = 0,
|
||||
color = "#55aa60",
|
||||
color = "#8EB971",
|
||||
is_ground_content = true,
|
||||
stack_max = 64,
|
||||
groups = {handy=1,shovely=1,dirt=2,grass_block=1, grass_block_no_snow=1, soil=1, soil_sapling=2, soil_sugarcane=1, cultivatable=2, spreading_dirt_type=1, enderman_takable=1, building_block=1},
|
||||
|
@ -53,7 +53,10 @@ minetest.register_node("mcl_core:reeds", {
|
||||
_doc_items_longdesc = S("Sugar canes are a plant which has some uses in crafting. Sugar canes will slowly grow up to 3 blocks when they are next to water and are placed on a grass block, dirt, sand, red sand, podzol or coarse dirt. When a sugar cane is broken, all sugar canes connected above will break as well."),
|
||||
_doc_items_usagehelp = S("Sugar canes can only be placed top of other sugar canes and on top of blocks on which they would grow."),
|
||||
drawtype = "plantlike",
|
||||
paramtype2 = "color",
|
||||
tiles = {"default_papyrus.png"},
|
||||
palette = "mcl_core_palette_grass.png",
|
||||
palette_index = 0,
|
||||
inventory_image = "mcl_core_reeds.png",
|
||||
wield_image = "mcl_core_reeds.png",
|
||||
paramtype = "light",
|
||||
@ -79,6 +82,7 @@ minetest.register_node("mcl_core:reeds", {
|
||||
groups = {dig_immediate=3, craftitem=1, deco_block=1, plant=1, non_mycelium_plant=1, dig_by_piston=1},
|
||||
sounds = mcl_sounds.node_sound_leaves_defaults(),
|
||||
node_placement_prediction = "",
|
||||
drop = "mcl_core:reeds", -- to prevent color inheritation
|
||||
on_place = mcl_util.generate_on_place_plant_function(function(place_pos, place_node)
|
||||
local soil_pos = {x=place_pos.x, y=place_pos.y-1, z=place_pos.z}
|
||||
local soil_node = minetest.get_node_or_nil(soil_pos)
|
||||
@ -114,6 +118,15 @@ minetest.register_node("mcl_core:reeds", {
|
||||
return false
|
||||
|
||||
end),
|
||||
on_construct = function(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.param2 == 0 then
|
||||
node.param2 = mcl_core.get_grass_palette_index(pos)
|
||||
if node.param2 ~= 0 then
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
end
|
||||
end,
|
||||
_mcl_blast_resistance = 0,
|
||||
_mcl_hardness = 0,
|
||||
})
|
||||
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 1.9 KiB |
14
mods/ITEMS/mcl_dye/API.md
Normal file
@ -0,0 +1,14 @@
|
||||
# mcl_dye
|
||||
|
||||
# Bone meal API
|
||||
Callback and particle functions.
|
||||
|
||||
## mcl_dye.add_bone_meal_particle(pos, def)
|
||||
Spawns standard or custom bone meal particles.
|
||||
* `pos`: position, is ignored if you define def.minpos and def.maxpos
|
||||
* `def`: (optional) particle definition
|
||||
|
||||
## mcl_dye.register_on_bone_meal_apply(function(pointed_thing, user))
|
||||
Called when the bone meal is applied anywhere.
|
||||
* `pointed_thing`: exact pointing location (see Minetest API), where the bone meal is applied
|
||||
* `user`: ObjectRef of the player who aplied the bone meal, can be nil!
|
@ -128,26 +128,35 @@ for _, row in ipairs(dyelocal.dyes) do
|
||||
end
|
||||
|
||||
-- Bone Meal
|
||||
local function bone_meal_particle(pos)
|
||||
function mcl_dye.add_bone_meal_particle(pos, def)
|
||||
if not def then
|
||||
def = {}
|
||||
end
|
||||
minetest.add_particlespawner({
|
||||
amount = 10,
|
||||
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, 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 = 1,
|
||||
maxexptime = 4,
|
||||
minsize = 0.7,
|
||||
maxsize = 2.4,
|
||||
amount = def.amount or 10,
|
||||
time = def.time or 0.1,
|
||||
minpos = def.minpos or vector.subtract(pos, 0.5),
|
||||
maxpos = def.maxpos or vector.add(pos, 0.5),
|
||||
minvel = def.minvel or vector.new(-0.01, 0.01, -0.01),
|
||||
maxvel = def.maxvel or vector.new(0.01, 0.01, 0.01),
|
||||
minacc = def.minacc or vector.new(0, 0, 0),
|
||||
maxacc = def.maxacc or vector.new(0, 0, 0),
|
||||
minexptime = def.minexptime or 1,
|
||||
maxexptime = def.maxexptime or 4,
|
||||
minsize = def.minsize or 0.7,
|
||||
maxsize = def.maxsize or 2.4,
|
||||
texture = "mcl_particles_bonemeal.png^[colorize:#00EE00:125", -- TODO: real MC color
|
||||
glow = 5,
|
||||
glow = def.glow or 1,
|
||||
})
|
||||
end
|
||||
|
||||
function mcl_dye.apply_bone_meal(pointed_thing)
|
||||
mcl_dye.bone_meal_callbacks = {}
|
||||
|
||||
function mcl_dye.register_on_bone_meal_apply(func)
|
||||
table.insert(mcl_dye.bone_meal_callbacks, func)
|
||||
end
|
||||
|
||||
local function apply_bone_meal(pointed_thing)
|
||||
-- Bone meal currently spawns all flowers found in the plains.
|
||||
local flowers_table_plains = {
|
||||
"mcl_flowers:dandelion",
|
||||
@ -183,14 +192,21 @@ function mcl_dye.apply_bone_meal(pointed_thing)
|
||||
local pos = pointed_thing.under
|
||||
local n = minetest.get_node(pos)
|
||||
if n.name == "" then return false end
|
||||
|
||||
for _, func in pairs(mcl_dye.bone_meal_callbacks) do
|
||||
if func(pointed_thing, user) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if minetest.get_item_group(n.name, "sapling") >= 1 then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Saplings: 45% chance to advance growth stage
|
||||
if math.random(1,100) <= 45 then
|
||||
return mcl_core.grow_sapling(pos, n)
|
||||
end
|
||||
elseif minetest.get_item_group(n.name, "mushroom") == 1 then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Try to grow huge mushroom
|
||||
|
||||
-- Must be on a dirt-type block
|
||||
@ -240,71 +256,71 @@ function mcl_dye.apply_bone_meal(pointed_thing)
|
||||
return false
|
||||
-- Wheat, Potato, Carrot, Pumpkin Stem, Melon Stem: Advance by 2-5 stages
|
||||
elseif string.find(n.name, "mcl_farming:wheat_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
local stages = math.random(2, 5)
|
||||
return mcl_farming:grow_plant("plant_wheat", pos, n, stages, true)
|
||||
elseif string.find(n.name, "mcl_farming:potato_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
local stages = math.random(2, 5)
|
||||
return mcl_farming:grow_plant("plant_potato", pos, n, stages, true)
|
||||
elseif string.find(n.name, "mcl_farming:carrot_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
local stages = math.random(2, 5)
|
||||
return mcl_farming:grow_plant("plant_carrot", pos, n, stages, true)
|
||||
elseif string.find(n.name, "mcl_farming:pumpkin_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
local stages = math.random(2, 5)
|
||||
return mcl_farming:grow_plant("plant_pumpkin_stem", pos, n, stages, true)
|
||||
elseif string.find(n.name, "mcl_farming:melontige_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
local stages = math.random(2, 5)
|
||||
return mcl_farming:grow_plant("plant_melon_stem", pos, n, stages, true)
|
||||
elseif string.find(n.name, "mcl_farming:beetroot_") then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Beetroot: 75% chance to advance to next stage
|
||||
if math.random(1, 100) <= 75 then
|
||||
return mcl_farming:grow_plant("plant_beetroot", pos, n, 1, true)
|
||||
end
|
||||
elseif n.name == "mcl_cocoas:cocoa_1" or n.name == "mcl_cocoas:cocoa_2" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Cocoa: Advance by 1 stage
|
||||
mcl_cocoas.grow(pos)
|
||||
return true
|
||||
elseif minetest.get_item_group(n.name, "grass_block") == 1 then
|
||||
local grass_block_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
|
||||
bone_meal_particle(grass_block_pos)
|
||||
-- Grass Block: Generate tall grass and random flowers all over the place
|
||||
for i = -2, 2 do
|
||||
for j = -2, 2 do
|
||||
pos = pointed_thing.above
|
||||
pos = {x=pos.x+i, y=pos.y, z=pos.z+j}
|
||||
n = minetest.get_node(pos)
|
||||
local n2 = minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z})
|
||||
for i = -7, 7 do
|
||||
for j = -7, 7 do
|
||||
for y = -1, 1 do
|
||||
pos = vector.offset(pointed_thing.above, i, y, j)
|
||||
n = minetest.get_node(pos)
|
||||
local n2 = minetest.get_node(vector.offset(pos, 0, -1, 0))
|
||||
|
||||
if n.name ~= "" and n.name == "air" and (minetest.get_item_group(n2.name, "grass_block_no_snow") == 1) then
|
||||
-- Randomly generate flowers, tall grass or nothing
|
||||
if math.random(1,100) <= 90 then
|
||||
-- 90% tall grass, 10% flower
|
||||
if math.random(1,100) <= 90 then
|
||||
local col = n2.param2
|
||||
minetest.add_node(pos, {name="mcl_flowers:tallgrass", param2=col})
|
||||
else
|
||||
local flowers_table
|
||||
if mg_name == "v6" then
|
||||
flowers_table = flowers_table_plains
|
||||
if n.name ~= "" and n.name == "air" and (minetest.get_item_group(n2.name, "grass_block_no_snow") == 1) then
|
||||
-- Randomly generate flowers, tall grass or nothing
|
||||
if math.random(1, 100) <= 90 / ((math.abs(i) + math.abs(j)) / 2)then
|
||||
-- 90% tall grass, 10% flower
|
||||
mcl_dye.add_bone_meal_particle(pos, {amount = 4})
|
||||
if math.random(1,100) <= 90 then
|
||||
local col = n2.param2
|
||||
minetest.add_node(pos, {name="mcl_flowers:tallgrass", param2=col})
|
||||
else
|
||||
local biome = minetest.get_biome_name(minetest.get_biome_data(pos).biome)
|
||||
if biome == "Swampland" or biome == "Swampland_shore" or biome == "Swampland_ocean" or biome == "Swampland_deep_ocean" or biome == "Swampland_underground" then
|
||||
flowers_table = flowers_table_swampland
|
||||
elseif biome == "FlowerForest" or biome == "FlowerForest_beach" or biome == "FlowerForest_ocean" or biome == "FlowerForest_deep_ocean" or biome == "FlowerForest_underground" then
|
||||
flowers_table = flowers_table_flower_forest
|
||||
elseif biome == "Plains" or biome == "Plains_beach" or biome == "Plains_ocean" or biome == "Plains_deep_ocean" or biome == "Plains_underground" or biome == "SunflowerPlains" or biome == "SunflowerPlains_ocean" or biome == "SunflowerPlains_deep_ocean" or biome == "SunflowerPlains_underground" then
|
||||
local flowers_table
|
||||
if mg_name == "v6" then
|
||||
flowers_table = flowers_table_plains
|
||||
else
|
||||
flowers_table = flowers_table_simple
|
||||
local biome = minetest.get_biome_name(minetest.get_biome_data(pos).biome)
|
||||
if biome == "Swampland" or biome == "Swampland_shore" or biome == "Swampland_ocean" or biome == "Swampland_deep_ocean" or biome == "Swampland_underground" then
|
||||
flowers_table = flowers_table_swampland
|
||||
elseif biome == "FlowerForest" or biome == "FlowerForest_beach" or biome == "FlowerForest_ocean" or biome == "FlowerForest_deep_ocean" or biome == "FlowerForest_underground" then
|
||||
flowers_table = flowers_table_flower_forest
|
||||
elseif biome == "Plains" or biome == "Plains_beach" or biome == "Plains_ocean" or biome == "Plains_deep_ocean" or biome == "Plains_underground" or biome == "SunflowerPlains" or biome == "SunflowerPlains_ocean" or biome == "SunflowerPlains_deep_ocean" or biome == "SunflowerPlains_underground" then
|
||||
flowers_table = flowers_table_plains
|
||||
else
|
||||
flowers_table = flowers_table_simple
|
||||
end
|
||||
end
|
||||
minetest.add_node(pos, {name=flowers_table[math.random(1, #flowers_table)]})
|
||||
end
|
||||
minetest.add_node(pos, {name=flowers_table[math.random(1, #flowers_table)]})
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -314,24 +330,24 @@ function mcl_dye.apply_bone_meal(pointed_thing)
|
||||
|
||||
-- Double flowers: Drop corresponding item
|
||||
elseif n.name == "mcl_flowers:rose_bush" or n.name == "mcl_flowers:rose_bush_top" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
minetest.add_item(pos, "mcl_flowers:rose_bush")
|
||||
return true
|
||||
elseif n.name == "mcl_flowers:peony" or n.name == "mcl_flowers:peony_top" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
minetest.add_item(pos, "mcl_flowers:peony")
|
||||
return true
|
||||
elseif n.name == "mcl_flowers:lilac" or n.name == "mcl_flowers:lilac_top" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
minetest.add_item(pos, "mcl_flowers:lilac")
|
||||
return true
|
||||
elseif n.name == "mcl_flowers:sunflower" or n.name == "mcl_flowers:sunflower_top" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
minetest.add_item(pos, "mcl_flowers:sunflower")
|
||||
return true
|
||||
|
||||
elseif n.name == "mcl_flowers:tallgrass" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Tall Grass: Grow into double tallgrass
|
||||
local toppos = { x=pos.x, y=pos.y+1, z=pos.z }
|
||||
local topnode = minetest.get_node(toppos)
|
||||
@ -342,7 +358,7 @@ function mcl_dye.apply_bone_meal(pointed_thing)
|
||||
end
|
||||
|
||||
elseif n.name == "mcl_flowers:fern" then
|
||||
bone_meal_particle(pos)
|
||||
mcl_dye.add_bone_meal_particle(pos)
|
||||
-- Fern: Grow into large fern
|
||||
local toppos = { x=pos.x, y=pos.y+1, z=pos.z }
|
||||
local topnode = minetest.get_node(toppos)
|
||||
@ -374,7 +390,7 @@ minetest.register_craftitem("mcl_dye:white", {
|
||||
end
|
||||
|
||||
-- Use the bone meal on the ground
|
||||
if(mcl_dye.apply_bone_meal(pointed_thing) and (not minetest.is_creative_enabled(user:get_player_name()))) then
|
||||
if (apply_bone_meal(pointed_thing, user) and (not minetest.is_creative_enabled(user:get_player_name()))) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
@ -387,7 +403,7 @@ minetest.register_craftitem("mcl_dye:white", {
|
||||
else
|
||||
pointed_thing = { above = pos, under = droppos }
|
||||
end
|
||||
local success = mcl_dye.apply_bone_meal(pointed_thing)
|
||||
local success = apply_bone_meal(pointed_thing, nil)
|
||||
if success then
|
||||
stack:take_item()
|
||||
end
|
||||
|
@ -379,6 +379,49 @@ mcl_enchanting.enchantments.mending = {
|
||||
inv_tool_tab = true,
|
||||
}
|
||||
|
||||
mcl_experience.register_on_add_xp(function(player, xp)
|
||||
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 = xp * multiplier
|
||||
local new_wear = wear - repair
|
||||
|
||||
if new_wear < 0 then
|
||||
xp = math.floor(-new_wear / multiplier + 0.5)
|
||||
new_wear = 0
|
||||
else
|
||||
xp = 0
|
||||
end
|
||||
|
||||
stack:set_wear(math.floor(new_wear))
|
||||
inv:set_stack(list, index, stack)
|
||||
end
|
||||
|
||||
return xp
|
||||
end, 0)
|
||||
|
||||
mcl_enchanting.enchantments.multishot = {
|
||||
name = S("Multishot"),
|
||||
|
@ -499,7 +499,7 @@ function mcl_enchanting.show_enchanting_formspec(player)
|
||||
.. "real_coordinates[true]"
|
||||
.. "image[3.15,0.6;7.6,4.1;mcl_enchanting_button_background.png]"
|
||||
local itemstack = inv:get_stack("enchanting_item", 1)
|
||||
local player_levels = mcl_experience.get_player_xp_level(player)
|
||||
local player_levels = mcl_experience.get_level(player)
|
||||
local y = 0.65
|
||||
local any_enchantment = false
|
||||
local table_slots = mcl_enchanting.get_table_slots(player, itemstack, num_bookshelves)
|
||||
@ -549,11 +549,11 @@ function mcl_enchanting.handle_formspec_fields(player, formname, fields)
|
||||
if not slot then
|
||||
return
|
||||
end
|
||||
local player_level = mcl_experience.get_player_xp_level(player)
|
||||
local player_level = mcl_experience.get_level(player)
|
||||
if player_level < slot.level_requirement then
|
||||
return
|
||||
end
|
||||
mcl_experience.set_player_xp_level(player, player_level - button_pressed)
|
||||
mcl_experience.set_level(player, player_level - button_pressed)
|
||||
inv:remove_item("enchanting_lapis", cost)
|
||||
mcl_enchanting.set_enchanted_itemstring(itemstack)
|
||||
mcl_enchanting.set_enchantments(itemstack, slot.enchantments)
|
||||
|
@ -183,7 +183,7 @@ minetest.register_entity("mcl_enchanting:book", {
|
||||
collisionbox = {0, 0, 0},
|
||||
pointable = false,
|
||||
physical = false,
|
||||
textures = {"mcl_enchanting_book_entity.png"},
|
||||
textures = {"mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png", "mcl_enchanting_book_entity.png"},
|
||||
static_save = false,
|
||||
},
|
||||
_player_near = false,
|
||||
|
123
mods/ITEMS/mcl_enchanting/locale/mcl_enchanting.es.tr
Normal file
@ -0,0 +1,123 @@
|
||||
# textdomain: mcl_enchanting
|
||||
|
||||
|
||||
### enchantments.lua ###
|
||||
|
||||
Arrows passes through multiple objects.=Las flechas atraviesan multiples enemigos.
|
||||
Arrows set target on fire.=Las flechas prenderan los enemigos.
|
||||
Bane of Arthropods=Perdición de los Artrópodos
|
||||
Channeling=Conductividad
|
||||
|
||||
Channels a bolt of lightning toward a target. Works only during thunderstorms and if target is unobstructed with opaque blocks.=Canaliza los rayos de una tormenta hacia el enemigo.
|
||||
|
||||
Curse of Vanishing=Maldición de Desaparición
|
||||
Decreases crossbow charging time.=Disminuye el tiempo de carga de las ballestas.
|
||||
Decreases time until rod catches something.=Disminuye el tiempo que tardan en picar los cebos en la pesca.
|
||||
Depth Strider=Agilidad acuática
|
||||
Efficiency=Eficiencia
|
||||
Extends underwater breathing time.=Aumenta el tiempo de mantener la respiración.
|
||||
Fire Aspect=Aspecto Ígneo
|
||||
Flame=Fuego
|
||||
Fortune=Fortuna
|
||||
Frost Walker=Paso Helado
|
||||
Impaling=Empalamiento
|
||||
Increases arrow damage.=Incrementa el daño de las flechas.
|
||||
Increases arrow knockback.=Incrementa el empuje de las flechas.
|
||||
Increases certain block drops.=Incrementa la cantidad de objetos que sueltan los bloques.
|
||||
|
||||
Increases damage and applies Slowness IV to arthropod mobs (spiders, cave spiders, silverfish and endermites).=Incrementa el daño y ralentiza a los artrópodos. (arañas, lepismas, endermitas, etc)
|
||||
|
||||
Increases damage to undead mobs.=Incrementa el daño contra no-muertos.
|
||||
Increases damage.=Incrementa el daño.
|
||||
Increases item durability.=Incrementa la durabilidad de una herramienta.
|
||||
Increases knockback.=Incrementa el empuje.
|
||||
Increases mining speed.=Incrementa la velocidad de picado.
|
||||
Increases mob loot.=Incrementa el botín de los enemigos.
|
||||
Increases rate of good loot (enchanting books, etc.)=Incrementa la probabilidad de encontrar tesoros.
|
||||
Increases sweeping attack damage.=Incrementa el daño de efecto area.
|
||||
Increases underwater movement speed.=Incrementa la velocidad de nado bajo el agua.
|
||||
Increases walking speed on soul sand.=Incrementa la velocidad al caminar sobre arena de Almas.
|
||||
Infinity=Infinidad
|
||||
Item destroyed on death.=El objeto se destruye tras tu muerte.
|
||||
Knockback=Empuje
|
||||
Looting=Botín
|
||||
Loyalty=Lealtad
|
||||
Luck of the Sea=Suerte Marina
|
||||
Lure=Atracción
|
||||
Mending=Reparación
|
||||
Mined blocks drop themselves.=Los bloques se minarán enteros.
|
||||
Multishot=Multidisparo
|
||||
Piercing=Perforación
|
||||
Power=Poder
|
||||
Punch=Retroceso
|
||||
Quick Charge=Carga Rápida
|
||||
Repair the item while gaining XP orbs.=Repara los objetos portados al recibir orbes de experiencia.
|
||||
Respiration=Respiración
|
||||
Riptide=Propulsión acuática
|
||||
Sets target on fire.=Incencia al enemigo.
|
||||
Sharpness=Filo
|
||||
Shoot 3 arrows at the cost of one.=Dispara 3 flechas al precio de una.
|
||||
Shooting consumes no regular arrows.=No se consumiran las flechas lanzadas.
|
||||
Silk Touch=Toque de Seda
|
||||
Smite=Golpeo
|
||||
Soul Speed=Velocidad de Almas
|
||||
Sweeping Edge=Filo Arrasador
|
||||
Trident deals additional damage to ocean mobs.=Incrementa el daño del tridente sobre criaturas acuáticas.
|
||||
|
||||
Trident launches player with itself when thrown. Works only in water or rain.=El tridente impulsa al portador dentro del agua o bajo la lluvia.
|
||||
|
||||
Trident returns after being thrown. Higher levels reduce return time.=El tridente regresa al portador tras lanzarlo.
|
||||
|
||||
Turns water beneath the player into frosted ice and prevents the damage from magma blocks.=Congela el agua bajo tus pies y evita el daño de los bloques de magma.
|
||||
|
||||
Unbreaking=Irrompibilidad
|
||||
|
||||
### engine.lua ###
|
||||
|
||||
@1 Enchantment Levels=Nivel de encantamiento: @1
|
||||
@1 Lapis Lazuli=@1 Lapis Lázuli
|
||||
Inventory=Inventario
|
||||
Level requirement: @1=Nivel requerido: @1
|
||||
|
||||
### init.lua ###
|
||||
|
||||
'@1' is not a valid number='@1' no es un número válido
|
||||
'@1' is not a valid number.='@1' no es un número válido
|
||||
<player> <enchantment> [<level>]=<jugador> <encantamiento> [<nivel>]
|
||||
@1 can't be combined with @2.=@1 no se puede combinar con @2
|
||||
|
||||
After finally selecting your enchantment; left-click on the selection, and you will see both the lapis lazuli and your experience levels consumed. And, an enchanted item left in its place.=Despues elige tu encantamiento, los niveles de experiencia y el lapis lázuli seran consumidos y el encantamiento aplicado al objeto.
|
||||
|
||||
After placing your items in the slots, the enchanting options will be shown. Hover over the options to read what is available to you.=Coloca el objeto en su ranura yse mostraran los encantamientos a elegir.
|
||||
|
||||
Enchant=Encantamiento
|
||||
Enchant an item=Encantar objeto
|
||||
Enchanted Book=Libro Encantado
|
||||
Enchanting Table=Mesa de Encantamientos
|
||||
|
||||
Enchanting Tables will let you enchant armors, tools, weapons, and books with various abilities. But, at the cost of some experience, and lapis lazuli.=La mesa de Encantamientos dara a tus herramientas, armas o armadura algunas habilidades magicas. Pero a coste de algo de experiencia y lapis lázuli.
|
||||
|
||||
Enchanting succeded.=Encantado correctamente.
|
||||
Forcefully enchant an item=Encantar objeto a la fuerza.
|
||||
|
||||
Place a tool, armor, weapon or book into the top left slot, and then place 1-3 Lapis Lazuli in the slot to the right.=Coloca una herramienta, arma, armadura o libro sobre la ranura izquierda, coloca de 1 a 3 Lapis lázulis en la ranura derecha.
|
||||
|
||||
Player '@1' cannot be found.=Jugador @1 no encontrado.
|
||||
Rightclick the Enchanting Table to open the enchanting menu.=Clic derecho sobre la mesa de encantamientos para abrir la interfaz.
|
||||
Spend experience, and lapis to enchant various items.=Experiencia y Lapis para encantar varios objetos.
|
||||
|
||||
The number you have entered (@1) is too big, it must be at most @2.=@1 es muy grande, debe ser menor que @2
|
||||
|
||||
The number you have entered (@1) is too small, it must be at least @2.=@1 es muy pequeño, debe ser mayor a @2
|
||||
|
||||
The selected enchantment can't be added to the target item.=El encantamiento seleccionado no puede añadirse a ese objeto.
|
||||
The target doesn't hold an item.=El jugador no sujeta un objeto.
|
||||
The target item is not enchantable.=El objeto del jugador no se puede encantar.
|
||||
There is no such enchantment '@1'.=@1 no es un encantamiento.
|
||||
|
||||
These options are randomized, and dependent on experience level; but the enchantment strength can be increased.=Las opciones seran aleatorias dependiendo del nivel de experiencia, los niveles de encantamiento pueden ser aumentados.
|
||||
|
||||
To increase the enchantment strength, place bookshelves around the enchanting table. However, you will need to keep 1 air node between the table, & the bookshelves to empower the enchanting table.=Para aumentar los niveles de encantamientos, coloca librerias alrededor y cerca de la mesa de encantamientos.
|
||||
|
||||
Usage: /enchant <player> <enchantment> [<level>]=Usa: /enchant <jugador> <encantamiento> [<nivel>]
|
||||
Usage: /forceenchant <player> <enchantment> [<level>]=Usa /forceenchant <jugador> <encantamiento> [<nivel>]
|
@ -1,100 +1,129 @@
|
||||
# textdomain: mcl_enchanting
|
||||
Aqua Affinity=
|
||||
Increases underwater mining speed.=
|
||||
Bane of Arthropods=
|
||||
Increases damage and applies Slowness IV to arthropod mobs (spiders, cave spiders, silverfish and endermites).=
|
||||
Blast Protection=
|
||||
Reduces explosion damage and knockback.=
|
||||
Channeling=
|
||||
Channels a bolt of lightning toward a target. Works only during thunderstorms and if target is unobstructed with opaque blocks.=
|
||||
Curse of Binding=
|
||||
Item cannot be removed from armor slots except due to death, breaking or in Creative Mode.=
|
||||
Curse of Vanishing=
|
||||
Item destroyed on death.=
|
||||
Depth Strider=
|
||||
Increases underwater movement speed.=
|
||||
Efficiency=
|
||||
Increases mining speed.=
|
||||
Feather Falling=
|
||||
Reduces fall damage.=
|
||||
Fire Aspect=
|
||||
Sets target on fire.=
|
||||
Fire Protection=
|
||||
Reduces fire damage.=
|
||||
Flame=
|
||||
Arrows set target on fire.=
|
||||
Fortune=
|
||||
Increases certain block drops.=
|
||||
Frost Walker=
|
||||
Turns water beneath the player into frosted ice and prevents the damage from magma blocks.=
|
||||
Impaling=
|
||||
Trident deals additional damage to ocean mobs.=
|
||||
Infinity=
|
||||
Shooting consumes no regular arrows.=
|
||||
Knockback=
|
||||
Increases knockback.=
|
||||
Looting=
|
||||
Increases mob loot.=
|
||||
Loyalty=
|
||||
Trident returns after being thrown. Higher levels reduce return time.=
|
||||
Luck of the Sea=
|
||||
Increases rate of good loot (enchanting books, etc.)=
|
||||
Lure=
|
||||
Decreases time until rod catches something.=
|
||||
Mending=
|
||||
Repair the item while gaining XP orbs.=
|
||||
Multishot=
|
||||
Shoot 3 arrows at the cost of one.=
|
||||
Piercing=
|
||||
|
||||
|
||||
### enchantments.lua ###
|
||||
|
||||
Arrows passes through multiple objects.=
|
||||
Power=
|
||||
Increases arrow damage.=
|
||||
Projectile Protection=
|
||||
Reduces projectile damage.=
|
||||
Protection=
|
||||
Reduces most types of damage by 4% for each level.=
|
||||
Punch=
|
||||
Increases arrow knockback.=
|
||||
Quick Charge=
|
||||
Arrows set target on fire.=
|
||||
Bane of Arthropods=
|
||||
Channeling=
|
||||
|
||||
Channels a bolt of lightning toward a target. Works only during thunderstorms and if target is unobstructed with opaque blocks.=
|
||||
|
||||
Curse of Vanishing=
|
||||
Decreases crossbow charging time.=
|
||||
Respiration=
|
||||
Decreases time until rod catches something.=
|
||||
Depth Strider=
|
||||
Efficiency=
|
||||
Extends underwater breathing time.=
|
||||
Riptide=
|
||||
Trident launches player with itself when thrown. Works only in water or rain.=
|
||||
Sharpness=
|
||||
Increases damage.=
|
||||
Silk Touch=
|
||||
Mined blocks drop themselves.=
|
||||
Smite=
|
||||
Fire Aspect=
|
||||
Flame=
|
||||
Fortune=
|
||||
Frost Walker=
|
||||
Impaling=
|
||||
Increases arrow damage.=
|
||||
Increases arrow knockback.=
|
||||
Increases certain block drops.=
|
||||
|
||||
Increases damage and applies Slowness IV to arthropod mobs (spiders, cave spiders, silverfish and endermites).=
|
||||
|
||||
Increases damage to undead mobs.=
|
||||
Soul Speed=
|
||||
Increases walking speed on soul sand.=
|
||||
Sweeping Edge=
|
||||
Increases sweeping attack damage.=
|
||||
Thorns=
|
||||
Reflects some of the damage taken when hit, at the cost of reducing durability with each proc.=
|
||||
Unbreaking=
|
||||
Increases damage.=
|
||||
Increases item durability.=
|
||||
Inventory=
|
||||
@1 Lapis Lazuli=
|
||||
Increases knockback.=
|
||||
Increases mining speed.=
|
||||
Increases mob loot.=
|
||||
Increases rate of good loot (enchanting books, etc.)=
|
||||
Increases sweeping attack damage.=
|
||||
Increases underwater movement speed.=
|
||||
Increases walking speed on soul sand.=
|
||||
Infinity=
|
||||
Item destroyed on death.=
|
||||
Knockback=
|
||||
Looting=
|
||||
Loyalty=
|
||||
Luck of the Sea=
|
||||
Lure=
|
||||
Mending=
|
||||
Mined blocks drop themselves.=
|
||||
Multishot=
|
||||
Piercing=
|
||||
Power=
|
||||
Punch=
|
||||
Quick Charge=
|
||||
Repair the item while gaining XP orbs.=
|
||||
Respiration=
|
||||
Riptide=
|
||||
Sets target on fire.=
|
||||
Sharpness=
|
||||
Shoot 3 arrows at the cost of one.=
|
||||
Shooting consumes no regular arrows.=
|
||||
Silk Touch=
|
||||
Smite=
|
||||
Soul Speed=
|
||||
Sweeping Edge=
|
||||
Trident deals additional damage to ocean mobs.=
|
||||
|
||||
Trident launches player with itself when thrown. Works only in water or rain.=
|
||||
|
||||
Trident returns after being thrown. Higher levels reduce return time.=
|
||||
|
||||
Turns water beneath the player into frosted ice and prevents the damage from magma blocks.=
|
||||
|
||||
Unbreaking=
|
||||
|
||||
### engine.lua ###
|
||||
|
||||
@1 Enchantment Levels=
|
||||
@1 Lapis Lazuli=
|
||||
Inventory=
|
||||
Level requirement: @1=
|
||||
Enchant an item=
|
||||
<player> <enchantment> [<level>]=
|
||||
Usage: /enchant <player> <enchantment> [<level>]=
|
||||
Player '@1' cannot be found.=
|
||||
There is no such enchantment '@1'.=
|
||||
The target doesn't hold an item.=
|
||||
The selected enchantment can't be added to the target item.=
|
||||
|
||||
### init.lua ###
|
||||
|
||||
'@1' is not a valid number=
|
||||
The number you have entered (@1) is too big, it must be at most @2.=
|
||||
The number you have entered (@1) is too small, it must be at least @2.=
|
||||
@1 can't be combined with @2.=
|
||||
Enchanting succeded.=
|
||||
Forcefully enchant an item=
|
||||
Usage: /forceenchant <player> <enchantment> [<level>]=
|
||||
The target item is not enchantable.=
|
||||
'@1' is not a valid number.=
|
||||
<player> <enchantment> [<level>]=
|
||||
@1 can't be combined with @2.=
|
||||
|
||||
After finally selecting your enchantment; left-click on the selection, and you will see both the lapis lazuli and your experience levels consumed. And, an enchanted item left in its place.=
|
||||
|
||||
After placing your items in the slots, the enchanting options will be shown. Hover over the options to read what is available to you.=
|
||||
|
||||
Enchant=
|
||||
Enchant an item=
|
||||
Enchanted Book=
|
||||
Enchanting Table=
|
||||
Enchant=
|
||||
|
||||
Enchanting Tables will let you enchant armors, tools, weapons, and books with various abilities. But, at the cost of some experience, and lapis lazuli.=
|
||||
|
||||
Enchanting succeded.=
|
||||
Forcefully enchant an item=
|
||||
|
||||
Place a tool, armor, weapon or book into the top left slot, and then place 1-3 Lapis Lazuli in the slot to the right.=
|
||||
|
||||
Player '@1' cannot be found.=
|
||||
Rightclick the Enchanting Table to open the enchanting menu.=
|
||||
Spend experience, and lapis to enchant various items.=
|
||||
|
||||
The number you have entered (@1) is too big, it must be at most @2.=
|
||||
|
||||
The number you have entered (@1) is too small, it must be at least @2.=
|
||||
|
||||
The selected enchantment can't be added to the target item.=
|
||||
The target doesn't hold an item.=
|
||||
The target item is not enchantable.=
|
||||
There is no such enchantment '@1'.=
|
||||
|
||||
These options are randomized, and dependent on experience level; but the enchantment strength can be increased.=
|
||||
|
||||
To increase the enchantment strength, place bookshelves around the enchanting table. However, you will need to keep 1 air node between the table, & the bookshelves to empower the enchanting table.=
|
||||
|
||||
Usage: /enchant <player> <enchantment> [<level>]=
|
||||
Usage: /forceenchant <player> <enchantment> [<level>]=
|
||||
|
||||
|
||||
##### not used anymore #####
|
||||
|
||||
# textdomain: mcl_enchanting
|
||||
Aqua Affinity=
|
||||
|
@ -37,7 +37,7 @@ local fish = function(itemstack, player, pointed_thing)
|
||||
local num = 0
|
||||
local ent = nil
|
||||
local noent = true
|
||||
|
||||
|
||||
local durability = 65
|
||||
local unbreaking = mcl_enchanting.get_enchantment(itemstack, "unbreaking")
|
||||
if unbreaking > 0 then
|
||||
@ -117,8 +117,8 @@ local fish = function(itemstack, player, pointed_thing)
|
||||
else
|
||||
minetest.add_item(pos, item)
|
||||
end
|
||||
if mcl_experience.throw_experience then
|
||||
mcl_experience.throw_experience(pos, math.random(1,6))
|
||||
if mcl_experience.throw_xp then
|
||||
mcl_experience.throw_xp(pos, math.random(1,6))
|
||||
end
|
||||
|
||||
if not minetest.is_creative_enabled(player:get_player_name()) then
|
||||
|
@ -75,9 +75,9 @@ local function give_xp(pos, player)
|
||||
local xp = meta:get_int("xp")
|
||||
if xp > 0 then
|
||||
if player then
|
||||
mcl_experience.add_experience(player, xp)
|
||||
mcl_experience.add_xp(player, xp)
|
||||
else
|
||||
mcl_experience.throw_experience(vector.add(pos, dir), xp)
|
||||
mcl_experience.throw_xp(vector.add(pos, dir), xp)
|
||||
end
|
||||
meta:set_int("xp", 0)
|
||||
end
|
||||
|
@ -317,7 +317,7 @@ minetest.register_node("mcl_mobspawners:spawner", {
|
||||
if obj then
|
||||
obj:remove()
|
||||
end
|
||||
mcl_experience.throw_experience(pos, math.random(15, 43))
|
||||
mcl_experience.throw_xp(pos, math.random(15, 43))
|
||||
end,
|
||||
|
||||
on_punch = function(pos)
|
||||
|
@ -115,16 +115,22 @@ function place_wet_sponge(itemstack, placer, pointed_thing)
|
||||
if mcl_worlds.pos_to_dimension(pointed_thing.above) == "nether" then
|
||||
minetest.item_place_node(ItemStack("mcl_sponges:sponge"), placer, pointed_thing)
|
||||
local pos = pointed_thing.above
|
||||
for n = 0, 25 do
|
||||
minetest.add_particle({
|
||||
pos = {x = pos.x + math.random(-1, 1)*math.random()/2, y = pos.y + 0.6, z = pos.z + math.random(-1, 1)*math.random()/2},
|
||||
velocity = {x = 0, y = math.random(), z = 0},
|
||||
acceleration = {x=0, y=0, z=0},
|
||||
expirationtime = math.random(),
|
||||
|
||||
for n = 1, 5 do
|
||||
minetest.add_particlespawner({
|
||||
amount = 5,
|
||||
time = 0.1,
|
||||
minpos = vector.offset(pos, -0.5, 0.6, -0.5),
|
||||
maxpos = vector.offset(pos, 0.5, 0.6, 0.5),
|
||||
minvel = vector.new(0, 0.1, 0),
|
||||
maxvel = vector.new(0, 1, 0),
|
||||
minexptime = 0.1,
|
||||
maxexptime = 1,
|
||||
minsize = 2,
|
||||
maxsize = 5,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
size = math.random(2, 5),
|
||||
texture = "mcl_particles_sponge"..math.random(1, 5)..".png",
|
||||
texture = "mcl_particles_sponge" .. n .. ".png",
|
||||
})
|
||||
end
|
||||
if not minetest.is_creative_enabled(name) then
|
||||
|
@ -4,6 +4,8 @@ minetest.register_on_leaveplayer(function(player)
|
||||
hud_totem[player] = nil
|
||||
end)
|
||||
|
||||
local particle_colors = {"98BF22", "C49E09", "337D0B", "B0B021", "1E9200"} -- TODO: real MC colors
|
||||
|
||||
-- Save the player from death when holding totem of undying in hand
|
||||
mcl_damage.register_modifier(function(obj, damage, reason)
|
||||
if obj:is_player() then
|
||||
@ -14,7 +16,7 @@ mcl_damage.register_modifier(function(obj, damage, reason)
|
||||
local ppos = obj:get_pos()
|
||||
local pnname = minetest.get_node(ppos).name
|
||||
-- Some exceptions when _not_ to save the player
|
||||
for n=1, #mobs_mc.misc.totem_fail_nodes do
|
||||
for n = 1, #mobs_mc.misc.totem_fail_nodes do
|
||||
if pnname == mobs_mc.misc.totem_fail_nodes[n] then
|
||||
return
|
||||
end
|
||||
@ -30,16 +32,41 @@ mcl_damage.register_modifier(function(obj, damage, reason)
|
||||
end
|
||||
|
||||
-- Effects
|
||||
minetest.sound_play({name = "mcl_totems_totem", gain=1}, {pos=ppos, max_hear_distance=16}, true)
|
||||
minetest.sound_play({name = "mcl_totems_totem", gain = 1}, {pos=ppos, max_hear_distance = 16}, true)
|
||||
|
||||
for i = 1, 4 do
|
||||
for c = 1, #particle_colors do
|
||||
minetest.add_particlespawner({
|
||||
amount = math.floor(100 / (4 * #particle_colors)),
|
||||
time = 1,
|
||||
minpos = vector.offset(ppos, 0, -1, 0),
|
||||
maxpos = vector.offset(ppos, 0, 1, 0),
|
||||
minvel = vector.new(-1.5, 0, -1.5),
|
||||
maxvel = vector.new(1.5, 1.5, 1.5),
|
||||
minacc = vector.new(0, -0.1, 0),
|
||||
maxacc = vector.new(0, -1, 0),
|
||||
minexptime = 1,
|
||||
maxexptime = 3,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = true,
|
||||
collision_removal = true,
|
||||
object_collision = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_totem" .. i .. ".png^[colorize:#" .. particle_colors[c],
|
||||
glow = 10,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Big totem overlay
|
||||
if not hud_totem[obj] then
|
||||
hud_totem[obj] = obj:hud_add({
|
||||
hud_elem_type = "image",
|
||||
text = "mcl_totems_totem.png",
|
||||
position = { x=0.5, y=1 },
|
||||
scale = { x=17, y=17 },
|
||||
offset = { x=0, y=-178 },
|
||||
position = {x = 0.5, y = 1},
|
||||
scale = {x = 17, y = 17},
|
||||
offset = {x = 0, y = -178},
|
||||
z_index = 100,
|
||||
})
|
||||
minetest.after(3, function()
|
||||
|
@ -1496,7 +1496,7 @@ local function register_dimension_biomes()
|
||||
heat_point = 100,
|
||||
humidity_point = 0,
|
||||
_mcl_biome_type = "hot",
|
||||
_mcl_palette_index = 19,
|
||||
_mcl_palette_index = 17,
|
||||
})
|
||||
|
||||
--[[ THE END ]]
|
||||
|
@ -91,8 +91,8 @@ end
|
||||
-- register saturation hudbar
|
||||
hb.register_hudbar("hunger", 0xFFFFFF, S("Food"), { icon = "hbhunger_icon.png", bgicon = "hbhunger_bgicon.png", bar = "hbhunger_bar.png" }, 1, 20, 20, false)
|
||||
if mcl_hunger.debug then
|
||||
hb.register_hudbar("saturation", 0xFFFFFF, S("Saturation"), { icon = "mcl_hunger_icon_saturation.png", bgicon = "mcl_hunger_bgicon_saturation.png", bar = "mcl_hunger_bar_saturation.png" }, 1, mcl_hunger.SATURATION_INIT, 200, false, S("%s: %.1f/%d"))
|
||||
hb.register_hudbar("exhaustion", 0xFFFFFF, S("Exhaust."), { icon = "mcl_hunger_icon_exhaustion.png", bgicon = "mcl_hunger_bgicon_exhaustion.png", bar = "mcl_hunger_bar_exhaustion.png" }, 1, 0, mcl_hunger.EXHAUST_LVL, false, S("%s: %d/%d"))
|
||||
hb.register_hudbar("saturation", 0xFFFFFF, S("Saturation"), { icon = "mcl_hunger_icon_saturation.png", bgicon = "mcl_hunger_bgicon_saturation.png", bar = "mcl_hunger_bar_saturation.png" }, 1, mcl_hunger.SATURATION_INIT, 200, false)
|
||||
hb.register_hudbar("exhaustion", 0xFFFFFF, S("Exhaust."), { icon = "mcl_hunger_icon_exhaustion.png", bgicon = "mcl_hunger_bgicon_exhaustion.png", bar = "mcl_hunger_bar_exhaustion.png" }, 1, 0, mcl_hunger.EXHAUST_LVL, false)
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
@ -134,47 +134,46 @@ minetest.register_on_player_hpchange(function(player, hp_change)
|
||||
end
|
||||
end)
|
||||
|
||||
local main_timer = 0
|
||||
local timer = 0 -- Half second timer
|
||||
local timerMult = 1 -- Cycles from 0 to 7, each time when timer hits half a second
|
||||
local food_tick_timers = {} -- one food_tick_timer per player, keys are the player-objects
|
||||
minetest.register_globalstep(function(dtime)
|
||||
main_timer = main_timer + dtime
|
||||
timer = timer + dtime
|
||||
if main_timer > mcl_hunger.HUD_TICK or timer > 0.25 then
|
||||
if main_timer > mcl_hunger.HUD_TICK then main_timer = 0 end
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
local name = player:get_player_name()
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
|
||||
local h = tonumber(mcl_hunger.get_hunger(player))
|
||||
local hp = player:get_hp()
|
||||
if timer > 0.25 then
|
||||
-- Slow health regeneration, and hunger damage (every 4s).
|
||||
-- Regeneration rate based on tutorial video <https://www.youtube.com/watch?v=zs2t-xCVHBo>.
|
||||
-- Minecraft Wiki seems to be wrong in claiming that full hunger gives 0.5s regen rate.
|
||||
if timerMult == 0 then
|
||||
if h >= 18 and hp > 0 and hp < 20 then
|
||||
-- +1 HP, +exhaustion
|
||||
player:set_hp(hp+1)
|
||||
mcl_hunger.exhaust(name, mcl_hunger.EXHAUST_REGEN)
|
||||
local food_tick_timer = food_tick_timers[player] and food_tick_timers[player] + dtime or 0
|
||||
local player_name = player:get_player_name()
|
||||
local food_level = mcl_hunger.get_hunger(player)
|
||||
local food_saturation_level = mcl_hunger.get_saturation(player)
|
||||
local player_health = player:get_hp()
|
||||
|
||||
if food_tick_timer > 4.0 then
|
||||
food_tick_timer = 0
|
||||
|
||||
if food_level >= 18 then -- slow regenration
|
||||
if player_health > 0 and player_health < 20 then
|
||||
player:set_hp(player_health+1)
|
||||
mcl_hunger.exhaust(player_name, mcl_hunger.EXHAUST_REGEN)
|
||||
mcl_hunger.update_exhaustion_hud(player, mcl_hunger.get_exhaustion(player))
|
||||
elseif h == 0 then
|
||||
-- Damage hungry player down to 1 HP
|
||||
-- TODO: Allow starvation at higher difficulty levels
|
||||
if hp-1 > 0 then
|
||||
mcl_util.deal_damage(player, 1, {type = "starve"})
|
||||
end
|
||||
end
|
||||
|
||||
elseif food_level == 0 then -- starvation
|
||||
-- the amount of health at which a player will stop to get
|
||||
-- harmed by starvation (10 for Easy, 1 for Normal, 0 for Hard)
|
||||
local maximum_starvation = 1
|
||||
-- TODO: implement Minecraft-like difficulty modes and the update maximumStarvation here
|
||||
if player_health > maximum_starvation then
|
||||
mcl_util.deal_damage(player, 1, {type = "starve"})
|
||||
end
|
||||
end
|
||||
|
||||
elseif food_tick_timer > 0.5 and food_level == 20 and food_saturation_level >= 6 then -- fast regeneration
|
||||
if player_health > 0 and player_health < 20 then
|
||||
food_tick_timer = 0
|
||||
player:set_hp(player_health+1)
|
||||
mcl_hunger.exhaust(player_name, mcl_hunger.EXHAUST_REGEN)
|
||||
mcl_hunger.update_exhaustion_hud(player, mcl_hunger.get_exhaustion(player))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if timer > 0.25 then
|
||||
timer = 0
|
||||
timerMult = timerMult + 2
|
||||
if timerMult > 7 then
|
||||
timerMult = 0
|
||||
end
|
||||
|
||||
food_tick_timers[player] = food_tick_timer -- update food_tick_timer table
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -118,17 +118,6 @@ def colorize_alpha(colormap, source, colormap_pixel, texture_size, destination):
|
||||
colorize(colormap, source, colormap_pixel, texture_size, tempfile2.name)
|
||||
os.system("composite -compose Dst_In "+source+" "+tempfile2.name+" -alpha Set "+destination)
|
||||
|
||||
# This function is unused atm.
|
||||
# TODO: Implemnt colormap extraction
|
||||
def extract_colormap(colormap, colormap_pixel, positions):
|
||||
os.system("convert -size 16x16 canvas:black "+tempfile1.name)
|
||||
x=0
|
||||
y=0
|
||||
for p in positions:
|
||||
os.system("convert "+colormap+" -crop 1x1+"+colormap_pixel+" -depth 8 "+tempfile2.name)
|
||||
os.system("composite -geometry 16x16+"+x+"+"+y+" "+tempfile2.name)
|
||||
x = x+1
|
||||
|
||||
def target_dir(directory):
|
||||
if make_texture_pack:
|
||||
return output_dir + "/" + output_dir_name
|
||||
@ -397,20 +386,60 @@ def convert_textures():
|
||||
colorize_alpha(FOLIAG, tex_dir+"/blocks/vine.png", "16+39", str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/mcl_core_vine.png")
|
||||
|
||||
# Tall grass, fern (inventory images)
|
||||
pcol = "49+172" # Plains grass color
|
||||
pcol = "50+173" # Plains grass color
|
||||
colorize_alpha(GRASS, tex_dir+"/blocks/tallgrass.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_tallgrass_inv.png")
|
||||
colorize_alpha(GRASS, tex_dir+"/blocks/fern.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_fern_inv.png")
|
||||
colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_fern_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_fern_inv.png")
|
||||
colorize_alpha(GRASS, tex_dir+"/blocks/double_plant_grass_top.png", pcol, str(PXSIZE), target_dir("/mods/ITEMS/mcl_flowers/textures")+"/mcl_flowers_double_plant_grass_inv.png")
|
||||
|
||||
# TODO: Convert grass palette
|
||||
|
||||
offset = [
|
||||
[ pcol, "", "grass" ], # Default grass: Plains
|
||||
# Convert grass palette: https://minecraft.fandom.com/wiki/Tint
|
||||
grass_colors = [
|
||||
# [Coords or #Color, AdditionalTint], # Index - Minecraft biome name (MineClone2 biome names)
|
||||
["50+173"], # 0 - Plains (flat, Plains, Plains_beach, Plains_ocean, End)
|
||||
["0+255"], # 1 - Savanna (Savanna, Savanna_beach, Savanna_ocean)
|
||||
["255+255"], # 2 - Ice Spikes (IcePlainsSpikes, IcePlainsSpikes_ocean)
|
||||
["255+255"], # 3 - Snowy Taiga (ColdTaiga, ColdTaiga_beach, ColdTaiga_beach_water, ColdTaiga_ocean)
|
||||
["178+193"], # 4 - Giant Tree Taiga (MegaTaiga, MegaTaiga_ocean)
|
||||
["178+193"], # 5 - Giant Tree Taiga (MegaSpruceTaiga, MegaSpruceTaiga_ocean)
|
||||
["203+239"], # 6 - Montains (ExtremeHills, ExtremeHills_beach, ExtremeHills_ocean)
|
||||
["203+239"], # 7 - Montains (ExtremeHillsM, ExtremeHillsM_ocean)
|
||||
["203+239"], # 8 - Montains (ExtremeHills+, ExtremeHills+_snowtop, ExtremeHills+_ocean)
|
||||
["50+173"], # 9 - Beach (StoneBeach, StoneBeach_ocean)
|
||||
["255+255"], # 10 - Snowy Tundra (IcePlains, IcePlains_ocean)
|
||||
["50+173"], # 11 - Sunflower Plains (SunflowerPlains, SunflowerPlains_ocean)
|
||||
["191+203"], # 12 - Taiga (Taiga, Taiga_beach, Taiga_ocean)
|
||||
["76+112"], # 13 - Forest (Forest, Forest_beach, Forest_ocean)
|
||||
["76+112"], # 14 - Flower Forest (FlowerForest, FlowerForest_beach, FlowerForest_ocean)
|
||||
["101+163"], # 15 - Birch Forest (BirchForest, BirchForest_ocean)
|
||||
["101+163"], # 16 - Birch Forest Hills (BirchForestM, BirchForestM_ocean)
|
||||
["0+255"], # 17 - Desert and Nether (Desert, Desert_ocean, Nether)
|
||||
["76+112", "#28340A"], # 18 - Dark Forest (RoofedForest, RoofedForest_ocean)
|
||||
["#90814d"], # 19 - Mesa (Mesa, Mesa_sandlevel, Mesa_ocean, )
|
||||
["#90814d"], # 20 - Mesa (MesaBryce, MesaBryce_sandlevel, MesaBryce_ocean)
|
||||
["#90814d"], # 21 - Mesa (MesaPlateauF, MesaPlateauF_grasstop, MesaPlateauF_sandlevel, MesaPlateauF_ocean)
|
||||
["#90814d"], # 22 - Mesa (MesaPlateauFM, MesaPlateauFM_grasstop, MesaPlateauFM_sandlevel, MesaPlateauFM_ocean)
|
||||
["0+255"], # 23 - Shattered Savanna (or Savanna Plateau ?) (SavannaM, SavannaM_ocean)
|
||||
["12+36"], # 24 - Jungle (Jungle, Jungle_shore, Jungle_ocean)
|
||||
["12+36"], # 25 - Modified Jungle (JungleM, JungleM_shore, JungleM_ocean)
|
||||
["12+61"], # 26 - Jungle Edge (JungleEdge, JungleEdge_ocean)
|
||||
["12+61"], # 27 - Modified Jungle Edge (JungleEdgeM, JungleEdgeM_ocean)
|
||||
["#6A7039"], # 28 - Swamp (Swampland, Swampland_shore, Swampland_ocean)
|
||||
["25+25"], # 29 - Mushroom Fields and Mushroom Field Shore (MushroomIsland, MushroomIslandShore, MushroomIsland_ocean)
|
||||
]
|
||||
for o in offset:
|
||||
colorize(GRASS, tex_dir+"/blocks/grass_top.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+".png")
|
||||
colorize_alpha(GRASS, tex_dir+"/blocks/grass_side_overlay.png", o[0], str(PXSIZE), target_dir("/mods/ITEMS/mcl_core/textures")+"/default_"+o[2]+"_side.png")
|
||||
|
||||
grass_palette_file = target_dir("/mods/ITEMS/mcl_core/textures") + "/mcl_core_palette_grass.png"
|
||||
os.system("convert -size 16x16 canvas:transparent " + grass_palette_file)
|
||||
|
||||
for i, color in enumerate(grass_colors):
|
||||
if color[0][0] == "#":
|
||||
os.system("convert -size 1x1 xc:\"" + color[0] + "\" " + tempfile1.name + ".png")
|
||||
else:
|
||||
os.system("convert " + GRASS + " -crop 1x1+" + color[0] + " " + tempfile1.name + ".png")
|
||||
|
||||
if len(color) > 1:
|
||||
os.system("convert " + tempfile1.name + ".png \\( -size 1x1 xc:\"" + color[1] + "\" \\) -compose blend -define compose:args=50,50 -composite " + tempfile1.name + ".png")
|
||||
|
||||
os.system("convert " + grass_palette_file + " \\( " + tempfile1.name + ".png -geometry +" + str(i % 16) + "+" + str(int(i / 16)) + " \\) -composite " + grass_palette_file)
|
||||
|
||||
# Metadata
|
||||
if make_texture_pack:
|
||||
|
60
tools/analyze-packet-spam
Executable file
@ -0,0 +1,60 @@
|
||||
#!/bin/sh -eu
|
||||
# analyze-packet-spam – show minetest client packet count per second
|
||||
# Copyright © 2021 Nils Dagsson Moskopp (erlehmann)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
|
||||
# Dieses Programm hat das Ziel, die Medienkompetenz der Leser zu
|
||||
# steigern. Gelegentlich packe ich sogar einen handfesten Buffer
|
||||
# Overflow oder eine Format String Vulnerability zwischen die anderen
|
||||
# Codezeilen und schreibe das auch nicht dran.
|
||||
|
||||
# This script takes a minetest log with at least INFO log level and
|
||||
# outputs the MINETEST network protocol packet count per second.
|
||||
|
||||
# To collect such a log file of minetest running for 10 minutes, run:
|
||||
# timeout 600 minetest --info >log.txt 2>&1 >/dev/null
|
||||
|
||||
# To get packet counts from that file, run:
|
||||
# ./analyze-packet-spam <log.txt
|
||||
|
||||
TEMPFILE=$(mktemp /tmp/minetest.analyze-packet-spam.XXXXXXXX)
|
||||
|
||||
grep -F 'INFO[Main]: cmd' \
|
||||
|while read DATE TIME _ _ PACKET_ID PACKET_NAME _ PACKET_COUNT; do
|
||||
TIMESTAMP=$(date +%s --date "${DATE} ${TIME%:}")
|
||||
PACKET_NAME=${PACKET_NAME#(}
|
||||
PACKET_NAME=${PACKET_NAME%)}
|
||||
VARIABLE=PACKET_COUNT_"${PACKET_NAME}"
|
||||
eval "$( echo $VARIABLE=\$\(\( \${$VARIABLE:-0} + ${PACKET_COUNT} \)\) )"
|
||||
printf '%s ' \
|
||||
"${TIMESTAMP}" \
|
||||
"${VARIABLE}"
|
||||
eval echo \$${VARIABLE}
|
||||
done >"${TEMPFILE}"
|
||||
|
||||
TIMESTAMP_START=$( <"${TEMPFILE}" head -n1 |cut -d' ' -f1 )
|
||||
TIMESTAMP_END=$( <"${TEMPFILE}" tail -n1 |cut -d' ' -f1 )
|
||||
DURATION=$(( 30 + ${TIMESTAMP_END} - ${TIMESTAMP_START} ))
|
||||
|
||||
PACKET_NAME_SEEN=''
|
||||
<"${TEMPFILE}" tac \
|
||||
|while read _ PACKET_NAME PACKET_COUNT; do
|
||||
case "${PACKET_NAME_SEEN}" in
|
||||
*"${PACKET_NAME}"*)
|
||||
;;
|
||||
*)
|
||||
PACKET_COUNT_PER_SECOND=$(
|
||||
printf '1k %s %s /p' "${PACKET_COUNT}" "${DURATION}" \
|
||||
|dc
|
||||
)
|
||||
printf '%s\t%s\n' "${PACKET_COUNT_PER_SECOND}" "${PACKET_NAME}"
|
||||
PACKET_NAME_SEEN="${PACKET_NAME_SEEN} ${PACKET_NAME}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
unlink "${TEMPFILE}"
|
50
tools/generate_ingame_credits.lua
Executable file
@ -0,0 +1,50 @@
|
||||
#! /usr/bin/env lua
|
||||
-- Script to automatically generate mods/HUD/mcl_credits/people.lua from CREDITS.md
|
||||
-- Run from MCL2 root folder
|
||||
|
||||
local colors = {
|
||||
["Creator of MineClone"] = "0x0A9400",
|
||||
["Creator of MineClone2"] = "0xFBF837",
|
||||
["Maintainers"] = "0xFF51D5",
|
||||
["Developers"] = "0xF84355",
|
||||
["Contributors"] = "0x52FF00",
|
||||
["MineClone5"] = "0xA60014",
|
||||
["Original Mod Authors"] = "0x343434",
|
||||
["3D Models"] = "0x0019FF",
|
||||
["Textures"] = "0xFF9705",
|
||||
["Translations"] = "0x00FF60",
|
||||
["Funders"] = "0xF7FF00",
|
||||
["Special thanks"] = "0x00E9FF",
|
||||
}
|
||||
|
||||
local from = io.open("CREDITS.md", "r")
|
||||
local to = io.open("mods/HUD/mcl_credits/people.lua", "w")
|
||||
|
||||
to:write([[
|
||||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
]])
|
||||
|
||||
to:write("return {\n")
|
||||
|
||||
local started_block = false
|
||||
|
||||
for line in from:lines() do
|
||||
if line:find("## ") == 1 then
|
||||
if started_block then
|
||||
to:write("\t}},\n")
|
||||
end
|
||||
local title = line:sub(4, #line)
|
||||
to:write("\t{S(\"" .. title .. "\"), " .. (colors[title] or "0xFFFFFF") .. ", {\n")
|
||||
started_block = true
|
||||
elseif line:find("*") == 1 then
|
||||
to:write("\t\t\"" .. line:sub(3, #line) .. "\",\n")
|
||||
end
|
||||
end
|
||||
|
||||
if started_block then
|
||||
to:write("\t}},\n")
|
||||
end
|
||||
|
||||
to:write("}\n")
|