Compare commits
112 Commits
master
...
flying-min
Author | SHA1 | Date |
---|---|---|
teknomunk | 89f4209565 | |
teknomunk | 2a7ffd1528 | |
teknomunk | 5feb7706a3 | |
teknomunk | 5e8d8892f4 | |
teknomunk | 521d60192f | |
teknomunk | c57e9402fb | |
teknomunk | 2d23d7967f | |
teknomunk | 4b8a32ac45 | |
teknomunk | 2fd1c001f5 | |
teknomunk | 4d0c767e66 | |
teknomunk | cef458a959 | |
teknomunk | 2343b2d3df | |
teknomunk | 137c0d0c49 | |
teknomunk | d571f6d8f2 | |
teknomunk | ea2d4ac889 | |
teknomunk | c127eb449e | |
teknomunk | 51a29ed423 | |
teknomunk | c905b7c9b5 | |
teknomunk | 51ec5990c3 | |
teknomunk | e67aeaf800 | |
teknomunk | 15616cb103 | |
teknomunk | 1db7d4bc0e | |
teknomunk | f18e086da1 | |
teknomunk | fd81d19a94 | |
teknomunk | 0fedfd2e40 | |
teknomunk | c54dff1f27 | |
teknomunk | d3bb27c053 | |
teknomunk | 077834951f | |
teknomunk | fc3de80bf7 | |
teknomunk | 5145deb273 | |
teknomunk | 62964b4dab | |
teknomunk | 83b794060a | |
teknomunk | 9bb814620d | |
teknomunk | 8eee894429 | |
teknomunk | cdc2310e23 | |
teknomunk | 19da277b3b | |
teknomunk | 4965d6a24d | |
teknomunk | 8fb5412bb0 | |
teknomunk | 8e71c40394 | |
teknomunk | dd9ede59dc | |
teknomunk | f4e74a11e8 | |
teknomunk | 95867b5da8 | |
teknomunk | 7bdceb1c21 | |
teknomunk | 0e899d1bd0 | |
teknomunk | 3252ac7919 | |
teknomunk | 33866ad21f | |
teknomunk | d8bdf7b9d2 | |
teknomunk | 74f0542ba7 | |
teknomunk | 9ddfd0c4e1 | |
teknomunk | 020737266b | |
teknomunk | 6203d20e04 | |
teknomunk | 74f9d13361 | |
teknomunk | 2f15de0150 | |
teknomunk | 8a69a80707 | |
teknomunk | 7120365652 | |
teknomunk | 1515a9ecbd | |
teknomunk | 233495d6f8 | |
teknomunk | 47ed64f45b | |
teknomunk | 1315f7ea95 | |
teknomunk | f13f52ac3b | |
teknomunk | 320e36b455 | |
teknomunk | f322dc9e26 | |
teknomunk | e5f194cdd5 | |
teknomunk | bd4d09b6e2 | |
teknomunk | 178719b247 | |
teknomunk | 256018ca1e | |
teknomunk | 019943346d | |
teknomunk | 98f58cd78f | |
teknomunk | 6b650511b1 | |
teknomunk | 65cd8dabd8 | |
teknomunk | 3ed88a8fba | |
teknomunk | e27d5a9ae0 | |
teknomunk | 59e2ab01a4 | |
teknomunk | 32e626fa6f | |
teknomunk | bdab5b1853 | |
teknomunk | 253c82eb31 | |
teknomunk | 62de57b13c | |
teknomunk | 59b32a89e0 | |
teknomunk | d07a7d4ae6 | |
teknomunk | 430842f052 | |
teknomunk | 52846d67a0 | |
teknomunk | dbd4675856 | |
teknomunk | 8bd5559d13 | |
teknomunk | 2265ac1dce | |
teknomunk | 3efd9d123a | |
teknomunk | 91964536c7 | |
teknomunk | 9332d828a8 | |
teknomunk | df29329d74 | |
teknomunk | 23c2fa8649 | |
teknomunk | 7a22c1de23 | |
teknomunk | 44142b65dd | |
teknomunk | 4d807e2716 | |
teknomunk | b179bff3b9 | |
teknomunk | b1cd177bb6 | |
teknomunk | ada5fe43ee | |
teknomunk | 7736bfc0dd | |
teknomunk | 29fa07f785 | |
teknomunk | 972b104b68 | |
teknomunk | 72a2dfebc2 | |
teknomunk | 56bf3257de | |
teknomunk | 63ed9bc048 | |
teknomunk | a667721c3d | |
teknomunk | 30ccead2b4 | |
teknomunk | 8447b99e78 | |
teknomunk | 382c5ee7d9 | |
teknomunk | 153917fc35 | |
teknomunk | 8c13ef1784 | |
teknomunk | 5a54050197 | |
teknomunk | 2b51f34e7c | |
teknomunk | 6ee2a0cf84 | |
teknomunk | 5cb23790a9 | |
teknomunk | 8ab04f0305 |
|
@ -13,19 +13,18 @@ labels:
|
|||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CODE_OF_CONDUCT.md
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
<!--
|
||||
What version of VoxeLibre are you using? We do not provide support for outdated versions of VoxeLibre.
|
||||
"/ver" command will output the version you're running.
|
||||
What version of MineClone2 are you using? We do not provide support for outdated versions of MineClone2.
|
||||
Current latest version is listed here, at the top:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/tags
|
||||
https://git.minetest.land/MineClone2/MineClone2/tags
|
||||
-->
|
||||
VoxeLibre version:
|
||||
MineClone2 version:
|
||||
|
||||
### What happened?
|
||||
Report about the bug! Please send large log snippets as an attachement file.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
|
||||
name: "Feature request"
|
||||
about: "File a feature request"
|
||||
about: "File a feature request not in Minecraft"
|
||||
labels:
|
||||
|
||||
- "non-Minecraft feature"
|
||||
|
@ -10,17 +10,17 @@ labels:
|
|||
---
|
||||
|
||||
<!--
|
||||
Got a new feature request? Explain to us why we should consider your idea.
|
||||
Got a new non-Minecraft feature request? Explain to us why we should consider your idea.
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CODE_OF_CONDUCT.md
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
### Feature
|
||||
Tell us about your requested feature!
|
||||
Tell us about your requested feature not in Minecraft!
|
||||
|
||||
### Why
|
||||
Tell us why should we implement it!
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
|
||||
name: "Missing Feature request"
|
||||
about: "File a missing feature request in Minecraft but not in MineClone2"
|
||||
labels:
|
||||
|
||||
- "missing feature"
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for taking the time to fill out this missing feature request!
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
### Current feature in Minecraft
|
||||
Tell us about the feature currently in Minecraft! What is it like on Minecraft?
|
||||
|
||||
### Current feature in MineClone2
|
||||
Tell us about the feature currently in MineClone2! What is different?
|
|
@ -8,13 +8,13 @@ labels:
|
|||
|
||||
<!--
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CONTRIBUTING.md#how-you-can-help-as-a-programmer
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#how-you-can-help-as-a-programmer
|
||||
|
||||
By submitting this pull request, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/src/branch/master/CODE_OF_CONDUCT.md
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
Tell us about your pull request! Reference related issues, if necessary.
|
||||
Tell us about your pull request! Reference related issues, if necessary
|
||||
|
||||
### Testing
|
||||
Tell us how to test your changes!
|
||||
|
|
14
API.md
|
@ -1,10 +1,10 @@
|
|||
# API
|
||||
## Groups
|
||||
VoxeLibre makes very extensive use of groups. Making sure your items and objects have the correct group memberships is very important.
|
||||
MineClone 2 makes very extensive use of groups. Making sure your items and objects have the correct group memberships is very important.
|
||||
Groups are explained in `GROUPS.md`.
|
||||
|
||||
## Mod naming convention
|
||||
Mods mods in VoxeLibre follow a simple naming convention: Mods with the prefix "`vl_`" and “`mcl_`” are specific to VoxeLibre (formerly known as MineClone2), although they may be based on an existing standalone. Mods which lack this prefix are *usually* verbatim copies of a standalone mod. Some modifications may still have been applied, but the APIs are held compatible.
|
||||
Mods mods in MineClone 2 follow a simple naming convention: Mods with the prefix “`mcl_`” are specific to MineClone 2, although they may be based on an existing standalone. Mods which lack this prefix are *usually* verbatim copies of a standalone mod. Some modifications may still have been applied, but the APIs are held compatible.
|
||||
|
||||
## Adding items
|
||||
### Special fields
|
||||
|
@ -31,7 +31,7 @@ All nodes can have these fields:
|
|||
Use the `mcl_sounds` mod for the sounds.
|
||||
|
||||
## APIs
|
||||
A lot of things are possible by using one of the APIs in the mods. Many of them are documented in `API.md` files located in the directories of the specific mods. Some use `.txt` files or have some documentation in the comments along the code. Note that not all APIs are documented yet, but it is planned. The following APIs should be more or less stable but keep in mind that VoxeLibre is still unfinished. All directory names are relative to `mods/`
|
||||
A lot of things are possible by using one of the APIs in the mods. Note that not all APIs are documented yet, but it is planned. The following APIs should be more or less stable but keep in mind that MineClone 2 is still unfinished. All directory names are relative to `mods/`
|
||||
|
||||
### Items
|
||||
* Doors: `ITEMS/mcl_doors`
|
||||
|
@ -46,7 +46,8 @@ A lot of things are possible by using one of the APIs in the mods. Many of them
|
|||
### Mobs
|
||||
* Mobs: `ENTITIES/mcl_mobs`
|
||||
|
||||
VoxeLibre uses its own mobs framework, which is a fork of Mobs Redo [`mobs`] by TenPlus1.
|
||||
MineClone 2 uses its own mobs framework, called “Mobs Redo: MineClone 2 Edition” or “MRM” for short.
|
||||
This is a fork of Mobs Redo [`mobs`] by TenPlus1.
|
||||
|
||||
You can add your own mobs, spawn eggs and spawning rules with this mod.
|
||||
API documnetation is included in `ENTITIES/mcl_mobs/api.txt`.
|
||||
|
@ -54,7 +55,7 @@ API documnetation is included in `ENTITIES/mcl_mobs/api.txt`.
|
|||
This mod includes modificiations from the original Mobs Redo. Some items have been removed or moved to other mods.
|
||||
The API is mostly identical, but a few features have been added. Compability is not really a goal,
|
||||
but function and attribute names of Mobs Redo 1.41 are kept.
|
||||
If you have code for a mod which works fine under Mobs Redo, it should be easy to make it work in VoxeLibre.
|
||||
If you have code for a mod which works fine under Mobs Redo, it should be easy to make it work in MineClone 2,
|
||||
chances are good that it works out of the box.
|
||||
|
||||
### Help
|
||||
|
@ -67,7 +68,6 @@ chances are good that it works out of the box.
|
|||
|
||||
### Utility APIs
|
||||
* Change player physics: `PLAYER/playerphysics`
|
||||
* Change player FOV: `PLAYER/mcl_fovapi`
|
||||
* Select random treasures: `CORE/mcl_loot`
|
||||
* Get flowing direction of liquids: `CORE/flowlib`
|
||||
* `on_walk_over` callback for nodes: `CORE/walkover`
|
||||
|
@ -77,7 +77,7 @@ chances are good that it works out of the box.
|
|||
* Flowers and flower pots
|
||||
|
||||
### Unstable APIs
|
||||
The following APIs may be subject to change in the future. You could already use these APIs but there will probably be breaking changes in the future, or the API is not as fleshed out as it should be. Use at your own risk!
|
||||
The following APIs may be subject to change in future. You could already use these APIs but there will probably be breaking changes in the future, or the API is not as fleshed out as it should be. Use at your own risk!
|
||||
|
||||
* Panes (like glass panes and iron bars): `ITEMS/xpanes`
|
||||
* `_on_ignite` callback: `ITEMS/mcl_fire`
|
||||
|
|
159
CONTRIBUTING.md
|
@ -1,47 +1,48 @@
|
|||
# Contributing to VoxeLibre
|
||||
So you want to contribute to VoxeLibre?
|
||||
# Contributing to MineClone2
|
||||
So you want to contribute to MineClone2?
|
||||
Wow, thank you! :-)
|
||||
|
||||
VoxeLibre is maintained by AncientMariner and Herowl. If you have any
|
||||
MineClone2 is maintained by AncientMariner and Nicu. If you have any
|
||||
problems or questions, contact us on Discord/Matrix (See Links section below).
|
||||
|
||||
You can help with VoxeLibre's development in many different ways,
|
||||
You can help with MineClone2's development in many different ways,
|
||||
whether you're a programmer or not.
|
||||
|
||||
## VoxeLibre's development target is to...
|
||||
- Create a stable, peformant, moddable, free/libre game inspired by Minecraft
|
||||
## MineClone2's development target is to...
|
||||
- Create a stable, peformant, moddable, free/libre game based on Minecraft
|
||||
using the Minetest engine, usable in both singleplayer and multiplayer.
|
||||
- Currently, a lot of features are already implemented.
|
||||
Polishing existing features is always welcome.
|
||||
|
||||
## Links
|
||||
* [Mesehub](https://git.minetest.land/VoxeLibre/VoxeLibre)
|
||||
* [Mesehub](https://git.minetest.land/MineClone2/MineClone2)
|
||||
* [Discord](https://discord.gg/xE4z8EEpDC)
|
||||
* [YouTube](https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A)
|
||||
* [Matrix](https://app.element.io/#/room/#voxelibre:matrix.org)
|
||||
* [Reddit](https://www.reddit.com/r/VoxeLibre/)
|
||||
* [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)
|
||||
|
||||
## Using git
|
||||
VoxeLibre is developed using the version control system
|
||||
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
|
||||
VoxeLibre, git is not required. However, git is a tool that will be
|
||||
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.
|
||||
|
||||
Look at our wiki for some concrete guides:
|
||||
https://git.minetest.land/VoxeLibre/VoxeLibre/wiki/
|
||||
https://git.minetest.land/MineClone2/MineClone2/wiki/
|
||||
|
||||
## How you can help as a non-programmer
|
||||
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/VoxeLibre/VoxeLibre/issues),
|
||||
[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
|
||||
|
@ -59,7 +60,8 @@ 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 or Matrix space.
|
||||
an issue, feel free to ask on the Discord / Matrix server or the IRC
|
||||
channel.
|
||||
|
||||
The link to the mesehub registration page is: https://git.minetest.land/user/sign_up
|
||||
(It appears to sometimes get lost on the page itsself)
|
||||
|
@ -73,7 +75,7 @@ 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 VoxeLibre and Minetest versions you are using (from Minetest 5.7 type /ver, for previous versions, check the game.conf or README.md file).
|
||||
* Tell us which MineClone2 and Minetest versions you are using (from Minetest 5.7 type /ver, for previous versions, check the game.conf or README.md file).
|
||||
* 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.
|
||||
|
@ -82,14 +84,14 @@ behavior.
|
|||
* 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 VoxeLibre development. It's better
|
||||
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 VoxeLibre
|
||||
* 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 VoxeLibre development and
|
||||
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
|
||||
|
@ -101,21 +103,20 @@ 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/VoxeLibre/VoxeLibre/pulls>. Note that pull
|
||||
<https://git.minetest.land/MineClone2/MineClone2/pulls>. Note that pull
|
||||
requests that start with a `WIP:` are not done yet and therefore could
|
||||
still undergo substantial change. Testing these is still helpful however
|
||||
because that is the reason developers put them up as WIP so other people
|
||||
can have a look at the PR. The wiki has an article with instructions
|
||||
on how to test Pull Requests:
|
||||
<https://git.minetest.land/VoxeLibre/VoxeLibre/wiki/Testing-Pull-Requests>.
|
||||
can have a look at the PR.
|
||||
|
||||
### Contributing assets
|
||||
Due to license problems, VoxeLibre cannot use Minecraft's assets,
|
||||
Due to license problems, MineClone2 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 and/or Matrix space.
|
||||
It's also a good idea to join the Discord server
|
||||
(or alternatively IRC or Matrix).
|
||||
|
||||
#### Textures
|
||||
For textures we prefer original art, but in the absence of that will accept
|
||||
|
@ -127,9 +128,9 @@ If you want to make such contributions, join our Discord server. Demands
|
|||
for textures will be communicated there.
|
||||
|
||||
#### Sounds
|
||||
VoxeLibre currently does not have a consistent way to handle 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, VoxeLibre does not play
|
||||
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
|
||||
|
@ -139,7 +140,7 @@ changes made by the contributor. Use the README files in the mod to
|
|||
communicate this information.
|
||||
|
||||
#### 3D Models
|
||||
Many of the 3D Models in VoxeLibre come from
|
||||
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
|
||||
|
@ -153,13 +154,13 @@ also be credited in the Contributors section.
|
|||
### Contributing Translations
|
||||
|
||||
#### Workflow
|
||||
To add/update support for your language to VoxeLibre, you should take
|
||||
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 or Matrix - they will credit you appropriately.
|
||||
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
|
||||
|
@ -177,7 +178,7 @@ 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 VoxeLibre's code
|
||||
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.
|
||||
|
@ -202,23 +203,18 @@ 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) and other unforeseen
|
||||
expenses (in the last few years, only payments for Mesehub have been done)
|
||||
by donating to our OpenCollective link (See Links section).
|
||||
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).
|
||||
If you have been an author of a PR that got merged or contributed
|
||||
significantly to art that got merged into the game, you receive the
|
||||
`Contributor` role on our Discord (after asking for it).
|
||||
Please note that what counts as "significant" is decided by Maintainers.
|
||||
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 VoxeLibre development is done using pull requests.
|
||||
(Almost) all the MineClone2 development is done using pull requests.
|
||||
|
||||
### Recommended workflow
|
||||
* Fork the repository (in case you have not already)
|
||||
|
@ -241,11 +237,11 @@ 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.
|
||||
|
||||
### Don't hesitate to ask for help
|
||||
We appreciate any contributing effort to VoxeLibre. If you are a
|
||||
relatively new programmer, you can reach us on Discord or Matrix
|
||||
for questions about git, Lua, Minetest API, VoxeLibre codebase or
|
||||
anything related to VoxeLibre. We can help you avoid writing code that
|
||||
would be deemed inadequate, or help you become familiar with VoxeLibre
|
||||
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.
|
||||
|
||||
### Maintain your own code, even if already got merged
|
||||
|
@ -254,52 +250,40 @@ 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.
|
||||
|
||||
### Changing Gameplay
|
||||
Pull Requests that change gameplay are always subject to discussion.
|
||||
Opinions from the community on such PRs are valued, and Maintainer
|
||||
should approve the concept (which is usually granted) as well as
|
||||
the implementation (for which changes are often requested for either
|
||||
code quality or game design reasons).
|
||||
Pull Requests that change gameplay have to be properly researched and
|
||||
need to state their sources. These PRs also need the maintainer's approval
|
||||
before they are merged.
|
||||
You can use these sources:
|
||||
|
||||
* Testing things inside of Minecraft (Attach screenshots / video footage
|
||||
of the results)
|
||||
* Looking at [Minestom](https://github.com/Minestom/Minestom) code. An open source Minecraft Server implementation
|
||||
* [Official Minecraft Wiki](https://minecraft.fandom.com/wiki/Minecraft_Wiki)
|
||||
(Include a link to the specific page you used)
|
||||
|
||||
### Guidelines
|
||||
|
||||
#### Git Guidelines
|
||||
* Pushing to master is disabled - don't even try it!
|
||||
* Every change is tracked as a PR
|
||||
* All changes require at least one approval from a Developer
|
||||
* Maintainers may merge PRs without formal approval, but should also
|
||||
take others' opinions and testing into account
|
||||
* Pushing to master is disabled - don't even try it.
|
||||
* Every change is tracked as a PR.
|
||||
* All but the tiniest changes require at least one approval from a Developer
|
||||
* To update branches we use rebase not merge (so we don't end up with
|
||||
excessive git bureaucracy commits in master)
|
||||
* We use merge to add the commits from a PR/branch to master
|
||||
* Smaller PRs may be squashed before merging (especially if the commit history
|
||||
on them isn't valuable), but when in doubt prefer merging
|
||||
* Manual merging may be done by a Maintainer if there are technical problems
|
||||
with the branch, with Gitea, or the PR had been merged to from master and
|
||||
the author can't fix it for whatever reason
|
||||
* PR from a fork (usually the author has no contributor/developer privileges)
|
||||
can be retargeted and merged first into a buffer (normal new) branch on the repo
|
||||
when adopted by a Developer, and only later into master
|
||||
* Submodules should only be used if a) upstream is highly reliable and
|
||||
b) it is 100% certain that no VL specific changes to the code will be
|
||||
needed (this has never been the case before, hence VL is submodule free so far)
|
||||
* Subtrees may be used for including outside mods that don't need changes
|
||||
in the foreseeable future
|
||||
b) it is 100% certain that no mcl2 specific changes to the code will be
|
||||
needed (this has never been the case before, hence mcl2 is submodule free so far)
|
||||
* Commit messages should be descriptive
|
||||
* Try to group your submissions best as you can:
|
||||
* Try to keep your PRs small: In some cases things reasonably be can't
|
||||
split up but in general multiple small PRs are better than a big one
|
||||
* Similarly multiple small commits are better than a giant one. (use git commit -p)
|
||||
* Try to keep your PRs small: In some cases things reasonably be can't
|
||||
split up but in general multiple small PRs are better than a big one.
|
||||
* Similarly multiple small commits are better than a giant one. (use git commit -p)
|
||||
|
||||
#### Code Guidelines
|
||||
* Each mod must provide `mod.conf`.
|
||||
* Mod names are snake case, and newly added mods (or substantially changed mods
|
||||
that are included from the outside) start with `vl_`, e.g.
|
||||
`vl_hollow_logs`, . Keep in mind Minetest
|
||||
* 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.
|
||||
* In the past mods were prefixed with `mcl_`, e.g.
|
||||
`mcl_core`, `mcl_farming`, `mcl_monster_eggs`. New mods should **never** use this prefix.
|
||||
* Mods included from outside with no significant changes to the API
|
||||
(especially those using git-subtree or such) aren't prefixed.
|
||||
* To export functions, store them inside a global table named like the
|
||||
mod, e.g.
|
||||
|
||||
|
@ -373,21 +357,17 @@ end
|
|||
|
||||
### Developer status
|
||||
Active and trusted contributors are often granted write access to the
|
||||
VoxeLibre repository as a contributor. This means that they can push
|
||||
directly to the branches of our repo (except for `master`).
|
||||
Pushing to others' branches without asking is discouraged, open a PR
|
||||
targeting that branch instead (PRs can target any branch).
|
||||
|
||||
Those that have demonstrated the right technical skills and behaviour
|
||||
may be granted developer access. These are the most trusted contributors
|
||||
who will contribute to ensure coding standards and processes are followed.
|
||||
MineClone2 repository as a contributor. Those that have demonstrated the right
|
||||
technical skills and behaviours may be granted developer access. These are the
|
||||
most trusted contributors who will contribute to ensure coding standards and
|
||||
processes are followed.
|
||||
|
||||
#### Developer responsibilities
|
||||
- If you have developer/contributor privileges you can just open a new branch
|
||||
in the VL repository (which is preferred). From that you create a pull request.
|
||||
in the mcl2 repository (which is preferred). From that you create a pull request.
|
||||
This way other people can review your changes and make sure they work
|
||||
before they get merged.
|
||||
- If you do not (yet) have contributor or developer privs you do your work on a branch
|
||||
- If you do not (yet) have developer privs you do your work on a branch
|
||||
on your private repository e.g. using the "fork" function on mesehub.
|
||||
- Any developer is welcome to review, test and approve PRs. A maintainer may prefer
|
||||
to merge the PR especially if it is in a similar area to what has been worked on
|
||||
|
@ -410,14 +390,14 @@ merged.
|
|||
- Resolving conflicts and problems within the community
|
||||
|
||||
#### Current maintainers
|
||||
* AncientMariner - responsible for gameplay review, publishing releases
|
||||
* Herowl - responsible for gameplay review, publishing releases,
|
||||
* AncientMariner - responsible for gameplay review, publishing releases,
|
||||
technical guidelines
|
||||
* 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 VoxeLibre to make sure it still runs
|
||||
* 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
|
||||
|
@ -435,5 +415,6 @@ become part of a free/libre software.
|
|||
|
||||
### Crediting
|
||||
Contributors, Developers and Maintainers will be credited in
|
||||
`CREDITS.md`. There are also Discord roles for Contributors,
|
||||
`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.
|
||||
|
|
24
CREDITS.md
|
@ -3,7 +3,7 @@
|
|||
## Creator of MineClone
|
||||
* davedevils
|
||||
|
||||
## Creator of VoxeLibre
|
||||
## Creator of MineClone2
|
||||
* Wuzzy
|
||||
|
||||
## Maintainers
|
||||
|
@ -20,10 +20,10 @@
|
|||
* epCode
|
||||
* chmodsayshello
|
||||
* MrRar
|
||||
* FossFanatic
|
||||
* SmokeyDope
|
||||
* Faerraven / Michieal
|
||||
* rudzik8
|
||||
* teknomunk
|
||||
* Codiac
|
||||
|
||||
## Past Developers
|
||||
* jordan4ibanez
|
||||
|
@ -34,11 +34,10 @@
|
|||
* NO11
|
||||
* SumianVoice
|
||||
* PrairieWind
|
||||
* FossFanatic
|
||||
* Codiac
|
||||
|
||||
## Contributors
|
||||
* RandomLegoBrick
|
||||
* rudzik8
|
||||
* Code-Sploit
|
||||
* aligator
|
||||
* Rootyjr
|
||||
|
@ -130,23 +129,12 @@
|
|||
* Bakawun
|
||||
* JoseDouglas26
|
||||
* Zasco
|
||||
* PrWalterB
|
||||
* michaljmalinowski
|
||||
* nixnoxus
|
||||
* Potiron
|
||||
* Tuxilio
|
||||
* Impulse
|
||||
* Doods
|
||||
* SOS-Games
|
||||
* Bram
|
||||
* qoheniac
|
||||
* WillConker
|
||||
|
||||
## Music
|
||||
* Jordach for the jukebox music compilation from Big Freaking Dig
|
||||
* Dark Reaven Music (https://soundcloud.com/dark-reaven-music) for the main menu theme (Calmed Cube) and Traitor (horizonchris96), which is licensed under https://creativecommons.org/licenses/by-sa/3.0/
|
||||
* Jester for helping to finely tune VoxeLibre (https://www.youtube.com/@Jester-8-bit). Songs: Hailing Forest, Gift, 0dd BL0ck, Flock of One (License CC BY-SA 4.0)
|
||||
* Exhale & Tim Unwin for some wonderful VoxeLibre tracks (https://www.youtube.com/channel/UClFo_JDWoG4NGrPQY0JPD_g). Songs: Valley of Ghosts, Lonely Blossom, Farmer (License CC BY-SA 4.0)
|
||||
* Jester for helping to finely tune MineClone2 (https://www.youtube.com/@Jester-8-bit). Songs: Hailing Forest, Gift, 0dd BL0ck, Flock of One (License CC BY-SA 4.0)
|
||||
* Exhale & Tim Unwin for some wonderful MineClone2 tracks (https://www.youtube.com/channel/UClFo_JDWoG4NGrPQY0JPD_g). Songs: Valley of Ghosts, Lonely Blossom, Farmer (License CC BY-SA 4.0)
|
||||
* Diminixed for 3 fantastic tracks and remastering and leveling volumes. Songs: Afternoon Lullaby (pianowtune02), Spooled (ambientwip02), Never Grow Up (License CC BY-SA 4.0)
|
||||
|
||||
## Original Mod Authors
|
||||
|
|
|
@ -25,7 +25,7 @@ The basic digging time groups determine by which tools a node can be dug.
|
|||
* `handy=1`: Breakable by hand and this node gives it useful drop when dug by hand. All nodes which are breakable by pickaxe, axe, shovel, sword or shears are also automatically breakable by hand, but not neccess
|
||||
* `creative_breakable=1`: Block is breakable by hand in creative mode. This group is implied if the node belongs to any other digging group
|
||||
|
||||
Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times work in Minecraft, as VoxeLibre is based on the same system.
|
||||
Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times work in Minecraft, as MineClone 2 is based on the same system.
|
||||
|
||||
### Groups for interactions
|
||||
|
||||
|
@ -117,7 +117,7 @@ These groups correspond to the Minecraft materials. They classify the block into
|
|||
* `material_glass=1`: Glass
|
||||
|
||||
Currently, these groups are used for the note block.
|
||||
Note that not all Minecraft materials are used so far. More Minecraft materials will likely only be added when they are needed for a concrete use case.
|
||||
Note that not all Minecraft materials are used so far. More Minecraft materials will lilely only be added when they are needed for a concrete use case.
|
||||
|
||||
### Declarative groups
|
||||
These groups are used mostly for informational purposes
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Survive, farm, build, explore, play with friends, and do much more. Inspired by a well-known block game, pushing beyond.
|
||||
Survive, farm, build, explore, play with friends, and do much more. Inspired by a well known block game, pushing beyond.
|
||||
|
||||
How to play:
|
||||
|
||||
|
@ -6,18 +6,16 @@ How to play:
|
|||
- Navigate to https://www.minetest.net/ to download the client.
|
||||
- Once installed, open and select the "Content" tab
|
||||
|
||||
#### Install VoxeLibre from ContentDB
|
||||
#### Install MineClone2 from ContentDB
|
||||
- Click "Browse Online Content" and filter by Games (select "Games" from the dropdown box)
|
||||
- Find "VoxeLibre" (should be first on the list or on the first page)
|
||||
- Click the [+] button next to VoxeLibre and wait for download to finish
|
||||
- Find "MineClone2" (should be first on the list or on the first page)
|
||||
- Click the [+] button next to MineClone2 and wait for download to finish
|
||||
- Click "Back to Main Menu"
|
||||
|
||||
#### Create new world and play
|
||||
- Click "Start Game" tab
|
||||
- At the bottom click the VoxeLibre icon (the stone & sandstone ball with the letters VL)
|
||||
- At the bottom click the MineClone2 icon (the 2 dirt with grass blocks)
|
||||
- Click "New", give your world a name
|
||||
- You can leave seed blank or put in a word of your choice
|
||||
- Pick a mapgen or leave the default (v7, valleys or carpathian mapgens are recommended)
|
||||
- Pick mapgen options on the right (enabling everything is recommended)
|
||||
- Select your new world
|
||||
- Click "Play Game" and enjoy!
|
14
LEGAL.md
|
@ -1,14 +1,14 @@
|
|||
# Legal information
|
||||
This is a game inspired by Minecraft with unique content.
|
||||
This is a fan game, not developed or endorsed by Mojang AB.
|
||||
|
||||
Copying is an act of love. Please copy and share! <3
|
||||
Here's the detailed legalese for those who need it:
|
||||
|
||||
## License of source code
|
||||
VoxeLibre (by Lizzy Fleckenstein, Wuzzy, davedevils and countless others)
|
||||
is inspired by Minecraft.
|
||||
MineClone 2 (by Lizzy Fleckenstein, Wuzzy, davedevils and countless others)
|
||||
is an imitation of Minecraft.
|
||||
|
||||
VoxeLibre is free software: you can redistribute it and/or modify
|
||||
MineClone 2 is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
@ -22,15 +22,15 @@ details.
|
|||
In the mods you might find in the read-me or license
|
||||
text files a different license. This counts as dual-licensing.
|
||||
You can choose which license applies to you: Either the
|
||||
license of VoxeLibre (GNU GPLv3) or the mod's license.
|
||||
license of MineClone 2 (GNU GPLv3) or the mod's license.
|
||||
|
||||
VoxeLibre is a direct continuation of the discontinued MineClone
|
||||
MineClone 2 is a direct continuation of the discontinued MineClone
|
||||
project by davedevils.
|
||||
|
||||
Mod credits:
|
||||
See `README.txt` or `README.md` in each mod directory for information about other authors.
|
||||
For mods that do not have such a file, the license is the source code license
|
||||
of VoxeLibre and the author is Wuzzy.
|
||||
of MineClone 2 and the author is Wuzzy.
|
||||
|
||||
## License of media (textures and sounds)
|
||||
No non-free licenses are used anywhere.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Models in Minetest/VoxeLibre
|
||||
# Models in Minetest/Mineclone2
|
||||
|
||||
Models are an important part of all entities & unique nodes in VoxeLibre. They provide a 3 dimensional map of an object for which textures are then applied to. This document is for modders, it quickly highlights some important information for the software needed to open models in VoxeLibre.
|
||||
Models are an important part of all entities & unique nodes in Mineclone2. They provide a 3 dimensional map of an object for which textures are then applied to. This document is for modders, it quickly highlights some important information for the software needed to open models in Mineclone2.
|
||||
|
||||
## Minetest Wiki
|
||||
|
||||
For more detailed information on actually using blender to create and modify models for Minetest/VoxeLibre, please visit the Minetest wiki's page on using Blender [Here](https://wiki.minetest.net/Using_Blender)
|
||||
For more detailed information on actually using blender to create and modify models for Minetest/Mineclone2, please visit the Minetest wiki's page on using Blender [Here](https://wiki.minetest.net/Using_Blender)
|
||||
|
||||
## Recommended software
|
||||
|
||||
|
|
49
README.md
|
@ -1,6 +1,6 @@
|
|||
# VoxeLibre
|
||||
A game inspired by Minecraft for Minetest. Forked from MineClone by davedevils.
|
||||
Developed by many people, see CREDITS.md for a complete list.
|
||||
# MineClone2
|
||||
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
|
||||
Developed by many people. Not developed or endorsed by Mojang AB.
|
||||
|
||||
### Gameplay
|
||||
You start in a randomly-generated world made entirely of cubes. You can explore
|
||||
|
@ -64,44 +64,47 @@ Use the `/giveme` chat command to obtain them. See the in-game help for
|
|||
an explanation.
|
||||
|
||||
## Installation
|
||||
To run the game with the best performance and support, we recommend the latest
|
||||
stable version of [Minetest](http://minetest.net), be we always make an effort
|
||||
to support one version behind the latest stable version. In some cases, older
|
||||
versions might still be good enough but you would be missing out on important
|
||||
Minetest features that enable important features for our game.
|
||||
This game requires [Minetest](http://minetest.net) to run (version 5.4.1 or
|
||||
later). So you need to install Minetest first. Only stable versions of Minetest
|
||||
are officially supported.
|
||||
There is no support for running MineClone2 in development versions of Minetest.
|
||||
|
||||
There is no support for running VoxeLibre in development versions of Minetest.
|
||||
|
||||
To install VoxeLibre (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.
|
||||
|
||||
## Useful links
|
||||
The VoxeLibre repository is hosted at Mesehub. To contribute or report issues, head there.
|
||||
The MineClone2 repository is hosted at Mesehub. To contribute or report issues, head there.
|
||||
|
||||
* Mesehub: <https://git.minetest.land/VoxeLibre/VoxeLibre>
|
||||
* Mesehub: <https://git.minetest.land/MineClone2/MineClone2>
|
||||
* Discord: <https://discord.gg/xE4z8EEpDC>
|
||||
* YouTube: <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
|
||||
* ContentDB: <https://content.minetest.net/packages/wuzzy/mineclone2/>
|
||||
* OpenCollective: <https://opencollective.com/mineclone2>
|
||||
* Mastodon: <https://fosstodon.org/@VoxeLibre>
|
||||
* Lemmy: <https://lemm.ee/c/voxelibre>
|
||||
* Matrix space: <https://app.element.io/#/room/#voxelibre:matrix.org>
|
||||
* Mastodon: <https://fosstodon.org/@MineClone2>
|
||||
* Lemmy: <https://lemmy.world/c/mineclone2>
|
||||
* Matrix space: <https://app.element.io/#/room/#mcl2:matrix.org>
|
||||
* Minetest forums: <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* Reddit: <https://www.reddit.com/r/VoxeLibre/>
|
||||
* Reddit: <https://www.reddit.com/r/MineClone2/>
|
||||
* IRC (barely used): <https://web.libera.chat/#mineclone2>
|
||||
|
||||
## Target
|
||||
- Create a stable, peformant, moddable, free/libre game inspired by Minecraft
|
||||
using the Minetest engine, usable in both singleplayer and multiplayer.
|
||||
- Currently, a lot of features are already implemented.
|
||||
Polishing existing features is always welcome.
|
||||
- Create a stable, moddable, free/libre game based on Minecraft
|
||||
on the Minetest engine with polished features, usable in both
|
||||
singleplayer and multiplayer. Currently, a lot of **Minecraft Java
|
||||
Edition** features are already implemented and polishing existing
|
||||
features are prioritized over new feature requests.
|
||||
- Implement features targetting
|
||||
**Current Minecraft versions + OptiFine** (OptiFine only as far as supported
|
||||
by the Minetest Engine).
|
||||
- Create a performant experience that will run relatively
|
||||
well on really low spec computers.
|
||||
|
||||
## Completion status
|
||||
This game is currently in **beta** stage.
|
||||
It is playable, but not yet feature-complete.
|
||||
Backwards-compability is not entirely guaranteed, updating your world might cause small bugs.
|
||||
If you want to use the development version of VoxeLibre in production, the master branch is usually relatively stable.
|
||||
If you want to use the development version of MineClone2 in production, the master branch is usually relatively stable.
|
||||
|
||||
The following main features are available:
|
||||
|
||||
|
@ -184,7 +187,7 @@ Technical differences from Minecraft:
|
|||
* Different engine (Minetest)
|
||||
* Different easter eggs
|
||||
|
||||
… and finally, VoxeLibre is free software (“free” as in “freedom”)!
|
||||
… and finally, MineClone2 is free software (“free” as in “freedom”)!
|
||||
|
||||
## Other readme files
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# VoxeLibre
|
||||
Un jeu inspiré de Minecraft pour Minetest. Forké depuis Mineclone par davedevils.
|
||||
Développé par de nombreuses personnes, voir CREDITS.md pour une liste complète.
|
||||
# MineClone2
|
||||
Un jeu non-officiel similaire à Minecraft pour Minetest. Forké depuis Mineclone par davedevils. Développé par de nombreuses personnes. Pas développé ni supporté par Mojang AB.
|
||||
|
||||
### Gameplay
|
||||
Vous atterissez dans un monde fait entièrement de cubes et généré aléatoirement. Vous pouvez explorer le monde, miner et construire presque n'importe quel bloc pour créer de nouvelles structures. Vous pouvez choisir de jouer en "mode survie" dans lequel vous devez combattre des monstres et la faim et progresser lentement dans différents aspects du jeu, comme l'extraction de minerai, l'agriculture, la construction de machines et ainsi de suite. Ou alors vous pouvez jouer en "mode créatif" où vous pouvez construire à peu près n'importe quoi instantanément.
|
||||
|
@ -22,7 +21,7 @@ Vous atterissez dans un monde fait entièrement de cubes et généré aléatoire
|
|||
### Commencer
|
||||
* **Frappez un arbre** jusqu'à ce qu'il casse et donne du bois
|
||||
* Placez le **bois dans la grille 2x2** (la "grille de fabrication" de votre menu d'inventaire) et fabriquez 4 planches de bois
|
||||
* Placez les 4 planches de bois dans la grille 2x2 et **fabriquez un établi**
|
||||
* Placer les 4 planches de bois dans la grille 2x2 et **fabriquez un établi**
|
||||
* **Faites un clic droit sur l'établi** (icone livre) pour apprendre toutes les recettes possibles
|
||||
* **Fabriquez une pioche de bois** pour miner la pierre
|
||||
* Différents outils minent différentes sortes de blocs. Essayez-les !
|
||||
|
@ -31,10 +30,10 @@ Vous atterissez dans un monde fait entièrement de cubes et généré aléatoire
|
|||
### Agriculture
|
||||
* Trouvez des graines
|
||||
* Fabriquez une houe
|
||||
* Faites un clic droit sur la terre ou un bloc similaire avec la houe pour créer des terres agricoles
|
||||
* Placez des graines sur des terres agricoles et regardez les pousser
|
||||
* Faites un clic droit sur la terre ou des blocs similaires avec la houe pour créer des terres agricoles
|
||||
* Placer des graines sur des terres agricoles et regardez les pousser
|
||||
* Récoltez les plantes une fois matûres
|
||||
* Les terres agricoles proches de l'eau deviennent humides et accélèrent la croissance
|
||||
* Les terres agricoles proche de l'eau deviennent humides et accélèrent la croissance
|
||||
|
||||
### Four
|
||||
* Fabriquez un four
|
||||
|
@ -55,35 +54,33 @@ Utilisez la commande de chat `/giveme` pour les obtenir. Voir l'aide interne au
|
|||
|
||||
## Installation
|
||||
Ce jeu nécessite [Minetest](http://minetest.net) pour fonctionner (version 5.4.1 ou plus). Vous devez donc installer Minetest d'abord. Seules les versions stables de Minetest sont officielement supportées.
|
||||
Il n'y a pas de support de VoxeLibre dans les versions développement de Minetest.
|
||||
Il n'y a pas de support de MineClone2 dans les versions développement de Minetest.
|
||||
|
||||
Pour installer VoxeLibre (si ce n'est pas déjà fait), déplacez ce dossier dans le dossier “games” de Minetest. Consultez l'aide de Minetest pour en apprendre plus.
|
||||
Pour installer MineClone2 (si ce n'est pas déjà fait), déplacez ce dossier dans le dossier “games” de Minetest. Consultez l'aide de Minetest pour en apprendre plus.
|
||||
|
||||
## Liens utiles
|
||||
Le dépôt de VoxeLibre est hébergé sur Mesehub. Pour contribuer ou signaler des problèmes, allez là-bas.
|
||||
Le dépôt de MineClone2 est hébergé sur Mesehub. Pour contribuer ou signaler des problèmes, allez là-bas.
|
||||
|
||||
* Mesehub : <https://git.minetest.land/VoxeLibre/VoxeLibre>
|
||||
* 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/>
|
||||
* Forums Minetest : <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* ContentDB : <https://content.minetest.net/packages/wuzzy/mineclone2/>
|
||||
* OpenCollective : <https://opencollective.com/mineclone2>
|
||||
* Mastodon : <https://fosstodon.org/@VoxeLibre>
|
||||
* Lemmy : <https://lemm.ee/c/voxelibre>
|
||||
* Espace Matrix : <https://app.element.io/#/room/#voxelibre:matrix.org>
|
||||
* Forums Minetest : <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* Reddit : <https://www.reddit.com/r/VoxeLibre/>
|
||||
* IRC (peu utilisé) : <https://web.libera.chat/#mineclone2>
|
||||
|
||||
## Objectif
|
||||
* Créer un jeu stable, performant, moddable et libre inspiré de Minecraft en utilisant le moteur de jeu Minetest, utilisable à la fois en mode solo et multijoueur.
|
||||
* Actuellement, un grand nombre de fonctionnalités sont déjà implémentées.
|
||||
L'amélioration des fonctionnalités existantes est toujours la bienvenue.
|
||||
* Essentiellement, créer un clone de Minecraft stable, moddable, libre et gratuit basé sur le moteur de jeu Minetest avec des fonctionnalités abouties, utilisable à la fois en mode solo et multijoueur. Actuellement, beaucoup des fonctionnalités de **Minecraft Java Edition** sont déjà implémentées et leur amélioration est prioritaire sur les nouvelles demandes.
|
||||
* Avec une priorité moindre, implémenter les fonctionnalités des versions **Minecraft + OptiFine** (OptiFine autant que supporté par le moteur Minetest). Cela signifie que les fonctionnalités présentes dans les versions listées sont priorisées.
|
||||
* Dans l'idéal, créer une expérience performante qui tourne bien sur des ordinateurs à basse performance. Malheureusement, en raison des mécanismes de Minecraft et des limitations du moteur Minetest ainsi que de la petite taille de la communauté de joueurs sur des ordinateurs à basses performances, les optimisations sont difficiles à explorer.
|
||||
|
||||
## Statut de complétion
|
||||
Ce jeu est actuellement au stade **beta**.
|
||||
Il est jouable mais incomplet en fonctionnalités.
|
||||
La rétro-compatibilité n'est pas entièrement garantie, mettre votre monde à jour peut causer de petits bugs.
|
||||
Si vous voulez utiliser la version de développement de VoxeLibre en production, la branche master est habituellement relativement stable.
|
||||
Si vous voulez utiliser la version de développement de MineClone2 en production, la branche master est habituellement relativement stable. Les branches de test fusionnent souvent des pull requests expérimentales et doivent être considérées comme moins stable.
|
||||
|
||||
Les principales fonctionnalités suivantes sont disponibles :
|
||||
|
||||
|
@ -165,7 +162,7 @@ Différences techniques avec Minecraft :
|
|||
* Un moteur de jeu différent (Minetest)
|
||||
* Des bonus cachés différents
|
||||
|
||||
...et enfin VoxeLibre est un logiciel libre !
|
||||
...et enfin MineClone2 est un logiciel libre !
|
||||
|
||||
## Autres fichiers readme
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# VoxeLibre
|
||||
# MineClone2
|
||||
Неофициальная игра в стиле Minecraft для Minetest. Форк MineClone от davedevils.
|
||||
Разработана многими людьми. Не разработана и не одобрена Mojang AB.
|
||||
|
||||
|
@ -67,13 +67,13 @@
|
|||
## Установка
|
||||
Эта игра требует [Minetest](http://minetest.net) для запуска (версия 5.4.1 или
|
||||
выше). Вам нужно сперва установить Minetest. Только стабильные версии поддерживаются
|
||||
официально. Не поддерживается запуск VoxeLibre на разрабатываемых версиях Minetest.
|
||||
официально. Не поддерживается запуск MineClone2 на разрабатываемых версиях Minetest.
|
||||
|
||||
Чтобы установить VoxeLibre (если вы этого еще не сделали), переместите эту папку в
|
||||
Чтобы установить MineClone2 (если вы этого еще не сделали), переместите эту папку в
|
||||
“games” в папке данных Minetest. Смотрите справку Minetest, чтобы узнать больше.
|
||||
|
||||
## Полезные ссылки
|
||||
Репозиторий VoxeLibre хранится на Mesehub. Зайдите туда, чтобы оставить запрос или
|
||||
Репозиторий MineClone2 хранится на Mesehub. Зайдите туда, чтобы оставить запрос или
|
||||
поучаствовать в разработке.
|
||||
|
||||
* Mesehub: <https://git.minetest.land/MineClone2/MineClone2>
|
||||
|
@ -102,7 +102,7 @@ Edition** уже реализовано и доработка имеющегос
|
|||
Игра сейчас на стадии **бета**. Она играбельна, но еще не имеет всех возможностей.
|
||||
Обратная совместимость целиком не гарантируется, обновление вашего мира может повлечь
|
||||
за собой небольшие ошибки. Если вы хотите использовать разрабатываемую версию
|
||||
VoxeLibre, то ветка master обычно относительно стабильна.
|
||||
Mineclone2, то ветка master обычно относительно стабильна.
|
||||
|
||||
Следущие возможности уже доступны:
|
||||
|
||||
|
@ -182,7 +182,7 @@ VoxeLibre, то ветка master обычно относительно стаб
|
|||
* Другой движок (Minetest)
|
||||
* Другие пасхалки
|
||||
|
||||
… и наконец, VoxeLibre это свободное программное обеспечение!
|
||||
… и наконец, MineClone2 это свободное программное обеспечение!
|
||||
|
||||
## Другие readme файлы
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
This file is severely out of date. If you can help updating this translation, please reach out to us (contact in README.md - the English version).
|
||||
|
||||
# VoxeLibre
|
||||
# MineClone 2
|
||||
一個非官方的Minetest遊戲,遊玩方式和Minecraft類似。由davedevils從MineClone分拆。
|
||||
由許多人開發。並非由Mojang Studios開發。<!-- "Mojang AB"'s Name changed at 2020/05, main README should change too -->
|
||||
|
||||
|
@ -85,11 +83,11 @@ Minetest to learn more.
|
|||
The main goal of **MineClone 2** is to be a clone of Minecraft and to be released as free software.
|
||||
|
||||
* **開發目標:我的世界, Java版, 版本 1.12**
|
||||
* VoxeLibre還包括Minetest支持的Optifine功能。
|
||||
* MineClone2還包括Minetest支持的Optifine功能。
|
||||
* 後期Minecraft版本的功能可能會偷偷加入,但它們的優先級較低。
|
||||
* 總的來說,Minecraft的目標是在Minetest目前允許的情況下進行克隆。
|
||||
* 克隆Minecraft是最優先的。
|
||||
* VoxeLibre將使用不同的圖形和聲音,但風格相似。
|
||||
* MineClone2將使用不同的圖形和聲音,但風格相似。
|
||||
* 克隆界面沒有優先權。只會被粗略地模仿。
|
||||
* 在Minetest中發現的局限性將在開發過程中被記錄和報告。
|
||||
|
||||
|
@ -175,7 +173,7 @@ The main goal of **MineClone 2** is to be a clone of Minecraft and to be release
|
|||
* 不同的聲音(各種來源)
|
||||
* 不同的引擎(Minetest)
|
||||
|
||||
...最後,VoxeLibre是自由軟件!
|
||||
...最後,MineClone2是自由軟件!
|
||||
|
||||
## 錯誤報告
|
||||
請在此處報告所有錯誤和缺少的功能:
|
||||
|
@ -192,7 +190,7 @@ The main goal of **MineClone 2** is to be a clone of Minecraft and to be release
|
|||
|
||||
* `LICENSE.txt`:GPLv3許可文本
|
||||
* `CONTRIBUTING.md`: 為那些想參與貢獻的人提供資訊
|
||||
* `MISSING_ENGINE_FEATURES.md`: VoxeLibre需要改进,Minetest中缺失的功能列表。
|
||||
* `MISSING_ENGINE_FEATURES.md`: MineClone2需要改进,Minetest中缺失的功能列表。
|
||||
* `API.md`: 關於MineClone2的API
|
||||
|
||||
## 參與者
|
||||
|
@ -237,7 +235,7 @@ The main goal of **MineClone 2** is to be a clone of Minecraft and to be release
|
|||
* [kingoscargames](https://github.com/kingoscargames):現有材質的各種編輯和添加
|
||||
* [leorockway](https://github.com/leorockway):怪物紋理的一些編輯
|
||||
* [xMrVizzy](https://minecraft.curseforge.com/members/xMrVizzy):釉陶(材質以後會被替換)
|
||||
* yutyo <tanakinci2002@gmail.com>:VoxeLibre標志
|
||||
* yutyo <tanakinci2002@gmail.com>:MineClone2標志
|
||||
* 其他:GUI圖片
|
||||
|
||||
### 翻譯
|
||||
|
@ -256,7 +254,7 @@ The main goal of **MineClone 2** is to be a clone of Minecraft and to be release
|
|||
|
||||
### 特殊感謝
|
||||
|
||||
* Wuzzy,感謝他啟動和維護VoxeLibre多年。
|
||||
* Wuzzy,感謝他啟動和維護MineClone2多年。
|
||||
* celeron55,創建Minetest。
|
||||
* Minetest的社區提供了大量的mods選擇,其中一些最終被納入MineClone 2。
|
||||
* Jordach,為《Big Freaking Dig》的唱片機音樂合輯而來
|
||||
|
|
182
RELEASE.md
|
@ -1,167 +1,89 @@
|
|||
## Standard Release
|
||||
### Standard Release
|
||||
|
||||
### Before releasing
|
||||
# File to document release steps with a view to evolving into a script
|
||||
|
||||
Make sure all PRs in the release milestone are merged and you are working on a clean branch based on the master branch, up-to-date with the one on the repo.
|
||||
# Update CREDITS.md
|
||||
# Update version in game.conf
|
||||
|
||||
### Release process
|
||||
|
||||
1. Update CREDITS.md
|
||||
2. Update version in game.conf
|
||||
3. Run the script:
|
||||
```
|
||||
lua tools/generate_ingame_credits.lua
|
||||
```
|
||||
4. Make a commit for the above:
|
||||
```
|
||||
|
||||
git add CREDITS.md
|
||||
git add mods/HUD/mcl_credits/people.lua
|
||||
git add game.conf
|
||||
git commit -m "Updated release credits and set version for v0.87"
|
||||
|
||||
#git add RELEASE.md
|
||||
|
||||
git commit -m "Pre-release update credits and set version 0.83.0"
|
||||
|
||||
git tag 0.83.0
|
||||
|
||||
git push origin 0.83.0
|
||||
```
|
||||
5. Add release notes to the `releasenotes` folder, named like
|
||||
```
|
||||
0_87-the_prismatic_release.md
|
||||
```
|
||||
6. Make a commit for the release notes:
|
||||
```
|
||||
git add releasenotes/0_87-the_prismatic_release.md
|
||||
git commit -m "Add release notes for v0.87"
|
||||
```
|
||||
7. **Tag and push to the tag:**
|
||||
```
|
||||
git tag 0.87.0
|
||||
git push origin 0.87.0
|
||||
```
|
||||
8. Update version in game.conf to the next version with -SNAPSHOT suffix:
|
||||
```
|
||||
git commit -m "Post-release set version 0.88.0-SNAPSHOT"
|
||||
```
|
||||
9. Push the above to a new branch, and make the release PR. Merge to finalize release process.
|
||||
|
||||
### Release via ContentDB
|
||||
# Update version in game.conf to the next version with -SNAPSHOT suffix
|
||||
|
||||
1. Go to VoxeLibre page (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
2. Click [+Release] button
|
||||
3. Enter the release tag number in the title and Git reference box. For example (without quotes): "0.87.0"
|
||||
4. In the minimum minetest version, put the oldest supported version (as of 19/05/2024 it is 5.6), leave the Maximum minetest version blank
|
||||
5. Click save. Release is now live.
|
||||
`git commit -m "Post-release set version 0.84.0-SNAPSHOT"`
|
||||
|
||||
### After releasing
|
||||
### Hotfix Release
|
||||
|
||||
...inform people.
|
||||
##### Prepare release branch
|
||||
|
||||
* Open a release meta issue on the tracker, unpin and close the issue for the previous release, pin the new one.
|
||||
* Upload video to YouTube.
|
||||
* Add a comment to the forum post with the release number and change log. Maintainer will update the main post with code link.
|
||||
* Add a Discord announcement post and @everyone with link to the release issue, release notes and other content, like video and forum post.
|
||||
* Add a Matrix announcement post and @room with links like above.
|
||||
* Share the news on reddit + Lemmy. Good subs to share with:
|
||||
* r/linux_gaming
|
||||
* r/opensourcegames
|
||||
* r/opensource
|
||||
* r/freesoftware
|
||||
* r/linuxmasterrace
|
||||
* r/VoxeLibre
|
||||
* r/MineClone2 (*for now*)
|
||||
When hotfixing, you should never release new features. Any new code increases risk of new bugs which has additional testing/release concerns.
|
||||
To mitigate this, you just release the last release, and the relevant bug fix. For this, we do the following:
|
||||
|
||||
* Create release branch from the last release tag, push it:
|
||||
|
||||
## Hotfix Release
|
||||
|
||||
### Before releasing
|
||||
|
||||
First, determine if the current state of the master branch is fine for the Hotfix.
|
||||
In general, Hotfixes shouldn't contain new features to minimize the risk of regressions.
|
||||
|
||||
* If it hasn't been long since the release, and the only PRs merged so far are bugfixes and/or documentation changes,
|
||||
it is certainly fine to use it as a base for the release.
|
||||
* If there are some features merged, but they are aimed at fixing/alleviating important issues with the last released version, it may still be fine.
|
||||
* If there are some simple QoL features merged that are irrelevant to the last release, it may still be fine to use it as a base for the Hotfix.
|
||||
* If there are major features or large overhauls merged, it *most probably* is **not** fine to use as a base for the Hotfix.
|
||||
|
||||
If you decided that the current state of the master branch can be used as the Hotfix version, make sure that all the PRs merged since the last release
|
||||
are in the Hotfix milestone and you are working on a clean branch based on the master branch, up-to-date with the one on the repo.
|
||||
In this case, **skip** the following section.
|
||||
|
||||
### Prepare release branch
|
||||
|
||||
If you decided that the current state of the master branch shouldn't be used as the Hotfix version, you must prepare a release branch.
|
||||
|
||||
1. Create release branch from the last release tag, push it:
|
||||
```
|
||||
git checkout -b release/0.82.1 0.82.0
|
||||
|
||||
git push origin release/0.82.1
|
||||
```
|
||||
2. Cherry-pick the relevant commits from the master branch, or merge them from other (PR) branches.
|
||||
3. Make sure your local copy of the branch contains all the relevant changes, **do not rebase**.
|
||||
|
||||
### Release process
|
||||
##### Prepare feature branch and fix
|
||||
|
||||
1. Update CREDITS.md if it is needed
|
||||
2. Update version in game.conf
|
||||
3. If you've changed CREDITS.md, run the script:
|
||||
```
|
||||
lua tools/generate_ingame_credits.lua
|
||||
```
|
||||
4. Make a commit for the above:
|
||||
```
|
||||
git add game.conf
|
||||
git commit -m "Set version for hotfix v0.87.1"
|
||||
```
|
||||
or, if credits got updated:
|
||||
```
|
||||
git add CREDITS.md
|
||||
git add mods/HUD/mcl_credits/people.lua
|
||||
git add game.conf
|
||||
git commit -m "Updated release credits and set version for hotfix v0.87.1"
|
||||
```
|
||||
5. Add a section in the last releasnotes, like this:
|
||||
```
|
||||
## 0.87.1 hotfix
|
||||
```
|
||||
and describe the changes there
|
||||
* Create feature branch from that release branch (can review it to check only fix is there, nothing else, and use to also merge into master separately)
|
||||
|
||||
`git checkout -b hotfix_bug_1_branch`
|
||||
|
||||
* Fix crash/serious bug and commit
|
||||
* Push branch and create pr to the release and also the master branch (Do not rebase, to reduce merge conflict risk. Do not delete after first merge or it needs to be repushed)
|
||||
|
||||
##### Update version and tag the release
|
||||
|
||||
* After all fixes are in release branch, pull it locally (best to avoid a merge conflict as feature branch will need to be merged into master also, which already changed version):
|
||||
|
||||
* Update version in game.conf to hotfix version and commit it. Example: version=0.82.1
|
||||
|
||||
* Tag it, push tag and branch:
|
||||
|
||||
6. Make a commit for the releasenotes changes:
|
||||
```
|
||||
git add releasenotes/0_87-the_prismatic_release.md
|
||||
git commit -m "Update release notes for hotfix v0.87.1"
|
||||
```
|
||||
7. **Tag and push to the tag:**
|
||||
```
|
||||
git tag 0.87.1
|
||||
git push origin 0.87.1
|
||||
```
|
||||
8. If you are skipping some changes from the master branch (and thus are using a prepared master branch from the previous section),
|
||||
push to the remote and skip the next two steps:
|
||||
```
|
||||
git tag 0.82.1
|
||||
|
||||
git push origin 0.82.1
|
||||
|
||||
git push origin release/0.82.1
|
||||
```
|
||||
9. If you're releasing master branch, update version in game.conf to the next version with -SNAPSHOT suffix:
|
||||
```
|
||||
git commit -m "Post-hotfix reset version 0.88.0-SNAPSHOT"
|
||||
```
|
||||
10. If you're releasing master branch, push the above to a new branch, and make the release PR. Merge to finalize release process.
|
||||
|
||||
Note: If you have to do more than 1 hotfix release, can do it on the same release branch.
|
||||
|
||||
### Release via ContentDB
|
||||
|
||||
1. Go to VoxeLibre page (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
2. Click [+Release] button
|
||||
3. Enter the release tag number in the title and Git reference box. For example (without quotes): "0.87.1"
|
||||
4. In the minimum minetest version, put the oldest supported version (as of 19/05/2024 it is 5.6), leave the Maximum minetest version blank
|
||||
5. Click save. Hotfix is now live.
|
||||
* Go to MineClone2 page (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Click +Release
|
||||
* Enter the release tag number in the title and Git reference box. For example (without quotes): "0.82.1"
|
||||
* In the minimum minetest version, put the oldest supported version (as of 14/02/2023 it is 5.5), leave the Maximum minetest version blank
|
||||
* Click save. Release is now live.
|
||||
|
||||
### After releasing
|
||||
##### Inform people
|
||||
|
||||
...inform people.
|
||||
|
||||
* Add a comment to the forum post with the release number and change log. Maintainer will update the main post with code link.
|
||||
* Add a Discord announcement post and @everyone with link to the release issue and release notes, and describe briefly what the hotfix does.
|
||||
* Add a Matrix announcement post and @room with content like above.
|
||||
* Upload video to YouTube
|
||||
* Add a comment to the forum post with the release number and change log. Maintainer will update main post with code link.
|
||||
* Add a Discord announcement post and @everyone with link to video, forum post and release notes.
|
||||
* Share the news on reddit + Lemmy. Good subs to share with:
|
||||
* r/linux_gaming
|
||||
* r/opensourcegames
|
||||
* r/opensource
|
||||
* r/freesoftware
|
||||
* r/linuxmasterrace
|
||||
* r/VoxeLibre
|
||||
* r/MineClone2 (*for now*)
|
||||
* r/MineClone2
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Making Textures In VoxeLibre
|
||||
# Making Textures In Mineclone2
|
||||
|
||||
Textures are a crucial asset for all items, nodes, and models in VoxeLibre. This document is for artist who would like to make and modify textures for VoxeLibre. While no means comprehensive, this document contains the basic important information for beginners to get started with texture curation and optimization.
|
||||
Textures are a crucial asset for all items, nodes, and models in mineclone2. This document is for artist who would like to make and modify textures for mineclone2. While no means comprehensive, this document contains the basic important information for beginners to get started with texture curation and optimization.
|
||||
|
||||
## Minetest Wiki
|
||||
For more detailed information on creating and modifing texture packs for Minetest/VoxeLibre, please visit the Minetest wiki's page on creating a texture pack. Click [here](https://wiki.minetest.net/Creating_texture_packs) to view the wiki page on creating texture packs.
|
||||
For more detailed information on creating and modifing texture packs for Minetest/Mineclone2, please visit the Minetest wiki's page on creating a texture pack. Click [here](https://wiki.minetest.net/Creating_texture_packs) to view the wiki page on creating texture packs.
|
||||
|
||||
## GIMP Tutorials Pixel Art Guide
|
||||
GIMP Tutorials has an excellent guide to making pixel art in GIMP. If you would like further clarification as well as screenshots for what we are about to cover, it is an excellent resource to turn to. Click [here](https://thegimptutorials.com/how-to-make-pixel-art/) to view the guide
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
title = VoxeLibre
|
||||
title = MineClone 2
|
||||
description = A survival sandbox game. Survive, gather, hunt, build, explore, and do much more.
|
||||
disallowed_mapgens = v6
|
||||
version=0.88.0-SNAPSHOT
|
||||
version=0.87.0-SNAPSHOT
|
||||
|
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 82 KiB |
BIN
menu/header.png
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 82 KiB |
BIN
menu/icon.png
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 51 KiB |
|
@ -16,7 +16,7 @@ information.
|
|||
How the mod is used
|
||||
===================
|
||||
|
||||
In VoxeLibre, all diggable nodes have the hardness set in the custom field
|
||||
In MineClone 2, all diggable nodes have the hardness set in the custom field
|
||||
"_mcl_hardness" (0 by default). These values are used together with digging
|
||||
groups by this mod to create the correct digging times for nodes. Digging
|
||||
groups are registered using the following code:
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = _mcl_autogroup
|
||||
author = ryvnf
|
||||
description = VoxeLibre core mod which automatically adds groups to all items. Very important for digging times.
|
||||
description = MineClone 2 core mod which automatically adds groups to all items. Very important for digging times.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
-- Overrides the builtin minetest.check_single_for_falling.
|
||||
-- We need to do this in order to handle nodes in VoxeLibre specific groups
|
||||
-- We need to do this in order to handle nodes in mineclone specific groups
|
||||
-- "supported_node" and "attached_node_facedir".
|
||||
--
|
||||
-- Nodes in group "supported_node" can be placed on any node that does not
|
||||
|
@ -94,16 +94,5 @@ function minetest.check_single_for_falling(pos)
|
|||
end
|
||||
end
|
||||
|
||||
if get_item_group(node.name, "supported_node_facedir") ~= 0 then
|
||||
local dir = facedir_to_dir(node.param2)
|
||||
if dir then
|
||||
local def = registered_nodes[get_node(vector.add(pos, dir)).name]
|
||||
if def and def.drawtype == "airlike" then
|
||||
drop_attached_node(pos)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_autogroup
|
||||
author = ryvnf
|
||||
description = VoxeLibre core mod which automatically adds groups to all items. Very important for digging times.
|
||||
description = MineClone 2 core mod which automatically adds groups to all items. Very important for digging times.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
--[[
|
||||
Explosion API mod for Minetest (adapted to VoxeLibre)
|
||||
Explosion API mod for Minetest (adapted to MineClone 2)
|
||||
|
||||
This mod is based on the Minetest explosion API mod, but has been changed
|
||||
to have the same explosion mechanics as Minecraft and work with VoxeLibre
|
||||
to have the same explosion mechanics as Minecraft and work with MineClone.
|
||||
The computation-intensive parts of the mod has been optimized to allow for
|
||||
larger explosions and faster world updating.
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
-- Some global variables (don't overwrite them!)
|
||||
mcl_vars = {}
|
||||
|
||||
minetest.log("action", "World seed = " .. minetest.get_mapgen_setting("seed"))
|
||||
|
||||
mcl_vars.redstone_tick = 0.1
|
||||
|
||||
-- GUI / inventory menu settings
|
||||
|
@ -39,7 +37,7 @@ mcl_vars.mapgen_limit = math.max(1, tonumber(minetest.get_mapgen_setting("mapgen
|
|||
mcl_vars.MAX_MAP_GENERATION_LIMIT = math.max(1, minetest.MAX_MAP_GENERATION_LIMIT or 31000)
|
||||
|
||||
-- Central chunk is offset from 0,0,0 coordinates by 32 nodes (2 blocks)
|
||||
-- See more in https://git.minetest.land/VoxeLibre/VoxeLibre/wiki/World-structure%3A-positions%2C-boundaries%2C-blocks%2C-chunks%2C-dimensions%2C-barriers-and-the-void
|
||||
-- See more in https://git.minetest.land/MineClone2/MineClone2/wiki/World-structure%3A-positions%2C-boundaries%2C-blocks%2C-chunks%2C-dimensions%2C-barriers-and-the-void
|
||||
local central_chunk_offset = -math.floor(mcl_vars.chunksize / 2)
|
||||
|
||||
mcl_vars.central_chunk_offset_in_nodes = central_chunk_offset * mcl_vars.MAP_BLOCKSIZE
|
||||
|
@ -185,7 +183,7 @@ mcl_vars.mg_end_exit_portal_pos = vector.new(0, mcl_vars.mg_end_min + 71, 0)
|
|||
mcl_vars.mg_realm_barrier_overworld_end_max = mcl_vars.mg_end_max
|
||||
mcl_vars.mg_realm_barrier_overworld_end_min = mcl_vars.mg_end_max - 11
|
||||
|
||||
-- Use VoxeLibre-style dungeons
|
||||
-- Use MineClone 2-style dungeons
|
||||
mcl_vars.mg_dungeons = true
|
||||
|
||||
-- Set default stack sizes
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_init
|
||||
author = Wuzzy
|
||||
description = Initialization mod of VoxeLibre. Defines some common shared variables and sets up initial default settings which have to be set at the beginning.
|
||||
description = Initialization mod of MineClone 2. Defines some common shared variables and sets up initial default settings which have to be set at the beginning.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Oxidization API for VoxeLibre
|
||||
# Oxidization API for MineClone 2
|
||||
This mods adds the oxidization api, so that modders can easily use the same features that copper uses.
|
||||
|
||||
## API
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name = mcl_oxidation
|
||||
title = Oxidation API for VoxeLibre
|
||||
title = Oxidation API for MineClone 2
|
||||
author = PrairieWind, N011, Michael
|
||||
description = API to allow oxidizing different nodes.
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
name = mcl_particles
|
||||
author = Wuzzy
|
||||
description = Contains particle images of MineClone 2. No code.
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = mcl_sounds
|
||||
author = Wuzzy
|
||||
description = This mod contains the core sounds of VoxeLibre as well as helper function for mods to access them.
|
||||
description = This mod contains the core sounds of MineClone 2 as well as helper function for mods to access them.
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
mcl_util = {}
|
||||
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()).."/roman_numerals.lua")
|
||||
|
||||
-- Updates all values in t using values from to*.
|
||||
function table.update(t, ...)
|
||||
for _, to in ipairs {...} do
|
||||
|
@ -59,6 +57,15 @@ function mcl_util.mcl_log(message, module, bypass_default_logger)
|
|||
minetest.log(selected_module .. " " .. message)
|
||||
end
|
||||
end
|
||||
function mcl_util.make_mcl_logger(label, option)
|
||||
-- Return dummy function if debug option isn't set
|
||||
if not minetest.settings:get_bool(option,false) then return function() end, false end
|
||||
|
||||
local label_text = "["..tostring(label).."]"
|
||||
return function(message)
|
||||
mcl_util.mcl_log(message, label_text, true)
|
||||
end, true
|
||||
end
|
||||
|
||||
local player_timers = {}
|
||||
|
||||
|
@ -115,7 +122,7 @@ end
|
|||
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
|
||||
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
|
||||
-- debugging. See:
|
||||
-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392
|
||||
-- https://git.minetest.land/MineClone2/MineClone2/issues/1392
|
||||
function mcl_util.get_natural_light (pos, time)
|
||||
local status, retVal = pcall(minetest.get_natural_light, pos, time)
|
||||
if status then
|
||||
|
@ -345,6 +352,33 @@ function mcl_util.hopper_push(pos, dst_pos)
|
|||
return ok
|
||||
end
|
||||
|
||||
function mcl_util.hopper_pull_to_inventory(hop_inv, hop_list, src_pos, pos)
|
||||
-- TODO: merge together with hopper_pull after https://git.minetest.land/MineClone2/MineClone2/pulls/4190 is merged
|
||||
-- Get node pos' for item transfer
|
||||
local src = minetest.get_node(src_pos)
|
||||
if not minetest.registered_nodes[src.name] then return end
|
||||
local src_type = minetest.get_item_group(src.name, "container")
|
||||
if src_type ~= 2 then return end
|
||||
local src_def = minetest.registered_nodes[src.name]
|
||||
|
||||
local src_list = 'main'
|
||||
local src_inv, stack_id
|
||||
|
||||
if src_def._mcl_hoppers_on_try_pull then
|
||||
src_inv, src_list, stack_id = src_def._mcl_hoppers_on_try_pull(src_pos, pos, hop_inv, hop_list)
|
||||
else
|
||||
local src_meta = minetest.get_meta(src_pos)
|
||||
src_inv = src_meta:get_inventory()
|
||||
stack_id = mcl_util.select_stack(src_inv, src_list, hop_inv, hop_list)
|
||||
end
|
||||
|
||||
if stack_id ~= nil then
|
||||
local ok = mcl_util.move_item(src_inv, src_list, stack_id, hop_inv, hop_list)
|
||||
if src_def._mcl_hoppers_on_after_pull then
|
||||
src_def._mcl_hoppers_on_after_pull(src_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Try pulling from source inventory to hopper inventory
|
||||
---@param pos Vector
|
||||
---@param src_pos Vector
|
||||
|
@ -438,11 +472,10 @@ function mcl_util.generate_on_place_plant_function(condition)
|
|||
if not def_under or not def_above then
|
||||
return itemstack
|
||||
end
|
||||
if def_under.buildable_to and def_under.name ~= itemstack:get_name() then
|
||||
if def_under.buildable_to then
|
||||
place_pos = pointed_thing.under
|
||||
elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then
|
||||
elseif def_above.buildable_to then
|
||||
place_pos = pointed_thing.above
|
||||
pointed_thing.under = pointed_thing.above
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
|
@ -537,7 +570,6 @@ end
|
|||
function mcl_util.use_item_durability(itemstack, n)
|
||||
local uses = mcl_util.calculate_durability(itemstack)
|
||||
itemstack:add_wear(65535 / uses * n)
|
||||
tt.reload_itemstack_description(itemstack) -- update tooltip
|
||||
end
|
||||
|
||||
function mcl_util.deal_damage(target, damage, mcl_reason)
|
||||
|
@ -1103,3 +1135,89 @@ function mcl_util.is_it_christmas()
|
|||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function table_merge(base, overlay)
|
||||
for k,v in pairs(overlay) do
|
||||
if type(base[k]) == "table" and type(v) == "table" then
|
||||
table_merge(base[k], v)
|
||||
else
|
||||
base[k] = v
|
||||
end
|
||||
end
|
||||
return base
|
||||
end
|
||||
mcl_util.table_merge = table_merge
|
||||
|
||||
function mcl_util.table_keys(t)
|
||||
local keys = {}
|
||||
for k,_ in pairs(t) do
|
||||
keys[#keys + 1] = k
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
local uuid_to_aoid_cache = {}
|
||||
local function scan_active_objects()
|
||||
-- Update active object ids for all active objects
|
||||
for active_object_id,o in pairs(minetest.luaentities) do
|
||||
o._active_object_id = active_object_id
|
||||
if o._uuid then
|
||||
uuid_to_aoid_cache[o._uuid] = active_object_id
|
||||
end
|
||||
end
|
||||
end
|
||||
function mcl_util.get_active_object_id(obj)
|
||||
local le = obj:get_luaentity()
|
||||
|
||||
-- If the active object id in the lua entity is correct, return that
|
||||
if le._active_object_id and minetest.luaentities[le._active_object_id] == le then
|
||||
return le._active_object_id
|
||||
end
|
||||
|
||||
scan_active_objects()
|
||||
|
||||
return le._active_object_id
|
||||
end
|
||||
function mcl_util.get_active_object_id_from_uuid(uuid)
|
||||
return uuid_to_aoid_cache[uuid] or scan_active_objects() or uuid_to_aoid_cache[uuid]
|
||||
end
|
||||
function mcl_util.get_luaentity_from_uuid(uuid)
|
||||
return minetest.luaentities[ mcl_util.get_active_object_id_from_uuid(uuid) ]
|
||||
end
|
||||
function mcl_util.gen_uuid()
|
||||
-- Generate a random 128-bit ID that can be assumed to be unique
|
||||
-- To have a 1% chance of a collision, there would have to be 1.6x10^76 IDs generated
|
||||
-- https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
|
||||
local u = {}
|
||||
for i = 1,16 do
|
||||
u[#u + 1] = string.format("%02X",math.random(1,255))
|
||||
end
|
||||
return table.concat(u)
|
||||
end
|
||||
function mcl_util.assign_uuid(obj)
|
||||
assert(obj)
|
||||
|
||||
local le = obj:get_luaentity()
|
||||
if not le._uuid then
|
||||
le._uuid = mcl_util.gen_uuid()
|
||||
end
|
||||
|
||||
-- Update the cache with this new id
|
||||
aoid = mcl_util.get_active_object_id(obj)
|
||||
uuid_to_aoid_cache[le._uuid] = aoid
|
||||
|
||||
return le._uuid
|
||||
end
|
||||
function mcl_util.metadata_last_act(meta, name, delay)
|
||||
local last_act = meta:get_float(name)
|
||||
local now = minetest.get_us_time() * 1e-6
|
||||
if last_act > now + 0.5 then
|
||||
-- Last action was in the future, clock went backwards, so reset
|
||||
elseif last_act >= now - delay then
|
||||
return false
|
||||
end
|
||||
|
||||
meta:set_float(name, now)
|
||||
return true
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name = mcl_util
|
||||
author = Wuzzy
|
||||
description = Helper functions for VoxeLibre.
|
||||
description = Helper functions for MineClone 2.
|
||||
depends = mcl_init
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
local converter = {
|
||||
{1000, "M"},
|
||||
{900, "CM"},
|
||||
{500, "D"},
|
||||
{400, "CD"},
|
||||
{100, "C"},
|
||||
{90, "XC"},
|
||||
{50, "L"},
|
||||
{40, "XL"},
|
||||
{10, "X"},
|
||||
{9, "IX"},
|
||||
{5, "V"},
|
||||
{4, "IV"},
|
||||
{1, "I"}
|
||||
}
|
||||
|
||||
mcl_util.to_roman = function(number)
|
||||
local r = ""
|
||||
local a = number
|
||||
local i = 1
|
||||
while a > 0 do
|
||||
if a >= converter[i][1] then
|
||||
a = a - converter[i][1]
|
||||
r = r.. converter[i][2]
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
|
@ -31,7 +31,7 @@ This function return the Minecraft dimension of <pos> ("overworld", "nether" or
|
|||
* pos: position
|
||||
|
||||
## mcl_worlds.layer_to_y(layer, mc_dimension)
|
||||
Takes a Minecraft layer and a “dimension” name and returns the corresponding Y coordinate for VoxeLibre.
|
||||
Takes a Minecraft layer and a “dimension” name and returns the corresponding Y coordinate for MineClone 2.
|
||||
mc_dimension can be "overworld", "nether", "end" (default: "overworld").
|
||||
|
||||
* layer: int
|
||||
|
|
|
@ -58,7 +58,7 @@ local pos_to_dimension = mcl_worlds.pos_to_dimension
|
|||
|
||||
-- Takes a Minecraft layer and a “dimension” name
|
||||
-- and returns the corresponding Y coordinate for
|
||||
-- VoxeLibre
|
||||
-- MineClone 2.
|
||||
-- mc_dimension is one of "overworld", "nether", "end" (default: "overworld").
|
||||
function mcl_worlds.layer_to_y(layer, mc_dimension)
|
||||
if mc_dimension == "overworld" or mc_dimension == nil then
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# tga_encoder
|
||||
A TGA Encoder written in Lua without the use of external Libraries.
|
||||
|
||||
Created by fleckenstein for VoxeLibre, then improved by erlehmann.
|
||||
Created by fleckenstein for MineClone2, then improved by erlehmann.
|
||||
|
||||
May be used as a Minetest mod.
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ License of boat model:
|
|||
GNU GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
|
||||
|
||||
## Textures
|
||||
See the main VoxeLibre README.md file to learn more.
|
||||
See the main MineClone 2 README.md file to learn more.
|
||||
|
||||
## Code
|
||||
Code based on Minetest Game, licensed under the MIT License (MIT).
|
||||
|
|
|
@ -168,7 +168,7 @@ end
|
|||
|
||||
|
||||
function boat.on_activate(self, staticdata, dtime_s)
|
||||
self.object:set_armor_groups({fleshy = 125})
|
||||
self.object:set_armor_groups({fleshy = 100})
|
||||
local data = minetest.deserialize(staticdata)
|
||||
if type(data) == "table" then
|
||||
self._v = data.v
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name = mcl_burning
|
||||
description = Burning Objects for VoxeLibre
|
||||
description = Burning Objects for MineClone2
|
||||
author = Fleckenstein
|
||||
depends = mcl_weather
|
|
@ -1,6 +1,6 @@
|
|||
# mcl_dripping
|
||||
|
||||
Dripping Mod by kddekadenz, modified for VoxeLibre by Wuzzy, NO11 and AFCM
|
||||
Dripping Mod by kddekadenz, modified for MineClone 2 by Wuzzy, NO11 and AFCM
|
||||
|
||||
## Manual
|
||||
|
||||
|
|
|
@ -35,7 +35,9 @@ function mcl_entity_invs.load_inv(ent,size)
|
|||
mcl_log("load_inv 3")
|
||||
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
|
||||
inv:set_size("main", size)
|
||||
if ent._items then
|
||||
if ent._mcl_entity_invs_load_items then
|
||||
inv:set_list("main",ent:_mcl_entity_invs_load_items())
|
||||
elseif ent._items then
|
||||
inv:set_list("main",ent._items)
|
||||
end
|
||||
else
|
||||
|
@ -46,9 +48,14 @@ end
|
|||
|
||||
function mcl_entity_invs.save_inv(ent)
|
||||
if ent._inv then
|
||||
ent._items = {}
|
||||
local items = {}
|
||||
for i,it in ipairs(ent._inv:get_list("main")) do
|
||||
ent._items[i] = it:to_string()
|
||||
items[i] = it:to_string()
|
||||
end
|
||||
if ent._mcl_entity_invs_save_items then
|
||||
ent:_mcl_entity_invs_save_items(items)
|
||||
else
|
||||
ent._items = items
|
||||
end
|
||||
minetest.remove_detached_inventory(ent._inv_id)
|
||||
ent._inv = nil
|
||||
|
|
|
@ -6,6 +6,9 @@ local pool = {}
|
|||
|
||||
local tick = false
|
||||
|
||||
|
||||
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
pool[player:get_player_name()] = 0
|
||||
end)
|
||||
|
@ -127,7 +130,6 @@ local function try_object_pickup(player, inv, object, checkpos)
|
|||
|
||||
-- Add what we can to the inventory
|
||||
local itemstack = ItemStack(le.itemstring)
|
||||
tt.reload_itemstack_description(itemstack)
|
||||
local leftovers = inv:add_item("main", itemstack )
|
||||
|
||||
check_pickup_achievements(object, player)
|
||||
|
@ -170,17 +172,17 @@ minetest.register_globalstep(function(_)
|
|||
|
||||
local pos = player:get_pos()
|
||||
|
||||
if tick == true and (pool[name] or 0) > 0 then
|
||||
if tick == true and pool[name] > 0 then
|
||||
minetest.sound_play("item_drop_pickup", {
|
||||
pos = pos,
|
||||
gain = 0.3,
|
||||
max_hear_distance = 16,
|
||||
pitch = math.random(70, 110) / 100
|
||||
})
|
||||
if (pool[name] or 0) > 6 then
|
||||
if pool[name] > 6 then
|
||||
pool[name] = 6
|
||||
else
|
||||
pool[name] = (pool[name] or 1) - 1
|
||||
pool[name] = pool[name] - 1
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -449,7 +451,6 @@ function minetest.node_dig(pos, node, digger)
|
|||
end
|
||||
end
|
||||
end
|
||||
tt.reload_itemstack_description(wielded) -- update tooltip
|
||||
digger:set_wielded_item(wielded)
|
||||
end
|
||||
|
||||
|
@ -832,6 +833,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
_insta_collect = self._insta_collect,
|
||||
_flowing = self._flowing,
|
||||
_removed = self._removed,
|
||||
_immortal = self._immortal,
|
||||
})
|
||||
-- sfan5 guessed that the biggest serializable item
|
||||
-- entity would have a size of 65530 bytes. This has
|
||||
|
@ -884,6 +886,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
self._insta_collect = data._insta_collect
|
||||
self._flowing = data._flowing
|
||||
self._removed = data._removed
|
||||
self._immortal = data._immortal
|
||||
end
|
||||
else
|
||||
self.itemstring = staticdata
|
||||
|
@ -976,7 +979,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
if self._collector_timer then
|
||||
self._collector_timer = self._collector_timer + dtime
|
||||
end
|
||||
if time_to_live > 0 and self.age > time_to_live then
|
||||
if time_to_live > 0 and ( self.age > time_to_live and not self._immortal ) then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
# Table of Contents
|
||||
1. [Useful Constants](#useful-constants)
|
||||
2. [Rail](#rail)
|
||||
1. [Constants](#constants)
|
||||
2. [Functions](#functions)
|
||||
3. [Node Definition Options](#node-definition-options)
|
||||
3. [Cart Functions](#cart-functions)
|
||||
4. [Cart Data Functions](#cart-data-functions)
|
||||
5. [Cart-Node Interactions](#cart-node-interactions)
|
||||
6. [Train Functions](#train-functions)
|
||||
|
||||
## Useful Constants
|
||||
|
||||
- `mcl_minecarts.north`
|
||||
- `mcl_minecarts.south`
|
||||
- `mcl_minecarts.east`
|
||||
- `mcl_minecarts.west`
|
||||
|
||||
Human-readable names for the cardinal directions.
|
||||
|
||||
- `mcl_minecarts.SPEED_MAX`
|
||||
|
||||
Maximum speed that minecarts will be accelerated to with powered rails, in blocks per
|
||||
second. Defined as 10 blocks/second.
|
||||
|
||||
- `mcl_minecarts.CART_BLOCKS_SIZE`
|
||||
|
||||
The size of blocks to use when searching for carts to respawn. Defined as is 64 blocks.
|
||||
|
||||
- `mcl_minecarts.FRICTION`
|
||||
|
||||
Rail friction. Defined as is 0.4 blocks/second^2.
|
||||
|
||||
- `mcl_minecarts.MAX_TRAIN_LENGTH`
|
||||
|
||||
The maximum number of carts that can be in a single train. Defined as 4 carts.
|
||||
|
||||
- `mcl_minecarts.PASSENGER_ATTACH_POSITION`
|
||||
|
||||
Where to attach passengers to the minecarts.
|
||||
|
||||
## Rail
|
||||
|
||||
### Constants
|
||||
|
||||
`mcl_minecarts.HORIZONTAL_CURVES_RULES`
|
||||
`mcl_minecarts.HORIZONTAL_STANDARD_RULES`
|
||||
|
||||
Rail connection rules. Each rule is an table with the following indexes:
|
||||
|
||||
1. `node_name_suffix` - The suffix added to a node's `_mcl_minecarts.base_name` to
|
||||
get the name of the node to use for this connection.
|
||||
2. `param2_value` - The value of the node's param2. Used to specify rotation.
|
||||
|
||||
and the following named options:
|
||||
|
||||
- `mask` - Directional connections mask
|
||||
- `score` - priority of the rule. If more than one rule matches, the one with the
|
||||
highest store is selected.
|
||||
- `can_slope` - true if the result of this rule can be converted into a slope.
|
||||
|
||||
`mcl_minecarts.RAIL_GROUPS.STANDARD`
|
||||
`mcl_minecarts.RAIL_GROUPS.CURVES`
|
||||
|
||||
These constants are used to specify a rail node's `group.rail` value.
|
||||
|
||||
### Functions
|
||||
|
||||
`mcl_minecarts.get_rail_connections(node_position, options)`
|
||||
|
||||
Calculate the rail adjacency information for rail placement. Arguments are:
|
||||
|
||||
- `node_position` - the location of the node to calculate adjacency for.
|
||||
- `options` - A table containing any of these options:
|
||||
- `legacy`- if true, don't check that a connection proceeds out in a direction
|
||||
a cart can travel. Used for converting legacy rail to newer equivalents.
|
||||
- `ignore_neightbor_connections` - if true, don't check that a cart could leave
|
||||
the neighboring node from this direction.
|
||||
|
||||
`mcl_minecarts:is_rail(position, railtype)`
|
||||
|
||||
Determines if the node at `position` is a rail. If `railtype` is provided,
|
||||
determine if the node at `position` is that type of rail.
|
||||
|
||||
`mcl_minecarts.register_rail(itemstring, node_definition)`
|
||||
|
||||
Registers a rail with a few sensible defaults and if a craft recipe was specified,
|
||||
register that as well.
|
||||
|
||||
`mcl_minecarts.register_straight_rail(base_name, tiles, node_definition)`
|
||||
|
||||
Registers a rail with only straight and sloped variants.
|
||||
|
||||
`mcl_minecarts.register_curves_rail(base_name, tiles, node_definition)`
|
||||
|
||||
Registers a rail with straight, sloped, curved, tee and cross variants.
|
||||
|
||||
`mcl_minecarts.update_rail_connections(node_position, options)`
|
||||
|
||||
Converts the rail at `node_position`, if possible, another variant (curve, etc.)
|
||||
and rotates the node as needed so that rails connect together. `options` is
|
||||
passed thru to `mcl_minecarts.get_rail_connections()`
|
||||
|
||||
`mcl_minecarts:get_rail_direction(rail_position, cart_direction)`
|
||||
|
||||
Returns the next direction a cart traveling in the direction specified in `cart_direction`
|
||||
will travel from the rail located at `rail_position`.
|
||||
|
||||
### Node Definition Options
|
||||
|
||||
`_mcl_minecarts.railtype`
|
||||
|
||||
This declares the variant type of the rail. This will be one of the following:
|
||||
|
||||
- "straight" - two connections opposite each other and no vertical change.
|
||||
- "sloped" - two connections opposite each other with one of these connections
|
||||
one block higher.
|
||||
- "corner" - two connections at 90 degrees from each other.
|
||||
- "tee" - three connections
|
||||
- "cross" - four connections allowing only straight-thru movement
|
||||
|
||||
#### Hooks
|
||||
`_mcl_minecarts.get_next_dir = function(node_position, current_direction, node)`
|
||||
|
||||
Called to get the next direction a cart will travel after passing thru this node.
|
||||
|
||||
## Cart Functions
|
||||
|
||||
`mcl_minecarts.attach_driver(cart, player)`
|
||||
|
||||
This attaches (ObjectRef) `player` to the (LuaEntity) `cart`.
|
||||
|
||||
`mcl_minecarts.detach_minecart(cart_data)`
|
||||
|
||||
This detaches a minecart from any rail it is attached to and makes it start moving
|
||||
as an entity affected by gravity. It will keep moving in the same direction and
|
||||
at the same speed it was moving at before it detaches.
|
||||
|
||||
`mcl_minecarts.get_cart_position(cart_data)`
|
||||
|
||||
Compute the location of a minecart from its cart data. This works even when the entity
|
||||
is unloaded.
|
||||
|
||||
`mcl_minecarts.kill_cart(cart_data)`
|
||||
|
||||
Kills a cart and drops it as an item, even if the cart entity is unloaded.
|
||||
|
||||
`mcl_minecarts.place_minecart(itemstack, pointed_thing, placer)`
|
||||
|
||||
Places a minecart at the location specified by `pointed_thing`
|
||||
|
||||
`mcl_minecarts.register_minecart(minecart_definition)`
|
||||
|
||||
Registers a minecart. `minecart_definition` defines the entity. All the options supported by
|
||||
normal minetest entities are supported, with a few additions:
|
||||
|
||||
- `craft` - Crafting recipe for this cart.
|
||||
- `drop` - List of items to drop when the cart is killed. (required)
|
||||
- `entity_id` - The entity id of the cart. (required)
|
||||
- `itemstring` - This is the itemstring to use for this entity. (required)
|
||||
|
||||
`mcl_minecarts.reverse_cart_direction(cart_data)`
|
||||
|
||||
Force a minecart to start moving in the opposite direction of its current direction.
|
||||
|
||||
`mcl_minecarts.snap_direction(direction_vector)`
|
||||
|
||||
Returns a valid cart movement direction that has the smallest angle between it and `direction_vector`.
|
||||
|
||||
`mcl_minecarts:update_cart_orientation(cart)`
|
||||
|
||||
Updates the rotation of a cart entity to match the cart's data.
|
||||
|
||||
## Cart Data Functions
|
||||
|
||||
`mcl_minecarts.destroy_cart_data(uuid)`
|
||||
|
||||
Destroys the data for the cart with the identitfier in `uuid`.
|
||||
|
||||
`mcl_minecarts.find_carts_by_block_map(block_map)`
|
||||
|
||||
Returns a list of cart data for carts located in the blocks specified in `block_map`. Used
|
||||
to respawn carts entering areas around players.
|
||||
|
||||
`mcl_minecarts.add_blocks_to_map(block_map, min_pos, max_pos)`
|
||||
|
||||
Add blocks that fully contain `min_pos` and `max_pos` to `block_map` for use by
|
||||
`mcl_minecarts.find_cart_by_block_map`.
|
||||
|
||||
`mcl_minecarts.get_cart_data(uuid)`
|
||||
|
||||
Loads the data for the cart with the identitfier in `uuid`.
|
||||
|
||||
`mcl_minecarts.save_cart_data(uuid)`
|
||||
|
||||
Saves the data for the cart with the identifier in `uuid`.
|
||||
|
||||
`mcl_minecart.update_cart_data(data)`
|
||||
|
||||
Replaces the cart data for the cart with the identifier in `data.uuid`, then saves
|
||||
the data.
|
||||
|
||||
## Cart-Node Interactions
|
||||
|
||||
As the cart moves thru the environment, it can interact with the surrounding blocks
|
||||
thru a number of handlers in the block definitions. All these handlers are defined
|
||||
as:
|
||||
|
||||
`function(node_position, cart_luaentity, cart_direction, cart_position)`
|
||||
|
||||
Arguments:
|
||||
- `node_position` - position of the node the cart is interacting with
|
||||
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
|
||||
be nil for minecarts moving thru unloaded blocks
|
||||
- `cart_direction` - The direction the cart is moving
|
||||
- `cart_position` - The location of the cart
|
||||
- `cart_data` - Information about the cart. This will always be defined.
|
||||
|
||||
There are several variants of this handler:
|
||||
- `_mcl_minecarts_on_enter` - The cart enters this block
|
||||
- `_mcl_minecarts_on_enter_below` - The cart enters above this block
|
||||
- `_mcl_minecarts_on_enter_above` - The cart enters below this block
|
||||
- `_mcl_minecarts_on_enter_side` - The cart enters beside this block
|
||||
|
||||
Mods can also define global handlers that are called for every node. These
|
||||
handlers are defined as:
|
||||
|
||||
`function(node_position, cart_luaentity, cart_direction, node_definition, cart_data)`
|
||||
|
||||
Arguments:
|
||||
- `node_position` - position of the node the cart is interacting with
|
||||
- `cart_luaentity` - The luaentity of the cart that is entering this block. Will
|
||||
be nil for minecarts moving thru unloaded blocks
|
||||
- `cart_direction` - The direction the cart is moving
|
||||
- `cart_position` - The location of the cart
|
||||
- `cart_data` - Information about the cart. This will always be defined.
|
||||
- `node_definition` - The definition of the node at `node_position`
|
||||
|
||||
The available hooks are:
|
||||
- `_mcl_minecarts.on_enter` - The cart enters this block
|
||||
- `_mcl_minecarts.on_enter_below` - The cart enters above this block
|
||||
- `_mcl_minecarts.on_enter_above` - The cart enters below this block
|
||||
- `_mcl_minecarts.on_enter_side` - The cart enters beside this block
|
||||
|
||||
Only a single function can be installed in each of these handlers. Before installing,
|
||||
preserve the existing handler and call it from inside your handler if not `nil`.
|
||||
|
||||
## Train Functions
|
||||
|
||||
`mcl_minecarts.break_train_at(cart_data)`
|
||||
|
||||
Splits a train apart at the specified cart.
|
||||
|
||||
`mcl_minecarts.distance_between_cars(cart1_data, cart2_data)`
|
||||
|
||||
Returns the distance between two carts even if both entities are unloaded, or nil if either
|
||||
cart is not on a rail.
|
||||
|
||||
`mcl_minecarts.is_in_same_train(cart1_data, cart2_data)`
|
||||
|
||||
Returns true if cart1 and cart2 are a part of the same train and false otherwise.
|
||||
|
||||
`mcl_minecarts.link_cart_ahead(cart_data, cart_ahead_data)`
|
||||
|
||||
Given two carts, link them together into a train, with the second cart ahead of the first.
|
||||
|
||||
`mcl_minecarts.train_cars(cart_data)`
|
||||
|
||||
Use to iterate over all carts in a train. Expected usage:
|
||||
|
||||
`for cart in mcl_minecarts.train_cars(cart) do --[[ code ]] end`
|
||||
|
||||
`mcl_minecarts.reverse_train(cart)`
|
||||
|
||||
Make all carts in a train reverse and start moving in the opposite direction.
|
||||
|
||||
`mcl_minecarts.train_length(cart_data)`
|
||||
|
||||
Compute the current length of the train containing the cart whose data is `cart_data`.
|
||||
|
||||
`mcl_minecarts.update_train(cart_data)`
|
||||
|
||||
When provided with the rear-most cart of a tain, update speeds of all carts in the train
|
||||
so that it holds together and moves as a unit.
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
## Organization
|
||||
- [ init.lua](./init.lua) - module entrypoint. The other files are included from here
|
||||
and several constants are defined here
|
||||
|
||||
- [carts.lua](./carts/lua) - This file contains code related to cart entities, cart
|
||||
type registration, creation, estruction and updating. The global step function
|
||||
responsible for updating attached carts is in this file. The various carts are
|
||||
referenced from this file but actually reside in the subdirectory [carts/](./carts/).
|
||||
|
||||
- [functions.lua](./functions.lua) - This file contains various minecart and rail
|
||||
utility functions used by the rest of the code.
|
||||
|
||||
- [movement.lua](./movement.lua) - This file contains the code related to cart
|
||||
movement physics.
|
||||
|
||||
- [rails.lua](./rails.lua) - This file contains code related to rail registation,
|
||||
placement, connection rules and cart direction selection. This contains the rail
|
||||
behaviors and the LBM code for updating legacy rail nodes to the new versions
|
||||
that don't use the raillike draw type.
|
||||
|
||||
- [storage.lua](./storage.lua) - This file contains the code than manages minecart
|
||||
state data to allow processing minecarts while entities are unloaded.
|
||||
|
||||
- [train.lua](./train.lua) - This file contains code related to multi-car trains.
|
||||
|
||||
## Rail Nodes
|
||||
|
||||
Previous versions of mcl\_minecarts used one node type for each rail type (standard,
|
||||
powered, detector and activator) using the raillike draw type that minetest provides.
|
||||
This version does not use the raillike draw type and instead uses a 1/16th of a block
|
||||
high nodebox and uses an additional node definition for each variant. The variants
|
||||
present are:
|
||||
|
||||
- straight
|
||||
- sloped
|
||||
- corner
|
||||
- tee
|
||||
- cross
|
||||
|
||||
Of the rail types provided by this module, standard has all of these variants. The
|
||||
remaining types only have straight and sloped variants.
|
||||
|
||||
Unlike the old rail type, this version will only update connections when placed, and
|
||||
will only place a variant that already has connections into the space the rail is
|
||||
being placed. Here is how to create the various varients:
|
||||
|
||||
- Straight rail is placed when with zero or one adjacent rail nodes. If no rails
|
||||
are adjacent, the rail is placed in line with the direction the player is facing.
|
||||
If there is exactly one adjacent rail present, the straight rail will always rotate
|
||||
to connect to it.
|
||||
|
||||
- Sloped rail is placed when there are two rails in a straight line, with one being
|
||||
one block higher. When rail is placed adjacent to a straight rail one block lower
|
||||
and the rail is facing the block the rail is being placed on, the lower rail will
|
||||
convert into a slope.
|
||||
|
||||
- A corner rail is placed when there are exactly two adjacent rails that are not in
|
||||
a line and lead into the space the rail is being placed. The corner will be rotated
|
||||
to connect these two rails.
|
||||
|
||||
- A tee rail is placed where there are exactly three rails adjact and those existing
|
||||
rails lead into the the space the new rail is being placed.
|
||||
|
||||
- A rail cross is placed when there is rail in all four adjacent blocks and they all
|
||||
have a path into the space the new rail is being placed.
|
||||
|
||||
The tee variant will interact with redstone and mesecons to switch the curved section.
|
||||
|
||||
## On-rail Minecart Movement
|
||||
|
||||
Minecart movement is handled in two distinct regimes: on a rail and off. The
|
||||
off-rail movement is handled with minetest's builtin entity movement handling.
|
||||
The on-rail movement is handled with a custom algorithm. This section details
|
||||
the latter.
|
||||
|
||||
The data for on-rail minecart movement is stored entirely inside mod storage
|
||||
and indexed by a hex-encoded 128-bit universally-unique identifier (uuid). Minecart
|
||||
entities store this uuid and a sequence identifier. The code for handling this
|
||||
storage is in [storage.lua](./storage.lua). This was done so that minecarts can
|
||||
still move while no players are connected or when out of range of players. Inspiration
|
||||
for this was the [Advanced Trains mod](http://advtrains.de/). This is a behavior difference
|
||||
when compared to minecraft, as carts there will stop movement when out of range of
|
||||
players.
|
||||
|
||||
Processing for minecart movement is as follows:
|
||||
1. In a globalstep handler in [carts.lua](./carts.lua), determine which carts are
|
||||
moving.
|
||||
2. Call `do_movement` in [movement.lua](./movement.lua) to update
|
||||
each cart's location and handle interactions with the environment.
|
||||
1. Each movement is broken up into one or more steps that are completely
|
||||
contained inside a block. This prevents carts from ever jumping from
|
||||
one rail to another over a gap or thru solid blocks because of server
|
||||
lag. Each step is processed with `do_movement_step`
|
||||
2. Each step uses physically accurate, timestep-independent physics
|
||||
to move the cart. Calculating the acceleration to apply to a cart
|
||||
is broken out into its own function (`calculate_acceperation`).
|
||||
3. As the cart enters and leaves blocks, handlers in nearby blocks are called
|
||||
to allow the cart to efficiently interact with the environment. Handled by
|
||||
the functions `handle_cart_enter` and `handle_cart_leave`
|
||||
4. The cart checks for nearby carts and collides elastically with these. The
|
||||
calculations for these collisions are in the function `handle_cart_collision`
|
||||
5. If the cart enters a new block, determine the new direction the cart will
|
||||
move with `mcl_minecarts:get_rail_direction` in [functions.lua](./functions.lua).
|
||||
The rail nodes provide a hook `_mcl_minecarts.get_next_direction` that
|
||||
provides this information based on the previous movement direction.
|
||||
3. If an entity exists for a given cart, the entity will update its position
|
||||
while loaded in.
|
||||
|
||||
Cart movement when on a rail occurs regarless of whether an entity for that
|
||||
cart exists or is loaded into memory. As a consequence of this movement, it
|
||||
is possible for carts with unloaded entities to enter range of a player.
|
||||
To handle this, periodic checks are performed around players and carts that
|
||||
are within range but don't have a cart have a new entity spawned.
|
||||
|
||||
Every time a cart has a new entity spawned, it increases a sequence number in
|
||||
the cart data to allow removing old entities from the minetest engine. Any cart
|
||||
entity that does not have the current sequence number for a minecart gets removed
|
||||
once processing for that entity resumes.
|
||||
|
|
@ -10,6 +10,7 @@ MIT License
|
|||
Copyright (C) 2012-2016 PilzAdam
|
||||
Copyright (C) 2014-2016 SmallJoker
|
||||
Copyright (C) 2012-2016 Various Minetest developers and contributors
|
||||
Copyright (C) 2024 teknomunk
|
||||
|
||||
Authors/licenses of media files:
|
||||
-----------------------
|
||||
|
|
|
@ -0,0 +1,666 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local mcl_log,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
|
||||
|
||||
-- Imports
|
||||
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
|
||||
local table_merge = mcl_util.table_merge
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local save_cart_data = mod.save_cart_data
|
||||
local update_cart_data = mod.update_cart_data
|
||||
local destroy_cart_data = mod.destroy_cart_data
|
||||
local find_carts_by_block_map = mod.find_carts_by_block_map
|
||||
local do_movement,do_detached_movement,handle_cart_enter = dofile(modpath.."/movement.lua")
|
||||
assert(do_movement)
|
||||
assert(do_detached_movement)
|
||||
assert(handle_cart_enter)
|
||||
|
||||
-- Constants
|
||||
local max_step_distance = 0.5
|
||||
local MINECART_MAX_HP = 4
|
||||
local TWO_OVER_PI = 2 / math.pi
|
||||
|
||||
local function detach_driver(self)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
if not self._driver then
|
||||
return
|
||||
end
|
||||
|
||||
-- Update player infomation
|
||||
local driver_name = self._driver
|
||||
local playerinfo = mcl_playerinfo[driver_name]
|
||||
if playerinfo then
|
||||
playerinfo.attached_to = nil
|
||||
end
|
||||
mcl_player.player_attached[driver_name] = nil
|
||||
|
||||
minetest.log("action", driver_name.." left a minecart")
|
||||
|
||||
-- Update cart informatino
|
||||
self._driver = nil
|
||||
self._start_pos = nil
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(driver_name, modname)
|
||||
player_meta.attached_to = nil
|
||||
|
||||
-- Detatch the player object from the minecart
|
||||
local player = minetest.get_player_by_name(driver_name)
|
||||
if player then
|
||||
local dir = staticdata.dir or vector.new(1,0,0)
|
||||
local cart_pos = mod.get_cart_position(staticdata) or self.object:get_pos()
|
||||
local new_pos = vector.offset(cart_pos, -dir.z, 0, dir.x)
|
||||
player:set_detach()
|
||||
--print("placing player at "..tostring(new_pos).." from cart at "..tostring(cart_pos)..", old_pos="..tostring(player:get_pos()).."dir="..tostring(dir))
|
||||
|
||||
-- There needs to be a delay here or the player's position won't update
|
||||
minetest.after(0.1,function(driver_name,new_pos)
|
||||
local player = minetest.get_player_by_name(driver_name)
|
||||
player:moveto(new_pos, false)
|
||||
end, driver_name, new_pos)
|
||||
|
||||
player:set_eye_offset(vector.new(0,0,0),vector.new(0,0,0))
|
||||
mcl_player.player_set_animation(player, "stand" , 30)
|
||||
--else
|
||||
--print("No player object found for "..driver_name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
|
||||
local entity_mapping = {}
|
||||
|
||||
local function make_staticdata( _, connected_at, dir )
|
||||
return {
|
||||
connected_at = connected_at,
|
||||
distance = 0,
|
||||
velocity = 0,
|
||||
dir = vector.new(dir),
|
||||
mass = 1,
|
||||
seq = 1,
|
||||
}
|
||||
end
|
||||
|
||||
local DEFAULT_CART_DEF = {
|
||||
initial_properties = {
|
||||
physical = true,
|
||||
collisionbox = {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
|
||||
visual = "mesh",
|
||||
visual_size = {x=1, y=1},
|
||||
},
|
||||
|
||||
hp_max = MINECART_MAX_HP,
|
||||
|
||||
groups = {
|
||||
minecart = 1,
|
||||
},
|
||||
|
||||
_driver = nil, -- player who sits in and controls the minecart (only for minecart!)
|
||||
_passenger = nil, -- for mobs
|
||||
_start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
|
||||
_last_float_check = nil, -- timestamp of last time the cart was checked to be still on a rail
|
||||
_boomtimer = nil, -- how many seconds are left before exploding
|
||||
_blinktimer = nil, -- how many seconds are left before TNT blinking
|
||||
_blink = false, -- is TNT blink texture active?
|
||||
_old_pos = nil,
|
||||
_staticdata = nil,
|
||||
}
|
||||
function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
|
||||
-- Transfer older data
|
||||
local data = minetest.deserialize(staticdata) or {}
|
||||
if not data.uuid then
|
||||
data.uuid = mcl_util.assign_uuid(self.object)
|
||||
end
|
||||
self._seq = data.seq or 1
|
||||
|
||||
local cd = get_cart_data(data.uuid)
|
||||
if not cd then
|
||||
update_cart_data(data)
|
||||
else
|
||||
if not cd.seq then cd.seq = 1 end
|
||||
data = cd
|
||||
end
|
||||
|
||||
-- Fix up types
|
||||
data.dir = vector.new(data.dir)
|
||||
|
||||
-- Fix mass
|
||||
data.mass = data.mass or 1
|
||||
|
||||
-- Make sure all carts have an ID to isolate them
|
||||
self._uuid = data.uuid
|
||||
self._staticdata = data
|
||||
|
||||
-- Activate cart if on powered activator rail
|
||||
if self.on_activate_by_rail then
|
||||
local pos = self.object:get_pos()
|
||||
local node = minetest.get_node(vector.floor(pos))
|
||||
if node.name == "mcl_minecarts:activator_rail_on" then
|
||||
self:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end
|
||||
function DEFAULT_CART_DEF:get_staticdata()
|
||||
save_cart_data(self._staticdata.uuid)
|
||||
return minetest.serialize({uuid = self._staticdata.uuid, seq=self._seq})
|
||||
end
|
||||
|
||||
function DEFAULT_CART_DEF:_mcl_entity_invs_load_items()
|
||||
local staticdata = self._staticdata
|
||||
return staticdata.inventory or {}
|
||||
end
|
||||
function DEFAULT_CART_DEF:_mcl_entity_invs_save_items(items)
|
||||
local staticdata = self._staticdata
|
||||
staticdata.inventory = table.copy(items)
|
||||
end
|
||||
|
||||
function DEFAULT_CART_DEF:add_node_watch(pos)
|
||||
local staticdata = self._staticdata
|
||||
local watches = staticdata.node_watches or {}
|
||||
|
||||
for i=1,#watches do
|
||||
if watches[i] == pos then return end
|
||||
end
|
||||
|
||||
watches[#watches+1] = pos
|
||||
staticdata.node_watches = watches
|
||||
end
|
||||
function DEFAULT_CART_DEF:remove_node_watch(pos)
|
||||
local staticdata = self._staticdata
|
||||
local watches = staticdata.node_watches or {}
|
||||
|
||||
local new_watches = {}
|
||||
for i=1,#watches do
|
||||
local node_pos = watches[i]
|
||||
if node_pos ~= pos then
|
||||
new_watches[#new_watches + 1] = node_pos
|
||||
end
|
||||
end
|
||||
staticdata.node_watches = new_watches
|
||||
end
|
||||
function DEFAULT_CART_DEF:get_cart_position()
|
||||
local staticdata = self._staticdata
|
||||
|
||||
if staticdata.connected_at then
|
||||
return staticdata.connected_at + staticdata.dir * staticdata.distance
|
||||
else
|
||||
return self.object:get_pos()
|
||||
end
|
||||
end
|
||||
function DEFAULT_CART_DEF:on_punch(puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
if puncher == self._driver then return end
|
||||
|
||||
local staticdata = self._staticdata
|
||||
local controls = staticdata.controls or {}
|
||||
|
||||
local impulse = vector.multiply(dir, damage * 20)
|
||||
|
||||
local accel = vector.dot(staticdata.dir, impulse)
|
||||
if accel < 0 and staticdata.velocity == 0 then
|
||||
mod.reverse_direction(staticdata)
|
||||
end
|
||||
|
||||
controls.impulse = impulse
|
||||
--print("uuid="..self._uuid..", controls="..dump(controls))
|
||||
staticdata.controls = controls
|
||||
end
|
||||
function DEFAULT_CART_DEF:on_step(dtime)
|
||||
local staticdata = self._staticdata
|
||||
if not staticdata then
|
||||
staticdata = make_staticdata()
|
||||
self._staticdata = staticdata
|
||||
end
|
||||
|
||||
-- Update entity position
|
||||
local pos = mod.get_cart_position(staticdata)
|
||||
if pos then self.object:move_to(pos) end
|
||||
|
||||
-- Repair cart_type
|
||||
if not staticdata.cart_type then
|
||||
staticdata.cart_type = self.name
|
||||
end
|
||||
|
||||
-- Remove superceded entities
|
||||
if self._seq ~= staticdata.seq then
|
||||
--print("removing cart #"..staticdata.uuid.." with sequence number mismatch")
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- Regen
|
||||
local hp = self.object:get_hp()
|
||||
local time_now = minetest.get_gametime()
|
||||
if hp < MINECART_MAX_HP and (staticdata.last_regen or 0) <= time_now - 1 then
|
||||
staticdata.last_regen = time_now
|
||||
hp = hp + 1
|
||||
self.object:set_hp(hp)
|
||||
end
|
||||
|
||||
-- Cart specific behaviors
|
||||
local hook = self._mcl_minecarts_on_step
|
||||
if hook then hook(self,dtime) end
|
||||
|
||||
if (staticdata.hopper_delay or 0) > 0 then
|
||||
staticdata.hopper_delay = staticdata.hopper_delay - dtime
|
||||
end
|
||||
|
||||
-- Controls
|
||||
local ctrl, player = nil, nil
|
||||
if self._driver then
|
||||
player = minetest.get_player_by_name(self._driver)
|
||||
if player then
|
||||
ctrl = player:get_player_control()
|
||||
-- player detach
|
||||
if ctrl.sneak then
|
||||
detach_driver(self)
|
||||
return
|
||||
end
|
||||
|
||||
-- Experimental controls
|
||||
local now_time = minetest.get_gametime()
|
||||
local controls = {}
|
||||
if ctrl.up then controls.forward = now_time end
|
||||
if ctrl.down then controls.brake = now_time end
|
||||
controls.look = math.round(player:get_look_horizontal() * TWO_OVER_PI) % 4
|
||||
staticdata.controls = controls
|
||||
end
|
||||
|
||||
-- Give achievement when player reached a distance of 1000 nodes from the start position
|
||||
if pos and vector.distance(self._start_pos, pos) >= 1000 then
|
||||
awards.unlock(self._driver, "mcl:onARail")
|
||||
end
|
||||
end
|
||||
|
||||
if not staticdata.connected_at then
|
||||
do_detached_movement(self, dtime)
|
||||
end
|
||||
|
||||
mod.update_cart_orientation(self)
|
||||
end
|
||||
function mod.kill_cart(staticdata, killer)
|
||||
local pos
|
||||
minetest.log("action", "cart #"..staticdata.uuid.." was killed")
|
||||
|
||||
-- Leave nodes
|
||||
if staticdata.attached_at then
|
||||
handle_cart_leave(self, staticdata.attached_at, staticdata.dir )
|
||||
else
|
||||
--mcl_log("TODO: handle detatched minecart death")
|
||||
end
|
||||
|
||||
-- Handle entity-related items
|
||||
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
if le then
|
||||
pos = le.object:get_pos()
|
||||
|
||||
detach_driver(le)
|
||||
|
||||
-- Detach passenger
|
||||
if le._passenger then
|
||||
local mob = le._passenger.object
|
||||
mob:set_detach()
|
||||
end
|
||||
|
||||
-- Remove the entity
|
||||
le.object:remove()
|
||||
else
|
||||
pos = mod.get_cart_position(staticdata)
|
||||
end
|
||||
|
||||
-- Drop items
|
||||
if not staticdata.dropped then
|
||||
|
||||
-- Try to drop the cart
|
||||
local entity_def = minetest.registered_entities[staticdata.cart_type]
|
||||
if entity_def then
|
||||
local drop_cart = true
|
||||
if killer and minetest.is_creative_enabled(killer:get_player_name()) then
|
||||
drop_cart = false
|
||||
end
|
||||
|
||||
if drop_cart then
|
||||
local drop = entity_def.drop
|
||||
for d=1, #drop do
|
||||
minetest.add_item(pos, drop[d])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Drop any items in the inventory
|
||||
local inventory = staticdata.inventory
|
||||
if inventory then
|
||||
for i=1,#inventory do
|
||||
minetest.add_item(pos, inventory[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- Prevent item duplication
|
||||
staticdata.dropped = true
|
||||
end
|
||||
|
||||
-- Remove data
|
||||
destroy_cart_data(staticdata.uuid)
|
||||
end
|
||||
local kill_cart = mod.kill_cart
|
||||
|
||||
function DEFAULT_CART_DEF:on_death(killer)
|
||||
kill_cart(self._staticdata, killer)
|
||||
end
|
||||
|
||||
-- Create a minecart
|
||||
function mod.create_minecart(entity_id, pos, dir)
|
||||
-- Setup cart data
|
||||
local uuid = mcl_util.gen_uuid()
|
||||
data = make_staticdata( nil, pos, dir )
|
||||
data.uuid = uuid
|
||||
data.cart_type = entity_id
|
||||
update_cart_data(data)
|
||||
save_cart_data(uuid)
|
||||
|
||||
return uuid
|
||||
end
|
||||
local create_minecart = mod.create_minecart
|
||||
|
||||
-- Place a minecart at pointed_thing
|
||||
function mod.place_minecart(itemstack, pointed_thing, placer)
|
||||
if not pointed_thing.type == "node" then
|
||||
return
|
||||
end
|
||||
|
||||
local spawn_pos = pointed_thing.above
|
||||
local cart_dir = vector.new(1,0,0)
|
||||
|
||||
local railpos, node
|
||||
if mcl_minecarts:is_rail(pointed_thing.under) then
|
||||
railpos = pointed_thing.under
|
||||
elseif mcl_minecarts:is_rail(pointed_thing.above) then
|
||||
railpos = pointed_thing.above
|
||||
end
|
||||
if railpos then
|
||||
spawn_pos = railpos
|
||||
node = minetest.get_node(railpos)
|
||||
cart_dir = mcl_minecarts:get_rail_direction(railpos, vector.new(1,0,0))
|
||||
end
|
||||
|
||||
local entity_id = entity_mapping[itemstack:get_name()]
|
||||
|
||||
local uuid = create_minecart(entity_id, railpos, cart_dir)
|
||||
|
||||
-- Create the entity with the staticdata already setup
|
||||
local sd = minetest.serialize({ uuid=uuid, seq=1 })
|
||||
local cart = minetest.add_entity(spawn_pos, entity_id, sd)
|
||||
|
||||
cart:set_yaw(minetest.dir_to_yaw(cart_dir))
|
||||
|
||||
-- Update static data
|
||||
local le = cart:get_luaentity()
|
||||
if le then
|
||||
le._staticdata = data
|
||||
end
|
||||
|
||||
-- Call placer
|
||||
if le._mcl_minecarts_on_place then
|
||||
le._mcl_minecarts_on_place(le, placer)
|
||||
end
|
||||
|
||||
if railpos then
|
||||
handle_cart_enter(data, railpos)
|
||||
end
|
||||
|
||||
local pname = ""
|
||||
if placer then
|
||||
pname = placer:get_player_name()
|
||||
end
|
||||
if not minetest.is_creative_enabled(pname) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local function dropper_place_minecart(dropitem, pos)
|
||||
-- Don't try to place the minecart if pos isn't a rail
|
||||
local node = minetest.get_node(pos)
|
||||
if minetest.get_item_group(node.name, "rail") == 0 then return false end
|
||||
|
||||
mod.place_minecart(dropitem, {
|
||||
above = pos,
|
||||
under = vector.offset(pos,0,-1,0)
|
||||
})
|
||||
return true
|
||||
end
|
||||
|
||||
local function register_minecart_craftitem(itemstring, def)
|
||||
local groups = { minecart = 1, transport = 1 }
|
||||
if def.creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
end
|
||||
local item_def = {
|
||||
stack_max = 1,
|
||||
_mcl_dropper_on_drop = dropper_place_minecart,
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
if not pointed_thing.type == "node" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
local node = minetest.get_node(pointed_thing.under)
|
||||
if placer and not placer:get_player_control().sneak then
|
||||
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
||||
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
|
||||
end
|
||||
end
|
||||
|
||||
return mod.place_minecart(itemstack, pointed_thing, placer)
|
||||
end,
|
||||
_on_dispense = function(stack, pos, droppos, dropnode, dropdir)
|
||||
-- Place minecart as entity on rail. If there's no rail, just drop it.
|
||||
local placed
|
||||
if minetest.get_item_group(dropnode.name, "rail") ~= 0 then
|
||||
-- FIXME: This places minecarts even if the spot is already occupied
|
||||
local pointed_thing = { under = droppos, above = vector.new( droppos.x, droppos.y+1, droppos.z ) }
|
||||
placed = mod.place_minecart(stack, pointed_thing)
|
||||
end
|
||||
if placed == nil then
|
||||
-- Drop item
|
||||
minetest.add_item(droppos, stack)
|
||||
end
|
||||
end,
|
||||
groups = groups,
|
||||
}
|
||||
item_def.description = def.description
|
||||
item_def._tt_help = def.tt_help
|
||||
item_def._doc_items_longdesc = def.longdesc
|
||||
item_def._doc_items_usagehelp = def.usagehelp
|
||||
item_def.inventory_image = def.icon
|
||||
item_def.wield_image = def.icon
|
||||
minetest.register_craftitem(itemstring, item_def)
|
||||
end
|
||||
|
||||
--[[
|
||||
Register a minecart
|
||||
* itemstring: Itemstring of minecart item
|
||||
* entity_id: ID of minecart entity
|
||||
* description: Item name / description
|
||||
* longdesc: Long help text
|
||||
* usagehelp: Usage help text
|
||||
* mesh: Minecart mesh
|
||||
* textures: Minecart textures table
|
||||
* icon: Item icon
|
||||
* drop: Dropped items after destroying minecart
|
||||
* on_rightclick: Called after rightclick
|
||||
* on_activate_by_rail: Called when above activator rail
|
||||
* creative: If false, don't show in Creative Inventory
|
||||
]]
|
||||
function mod.register_minecart(def)
|
||||
-- Make sure all required parameters are present
|
||||
for _,name in pairs({"drop","itemstring","entity_id"}) do
|
||||
assert( def[name], "def."..name..", a required parameter, is missing")
|
||||
end
|
||||
|
||||
local entity_id = def.entity_id; def.entity_id = nil
|
||||
local craft = def.craft; def.craft = nil
|
||||
local itemstring = def.itemstring; def.itemstring = nil
|
||||
|
||||
-- Build cart definition
|
||||
local cart = table.copy(DEFAULT_CART_DEF)
|
||||
table_merge(cart, def)
|
||||
minetest.register_entity(entity_id, cart)
|
||||
|
||||
-- Register item to entity mapping
|
||||
entity_mapping[itemstring] = entity_id
|
||||
|
||||
register_minecart_craftitem(itemstring, def)
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
|
||||
end
|
||||
|
||||
if craft then
|
||||
minetest.register_craft(craft)
|
||||
end
|
||||
end
|
||||
local register_minecart = mod.register_minecart
|
||||
|
||||
dofile(modpath.."/carts/minecart.lua")
|
||||
dofile(modpath.."/carts/with_chest.lua")
|
||||
dofile(modpath.."/carts/with_commandblock.lua")
|
||||
dofile(modpath.."/carts/with_hopper.lua")
|
||||
dofile(modpath.."/carts/with_furnace.lua")
|
||||
dofile(modpath.."/carts/with_tnt.lua")
|
||||
|
||||
if minetest.get_modpath("mcl_wip") then
|
||||
mcl_wip.register_wip_item("mcl_minecarts:chest_minecart")
|
||||
mcl_wip.register_wip_item("mcl_minecarts:furnace_minecart")
|
||||
mcl_wip.register_wip_item("mcl_minecarts:command_block_minecart")
|
||||
end
|
||||
|
||||
local function respawn_cart(cart)
|
||||
local cart_type = cart.cart_type or "mcl_minecarts:minecart"
|
||||
local pos = mod.get_cart_position(cart)
|
||||
|
||||
local players = minetest.get_connected_players()
|
||||
local distance = nil
|
||||
for _,player in pairs(players) do
|
||||
local d = vector.distance(player:get_pos(), pos)
|
||||
if not distance or d < distance then distance = d end
|
||||
end
|
||||
if not distance or distance > 90 then return end
|
||||
|
||||
mcl_log("Respawning cart #"..cart.uuid.." at "..tostring(pos)..",distance="..distance..",node="..minetest.get_node(pos).name)
|
||||
|
||||
-- Update sequence so that old cart entities get removed
|
||||
cart.seq = (cart.seq or 1) + 1
|
||||
save_cart_data(cart.uuid)
|
||||
|
||||
-- Create the new entity and refresh caches
|
||||
local sd = minetest.serialize({ uuid=cart.uuid, seq=cart.seq })
|
||||
local entity = minetest.add_entity(pos, cart_type, sd)
|
||||
local le = entity:get_luaentity()
|
||||
le._staticdata = cart
|
||||
mcl_util.assign_uuid(entity)
|
||||
|
||||
-- We intentionally don't call the normal hooks because this minecart was already there
|
||||
end
|
||||
|
||||
-- Try to respawn cart entities for carts that have moved into range of a player
|
||||
local function try_respawn_carts()
|
||||
-- Build a map of blocks near players
|
||||
local block_map = {}
|
||||
local players = minetest.get_connected_players()
|
||||
for _,player in pairs(players) do
|
||||
local pos = player:get_pos()
|
||||
mod.add_blocks_to_map(
|
||||
block_map,
|
||||
vector.offset(pos,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE,-CART_BLOCK_SIZE),
|
||||
vector.offset(pos, CART_BLOCK_SIZE, CART_BLOCK_SIZE, CART_BLOCK_SIZE)
|
||||
)
|
||||
end
|
||||
|
||||
-- Find all cart data that are in these blocks
|
||||
local carts = find_carts_by_block_map(block_map)
|
||||
|
||||
-- Check to see if any of these don't have an entity
|
||||
for _,cart in pairs(carts) do
|
||||
local le = mcl_util.get_luaentity_from_uuid(cart.uuid)
|
||||
if not le then
|
||||
respawn_cart(cart)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
-- Periodically respawn carts that come into range of a player
|
||||
timer = timer - dtime
|
||||
if timer <= 0 then
|
||||
local start_time = minetest.get_us_time()
|
||||
try_respawn_carts()
|
||||
local stop_time = minetest.get_us_time()
|
||||
local duration = (stop_time - start_time) / 1e6
|
||||
timer = duration / 250e-6 -- Schedule 50us per second
|
||||
if timer > 5 then timer = 5 end
|
||||
end
|
||||
|
||||
-- Handle periodically updating out-of-range carts
|
||||
-- TODO: change how often cart positions are updated based on velocity
|
||||
local start_time
|
||||
if DEBUG then start_time = minetest.get_us_time() end
|
||||
|
||||
for uuid,staticdata in mod.carts() do
|
||||
local pos = mod.get_cart_position(staticdata)
|
||||
--[[
|
||||
local le = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
print("cart# "..uuid..
|
||||
",velocity="..tostring(staticdata.velocity)..
|
||||
",pos="..tostring(pos)..
|
||||
",le="..tostring(le)..
|
||||
",connected_at="..tostring(staticdata.connected_at)
|
||||
)]]
|
||||
|
||||
--- Non-entity code
|
||||
if staticdata.connected_at then
|
||||
do_movement(staticdata, dtime)
|
||||
end
|
||||
end
|
||||
|
||||
if DEBUG then
|
||||
local stop_time = minetest.get_us_time()
|
||||
print("Update took "..((stop_time-start_time)*1e-6).." seconds")
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
-- Try cart reattachment
|
||||
local player_name = player:get_player_name()
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
local cart_uuid = player_meta.attached_to
|
||||
if cart_uuid then
|
||||
local cartdata = get_cart_data(cart_uuid)
|
||||
|
||||
-- Can't get into a cart that was destroyed
|
||||
if not cartdata then
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't reattach players if someone else got in the cart
|
||||
if cartdata.last_player ~= player_name then
|
||||
return
|
||||
end
|
||||
|
||||
minetest.after(0.2,function(player_name, cart_uuid)
|
||||
local player = minetest.get_player_by_name(player_name)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
|
||||
local cart = mcl_util.get_luaentity_from_uuid(cart_uuid)
|
||||
if not cart then
|
||||
return
|
||||
end
|
||||
|
||||
mod.attach_driver(cart, player)
|
||||
end, player_name, cart_uuid)
|
||||
end
|
||||
end)
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
local mcl_log = mcl_util.make_mcl_logger("mcl_logging_minecarts", "Minecarts")
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local PASSENGER_ATTACH_POSITION = mod.PASSENGER_ATTACH_POSITION
|
||||
|
||||
local function activate_normal_minecart(self)
|
||||
detach_driver(self)
|
||||
|
||||
-- Detach passenger
|
||||
if self._passenger then
|
||||
local mob = self._passenger.object
|
||||
mob:set_detach()
|
||||
end
|
||||
end
|
||||
|
||||
function mod.attach_driver(cart, player)
|
||||
local staticdata = cart._staticdata
|
||||
|
||||
-- Make sure we have a player
|
||||
if not player or not player:is_player() then return end
|
||||
|
||||
local player_name = player:get_player_name()
|
||||
if cart._driver or player:get_player_control().sneak then return end
|
||||
|
||||
-- Update cart information
|
||||
cart._driver = player_name
|
||||
cart._start_pos = cart.object:get_pos()
|
||||
|
||||
-- Keep track of player attachment
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
player_meta.attached_to = cart._uuid
|
||||
staticdata.last_player = player_name
|
||||
|
||||
-- Update player information
|
||||
local uuid = staticdata.uuid
|
||||
mcl_player.player_attached[player_name] = true
|
||||
minetest.log("action", player_name.." entered minecart #"..tostring(uuid).." at "..tostring(cart._start_pos))
|
||||
|
||||
-- Attach the player object to the minecart
|
||||
player:set_attach(cart.object, "", vector.new(1,-1.75,-2), vector.new(0,0,0))
|
||||
minetest.after(0.2, function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
mcl_player.player_set_animation(player, "sit" , 30)
|
||||
player:set_eye_offset(vector.new(0,-5.5,0), vector.new(0,-4,0))
|
||||
mcl_title.set(player, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
end
|
||||
end, player_name)
|
||||
end
|
||||
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:minecart",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:minecart",
|
||||
description = S("Minecart"),
|
||||
tt_helop = S("Vehicle for fast travel on rails"),
|
||||
long_descp = S("Minecarts can be used for a quick transportion on rails.") .. "\n" ..
|
||||
S("Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type."),
|
||||
S("You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.") .. "\n" ..
|
||||
S("To obtain the minecart, punch it while holding down the sneak key.") .. "\n" ..
|
||||
S("If it moves over a powered activator rail, you'll get ejected."),
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart.b3d",
|
||||
textures = {"mcl_minecarts_minecart.png"},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_normal.png",
|
||||
drop = {"mcl_minecarts:minecart"},
|
||||
on_rightclick = mod.attach_driver,
|
||||
on_activate_by_rail = activate_normal_minecart,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
-- Grab mob
|
||||
if math.random(1,20) > 15 and not self._passenger then
|
||||
local mobsnear = minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)
|
||||
for n=1, #mobsnear do
|
||||
local mob = mobsnear[n]
|
||||
if mob then
|
||||
local entity = mob:get_luaentity()
|
||||
if entity and entity.is_mob then
|
||||
self._passenger = entity
|
||||
mob:set_attach(self.object, "", PASSENGER_ATTACH_POSITION, vector.zero())
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif self._passenger then
|
||||
local passenger_pos = self._passenger.object:get_pos()
|
||||
if not passenger_pos then
|
||||
self._passenger = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
|
@ -0,0 +1,35 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Minecart with Chest
|
||||
mcl_minecarts.register_minecart({
|
||||
itemstring = "mcl_minecarts:chest_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:chest_minecart",
|
||||
recipe = {
|
||||
{"mcl_chests:chest"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:chest_minecart",
|
||||
description = S("Minecart with Chest"),
|
||||
tt_help = nil,
|
||||
longdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_chest.b3d",
|
||||
textures = {
|
||||
"mcl_chests_normal.png",
|
||||
"mcl_minecarts_minecart.png"
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_chest.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_chests:chest"},
|
||||
groups = { container = 1 },
|
||||
on_rightclick = nil,
|
||||
on_activate_by_rail = nil,
|
||||
creative = true
|
||||
})
|
||||
mcl_entity_invs.register_inv("mcl_minecarts:chest_minecart","Minecart",27,false,true)
|
|
@ -0,0 +1,64 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
function table_metadata(table)
|
||||
return {
|
||||
table = table,
|
||||
set_string = function(self, key, value)
|
||||
--print("set_string("..tostring(key)..", "..tostring(value)..")")
|
||||
self.table[key] = tostring(value)
|
||||
end,
|
||||
get_string = function(self, key)
|
||||
if self.table[key] then
|
||||
return tostring(self.table[key])
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Minecart with Command Block
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:command_block_minecart",
|
||||
entity_id = "mcl_minecarts:command_block_minecart",
|
||||
description = S("Minecart with Command Block"),
|
||||
tt_help = nil,
|
||||
loncdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"jeija_commandblock_off.png^[verticalframe:2:0",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_command_block.png",
|
||||
drop = {"mcl_minecarts:minecart"},
|
||||
on_rightclick = function(self, clicker)
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.handle_rightclick(meta, clicker)
|
||||
end,
|
||||
_mcl_minecarts_on_place = function(self, placer)
|
||||
-- Create a fake metadata object that stores into the cart's staticdata
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.initialize(meta)
|
||||
mesecon.commandblock.place(meta, placer)
|
||||
end,
|
||||
on_activate_by_rail = function(self, timer)
|
||||
self._staticdata.meta = self._staticdata.meta or {}
|
||||
local meta = table_metadata(self._staticdata.meta)
|
||||
|
||||
mesecon.commandblock.action_on(meta, self.object:get_pos())
|
||||
end,
|
||||
creative = true
|
||||
})
|
|
@ -0,0 +1,96 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Minecart with Furnace
|
||||
mcl_minecarts.register_minecart({
|
||||
itemstring = "mcl_minecarts:furnace_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:furnace_minecart",
|
||||
recipe = {
|
||||
{"mcl_furnaces:furnace"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:furnace_minecart",
|
||||
description = S("Minecart with Furnace"),
|
||||
tt_help = nil,
|
||||
longdesc = S("A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel."),
|
||||
usagehelp = S("Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving.") .. "\n" ..
|
||||
S("To obtain the minecart and furnace, punch them while holding down the sneak key."),
|
||||
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_furnace.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
|
||||
on_rightclick = function(self, clicker)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Feed furnace with coal
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
local held = clicker:get_wielded_item()
|
||||
if minetest.get_item_group(held:get_name(), "coal") == 1 then
|
||||
staticdata.fueltime = (staticdata.fueltime or 0) + 180
|
||||
|
||||
-- Trucate to 27 minutes (9 uses)
|
||||
if staticdata.fueltime > 27*60 then
|
||||
staticdata.fuel_time = 27*60
|
||||
end
|
||||
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
held:take_item()
|
||||
local index = clicker:get_wield_index()
|
||||
local inv = clicker:get_inventory()
|
||||
inv:set_stack("main", index, held)
|
||||
end
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front_active.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
end
|
||||
end,
|
||||
on_activate_by_rail = nil,
|
||||
creative = true,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Update furnace stuff
|
||||
if (staticdata.fueltime or 0) > 0 then
|
||||
if staticdata.velocity < 0.25 then
|
||||
staticdata.velocity = 0.25
|
||||
end
|
||||
|
||||
staticdata.fueltime = (staticdata.fueltime or dtime) - dtime
|
||||
if staticdata.fueltime <= 0 then
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_top.png",
|
||||
"default_furnace_front.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
staticdata.fueltime = 0
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,178 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
|
||||
local function mcl_log(message)
|
||||
if LOGGING_ON then
|
||||
mcl_util.mcl_log(message, "[Minecarts]", true)
|
||||
end
|
||||
end
|
||||
|
||||
local function hopper_take_item(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if not self or self.name ~= "mcl_minecarts:hopper_minecart" then return end
|
||||
|
||||
if mcl_util.check_dtime_timer(self, dtime, "hoppermc_take", 0.15) then
|
||||
--minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name)
|
||||
else
|
||||
--minetest.log("The check timer was not triggered")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local above_pos = vector.offset(pos, 0, 0.9, 0)
|
||||
local objs = minetest.get_objects_inside_radius(above_pos, 1.25)
|
||||
|
||||
if objs then
|
||||
mcl_log("there is an itemstring. Number of objs: ".. #objs)
|
||||
|
||||
for k, v in pairs(objs) do
|
||||
local ent = v:get_luaentity()
|
||||
|
||||
if ent and not ent._removed and ent.itemstring and ent.itemstring ~= "" then
|
||||
local taken_items = false
|
||||
|
||||
mcl_log("ent.name: " .. tostring(ent.name))
|
||||
mcl_log("ent pos: " .. tostring(ent.object:get_pos()))
|
||||
|
||||
local inv = mcl_entity_invs.load_inv(self, 5)
|
||||
if not inv then return false end
|
||||
|
||||
local current_itemstack = ItemStack(ent.itemstring)
|
||||
|
||||
mcl_log("inv. size: " .. self._inv_size)
|
||||
if inv:room_for_item("main", current_itemstack) then
|
||||
mcl_log("Room")
|
||||
inv:add_item("main", current_itemstack)
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
taken_items = true
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
if not taken_items then
|
||||
local items_remaining = current_itemstack:get_count()
|
||||
|
||||
-- This will take part of a floating item stack if no slot can hold the full amount
|
||||
for i = 1, self._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Items remaining: " .. items_remaining)
|
||||
mcl_log("Name: " .. tostring(stack:get_name()))
|
||||
|
||||
if current_itemstack:get_name() == stack:get_name() then
|
||||
mcl_log("We have a match. Name: " .. tostring(stack:get_name()))
|
||||
|
||||
local room_for = stack:get_stack_max() - stack:get_count()
|
||||
mcl_log("Room for: " .. tostring(room_for))
|
||||
|
||||
if room_for == 0 then
|
||||
-- Do nothing
|
||||
mcl_log("No room")
|
||||
elseif room_for < items_remaining then
|
||||
mcl_log("We have more items remaining than space")
|
||||
|
||||
items_remaining = items_remaining - room_for
|
||||
stack:set_count(stack:get_stack_max())
|
||||
inv:set_stack("main", i, stack)
|
||||
taken_items = true
|
||||
else
|
||||
local new_stack_size = stack:get_count() + items_remaining
|
||||
stack:set_count(new_stack_size)
|
||||
mcl_log("We have more than enough space. Now holds: " .. new_stack_size)
|
||||
|
||||
inv:set_stack("main", i, stack)
|
||||
items_remaining = 0
|
||||
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
|
||||
taken_items = true
|
||||
break
|
||||
end
|
||||
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
--mcl_log("Is it empty: " .. stack:to_string())
|
||||
end
|
||||
|
||||
if i == self._inv_size and taken_items then
|
||||
mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining)
|
||||
current_itemstack:set_count(items_remaining)
|
||||
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
|
||||
ent.itemstring = current_itemstack:to_string()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--Add in, and delete
|
||||
if taken_items then
|
||||
mcl_log("Saving")
|
||||
mcl_entity_invs.save_inv(ent)
|
||||
return taken_items
|
||||
else
|
||||
mcl_log("No need to save")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Minecart with Hopper
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:hopper_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:hopper_minecart",
|
||||
recipe = {
|
||||
{"mcl_hoppers:hopper"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:hopper_minecart",
|
||||
description = S("Minecart with Hopper"),
|
||||
tt_help = nil,
|
||||
longdesc = nil,
|
||||
usagehelp = nil,
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_hopper.b3d",
|
||||
textures = {
|
||||
"mcl_hoppers_hopper_inside.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
"mcl_hoppers_hopper_outside.png",
|
||||
"mcl_hoppers_hopper_top.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_hopper.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
|
||||
groups = { container = 1 },
|
||||
on_rightclick = nil,
|
||||
on_activate_by_rail = nil,
|
||||
_mcl_minecarts_on_enter = function(self, pos, staticdata)
|
||||
if (staticdata.hopper_delay or 0) > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- try to pull from containers into our inventory
|
||||
if not self then return end
|
||||
local inv = mcl_entity_invs.load_inv(self,5)
|
||||
local above_pos = pos + vector.new(0,1,0)
|
||||
mcl_util.hopper_pull_to_inventory(inv, 'main', above_pos, pos)
|
||||
|
||||
staticdata.hopper_delay = (staticdata.hopper_delay or 0) + (1/20)
|
||||
end,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
hopper_take_item(self, dtime)
|
||||
end,
|
||||
creative = true
|
||||
})
|
||||
mcl_entity_invs.register_inv("mcl_minecarts:hopper_minecart", "Hopper Minecart", 5, false, true)
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local function detonate_tnt_minecart(self)
|
||||
local pos = self.object:get_pos()
|
||||
self.object:remove()
|
||||
mcl_explosions.explode(pos, 6, { drop_chance = 1.0 })
|
||||
end
|
||||
|
||||
local function activate_tnt_minecart(self, timer)
|
||||
if self._boomtimer then
|
||||
return
|
||||
end
|
||||
if timer then
|
||||
self._boomtimer = timer
|
||||
else
|
||||
self._boomtimer = tnt.BOOMTIMER
|
||||
end
|
||||
self.object:set_properties({textures = {
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
self._blinktimer = tnt.BLINKTIMER
|
||||
minetest.sound_play("tnt_ignite", {pos = self.object:get_pos(), gain = 1.0, max_hear_distance = 15}, true)
|
||||
end
|
||||
mod.register_minecart({
|
||||
itemstring = "mcl_minecarts:tnt_minecart",
|
||||
craft = {
|
||||
output = "mcl_minecarts:tnt_minecart",
|
||||
recipe = {
|
||||
{"mcl_tnt:tnt"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
},
|
||||
entity_id = "mcl_minecarts:tnt_minecart",
|
||||
description = S("Minecart with TNT"),
|
||||
tt_help = S("Vehicle for fast travel on rails").."\n"..S("Can be ignited by tools or powered activator rail"),
|
||||
longdesc = S("A minecart with TNT is an explosive vehicle that travels on rail."),
|
||||
usagehelp = S("Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail.") .. "\n" ..
|
||||
S("To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited."),
|
||||
initial_properties = {
|
||||
mesh = "mcl_minecarts_minecart_block.b3d",
|
||||
textures = {
|
||||
"default_tnt_top.png",
|
||||
"default_tnt_bottom.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
},
|
||||
},
|
||||
icon = "mcl_minecarts_minecart_tnt.png",
|
||||
drop = {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
|
||||
on_rightclick = function(self, clicker)
|
||||
-- Ingite
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
if self._boomtimer then
|
||||
return
|
||||
end
|
||||
local held = clicker:get_wielded_item()
|
||||
if held:get_name() == "mcl_fire:flint_and_steel" then
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
held:add_wear(65535/65) -- 65 uses
|
||||
local index = clicker:get_wield_index()
|
||||
local inv = clicker:get_inventory()
|
||||
inv:set_stack("main", index, held)
|
||||
end
|
||||
activate_tnt_minecart(self)
|
||||
end
|
||||
end,
|
||||
on_activate_by_rail = activate_tnt_minecart,
|
||||
creative = true,
|
||||
_mcl_minecarts_on_step = function(self, dtime)
|
||||
-- Impacts reduce the speed greatly. Use this to trigger explosions
|
||||
local current_speed = vector.length(self.object:get_velocity())
|
||||
if current_speed < (self._old_speed or 0) - 6 then
|
||||
detonate_tnt_minecart(self)
|
||||
end
|
||||
self._old_speed = current_speed
|
||||
|
||||
if self._boomtimer then
|
||||
-- Explode
|
||||
self._boomtimer = self._boomtimer - dtime
|
||||
if self._boomtimer <= 0 then
|
||||
detonate_tnt_minecart(self)
|
||||
return
|
||||
else
|
||||
tnt.smoke_step(pos)
|
||||
end
|
||||
end
|
||||
|
||||
if self._blinktimer then
|
||||
self._blinktimer = self._blinktimer - dtime
|
||||
if self._blinktimer <= 0 then
|
||||
self._blink = not self._blink
|
||||
if self._blink then
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"default_tnt_top.png",
|
||||
"default_tnt_bottom.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"default_tnt_side.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
else
|
||||
self.object:set_properties({textures =
|
||||
{
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_tnt_blink.png",
|
||||
"mcl_minecarts_minecart.png",
|
||||
}})
|
||||
end
|
||||
self._blinktimer = tnt.BLINKTIMER
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -1,4 +1,33 @@
|
|||
local vector = vector
|
||||
local mod = mcl_minecarts
|
||||
local table_merge = mcl_util.table_merge
|
||||
|
||||
function get_path(base, first, ...)
|
||||
if not first then return base end
|
||||
if not base then return end
|
||||
return get_path(base[first], ...)
|
||||
end
|
||||
local function force_get_node(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "ignore" then return node end
|
||||
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos, pos)
|
||||
local area = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax,
|
||||
}
|
||||
local data = vm:get_data()
|
||||
local param_data = vm:get_light_data()
|
||||
local param2_data = vm:get_param2_data()
|
||||
|
||||
local vi = area:indexp(pos)
|
||||
return {
|
||||
name = minetest.get_name_from_content_id(data[vi]),
|
||||
param = param_data[vi],
|
||||
param2 = param2_data[vi]
|
||||
}
|
||||
end
|
||||
|
||||
function mcl_minecarts:get_sign(z)
|
||||
if z == 0 then
|
||||
|
@ -10,158 +39,356 @@ end
|
|||
|
||||
function mcl_minecarts:velocity_to_dir(v)
|
||||
if math.abs(v.x) > math.abs(v.z) then
|
||||
return {x=mcl_minecarts:get_sign(v.x), y=mcl_minecarts:get_sign(v.y), z=0}
|
||||
return vector.new(
|
||||
mcl_minecarts:get_sign(v.x),
|
||||
mcl_minecarts:get_sign(v.y),
|
||||
0
|
||||
)
|
||||
else
|
||||
return {x=0, y=mcl_minecarts:get_sign(v.y), z=mcl_minecarts:get_sign(v.z)}
|
||||
return vector.new(
|
||||
0,
|
||||
mcl_minecarts:get_sign(v.y),
|
||||
mcl_minecarts:get_sign(v.z)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_minecarts:is_rail(pos, railtype)
|
||||
local node = minetest.get_node(pos).name
|
||||
if node == "ignore" then
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local emin, emax = vm:read_from_map(pos, pos)
|
||||
local area = VoxelArea:new{
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax,
|
||||
}
|
||||
local data = vm:get_data()
|
||||
local vi = area:indexp(pos)
|
||||
node = minetest.get_name_from_content_id(data[vi])
|
||||
end
|
||||
if minetest.get_item_group(node, "rail") == 0 then
|
||||
local node_name = force_get_node(pos).name
|
||||
|
||||
if minetest.get_item_group(node_name, "rail") == 0 then
|
||||
return false
|
||||
end
|
||||
if not railtype then
|
||||
return true
|
||||
end
|
||||
return minetest.get_item_group(node, "connect_to_raillike") == railtype
|
||||
return minetest.get_item_group(node_name, "connect_to_raillike") == railtype
|
||||
end
|
||||
|
||||
function mcl_minecarts:check_front_up_down(pos, dir_, check_down, railtype)
|
||||
local dir = vector.new(dir_)
|
||||
-- Front
|
||||
dir.y = 0
|
||||
local cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
end
|
||||
-- Up
|
||||
if check_down then
|
||||
dir.y = 1
|
||||
cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
-- Directional constants
|
||||
local north = vector.new( 0, 0, 1); local N = 1 -- 4dir = 0
|
||||
local east = vector.new( 1, 0, 0); local E = 4 -- 4dir = 1
|
||||
local south = vector.new( 0, 0,-1); local S = 2 -- 4dir = 2 Note: S is overwritten below with the translator
|
||||
local west = vector.new(-1, 0, 0); local W = 8 -- 4dir = 3
|
||||
|
||||
-- Share. Consider moving this to some shared location
|
||||
mod.north = north
|
||||
mod.south = south
|
||||
mod.east = east
|
||||
mod.west = west
|
||||
|
||||
--[[
|
||||
mcl_minecarts.snap_direction(dir)
|
||||
|
||||
returns a valid cart direction that has the smallest angle difference to `dir'
|
||||
]]
|
||||
local VALID_DIRECTIONS = {
|
||||
north, vector.offset(north, 0, 1, 0), vector.offset(north, 0, -1, 0),
|
||||
south, vector.offset(south, 0, 1, 0), vector.offset(south, 0, -1, 0),
|
||||
east, vector.offset(east, 0, 1, 0), vector.offset(east, 0, -1, 0),
|
||||
west, vector.offset(west, 0, 1, 0), vector.offset(west, 0, -1, 0),
|
||||
}
|
||||
function mod.snap_direction(dir)
|
||||
dir = vector.normalize(dir)
|
||||
local best = nil
|
||||
local diff = -1
|
||||
for _,d in pairs(VALID_DIRECTIONS) do
|
||||
local dot = vector.dot(dir,d)
|
||||
if dot > diff then
|
||||
best = d
|
||||
diff = dot
|
||||
end
|
||||
end
|
||||
-- Down
|
||||
dir.y = -1
|
||||
cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
end
|
||||
return nil
|
||||
return best
|
||||
end
|
||||
|
||||
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
||||
local pos = vector.round(pos_)
|
||||
local cur
|
||||
local left_check, right_check = true, true
|
||||
local CONNECTIONS = { north, south, east, west }
|
||||
local HORIZONTAL_STANDARD_RULES = {
|
||||
[N] = { "", 0, mask = N, score = 1, can_slope = true },
|
||||
[S] = { "", 0, mask = S, score = 1, can_slope = true },
|
||||
[N+S] = { "", 0, mask = N+S, score = 2, can_slope = true },
|
||||
|
||||
-- Check left and right
|
||||
local left = {x=0, y=0, z=0}
|
||||
local right = {x=0, y=0, z=0}
|
||||
if dir.z ~= 0 and dir.x == 0 then
|
||||
left.x = -dir.z
|
||||
right.x = dir.z
|
||||
elseif dir.x ~= 0 and dir.z == 0 then
|
||||
left.z = dir.x
|
||||
right.z = -dir.x
|
||||
end
|
||||
[E] = { "", 1, mask = E, score = 1, can_slope = true },
|
||||
[W] = { "", 1, mask = W, score = 1, can_slope = true },
|
||||
[E+W] = { "", 1, mask = E+W, score = 2, can_slope = true },
|
||||
}
|
||||
mod.HORIZONTAL_STANDARD_RULES = HORIZONTAL_STANDARD_RULES
|
||||
|
||||
if ctrl then
|
||||
if old_switch == 1 then
|
||||
left_check = false
|
||||
elseif old_switch == 2 then
|
||||
right_check = false
|
||||
end
|
||||
if ctrl.left and left_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
|
||||
if cur then
|
||||
return cur, 1
|
||||
end
|
||||
left_check = false
|
||||
end
|
||||
if ctrl.right and right_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
|
||||
if cur then
|
||||
return cur, 2
|
||||
end
|
||||
right_check = true
|
||||
end
|
||||
end
|
||||
local HORIZONTAL_CURVES_RULES = {
|
||||
[N+E] = { "_corner", 3, name = "ne corner", mask = N+E, score = 3 },
|
||||
[N+W] = { "_corner", 2, name = "nw corner", mask = N+W, score = 3 },
|
||||
[S+E] = { "_corner", 0, name = "se corner", mask = S+E, score = 3 },
|
||||
[S+W] = { "_corner", 1, name = "sw corner", mask = S+W, score = 3 },
|
||||
|
||||
-- Normal
|
||||
cur = mcl_minecarts:check_front_up_down(pos, dir, true, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
[N+E+W] = { "_tee_off", 3, mask = N+E+W, score = 4 },
|
||||
[S+E+W] = { "_tee_off", 1, mask = S+E+W, score = 4 },
|
||||
[N+S+E] = { "_tee_off", 0, mask = N+S+E, score = 4 },
|
||||
[N+S+W] = { "_tee_off", 2, mask = N+S+W, score = 4 },
|
||||
|
||||
-- Left, if not already checked
|
||||
if left_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, left, false, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
[N+S+E+W] = { "_cross", 0, mask = N+S+E+W, score = 5 },
|
||||
}
|
||||
table_merge(HORIZONTAL_CURVES_RULES, HORIZONTAL_STANDARD_RULES)
|
||||
mod.HORIZONTAL_CURVES_RULES = HORIZONTAL_CURVES_RULES
|
||||
|
||||
-- Right, if not already checked
|
||||
if right_check then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, right, false, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
-- Backwards
|
||||
if not old_switch then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, {
|
||||
x = -dir.x,
|
||||
y = dir.y,
|
||||
z = -dir.z
|
||||
}, true, railtype)
|
||||
if cur then
|
||||
return cur
|
||||
end
|
||||
end
|
||||
return {x=0, y=0, z=0}
|
||||
end
|
||||
|
||||
local plane_adjacents = {
|
||||
vector.new(-1,0,0),
|
||||
vector.new(1,0,0),
|
||||
vector.new(0,0,-1),
|
||||
vector.new(0,0,1),
|
||||
local HORIZONTAL_RULES_BY_RAIL_GROUP = {
|
||||
[1] = HORIZONTAL_STANDARD_RULES,
|
||||
[2] = HORIZONTAL_CURVES_RULES,
|
||||
}
|
||||
|
||||
function mcl_minecarts:get_start_direction(pos)
|
||||
local dir
|
||||
local i = 0
|
||||
while (not dir and i < #plane_adjacents) do
|
||||
i = i+1
|
||||
local node = minetest.get_node_or_nil(vector.add(pos, plane_adjacents[i]))
|
||||
if node ~= nil
|
||||
and minetest.get_item_group(node.name, "rail") == 0
|
||||
and minetest.get_item_group(node.name, "solid") == 1
|
||||
and minetest.get_item_group(node.name, "opaque") == 1
|
||||
then
|
||||
dir = mcl_minecarts:check_front_up_down(pos, vector.multiply(plane_adjacents[i], -1), true)
|
||||
end
|
||||
local function check_connection_rule(pos, connections, rule)
|
||||
-- All bits in the mask must be set for the connection to be possible
|
||||
if bit.band(rule.mask,connections) ~= rule.mask then
|
||||
--print("Mask mismatch ("..tostring(rule.mask)..","..tostring(connections)..")")
|
||||
return false
|
||||
end
|
||||
return dir
|
||||
|
||||
-- If there is an allow filter, that mush also return true
|
||||
if rule.allow and rule.allow(rule, connections, pos) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mcl_minecarts:set_velocity(obj, dir, factor)
|
||||
obj._velocity = vector.multiply(dir, factor or 3)
|
||||
obj._old_pos = nil
|
||||
obj._punched = true
|
||||
local function make_sloped_if_straight(pos, dir)
|
||||
local node = minetest.get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
local param2 = 0
|
||||
if dir == east then
|
||||
param2 = 3
|
||||
elseif dir == west then
|
||||
param2 = 1
|
||||
elseif dir == north then
|
||||
param2 = 2
|
||||
elseif dir == south then
|
||||
param2 = 0
|
||||
end
|
||||
|
||||
if get_path( nodedef, "_mcl_minecarts", "railtype" ) == "straight" then
|
||||
minetest.swap_node(pos, {name = nodedef._mcl_minecarts.base_name .. "_sloped", param2 = param2})
|
||||
end
|
||||
end
|
||||
|
||||
local function is_connection(pos, dir)
|
||||
local node = force_get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
local get_next_dir = get_path(nodedef, "_mcl_minecarts", "get_next_dir")
|
||||
if not get_next_dir then return end
|
||||
|
||||
local next_dir = get_next_dir(pos, dir, node)
|
||||
next_dir.y = 0
|
||||
return vector.equals(next_dir, dir)
|
||||
end
|
||||
|
||||
local function get_rail_connections(pos, opt)
|
||||
local legacy = opt and opt.legacy
|
||||
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
||||
|
||||
local connections = 0
|
||||
for i = 1,#CONNECTIONS do
|
||||
dir = CONNECTIONS[i]
|
||||
local neighbor = vector.add(pos, dir)
|
||||
local node = force_get_node(neighbor)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
|
||||
-- Only allow connections to the open ends of rails, as decribed by get_next_dir
|
||||
if mcl_minecarts:is_rail(neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
|
||||
local rev_dir = vector.direction(dir,vector.zero())
|
||||
if ignore_neighbor_connections or is_connection(neighbor, rev_dir) then
|
||||
connections = bit.bor(connections, bit.lshift(1,i - 1))
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for sloped rail one block down
|
||||
local below_neighbor = vector.offset(neighbor, 0, -1, 0)
|
||||
local node = force_get_node(below_neighbor)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
if mcl_minecarts:is_rail(below_neighbor) and ( legacy or get_path(nodedef, "_mcl_minecarts", "get_next_dir" ) ) then
|
||||
local rev_dir = vector.direction(dir, vector.zero())
|
||||
if ignore_neighbor_connections or is_connection(below_neighbor, rev_dir) then
|
||||
connections = bit.bor(connections, bit.lshift(1,i - 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
return connections
|
||||
end
|
||||
mod.get_rail_connections = get_rail_connections
|
||||
|
||||
local function update_rail_connections(pos, opt)
|
||||
local ignore_neighbor_connections = opt and opt.ignore_neighbor_connections
|
||||
|
||||
local node = minetest.get_node(pos)
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
if not nodedef or not nodedef._mcl_minecarts then
|
||||
minetest.log("warning", "attemting to rail connect to "..node.name)
|
||||
return
|
||||
end
|
||||
|
||||
-- Get the mappings to use
|
||||
local rules = HORIZONTAL_RULES_BY_RAIL_GROUP[nodedef.groups.rail]
|
||||
if nodedef._mcl_minecarts and nodedef._mcl_minecarts.connection_rules then -- Custom connection rules
|
||||
rules = nodedef._mcl_minecarts.connection_rules
|
||||
end
|
||||
if not rules then return end
|
||||
|
||||
-- Horizontal rules, Check for rails on each neighbor
|
||||
local connections = get_rail_connections(pos, opt)
|
||||
|
||||
-- Check for rasing rails to slopes
|
||||
for i = 1,#CONNECTIONS do
|
||||
local dir = CONNECTIONS[i]
|
||||
local neighbor = vector.add(pos, dir)
|
||||
make_sloped_if_straight( vector.offset(neighbor, 0, -1, 0), dir )
|
||||
end
|
||||
|
||||
-- Select the best allowed connection
|
||||
local rule = nil
|
||||
local score = 0
|
||||
for k,r in pairs(rules) do
|
||||
if check_connection_rule(pos, connections, r) then
|
||||
if r.score > score then
|
||||
--print("Best rule so far is "..dump(r))
|
||||
score = r.score
|
||||
rule = r
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if rule then
|
||||
-- Apply the mapping
|
||||
local new_name = nodedef._mcl_minecarts.base_name..rule[1]
|
||||
if new_name ~= node.name or node.param2 ~= rule[2] then
|
||||
--print("swapping "..node.name.." for "..new_name..","..tostring(rule[2]).." at "..tostring(pos))
|
||||
node.name = new_name
|
||||
node.param2 = rule[2]
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
if rule.after then
|
||||
rule.after(rule, pos, connections)
|
||||
end
|
||||
end
|
||||
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if get_path(node_def, "_mcl_minecarts", "can_slope") then
|
||||
for i=1,#CONNECTIONS do
|
||||
local dir = CONNECTIONS[i]
|
||||
local higher_rail_pos = vector.offset(pos,dir.x,1,dir.z)
|
||||
local rev_dir = vector.direction(dir,vector.new(0,0,0))
|
||||
if mcl_minecarts:is_rail(higher_rail_pos) and is_connection(higher_rail_pos, rev_dir) then
|
||||
make_sloped_if_straight(pos, rev_dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
mod.update_rail_connections = update_rail_connections
|
||||
|
||||
local north = vector.new(0,0,1)
|
||||
local south = vector.new(0,0,-1)
|
||||
local east = vector.new(1,0,0)
|
||||
local west = vector.new(-1,0,0)
|
||||
|
||||
local function is_ahead_slope(pos, dir)
|
||||
local ahead = vector.add(pos,dir)
|
||||
if mcl_minecarts:is_rail(ahead) then return false end
|
||||
|
||||
local below = vector.offset(ahead,0,-1,0)
|
||||
if not mcl_minecarts:is_rail(below) then return false end
|
||||
|
||||
local node_name = force_get_node(below).name
|
||||
return minetest.get_item_group(node_name, "rail_slope") ~= 0
|
||||
end
|
||||
function mcl_minecarts:get_rail_direction(pos_, dir)
|
||||
local pos = vector.round(pos_)
|
||||
|
||||
-- Handle new track types that have track-specific direction handler
|
||||
local node = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
local get_next_dir = get_path(node_def,"_mcl_minecarts","get_next_dir")
|
||||
if not get_next_dir then return dir end
|
||||
|
||||
dir = node_def._mcl_minecarts.get_next_dir(pos, dir, node)
|
||||
|
||||
-- Handle reversing if there is a solid block in the next position
|
||||
local next_pos = vector.add(pos, dir)
|
||||
local next_node = minetest.get_node(next_pos)
|
||||
local node_def = minetest.registered_nodes[next_node.name]
|
||||
if node_def and node_def.groups and ( node_def.groups.solid or node_def.groups.stair ) then
|
||||
-- Reverse the direction without giving -0 members
|
||||
dir = vector.direction(next_pos, pos)
|
||||
end
|
||||
|
||||
-- Handle going downhill
|
||||
if is_ahead_slope(pos,dir) then
|
||||
dir = vector.offset(dir,0,-1,0)
|
||||
end
|
||||
|
||||
return dir
|
||||
end
|
||||
function mod.update_cart_orientation(self)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- constants
|
||||
local _2_pi = math.pi * 2
|
||||
local pi = math.pi
|
||||
local dir = staticdata.dir
|
||||
|
||||
-- Calculate an angle from the x,z direction components
|
||||
local rot_y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
|
||||
if rot_y < 0 then
|
||||
rot_y = rot_y + _2_pi
|
||||
end
|
||||
|
||||
-- Check if the rotation is a 180 flip and don't change if so
|
||||
local rot = self.object:get_rotation()
|
||||
local diff = math.abs((rot_y - ( rot.y + pi ) % _2_pi) )
|
||||
if diff < 0.001 or diff > _2_pi - 0.001 then
|
||||
-- Update rotation adjust and recalculate the rotation
|
||||
staticdata.rot_adjust = ( ( staticdata.rot_adjust or 0 ) + pi ) % _2_pi
|
||||
rot.y = math.atan2( dir.x, dir.z ) + ( staticdata.rot_adjust or 0 )
|
||||
else
|
||||
rot.y = rot_y
|
||||
end
|
||||
|
||||
-- Forward/backwards tilt (pitch)
|
||||
if dir.y < 0 then
|
||||
rot.x = -0.25 * pi
|
||||
elseif dir.y > 0 then
|
||||
rot.x = 0.25 * pi
|
||||
else
|
||||
rot.x = 0
|
||||
end
|
||||
|
||||
if ( staticdata.rot_adjust or 0 ) < 0.01 then
|
||||
rot.x = -rot.x
|
||||
end
|
||||
if dir.z ~= 0 then
|
||||
rot.x = -rot.x
|
||||
end
|
||||
|
||||
self.object:set_rotation(rot)
|
||||
end
|
||||
|
||||
function mod.get_cart_position(cart_staticdata)
|
||||
local data = cart_staticdata
|
||||
if not data then return nil end
|
||||
if not data.connected_at then return nil end
|
||||
|
||||
return vector.add(data.connected_at, vector.multiply(data.dir or vector.zero(), data.distance or 0))
|
||||
end
|
||||
|
||||
function mod.reverse_cart_direction(staticdata)
|
||||
|
||||
-- Complete moving thru this block into the next, reverse direction, and put us back at the same position we were at
|
||||
local next_dir = -staticdata.dir
|
||||
staticdata.connected_at = staticdata.connected_at + staticdata.dir
|
||||
staticdata.distance = 1 - (staticdata.distance or 0)
|
||||
|
||||
-- recalculate direction
|
||||
local next_dir,_ = mod:get_rail_direction(staticdata.connected_at, next_dir)
|
||||
staticdata.dir = next_dir
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name = mcl_minecarts
|
||||
author = Krock
|
||||
description = Minecarts are vehicles to move players quickly on rails.
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
|
||||
optional_depends = doc_identifier, mcl_wip
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_util, mcl_sounds, mcl_player, mcl_playerinfo, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons, mcl_entity_invs
|
||||
optional_depends = doc_identifier, mcl_wip, mcl_physics
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# hand-made Wavefront .OBJ file for sloped rail
|
||||
mtllib mcl_minecarts_rail.mtl
|
||||
o flat_track.001
|
||||
v -0.500000 -0.437500 -0.500000
|
||||
v -0.500000 -0.437500 0.500000
|
||||
v 0.500000 -0.437500 0.500000
|
||||
v 0.500000 -0.437500 -0.500000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vn 0.000000 1.000000 0.000000
|
||||
usemtl None
|
||||
s off
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
|
@ -0,0 +1,15 @@
|
|||
# hand-made Wavefront .OBJ file for sloped rail
|
||||
mtllib mcl_minecarts_rail.mtl
|
||||
o sloped_rail.001
|
||||
v -0.500000 -0.437500 -0.500000
|
||||
v -0.500000 0.562500 0.500000
|
||||
v 0.500000 0.562500 0.500000
|
||||
v 0.500000 -0.437500 -0.500000
|
||||
vt 1.000000 0.000000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vn 0.707106 0.707106 0.000000
|
||||
usemtl None
|
||||
s off
|
||||
f 1/1/1 2/2/1 3/3/1 4/4/1
|
|
@ -0,0 +1,543 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
-- Constants
|
||||
local mcl_debug,DEBUG = mcl_util.make_mcl_logger("mcl_logging_minecart_debug", "Minecart Debug")
|
||||
--DEBUG = false
|
||||
--mcl_debug = function(msg) print(msg) end
|
||||
|
||||
-- Imports
|
||||
local FRICTION = mcl_minecarts.FRICTION
|
||||
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
|
||||
local SPEED_MAX = mod.SPEED_MAX
|
||||
local train_length = mod.train_length
|
||||
local update_train = mod.update_train
|
||||
local reverse_train = mod.reverse_train
|
||||
local link_cart_ahead = mod.link_cart_ahead
|
||||
local update_cart_orientation = mod.update_cart_orientation
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local get_cart_position = mod.get_cart_position
|
||||
|
||||
local function detach_minecart(staticdata)
|
||||
staticdata.connected_at = nil
|
||||
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
if luaentity then
|
||||
luaentity.object:set_velocity(staticdata.dir * staticdata.velocity)
|
||||
end
|
||||
end
|
||||
mod.detach_minecart = detach_minecart
|
||||
|
||||
local function try_detach_minecart(staticdata)
|
||||
if not staticdata or not staticdata.connected_at then return end
|
||||
if not mod:is_rail(staticdata.connected_at) then
|
||||
mcl_debug("Detaching minecart #"..tostring(staticdata.uuid))
|
||||
detach_minecart(staticdata)
|
||||
end
|
||||
end
|
||||
|
||||
local function reverse_direction(staticdata)
|
||||
if staticdata.behind or staticdata.ahead then
|
||||
reverse_train(staticdata)
|
||||
return
|
||||
end
|
||||
|
||||
mod.reverse_cart_direction(staticdata)
|
||||
end
|
||||
mod.reverse_direction = reverse_direction
|
||||
|
||||
|
||||
--[[
|
||||
Array of hooks { {u,v,w}, name }
|
||||
Actual position is pos + u * dir + v * right + w * up
|
||||
]]
|
||||
local enter_exit_checks = {
|
||||
{ 0, 0, 0, "" },
|
||||
{ 0, 0, 1, "_above" },
|
||||
{ 0, 0,-1, "_below" },
|
||||
{ 0, 1, 0, "_side" },
|
||||
{ 0,-1, 0, "_side" },
|
||||
}
|
||||
|
||||
local function handle_cart_enter_exit(staticdata, pos, next_dir, event)
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
local dir = staticdata.dir
|
||||
local right = vector.new( dir.z, dir.y, -dir.x)
|
||||
local up = vector.new(0,1,0)
|
||||
for i=1,#enter_exit_checks do
|
||||
local check = enter_exit_checks[i]
|
||||
|
||||
local check_pos = pos + dir * check[1] + right * check[2] + up * check[3]
|
||||
local node = minetest.get_node(check_pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def then
|
||||
-- node-specific hook
|
||||
local hook_name = "_mcl_minecarts_"..event..check[4]
|
||||
local hook = node_def[hook_name]
|
||||
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata) end
|
||||
|
||||
-- global minecart hook
|
||||
hook = mcl_minecarts[event..check[4]]
|
||||
if hook then hook(check_pos, luaentity, next_dir, pos, staticdata, node_def) end
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle cart-specific behaviors
|
||||
if luaentity then
|
||||
local hook = luaentity["_mcl_minecarts_"..event]
|
||||
if hook then hook(luaentity, pos, staticdata) end
|
||||
else
|
||||
--minetest.log("warning", "TODO: change _mcl_minecarts_"..event.." calling so it is not dependent on the existence of a luaentity")
|
||||
end
|
||||
end
|
||||
local function set_metadata_cart_status(pos, uuid, state)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||
carts[uuid] = state
|
||||
meta:set_string("_mcl_minecarts_carts", minetest.serialize(carts))
|
||||
end
|
||||
local function handle_cart_enter(staticdata, pos, next_dir)
|
||||
--print("entering "..tostring(pos))
|
||||
set_metadata_cart_status(pos, staticdata.uuid, 1)
|
||||
handle_cart_enter_exit(staticdata, pos, next_dir, "on_enter" )
|
||||
end
|
||||
local function handle_cart_leave(staticdata, pos, next_dir)
|
||||
--print("leaving "..tostring(pos))
|
||||
set_metadata_cart_status(pos, staticdata.uuid, nil)
|
||||
handle_cart_enter_exit(staticdata, pos, next_dir, "on_leave" )
|
||||
end
|
||||
local function handle_cart_node_watches(staticdata, dtime)
|
||||
local watches = staticdata.node_watches or {}
|
||||
local new_watches = {}
|
||||
local luaentity = mcl_util.get_luaentity_from_uuid(staticdata.uuid)
|
||||
for i=1,#watches do
|
||||
local node_pos = watches[i]
|
||||
local node = minetest.get_node(node_pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def then
|
||||
local hook = node_def._mcl_minecarts_node_on_step
|
||||
if hook and hook(node_pos, luaentity, dtime, staticdata) then
|
||||
new_watches[#new_watches+1] = node_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
staticdata.node_watches = new_watches
|
||||
end
|
||||
|
||||
local function handle_cart_collision(cart1_staticdata, prev_pos, next_dir)
|
||||
if not cart1_staticdata then return end
|
||||
|
||||
-- Look ahead one block
|
||||
local pos = vector.add(prev_pos, next_dir)
|
||||
|
||||
local meta = minetest.get_meta(pos)
|
||||
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||
local cart_uuid = nil
|
||||
local dirty = false
|
||||
for uuid,v in pairs(carts) do
|
||||
-- Clean up dead carts
|
||||
local data = get_cart_data(uuid)
|
||||
if not data or not data.connected_at then
|
||||
carts[uuid] = nil
|
||||
dirty = true
|
||||
uuid = nil
|
||||
end
|
||||
|
||||
if uuid and uuid ~= cart1_staticdata.uuid then cart_uuid = uuid end
|
||||
end
|
||||
if dirty then
|
||||
meta:set_string("_mcl_minecarts_carts",minetest.serialize(carts))
|
||||
end
|
||||
|
||||
local meta = minetest.get_meta(vector.add(pos,next_dir))
|
||||
if not cart_uuid then return end
|
||||
|
||||
-- Don't collide with the train car in front of you
|
||||
if cart1_staticdata.ahead == cart_uuid then return end
|
||||
|
||||
minetest.log("action","cart #"..cart1_staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos))
|
||||
|
||||
-- Standard Collision Handling
|
||||
local cart2_staticdata = get_cart_data(cart_uuid)
|
||||
|
||||
local u1 = cart1_staticdata.velocity
|
||||
local u2 = cart2_staticdata.velocity
|
||||
local m1 = cart1_staticdata.mass
|
||||
local m2 = cart2_staticdata.mass
|
||||
|
||||
--print("u1="..tostring(u1)..",u2="..tostring(u2))
|
||||
if u2 == 0 and u1 < 4 and train_length(cart1_staticdata) < MAX_TRAIN_LENGTH then
|
||||
link_cart_ahead(cart1_staticdata, cart2_staticdata)
|
||||
cart2_staticdata.dir = mcl_minecarts:get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
|
||||
cart2_staticdata.velocity = cart1_staticdata.velocity
|
||||
return
|
||||
end
|
||||
|
||||
-- Calculate new velocities according to https://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian
|
||||
local c1 = m1 + m2
|
||||
local d = m1 - m2
|
||||
local v1 = ( d * u1 + 2 * m2 * u2 ) / c1
|
||||
local v2 = ( 2 * m1 * u1 + d * u2 ) / c1
|
||||
|
||||
cart1_staticdata.velocity = v1
|
||||
cart2_staticdata.velocity = v2
|
||||
|
||||
-- Force the other cart to move the same direction this one was
|
||||
cart2_staticdata.dir = mcl_minecarts:get_rail_direction(cart2_staticdata.connected_at, cart1_staticdata.dir)
|
||||
end
|
||||
|
||||
local function vector_away_from_players(cart, staticdata)
|
||||
local function player_repel(obj)
|
||||
-- Only repel from players
|
||||
local player_name = obj:get_player_name()
|
||||
if not player_name or player_name == "" then return false end
|
||||
|
||||
-- Don't repel away from players in minecarts
|
||||
local player_meta = mcl_playerinfo.get_mod_meta(player_name, modname)
|
||||
if player_meta.attached_to then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Get the cart position
|
||||
local cart_pos = mod.get_cart_position(staticdata)
|
||||
if cart then cart_pos = cart.object:get_pos() end
|
||||
if not cart_pos then return nil end
|
||||
|
||||
for _,obj in pairs(minetest.get_objects_inside_radius(cart_pos, 1.1)) do
|
||||
if player_repel(obj) then
|
||||
return obj:get_pos() - cart_pos
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function direction_away_from_players(staticdata)
|
||||
local diff = vector_away_from_players(nil,staticdata)
|
||||
if not diff then return 0 end
|
||||
|
||||
local length = vector.distance(vector.new(0,0,0),diff)
|
||||
local vec = diff / length
|
||||
local force = vector.dot( vec, vector.normalize(staticdata.dir) )
|
||||
|
||||
-- Check if this would push past the end of the track and don't move it it would
|
||||
-- This prevents an oscillation that would otherwise occur
|
||||
local dir = staticdata.dir
|
||||
if force > 0 then
|
||||
dir = -dir
|
||||
end
|
||||
if mcl_minecarts:is_rail( staticdata.connected_at + dir ) then
|
||||
if force > 0.5 then
|
||||
return -length * 4
|
||||
elseif force < -0.5 then
|
||||
return length * 4
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function calculate_acceleration(staticdata)
|
||||
local acceleration = 0
|
||||
|
||||
-- Fix up movement data
|
||||
staticdata.velocity = staticdata.velocity or 0
|
||||
|
||||
-- Apply friction if moving
|
||||
if staticdata.velocity > 0 then
|
||||
acceleration = -FRICTION
|
||||
end
|
||||
|
||||
local pos = staticdata.connected_at
|
||||
local node_name = minetest.get_node(pos).name
|
||||
local node_def = minetest.registered_nodes[node_name]
|
||||
|
||||
local ctrl = staticdata.controls or {}
|
||||
local time_active = minetest.get_gametime() - 0.25
|
||||
|
||||
if (ctrl.forward or 0) > time_active then
|
||||
if staticdata.velocity == 0 then
|
||||
local look_dir = minetest.facedir_to_dir(ctrl.look or 0)
|
||||
local dot = vector.dot(staticdata.dir, look_dir)
|
||||
if dot < 0 then
|
||||
reverse_direction(staticdata)
|
||||
end
|
||||
end
|
||||
acceleration = 4
|
||||
elseif (ctrl.brake or 0) > time_active then
|
||||
acceleration = -1.5
|
||||
elseif ctrl.impulse then
|
||||
acceleration = vector.dot(staticdata.dir, ctrl.impulse)
|
||||
ctrl.impulse = nil
|
||||
elseif (staticdata.fueltime or 0) > 0 and staticdata.velocity <= 4 then
|
||||
acceleration = 0.6
|
||||
elseif staticdata.velocity >= ( node_def._max_acceleration_velocity or SPEED_MAX ) then
|
||||
-- Standard friction
|
||||
elseif node_def and node_def._rail_acceleration then
|
||||
acceleration = node_def._rail_acceleration * 4
|
||||
end
|
||||
|
||||
-- Factor in gravity after everything else
|
||||
local gravity_strength = 2.45 --friction * 5
|
||||
if staticdata.dir.y < 0 then
|
||||
acceleration = gravity_strength - FRICTION
|
||||
elseif staticdata.dir.y > 0 then
|
||||
acceleration = -gravity_strength + FRICTION
|
||||
end
|
||||
|
||||
return acceleration
|
||||
end
|
||||
|
||||
local function do_movement_step(staticdata, dtime)
|
||||
if not staticdata.connected_at then return 0 end
|
||||
|
||||
-- Calculate timestep remaiing in this block
|
||||
local x_0 = staticdata.distance or 0
|
||||
local remaining_in_block = 1 - x_0
|
||||
local a = calculate_acceleration(staticdata)
|
||||
local v_0 = staticdata.velocity
|
||||
|
||||
-- Repel minecarts
|
||||
local away = direction_away_from_players(staticdata)
|
||||
if away > 0 then
|
||||
v_0 = away
|
||||
elseif away < 0 then
|
||||
reverse_direction(staticdata)
|
||||
v_0 = -away
|
||||
end
|
||||
|
||||
if DEBUG and ( v_0 > 0 or a ~= 0 ) then
|
||||
mcl_debug(" cart "..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",dtime="..tostring(dtime)..
|
||||
",timestep="..tostring(timestep)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Not moving
|
||||
if a == 0 and v_0 == 0 then return 0 end
|
||||
|
||||
-- Movement equation with acceleration: x_1 = x_0 + v_0 * t + 0.5 * a * t*t
|
||||
local timestep
|
||||
local stops_in_block = false
|
||||
local inside = v_0 * v_0 + 2 * a * remaining_in_block
|
||||
if inside < 0 then
|
||||
-- Would stop or reverse direction inside this block, calculate time to v_1 = 0
|
||||
timestep = -v_0 / a
|
||||
stops_in_block = true
|
||||
elseif a ~= 0 then
|
||||
-- Setting x_1 = x_0 + remaining_in_block, and solving for t gives:
|
||||
timestep = ( math.sqrt( v_0 * v_0 + 2 * a * remaining_in_block) - v_0 ) / a
|
||||
else
|
||||
timestep = remaining_in_block / v_0
|
||||
end
|
||||
|
||||
-- Truncate timestep to remaining time delta
|
||||
if timestep > dtime then
|
||||
timestep = dtime
|
||||
end
|
||||
|
||||
-- Truncate timestep to prevent v_1 from being larger that speed_max
|
||||
local v_max = SPEED_MAX
|
||||
if (v_0 ~= v_max) and ( v_0 + a * timestep > v_max) then
|
||||
timestep = ( v_max - v_0 ) / a
|
||||
end
|
||||
|
||||
-- Prevent infinite loops
|
||||
if timestep <= 0 then return 0 end
|
||||
|
||||
-- Calculate v_1 taking v_max into account
|
||||
local v_1 = v_0 + a * timestep
|
||||
if v_1 > v_max then
|
||||
v_1 = v_max
|
||||
elseif v_1 < FRICTION / 5 then
|
||||
v_1 = 0
|
||||
end
|
||||
|
||||
-- Calculate x_1
|
||||
local x_1 = x_0 + timestep * v_0 + 0.5 * a * timestep * timestep
|
||||
|
||||
-- Update position and velocity of the minecart
|
||||
staticdata.velocity = v_1
|
||||
staticdata.distance = x_1
|
||||
|
||||
if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then
|
||||
mcl_debug( "- cart #"..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",v_1="..tostring(v_1)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",x_1="..tostring(x_1)..
|
||||
",timestep="..tostring(timestep)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Entity movement
|
||||
local pos = staticdata.connected_at
|
||||
|
||||
-- Handle movement to next block, account for loss of precision in calculations
|
||||
if x_1 >= 0.99 then
|
||||
staticdata.distance = 0
|
||||
|
||||
-- Anchor at the next node
|
||||
local old_pos = pos
|
||||
pos = pos + staticdata.dir
|
||||
staticdata.connected_at = pos
|
||||
|
||||
-- Get the next direction
|
||||
local next_dir,_ = mcl_minecarts:get_rail_direction(pos, staticdata.dir, nil, nil, staticdata.railtype)
|
||||
if DEBUG and next_dir ~= staticdata.dir then
|
||||
mcl_debug( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir))
|
||||
end
|
||||
|
||||
-- Handle cart collisions
|
||||
handle_cart_collision(staticdata, pos, next_dir)
|
||||
|
||||
-- Leave the old node
|
||||
handle_cart_leave(staticdata, old_pos, next_dir )
|
||||
|
||||
-- Enter the new node
|
||||
handle_cart_enter(staticdata, pos, next_dir)
|
||||
|
||||
-- Handle end of track
|
||||
if next_dir == staticdata.dir * -1 and next_dir.y == 0 then
|
||||
if DEBUG then mcl_debug("Stopping cart at end of track at "..tostring(pos)) end
|
||||
staticdata.velocity = 0
|
||||
end
|
||||
|
||||
-- Update cart direction
|
||||
staticdata.dir = next_dir
|
||||
elseif stops_in_block and v_1 < (FRICTION/5) and a <= 0 and staticdata.dir.y > 0 then
|
||||
-- Handle direction flip due to gravity
|
||||
if DEBUG then mcl_debug("Gravity flipped direction") end
|
||||
|
||||
-- Velocity should be zero at this point
|
||||
staticdata.velocity = 0
|
||||
|
||||
reverse_direction(staticdata)
|
||||
|
||||
-- Intermediate movement
|
||||
pos = staticdata.connected_at + staticdata.dir * staticdata.distance
|
||||
else
|
||||
-- Intermediate movement
|
||||
pos = pos + staticdata.dir * staticdata.distance
|
||||
end
|
||||
|
||||
-- Debug reporting
|
||||
if DEBUG and ( v_0 > 0 or v_1 > 0 ) then
|
||||
mcl_debug( " cart #"..tostring(staticdata.uuid)..
|
||||
": a="..tostring(a)..
|
||||
",v_0="..tostring(v_0)..
|
||||
",v_1="..tostring(v_1)..
|
||||
",x_0="..tostring(x_0)..
|
||||
",x_1="..tostring(x_1)..
|
||||
",timestep="..tostring(timestep)..
|
||||
",dir="..tostring(staticdata.dir)..
|
||||
",pos="..tostring(pos)..
|
||||
",connected_at="..tostring(staticdata.connected_at)..
|
||||
",distance="..tostring(staticdata.distance)
|
||||
)
|
||||
end
|
||||
|
||||
-- Report the amount of time processed
|
||||
return dtime - timestep
|
||||
end
|
||||
|
||||
local function do_movement( staticdata, dtime )
|
||||
assert(staticdata)
|
||||
|
||||
-- Allow the carts to be delay for the rest of the world to react before moving again
|
||||
--[[
|
||||
if ( staticdata.delay or 0 ) > dtime then
|
||||
staticdata.delay = staticdata.delay - dtime
|
||||
return
|
||||
else
|
||||
staticdata.delay = 0
|
||||
end]]
|
||||
|
||||
-- Break long movements at block boundaries to make it
|
||||
-- it impossible to jump across gaps due to server lag
|
||||
-- causing large timesteps
|
||||
while dtime > 0 do
|
||||
local new_dtime = do_movement_step(staticdata, dtime)
|
||||
try_detach_minecart(staticdata)
|
||||
|
||||
update_train(staticdata)
|
||||
|
||||
-- Handle node watches here in steps to prevent server lag from changing behavior
|
||||
handle_cart_node_watches(staticdata, dtime - new_dtime)
|
||||
|
||||
dtime = new_dtime
|
||||
end
|
||||
end
|
||||
|
||||
local function do_detached_movement(self, dtime)
|
||||
local staticdata = self._staticdata
|
||||
|
||||
-- Make sure the object is still valid before trying to move it
|
||||
if not self.object or not self.object:get_pos() then return end
|
||||
|
||||
-- Apply physics
|
||||
if mcl_physics then
|
||||
mcl_physics.apply_entity_environmental_physics(self)
|
||||
else
|
||||
-- Simple physics
|
||||
local friction = self.object:get_velocity() or vector.new(0,0,0)
|
||||
friction.y = 0
|
||||
|
||||
local accel = vector.new(0,-9.81,0) -- gravity
|
||||
|
||||
-- Don't apply friction in the air
|
||||
local pos_rounded = vector.round(self.object:get_pos())
|
||||
if minetest.get_node(vector.offset(pos_rounded,0,-1,0)).name ~= "air" then
|
||||
accel = vector.add(accel, vector.multiply(friction,-0.9))
|
||||
end
|
||||
|
||||
self.object:set_acceleration(accel)
|
||||
end
|
||||
|
||||
local away = vector_away_from_players(self, staticdata)
|
||||
if away then
|
||||
local v = self.object:get_velocity()
|
||||
self.object:set_velocity(v - away)
|
||||
|
||||
-- Boost the minecart vertically a bit to get over the edge of rails and things like carpets
|
||||
local boost = vector.offset(vector.multiply(vector.normalize(away), 0.1), 0, 0.07, 0) -- 1/16th + 0.0075
|
||||
local pos = self.object:get_pos()
|
||||
self.object:set_pos(vector.add(pos,boost))
|
||||
end
|
||||
|
||||
-- Try to reconnect to rail
|
||||
local pos_r = vector.round(self.object:get_pos())
|
||||
local node = minetest.get_node(pos_r)
|
||||
if minetest.get_item_group(node.name, "rail") ~= 0 then
|
||||
staticdata.connected_at = pos_r
|
||||
staticdata.railtype = node.name
|
||||
|
||||
local freebody_velocity = self.object:get_velocity()
|
||||
staticdata.dir = mod:get_rail_direction(pos_r, mod.snap_direction(freebody_velocity))
|
||||
|
||||
-- Use vector projection to only keep the velocity in the new direction of movement on the rail
|
||||
-- https://en.wikipedia.org/wiki/Vector_projection
|
||||
staticdata.velocity = vector.dot(staticdata.dir,freebody_velocity)
|
||||
|
||||
-- Clear freebody movement
|
||||
self.object:set_velocity(vector.new(0,0,0))
|
||||
self.object:set_acceleration(vector.new(0,0,0))
|
||||
end
|
||||
end
|
||||
|
||||
--return do_movement, do_detatched_movement
|
||||
return do_movement,do_detached_movement,handle_cart_enter
|
||||
|
|
@ -1,48 +1,399 @@
|
|||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
local S = minetest.get_translator(modname)
|
||||
mod.RAIL_GROUPS = {
|
||||
STANDARD = 1,
|
||||
CURVES = 2,
|
||||
}
|
||||
|
||||
-- Template rail function
|
||||
local function register_rail(itemstring, tiles, def_extras, creative)
|
||||
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=0,destroy_by_lava_flow=0, transport=1}
|
||||
if creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
end
|
||||
local ndef = {
|
||||
drawtype = "raillike",
|
||||
tiles = tiles,
|
||||
is_ground_content = false,
|
||||
inventory_image = tiles[1],
|
||||
wield_image = tiles[1],
|
||||
paramtype = "light",
|
||||
walkable = false,
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
|
||||
},
|
||||
stack_max = 64,
|
||||
groups = groups,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
_mcl_blast_resistance = 0.7,
|
||||
_mcl_hardness = 0.7,
|
||||
after_destruct = function(pos)
|
||||
-- Scan for minecarts in this pos and force them to execute their "floating" check.
|
||||
-- Normally, this will make them drop.
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||||
for o=1, #objs do
|
||||
local le = objs[o]:get_luaentity()
|
||||
if le then
|
||||
-- All entities in this mod are minecarts, so this works
|
||||
if string.sub(le.name, 1, 14) == "mcl_minecarts:" then
|
||||
le._last_float_check = mcl_minecarts.check_float_time
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
if def_extras then
|
||||
for k,v in pairs(def_extras) do
|
||||
ndef[k] = v
|
||||
-- Inport functions and constants from elsewhere
|
||||
local table_merge = mcl_util.table_merge
|
||||
local check_connection_rules = mod.check_connection_rules
|
||||
local update_rail_connections = mod.update_rail_connections
|
||||
local north = mod.north
|
||||
local south = mod.south
|
||||
local east = mod.east
|
||||
local west = mod.west
|
||||
|
||||
--- Rail direction Handleres
|
||||
local function rail_dir_straight(pos, dir, node)
|
||||
dir = vector.new(dir.x, 0, dir.z)
|
||||
|
||||
if node.param2 == 0 or node.param2 == 2 then
|
||||
if vector.equals(dir, north) then
|
||||
return north
|
||||
else
|
||||
return south
|
||||
end
|
||||
else
|
||||
if vector.equals(dir,east) then
|
||||
return east
|
||||
else
|
||||
return west
|
||||
end
|
||||
end
|
||||
end
|
||||
local function rail_dir_sloped(pos, dir, node)
|
||||
local uphill = minetest.fourdir_to_dir(node.param2)
|
||||
local downhill = minetest.fourdir_to_dir((node.param2+2)%4)
|
||||
local up_uphill = vector.offset(uphill,0,1,0)
|
||||
|
||||
if vector.equals(dir, uphill) or vector.equals(dir, up_uphill) then
|
||||
return up_uphill
|
||||
else
|
||||
return downhill
|
||||
end
|
||||
end
|
||||
local function rail_dir_curve(pos, dir, node)
|
||||
dir = vector.new(dir.x, 0, dir.z)
|
||||
|
||||
if node.param2 == 0 then -- north
|
||||
-- South and East
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return east end
|
||||
if vector.equals(dir, west) then return south end
|
||||
if vector.equals(dir, east) then return east end
|
||||
elseif node.param2 == 1 then -- east
|
||||
-- South and West
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return west end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return south end
|
||||
elseif node.param2 == 2 then
|
||||
-- North and West
|
||||
if vector.equals(dir, south) then return west end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return north end
|
||||
elseif node.param2 == 3 then
|
||||
-- North and East
|
||||
if vector.equals(dir, south) then return east end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return north end
|
||||
if vector.equals(dir, east) then return east end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function rail_dir_tee_off(pos, dir, node)
|
||||
dir = vector.new(dir.x, 0, dir.z)
|
||||
|
||||
if node.param2 == 0 then -- north
|
||||
-- South and East
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return east end
|
||||
if vector.equals(dir, west) then return south end
|
||||
if vector.equals(dir, east) then return east end
|
||||
elseif node.param2 == 1 then -- east
|
||||
-- South and West
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return west end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return south end
|
||||
elseif node.param2 == 2 then
|
||||
-- North and West
|
||||
if vector.equals(dir, south) then return west end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return north end
|
||||
elseif node.param2 == 3 then
|
||||
-- North and East
|
||||
if vector.equals(dir, south) then return east end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return north end
|
||||
if vector.equals(dir, east) then return east end
|
||||
end
|
||||
end
|
||||
local function rail_dir_tee_on(pos, dir, node)
|
||||
dir = vector.new(dir.x, 0, dir.z)
|
||||
|
||||
if node.param2 == 0 then -- north
|
||||
-- South and East
|
||||
if vector.equals(dir, south) then return east end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return north end
|
||||
if vector.equals(dir, east) then return east end
|
||||
elseif node.param2 == 1 then -- east
|
||||
-- South and West
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return east end
|
||||
if vector.equals(dir, west) then return south end
|
||||
if vector.equals(dir, east) then return east end
|
||||
elseif node.param2 == 2 then
|
||||
-- North and West
|
||||
if vector.equals(dir, south) then return south end
|
||||
if vector.equals(dir, north) then return west end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return south end
|
||||
elseif node.param2 == 3 then
|
||||
-- North and East
|
||||
if vector.equals(dir, south) then return west end
|
||||
if vector.equals(dir, north) then return north end
|
||||
if vector.equals(dir, west) then return west end
|
||||
if vector.equals(dir, east) then return north end
|
||||
end
|
||||
end
|
||||
local function rail_dir_cross(pos, dir, node)
|
||||
dir = vector.new(dir.x, 0, dir.z)
|
||||
|
||||
-- Always continue in the same direction. No direction changes allowed
|
||||
return dir
|
||||
end
|
||||
|
||||
-- Setup shared text
|
||||
local railuse = S(
|
||||
"Place them on the ground to build your railway, the rails will automatically connect to each other and will"..
|
||||
" turn into curves, T-junctions, crossings and slopes as needed."
|
||||
)
|
||||
mod.text = mod.text or {}
|
||||
mod.text.railuse = railuse
|
||||
local BASE_DEF = {
|
||||
drawtype = "mesh",
|
||||
mesh = "flat_track.obj",
|
||||
paramtype = "light",
|
||||
paramtype2 = "4dir",
|
||||
stack_max = 64,
|
||||
sounds = mcl_sounds.node_sound_metal_defaults(),
|
||||
is_ground_content = true,
|
||||
paramtype = "light",
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = { -8/16, -8/16, -8/16, 8/16, -7/16, 8/15 }
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
|
||||
},
|
||||
groups = {
|
||||
handy=1, pickaxey=1,
|
||||
attached_node=1,
|
||||
rail=1,
|
||||
connect_to_raillike=minetest.raillike_group("rail"),
|
||||
dig_by_water=0,destroy_by_lava_flow=0,
|
||||
transport=1
|
||||
},
|
||||
description = S("New Rail"), -- Temporary name to make debugging easier
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_usagehelp = railuse,
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
local node_name = minetest.get_node(pointed_thing.under).name
|
||||
-- Don't allow placing rail above rail
|
||||
if minetest.get_item_group(node_name,"rail") == 0 then
|
||||
return minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
end,
|
||||
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
||||
update_rail_connections(pos)
|
||||
end,
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_straight,
|
||||
},
|
||||
_mcl_blast_resistance = 0.7,
|
||||
_mcl_hardness = 0.7,
|
||||
}
|
||||
|
||||
local SLOPED_RAIL_DEF = table.copy(BASE_DEF)
|
||||
table_merge(SLOPED_RAIL_DEF,{
|
||||
drawtype = "mesh",
|
||||
mesh = "sloped_track.obj",
|
||||
groups = {
|
||||
rail_slope = 1,
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
collision_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
|
||||
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
|
||||
}
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -0.5, -0.5, -0.5, 0.5, 0.0, 0.5 },
|
||||
{ -0.5, 0.0, 0.0, 0.5, 0.5, 0.5 }
|
||||
}
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
},
|
||||
})
|
||||
|
||||
function mod.register_rail(itemstring, ndef)
|
||||
assert(ndef.tiles)
|
||||
|
||||
-- Extract out the craft recipe
|
||||
local craft = ndef.craft
|
||||
ndef.craft = nil
|
||||
|
||||
-- Add sensible defaults
|
||||
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
||||
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
||||
|
||||
--print("registering rail "..itemstring.." with definition: "..dump(ndef))
|
||||
|
||||
-- Make registrations
|
||||
minetest.register_node(itemstring, ndef)
|
||||
if craft then minetest.register_craft(craft) end
|
||||
end
|
||||
|
||||
function mod.register_straight_rail(base_name, tiles, def)
|
||||
def = def or {}
|
||||
local base_def = table.copy(BASE_DEF)
|
||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||
local add = {
|
||||
tiles = { tiles[1] },
|
||||
drop = base_name,
|
||||
groups = {
|
||||
rail = mod.RAIL_GROUPS.STANDARD,
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
base_name = base_name,
|
||||
can_slope = true,
|
||||
},
|
||||
}
|
||||
table_merge(base_def, add); table_merge(sloped_def, add)
|
||||
table_merge(base_def, def); table_merge(sloped_def, def)
|
||||
|
||||
-- Register the base node
|
||||
mod.register_rail(base_name, base_def)
|
||||
base_def.craft = nil; sloped_def.craft = nil
|
||||
table_merge(base_def,{
|
||||
_mcl_minecarts = {
|
||||
railtype = "straight",
|
||||
},
|
||||
})
|
||||
|
||||
-- Sloped variant
|
||||
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
|
||||
description = S("Sloped Rail"), -- Temporary name to make debugging easier
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
},
|
||||
mesecons = def.mesecons_sloped,
|
||||
tiles = { tiles[1] },
|
||||
_mcl_minecarts = {
|
||||
railtype = "sloped",
|
||||
},
|
||||
}))
|
||||
end
|
||||
|
||||
function mod.register_curves_rail(base_name, tiles, def)
|
||||
def = def or {}
|
||||
local base_def = table.copy(BASE_DEF)
|
||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||
local add = {
|
||||
_mcl_minecarts = { base_name = base_name },
|
||||
groups = {
|
||||
rail = mod.RAIL_GROUPS.CURVES
|
||||
},
|
||||
drop = base_name,
|
||||
}
|
||||
table_merge(base_def, add); table_merge(sloped_def, add)
|
||||
table_merge(base_def, def); table_merge(sloped_def, def)
|
||||
|
||||
-- Register the base node
|
||||
mod.register_rail(base_name, table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[1] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_straight,
|
||||
railtype = "straight",
|
||||
can_slope = true,
|
||||
},
|
||||
}))
|
||||
|
||||
-- Update for other variants
|
||||
base_def.craft = nil
|
||||
table_merge(base_def, {
|
||||
groups = {
|
||||
not_in_creative_inventory = 1
|
||||
}
|
||||
})
|
||||
|
||||
-- Corner variants
|
||||
mod.register_rail(base_name.."_corner", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[2] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_curve,
|
||||
railtype = "corner",
|
||||
},
|
||||
}))
|
||||
|
||||
-- Tee variants
|
||||
mod.register_rail(base_name.."_tee_off", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[3] },
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
mesecons = {
|
||||
effector = {
|
||||
action_on = function(pos, node)
|
||||
local new_node = {name = base_name.."_tee_on", param2 = node.param2}
|
||||
minetest.swap_node(pos, new_node)
|
||||
end,
|
||||
rules = mesecon.rules.alldirs,
|
||||
}
|
||||
},
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_tee_off,
|
||||
railtype = "tee",
|
||||
},
|
||||
}))
|
||||
mod.register_rail(base_name.."_tee_on", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[4] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_tee_on,
|
||||
railtype = "tee",
|
||||
},
|
||||
mesecons = {
|
||||
effector = {
|
||||
action_off = function(pos, node)
|
||||
local new_node = {name = base_name.."_tee_off", param2 = node.param2}
|
||||
minetest.swap_node(pos, new_node)
|
||||
end,
|
||||
rules = mesecon.rules.alldirs,
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
-- Sloped variant
|
||||
mod.register_rail_sloped(base_name.."_sloped", table_merge(table.copy(sloped_def),{
|
||||
description = S("Sloped Rail"), -- Temporary name to make debugging easier
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_sloped,
|
||||
railtype = "tee",
|
||||
},
|
||||
tiles = { tiles[1] },
|
||||
}))
|
||||
|
||||
-- Cross variant
|
||||
mod.register_rail(base_name.."_cross", table_merge(table.copy(base_def),{
|
||||
tiles = { tiles[5] },
|
||||
_mcl_minecarts = {
|
||||
get_next_dir = rail_dir_cross,
|
||||
railtype = "cross",
|
||||
},
|
||||
}))
|
||||
end
|
||||
|
||||
function mod.register_rail_sloped(itemstring, def)
|
||||
assert(def.tiles)
|
||||
|
||||
-- Build the node definition
|
||||
local ndef = table.copy(SLOPED_RAIL_DEF)
|
||||
table_merge(ndef, def)
|
||||
|
||||
-- Add sensible defaults
|
||||
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
||||
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
||||
|
||||
--print("registering sloped rail "..itemstring.." with definition: "..dump(ndef))
|
||||
|
||||
-- Make registrations
|
||||
minetest.register_node(itemstring, ndef)
|
||||
end
|
||||
|
||||
|
@ -66,200 +417,309 @@ local rail_rules_long =
|
|||
|
||||
local rail_rules_short = mesecon.rules.pplate
|
||||
|
||||
local railuse = S("Place them on the ground to build your railway, the rails will automatically connect to each other and will turn into curves, T-junctions, crossings and slopes as needed.")
|
||||
|
||||
-- Normal rail
|
||||
register_rail("mcl_minecarts:rail",
|
||||
{"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
|
||||
{
|
||||
description = S("Rail"),
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
_doc_items_usagehelp = railuse,
|
||||
}
|
||||
)
|
||||
mod.register_curves_rail("mcl_minecarts:rail_v2", {
|
||||
"default_rail.png",
|
||||
"default_rail_curved.png",
|
||||
"default_rail_t_junction.png",
|
||||
"default_rail_t_junction_on.png",
|
||||
"default_rail_crossing.png"
|
||||
},{
|
||||
description = S("Rail"),
|
||||
_tt_help = S("Track for minecarts"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction."),
|
||||
_doc_items_usagehelp = railuse,
|
||||
craft = {
|
||||
output = "mcl_minecarts:rail_v2 16",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
-- Powered rail (off = brake mode)
|
||||
register_rail("mcl_minecarts:golden_rail",
|
||||
{"mcl_minecarts_rail_golden.png", "mcl_minecarts_rail_golden_curved.png", "mcl_minecarts_rail_golden_t_junction.png", "mcl_minecarts_rail_golden_crossing.png"},
|
||||
{
|
||||
description = S("Powered Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power."),
|
||||
_rail_acceleration = -3,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail",
|
||||
onstate = "mcl_minecarts:golden_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
mod.register_straight_rail("mcl_minecarts:golden_rail_v2",{ "mcl_minecarts_rail_golden.png" },{
|
||||
description = S("Powered Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Speed up when powered, slow down when not powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("Without redstone power, the rail will brake minecarts. To make this rail accelerate"..
|
||||
" minecarts, power it with redstone power."),
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = -3,
|
||||
_max_acceleration_velocity = 8,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail_v2",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:golden_rail_v2_sloepd",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail_v2",
|
||||
craft = {
|
||||
output = "mcl_minecarts:golden_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
-- Powered rail (on = acceleration mode)
|
||||
register_rail("mcl_minecarts:golden_rail_on",
|
||||
{"mcl_minecarts_rail_golden_powered.png", "mcl_minecarts_rail_golden_curved_powered.png", "mcl_minecarts_rail_golden_t_junction_powered.png", "mcl_minecarts_rail_golden_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = 4,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail",
|
||||
onstate = "mcl_minecarts:golden_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
action_on = function(pos, node)
|
||||
local dir = mcl_minecarts:get_start_direction(pos)
|
||||
if not dir then return end
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
local v = o:get_velocity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:"
|
||||
and v and vector.equals(v, vector.zero())
|
||||
then
|
||||
mcl_minecarts:set_velocity(l, dir)
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail",
|
||||
mod.register_straight_rail("mcl_minecarts:golden_rail_v2_on",{ "mcl_minecarts_rail_golden_powered.png" },{
|
||||
_doc_items_create_entry = false,
|
||||
_rail_acceleration = 4,
|
||||
_max_acceleration_velocity = 8,
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
false
|
||||
)
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail_v2",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:golden_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:golden_rail_v2_on_sloped",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:golden_rail_v2",
|
||||
})
|
||||
|
||||
-- Activator rail (off)
|
||||
register_rail("mcl_minecarts:activator_rail",
|
||||
{"mcl_minecarts_rail_activator.png", "mcl_minecarts_rail_activator_curved.png", "mcl_minecarts_rail_activator_t_junction.png", "mcl_minecarts_rail_activator_crossing.png"},
|
||||
{
|
||||
description = S("Activator Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail",
|
||||
onstate = "mcl_minecarts:activator_rail_on",
|
||||
rules = rail_rules_long,
|
||||
|
||||
},
|
||||
mod.register_straight_rail("mcl_minecarts:activator_rail_v2", {"mcl_minecarts_rail_activator.png"},{
|
||||
description = S("Activator Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Activates minecarts when powered"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail."),
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail_v2",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.off,
|
||||
offstate = "mcl_minecarts:activator_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
},
|
||||
craft = {
|
||||
output = "mcl_minecarts:activator_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
-- Activator rail (on)
|
||||
register_rail("mcl_minecarts:activator_rail_on",
|
||||
{"mcl_minecarts_rail_activator_powered.png", "mcl_minecarts_rail_activator_curved_powered.png", "mcl_minecarts_rail_activator_t_junction_powered.png", "mcl_minecarts_rail_activator_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail",
|
||||
onstate = "mcl_minecarts:activator_rail_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = function(pos, node)
|
||||
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
|
||||
local objs = minetest.get_objects_inside_radius(pos2, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
|
||||
l:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
},
|
||||
drop = "mcl_minecarts:activator_rail",
|
||||
local function activator_rail_action_on(pos, node)
|
||||
local pos2 = { x = pos.x, y =pos.y + 1, z = pos.z }
|
||||
local objs = minetest.get_objects_inside_radius(pos2, 1)
|
||||
for _, o in pairs(objs) do
|
||||
local l = o:get_luaentity()
|
||||
if l and string.sub(l.name, 1, 14) == "mcl_minecarts:" and l.on_activate_by_rail then
|
||||
l:on_activate_by_rail()
|
||||
end
|
||||
end
|
||||
end
|
||||
mod.register_straight_rail("mcl_minecarts:activator_rail_v2_on", {"mcl_minecarts_rail_activator_powered.png"},{
|
||||
_doc_items_create_entry = false,
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
false
|
||||
)
|
||||
mesecons = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail_v2",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = activator_rail_action_on,
|
||||
},
|
||||
|
||||
},
|
||||
mesecons_sloped = {
|
||||
conductor = {
|
||||
state = mesecon.state.on,
|
||||
offstate = "mcl_minecarts:activator_rail_v2_sloped",
|
||||
onstate = "mcl_minecarts:activator_rail_v2_on_sloped",
|
||||
rules = rail_rules_long,
|
||||
},
|
||||
effector = {
|
||||
-- Activate minecarts
|
||||
action_on = activator_rail_action_on,
|
||||
},
|
||||
|
||||
},
|
||||
_mcl_minecarts_on_enter = function(pos, cart)
|
||||
if cart.on_activate_by_rail then
|
||||
cart:on_activate_by_rail()
|
||||
end
|
||||
end,
|
||||
drop = "mcl_minecarts:activator_rail_v2",
|
||||
})
|
||||
|
||||
-- Detector rail (off)
|
||||
register_rail("mcl_minecarts:detector_rail",
|
||||
{"mcl_minecarts_rail_detector.png", "mcl_minecarts_rail_detector_curved.png", "mcl_minecarts_rail_detector_t_junction.png", "mcl_minecarts_rail_detector_crossing.png"},
|
||||
{
|
||||
description = S("Detector Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.off,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
mod.register_straight_rail("mcl_minecarts:detector_rail_v2",{"mcl_minecarts_rail_detector.png"},{
|
||||
description = S("Detector Rail"),
|
||||
_tt_help = S("Track for minecarts").."\n"..S("Emits redstone power when a minecart is detected"),
|
||||
_doc_items_longdesc = S("Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms."),
|
||||
_doc_items_usagehelp = railuse .. "\n" .. S("To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail."),
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.off,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
_mcl_minecarts_on_enter = function(pos, cart)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
local newnode = {
|
||||
name = "mcl_minecarts:detector_rail_v2_on",
|
||||
param2 = node.param2
|
||||
}
|
||||
minetest.swap_node( pos, newnode )
|
||||
mesecon.receptor_on(pos)
|
||||
end,
|
||||
craft = {
|
||||
output = "mcl_minecarts:detector_rail_v2 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
-- Detector rail (on)
|
||||
register_rail("mcl_minecarts:detector_rail_on",
|
||||
{"mcl_minecarts_rail_detector_powered.png", "mcl_minecarts_rail_detector_curved_powered.png", "mcl_minecarts_rail_detector_t_junction_powered.png", "mcl_minecarts_rail_detector_crossing_powered.png"},
|
||||
{
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.on,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
drop = "mcl_minecarts:detector_rail",
|
||||
mod.register_straight_rail("mcl_minecarts:detector_rail_v2_on",{"mcl_minecarts_rail_detector_powered.png"},{
|
||||
groups = {
|
||||
not_in_creative_inventory = 1,
|
||||
},
|
||||
false
|
||||
)
|
||||
_doc_items_create_entry = false,
|
||||
mesecons = {
|
||||
receptor = {
|
||||
state = mesecon.state.on,
|
||||
rules = rail_rules_short,
|
||||
},
|
||||
},
|
||||
_mcl_minecarts_on_leave = function(pos, cart)
|
||||
local node = minetest.get_node(pos)
|
||||
|
||||
|
||||
-- Crafting
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:rail 16",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
}
|
||||
local newnode = {
|
||||
name = "mcl_minecarts:detector_rail",
|
||||
param2 = node.param2
|
||||
}
|
||||
minetest.swap_node( pos, newnode )
|
||||
mesecon.receptor_off(pos)
|
||||
end,
|
||||
drop = "mcl_minecarts:detector_rail_v2",
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:golden_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:gold_ingot", "", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mcl_core:stick", "mcl_core:gold_ingot"},
|
||||
{"mcl_core:gold_ingot", "mesecons:redstone", "mcl_core:gold_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:activator_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_torch:mesecon_torch_on", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:detector_rail 6",
|
||||
recipe = {
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons_pressureplates:pressure_plate_stone_off", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mesecons:redstone", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
-- Aliases
|
||||
if minetest.get_modpath("doc") then
|
||||
doc.add_entry_alias("nodes", "mcl_minecarts:golden_rail", "nodes", "mcl_minecarts:golden_rail_on")
|
||||
end
|
||||
|
||||
local CURVY_RAILS_MAP = {
|
||||
["mcl_minecarts:rail"] = "mcl_minecarts:rail_v2",
|
||||
}
|
||||
for old,new in pairs(CURVY_RAILS_MAP) do
|
||||
minetest.register_node(old, {
|
||||
inventory_image = minetest.registered_nodes[new].inventory_image,
|
||||
groups = { rail = 1 }
|
||||
})
|
||||
end
|
||||
minetest.register_lbm({
|
||||
name = "mcl_minecarts:update_legacy_curvy_rails",
|
||||
nodenames = mcl_util.table_keys(CURVY_RAILS_MAP),
|
||||
action = function(pos, node)
|
||||
node.name = CURVY_RAILS_MAP[node.name]
|
||||
if node.name then
|
||||
minetest.swap_node(pos, node)
|
||||
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
end
|
||||
end
|
||||
})
|
||||
local STRAIGHT_RAILS_MAP ={
|
||||
["mcl_minecarts:golden_rail"] = "mcl_minecarts:golden_rail_v2",
|
||||
["mcl_minecarts:golden_rail_on"] = "mcl_minecarts:golden_rail_v2_on",
|
||||
["mcl_minecarts:activator_rail"] = "mcl_minecarts:activator_rail_v2",
|
||||
["mcl_minecarts:activator_rail_on"] = "mcl_minecarts:activator_rail_v2_on",
|
||||
["mcl_minecarts:detector_rail"] = "mcl_minecarts:detector_rail_v2",
|
||||
["mcl_minecarts:detector_rail_on"] = "mcl_minecarts:detector_rail_v2_on",
|
||||
}
|
||||
for old,new in pairs(STRAIGHT_RAILS_MAP) do
|
||||
minetest.register_node(old, {
|
||||
inventory_image = minetest.registered_nodes[new].inventory_image,
|
||||
groups = { rail = 1 }
|
||||
})
|
||||
end
|
||||
local TRANSLATE_RAILS_MAP = table.copy(STRAIGHT_RAILS_MAP)
|
||||
table_merge(TRANSLATE_RAILS_MAP, CURVY_RAILS_MAP)
|
||||
|
||||
minetest.register_lbm({
|
||||
name = "mcl_minecarts:update_legacy_straight_rails",
|
||||
nodenames = mcl_util.table_keys(STRAIGHT_RAILS_MAP),
|
||||
run_at_every_load = true,
|
||||
action = function(pos, node)
|
||||
node.name = STRAIGHT_RAILS_MAP[node.name]
|
||||
if node.name then
|
||||
local connections = mod.get_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
if not mod.HORIZONTAL_STANDARD_RULES[connections] then
|
||||
-- Drop an immortal object at this location
|
||||
local item_entity = minetest.add_item(pos, ItemStack(node.name))
|
||||
if item_entity then
|
||||
item_entity:get_luaentity()._immortal = true
|
||||
end
|
||||
|
||||
-- This is a configuration that doesn't exist in the new rail
|
||||
-- Replace with a standard rail
|
||||
node.name = "mcl_minecarts:rail_v2"
|
||||
end
|
||||
minetest.swap_node(pos, node)
|
||||
mod.update_rail_connections(pos, { legacy = true, ignore_neighbor_connections = true })
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Convert old rail in the player's inventory to new rail
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local inv = player:get_inventory()
|
||||
local size = inv:get_size("main")
|
||||
for i=1,size do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
local new_name = TRANSLATE_RAILS_MAP[stack:get_name()]
|
||||
if new_name then
|
||||
stack:set_name(new_name)
|
||||
inv:set_stack("main", i, stack)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
local storage = minetest.get_mod_storage()
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local CART_BLOCK_SIZE = mod.CART_BLOCK_SIZE
|
||||
assert(CART_BLOCK_SIZE)
|
||||
|
||||
local cart_data = {}
|
||||
local cart_data_fail_cache = {}
|
||||
local cart_ids = storage:get_keys()
|
||||
|
||||
local function get_cart_data(uuid)
|
||||
if cart_data[uuid] then return cart_data[uuid] end
|
||||
if cart_data_fail_cache[uuid] then return nil end
|
||||
|
||||
local data = minetest.deserialize(storage:get_string("cart-"..uuid))
|
||||
if not data then
|
||||
cart_data_fail_cache[uuid] = true
|
||||
return nil
|
||||
else
|
||||
-- Repair broken data
|
||||
if not data.distance then data.distance = 0 end
|
||||
if data.distance == 0/0 then data.distance = 0 end
|
||||
if data.distance == -0/0 then data.distance = 0 end
|
||||
data.dir = vector.new(data.dir)
|
||||
data.connected_at = vector.new(data.connected_at)
|
||||
end
|
||||
|
||||
cart_data[uuid] = data
|
||||
return data
|
||||
end
|
||||
mod.get_cart_data = get_cart_data
|
||||
|
||||
-- Preload all cart data into memory
|
||||
for _,id in pairs(cart_ids) do
|
||||
local uuid = string.sub(id,6)
|
||||
get_cart_data(uuid)
|
||||
end
|
||||
|
||||
local function save_cart_data(uuid)
|
||||
if not cart_data[uuid] then return end
|
||||
storage:set_string("cart-"..uuid,minetest.serialize(cart_data[uuid]))
|
||||
end
|
||||
mod.save_cart_data = save_cart_data
|
||||
|
||||
function mod.update_cart_data(data)
|
||||
local uuid = data.uuid
|
||||
cart_data[uuid] = data
|
||||
cart_data_fail_cache[uuid] = nil
|
||||
save_cart_data(uuid)
|
||||
end
|
||||
function mod.destroy_cart_data(uuid)
|
||||
storage:set_string("cart-"..uuid,"")
|
||||
cart_data[uuid] = nil
|
||||
cart_data_fail_cache[uuid] = true
|
||||
end
|
||||
|
||||
function mod.carts()
|
||||
return pairs(cart_data)
|
||||
end
|
||||
|
||||
function mod.find_carts_by_block_map(block_map)
|
||||
local cart_list = {}
|
||||
for _,data in pairs(cart_data) do
|
||||
if data and data.connected_at then
|
||||
local pos = mod.get_cart_position(data)
|
||||
local block = vector.floor(vector.divide(pos,CART_BLOCK_SIZE))
|
||||
if block_map[vector.to_string(block)] then
|
||||
cart_list[#cart_list + 1] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
return cart_list
|
||||
end
|
||||
|
||||
function mod.add_blocks_to_map(block_map, min_pos, max_pos)
|
||||
local min = vector.floor(vector.divide(min_pos, CART_BLOCK_SIZE))
|
||||
local max = vector.floor(vector.divide(max_pos, CART_BLOCK_SIZE)) + vector.new(1,1,1)
|
||||
for z = min.z,max.z do
|
||||
for y = min.y,max.y do
|
||||
for x = min.x,max.x do
|
||||
block_map[ vector.to_string(vector.new(x,y,z)) ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_shutdown(function()
|
||||
for uuid,_ in pairs(cart_data) do
|
||||
save_cart_data(uuid)
|
||||
end
|
||||
end)
|
|
@ -0,0 +1,155 @@
|
|||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local mod = mcl_minecarts
|
||||
|
||||
-- Imports
|
||||
local get_cart_data = mod.get_cart_data
|
||||
local save_cart_data = mod.save_cart_data
|
||||
local MAX_TRAIN_LENGTH = mod.MAX_TRAIN_LENGTH
|
||||
|
||||
-- Follow .behind to the back end of a train
|
||||
local function find_back(start)
|
||||
assert(start)
|
||||
|
||||
while start.behind do
|
||||
local nxt = get_cart_data(start.behind)
|
||||
if not nxt then return start end
|
||||
start = nxt
|
||||
end
|
||||
return start
|
||||
end
|
||||
|
||||
-- Iterate across all the cars in a train
|
||||
local function train_cars(staticdata)
|
||||
assert(staticdata)
|
||||
|
||||
local back = find_back(staticdata)
|
||||
local limit = MAX_TRAIN_LENGTH
|
||||
return function()
|
||||
if not back or limit <= 0 then return end
|
||||
limit = limit - 1
|
||||
|
||||
local ret = back
|
||||
if back.ahead then
|
||||
back = get_cart_data(back.ahead)
|
||||
else
|
||||
back = nil
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
function mod.train_length(cart)
|
||||
local count = 0
|
||||
for cart in train_cars(cart) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
function mod.is_in_same_train(anchor, other)
|
||||
for cart in train_cars(anchor) do
|
||||
if cart.uuid == other.uuid then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function mod.distance_between_cars(car1, car2)
|
||||
if not car1.connected_at then return nil end
|
||||
if not car2.connected_at then return nil end
|
||||
|
||||
if not car1.dir then car1.dir = vector.zero() end
|
||||
if not car2.dir then car2.dir = vector.zero() end
|
||||
|
||||
local pos1 = vector.add(car1.connected_at, vector.multiply(car1.dir, car1.distance))
|
||||
local pos2 = vector.add(car2.connected_at, vector.multiply(car2.dir, car2.distance))
|
||||
|
||||
return vector.distance(pos1, pos2)
|
||||
end
|
||||
local distance_between_cars = mod.distance_between_cars
|
||||
|
||||
local function break_train_at(cart)
|
||||
if cart.ahead then
|
||||
local ahead = get_cart_data(cart.ahead)
|
||||
if ahead then
|
||||
ahead.behind = nil
|
||||
cart.ahead = nil
|
||||
save_cart_data(ahead.uuid)
|
||||
end
|
||||
end
|
||||
if cart.behind then
|
||||
local behind = get_cart_data(cart.behind)
|
||||
if behind then
|
||||
behind.ahead = nil
|
||||
cart.behind = nil
|
||||
save_cart_data(behind.uuid)
|
||||
end
|
||||
end
|
||||
save_cart_data(cart.uuid)
|
||||
end
|
||||
mod.break_train_at = break_train_at
|
||||
|
||||
function mod.update_train(staticdata)
|
||||
--local staticdata = cart._staticdata
|
||||
|
||||
-- Only update from the back
|
||||
if staticdata.behind or not staticdata.ahead then return end
|
||||
--print("\nUpdating train")
|
||||
|
||||
-- Do no special processing if the cart is not part of a train
|
||||
if not staticdata.ahead and not staticdata.behind then return end
|
||||
|
||||
-- Calculate the maximum velocity of all train cars
|
||||
local velocity = 0
|
||||
local count = 0
|
||||
for cart in train_cars(staticdata) do
|
||||
velocity = velocity + (cart.velocity or 0)
|
||||
count = count + 1
|
||||
end
|
||||
velocity = velocity / count
|
||||
--print("Using velocity "..tostring(velocity))
|
||||
|
||||
-- Set the entire train to the average velocity
|
||||
local behind = nil
|
||||
for c in train_cars(staticdata) do
|
||||
local e = 0
|
||||
local separation
|
||||
local cart_velocity = velocity
|
||||
if not c.connected_at then
|
||||
break_train_at(c)
|
||||
elseif behind then
|
||||
separation = distance_between_cars(behind, c)
|
||||
local e = 0
|
||||
if not separation then
|
||||
break_train_at(c)
|
||||
elseif separation > 1.6 then
|
||||
cart_velocity = velocity * 0.9
|
||||
elseif separation > 2.5 then
|
||||
break_train_at(c)
|
||||
elseif separation < 1.15 then
|
||||
cart_velocity = velocity * 1.1
|
||||
end
|
||||
end
|
||||
--[[
|
||||
print(tostring(c.behind).."->"..c.uuid.."->"..tostring(c.ahead).."("..tostring(separation)..") setting cart #"..
|
||||
c.uuid.." velocity from "..tostring(c.velocity).." to "..tostring(cart_velocity))
|
||||
]]
|
||||
c.velocity = cart_velocity
|
||||
|
||||
behind = c
|
||||
end
|
||||
end
|
||||
|
||||
function mod.link_cart_ahead(staticdata, ca_staticdata)
|
||||
minetest.log("action","Linking cart #"..staticdata.uuid.." to cart #"..ca_staticdata.uuid)
|
||||
|
||||
staticdata.ahead = ca_staticdata.uuid
|
||||
ca_staticdata.behind = staticdata.uuid
|
||||
end
|
||||
|
||||
function mod.reverse_train(cart)
|
||||
for c in train_cars(cart) do
|
||||
mod.reverse_cart_direction(c)
|
||||
c.behind,c.ahead = c.ahead,c.behind
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
local mob_class = mcl_mobs.mob_class
|
||||
local mob_class_meta = {__index = mcl_mobs.mob_class}
|
||||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
-- API for Mobs Redo: VoxeLibre Edition
|
||||
-- API for Mobs Redo: MineClone 2 Edition (MRM)
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
local CRASH_WARN_FREQUENCY = 60
|
||||
|
@ -96,23 +96,15 @@ function mob_class:get_staticdata()
|
|||
|
||||
local tmp = {}
|
||||
|
||||
for tag, stat in pairs(self) do
|
||||
for _,stat in pairs(self) do
|
||||
|
||||
local t = type(stat)
|
||||
|
||||
if t ~= "function"
|
||||
and t ~= "nil"
|
||||
and t ~= "userdata"
|
||||
and tag ~= "_cmi_components" then
|
||||
tmp[tag] = self[tag]
|
||||
end
|
||||
end
|
||||
|
||||
tmp._mcl_potions = self._mcl_potions
|
||||
if tmp._mcl_potions then
|
||||
for name_raw, data in pairs(tmp._mcl_potions) do
|
||||
local def = mcl_potions.registered_effects[name_raw:match("^_EF_(.+)$")]
|
||||
if def and def.on_save_effect then def.on_save_effect(self.object) end
|
||||
and _ ~= "_cmi_components" then
|
||||
tmp[_] = self[_]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -150,11 +142,6 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
local tmp = minetest.deserialize(staticdata)
|
||||
|
||||
if tmp then
|
||||
-- Patch incorrectly converted mobs
|
||||
if tmp.base_mesh ~= minetest.registered_entities[self.name].mesh then
|
||||
mcl_mobs.strip_staticdata(tmp)
|
||||
end
|
||||
|
||||
for _,stat in pairs(tmp) do
|
||||
self[_] = stat
|
||||
end
|
||||
|
@ -319,10 +306,7 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
self._run_armor_init = true
|
||||
end
|
||||
|
||||
if not self._mcl_potions then
|
||||
self._mcl_potions = {}
|
||||
end
|
||||
mcl_potions._load_entity_effects(self)
|
||||
|
||||
|
||||
|
||||
if def.after_activate then
|
||||
|
@ -489,7 +473,7 @@ local function warn_user_error ()
|
|||
|
||||
if time_since_warning > CRASH_WARN_FREQUENCY then
|
||||
last_crash_warn_time = current_time
|
||||
minetest.log("A game crashing bug was prevented. Please provide debug.log information to VoxeLibre dev team for investigation. (Search for: --- Bug report start)")
|
||||
minetest.log("A game crashing bug was prevented. Please provide debug.log information to MineClone2 dev team for investigation. (Search for: --- Bug report start)")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
Mobs Redo: VoxeLibre Edition
|
||||
Mobs Redo: MineClone 2 Edition
|
||||
API documentation
|
||||
==============================
|
||||
|
||||
|
@ -231,7 +231,7 @@ functions needed for the mob to work properly which contains the following:
|
|||
|
||||
|
||||
|
||||
VoxeLibre extensions:
|
||||
MineClone 2 extensions:
|
||||
|
||||
'spawn_class' Classification of mod for the spawning algorithm:
|
||||
"hostile", "passive", "ambient" or "water"
|
||||
|
@ -434,7 +434,7 @@ true the mob will not spawn.
|
|||
'name' is the name of the animal/monster
|
||||
|
||||
|
||||
VoxeLibre extensions
|
||||
MineClone 2 extensions
|
||||
----------------------
|
||||
|
||||
mcl_mobs:spawn_child(pos, mob_type)
|
||||
|
@ -524,7 +524,7 @@ Does nothing and returns false.
|
|||
|
||||
This function is provided for compability with Mobs Redo for an attempt to
|
||||
capture a mob.
|
||||
Mobs cannot be captured in VoxeLibre.
|
||||
Mobs cannot be captured in MineClone 2.
|
||||
|
||||
In Mobs Redo, this is generally called inside the on_rightclick section of the mob
|
||||
api code, it provides a chance of capturing the mob. See Mobs Redo documentation
|
||||
|
@ -671,13 +671,6 @@ mob will spawn e.g.
|
|||
mobs_animal:sheep_chance 11000
|
||||
mobs_monster:sand_monster_chance 100
|
||||
|
||||
Registering Mob Conversion
|
||||
----------------
|
||||
|
||||
Sometimes you need to completely replace one mob with a different version. To do this, use:
|
||||
|
||||
mcl_mobs.register_conversion(old_name, new_name)
|
||||
|
||||
|
||||
Rideable Horse Example Mob
|
||||
--------------------------
|
||||
|
|
|
@ -78,7 +78,6 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
|
|||
self.food = 0
|
||||
self.horny = true
|
||||
self.persistent = true
|
||||
self._luck = mcl_luck.get_luck(clicker:get_player_name())
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -274,7 +273,7 @@ function mob_class:check_breeding()
|
|||
return
|
||||
end
|
||||
|
||||
mcl_experience.throw_xp(pos, math.random(1, 7) + (parent1._luck or 0) + (parent2._luck or 0))
|
||||
mcl_experience.throw_xp(pos, math.random(1, 7))
|
||||
|
||||
-- custom breed function
|
||||
if parent1.on_breed then
|
||||
|
|
|
@ -21,6 +21,8 @@ local function atan(x)
|
|||
end
|
||||
end
|
||||
|
||||
mcl_mobs.effect_functions = {}
|
||||
|
||||
|
||||
-- check if daytime and also if mob is docile during daylight hours
|
||||
function mob_class:day_docile()
|
||||
|
@ -532,8 +534,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
if self.protected and minetest.is_protected(mob_pos, hitter:get_player_name()) then
|
||||
return
|
||||
end
|
||||
|
||||
mcl_potions.update_haste_and_fatigue(hitter)
|
||||
end
|
||||
|
||||
local time_now = minetest.get_us_time()
|
||||
|
@ -605,13 +605,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
* tmp * ((armor[group] or 0) / 100.0)
|
||||
end
|
||||
|
||||
-- strength and weakness effects
|
||||
local strength = mcl_potions.get_effect(hitter, "strength")
|
||||
local weakness = mcl_potions.get_effect(hitter, "weakness")
|
||||
local str_fac = strength and strength.factor or 1
|
||||
local weak_fac = weakness and weakness.factor or 1
|
||||
damage = damage * str_fac * weak_fac
|
||||
|
||||
if weapon then
|
||||
local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect")
|
||||
if fire_aspect_level > 0 then
|
||||
|
@ -653,7 +646,6 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then
|
||||
local wear = math.floor(65535/tool_capabilities.punch_attack_uses)
|
||||
weapon:add_wear(wear)
|
||||
tt.reload_itemstack_description(weapon) -- update tooltip
|
||||
hitter:set_wielded_item(weapon)
|
||||
end
|
||||
end, hitter:get_player_name())
|
||||
|
@ -802,37 +794,34 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
end
|
||||
|
||||
-- alert others to the attack
|
||||
local alert_pos = hitter:get_pos()
|
||||
if alert_pos then
|
||||
local objs = minetest.get_objects_inside_radius(alert_pos, self.view_range)
|
||||
local obj = nil
|
||||
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
|
||||
local obj = nil
|
||||
|
||||
for n = 1, #objs do
|
||||
for n = 1, #objs do
|
||||
|
||||
obj = objs[n]:get_luaentity()
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
||||
if obj then
|
||||
-- only alert members of same mob or friends
|
||||
if obj.group_attack
|
||||
and obj.state ~= "attack"
|
||||
and obj.owner ~= name then
|
||||
if obj.name == self.name then
|
||||
obj:do_attack(hitter)
|
||||
elseif type(obj.group_attack) == "table" then
|
||||
for i=1, #obj.group_attack do
|
||||
if obj.group_attack[i] == self.name then
|
||||
obj._aggro = true
|
||||
obj:do_attack(hitter)
|
||||
break
|
||||
end
|
||||
if obj then
|
||||
-- only alert members of same mob or friends
|
||||
if obj.group_attack
|
||||
and obj.state ~= "attack"
|
||||
and obj.owner ~= name then
|
||||
if obj.name == self.name then
|
||||
obj:do_attack(hitter)
|
||||
elseif type(obj.group_attack) == "table" then
|
||||
for i=1, #obj.group_attack do
|
||||
if obj.group_attack[i] == self.name then
|
||||
obj._aggro = true
|
||||
obj:do_attack(hitter)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- have owned mobs attack player threat
|
||||
if obj.owner == name and obj.owner_loyal then
|
||||
obj:do_attack(self.object)
|
||||
end
|
||||
-- have owned mobs attack player threat
|
||||
if obj.owner == name and obj.owner_loyal then
|
||||
obj:do_attack(self.object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -968,7 +957,6 @@ function mob_class:do_states_attack (dtime)
|
|||
if self.v_start then
|
||||
self.timer = self.timer + dtime
|
||||
self.blinktimer = (self.blinktimer or 0) + dtime
|
||||
self:set_animation("fuse")
|
||||
|
||||
if self.blinktimer > 0.2 then
|
||||
self.blinktimer = 0
|
||||
|
@ -1154,8 +1142,9 @@ function mob_class:do_states_attack (dtime)
|
|||
damage_groups = {fleshy = self.damage}
|
||||
}, nil)
|
||||
if self.dealt_effect then
|
||||
mcl_potions.give_effect_by_level(self.dealt_effect.name, self.attack,
|
||||
self.dealt_effect.level, self.dealt_effect.dur)
|
||||
mcl_mobs.effect_functions[self.dealt_effect.name](
|
||||
self.attack, self.dealt_effect.factor, self.dealt_effect.dur
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
|
|
|
@ -314,7 +314,6 @@ function mcl_mobs.register_mob(name, def)
|
|||
|
||||
return self:mob_activate(staticdata, def, dtime)
|
||||
end,
|
||||
after_activate = def.after_activate,
|
||||
attack_state = def.attack_state, -- custom attack state
|
||||
on_attack = def.on_attack, -- called after attack, useful with otherwise predefined attack states (not custom)
|
||||
harmed_by_heal = def.harmed_by_heal,
|
||||
|
@ -325,7 +324,6 @@ function mcl_mobs.register_mob(name, def)
|
|||
attack_exception = def.attack_exception or function(p) return false end,
|
||||
|
||||
_spawner = def._spawner,
|
||||
_mcl_potions = {},
|
||||
}
|
||||
|
||||
if minetest.get_modpath("doc_identifier") ~= nil then
|
||||
|
@ -343,37 +341,6 @@ function mcl_mobs.register_mob(name, def)
|
|||
end -- END mcl_mobs.register_mob function
|
||||
|
||||
|
||||
local STRIP_FIELDS = { "mesh", "base_size", "textures", "base_mesh", "base_texture" }
|
||||
function mcl_mobs.strip_staticdata(unpacked_staticdata)
|
||||
-- Strip select fields from the staticdata to prevent conversion issues
|
||||
for i = 1,#STRIP_FIELDS do
|
||||
unpacked_staticdata[STRIP_FIELDS[i]] = nil
|
||||
end
|
||||
end
|
||||
function mcl_mobs.register_conversion(old_name, new_name)
|
||||
minetest.register_entity(old_name, {
|
||||
on_activate = function(self, staticdata, dtime)
|
||||
local unpacked_staticdata = minetest.deserialize(staticdata)
|
||||
mcl_mobs.strip_staticdata(unpacked_staticdata)
|
||||
staticdata = minetest.serialize(unpacked_staticdata)
|
||||
|
||||
local old_object = self.object
|
||||
if not old_object then return end
|
||||
|
||||
local pos = old_object:get_pos()
|
||||
if not pos then return end
|
||||
old_object:remove()
|
||||
|
||||
local new_object = minetest.add_entity(pos, new_name, staticdata)
|
||||
if not new_object then return end
|
||||
|
||||
local hook = (new_object:get_luaentity() or {})._on_after_convert
|
||||
if hook then hook(new_object) end
|
||||
end,
|
||||
_convert_to = new_name,
|
||||
})
|
||||
end
|
||||
|
||||
function mcl_mobs.get_arrow_damage_func(damage, typ)
|
||||
local typ = mcl_damage.types[typ] and typ or "arrow"
|
||||
return function(projectile, object)
|
||||
|
@ -407,7 +374,7 @@ function mcl_mobs.register_arrow(name, def)
|
|||
rotate = def.rotate,
|
||||
on_punch = def.on_punch or function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
local vel = self.object:get_velocity():length()
|
||||
self.object:set_velocity(dir * vel)
|
||||
self.object:set_velocity({x=dir.x * vel, y=dir.y * vel, z=dir.z * vel})
|
||||
self._puncher = puncher
|
||||
end,
|
||||
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
|
||||
|
@ -592,12 +559,7 @@ function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addeg
|
|||
--minetest.log("min light: " .. mob_light_lvl[1])
|
||||
--minetest.log("max light: " .. mob_light_lvl[2])
|
||||
|
||||
-- Handle egg conversion
|
||||
local mob_name = itemstack:get_name()
|
||||
local convert_to = (minetest.registered_entities[mob_name] or {})._convert_to
|
||||
if convert_to then mob_name = convert_to end
|
||||
|
||||
mcl_mobspawners.setup_spawner(pointed_thing.under, mob_name, mob_light_lvl[1], mob_light_lvl[2])
|
||||
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name(), mob_light_lvl[1], mob_light_lvl[2])
|
||||
if not minetest.is_creative_enabled(name) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
|
|
|
@ -87,8 +87,7 @@ function mob_class:check_item_pickup()
|
|||
end
|
||||
if self.pick_up then
|
||||
for k,v in pairs(self.pick_up) do
|
||||
local itemstack = ItemStack(l.itemstring)
|
||||
if not player_near(p) and self.on_pick_up and itemstack:get_name():find(v) then
|
||||
if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then
|
||||
local r = self.on_pick_up(self,l)
|
||||
if r and r.is_empty and not r:is_empty() then
|
||||
l.itemstring = r:to_string()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name = mcl_mobs
|
||||
author = PilzAdam
|
||||
description = Adds a mob API for mods to add animals or monsters, etc.
|
||||
depends = mcl_particles, mcl_luck
|
||||
depends = mcl_particles
|
||||
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk
|
||||
|
|
|
@ -684,7 +684,7 @@ function mob_class:do_env_damage()
|
|||
self.object:set_velocity({x = 0, y = 0, z = 0})
|
||||
-- wither rose effect
|
||||
elseif self.standing_in == "mcl_flowers:wither_rose" then
|
||||
mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
|
||||
mcl_potions.withering_func(self.object, 1, 2)
|
||||
end
|
||||
|
||||
local nodef = minetest.registered_nodes[self.standing_in]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
Mobs Redo: VoxeLibre Edition
|
||||
Mobs Redo: MineClone 2 Edition
|
||||
|
||||
Based on Mobs Redo from TenPlus1
|
||||
Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
|
||||
|
|
|
@ -23,7 +23,6 @@ local math_ceil = math.ceil
|
|||
local math_cos = math.cos
|
||||
local math_sin = math.sin
|
||||
local math_round = function(x) return (x > 0) and math_floor(x + 0.5) or math_ceil(x - 0.5) end
|
||||
local math_sqrt = math.sqrt
|
||||
|
||||
local vector_distance = vector.distance
|
||||
local vector_new = vector.new
|
||||
|
@ -565,9 +564,6 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_
|
|||
return
|
||||
end
|
||||
|
||||
assert(min_height)
|
||||
assert(max_height)
|
||||
|
||||
-- chance/spawn number override in minetest.conf for registered mob
|
||||
local numbers = minetest.settings:get(name)
|
||||
|
||||
|
@ -599,119 +595,25 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_
|
|||
spawn_dictionary[key]["max_height"] = max_height
|
||||
spawn_dictionary[key]["day_toggle"] = day_toggle
|
||||
spawn_dictionary[key]["check_position"] = check_position
|
||||
|
||||
end
|
||||
|
||||
-- Calculate the inverse of a piecewise linear function f(x). Line segments are represented as two
|
||||
-- adjacent points specified as { x, f(x) }. At least 2 points are required. If there are most solutions,
|
||||
-- the one with a lower x value will be chosen.
|
||||
local function inverse_pwl(fx, f)
|
||||
if fx < f[1][2] then
|
||||
return f[1][1]
|
||||
end
|
||||
|
||||
for i=2,#f do
|
||||
local x0,fx0 = unpack(f[i-1])
|
||||
local x1,fx1 = unpack(f[i ])
|
||||
if fx < fx1 then
|
||||
return (fx - fx0) * (x1 - x0) / (fx1 - fx0) + x0
|
||||
end
|
||||
end
|
||||
|
||||
return f[#f][1]
|
||||
end
|
||||
|
||||
local SPAWN_DISTANCE_CDF_PWL = {
|
||||
{0.000,0.00},
|
||||
{0.083,0.40},
|
||||
{0.416,0.75},
|
||||
{1.000,1.00},
|
||||
}
|
||||
|
||||
local two_pi = 2 * math.pi
|
||||
local function get_next_mob_spawn_pos(pos)
|
||||
-- Select a distance such that distances closer to the player are selected much more often than
|
||||
-- those further away from the player.
|
||||
local fx = (math_random(1,10000)-1) / 10000
|
||||
local x = inverse_pwl(fx, SPAWN_DISTANCE_CDF_PWL)
|
||||
local distance = x * (MOB_SPAWN_ZONE_OUTER - MOB_SPAWN_ZONE_INNER) + MOB_SPAWN_ZONE_INNER
|
||||
--print("Using spawn distance of "..tostring(distance).." fx="..tostring(fx)..",x="..tostring(x))
|
||||
-- TODO We should consider spawning something a little further away sporadically.
|
||||
-- It would be good for sky farms and variance, rather than all being on the 24 - 32 block away radius
|
||||
local distance = math_random(MOB_SPAWN_ZONE_INNER, MOB_SPAWN_ZONE_MIDDLE)
|
||||
local angle = math_random() * two_pi
|
||||
|
||||
-- TODO Floor xoff and zoff and add 0.5 so it tries to spawn in the middle of the square. Less failed attempts.
|
||||
-- Use spherical coordinates https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
|
||||
local theta = math_random() * two_pi
|
||||
local phi = math_random() * two_pi
|
||||
local xoff = math_round(distance * math_sin(theta) * math_cos(phi))
|
||||
local yoff = math_round(distance * math_cos(theta))
|
||||
local zoff = math_round(distance * math_sin(theta) * math_sin(phi))
|
||||
local goal_pos = vector.offset(pos, xoff, yoff, zoff)
|
||||
local xoff = math_round(distance * math_cos(angle))
|
||||
local zoff = math_round(distance * math_sin(angle))
|
||||
return vector.offset(pos, xoff, 0, zoff)
|
||||
end
|
||||
|
||||
if not ( math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT ) then
|
||||
mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Calculate upper/lower y limits
|
||||
local R1 = MOB_SPAWN_ZONE_OUTER
|
||||
local d = vector_distance( pos, vector.new( goal_pos.x, pos.y, goal_pos.z ) ) -- distance from player to projected point on horizontal plane
|
||||
local y1 = math_sqrt( R1*R1 - d*d ) -- absolue value of distance to outer sphere
|
||||
|
||||
local y_min
|
||||
local y_max
|
||||
if d >= MOB_SPAWN_ZONE_INNER then
|
||||
-- Outer region, y range has both ends on the outer sphere
|
||||
y_min = pos.y - y1
|
||||
y_max = pos.y + y1
|
||||
else
|
||||
-- Inner region, y range spans between inner and outer spheres
|
||||
local R2 = MOB_SPAWN_ZONE_INNER
|
||||
local y2 = math_sqrt( R2*R2 - d*d )
|
||||
if goal_pos.y > pos. y then
|
||||
-- Upper hemisphere
|
||||
y_min = pos.y + y2
|
||||
y_max = pos.y + y1
|
||||
else
|
||||
-- Lower hemisphere
|
||||
y_min = pos.y - y1
|
||||
y_max = pos.y - y2
|
||||
end
|
||||
end
|
||||
y_min = math_round(y_min)
|
||||
y_max = math_round(y_max)
|
||||
|
||||
-- Limit total range of check to 32 nodes (maximum of 3 map blocks)
|
||||
if y_max > goal_pos.y + 16 then
|
||||
y_max = goal_pos.y + 16
|
||||
end
|
||||
if y_min < goal_pos.y - 16 then
|
||||
y_min = goal_pos.y - 16
|
||||
end
|
||||
|
||||
-- Ask engine for valid spawn locations
|
||||
local spawning_position_list = find_nodes_in_area_under_air(
|
||||
{x = goal_pos.x, y = y_min, z = goal_pos.z},
|
||||
{x = goal_pos.x, y = y_max, z = goal_pos.z},
|
||||
{"group:solid", "group:water", "group:lava"}
|
||||
) or {}
|
||||
|
||||
-- Select only the locations at a valid distance
|
||||
local valid_positions = {}
|
||||
for _,check_pos in ipairs(spawning_position_list) do
|
||||
local dist = vector.distance(pos, check_pos)
|
||||
if dist >= MOB_SPAWN_ZONE_INNER and dist <= MOB_SPAWN_ZONE_OUTER then
|
||||
valid_positions[#valid_positions + 1] = check_pos
|
||||
end
|
||||
end
|
||||
spawning_position_list = valid_positions
|
||||
|
||||
-- No valid locations, failed to find a position
|
||||
if #spawning_position_list == 0 then
|
||||
mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Pick a random valid location
|
||||
mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos))
|
||||
return spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
local function decypher_limits(posy)
|
||||
posy = math_floor(posy)
|
||||
return posy - MOB_SPAWN_ZONE_MIDDLE, posy + MOB_SPAWN_ZONE_MIDDLE
|
||||
end
|
||||
|
||||
--a simple helper function for mob_spawn
|
||||
|
@ -1036,16 +938,42 @@ if mobs_spawn then
|
|||
|
||||
local function find_spawning_position(pos, max_times)
|
||||
local spawning_position
|
||||
local max_loops = max_times or 1
|
||||
|
||||
local max_loops = 1
|
||||
if max_times then max_loops = max_times end
|
||||
|
||||
local y_min, y_max = decypher_limits(pos.y)
|
||||
|
||||
--mcl_log("mapgen_limit: " .. SPAWN_MAPGEN_LIMIT)
|
||||
while max_loops > 0 do
|
||||
local spawning_position = get_next_mob_spawn_pos(pos)
|
||||
if spawning_position then return spawning_position end
|
||||
max_loops = max_loops - 1
|
||||
local i = 0
|
||||
repeat
|
||||
local goal_pos = get_next_mob_spawn_pos(pos)
|
||||
|
||||
end
|
||||
return nil
|
||||
if math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT then
|
||||
local spawning_position_list = find_nodes_in_area_under_air(
|
||||
{x = goal_pos.x, y = y_min, z = goal_pos.z},
|
||||
{x = goal_pos.x, y = y_max, z = goal_pos.z},
|
||||
{"group:solid", "group:water", "group:lava"}
|
||||
)
|
||||
if #spawning_position_list > 0 then
|
||||
mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos))
|
||||
spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
else
|
||||
mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
|
||||
i = i + 1
|
||||
if i >= max_loops then
|
||||
mcl_log("Cancel finding spawn positions at: " .. max_loops)
|
||||
break
|
||||
end
|
||||
until spawning_position
|
||||
return spawning_position
|
||||
end
|
||||
|
||||
local cumulative_chance = nil
|
||||
|
@ -1271,6 +1199,7 @@ function mob_class:check_despawn(pos, dtime)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_chatcommand("mobstats",{
|
||||
privs = { debug = true },
|
||||
func = function(n,param)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name = mcl_wither_spawning
|
||||
description = Wither Spawning for VoxeLibre
|
||||
description = Wither Spawning for MineClone2
|
||||
author = Fleckenstein
|
||||
depends = mobs_mc, mcl_heads
|
||||
|
|
|
@ -33,11 +33,11 @@ This mod adds mobs which closely resemble the mobs from the game Minecraft, vers
|
|||
* Husk
|
||||
* Skeleton
|
||||
* Stray
|
||||
* Stalker
|
||||
* Creeper
|
||||
* Slime
|
||||
* Spider
|
||||
* Cave Spider
|
||||
* Rover
|
||||
* Enderman
|
||||
* Zombie Villager
|
||||
* Zombie Piglin
|
||||
* Wither Skeleton
|
||||
|
|
|
@ -122,7 +122,6 @@ mooshroom_def.on_rightclick = function(self, clicker)
|
|||
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
item:add_wear(mobs_mc.shears_wear)
|
||||
tt.reload_itemstack_description(item) -- update tooltip
|
||||
clicker:get_inventory():set_stack("main", clicker:get_wield_index(), item)
|
||||
end
|
||||
-- Use bucket to milk
|
||||
|
|
|
@ -3,60 +3,14 @@
|
|||
local S = minetest.get_translator("mobs_mc")
|
||||
|
||||
--###################
|
||||
--################### STALKER
|
||||
--################### CREEPER
|
||||
--###################
|
||||
|
||||
|
||||
local function get_texture(self)
|
||||
local on_name = self.standing_on
|
||||
local texture
|
||||
local texture_suff = ""
|
||||
if on_name and on_name ~= "air" then
|
||||
local tiles = minetest.registered_nodes[on_name].tiles
|
||||
if tiles then
|
||||
local tile = tiles[1]
|
||||
local color
|
||||
if type(tile) == "table" then
|
||||
texture = tile.name or tile.image
|
||||
if tile.color then
|
||||
color = minetest.colorspec_to_colorstring(tile.color)
|
||||
end
|
||||
elseif type(tile) == "string" then
|
||||
texture = tile
|
||||
end
|
||||
if not color then
|
||||
color = minetest.colorspec_to_colorstring(minetest.registered_nodes[on_name].color)
|
||||
end
|
||||
if color then
|
||||
texture_suff = "^[multiply:" .. color .. "^[hsl:0:0:20"
|
||||
end
|
||||
end
|
||||
end
|
||||
if not texture or texture == "" then
|
||||
texture = "vl_stalker_default.png"
|
||||
end
|
||||
texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers
|
||||
texture = "([combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture ..")".. texture_suff
|
||||
if self.attack then
|
||||
texture = texture .. ")^vl_mobs_stalker_overlay_angry.png"
|
||||
else
|
||||
texture = texture .. ")^vl_mobs_stalker_overlay.png"
|
||||
end
|
||||
return texture
|
||||
end
|
||||
|
||||
local AURA = "vl_stalker_overloaded_aura.png"
|
||||
local function get_overloaded_aura(timer)
|
||||
local frame = math.floor(timer*16)
|
||||
local f = tostring(frame)
|
||||
local nf = tostring(16-f)
|
||||
return "[combine:16x24:-" .. nf ..",0=" .. AURA .. ":" .. f .. ",0=" .. AURA
|
||||
end
|
||||
|
||||
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:stalker", {
|
||||
description = S("Stalker"),
|
||||
mcl_mobs.register_mob("mobs_mc:creeper", {
|
||||
description = S("Creeper"),
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
spawn_in_group = 1,
|
||||
|
@ -67,16 +21,16 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.69, 0.3},
|
||||
pathfinding = 1,
|
||||
visual = "mesh",
|
||||
mesh = "vl_stalker.b3d",
|
||||
-- head_swivel = "Head_Control",
|
||||
mesh = "mobs_mc_creeper.b3d",
|
||||
head_swivel = "Head_Control",
|
||||
bone_eye_height = 2.35,
|
||||
head_eye_height = 1.8;
|
||||
curiosity = 2,
|
||||
textures = {
|
||||
{get_texture({}),
|
||||
{"mobs_mc_creeper.png",
|
||||
"mobs_mc_empty.png"},
|
||||
},
|
||||
visual_size = {x=2, y=2},
|
||||
visual_size = {x=3, y=3},
|
||||
sounds = {
|
||||
attack = "tnt_ignite",
|
||||
death = "mobs_mc_creeper_death",
|
||||
|
@ -102,8 +56,8 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
allow_fuse_reset = true,
|
||||
stop_to_explode = true,
|
||||
|
||||
-- Force-ignite stalker with flint and steel and explode after 1.5 seconds.
|
||||
-- TODO: Make stalker flash after doing this as well.
|
||||
-- Force-ignite creeper with flint and steel and explode after 1.5 seconds.
|
||||
-- TODO: Make creeper flash after doing this as well.
|
||||
-- TODO: Test and debug this code.
|
||||
on_rightclick = function(self, clicker)
|
||||
if self._forced_explosion_countdown_timer ~= nil then
|
||||
|
@ -132,11 +86,6 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
self:boom(mcl_util.get_object_center(self.object), self.explosion_strength)
|
||||
end
|
||||
end
|
||||
local new_texture = get_texture(self)
|
||||
if self._stalker_texture ~= new_texture then
|
||||
self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}})
|
||||
self._stalker_texture = new_texture
|
||||
end
|
||||
end,
|
||||
on_die = function(self, pos, cmi_cause)
|
||||
-- Drop a random music disc when killed by skeleton or stray
|
||||
|
@ -159,42 +108,35 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
|
|||
looting = "common",},
|
||||
|
||||
-- Head
|
||||
-- TODO: Only drop if killed by charged stalker
|
||||
{name = "mcl_heads:stalker",
|
||||
-- TODO: Only drop if killed by charged creeper
|
||||
{name = "mcl_heads:creeper",
|
||||
chance = 200, -- 0.5%
|
||||
min = 1,
|
||||
max = 1,},
|
||||
},
|
||||
animation = {
|
||||
speed_normal = 30,
|
||||
speed_run = 60,
|
||||
speed_normal = 24,
|
||||
speed_run = 48,
|
||||
stand_start = 0,
|
||||
stand_end = 23,
|
||||
walk_start = 24,
|
||||
walk_end = 49,
|
||||
run_start = 24,
|
||||
run_end = 49,
|
||||
fuse_start = 49,
|
||||
fuse_end = 80,
|
||||
hurt_start = 110,
|
||||
hurt_end = 139,
|
||||
death_start = 140,
|
||||
death_end = 189,
|
||||
look_start = 50,
|
||||
look_end = 108,
|
||||
},
|
||||
floats = 1,
|
||||
fear_height = 4,
|
||||
view_range = 16,
|
||||
})
|
||||
|
||||
_on_after_convert = function(obj)
|
||||
obj:set_properties({
|
||||
visual_size = {x=2, y=2},
|
||||
mesh = "vl_stalker.b3d",
|
||||
textures = {
|
||||
{get_texture({}),
|
||||
"mobs_mc_empty.png"},
|
||||
},
|
||||
})
|
||||
end,
|
||||
}) -- END mcl_mobs.register_mob("mobs_mc:stalker", {
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
||||
description = S("Overloaded Stalker"),
|
||||
mcl_mobs.register_mob("mobs_mc:creeper_charged", {
|
||||
description = S("Charged Creeper"),
|
||||
type = "monster",
|
||||
spawn_class = "hostile",
|
||||
hp_min = 20,
|
||||
|
@ -204,16 +146,15 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.69, 0.3},
|
||||
pathfinding = 1,
|
||||
visual = "mesh",
|
||||
mesh = "vl_stalker.b3d",
|
||||
mesh = "mobs_mc_creeper.b3d",
|
||||
|
||||
--BOOM
|
||||
|
||||
textures = {
|
||||
{get_texture({}),
|
||||
AURA},
|
||||
{"mobs_mc_creeper.png",
|
||||
"mobs_mc_creeper_charge.png"},
|
||||
},
|
||||
use_texture_alpha = true,
|
||||
visual_size = {x=2, y=2},
|
||||
visual_size = {x=3, y=3},
|
||||
sounds = {
|
||||
attack = "tnt_ignite",
|
||||
death = "mobs_mc_creeper_death",
|
||||
|
@ -237,8 +178,8 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
allow_fuse_reset = true,
|
||||
stop_to_explode = true,
|
||||
|
||||
-- Force-ignite stalker with flint and steel and explode after 1.5 seconds.
|
||||
-- TODO: Make stalker flash after doing this as well.
|
||||
-- Force-ignite creeper with flint and steel and explode after 1.5 seconds.
|
||||
-- TODO: Make creeper flash after doing this as well.
|
||||
-- TODO: Test and debug this code.
|
||||
on_rightclick = function(self, clicker)
|
||||
if self._forced_explosion_countdown_timer ~= nil then
|
||||
|
@ -267,9 +208,6 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
self:boom(mcl_util.get_object_center(self.object), self.explosion_strength)
|
||||
end
|
||||
end
|
||||
if not self._aura_timer or self._aura_timer > 1 then self._aura_timer = 0 end
|
||||
self._aura_timer = self._aura_timer + dtime
|
||||
self.object:set_properties({textures={get_texture(self), get_overloaded_aura(self._aura_timer)}})
|
||||
end,
|
||||
on_die = function(self, pos, cmi_cause)
|
||||
-- Drop a random music disc when killed by skeleton or stray
|
||||
|
@ -284,7 +222,7 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
end
|
||||
end,
|
||||
on_lightning_strike = function(self, pos, pos2, objects)
|
||||
mcl_util.replace_mob(self.object, "mobs_mc:stalker_overloaded")
|
||||
mcl_util.replace_mob(self.object, "mobs_mc:creeper_charged")
|
||||
return true
|
||||
end,
|
||||
maxdrops = 2,
|
||||
|
@ -296,23 +234,27 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
looting = "common",},
|
||||
|
||||
-- Head
|
||||
-- TODO: Only drop if killed by overloaded stalker
|
||||
{name = "mcl_heads:stalker",
|
||||
-- TODO: Only drop if killed by charged creeper
|
||||
{name = "mcl_heads:creeper",
|
||||
chance = 200, -- 0.5%
|
||||
min = 1,
|
||||
max = 1,},
|
||||
},
|
||||
animation = {
|
||||
speed_normal = 30,
|
||||
speed_run = 60,
|
||||
speed_normal = 24,
|
||||
speed_run = 48,
|
||||
stand_start = 0,
|
||||
stand_end = 23,
|
||||
walk_start = 24,
|
||||
walk_end = 49,
|
||||
run_start = 24,
|
||||
run_end = 49,
|
||||
fuse_start = 49,
|
||||
fuse_end = 80,
|
||||
hurt_start = 110,
|
||||
hurt_end = 139,
|
||||
death_start = 140,
|
||||
death_end = 189,
|
||||
look_start = 50,
|
||||
look_end = 108,
|
||||
},
|
||||
floats = 1,
|
||||
fear_height = 4,
|
||||
|
@ -320,25 +262,10 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
|||
--Having trouble when fire is placed with lightning
|
||||
fire_resistant = true,
|
||||
glow = 3,
|
||||
|
||||
_on_after_convert = function(obj)
|
||||
obj:set_properties({
|
||||
visual_size = {x=2, y=2},
|
||||
mesh = "vl_stalker.b3d",
|
||||
textures = {
|
||||
{get_texture({}),
|
||||
AURA},
|
||||
},
|
||||
})
|
||||
end,
|
||||
}) -- END mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
|
||||
|
||||
-- compat
|
||||
mcl_mobs.register_conversion("mobs_mc:creeper", "mobs_mc:stalker")
|
||||
mcl_mobs.register_conversion("mobs_mc:creeper_charged", "mobs_mc:stalker_overloaded")
|
||||
})
|
||||
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:stalker",
|
||||
"mobs_mc:creeper",
|
||||
"overworld",
|
||||
"ground",
|
||||
{
|
||||
|
@ -486,6 +413,4 @@ mcl_vars.mg_overworld_min,
|
|||
mcl_vars.mg_overworld_max)
|
||||
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:stalker", S("Stalker"), "#0da70a", "#000000", 0)
|
||||
minetest.register_alias("mobs_mc:creeper", "mobs_mc:stalker")
|
||||
mcl_mobs.register_egg("mobs_mc:stalker_overloaded", S("Overloaded Stalker"), "#00a77a", "#000000", 0)
|
||||
mcl_mobs.register_egg("mobs_mc:creeper", S("Creeper"), "#0da70a", "#000000", 0)
|
|
@ -31,9 +31,20 @@ local place_frequency_min = 235
|
|||
local place_frequency_max = 245
|
||||
|
||||
minetest.register_entity("mobs_mc:ender_eyes", {
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_spider.b3d",
|
||||
visual_size = {x=1.01/3, y=1.01/3},
|
||||
textures = {
|
||||
"mobs_mc_enderman_eyes.png",
|
||||
},
|
||||
on_step = function(self)
|
||||
self.object:remove()
|
||||
if self and self.object then
|
||||
if not self.object:get_attach() then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end,
|
||||
glow = 50,
|
||||
})
|
||||
|
||||
local S = minetest.get_translator("mobs_mc")
|
||||
|
@ -55,8 +66,142 @@ end
|
|||
|
||||
local pr = PseudoRandom(os.time()*(-334))
|
||||
|
||||
-- Texuture overrides for enderman block. Required for cactus because it's original is a nodebox
|
||||
-- and the textures have tranparent pixels.
|
||||
local block_texture_overrides
|
||||
do
|
||||
local cbackground = "mobs_mc_enderman_cactus_background.png"
|
||||
local ctiles = minetest.registered_nodes["mcl_core:cactus"].tiles
|
||||
|
||||
local ctable = {}
|
||||
local last
|
||||
for i=1, 6 do
|
||||
if ctiles[i] then
|
||||
last = ctiles[i]
|
||||
end
|
||||
table.insert(ctable, cbackground .. "^" .. last)
|
||||
end
|
||||
|
||||
block_texture_overrides = {
|
||||
["mcl_core:cactus"] = ctable,
|
||||
-- FIXME: replace colorize colors with colors from palette
|
||||
["mcl_core:dirt_with_grass"] =
|
||||
{
|
||||
"mcl_core_grass_block_top.png^[colorize:green:90",
|
||||
"default_dirt.png",
|
||||
"default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)",
|
||||
"default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)",
|
||||
"default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)",
|
||||
"default_dirt.png^(mcl_core_grass_block_side_overlay.png^[colorize:green:90)"}
|
||||
}
|
||||
end
|
||||
|
||||
-- Create the textures table for the enderman, depending on which kind of block
|
||||
-- the enderman holds (if any).
|
||||
local create_enderman_textures = function(block_type, itemstring)
|
||||
local base = "mobs_mc_enderman.png^mobs_mc_enderman_eyes.png"
|
||||
|
||||
--[[ Order of the textures in the texture table:
|
||||
Flower, 90 degrees
|
||||
Flower, 45 degrees
|
||||
Held block, backside
|
||||
Held block, bottom
|
||||
Held block, front
|
||||
Held block, left
|
||||
Held block, right
|
||||
Held block, top
|
||||
Enderman texture (base)
|
||||
]]
|
||||
-- Regular cube
|
||||
if block_type == "cube" then
|
||||
local tiles = minetest.registered_nodes[itemstring].tiles
|
||||
local textures = {}
|
||||
local last
|
||||
if block_texture_overrides[itemstring] then
|
||||
-- Texture override available? Use these instead!
|
||||
textures = block_texture_overrides[itemstring]
|
||||
else
|
||||
-- Extract the texture names
|
||||
for i = 1, 6 do
|
||||
if type(tiles[i]) == "string" then
|
||||
last = tiles[i]
|
||||
elseif type(tiles[i]) == "table" then
|
||||
if tiles[i].name then
|
||||
last = tiles[i].name
|
||||
end
|
||||
end
|
||||
table.insert(textures, last)
|
||||
end
|
||||
end
|
||||
return {
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
textures[5],
|
||||
textures[2],
|
||||
textures[6],
|
||||
textures[3],
|
||||
textures[4],
|
||||
textures[1],
|
||||
base, -- Enderman texture
|
||||
}
|
||||
-- Node of plantlike drawtype, 45° (recommended)
|
||||
elseif block_type == "plantlike45" then
|
||||
local textures = minetest.registered_nodes[itemstring].tiles
|
||||
return {
|
||||
"blank.png",
|
||||
textures[1],
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
base,
|
||||
}
|
||||
-- Node of plantlike drawtype, 90°
|
||||
elseif block_type == "plantlike90" then
|
||||
local textures = minetest.registered_nodes[itemstring].tiles
|
||||
return {
|
||||
textures[1],
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
base,
|
||||
}
|
||||
elseif block_type == "unknown" then
|
||||
return {
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"unknown_node.png",
|
||||
"unknown_node.png",
|
||||
"unknown_node.png",
|
||||
"unknown_node.png",
|
||||
"unknown_node.png",
|
||||
"unknown_node.png",
|
||||
base, -- Enderman texture
|
||||
}
|
||||
-- No block held (for initial texture)
|
||||
elseif block_type == "nothing" or block_type == nil then
|
||||
return {
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
"blank.png",
|
||||
base, -- Enderman texture
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Select a new animation definition.
|
||||
local select_rover_animation = function(animation_type)
|
||||
local select_enderman_animation = function(animation_type)
|
||||
-- Enderman holds a block
|
||||
if animation_type == "block" then
|
||||
return {
|
||||
|
@ -109,8 +254,8 @@ local psdefs = {{
|
|||
texture = "mcl_portals_particle"..math.random(1, 5)..".png",
|
||||
}}
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:rover", {
|
||||
description = S("Rover"),
|
||||
mcl_mobs.register_mob("mobs_mc:enderman", {
|
||||
description = S("Enderman"),
|
||||
type = "monster",
|
||||
spawn_class = "passive",
|
||||
can_despawn = true,
|
||||
|
@ -122,11 +267,23 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
xp_max = 5,
|
||||
collisionbox = {-0.3, -0.01, -0.3, 0.3, 2.89, 0.3},
|
||||
visual = "mesh",
|
||||
mesh = "vl_rover.b3d",
|
||||
textures = { "vl_mobs_rover.png^vl_mobs_rover_face.png" },
|
||||
glow = 100,
|
||||
visual_size = {x=10, y=10},
|
||||
mesh = "mobs_mc_enderman.b3d",
|
||||
textures = create_enderman_textures(),
|
||||
visual_size = {x=3, y=3},
|
||||
makes_footstep_sound = true,
|
||||
on_spawn = function(self)
|
||||
local spider_eyes=false
|
||||
for n = 1, #self.object:get_children() do
|
||||
local obj = self.object:get_children()[n]
|
||||
if obj:get_luaentity() and self.object:get_luaentity().name == "mobs_mc:ender_eyes" then
|
||||
spider_eyes = true
|
||||
end
|
||||
end
|
||||
if not spider_eyes then
|
||||
minetest.add_entity(self.object:get_pos(), "mobs_mc:ender_eyes"):set_attach(self.object, "head.top", vector.new(0,2.54,-1.99), vector.new(90,0,180))
|
||||
minetest.add_entity(self.object:get_pos(), "mobs_mc:ender_eyes"):set_attach(self.object, "head.top", vector.new(1,2.54,-1.99), vector.new(90,0,180))
|
||||
end
|
||||
end,
|
||||
sounds = {
|
||||
-- TODO: Custom war cry sound
|
||||
war_cry = "mobs_sandmonster",
|
||||
|
@ -135,8 +292,8 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
random = {name="mobs_mc_enderman_random", gain=0.5},
|
||||
distance = 16,
|
||||
},
|
||||
walk_velocity = 2,
|
||||
run_velocity = 4,
|
||||
walk_velocity = 0.2,
|
||||
run_velocity = 3.4,
|
||||
damage = 7,
|
||||
reach = 2,
|
||||
particlespawners = psdefs,
|
||||
|
@ -147,7 +304,7 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
max = 1,
|
||||
looting = "common"},
|
||||
},
|
||||
animation = select_rover_animation("normal"),
|
||||
animation = select_enderman_animation("normal"),
|
||||
_taken_node = "",
|
||||
can_spawn = function(pos)
|
||||
return #minetest.find_nodes_in_area(vector.offset(pos,0,1,0),vector.offset(pos,0,3,0),{"air"}) > 2
|
||||
|
@ -191,7 +348,6 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
|
||||
-- AGRESSIVELY WARP/CHASE PLAYER BEHAVIOUR HERE.
|
||||
if self.state == "attack" then
|
||||
self.object:set_properties({textures={"vl_mobs_rover.png^vl_mobs_rover_face_angry.png"}})
|
||||
if self.attack then
|
||||
local target = self.attack
|
||||
local pos = target:get_pos()
|
||||
|
@ -202,7 +358,6 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
end
|
||||
end
|
||||
else --if not attacking try to tp to the dark
|
||||
self.object:set_properties({textures={"vl_mobs_rover.png^vl_mobs_rover_face.png"}})
|
||||
if dim == 'overworld' then
|
||||
local light = minetest.get_node_light(enderpos)
|
||||
if light and light > minetest.LIGHT_MAX then
|
||||
|
@ -334,17 +489,38 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
minetest.remove_node(take_pos)
|
||||
local dug = minetest.get_node_or_nil(take_pos)
|
||||
if dug and dug.name == "air" then
|
||||
local node_obj = vl_held_item.create_item_entity(take_pos, node.name)
|
||||
if node_obj then
|
||||
node_obj:set_attach(self.object, "held_node")
|
||||
self._node_obj = node_obj
|
||||
self._taken_node = node.name
|
||||
node_obj:set_properties({visual_size={x=0.02, y=0.02}})
|
||||
end
|
||||
self._taken_node = node.name
|
||||
self.persistent = true
|
||||
local def = minetest.registered_nodes[self._taken_node]
|
||||
self.animation = select_rover_animation("block")
|
||||
-- Update animation and texture accordingly (adds visibly carried block)
|
||||
local block_type
|
||||
-- Cube-shaped
|
||||
if def.drawtype == "normal" or
|
||||
def.drawtype == "nodebox" or
|
||||
def.drawtype == "liquid" or
|
||||
def.drawtype == "flowingliquid" or
|
||||
def.drawtype == "glasslike" or
|
||||
def.drawtype == "glasslike_framed" or
|
||||
def.drawtype == "glasslike_framed_optional" or
|
||||
def.drawtype == "allfaces" or
|
||||
def.drawtype == "allfaces_optional" or
|
||||
def.drawtype == nil then
|
||||
block_type = "cube"
|
||||
elseif def.drawtype == "plantlike" then
|
||||
-- Flowers and stuff
|
||||
block_type = "plantlike45"
|
||||
elseif def.drawtype == "airlike" then
|
||||
-- Just air
|
||||
block_type = nil
|
||||
else
|
||||
-- Fallback for complex drawtypes
|
||||
block_type = "unknown"
|
||||
end
|
||||
self.base_texture = create_enderman_textures(block_type, self._taken_node)
|
||||
self.object:set_properties({ textures = self.base_texture })
|
||||
self.animation = select_enderman_animation("block")
|
||||
self:set_animation(self.animation.current)
|
||||
if def and def.sounds and def.sounds.dug then
|
||||
if def.sounds and def.sounds.dug then
|
||||
minetest.sound_play(def.sounds.dug, {pos = take_pos, max_hear_distance = 16}, true)
|
||||
end
|
||||
end
|
||||
|
@ -366,14 +542,12 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
local def = minetest.registered_nodes[self._taken_node]
|
||||
-- Update animation accordingly (removes visible block)
|
||||
self.persistent = false
|
||||
self.animation = select_rover_animation("normal")
|
||||
self.animation = select_enderman_animation("normal")
|
||||
self:set_animation(self.animation.current)
|
||||
if def and def.sounds and def.sounds.place then
|
||||
if def.sounds and def.sounds.place then
|
||||
minetest.sound_play(def.sounds.place, {pos = place_pos, max_hear_distance = 16}, true)
|
||||
end
|
||||
self._node_obj:remove()
|
||||
self._node_obj = nil
|
||||
self._taken_node = nil
|
||||
self._taken_node = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -471,41 +645,16 @@ mcl_mobs.register_mob("mobs_mc:rover", {
|
|||
--end
|
||||
end
|
||||
end,
|
||||
after_activate = function(self, staticdata, def, dtime)
|
||||
if not self._taken_node or self._taken_node == "" then
|
||||
self.animation = select_rover_animation("normal")
|
||||
self:set_animation(self.animation.current)
|
||||
return
|
||||
end
|
||||
self.animation = select_rover_animation("block")
|
||||
self:set_animation(self.animation.current)
|
||||
local node_obj = vl_held_item.create_item_entity(self.object:get_pos(), self._taken_node)
|
||||
if node_obj then
|
||||
node_obj:set_attach(self.object, "held_node")
|
||||
self._node_obj = node_obj
|
||||
node_obj:set_properties({visual_size={x=0.02, y=0.02}})
|
||||
end
|
||||
end,
|
||||
armor = { fleshy = 100, water_vulnerable = 100 },
|
||||
water_damage = 8,
|
||||
view_range = 64,
|
||||
fear_height = 4,
|
||||
attack_type = "dogfight",
|
||||
_on_after_convert = function(obj)
|
||||
obj:set_properties({
|
||||
mesh = "vl_rover.b3d",
|
||||
textures = { "vl_mobs_rover.png^vl_mobs_rover_face.png" },
|
||||
visual_size = {x=10, y=10},
|
||||
})
|
||||
end
|
||||
}) -- END mcl_mobs.register_mob("mobs_mc:rover", {
|
||||
|
||||
-- compat
|
||||
mcl_mobs.register_conversion("mobs_mc:enderman", "mobs_mc:rover")
|
||||
})
|
||||
|
||||
-- End spawn
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:rover",
|
||||
"mobs_mc:enderman",
|
||||
"end",
|
||||
"ground",
|
||||
{
|
||||
|
@ -525,7 +674,7 @@ mcl_vars.mg_end_min,
|
|||
mcl_vars.mg_end_max)
|
||||
-- Overworld spawn
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:rover",
|
||||
"mobs_mc:enderman",
|
||||
"overworld",
|
||||
"ground",
|
||||
{
|
||||
|
@ -674,7 +823,7 @@ mcl_vars.mg_overworld_max)
|
|||
|
||||
-- Nether spawn (rare)
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:rover",
|
||||
"mobs_mc:enderman",
|
||||
"nether",
|
||||
"ground",
|
||||
{
|
||||
|
@ -691,7 +840,7 @@ mcl_vars.mg_nether_max)
|
|||
|
||||
-- Warped Forest spawn (common)
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:rover",
|
||||
"mobs_mc:enderman",
|
||||
"nether",
|
||||
"ground",
|
||||
{
|
||||
|
@ -706,5 +855,4 @@ mcl_vars.mg_nether_min,
|
|||
mcl_vars.mg_nether_max)
|
||||
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:rover", S("Rover"), "#252525", "#151515", 0)
|
||||
minetest.register_alias("mobs_mc:enderman", "mobs_mc:rover")
|
||||
mcl_mobs.register_egg("mobs_mc:enderman", S("Enderman"), "#252525", "#151515", 0)
|
|
@ -126,14 +126,13 @@ mcl_mobs.register_arrow("mobs_mc:fireball", {
|
|||
end,
|
||||
|
||||
hit_mob = function(self, mob)
|
||||
local name = mob:get_luaentity().name
|
||||
mob:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = 6},
|
||||
}, nil)
|
||||
mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1, true)
|
||||
local ent = mob:get_luaentity()
|
||||
if (not ent or ent.health <= 0) and self._puncher and name == "mobs_mc:ghast" then
|
||||
if not ent or ent.health <= 0 then
|
||||
awards.unlock(self._puncher:get_player_name(), "mcl:fireball_redir_serv")
|
||||
end
|
||||
end,
|
||||
|
|
|
@ -99,7 +99,9 @@ mcl_mobs.register_mob("mobs_mc:guardian", {
|
|||
view_range = 16,
|
||||
})
|
||||
|
||||
mcl_mobs:spawn_specific("mobs_mc:guardian", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 25000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level - 10, mobs_mc.water_level)
|
||||
-- Spawning disabled due to size issues
|
||||
-- TODO: Re-enable spawning
|
||||
--mcl_mobs:spawn_specific("mobs_mc:guardian", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 25000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level - 10)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:guardian","overworld",0,minetest.LIGHT_MAX+1)
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:guardian", S("Guardian"), "#5a8272", "#f17d31", 0)
|
||||
|
|
|
@ -105,14 +105,11 @@ mcl_mobs.register_mob("mobs_mc:guardian_elder", {
|
|||
fly_in = { "mcl_core:water_source", "mclx_core:river_water_source" },
|
||||
jump = false,
|
||||
view_range = 16,
|
||||
dealt_effect = {
|
||||
name = "fatigue",
|
||||
level = 3,
|
||||
dur = 30,
|
||||
},
|
||||
})
|
||||
|
||||
mcl_mobs:spawn_specific("mobs_mc:guardian_elder", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 40000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level-18, mobs_mc.water_level)
|
||||
-- Spawning disabled due to size issues <- what do you mean? -j4i
|
||||
-- TODO: Re-enable spawning
|
||||
-- mcl_mobs:spawn_specific("mobs_mc:guardian_elder", { "mcl_core:water_source", "mclx_core:river_water_source" }, { "mcl_core:water_source", "mclx_core:river_water_source" }, 0, minetest.LIGHT_MAX+1, 30, 40000, 2, mcl_vars.mg_overworld_min, mobs_mc.water_level-18)
|
||||
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:guardian_elder", S("Elder Guardian"), "#ceccba", "#747693", 0)
|
||||
|
|
|
@ -122,10 +122,10 @@ local horse = {
|
|||
stand_speed = 25,
|
||||
stand_start = 0,
|
||||
stand_end = 0,
|
||||
walk_speed = 100,
|
||||
walk_speed = 25,
|
||||
walk_start = 0,
|
||||
walk_end = 40,
|
||||
run_speed = 200,
|
||||
run_speed = 60,
|
||||
run_start = 0,
|
||||
run_end = 40,
|
||||
},
|
||||
|
@ -543,6 +543,11 @@ donkey.description = S("Donkey")
|
|||
donkey.textures = {{"blank.png", "mobs_mc_donkey.png", "blank.png"}}
|
||||
donkey.spawn_in_group = 3
|
||||
donkey.spawn_in_group_min = 1
|
||||
donkey.animation = {
|
||||
speed_normal = 25,
|
||||
stand_start = 0, stand_end = 0,
|
||||
walk_start = 0, walk_end = 40,
|
||||
}
|
||||
donkey.sounds = {
|
||||
random = "mobs_mc_donkey_random",
|
||||
damage = "mobs_mc_donkey_hurt",
|
||||
|
|
|
@ -124,7 +124,9 @@ dofile(path .. "/witch.lua") -- Mesh and animation by toby109tt / https://githu
|
|||
|
||||
--Monsters
|
||||
dofile(path .. "/blaze.lua") -- Animation by daufinsyd
|
||||
dofile(path .. "/creeper.lua") -- Mesh by Morn76 Animation by Pavel_S
|
||||
dofile(path .. "/ender_dragon.lua") -- Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/enderman.lua") -- Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/endermite.lua") -- Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/villager_illusioner.lua") -- Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/ghast.lua") -- maikerumine
|
||||
|
@ -132,12 +134,10 @@ dofile(path .. "/guardian.lua") -- maikerumine Mesh and animation by toby109tt
|
|||
dofile(path .. "/guardian_elder.lua") -- maikerumine Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/snowman.lua")
|
||||
dofile(path .. "/iron_golem.lua") -- maikerumine Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/rover.lua") -- Mesh and Animation by Herowl
|
||||
dofile(path .. "/shulker.lua") -- maikerumine Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/silverfish.lua") -- maikerumine Mesh and animation by toby109tt / https://github.com/22i
|
||||
dofile(path .. "/skeleton+stray.lua") -- Mesh by Morn76 Animation by Pavel_S
|
||||
dofile(path .. "/skeleton_wither.lua") -- Mesh by Morn76 Animation by Pavel_S
|
||||
dofile(path .. "/stalker.lua") -- Mesh and Animation by Herowl
|
||||
dofile(path .. "/zombie.lua") -- Mesh by Morn76 Animation by Pavel_S
|
||||
dofile(path .. "/slime+magma_cube.lua") -- Wuzzy
|
||||
dofile(path .. "/spider.lua") -- Spider by AspireMint (fishyWET (CC-BY-SA 3.0 license for texture)
|
||||
|
|
|
@ -5,7 +5,7 @@ Blaze=Lohe
|
|||
Chicken=Huhn
|
||||
Cow=Kuh
|
||||
Mooshroom=Pilzkuh
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Ender Dragon=Enderdrache
|
||||
Enderman=Enderman
|
||||
Endermite=Endermilbe
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=Blaze
|
|||
Chicken=Kylling
|
||||
Cow=Ko
|
||||
Mooshroom=Svamp
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Ender Dragon=Enderdrage
|
||||
Enderman=Enderman
|
||||
Endermite=Endermide
|
||||
|
|
|
@ -5,7 +5,7 @@ Chicken=Pollo
|
|||
Cod=Bacalao
|
||||
Cow=Vaca
|
||||
Mooshroom=Champivaca
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Dolphin=Delfín
|
||||
Ender Dragon=Ender Dragon
|
||||
Enderman=Enderman
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=Blaze
|
|||
Chicken=Poulet
|
||||
Cow=Vache
|
||||
Mooshroom=Champimeuh
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Ender Dragon=Ender Dragon
|
||||
Enderman=Enderman
|
||||
Endermite=Endermite
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=Flamor
|
|||
Chicken=Polet
|
||||
Cow=Vacha
|
||||
Mooshroom=Vachairòla
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Ender Dragon=Dragon de Finuèit
|
||||
Enderman=Finuèairi
|
||||
Endermite=Finuèibau
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=Blaze
|
|||
Chicken=Galinha
|
||||
Cow=Vaca
|
||||
Mooshroom=Coguvaca
|
||||
Stalker=Stalker
|
||||
Creeper=Creeper
|
||||
Ender Dragon=Dragão do Fim
|
||||
Enderman=Enderman
|
||||
Endermite=Endermite
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=Ифрит
|
|||
Chicken=Курица
|
||||
Cow=Корова
|
||||
Mooshroom=Грибная корова
|
||||
Stalker=Сталкер
|
||||
Creeper=Крипер
|
||||
Ender Dragon=Дракон Края
|
||||
Enderman=Эндермен
|
||||
Endermite=Эндермит
|
||||
|
|
|
@ -6,7 +6,7 @@ Blaze=
|
|||
Chicken=
|
||||
Cow=
|
||||
Mooshroom=
|
||||
Stalker=
|
||||
Creeper=
|
||||
Ender Dragon=
|
||||
Enderman=
|
||||
Endermite=
|
||||
|
|
|
@ -249,7 +249,6 @@ mcl_mobs.register_mob("mobs_mc:sheep", {
|
|||
})
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
item:add_wear(mobs_mc.shears_wear)
|
||||
tt.reload_itemstack_description(item) -- update tooltip
|
||||
clicker:get_inventory():set_stack("main", clicker:get_wield_index(), item)
|
||||
end
|
||||
return
|
||||
|
|
|
@ -73,7 +73,7 @@ local skeleton = {
|
|||
looting = "common",},
|
||||
|
||||
-- Head
|
||||
-- TODO: Only drop if killed by charged stalker
|
||||
-- TODO: Only drop if killed by charged creeper
|
||||
{name = "mcl_heads:skeleton",
|
||||
chance = 200, -- 0.5% chance
|
||||
min = 1,
|
||||
|
@ -113,8 +113,7 @@ local skeleton = {
|
|||
self.object:set_yaw(minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())))
|
||||
end
|
||||
local dmg = math.random(2, 4)
|
||||
local arrow = self.arrow:match("^(.+)_entity$")
|
||||
mcl_bows.shoot_arrow(arrow, pos, dir, self.object:get_yaw(), self.object, nil, dmg)
|
||||
mcl_bows.shoot_arrow("mcl_bows:arrow", pos, dir, self.object:get_yaw(), self.object, nil, dmg)
|
||||
end
|
||||
end,
|
||||
shoot_interval = 2,
|
||||
|
@ -141,10 +140,10 @@ stray.textures = {
|
|||
"mcl_bows_bow_0.png",
|
||||
},
|
||||
}
|
||||
stray.arrow = "mcl_potions:frost_arrow_entity"
|
||||
-- TODO: different sound (w/ echo)
|
||||
-- TODO: stray's arrow inflicts slowness status
|
||||
table.insert(stray.drops, {
|
||||
name = "mcl_potions:frost_arrow",
|
||||
name = "mcl_potions:slowness_arrow",
|
||||
chance = 2,
|
||||
min = 1,
|
||||
max = 1,
|
||||
|
@ -153,20 +152,13 @@ table.insert(stray.drops, {
|
|||
local chance = 0.5
|
||||
for i = 1, lvl do
|
||||
if chance > 1 then
|
||||
return 1 -- TODO verify this logic, I think this is not how chance works
|
||||
return 1
|
||||
end
|
||||
chance = chance + (1 - chance) / 2
|
||||
end
|
||||
return chance
|
||||
end,
|
||||
})
|
||||
table.insert(stray.drops, {
|
||||
name = "mcl_mobitems:shiny_ice_crystal",
|
||||
chance = 3,
|
||||
min = 1,
|
||||
max = 2,
|
||||
looting = "rare",
|
||||
})
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:stray", stray)
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ mcl_mobs.register_mob("mobs_mc:witherskeleton", {
|
|||
fire_resistant = true,
|
||||
dealt_effect = {
|
||||
name = "withering",
|
||||
level = 1,
|
||||
factor = 1,
|
||||
dur = 10,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -131,7 +131,6 @@ mcl_mobs.register_mob("mobs_mc:snowman", {
|
|||
-- Wear out
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
item:add_wear(mobs_mc.shears_wear)
|
||||
tt.reload_itemstack_description(item) -- update tooltip
|
||||
clicker:get_inventory():set_stack("main", clicker:get_wield_index(), item)
|
||||
end
|
||||
end
|
||||
|
|