Compare commits

..

119 Commits

Author SHA1 Message Date
Dieter44 ffde25c3ce ITEMS/mcl_anvils: fix renaming items w/o desc… 2022-03-08 23:19:18 +01:00
MysticTempest d2e635b13f Rename a few textures to help support texture packs. 2022-03-08 23:19:18 +01:00
Elias Åström 00cb4f6233 Rewrite portal removal to avoid stack overflow
This solves a problem were nether portal removal would trigger deep
recursion through node callbacks.  For large portals this could result
in stack overflow crashes on LuaJIT.  The issue is solved by rewriting
the portal removal to avoid recursion and removing the portal in one
operation using minetest.bulk_set_node.
2022-03-08 23:19:18 +01:00
kabou cff49c015a Fix fire HUD not showing on join while burning.
* Add the fire HUD to a player who joins the server while still burning
  from the previous session.
2022-03-08 23:19:18 +01:00
kabou 03c5f24649 Add comments to mcl_burning.update_hud(). 2022-03-08 23:19:18 +01:00
kabou c83e6c3267 Do not set fire to objects that are already burning.
Changes to mcl_burning.set_on_fire():
* Add logic that only updates the burn time, but skips adding fire
  entities to objects that are already burning.
* Condense code a little, remove single use variables.
* Add a comment to a questionable piece of code.
* Add comments to the function.
2022-03-08 23:19:18 +01:00
kabou 97fb9e5828 Add comments to "storage" table.
* Document attributes of the "storage" table.
2022-03-08 23:19:18 +01:00
kabou 08775b0cc9 Add local copy of minetest.find_nodes_in_area. 2022-03-08 23:19:17 +01:00
kabou ed9a403d78 Make player burning HUD work without csm.
* Add update_hud() function.
* Remove the client channels.
* Unglobalize animation_frames variable.
* Fix bug where player state was not stored on shutdown of singleplayer
  game.
* Remove superfluous sanity_check() function, this code could easily be
  inlined in its only caller.
2022-03-08 23:19:17 +01:00
kabou 3f1247d4cf Fix burning entity animation.
The parameters for the flames sprite were incorrect, causing the reverse
side of the sprite to appear as an opaque black rectangle.  Use the
correct incantation.
2022-03-08 23:19:17 +01:00
Nils Dagsson Moskopp 84fa3db68a Fix TGA file writing on Windows
Before this patch, the tga_encoder mod would write corrupted TGA files
on Windows: Bytes that looked like newlines were replaced by a carriage
return and a newline.
2022-03-08 23:19:17 +01:00
AFCMS 1048b3a38f make all arrow types activate target (+ depends and code fixes) 2022-03-08 23:19:17 +01:00
AFCMS fb35af4dba make lingering potions activate target (+ indentation fix) 2022-03-08 23:19:17 +01:00
AFCMS 1a1c592452 make splash potions activate target 2022-03-08 23:19:17 +01:00
AFCMS 6299cd07fa make fire charges activate target (+ fix possible crash) 2022-03-08 23:19:14 +01:00
AFCMS b0695cf233 make bottle o enchanting activate target 2022-03-08 23:16:54 +01:00
AFCMS 1cf53722a4 make ender pearls activate target 2022-03-08 23:16:54 +01:00
AFCMS bb1586ce72 make eggs activate target 2022-03-08 23:16:54 +01:00
AFCMS a848b9d158 add target help text 2022-03-08 23:16:54 +01:00
AFCMS 8f5bb24771 add target crafting receipe 2022-03-08 23:16:54 +01:00
AFCMS 25f9baff2c make snowballs activate target 2022-03-08 23:16:54 +01:00
AFCMS 01c9fc9652 add target with API 2022-03-08 23:16:54 +01:00
E b737e3bc39 mapgen/mcl_mapgen_core: move set_node decorations a node callback
During the rewrite in 89e55e9065, a queue system was added to allow
using both VoxelManip-based generation steps, and set_node-based steps,
however some set_node-based steps (underground mushrooms, nether
decorations, and structures) were missed and remained in a VoxelManip
step. The result is that the changes made by the set_node stages were
overwritten after the (now-stale) VoxelManip data was committed later.

(ref. Mineclonia/Mineclonia#26)
2022-03-08 23:16:54 +01:00
cora b35959e005 do not randomseed twice 2022-03-08 23:16:54 +01:00
cora 56ec2b8066 fix beds dropping when burning up 2022-03-08 23:16:54 +01:00
cora c0fdd12172 make fire use abms instead of laggy node timers 2022-03-08 23:16:54 +01:00
Nils Dagsson Moskopp 6e3f22c1ca Fix handheld maps not displaying in Minetest 5.5.0
The function dynamic_add_media() was changed in incompatible ways in
several minor versions of Minetest, breaking the display of handheld
maps in Minetest 5.5.0. This patch makes handheld maps display there.

The function was blocking with one argument in Minetest 5.3. It was also
blocking in Minetest 5.4, but took an additional argument for a function
to execute once the media had been received. Calling dynamic_add_media()
with a single argument had been deprecated; a function that did nothing
was provided in mcl_maps to satisfy the changed argument requirements.

In Minetest 5.5, dynamic_add_media() was changed to non-blocking. This
introduced a race condition in mcl_maps, where a client often tried to
display a map before it had received the map texture from the server.

Opening an issue on the Minetest issue tracker led to it being closed in
about 20 minutes: <https://github.com/minetest/minetest/issues/11997>
2022-03-08 23:16:54 +01:00
Nils Dagsson Moskopp 3cfa41de6d Fix redstone comparator flooding crash
Redstone comparators have two modes, comparison mode & subtraction mode.
Before this patch, the functions to turn comparators on or off attempted
to swap nodes with comparators in the same mode, but failed to determine
the correct replacement node, if the existing node was not a comparator.

When a comparator in an on state (e.g. powered by a filled cauldron) was
flooded, the flooding dropped the comparator and replaced the comparator
node that was to be swapped out with air, which lead to a server crash.

This patch changes the functions that turn comparators on or off so they
only swap existing nodes with comparators in the same mode if the name
of the replacement node can be determined – i.e. if it is not nil.
2022-03-08 23:16:54 +01:00
AFCMS 86cf8010af add `.blend2` and `.blend3` files to gitignore 2022-03-08 23:16:54 +01:00
AFCMS b9eb00613c add `.blend1` files to gitignore 2022-03-08 23:16:54 +01:00
AFCMS fe4f18705e remove blender backup file 2022-03-08 23:16:54 +01:00
kabou 178b929c6c Fix hopper breakage in get_item_group return value.
In commit 55009c257e that added vectors to
mcl_composters, mcl_hoppers was accidentally patched with a unrelated
change, updating it to get_item_group().  This mostly works, but in one
particular case the semantics of the return value differs.  Instead of
returning 'nil' it returns '0'.  That altered the evaluation of an if
condition, breaking the abm that sucks in items.

This commit fixes the conditional by explicitly comparing  '~= 0'.
2022-03-08 23:16:54 +01:00
1F616EMO de929349b9 mcl_tools 2022-03-08 23:16:54 +01:00
1F616EMO 2bd8f5bdb7 mcl_flowers 2022-03-08 23:16:54 +01:00
1F616EMO f51e01ae80 mcl_books 2022-03-08 23:16:54 +01:00
1F616EMO 5b75bf74fc mcl_dye 2022-03-08 23:16:54 +01:00
1F616EMO 6fd55fb10c mcl_flowerpots 2022-03-08 23:16:54 +01:00
1F616EMO ec9db5830a mcl_mushrooms 2022-03-08 23:16:54 +01:00
1F616EMO fa8d9e2dc0 mcl_doors 2022-03-08 23:16:54 +01:00
1F616EMO 83b7f03f0d mcl_commands 2022-03-08 23:16:54 +01:00
1F616EMO fd00164bde xpanes 2022-03-08 23:16:54 +01:00
1F616EMO 87dba5f3fb mcl_armor 2022-03-08 23:16:54 +01:00
1F616EMO 5ba99c8a19 mcl_inventory 2022-03-08 23:16:54 +01:00
1F616EMO 6094f115dc mcl_fire 2022-03-08 23:16:54 +01:00
1F616EMO 5cf10b28e9 mcl_fishing 2022-03-08 23:16:54 +01:00
1F616EMO 23f6cdb492 mcl_fences 2022-03-08 23:16:54 +01:00
1F616EMO 7358ef40cc mcl_buckets 2022-03-08 23:16:54 +01:00
1F616EMO 574ca21018 mcl_walls 2022-03-08 23:16:54 +01:00
1F616EMO 2358a8be4f mcl_hoppers 2022-03-08 23:16:54 +01:00
1F616EMO 7e94436cfb mcl_anvils 2022-03-08 23:16:54 +01:00
1F616EMO 2932209f60 mcl_portals 2022-03-08 23:16:54 +01:00
1F616EMO 15a4a1f31f mcl_bows 2022-03-08 23:16:54 +01:00
1F616EMO 968385acd8 mcl_skins 2022-03-08 23:16:54 +01:00
1F616EMO 188dbfc6b9 mcl_throwing 2022-03-08 23:16:54 +01:00
1F616EMO 3efeaf0fe2 mcl_cake 2022-03-08 23:16:54 +01:00
1F616EMO c69cbe85fe mcl_jukebox 2022-03-08 23:16:54 +01:00
1F616EMO fe309e802d mcl_heads 2022-03-08 23:16:54 +01:00
1F616EMO 10b0a86c8e mcl_cauldrons 2022-03-08 23:16:54 +01:00
1F616EMO 7a431037e0 mcl_mobs 2022-03-08 23:16:54 +01:00
1F616EMO 6c9fab1e82 findbiome 2022-03-08 23:16:54 +01:00
1F616EMO bcfddc1b78 mcl_sponges 2022-03-08 23:16:54 +01:00
1F616EMO d1d81d9cc7 mcl_maps 2022-03-08 23:16:54 +01:00
1F616EMO 62e32d6fb2 mcl_brewing 2022-03-08 23:16:54 +01:00
1F616EMO a1213f2465 mcl_signs 2022-03-08 23:16:54 +01:00
1F616EMO f55e1f5a7d mcl_monster_eggs 2022-03-08 23:16:54 +01:00
1F616EMO 8d220039b0 mcl_furnaces 2022-03-08 23:16:54 +01:00
1F616EMO 9035e1292f mcl_hunger 2022-03-08 23:16:54 +01:00
1F616EMO 50f9291469 mcl_tnt 2022-03-08 23:16:54 +01:00
1F616EMO 7681ca8122 mcl_crafting_table 2022-03-08 23:16:54 +01:00
1F616EMO 6ec838c05d mcl_weather 2022-03-08 23:16:54 +01:00
1F616EMO f48e2c691b mcl_structures 2022-03-08 23:16:54 +01:00
1F616EMO c6bbfbe49f mcl_experience 2022-03-08 23:16:54 +01:00
1F616EMO 1dcac179e5 mcl_cocoas 2022-03-08 23:16:54 +01:00
1F616EMO 9d5465a2d2 hudbars 2022-03-08 23:16:54 +01:00
1F616EMO c5f6068a9d mclx_core 2022-03-08 23:16:54 +01:00
1F616EMO 8257e539c4 mcl_paintings 2022-03-08 23:16:54 +01:00
1F616EMO 3e25d63881 mcl_mobspawners 2022-03-08 23:16:54 +01:00
1F616EMO 32c102cf6d mcl_itemframes 2022-03-08 23:16:54 +01:00
1F616EMO 087596ee27 mcl_clock 2022-03-08 23:16:54 +01:00
1F616EMO 8b5946c603 mcl_armor_stand 2022-03-08 23:16:53 +01:00
1F616EMO 8f81d9cc39 mcl_spawn 2022-03-08 23:16:53 +01:00
1F616EMO a196cde3cf mcl_wip 2022-03-08 23:16:53 +01:00
1F616EMO 41c2c1bb54 mclx_fences 2022-03-08 23:16:53 +01:00
1F616EMO 48bd9259f6 mcl_compass 2022-03-08 23:16:53 +01:00
1F616EMO f6743c6a84 lightning 2022-03-08 23:16:53 +01:00
1F616EMO 33b42594a6 mcl_playerplus 2022-03-08 23:16:53 +01:00
1F616EMO 7efea28e55 mcl_torches 2022-03-08 23:16:53 +01:00
1F616EMO 5fad369709 mcl_void_damage 2022-03-08 23:16:53 +01:00
1F616EMO 0acaf04212 mcl_falling_nodes 2022-03-08 23:16:53 +01:00
1F616EMO 2711d5dcc4 mcl_privs 2022-03-08 23:16:53 +01:00
1F616EMO 6677e7f590 screwdriver 2022-03-08 23:16:53 +01:00
1F616EMO 5cb517faba mcl_hbarmor 2022-03-08 23:16:53 +01:00
1F616EMO 86e63b74d7 mcl_explosions 2022-03-08 23:16:53 +01:00
1F616EMO 4c0edaa3f7 mcl_farming 2022-03-08 23:16:53 +01:00
1F616EMO 0228758559 (WIP) mcl_farming 2022-03-08 23:16:53 +01:00
1F616EMO 2e446737cb MORE MESS SORRY XD 2022-03-08 23:16:53 +01:00
1F616EMO 8c17897801 Revert "add translates for `mcl_boats`"
This reverts commit 2ee84f2ad3bd1f067a43c9382d7fc70f30fab9f0.
2022-03-08 23:16:53 +01:00
1F616EMO 25fa3cab03 add translates for `mcl_boats`
Co-authored-by: Emojigit <emojigit@noreply.git.minetest.land>
2022-03-08 23:16:53 +01:00
1F616EMO acbaf0fb65 add translate of README.md 2022-03-08 23:16:53 +01:00
1F616EMO cca4260f1c add translate for `doc_identifier` 2022-03-08 23:16:53 +01:00
1F616EMO 03c7e6df8a add zh_TW translate to `mcl_beds` and `mcl_core` 2022-03-08 23:16:53 +01:00
AFCMS c5e372f825 obtimize icons again
`optipng -strip all -o9`
2022-03-08 23:16:53 +01:00
AFCMS 599c147ec8 make menu icon smaller (128x) 2022-03-08 23:16:53 +01:00
AFCMS d242167048 add @epCode logo 2022-03-08 23:16:53 +01:00
AFCMS dfec4a5422 Revert "move barrels to mcl_chests"
This reverts commit a272322cb5.
2022-03-08 23:16:53 +01:00
AFCMS cfe9067393 apply @kay27 fix to `mcl_util.drop_items_from_meta_container` 2022-03-08 23:16:53 +01:00
AFCMS 1f000e760d move barrels to mcl_chests 2022-03-08 23:16:53 +01:00
AFCMS a7e234765e fix barrel top texture rotation 2022-03-08 23:16:53 +01:00
AFCMS 29e9f45b6e cleanup barrel TODO list 2022-03-08 23:16:53 +01:00
AFCMS 6e0adcedef simplify random dropping position code 2022-03-08 23:16:53 +01:00
AFCMS 4a8f73dd10 rename `mcl_util.drop_items_container_get` to `drop_items_from_meta_container` 2022-03-08 23:16:53 +01:00
AFCMS 3c2182a677 add barrel tooltip help text 2022-03-08 23:16:53 +01:00
AFCMS 87fde16918 add doc description to barrels 2022-03-08 23:16:53 +01:00
AFCMS 3eb6753d92 fix broken tree placement 2022-03-08 23:16:53 +01:00
AFCMS 80bfb18d35 fixes 2022-03-08 23:16:53 +01:00
AFCMS 2d07f85697 add craft 2022-03-08 23:16:53 +01:00
AFCMS eeb288dca7 add barrel 2022-03-08 23:16:53 +01:00
cora 0886f16618 Do not send redundant bone position and properties 2022-03-08 23:16:53 +01:00
kabou 77c893453f Reinstate nil check in on_place_bucket.
* Oops, nil checks on registered_node[name] were erroneously optimized
  away, risking crashes.  Reinstate those checks.
2022-03-08 23:16:52 +01:00
2061 changed files with 22785 additions and 53587 deletions

View File

@ -1,12 +0,0 @@
root = true
[*]
end_of_line = lf
[*.lua]
charset = utf8
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
keep_one_space_between_table_and_bracket = false
spaces_around_operators = true

1
.gitignore vendored
View File

@ -3,4 +3,3 @@
*.blend1
*.blend2
*.blend3
/.idea/

2
API.md
View File

@ -42,7 +42,7 @@ A lot of things are possible by using one of the APIs in the mods. Note that not
* Buckets: `ITEMS/mcl_buckets`
* Dispenser support: `ITEMS/REDSTONE/mcl_dispensers`
### Mobs
## Mobs
* Mobs: `ENTITIES/mcl_mobs`
MineClone 2 uses its own mobs framework, called “Mobs Redo: MineClone 2 Edition” or “MRM” for short.

View File

@ -2,7 +2,7 @@
So you want to contribute to MineClone2?
Wow, thank you! :-)
MineClone2 is maintained by Nicu and Cora. If you have any
MineClone2 is maintained by Nicu and Fleckenstein. If you have any
problems or questions, contact us (See Links section below).
You can help with MineClone2's development in many different ways,
@ -11,9 +11,18 @@ whether you're a programmer or not.
## MineClone2's development target is to...
- Crucially, create a stable, moddable, free/libre clone of Minecraft
based on the Minetest engine with polished features, usable in both
singleplayer and multiplayer. Currently, a lot of Minecraft features
are already implemented.
Polishing existing features is always welcome.
singleplayer and multiplayer. Currently, most of **Minecraft Java
Edition 1.12.2** features are already implemented and polishing existing
features are prioritized over new feature requests.
- With lessened priority yet strictly, implement features targetting
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
by the Minetest Engine). This means features in parity with the listed
Minecraft experiences are prioritized over those that don't fulfill this
scope.
- Optionally, create a performant experience that will run relatively
well on really low spec computers. Unfortunately, due to Minecraft's
mechanisms and Minetest engine's limitations along with a very small
playerbase on low spec computers, optimizations are hard to investigate.
## Links
* [Mesehub](https://git.minetest.land/MineClone2/MineClone2)
@ -36,10 +45,8 @@ 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/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
@ -51,10 +58,12 @@ you can report a bug or request a feature.
discussion.
* Choose a descriptive title (e.g. not just "crash", "bug" or "question"
).
* Please write in plain, understandable English. It will be easier to
communicate.
* Please start the issue title with a capital letter.
* Always check the currently opened issues before creating a new one.
Try not to report bugs that have already been reported or request features
that already have been requested. This can often be ambiguous though.
If in doubt open an issue!
Don't report bugs that have already been reported or request features
that already have been requested.
* If you know about Minetest's inner workings, please think about
whether the bug / the feature that you are reporting / requesting is
actually an issue with Minetest itself, and if it is, head to the
@ -64,9 +73,6 @@ instead.
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)
### Reporting bugs
* A bug is an unintended behavior or, in the worst case, a crash.
However, it is not a bug if you believe something is missing in the
@ -105,28 +111,23 @@ would report issues will pull requests similar to when you were
reporting bugs that are the mainline (See Reporting bugs section). You
can find currently open pull requests here:
<https://git.minetest.land/MineClone2/MineClone2/pulls>. Note that pull
requests that start with a `WIP:` are not done yet and therefore 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.
requests that start with a `WIP:` are not done yet, and therefore might
not work, so it's not very useful to try them out yet.
### Contributing assets
Due to license problems, MineClone2 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
Due to license problems, MineClone2 unfortunately cannot use
Minecraft's assets, therefore we are always looking for asset
contributions. To contribute assets, it can be useful to learn git
basics and read the section for Programmers of this document, however
this is not required. It's also a good idea to join the Discord server
(or alternatively IRC or Matrix).
#### Textures
For textures we use the Pixel Perfection texture pack. For older Minecraft
features that is mostly enough but a lot of the newer textures in it are
copies or slight modifications of the original MC textures so great caution
needs to be taken when using any textures coming from Minecraft texture
packs.
If you want to make such contributions, join our Discord server. Demands
for textures will be communicated there.
For textures we use the Pixel Perfection texture pack. This is mostly
enough; however in some cases - e.g. for newer Minecraft features, it's
useful to have texture artists around. If you want to make such
contributions, join our Discord server. Demands for textures will be
communicated there.
#### Sounds
MineClone2 currently does not have a consistent way to handle sounds.
@ -250,25 +251,16 @@ of the results)
* [Official Minecraft Wiki](https://minecraft.fandom.com/wiki/Minecraft_Wiki)
(Include a link to the specific page you used)
### Guidelines
### Stick to our guidelines
#### Git Guidelines
* 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
* Submodules should only be used if a) upstream is highly reliable and
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 and never contain mcl2 specific
issueids - there are other projects who might use commits from mcl2 and
it will confuse their issue trackers.
* 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)
* We use merge rather than rebase or squash merge
* We don't use git submodules.
* Your commit names should be relatively descriptive, e.g. when saying
"Fix #issueid", the commit message should also contain the title of the
issue.
* Try to keep your commits as atomic as possible (advise, but completely
optional)
#### Code Guidelines
* Each mod must provide `mod.conf`.
@ -351,23 +343,36 @@ Active and trusted contributors are often granted write access to the
MineClone2 repository.
#### Developer responsibilities
- If you have developer privileges you can just open a new branch 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 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 merge PRs. A PR needs
at least one approval (by someone else than the author) but the maintainers
are usually relatively quick to react to new submissions.
- You should not push things directly to
MineClone2 master - rather, do your work on a branch on your private
repository, then create a pull request. This way other people can review
your changes and make sure they work before they get merged.
- Merge PRs only when they have recieved the necessary feedback and have
been tested by at least two different people (including the author of
the pull request), to avoid crashes or the introduction of new bugs.
- You may also be assigned to issues or pull
requests as a developer. In this case it is your responsibility to fix
the issue / review and merge the pull request when it is ready. You can
also unassign yourself from the issue / PR if you have no time or don't
want to take care of it for some other reason. After all, everyone is a
volunteer and we can't expect you to do work that you are not interested
in. **The important thing is that you make sure to inform us if you
won't take care of something that has been assigned to you.**
- Please assign yourself to something that you want to work on to avoid
duplicate work.
- As a developer, it should be easy to reach you about your work. You
should be in at least one of the public MineClone2 discussion rooms -
preferrably Discord, but if you really don't like Discord, Matrix
or IRC are fine too.
### Maintainer status
Maintainers carry the main responsibility for the project.
#### Maintainer responsibilities
- Making sure issues are addressed and pull requests are reviewed and
merged.
merged, by assigning either themselves or Developers to issues / PRs
- Making releases
- Making sure guidelines are kept
- Making project decisions based on community feedback
- Granting/revoking developer access
- Enforcing the code of conduct (See CODE_OF_CONDUCT.md)
@ -375,8 +380,8 @@ merged.
- Resolving conflicts and problems within the community
#### Current maintainers
* Cora - responsible for gameplay review, publishing releases,
technical guidelines
* Fleckenstein - responsible for gameplay review, publishing releases,
technical guidelines and issue/PR delegation
* Nicu - responsible for community related issues
#### Release process

View File

@ -6,15 +6,10 @@
## Creator of MineClone2
* Wuzzy
## Maintainers
* AncientMariner
* Nicu
## Previous Maintainers
* Fleckenstein
* jordan4ibanez
* cora
* Nicu
* kay27
## Developers
* bzoss
@ -27,15 +22,8 @@
* aligator
* Code-Sploit
* NO11
* kabou
* rudzik8
* chmodsayshello
* PrairieWind
* RandomLegoBrick
* SumianVoice
* MrRar
* talamh
* Faerraven
* cora
* jordan4ibanez
## Contributors
* Laurent Rocher
@ -69,6 +57,7 @@
* Benjamin Schötz
* Doloment
* Sydney Gems
* talamh
* Emily2255
* Emojigit
* FinishedFragment
@ -79,22 +68,6 @@
* Sven792
* aldum
* Dieter44
* Pepebotella
* MrRar
* Lazerbeak12345
* mrminer
* Thunder1035
* opfromthestart
* snowyu
* FaceDeer
* Faerraven / Michieal
* FossFanatic
* Herbert West
* GuyLiner
* 3raven
* anarquimico
* TheOnlyJoeEnderman
* Ranko Saotome
## MineClone5
* kay27
@ -102,18 +75,6 @@
* epCode
* NO11
* j45
* chmodsayshello
* 3raven
* PrairieWind
* Gustavo6046 / wallabra
* CableGuy67
* MrRar
## Mineclonia
* erlehmann
* Li0n
* E
* n_to
## Original Mod Authors
* Wuzzy
@ -145,7 +106,6 @@
* 4Evergreen4
* jordan4ibanez
* paramat
* cora
## 3D Models
* 22i
@ -161,10 +121,6 @@
* yutyo
* NO11
* kay27
* MysticTempest
* RandomLegoBrick
* cora
* Faerraven / Michieal
## Translations
* Wuzzy
@ -174,10 +130,6 @@
* pitchum
* todoporlalibertad
* Marcin Serwin
* Pepebotella
* Emojigit
* snowyu
* 3raven
## Funders
* 40W
@ -185,6 +137,5 @@
## Special thanks
* celeron55 for creating Minetest
* Jordach for the jukebox music compilation from Big Freaking Dig
* wsor for working tirelessly in the shadows for the good of all of us, particularly helping with solving contentDB and copyright issues.
* The workaholics who spent way too much time writing for the Minecraft Wiki. It's an invaluable resource for creating this game
* Notch and Jeb for being the major forces behind Minecraft

View File

@ -41,7 +41,6 @@ Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times
* `flammable=-1` Does not get destroyed by fire
* `fire_encouragement`: How quickly this block catches fire
* `fire_flammability`: How fast the block will burn away
* `path_creation_possible=1`: Node can be turned into grass path by using a shovel on it
* `spreading_dirt_type=1`: A dirt-type block with a cover (e.g. grass) which may spread to neighbor dirt blocks
* `dirtifies_below_solid=1`: This node turns into dirt immediately when a solid or dirtifier node is placed on top
* `dirtifier=1`: This node turns nodes the above group into dirt when placed above
@ -57,7 +56,6 @@ Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times
* `no_eat_delay=1`: Only for foodstuffs. When eating this, all eating delays are ignored.
* `can_eat_when_full=1`: Only for foodstuffs. This item can be eaten when the user has a full hunger bar
* `attached_node_facedir=1`: Like `attached_node`, but for facedir nodes
* `supported_node=1`: Like `attached_node`, but can be placed on any nodes that do not have the `drawtype="airlike"` attribute.
* `cauldron`: Cauldron. 1: Empty. 2-4: Water height
* `anvil`: Anvil. 1: No damage. 2-3: Higher damage levels
* `no_rename=1`: Item cannot be renamed by anvil
@ -73,9 +71,6 @@ Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times
* `coral_block=X`: Coral block (1 = alive, 2 = dead)
* `coral_species=X`: Specifies the species of a coral; equal X means equal species
* `set_on_fire=X`: Sets any (not fire-resistant) mob or player on fire for X seconds when touching
* `compostability=X`: Item can be used on a composter block; X (1-100) is the % chance of adding a level of compost
* `leaves=X`: Node will spotaneously decay if no tree trunk nodes remain within 6 blocks distance.
* `leaves_orphan`: See above, these nodes are in the process of decayed.
#### Footnotes
@ -104,8 +99,6 @@ Please read <http://minecraft.gamepedia.com/Breaking> to learn how digging times
* `water_bucket=1`: Bucket containing a liquid of group “water”
* `enchantability=X`: How good the enchantments are the item gets (1 equals book)
* `enchanted=1`: The item is already enchanted, meaning that it can't be enchanted using an enchanting table
* `cobble=1`: Cobblestone of any kind
* `soul_block`: Fire burning on these blocks turns to soul fire, can be used to craft soul torch
### Material groups
@ -207,9 +200,6 @@ These groups are used mostly for informational purposes
* `building_block=1`: Block is a building block
* `deco_block=1`: Block is a decorational block
* `blast_furnace_smeltable=1` : Item or node is smeltable by a blast furnace
* `smoker_cookable=1` : Food is cookable by a smoker.
## Fake item groups
These groups put similar items together which should all be treated by the gameplay or the GUI as a single item.

View File

@ -2,7 +2,7 @@
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
Developed by many people. Not developed or endorsed by Mojang AB.
Version: 0.82 (in development)
Version: 0.72.0 (in development)
### Gameplay
You start in a randomly-generated world made entirely of cubes. You can explore
@ -66,7 +66,7 @@ Use the `/giveme` chat command to obtain them. See the in-game help for
an explanation.
## Installation
This game requires [Minetest](http://minetest.net) to run (version 5.4.1 or
This game requires [Minetest](http://minetest.net) to run (version 5.3.0 or
later). So you need to install Minetest first. Only stable versions of Minetest
are officially supported.
There is no support for running MineClone2 in development versions of Minetest.
@ -91,11 +91,11 @@ The MineClone2 repository is hosted at Mesehub. To contribute or report issues,
## Target
- Crucially, create a stable, moddable, free/libre clone of Minecraft
based on the Minetest engine with polished features, usable in both
singleplayer and multiplayer. Currently, a lot of **Minecraft Java
Edition** features are already implemented and polishing existing
singleplayer and multiplayer. Currently, most of **Minecraft Java
Edition 1.12.2** features are already implemented and polishing existing
features are prioritized over new feature requests.
- With lessened priority yet strictly, implement features targetting
**Current Minecraft versions + OptiFine** (OptiFine only as far as supported
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
by the Minetest Engine). This means features in parity with the listed
Minecraft experiences are prioritized over those that don't fulfill this
scope.
@ -108,7 +108,8 @@ playerbase on low spec computers, optimizations are hard to investigate.
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 MineClone2 in production, the master branch is usually relatively stable. The testing branch often features some experimental PRs and should be considered less stable.
If you want to use the git version of MineClone2 in production, consider using the production branch.
It is updated weekly and contains relatively stable code for servers.
The following main features are available:
@ -123,7 +124,7 @@ The following main features are available:
* Most blocks in the overworld
* Water and lava
* Weather
* 28 biomes + 5 Nether Biomes
* 28 biomes
* The Nether, a fiery underworld in another dimension
* Redstone circuits (partially)
* Minecarts (partial)
@ -161,7 +162,7 @@ The following features are incomplete:
* Special minecarts
* A couple of non-trivial blocks and items
Bonus features (not found in Minecraft):
Bonus features (not found in Minecraft 1.12):
* Built-in crafting guide which shows you crafting and smelting recipes
* In-game help system containing extensive help about gameplay basics, blocks, items and more
@ -174,9 +175,6 @@ Bonus features (not found in Minecraft):
* Nether Brick Fence Gate
* Red Nether Brick Fence
* Red Nether Brick Fence Gate
* Structure replacements - these small variants of Minecraft structures serve as replacements until we can get large structures working:
* Woodland Cabin (Mansions)
* Nether Outpost (Fortress)
Technical differences from Minecraft:

View File

@ -1,170 +0,0 @@
# MineClone2
Un jeu non-officiel similaire à Minecraft pour Minetest. Forké depuis Mineclone par davedevils. Développé par de nombreuses personnes. Ni développé ou supporté par Mojang AB.
Version: 0.79 (en dévelopment)
### 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.
### Résumé du Gameplay
* Jeu de type bac-à-sable, sans objetifs
* Survie : combattre des monstres hostiles et la faim
* Creuser pour du minerai et d'autres trésors
* Magie : gagner de l'expérience et enchanter les outils
* Utiliser les blocs ramassés pour construire de magnifiques bâtiments, votre imagination est la limite
* Ramasser des fleurs (et d'autres sources de teinture) et colorez votre monde
* Trouvez des graines et commencez à cultiver
* Trouvez ou fabriquez des centaines d'objets
* Construisez un réseau ferroviaire complexe et amusez vous avec les wagonnets
* En mode créatif vous pouvez construire presque n'importe quoi gratuitement et sans limite
## Comment jouer (démarrer rapidement)
### 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
* Placer les 4 planches de bois dans la grille 2x2 et **fabriquez une table d'artisanat**
* **Cliquez droit la table d'artisanat** (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 !
* Continuez à jouer comme vous voulez. Amusez vous !
### Agriculture
* Trouvez des graines
* Fabriquez une houe
* Cliquez droit 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 proche de l'eau deviennent humides et accélèrent la croissance
### Four
* Fabriquer un Four
* Le four permet d'obtenir plus d'objets
* L'emplacement du haut doit contienir un objet fondable (par ex : minerai de fer)
* L'emplacement du bas doit contienir un objet combustible (par ex : charbon)
* Voir le guide d'artisanat pour en apprendre plus sur les objets fondables et combustibles
### Aide supplémentaire
Plus d'aide à propos du jeu, des blocs, objets et plus encore peuvent être trouvés dans le jeu. Vous pouvez accéder à l'aide depuis le menu inventaire.
### Objets spéciaux
Les objets suivants sont intéressants pour le mode Créatif et pour les constructeurs de cartes d'aventure. Ils ne peuvent être obtenus dans le jeu ou dans l'inventaire créatif.
* Barrière : `mcl_core:barrier`
Utilisez la commande de chat `/giveme` pour les obtenir. Voir l'aide interne au jeu pour une explication.
## 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 MineClone2 dans les versions développement de Minetest.
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 MineClone2 est hébergé sur Mesehub. Pour contribuer ou rapporter des problèmes, aller là-bas.
* Mesehub: <https://git.minetest.land/MineClone2/MineClone2>
* Discord: <https://discord.gg/xE4z8EEpDC>
* YouTube <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
* IRC: <https://web.libera.chat/#mineclone2>
* Matrix: <https://app.element.io/#/room/#mc2:matrix.org>
* Reddit: <https://www.reddit.com/r/MineClone2/>
* Minetest forums: <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
* ContentDB: <https://content.minetest.net/packages/wuzzy/mineclone2/>
* OpenCollective: <https://opencollective.com/mineclone2>
## Objectif
* Créer un clone stable, moddable, libre et gratuit basé sur le moteur de jeu Minetest avec des fonctionalités abouties, utilisable à la fois en mode solo et multijoueur. Actuellement, beaucoup des fonctionalité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 fonctionalités des versions **Minecraft + OptiFine** (OtiFine autant que supporté par le moteur Minetest). Cela signifie que les fonctionalité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 à basse performances, les optimisations sont difficiles à explorer.
## Statut de complétion
Ce jeu est actuellement au stade **beta**.
Il est jouable mais incomplet en fonctionalité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 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 fonctionalités suivantes sont disponibles :
* Outils, armes
* Armure
* Système de fabrication : grille 2x2, table d'artisanat (grille 3x3), four, incluant un guide de fabrication
* Coffres, grands coffres, coffre ender, boite de shulker
* Fours, entonnoirs
* Faim
* La plupart des monstres et animaux
* Tout les minerais de Minecraft
* La plupart des blocs de l'overworld
* Eau et lave
* Météo
* 28 biomes + 5 biomes du nether
* Le Nether, monde souterrain brûlant dans une autre dimension
* Circuits Redstone (partiel)
* Effets de Statut (partiel)
* Expérience
* Enchantement
* Brassage, potions, flèches trempées (partiel)
* Bâteaux
* Feu
* Blocs de construction : escaliers, dalles, portes, trappes, barrière, portillon, muret
* Horloge
* Boussole
* Eponge
* Bloc de slime
* Petites plantes et pousses
* Teintures
* Bannières
* Blocs de décoration : verre, verre teinté, vitres, barres de fer, terre cuites (et couleurs), têtes et plus
* Cadres d'objets
* Juke-boxes
* Livres pour écrire
* Commandes
* Villages
* L'End
* et plus !
Les fonctionalités suivantes sont incomplètes :
* certains monstres et animaux
* certains composants de Redstone
* Wagonnets spéciaux
* quelques blocs et objets non-triviaux
Fonctionalités bonus (absentes de Minecraft) :
* Guide d'artisanat intégré au jeu qui montre les recettes d'artisanat et de cuisson
* Système d'aide intégré au jeu contenant des informations à propos des techniques de base, blocs, objets et plus
* Recettes d'artisanat temporaires. Elles existent uniquement pour rendre des objets accessibles qui ne le seraient pas autrement sauf en mode créatif. Elles seront retirées au cours de l'avancement du développement et de l'ajout de nouvelles fonctionalités.
* Pousses dans les coffres en mapgen v6
* Entièrement moddable (grâce la puissante API lua de Minetest)
* Nouveaux blocs et objets :
* Outil de recherche, montre l'aide de ce qu'il touche
* Plus de dalles et d'escaliers
* Portillon en briques du Nether
* Barrière en briques du Nether rouges
* Portillon en briques du Nether rouges
* Structures de remplacement - ces petites variantes de structures de Minecraft servent de remplacement en attendant qu'on arrive à en faire fonctionner de plus grandes :
* Cabine dans les bois (Manoir des bois)
* Avant-poste du Nether (Forteresse)
Différences techniques avec Minecraft :
* Limite en hauteur de 31000 blocs (bien plus grand que Minecraft)
* Taille horizontale du monde 62000×62000 blocs (bien plus petit que Minecraft mais toujours très grand)
* Toujours assez incomplet et buggé
* Des blocs, objets, ennemis et fonctionalités manquent
* Quelques objets ont des noms légèrement différents pour être plus faciles à distinguer
* Des musiques différentes pour le juke-boxe
* Des textures différentes (Pixel Perfection)
* Des sons différents (sources diverses)
* Un moteur de jeu différent (Minetest)
* Des bonus cachés différents
...et enfin MineClone2 est un logiciel libre !
## Autres fichiers readme
* `LICENSE.txt`: Le texte de la license GPLv3
* `CONTRIBUTING.md`: Information pour ceux qui veulent contribuer
* `API.md`: Pour les modders Minetest qui veulent modder ce jeu
* `LEGAL.md`: Information légale
* `CREDITS.md`: Liste des contributeurs

View File

@ -1,19 +0,0 @@
#File to document release steps with a view to evolving into a script
#Update CREDITS.md
#Update version in README.md (soon to be game.conf from of 0.82.0)
lua tools/generate_ingame_credits.lua
git add CREDITS.md
git add mods/HUD/mcl_credits/people.lua
git add README.md
# To uncomment when applicable
#git add game.conf
git commit -m "Pre-release update credits and set version 0.81.1"
git tag 0.81.1
git push origin 0.81.1

View File

@ -1,4 +1,2 @@
title = MineClone 2
name = MineClone 2
description = A survival sandbox game. Survive, gather, hunt, build, explore, and do much more.
disallowed_mapgens = v6
version=MCL2-0.82-indev

View File

@ -1,98 +1,29 @@
-- Overrides the builtin minetest.check_single_for_falling.
-- 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
-- have the "airlike" drawtype. Carpets are an example of this type.
local pairs = pairs
local math = math
local vector = vector
local facedir_to_dir = minetest.facedir_to_dir
local get_item_group = minetest.get_item_group
local remove_node = minetest.remove_node
local get_node = minetest.get_node
local get_meta = minetest.get_meta
local registered_nodes = minetest.registered_nodes
local get_node_drops = minetest.get_node_drops
local add_item = minetest.add_item
-- drop_attached_node(p)
--
-- This function is copied verbatim from minetest/builtin/game/falling.lua
-- We need this to do the exact same dropping node handling in our override
-- minetest.check_single_for_falling() function as in the builtin function.
--
---@param p Vector
local function drop_attached_node(p)
local n = get_node(p)
local drops = get_node_drops(n, "")
local def = registered_nodes[n.name]
if def and def.preserve_metadata then
local oldmeta = get_meta(p):to_table().fields
-- Copy pos and node because the callback can modify them.
local pos_copy = vector.copy(p)
local node_copy = { name = n.name, param1 = n.param1, param2 = n.param2 }
local drop_stacks = {}
for k, v in pairs(drops) do
drop_stacks[k] = ItemStack(v)
end
drops = drop_stacks
def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
end
if def and def.sounds and def.sounds.fall then
minetest.sound_play(def.sounds.fall, { pos = p }, true)
end
remove_node(p)
for _, item in pairs(drops) do
local pos = vector.offset(p,
math.random() / 2 - 0.25,
math.random() / 2 - 0.25,
math.random() / 2 - 0.25
)
add_item(pos, item)
end
end
-- minetest.check_single_for_falling(pos)
--
-- * causes an unsupported `group:falling_node` node to fall and causes an
-- unattached `group:attached_node` or `group:attached_node_facedir` node
-- or unsupported `group:supported_node` node to drop.
-- * does not spread these updates to neighbours.
--
-- Returns true if the node at <pos> has spawned a falling node or has been
-- dropped as item(s).
--
local original_function = minetest.check_single_for_falling
function minetest.check_single_for_falling(pos)
if original_function(pos) then
return true
end
local node = get_node(pos)
local ret_o = original_function(pos)
local ret = false
local node = minetest.get_node(pos)
if get_item_group(node.name, "attached_node_facedir") ~= 0 then
local dir = facedir_to_dir(node.param2)
if dir then
if get_item_group(get_node(vector.add(pos, dir)).name, "solid") == 0 then
drop_attached_node(pos)
return true
remove_node(pos)
local drops = minetest.get_node_drops(node.name, "")
for dr=1, #drops do
minetest.add_item(pos, drops[dr])
end
ret = true
end
end
end
if get_item_group(node.name, "supported_node") ~= 0 then
local def = registered_nodes[get_node(vector.offset(pos, 0, -1, 0)).name]
if def and def.drawtype == "airlike" then
drop_attached_node(pos)
return true
end
end
return false
return ret_o or ret
end

View File

@ -12,10 +12,9 @@ mcl_damage = {
drown = {bypasses_armor = true},
starve = {bypasses_armor = true, bypasses_magic = true},
cactus = {},
sweet_berry = {},
fall = {bypasses_armor = true},
fly_into_wall = {bypasses_armor = true}, -- unused
out_of_world = {bypasses_armor = true, bypasses_magic = true, bypasses_invulnerability = true, bypasses_totem = true},
out_of_world = {bypasses_armor = true, bypasses_magic = true, bypasses_invulnerability = true},
generic = {bypasses_armor = true},
magic = {is_magic = true, bypasses_armor = true},
dragon_breath = {is_magic = true, bypasses_armor = true}, -- this is only used for dragon fireball; dragon fireball does not actually deal impact damage tho, so this is unreachable
@ -34,8 +33,6 @@ mcl_damage = {
}
}
local damage_enabled = minetest.settings:get_bool("enabled_damage",true)
function mcl_damage.register_modifier(func, priority)
table.insert(mcl_damage.modifiers, {func = func, priority = priority or 0})
end
@ -81,7 +78,7 @@ function mcl_damage.from_punch(mcl_reason, object)
mcl_reason.type = "arrow"
elseif luaentity._is_fireball then
mcl_reason.type = "fireball"
elseif luaentity.is_mob then
elseif luaentity._cmi_is_mob then
mcl_reason.type = "mob"
end
mcl_reason.source = mcl_reason.source or luaentity._source_object
@ -142,7 +139,6 @@ function mcl_damage.register_type(name, def)
end
minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
if not damage_enabled then return 0 end
if hp_change < 0 then
if player:get_hp() <= 0 then
return 0
@ -153,7 +149,6 @@ minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
end, true)
minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
if not damage_enabled then return 0 end
if player:get_hp() > 0 then
mt_reason.approved = true
if hp_change < 0 then
@ -166,9 +161,9 @@ minetest.register_on_dieplayer(function(player, mt_reason)
if mt_reason.approved then
mcl_damage.run_death_callbacks(player, mcl_damage.from_mt(mt_reason))
end
minetest.log("action","Player "..player:get_player_name().." died at "..minetest.pos_to_string(vector.round(player:get_pos())))
end)
minetest.register_on_mods_loaded(function()
table.sort(mcl_damage.modifiers, function(a, b) return a.priority < b.priority end)
end)

View File

@ -1,27 +0,0 @@
## mcl_events
### Registering Events
`mlc_events.register_event("name",def)`
#### Event Definition
{
stage = 0,
max_stage = 1,
percent = 100,
bars = {},
completed = false,
cond_start = function() end,
--return table of paramtables e.g. { { player = playername, pos = position, ... } }, custom parameters will be passed to the event object/table
on_step = function(event) end,
--this function is run every game step when the event is active
on_start = function(event) end,
-- this function is run when the event starts
on_stage_begin = function(event) end,
-- this function runs when a new stage of the event starts
cond_progress = function(event) end, --return false or next stage id
--this function checks if the event should progress to the next (or any other) stage
cond_complete = function(event) end,
--return true if event finished successfully
}
### Debugging
* /event_start <event> -- starts the given event at the current player coordinates

View File

@ -1,155 +0,0 @@
mcl_events = {}
mcl_events.registered_events = {}
local disabled_events = minetest.settings:get("mcl_disabled_events")
if disabled_events then disabled_events = disabled_events:split(",")
else disabled_events = {} end
local DBG = minetest.settings:get_bool("mcl_logging_event_api",false)
local active_events = {}
local event_tpl = {
stage = 0,
max_stage = 1,
percent = 100,
bars = {},
completed = false,
cond_start = function(event) end, --return table of positions
on_step = function(event) end,
on_start = function(event) end,
on_stage_begin = function(event) end,
cond_progress = function(event) end, --return next stage
cond_complete = function(event) end, --return success
}
local function mcl_log(m,l)
if DBG then
if not l then l = "action" end
minetest.log(l,"[mcl_events] "..m)
end
end
function mcl_events.register_event(name,def)
if table.indexof(disabled_events,name) ~= -1 then return end
mcl_events.registered_events[name] = def
mcl_events.registered_events[name].name = name
end
local function addbars(self)
if not self.enable_bossbar then return end
for _,player in pairs(minetest.get_connected_players()) do
if vector.distance(self.pos,player:get_pos()) < 64 then
local bar = mcl_bossbars.add_bar(player, {color = "red", text = self.readable_name .. ": Wave "..self.stage.." / "..self.max_stage, percentage = self.percent }, true,1)
table.insert(self.bars,bar)
end
end
end
local function start_event(p,e)
mcl_log("[mcl_events] Event started: "..e.readable_name.." at "..minetest.pos_to_string(vector.round(p.pos)))
local idx = #active_events + 1
active_events[idx] = table.copy(e)
setmetatable(active_events[idx],{__index = event_tpl})
for k,v in pairs(p) do active_events[idx][k] = v end
active_events[idx].stage = 0
active_events[idx].percent = 100
active_events[idx].bars = {}
active_events[idx].time_start = os.time()
if active_events[idx].on_start then
active_events[idx]:on_start(p.pos)
end
addbars(active_events[idx])
end
local function finish_event(self,idx)
mcl_log("[mcl_events] Finished: "..self.readable_name.." at "..minetest.pos_to_string(vector.round(self.pos)))
if self.on_complete then self:on_complete() end
for _,b in pairs(self.bars) do
mcl_bossbars.remove_bar(b)
end
table.remove(active_events,idx)
end
local etime = 0
function check_events(dtime)
--process active events
for idx,ae in pairs(active_events) do
if ae.cond_complete and ae:cond_complete() then
ae.finished = true
finish_event(ae,idx)
elseif not ae.cond_complete and ae.max_stage and ae.max_stage <= ae.stage then
ae.finished = true
finish_event(ae,idx)
elseif not ae.finished and ae.cond_progress then
local p = ae:cond_progress()
if p == true then
ae.stage = ae.stage + 1
if ae:on_stage_begin() == true then
mcl_log("[mcl_events] Event "..ae.readable_name.." at "..minetest.pos_to_string(vector.round(ae.pos)).." failed at stage_begin of stage "..ae.stage )
active_events[idx] = nil
end
elseif tonumber(p) then
ae.stage = tonumber(p) or ae.stage + 1
ae:on_stage_begin()
end
elseif not ae.finished and ae.on_step then
ae:on_step(dtime)
end
addbars(ae)
end
-- check if a new event should be started
etime = etime - dtime
if etime > 0 then return end
etime = 10
for _,e in pairs(mcl_events.registered_events) do
local pp = e.cond_start()
if pp then
--minetest.log("It's gonna start the raid maybe")
for _,p in pairs(pp) do
local start = true
if e.exclusive_to_area then
for _,ae in pairs(active_events) do
if e.name == ae.name and vector.distance(p.pos,ae.pos) < e.exclusive_to_area then start = false end
end
end
if start then
--minetest.log("It's gonna start the raid definitely")
start_event(p,e)
elseif DBG then
mcl_log("[mcl_events] Event "..e.readable_name.." already active at "..minetest.pos_to_string(vector.round(p.pos)))
end
end
else
--minetest.log("Do not start this raid")
end
end
for idx,ae in pairs(active_events) do
local player_near = false
for _,pl in pairs(minetest.get_connected_players()) do
if ae.pos and vector.distance(pl:get_pos(),ae.pos) < 64 then player_near = true end
end
if ae.pos and not player_near then
mcl_log("[mcl_events] Event "..ae.readable_name.." at "..minetest.pos_to_string(vector.round(ae.pos)).." aborted - no players near." )
active_events[idx] = nil
end
end
end
minetest.register_globalstep(check_events)
mcl_info.register_debug_field("Active Events",{
level = 4,
func = function(pl,pos)
return tostring(#active_events)
end
})
minetest.register_chatcommand("event_start",{
privs = {debug = true},
description = "Debug command to start events",
func = function(pname,param)
local p = minetest.get_player_by_name(pname)
local evdef = mcl_events.registered_events[param]
if not evdef then return false,"Event "..param.." doesn't exist.'" end
start_event({pos=p:get_pos(),player=pname,factor=1},evdef)
return true,"Started event "..param
end,
})

View File

@ -1,3 +0,0 @@
name = mcl_events
author = cora
depends = mcl_mobs,mcl_bossbars, mcl_info

View File

@ -130,10 +130,10 @@ local function add_particles(pos, radius)
time = 0.125,
minpos = pos,
maxpos = pos,
minvel = vector.new(-radius, -radius, -radius),
maxvel = vector.new(radius, radius, radius),
minacc = vector.zero(),
maxacc = vector.zero(),
minvel = {x = -radius, y = -radius, z = -radius},
maxvel = {x = radius, y = radius, z = radius},
minacc = vector.new(),
maxacc = vector.new(),
minexptime = 0.5,
maxexptime = 1.0,
minsize = radius * 0.5,
@ -207,7 +207,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
local npos_z = math.floor(rpos_z + 0.5)
local npos = { x = npos_x, y = npos_y, z = npos_z }
local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride +
npos_x - emin_x + 1
npos_x - emin_x + 1
local cid = data[idx]
local br = node_blastres[cid] or INDESTRUCT_BLASTRES
@ -288,7 +288,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
rdir_y = rdir_y / rdir_len
rdir_z = rdir_z / rdir_len
for i = 0, rdir_len / STEP_LENGTH do
for i=0, rdir_len / STEP_LENGTH do
rpos_x = rpos_x + rdir_x * STEP_LENGTH
rpos_y = rpos_y + rdir_y * STEP_LENGTH
rpos_z = rpos_z + rdir_z * STEP_LENGTH
@ -296,7 +296,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
local npos_y = math.floor(rpos_y + 0.5)
local npos_z = math.floor(rpos_z + 0.5)
local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride +
npos_x - emin_x + 1
npos_x - emin_x + 1
local cid = data[idx]
@ -333,17 +333,17 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
end
if sleep_formspec_doesnt_close_mt53 then
minetest.after(0.3,
function() -- 0.2 is minimum delay for closing old formspec and open died formspec -- TODO: REMOVE THIS IN THE FUTURE
if not obj:is_player() then
return
end
mcl_util.deal_damage(obj, damage, { type = "explosion", direct = direct, source = source })
minetest.after(0.3, function() -- 0.2 is minimum delay for closing old formspec and open died formspec -- TODO: REMOVE THIS IN THE FUTURE
if not obj:is_player() then
return
end
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
end)
mcl_util.deal_damage(obj, damage, {type = "explosion", direct = direct, source = source})
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
end)
else
mcl_util.deal_damage(obj, damage, { type = "explosion", direct = direct, source = source })
mcl_util.deal_damage(obj, damage, {type = "explosion", direct = direct, source = source})
if obj:is_player() or ent.tnt_knockback then
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
@ -389,24 +389,23 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
-- We use bulk_set_node instead of LVM because we want to have on_destruct and
-- on_construct being called
if #airs > 0 then
bulk_set_node(airs, { name = "air" })
bulk_set_node(airs, {name="air"})
end
if #fires > 0 then
bulk_set_node(fires, { name = "mcl_fire:fire" })
bulk_set_node(fires, {name="mcl_fire:fire"})
end
-- Update falling nodes
for a = 1, #airs do
for a=1, #airs do
local p = airs[a]
check_for_falling(vector.offset(p, 0, 1, 0))
check_for_falling({x=p.x, y=p.y+1, z=p.z})
end
for f = 1, #fires do
for f=1, #fires do
local p = fires[f]
check_for_falling(vector.offset(p, 0, 1, 0))
check_for_falling({x=p.x, y=p.y+1, z=p.z})
end
-- Log explosion
minetest.log("action", "Explosion at " .. pos_to_string(pos) .. " with strength " .. strength .. " and radius " ..
radius)
minetest.log("action", "Explosion at "..pos_to_string(pos).." with strength "..strength.." and radius "..radius)
end
-- Create an explosion with strength at pos.
@ -430,11 +429,6 @@ end
-- griefing - If true, the explosion will destroy nodes (default: true)
-- grief_protected - If true, the explosion will also destroy nodes which have
-- been protected (default: false)
---@param pos Vector
---@param strength number
---@param info {drop_chance: number, max_blast_resistance: number, sound: boolean, particles: boolean, fire: boolean, griefing: boolean, grief_protected: boolean}
---@param direct? ObjectRef
---@param source? ObjectRef
function mcl_explosions.explode(pos, strength, info, direct, source)
if info == nil then
info = {}

View File

@ -1,2 +0,0 @@
# textdomain:mcl_explosions
@1 was caught in an explosion.=@1は爆発に巻き込まれた。

View File

@ -138,7 +138,6 @@ mcl_vars.mg_nether_min = -29067 -- Carefully chosen to be at a mapchunk border
mcl_vars.mg_nether_max = mcl_vars.mg_nether_min + 128
mcl_vars.mg_bedrock_nether_bottom_min = mcl_vars.mg_nether_min
mcl_vars.mg_bedrock_nether_top_max = mcl_vars.mg_nether_max
mcl_vars.mg_nether_deco_max = mcl_vars.mg_nether_max -11 -- this is so ceiling decorations don't spill into other biomes as bedrock generation calls minetest.generate_decorations to put netherrack under the bedrock
if not superflat then
mcl_vars.mg_bedrock_nether_bottom_max = mcl_vars.mg_bedrock_nether_bottom_min + 4
mcl_vars.mg_bedrock_nether_top_min = mcl_vars.mg_bedrock_nether_top_max - 4
@ -163,8 +162,7 @@ end
mcl_vars.mg_end_min = -27073 -- Carefully chosen to be at a mapchunk border
mcl_vars.mg_end_max_official = mcl_vars.mg_end_min + minecraft_height_limit
mcl_vars.mg_end_max = mcl_vars.mg_overworld_min - 2000
mcl_vars.mg_end_platform_pos = { x = 100, y = mcl_vars.mg_end_min + 64, z = 0 }
mcl_vars.mg_end_exit_portal_pos = vector.new(0, mcl_vars.mg_end_min + 71, 0)
mcl_vars.mg_end_platform_pos = { x = 100, y = mcl_vars.mg_end_min + 74, z = 0 }
-- Realm barrier used to safely separate the End from the void below the Overworld
mcl_vars.mg_realm_barrier_overworld_end_max = mcl_vars.mg_end_max

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 B

View File

@ -22,29 +22,6 @@ function table.update_nil(t, ...)
return t
end
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_default",false)
local LOG_MODULE = "[MCL2]"
function mcl_util.mcl_log (message, module, bypass_default_logger)
local selected_module = LOG_MODULE
if module then
selected_module = module
end
if (bypass_default_logger or LOGGING_ON) and message then
minetest.log(selected_module .. " " .. message)
end
end
function mcl_util.file_exists(name)
if type(name) ~= "string" then return end
local f = io.open(name)
if not f then
return false
end
f:close()
return true
end
-- Based on minetest.rotate_and_place
--[[
@ -547,7 +524,7 @@ function mcl_util.deal_damage(target, damage, mcl_reason)
if luaentity.deal_damage then
luaentity:deal_damage(damage, mcl_reason or {type = "generic"})
return
elseif luaentity.is_mob then
elseif luaentity._cmi_is_mob then
-- local puncher = mcl_reason and mcl_reason.direct or target
-- target:punch(puncher, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, vector.direction(puncher:get_pos(), target:get_pos()), damage)
if luaentity.health > 0 then
@ -567,7 +544,7 @@ end
function mcl_util.get_hp(obj)
local luaentity = obj:get_luaentity()
if luaentity and luaentity.is_mob then
if luaentity and luaentity._cmi_is_mob then
return luaentity.health
else
return obj:get_hp()
@ -613,12 +590,10 @@ function mcl_util.get_object_name(object)
end
function mcl_util.replace_mob(obj, mob)
if not obj then return end
local rot = obj:get_yaw()
local pos = obj:get_pos()
obj:remove()
obj = minetest.add_entity(pos, mob)
if not obj then return end
obj:set_yaw(rot)
return obj
end
@ -628,103 +603,10 @@ function mcl_util.get_pointed_thing(player, liquid)
local look_dir = vector.multiply(player:get_look_dir(), 5)
local pos2 = vector.add(pos, look_dir)
local ray = minetest.raycast(pos, pos2, false, liquid)
if ray then
for pointed_thing in ray do
return pointed_thing
end
end
end
-- This following part is 2 wrapper functions + helpers for
-- object:set_bones
-- and player:set_properties preventing them from being resent on
-- every globalstep when they have not changed.
local function roundN(n, d)
if type(n) ~= "number" then return n end
local m = 10^d
return math.floor(n * m + 0.5) / m
end
local function close_enough(a,b)
local rt=true
if type(a) == "table" and type(b) == "table" then
for k,v in pairs(a) do
if roundN(v,2) ~= roundN(b[k],2) then
rt=false
break
end
end
else
rt = roundN(a,2) == roundN(b,2)
end
return rt
end
local function props_changed(props,oldprops)
local changed=false
local p={}
for k,v in pairs(props) do
if not close_enough(v,oldprops[k]) then
p[k]=v
changed=true
end
end
return changed,p
end
--tests for roundN
local test_round1=15
local test_round2=15.00199999999
local test_round3=15.00111111
local test_round4=15.00999999
assert(roundN(test_round1,2)==roundN(test_round1,2))
assert(roundN(test_round1,2)==roundN(test_round2,2))
assert(roundN(test_round1,2)==roundN(test_round3,2))
assert(roundN(test_round1,2)~=roundN(test_round4,2))
-- tests for close_enough
local test_cb = {-0.35,0,-0.35,0.35,0.8,0.35} --collisionboxes
local test_cb_close = {-0.351213,0,-0.35,0.35,0.8,0.351212}
local test_cb_diff = {-0.35,0,-1.35,0.35,0.8,0.35}
local test_eh = 1.65 --eye height
local test_eh_close = 1.65123123
local test_eh_diff = 1.35
local test_nt = { r = 225, b = 225, a = 225, g = 225 } --nametag
local test_nt_diff = { r = 225, b = 225, a = 0, g = 225 }
assert(close_enough(test_cb,test_cb_close))
assert(not close_enough(test_cb,test_cb_diff))
assert(close_enough(test_eh,test_eh_close))
assert(not close_enough(test_eh,test_eh_diff))
assert(not close_enough(test_nt,test_nt_diff)) --no floats involved here
--tests for properties_changed
local test_properties_set1={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
local test_properties_set2={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
local test_p1,_=props_changed(test_properties_set1,test_properties_set1)
local test_p2,_=props_changed(test_properties_set1,test_properties_set2)
assert(not test_p1)
assert(test_p2)
function mcl_util.set_properties(obj,props)
local changed,p=props_changed(props,obj:get_properties())
if changed then
obj:set_properties(p)
end
end
function mcl_util.set_bone_position(obj, bone, pos, rot)
local current_pos, current_rot = obj:get_bone_position(bone)
local pos_equal = not pos or vector.equals(vector.round(current_pos), vector.round(pos))
local rot_equal = not rot or vector.equals(vector.round(current_rot), vector.round(rot))
if not pos_equal or not rot_equal then
obj:set_bone_position(bone, pos or current_pos, rot or current_rot)
end
end

View File

@ -153,22 +153,3 @@ minetest.register_globalstep(function(dtime)
end
end)
function mcl_worlds.get_cloud_parameters()
if minetest.get_mapgen_setting("mg_name") == "valleys" then
return {
height = 384, --valleys has a much higher average elevation thus often "normal" landscape ends up in the clouds
speed = {x=-2, z=0},
thickness=5,
color="#FFF0FEF",
ambient = "#201060",
}
else
-- MC-style clouds: Layer 127, thickness 4, fly to the “West”
return {
height = mcl_worlds.layer_to_y(127),
speed = {x=-2, z=0},
thickness = 4,
color = "#FFF0FEF",
}
end
end

View File

@ -28,10 +28,11 @@ end)
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer >= 0.6 then
if timer >= 0.3 then
for _, player in pairs(get_connected_players()) do
local ppos = player:get_pos()
local npos = vector.add(ppos, vector.new(0, -0.1, 0))
ppos.y = ceil(ppos.y)
local npos = vector.add(ppos, vector.new(0, -1, 0))
if npos then
local node = get_node(npos)
if node then

View File

@ -13,21 +13,11 @@ local function is_group(pos, group)
end
local is_water = flowlib.is_water
local function is_river_water(p)
local n = minetest.get_node(p).name
if n == "mclx_core:river_water_source" or n == "mclx_core:river_water_flowing" then
return true
end
end
local function is_ice(pos)
return is_group(pos, "ice")
end
local function is_fire(pos)
return is_group(pos, "set_on_fire")
end
local function get_sign(i)
if i == 0 then
return 0
@ -56,7 +46,7 @@ end
local function set_attach(boat)
boat._driver:set_attach(boat.object, "",
{x = 0, y = 1.5, z = 1}, {x = 0, y = 0, z = 0})
{x = 0, y = 0.42, z = -1}, {x = 0, y = 0, z = 0})
end
local function set_double_attach(boat)
@ -65,13 +55,9 @@ local function set_double_attach(boat)
boat._passenger:set_attach(boat.object, "",
{x = 0, y = 0.42, z = -2.2}, {x = 0, y = 0, z = 0})
end
local function set_choat_attach(boat)
boat._driver:set_attach(boat.object, "",
{x = 0, y = 1.5, z = 1}, {x = 0, y = 0, z = 0})
end
local function attach_object(self, obj)
if self._driver and not self._inv_id then
if self._driver then
if self._driver:is_player() then
self._passenger = obj
else
@ -81,11 +67,7 @@ local function attach_object(self, obj)
set_double_attach(self)
else
self._driver = obj
if self._inv_id then
set_choat_attach(self)
else
set_attach(self)
end
set_attach(self)
end
local visual_size = get_visual_size(obj)
@ -109,7 +91,6 @@ local function attach_object(self, obj)
end
local function detach_object(obj, change_pos)
if not obj or not obj:get_pos() then return end
obj:set_detach()
obj:set_properties({visual_size = get_visual_size(obj)})
if obj:is_player() then
@ -129,14 +110,12 @@ end
local boat = {
physical = true,
pointable = true,
-- Warning: Do not change the position of the collisionbox top surface,
-- lowering it causes the boat to fall through the world if underwater
collisionbox = {-0.5, -0.15, -0.5, 0.5, 0.55, 0.5},
selectionbox = {-0.7, -0.15, -0.7, 0.7, 0.55, 0.7},
collisionbox = {-0.5, -0.35, -0.5, 0.5, 0.3, 0.5},
visual = "mesh",
mesh = "mcl_boats_boat.b3d",
textures = { "mcl_boats_texture_oak_boat.png", "blank.png" },
textures = {"mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png"},
visual_size = boat_visual_size,
hp_max = boat_max_hp,
damage_texture_modifier = "^[colorize:white:0",
@ -147,7 +126,7 @@ local boat = {
_last_v = 0, -- Temporary speed variable
_removed = false, -- If true, boat entity is considered removed (e.g. after punch) and should be ignored
_itemstring = "mcl_boats:boat", -- Itemstring of the boat item (implies boat type)
_animation = 0, -- 0: not animated; 1: paddling forwards; -1: paddling backwards
_animation = 0, -- 0: not animated; 1: paddling forwards; -1: paddling forwards
_regen_timer = 0,
_damage_anim = 0,
}
@ -170,14 +149,8 @@ function boat.on_activate(self, staticdata, dtime_s)
self._last_v = self._v
self._itemstring = data.itemstring
-- Update the texutes for existing old boat entity instances.
-- Maybe remove this in the future.
if #data.textures ~= 2 then
local has_chest = self._itemstring:find("chest")
data.textures = {
data.textures[1]:gsub("_chest", ""),
has_chest and "mcl_chests_normal.png" or "blank.png"
}
while #data.textures < 5 do
table.insert(data.textures, data.textures[1])
end
self.object:set_properties({textures = data.textures})
@ -221,8 +194,6 @@ end
function boat.on_step(self, dtime, moveresult)
mcl_burning.tick(self.object, dtime, self)
-- mcl_burning.tick may remove object immediately
if not self.object:get_pos() then return end
self._v = get_v(self.object:get_velocity()) * get_sign(self._v)
local v_factor = 1
@ -231,21 +202,16 @@ function boat.on_step(self, dtime, moveresult)
local on_water = true
local on_ice = false
local in_water = is_water({x=p.x, y=p.y-boat_y_offset+1, z=p.z})
local in_river_water = is_river_water({x=p.x, y=p.y-boat_y_offset+1, z=p.z})
local waterp = {x=p.x, y=p.y-boat_y_offset - 0.1, z=p.z}
if not is_water(waterp) then
on_water = false
if not in_water and is_ice(waterp) then
on_ice = true
elseif is_fire({x=p.x, y=p.y-boat_y_offset, z=p.z}) then
boat.on_death(self, nil)
self.object:remove()
return
else
v_slowdown = 0.04
v_factor = 0.5
end
elseif in_water and not in_river_water then
elseif in_water then
on_water = false
in_water = true
v_factor = 0.75
@ -348,7 +314,7 @@ function boat.on_step(self, dtime, moveresult)
for _, obj in pairs(minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)) do
local entity = obj:get_luaentity()
if entity and entity.is_mob then
if entity and entity._cmi_is_mob then
attach_object(self, obj)
break
end
@ -377,18 +343,7 @@ function boat.on_step(self, dtime, moveresult)
else
p.y = p.y + 1
local is_obsidian_boat = self.object:get_luaentity()._itemstring == "mcl_boats:boat_obsidian"
if is_river_water(p) then
local y = self.object:get_velocity().y
if y >= 5 then
y = 5
elseif y < 0 then
new_acce = {x = 0, y = 10, z = 0}
else
new_acce = {x = 0, y = 2, z = 0}
end
new_velo = get_velocity(self._v, self.object:get_yaw(), y)
self.object:set_pos(self.object:get_pos())
elseif is_water(p) and not is_river_water(p) or is_obsidian_boat then
if is_water(p) or is_obsidian_boat then
-- Inside water: Slowly sink
local y = self.object:get_velocity().y
y = y - 0.01
@ -428,18 +383,13 @@ end
-- Register one entity for all boat types
minetest.register_entity("mcl_boats:boat", boat)
local cboat = table.copy(boat)
cboat.textures = { "mcl_boats_texture_oak_chest_boat.png", "mcl_chests_normal.png" }
cboat._itemstring = "mcl_boats:chest_boat"
cboat.collisionbox = {-0.5, -0.15, -0.5, 0.5, 0.75, 0.5}
cboat.selectionbox = {-0.7, -0.15, -0.7, 0.7, 0.75, 0.7}
minetest.register_entity("mcl_boats:chest_boat", cboat)
mcl_entity_invs.register_inv("mcl_boats:chest_boat","Boat",27)
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian", "boat_mangrove", "chest_boat", "chest_boat_spruce", "chest_boat_birch", "chest_boat_jungle", "chest_boat_acacia", "chest_boat_dark_oak", "chest_boat_mangrove" }
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat"), S("Mangrove Boat"), S("Oak Chest Boat"), S("Spruce Chest Boat"), S("Birch Chest Boat"), S("Jungle Chest Boat"), S("Acacia Chest Boat"), S("Dark Oak Chest Boat"), S("Mangrove Chest Boat") }
local craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian", "mcl_mangrove:mangrove_wood" }
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian" }
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat") }
local craftstuffs = {}
if minetest.get_modpath("mcl_core") then
craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian" }
end
local images = { "oak", "spruce", "birch", "jungle", "acacia", "dark_oak", "obsidian" }
for b=1, #boat_ids do
local itemstring = "mcl_boats:"..boat_ids[b]
@ -455,21 +405,6 @@ for b=1, #boat_ids do
end
tt_help = S("Water vehicle")
local inventory_image
local texture
local id = boat_ids[b]
if id:find("chest") then
if id == "chest_boat" then id = "oak" end
local id = id:gsub("chest_boat_", "")
inventory_image = "mcl_boats_" .. id .. "_chest_boat.png"
texture = "mcl_boats_texture_" .. id .. "_boat.png"
else
if id == "boat" then id = "oak" end
local id = id:gsub("boat_", "")
inventory_image = "mcl_boats_" .. id .. "_boat.png"
texture = "mcl_boats_texture_" .. id .. "_boat.png"
end
minetest.register_craftitem(itemstring, {
description = names[b],
_tt_help = tt_help,
@ -477,7 +412,7 @@ for b=1, #boat_ids do
_doc_items_entry_name = helpname,
_doc_items_longdesc = longdesc,
_doc_items_usagehelp = usagehelp,
inventory_image = inventory_image,
inventory_image = "mcl_boats_"..images[b].."_boat.png",
liquids_pointable = true,
groups = { boat = 1, transport = 1},
stack_max = 1,
@ -504,15 +439,10 @@ for b=1, #boat_ids do
else
pos = vector.add(pos, vector.multiply(dir, boat_y_offset_ground))
end
local boat_ent = "mcl_boats:boat"
local chest_tex = "blank.png"
if itemstring:find("chest") then
boat_ent = "mcl_boats:chest_boat"
chest_tex = "mcl_chests_normal.png"
end
local boat = minetest.add_entity(pos, boat_ent)
local boat = minetest.add_entity(pos, "mcl_boats:boat")
local texture = "mcl_boats_texture_"..images[b].."_boat.png"
boat:get_luaentity()._itemstring = itemstring
boat:set_properties({ textures = { texture, chest_tex } })
boat:set_properties({textures = { texture, texture, texture, texture, texture }})
boat:set_yaw(placer:get_look_horizontal())
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
@ -532,22 +462,13 @@ for b=1, #boat_ids do
})
local c = craftstuffs[b]
if not itemstring:find("chest") then
minetest.register_craft({
output = itemstring:gsub(":boat",":chest_boat"),
recipe = {
{"mcl_chests:chest"},
{itemstring},
},
})
minetest.register_craft({
output = itemstring,
recipe = {
{c, "", c},
{c, c, c},
},
})
end
minetest.register_craft({
output = itemstring,
recipe = {
{c, "", c},
{c, c, c},
},
})
end
minetest.register_craft({

View File

@ -9,5 +9,4 @@ Oak Boat=Bateau en Chêne
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=Faites un clic droit sur une source d'eau pour placer le bateau. Faites un clic droit sur le bateau pour y entrer. Utilisez [Gauche] et [Droite] pour diriger, [Avant] pour accélérer et [Arrière] pour ralentir ou reculer. Utilisez [Sneak] pour le quitter, frappez le bateau pour le faire tomber en tant qu'objet.
Spruce Boat=Bateau en Sapin
Water vehicle=Véhicule aquatique
Sneak to dismount=Se baisser pour descendre
Obsidian Boat=Bateau en Obsidienne
Sneak to dismount=

View File

@ -1,13 +0,0 @@
# textdomain: mcl_boats
Acacia Boat=アカシアのボート
Birch Boat=シラカバのボート
Boat=ボート
Boats are used to travel on the surface of water.=ボートは、水面を移動するために使われます。
Dark Oak Boat=ダークオークのボート
Jungle Boat=ジャングルのボート
Oak Boat=オークのボート
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=水源を右クリックすると、ボートが配置されます。ボートを右クリックすると、乗り込みます。[左][右]で舵取り、[前]で加速、[後]で減速または後退します。[スニーク]でボートから離れ、ボートをパンチするとアイテムとしてドロップします。
Spruce Boat=トウヒのボート
Water vehicle=水上用の乗物
Sneak to dismount=スニークで降りる
Obsidian Boat=黒曜石のボート

View File

@ -10,4 +10,3 @@ Rightclick on a water source to place the boat. Rightclick the boat to enter it.
Spruce Boat=
Water vehicle=
Sneak to dismount=
Obsidian Boat=

View File

@ -1,5 +1,7 @@
name = mcl_boats
author = PilzAdam
description = Adds drivable boats.
depends = mcl_player, flowlib, mcl_title, mcl_entity_invs
depends = mcl_player, flowlib, mcl_title
optional_depends = mcl_core, doc_identifier

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,5 +1,3 @@
local enable_damage = minetest.settings:get_bool("enable_damage")
function mcl_burning.get_storage(obj)
return obj:is_player() and mcl_burning.storage[obj] or obj:get_luaentity()
end
@ -79,7 +77,7 @@ end
-- The effective burn duration is modified by obj's armor protection.
-- If obj was already burning, its burn duration is updated if the current
-- duration is less than burn_time.
-- If obj is dead, fireproof or enable_damage is disabled, this function does nothing.
-- If obj is dead, fireproof or a creative player, this function does nothing.
--
function mcl_burning.set_on_fire(obj, burn_time)
if obj:get_hp() < 0 then
@ -91,9 +89,8 @@ function mcl_burning.set_on_fire(obj, burn_time)
return
end
if obj:is_player() and not enable_damage then
if obj:is_player() and minetest.is_creative_enabled(obj:get_player_name()) then
burn_time = 0
return
else
local max_fire_prot_lvl = 0
local inv = mcl_util.get_inventory(obj)
@ -134,10 +131,20 @@ function mcl_burning.set_on_fire(obj, burn_time)
if obj:is_player() then
mcl_burning.update_hud(obj)
end
-- FIXME: does this code make sense? It removes attached fire luaentities from
-- another object that happen to be at the same position.
local fire_luaentity = fire_entity:get_luaentity()
for _, other in pairs(minetest.get_objects_inside_radius(fire_entity:get_pos(), 0)) do
local other_luaentity = other:get_luaentity()
if other_luaentity and other_luaentity.name == "mcl_burning:fire" and other_luaentity ~= fire_luaentity then
other:remove()
break
end
end
end
function mcl_burning.extinguish(obj)
if not obj:get_pos() then return end
if mcl_burning.is_burning(obj) then
local storage = mcl_burning.get_storage(obj)
if obj:is_player() then

View File

@ -57,7 +57,7 @@ minetest.register_on_joinplayer(function(player)
local storage = {}
local burn_data = player:get_meta():get_string("mcl_burning:data")
if burn_data ~= "" then
storage = minetest.deserialize(burn_data) or storage
storage = minetest.deserialize(burn_data)
end
mcl_burning.storage[player] = storage
if storage.burn_time and storage.burn_time > 0 then
@ -67,13 +67,6 @@ end)
local function on_leaveplayer(player)
local storage = mcl_burning.storage[player]
if not storage then
-- For some unexplained reasons, mcl_burning.storage can be `nil` here.
-- Logging this exception to assist in finding the cause of this.
minetest.log("warning", "on_leaveplayer: missing mcl_burning.storage "
.. "for player " .. player:get_player_name())
storage = {}
end
storage.fire_hud_id = nil
player:get_meta():set_string("mcl_burning:data", minetest.serialize(storage))
mcl_burning.storage[player] = nil
@ -105,7 +98,8 @@ minetest.register_entity("mcl_burning:fire", {
glow = -1,
backface_culling = false,
},
_mcl_animation_timer = 0,
animation_frame = 0,
animation_timer = 0,
on_activate = function(self)
self.object:set_sprite({x = 0, y = 0}, animation_frames, 1.0 / animation_frames)
end,
@ -121,9 +115,9 @@ minetest.register_entity("mcl_burning:fire", {
return
end
if parent:is_player() then
self._mcl_animation_timer = self._mcl_animation_timer + dtime
if self._mcl_animation_timer >= 0.1 then
self._mcl_animation_timer = 0
self.animation_timer = self.animation_timer + dtime
if self.animation_timer >= 0.1 then
self.animation_timer = 0
mcl_burning.update_hud(parent)
end
end

View File

@ -1,4 +1,3 @@
name = mcl_burning
description = Burning Objects for MineClone2
author = Fleckenstein
depends = mcl_weather

View File

@ -1,36 +0,0 @@
# mcl_dripping
Dripping Mod by kddekadenz, modified for MineClone 2 by Wuzzy, NO11 and AFCM
## Manual
- drops are generated rarely under solid nodes
- they will stay some time at the generated block and than they fall down
- when they collide with the ground, a sound is played and they are destroyed
Water and Lava have builtin drops registered.
## License
code & sounds: CC0
## API
```lua
mcl_dripping.register_drop({
-- The group the liquid's nodes belong to
liquid = "water",
-- The texture used (particles will take a random 2x2 area of it)
texture = "default_water_source_animated.png",
-- Define particle glow, ranges from `0` to `minetest.LIGHT_MAX`
light = 1,
-- The nodes (or node group) the particles will spawn under
nodes = { "group:opaque", "group:leaves" },
-- The sound that will be played then the particle detaches from the roof, see SimpleSoundSpec in lua_api.txt
sound = "drippingwater_drip",
-- The interval for the ABM to run
interval = 60,
-- The chance of the ABM
chance = 10,
})
```

View File

@ -1,101 +1,66 @@
-- Dripping Water Mod
-- by kddekadenz
-- License of code, textures & sounds: CC0
local math = math
mcl_dripping = {}
-- License of code, textures & sounds: CC0
---@param pos Vector
---@param liquid string
---@param sound SimpleSoundSpec
---@param interval integer
---@param texture string
local function make_drop(pos, liquid, sound, interval, texture)
local pt = {
velocity = vector.zero(),
collision_removal = false,
}
local t = math.random() + math.random(1, interval)
minetest.after(t, function()
local x, z = math.random(-45, 45) / 100, math.random(-45, 45) / 100
pt.pos = vector.offset(pos, x, -0.52, z)
pt.acceleration = vector.zero()
pt.collisiondetection = false
pt.expirationtime = t
pt.texture = "[combine:2x2:" ..
-math.random(1, 16) .. "," .. -math.random(1, 16) .. "=" .. texture
minetest.add_particle(pt)
minetest.after(t, function()
pt.acceleration = vector.new(0, -5, 0)
pt.collisiondetection = true
pt.expirationtime = math.random() + math.random(1, interval / 2)
minetest.add_particle(pt)
minetest.sound_play(sound, { pos = pos, gain = 0.5, max_hear_distance = 8 },
true)
end)
end)
end
---@class mcl_dripping_drop_definition
---@field liquid string The group the liquid's nodes belong to
---@field texture string The texture used (particles will take a random 2x2 area of it)
---@field light integer Define particle glow, ranges from `0` to `minetest.LIGHT_MAX`
---@field nodes string[] The nodes (or node group) the particles will spawn under
---@field interval integer The interval for the ABM to run
---@field chance integer The chance of the ABM
---@field sound SimpleSoundSpec The sound that will be played then the particle detaches from the roof
---@param def mcl_dripping_drop_definition
function mcl_dripping.register_drop(def)
local function register_drop(liquid, glow, sound, nodes)
minetest.register_entity("mcl_dripping:drop_" .. liquid, {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.01, 0.01, -0.01, 0.01, 0.01, 0.01},
glow = glow,
pointable = false,
visual = "sprite",
visual_size = {x = 0.1, y = 0.1},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
static_save = false,
_dropped = false,
on_activate = function(self)
self.object:set_properties({
textures = {"[combine:2x2:" .. -math.random(1, 16) .. "," .. -math.random(1, 16) .. "=default_" .. liquid .. "_source_animated.png"}
})
end,
on_step = function(self, dtime)
local k = math.random(1, 222)
local ownpos = self.object:get_pos()
if k == 1 then
self.object:set_acceleration(vector.new(0, -5, 0))
end
if minetest.get_node(vector.offset(ownpos, 0, 0.5, 0)).name == "air" then
self.object:set_acceleration(vector.new(0, -5, 0))
end
if minetest.get_node(vector.offset(ownpos, 0, -0.1, 0)).name ~= "air" then
local ent = self.object:get_luaentity()
if not ent._dropped then
ent._dropped = true
minetest.sound_play({name = "drippingwater_" .. sound .. "drip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
end
if k < 3 then
self.object:remove()
end
end
end,
})
minetest.register_abm({
label = "Create drops",
nodenames = def.nodes,
neighbors = { "group:" .. def.liquid },
interval = def.interval,
chance = def.chance,
nodenames = nodes,
neighbors = {"group:" .. liquid},
interval = 2,
chance = 22,
action = function(pos)
local below = minetest.get_node(vector.offset(pos,0,-1,0)).name
if below ~= "air" then return end
local r = math.ceil(def.interval / 20)
local nn = minetest.find_nodes_in_area(vector.offset(pos, -r, 0, -r), vector.offset(pos, r, 0, r), def.nodes)
--start a bunch of particle cycles to be able to get away
--with longer abm cycles
table.shuffle(nn)
for i = 1, math.random(#nn) do
if minetest.get_item_group(minetest.get_node(vector.offset(nn[i], 0, 1, 0)).name, def.liquid) ~= 0 then
make_drop(nn[i], def.liquid, def.sound, def.interval, def.texture)
end
if minetest.get_item_group(minetest.get_node(vector.offset(pos, 0, 1, 0)).name, liquid) ~= 0
and minetest.get_node(vector.offset(pos, 0, -1, 0)).name == "air" then
local x, z = math.random(-45, 45) / 100, math.random(-45, 45) / 100
minetest.add_entity(vector.offset(pos, x, -0.520, z), "mcl_dripping:drop_" .. liquid)
end
end,
})
end
mcl_dripping.register_drop({
liquid = "water",
texture = "default_water_source_animated.png",
light = 1,
nodes = { "group:opaque", "group:leaves" },
sound = "drippingwater_drip",
interval = 60.3,
chance = 10,
})
mcl_dripping.register_drop({
liquid = "lava",
texture = "default_lava_source_animated.png",
light = math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3),
nodes = { "group:opaque" },
sound = "drippingwater_lavadrip",
interval = 110.1,
chance = 10,
})
register_drop("water", 1, "", {"group:opaque", "group:leaves"})
register_drop("lava", math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3), "lava", {"group:opaque"})

View File

@ -0,0 +1,29 @@
Dripping Mod
by kddekadenz
modified for MineClone 2 by Wuzzy and NO11
Installing instructions:
1. Copy the mcl_dripping mod folder into games/gamemode/mods
2. Start game and enjoy :)
Manual:
-> drops are generated rarely under solid nodes
-> they will stay some time at the generated block and than they fall down
-> when they collide with the ground, a sound is played and they are destroyed
License:
code & sounds: CC0
Changelog:
16.04.2012 - first release
28.04.2012 - drops are now 3D; added lava drops; fixed generating of drops (not at edges now)

View File

@ -1,13 +0,0 @@
mcl_entity_invs
===============
Inventories for your entities. It's simple. Depend on mcl_entity_invs and register your entity like so:
* mcl_entity_invs.register_inv("entity:name","Title shown in formspec",inventory_size,disable_on_righclick)
*If disable_on_righclick is true other mods can handle when to show the inventory themselves
* The inventory size can be set dynamically by initializing it with an explicit nil
* mcl_entity_invs.show_inv_form(entity,clicker,[formspec text])
* formspec_text is an additional text that is put after the title
It works by setting up a detached inventory per entity which is accessed by an id/hash generated from the entities position at creation, the progressed gametime at creation and a random salt.

View File

@ -1,210 +0,0 @@
mcl_entity_invs = {}
local open_invs = {}
local function mcl_log (message)
mcl_util.mcl_log (message, "[Entity Invs]")
end
local function check_distance(inv,player,count)
for _,o in pairs(minetest.get_objects_inside_radius(player:get_pos(),5)) do
local l = o:get_luaentity()
if l and l._inv_id and inv:get_location().name == l._inv_id then return count end
end
return 0
end
local inv_callbacks = {
allow_take = function(inv, listname, index, stack, player)
return check_distance(inv,player,stack:get_count())
end,
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return check_distance(inv,player,count)
end,
allow_put = function(inv, listname, index, stack, player)
return check_distance(inv,player,stack:get_count())
end,
}
function mcl_entity_invs.load_inv(ent,size)
mcl_log("load_inv")
if not ent._inv_id then return end
mcl_log("load_inv 2")
local inv = minetest.get_inventory({type="detached", name=ent._inv_id})
if not inv then
mcl_log("load_inv 3")
inv = minetest.create_detached_inventory(ent._inv_id, inv_callbacks)
inv:set_size("main", size)
if ent._items then
inv:set_list("main",ent._items)
end
else
mcl_log("load_inv 4")
end
return inv
end
function mcl_entity_invs.save_inv(ent)
if ent._inv then
ent._items = {}
for i,it in ipairs(ent._inv:get_list("main")) do
ent._items[i] = it:to_string()
end
minetest.remove_detached_inventory(ent._inv_id)
ent._inv = nil
end
end
local function load_default_formspec (ent, text)
text = text or ""
local invent_size = ent._inv_size
local div_by_two = invent_size % 2 == 0
local div_by_three = invent_size % 3 == 0
--mcl_log("Div by 3: ".. tostring(div_by_three))
--mcl_log("Div by 2: ".. tostring(div_by_two))
--mcl_log("invent_size: ".. tostring(invent_size))
local rows = 3
if invent_size > 18 or (div_by_three == true and invent_size > 8) then
--mcl_log("Div by 3")
rows = 3
elseif (div_by_two == true and invent_size > 3) or invent_size > 9 then
--mcl_log("Div by 2")
rows = 2
else
--mcl_log("Not div by 2 or 3")
rows = 1
end
--local rows = 3
local cols = (math.ceil(ent._inv_size/rows))
local spacing = (9 - cols) / 2
local formspec = "size[9,8.75]"
.. "label[0,0;" .. minetest.formspec_escape(
minetest.colorize("#313131", ent._inv_title .. " ".. text)) .. "]"
.. "list[detached:"..ent._inv_id..";main;"..spacing..",0.5;"..cols..","..rows..";]"
.. mcl_formspec.get_itemslot_bg(spacing,0.5,cols,rows)
.. "label[0,4.0;" .. minetest.formspec_escape(
minetest.colorize("#313131", "Inventory")) .. "]"
.. "list[current_player;main;0,4.5;9,3;9]"
.. mcl_formspec.get_itemslot_bg(0,4.5,9,3)
.. "list[current_player;main;0,7.74;9,1;]"
.. mcl_formspec.get_itemslot_bg(0,7.74,9,1)
.. "listring[detached:"..ent._inv_id..";main]"
.. "listring[current_player;main]"
return formspec
end
function mcl_entity_invs.show_inv_form(ent,player,text)
if not ent._inv_id then return end
if not open_invs[ent] then
open_invs[ent] = 0
end
ent._inv = mcl_entity_invs.load_inv(ent,ent._inv_size)
open_invs[ent] = open_invs[ent] + 1
local playername = player:get_player_name()
minetest.show_formspec(playername, ent._inv_id, load_default_formspec (ent, text))
end
local function drop_inv(ent)
if not ent._items then return end
local pos = ent.object:get_pos()
for i,it in pairs(ent._items) do
local p = vector.add(pos,vector.new(math.random() - 0.5, math.random()-0.5, math.random()-0.5))
minetest.add_item(p,it)
end
ent._items = nil
end
local function on_remove(self,killer,oldf)
mcl_entity_invs.save_inv(self)
drop_inv(self)
if oldf then return oldf(self,killer) end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
for k,v in pairs(open_invs) do
if formname == k._inv_id then
open_invs[k] = open_invs[k] - 1
if open_invs[k] < 1 then
mcl_entity_invs.save_inv(k)
open_invs[k] = nil
end
end
end
end)
function mcl_entity_invs.register_inv(entity_name,show_name,size,no_on_righclick,no_sneak)
assert(minetest.registered_entities[entity_name],"mcl_entity_invs.register_inv called with invalid entity: "..tostring(entity_name))
minetest.registered_entities[entity_name]._inv_size = size
minetest.registered_entities[entity_name]._inv_title = show_name
local old_oa = minetest.registered_entities[entity_name].on_activate
minetest.registered_entities[entity_name].on_activate = function(self,staticdata,dtime_s)
local r
if old_oa then r=old_oa(self,staticdata,dtime_s) end
local d = minetest.deserialize(staticdata)
if type(d) == "table" and d._inv_id then
self._inv_id = d._inv_id
self._items = d._items
self._inv_size = d._inv_size
else
self._inv_id="entity_inv_"..minetest.sha1(minetest.get_gametime()..minetest.pos_to_string(self.object:get_pos())..tostring(math.random()))
--gametime and position for collision safety and math.random salt to protect against position brute-force
end
return r
end
if not no_on_righclick then
local old_rc = minetest.registered_entities[entity_name].on_rightclick
minetest.registered_entities[entity_name].on_rightclick = function(self,clicker)
if no_sneak or clicker:get_player_control().sneak then
mcl_entity_invs.show_inv_form(self,clicker,"")
if not no_sneak then return end
end
if old_rc then return old_rc(self,clicker) end
end
end
local old_gsd = minetest.registered_entities[entity_name].get_staticdata
minetest.registered_entities[entity_name].get_staticdata = function(self)
local old_sd = old_gsd(self)
local d = minetest.deserialize(old_sd)
assert(type(d) == "table","mcl_entity_invs currently only works with entities that return a (serialized) table in get_staticdata. "..tostring(self.name).." returned: "..tostring(old_sd))
d._inv_id = self._inv_id
d._inv_size = self._inv_size
d._items = {}
if self._items then
for i,it in ipairs(self._items) do
d._items[i] = it
end
end
return minetest.serialize(d)
end
local old_ode = minetest.registered_entities[entity_name].on_deactivate
minetest.registered_entities[entity_name].on_deactivate = function(self,removal)
mcl_entity_invs.save_inv(self)
if removal then
on_remove(self)
end
if old_ode then return old_ode(self,removal) end
end
local old_od = minetest.registered_entities[entity_name].on_death
minetest.registered_entities[entity_name].on_death = function(self,killer)
if not self.is_mob then
on_remove(self,killer,old_od)
end
end
local old_odi = minetest.registered_entities[entity_name].on_die
minetest.registered_entities[entity_name].on_die = function(self,killer)
if self.is_mob then
on_remove(self,killer,old_od)
end
end
end

View File

@ -1,3 +0,0 @@
name = mcl_entity_invs
author = cora
depends = mcl_formspec

View File

@ -25,10 +25,4 @@ http://minetest.net/
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
---------
Alterations and contributions are released under GNU GPLv3 after 11/11/2022 and for contributors:
AncientMariner/ancientmarinerdev
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@ -6,16 +6,6 @@ local pool = {}
local tick = false
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_item_entities",false)
local function mcl_log (message)
if LOGGING_ON then
mcl_util.mcl_log (message, "[Item Entities]", true)
end
end
minetest.register_on_joinplayer(function(player)
local name
name = player:get_player_name()
@ -69,10 +59,6 @@ mcl_item_entity.register_pickup_achievement("tree", "mcl:mineWood")
mcl_item_entity.register_pickup_achievement("mcl_mobitems:blaze_rod", "mcl:blazeRod")
mcl_item_entity.register_pickup_achievement("mcl_mobitems:leather", "mcl:killCow")
mcl_item_entity.register_pickup_achievement("mcl_core:diamond", "mcl:diamonds")
mcl_item_entity.register_pickup_achievement("mcl_core:crying_obsidian", "mcl:whosCuttingOnions")
mcl_item_entity.register_pickup_achievement("mcl_nether:ancient_debris", "mcl:hiddenInTheDepths")
mcl_item_entity.register_pickup_achievement("mcl_end:dragon_egg", "mcl:PickUpDragonEgg")
mcl_item_entity.register_pickup_achievement("mcl_armor:elytra", "mcl:skysTheLimit")
local function check_pickup_achievements(object, player)
if has_awards then
@ -92,6 +78,7 @@ local function enable_physics(object, luaentity, ignore_check)
object:set_properties({
physical = true
})
object:set_velocity({x=0,y=0,z=0})
object:set_acceleration({x=0,y=-get_gravity(),z=0})
end
end
@ -123,7 +110,7 @@ minetest.register_globalstep(function(dtime)
if tick == true and pool[name] > 0 then
minetest.sound_play("item_drop_pickup", {
pos = pos,
gain = 0.3,
gain = 0.7,
max_hear_distance = 16,
pitch = math.random(70,110)/100
})
@ -269,8 +256,6 @@ function minetest.handle_node_drops(pos, drops, digger)
local silk_touch_drop = false
local nodedef = minetest.registered_nodes[dug_node.name]
if not nodedef then return end
if shearsy_level and shearsy_level > 0 and nodedef._mcl_shears_drop then
if nodedef._mcl_shears_drop == true then
drops = { dug_node.name }
@ -330,14 +315,18 @@ function minetest.handle_node_drops(pos, drops, digger)
-- Spawn item and apply random speed
local obj = minetest.add_item(dpos, drop_item)
if obj then
-- set the velocity multiplier to the stored amount or if the game dug this node, apply a bigger velocity
local v = 1
if digger and digger:is_player() then
obj:get_luaentity().random_velocity = 1
else
obj:get_luaentity().random_velocity = 1.6
local x = math.random(1, 5)
if math.random(1,2) == 1 then
x = -x
end
local z = math.random(1, 5)
if math.random(1,2) == 1 then
z = -z
end
obj:set_velocity({x=1/x, y=obj:get_velocity().y, z=1/z})
obj:get_luaentity().age = item_drop_settings.dug_buffer
obj:get_luaentity()._insta_collect = false
end
end
@ -385,116 +374,6 @@ local function cxcz(o, cw, one, zero)
return o
end
local function hopper_take_item (self, pos)
--mcl_log("self.itemstring: ".. self.itemstring)
--mcl_log("self.itemstring: ".. minetest.pos_to_string(pos))
local objs = minetest.get_objects_inside_radius(pos, 2)
if objs and self.itemstring then
--mcl_log("there is an itemstring. Number of objs: ".. #objs)
for k,v in pairs(objs) do
local ent = v:get_luaentity()
-- Don't forget actual hoppers
if ent and ent.name == "mcl_minecarts:hopper_minecart" 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(ent,5)
if not inv then
mcl_log("No inv")
return false
end
local current_itemstack = ItemStack(self.itemstring)
mcl_log("inv. size: " .. ent._inv_size)
if inv:room_for_item("main", current_itemstack) then
mcl_log("Room")
inv:add_item("main", current_itemstack)
self.object:get_luaentity().itemstring = ""
self.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, ent._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
self.object:get_luaentity().itemstring = ""
self.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 == ent._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())
self.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
minetest.register_entity(":__builtin:item", {
initial_properties = {
hp_max = 1,
@ -526,33 +405,9 @@ minetest.register_entity(":__builtin:item", {
-- Number of seconds this item entity has existed so far
age = 0,
-- Multiplier for initial random velocity when the item is spawned
random_velocity = 1,
-- How old it has become in the collection animation
collection_age = 0,
-- Function to apply a random velocity
apply_random_vel = function(self, speed)
if not self or not self.object or not self.object:get_luaentity() then
return
end
-- if you passed a value then use that for the velocity multiplier
if speed ~= nil then self.random_velocity = speed end
local vel = self.object:get_velocity()
if vel and vel.x == 0 and vel.z == 0 and self.random_velocity > 0 then
local v = self.random_velocity
local x = math.random(5, 10) / 10 * v
if math.random(0,10) < 5 then x = -x end
local z = math.random(5, 10) / 10 * v
if math.random(0,10) < 5 then z = -z end
local y = math.random(2,4)
self.object:set_velocity({x=x, y=y, z=z})
end
self.random_velocity = 0
end,
set_item = function(self, itemstring)
self.itemstring = itemstring
if self.itemstring == "" then
@ -561,11 +416,7 @@ minetest.register_entity(":__builtin:item", {
end
local stack = ItemStack(itemstring)
if minetest.get_item_group(stack:get_name(), "compass") > 0 then
if string.find(stack:get_name(), "_lodestone") then
stack:set_name("mcl_compass:18_lodestone")
else
stack:set_name("mcl_compass:18")
end
stack:set_name("mcl_compass:16")
itemstring = stack:to_string()
self.itemstring = itemstring
end
@ -605,9 +456,27 @@ minetest.register_entity(":__builtin:item", {
glow = glow,
}
self.object:set_properties(prop)
if item_drop_settings.random_item_velocity == true and self.age < 1 then
minetest.after(0, self.apply_random_vel, self)
if item_drop_settings.random_item_velocity == true then
minetest.after(0, function(self)
if not self or not self.object or not self.object:get_luaentity() then
return
end
local vel = self.object:get_velocity()
if vel and vel.x == 0 and vel.z == 0 then
local x = math.random(1, 5)
if math.random(1,2) == 1 then
x = -x
end
local z = math.random(1, 5)
if math.random(1,2) == 1 then
z = -z
end
local y = math.random(2,4)
self.object:set_velocity({x=1/x, y=y, z=1/z})
end
end, self)
end
end,
get_staticdata = function(self)
@ -661,9 +530,9 @@ minetest.register_entity(":__builtin:item", {
self.itemstring = data.itemstring
self.always_collect = data.always_collect
if data.age then
self.age = data.age
self.age = data.age + dtime_s
else
self.age = self.age
self.age = dtime_s
end
--remember collection data
-- If true, can collect item without delay
@ -696,7 +565,7 @@ minetest.register_entity(":__builtin:item", {
self._forcetimer = 0
self.object:set_armor_groups({immortal = 1})
-- self.object:set_velocity({x = 0, y = 2, z = 0})
self.object:set_velocity({x = 0, y = 2, z = 0})
self.object:set_acceleration({x = 0, y = -get_gravity(), z = 0})
self:set_item(self.itemstring)
end,
@ -726,13 +595,12 @@ minetest.register_entity(":__builtin:item", {
end
-- Merge the remote stack into this one
-- local pos = object:get_pos()
-- pos.y = pos.y + ((total_count - count) / max_count) * 0.15
-- self.object:move_to(pos)
local pos = object:get_pos()
pos.y = pos.y + ((total_count - count) / max_count) * 0.15
self.object:move_to(pos)
self.age = 0 -- Handle as new entity
own_stack:set_count(total_count)
self.random_velocity = 0
self:set_item(own_stack:to_string())
entity._removed = true
@ -768,62 +636,36 @@ minetest.register_entity(":__builtin:item", {
end
local p = self.object:get_pos()
-- If hopper has taken item, it has gone, and no operations should be conducted on this item
if hopper_take_item(self, p) then
return
end
local node = minetest.get_node_or_nil(p)
local in_unloaded = (node == nil)
if in_unloaded then
-- Don't infinetly fall into unloaded map
disable_physics(self.object, self)
return
end
if self.is_clock then
self.object:set_properties({
textures = {"mcl_clock:clock_" .. (mcl_worlds.clock_works(p) and mcl_clock.old_time or mcl_clock.random_frame)}
})
end
local nn = node.name
local is_in_water = (minetest.get_item_group(nn, "liquid") ~= 0)
local nn_above = minetest.get_node({x=p.x, y=p.y+0.1, z=p.z}).name
-- make sure it's more or less stationary and is at water level
local sleep_threshold = 0.3
local is_floating = false
local is_stationary = math.abs(self.object:get_velocity().x) < sleep_threshold
and math.abs(self.object:get_velocity().y) < sleep_threshold
and math.abs(self.object:get_velocity().z) < sleep_threshold
if is_in_water and is_stationary then
is_floating = (is_in_water
and (minetest.get_item_group(nn_above, "liquid") == 0))
end
if is_floating and self.physical_state == true then
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
disable_physics(self.object, self)
end
-- If no collector was found for a long enough time, declare the magnet as disabled
if self._magnet_active and (self._collector_timer == nil or (self._collector_timer > item_drop_settings.magnet_time)) then
self._magnet_active = false
enable_physics(self.object, self)
return
end
if in_unloaded then
-- Don't infinetly fall into unloaded map
disable_physics(self.object, self)
return
end
-- Destroy item in lava, fire or special nodes
local nn = node.name
local def = minetest.registered_nodes[nn]
local lg = minetest.get_item_group(nn, "lava")
local fg = minetest.get_item_group(nn, "fire")
local dg = minetest.get_item_group(nn, "destroys_items")
if (def and (lg ~= 0 or fg ~= 0 or dg == 1)) then
--Wait 2 seconds to allow mob drops to be cooked, & picked up instead of instantly destroyed.
if self.age > 2 and minetest.get_item_group(self.itemstring, "fire_immune") == 0 then
if self.age > 2 then
if dg ~= 2 then
minetest.sound_play("builtin_item_lava", {pos = self.object:get_pos(), gain = 0.5})
end
@ -846,7 +688,7 @@ minetest.register_entity(":__builtin:item", {
end
-- Push item out when stuck inside solid opaque node
if not is_in_water and def and def.walkable and def.groups and def.groups.opaque == 1 then
if def and def.walkable and def.groups and def.groups.opaque == 1 then
local shootdir
local cx = (p.x % 1) - 0.5
local cz = (p.z % 1) - 0.5
@ -887,8 +729,8 @@ minetest.register_entity(":__builtin:item", {
local newv = vector.multiply(shootdir, 3)
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity(newv)
disable_physics(self.object, self, false, false)
disable_physics(self.object, self, false, false)
if shootdir.y == 0 then
self._force = newv
@ -926,9 +768,8 @@ minetest.register_entity(":__builtin:item", {
return
end
-- Move item around on flowing liquids; add 'source' check to allow items to continue flowing a bit in the source block of flowing water.
if def and not is_floating and (def.liquidtype == "flowing" or def.liquidtype == "source") then
self._flowing = true
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
@ -937,11 +778,11 @@ minetest.register_entity(":__builtin:item", {
-- Just to make sure we don't manipulate the speed for no reason
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
local f = 1.2
local f = 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
-- Swap to acceleration instead of a static speed to better mimic MC mechanics.
self.object:set_acceleration({x = newv.x, y = -0.22, z = newv.z})
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z})
self.physical_state = true
self._flowing = true
@ -950,29 +791,7 @@ minetest.register_entity(":__builtin:item", {
})
return
end
if is_in_water and def.liquidtype == "source" then
local cur_vec = self.object:get_velocity()
-- apply some acceleration in the opposite direction so it doesn't slide forever
local vec = {
x = 0 -cur_vec.x*0.9,
y = 3 -cur_vec.y*0.9,
z = 0 -cur_vec.z*0.9}
self.object:set_acceleration(vec)
-- slow down the item in water
local vel = self.object:get_velocity()
if vel.y < 0 then
vel.y = vel.y * 0.9
end
self.object:set_velocity(vel)
if self.physical_state ~= false or self._flowing ~= true then
self.physical_state = true
self._flowing = true
self.object:set_properties({
physical = true
})
end
end
elseif self._flowing == true and not is_in_water and not is_floating then
elseif self._flowing == true then
-- Disable flowing physics if not on/in flowing liquid
self._flowing = false
enable_physics(self.object, self, true)
@ -981,34 +800,28 @@ minetest.register_entity(":__builtin:item", {
-- If node is not registered or node is walkably solid and resting on nodebox
local nn = minetest.get_node({x=p.x, y=p.y-0.5, z=p.z}).name
local def = minetest.registered_nodes[nn]
local v = self.object:get_velocity()
local is_on_floor = def and (def.walkable
and not def.groups.slippery and v.y == 0)
if not minetest.registered_nodes[nn]
or is_floating or is_on_floor then
local own_stack = ItemStack(self.object:get_luaentity().itemstring)
-- Merge with close entities of the same item
for _, object in pairs(minetest.get_objects_inside_radius(p, 0.8)) do
local obj = object:get_luaentity()
if obj and obj.name == "__builtin:item"
and obj.physical_state == false then
if self:try_merge_with(own_stack, object, obj) then
return
if not minetest.registered_nodes[nn] or minetest.registered_nodes[nn].walkable and v.y == 0 then
if self.physical_state then
local own_stack = ItemStack(self.object:get_luaentity().itemstring)
-- Merge with close entities of the same item
for _, object in pairs(minetest.get_objects_inside_radius(p, 0.8)) do
local obj = object:get_luaentity()
if obj and obj.name == "__builtin:item"
and obj.physical_state == false then
if self:try_merge_with(own_stack, object, obj) then
return
end
end
end
-- don't disable if underwater
if not is_in_water then
disable_physics(self.object, self)
end
disable_physics(self.object, self)
end
else
if self._magnet_active == false and not is_floating then
if self._magnet_active == false then
enable_physics(self.object, self)
end
end
end,
-- Note: on_punch intentionally left out. The player should *not* be able to collect items by punching

View File

@ -432,8 +432,7 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
-- Slow down or speed up
local acc = dir.y * -1.8
local friction = 0.4
local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
local speed_mod = ndef and ndef._rail_acceleration
local speed_mod = minetest.registered_nodes[minetest.get_node(pos).name]._rail_acceleration
acc = acc - friction
@ -677,8 +676,7 @@ register_minecart(
{ "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
"mcl_minecarts_minecart_chest.png",
{"mcl_minecarts:minecart", "mcl_chests:chest"},
nil, nil, true)
mcl_entity_invs.register_inv("mcl_minecarts:chest_minecart","Minecart",27,false,true)
nil, nil, false)
-- Minecart with Furnace
register_minecart(
@ -731,7 +729,7 @@ register_minecart(
"mcl_minecarts_minecart.png",
}})
end
end, nil, true
end, nil, false
)
-- Minecart with Command Block
@ -770,9 +768,8 @@ register_minecart(
},
"mcl_minecarts_minecart_hopper.png",
{"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
nil, nil, true
nil, nil, false
)
mcl_entity_invs.register_inv("mcl_minecarts:hopper_minecart", "Hopper Minecart", 5, false, true)
-- Minecart with TNT
register_minecart(
@ -832,7 +829,8 @@ minetest.register_craft({
},
})
minetest.register_craft({
-- TODO: Re-enable crafting of special minecarts when they have been implemented
--[[minetest.register_craft({
output = "mcl_minecarts:furnace_minecart",
recipe = {
{"mcl_furnaces:furnace"},
@ -848,18 +846,18 @@ minetest.register_craft({
},
})
minetest.register_craft({
output = "mcl_minecarts:chest_minecart",
recipe = {
{"mcl_chests:chest"},
{"mcl_minecarts:minecart"},
},
})
})]]
if has_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
mcl_wip.register_wip_item("mcl_minecarts:hopper_minecart")
end

View File

@ -33,4 +33,4 @@ Activates minecarts when powered=Active les wagonnets lorsqu'il est alimenté
Emits redstone power when a minecart is detected=Émet de l'énergie redstone lorsqu'un wagonnet est détecté
Vehicle for fast travel on rails=Véhicule pour voyager rapidement sur rails
Can be ignited by tools or powered activator rail=Peut être allumé par des outils ou un rail d'activation motorisé
Sneak to dismount=Se baisser pour descendre
Sneak to dismount=

View File

@ -1,36 +0,0 @@
# textdomain: mcl_minecarts
Minecart=トロッコ
Minecarts can be used for a quick transportion on rails.=トロッコは、レールを使った高速輸送を可能にします。
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.=トロッコはレール上にしか乗らない為、常に線路に沿って走ります。直進できない丁字路では、取り敢えず左折します。速度はレールの種類によって異なります。
You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.=レールの上にトロッコを置けます。右クリックで乗り込みます。パンチすると動き出します。
To obtain the minecart, punch it while holding down the sneak key.=トロッコを入手するには、スニークキーを押しながらパンチします。
A minecart with TNT is an explosive vehicle that travels on rail.=TNT付きトロッコは、レール上を行きかう爆薬車両です。
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.=レール上に配置。パンチで移動。TNTが着火するのは、火打石と打金を使った時か、稼動中のアクティベーターレール上にトロッコが乗った時です。
To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited.=トロッコとTNTを入手するには、スニークキーを押しながらパンチしてください。TNTに火が着いていた場合は、無理です。
A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel.=かまど付きトロッコは、レール上を走行する車両です。燃料で自走できます。
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.=レールの上に置きます。石炭を与えると、かまどが長時間燃え続け、トロッコが自走可能になります。パンチすると動き出します。
To obtain the minecart and furnace, punch them while holding down the sneak key.=トロッコとかまどを入手するには、スニークキーを押しながらパンチします。
Minecart with Chest=チェスト付きトロッコ
Minecart with Furnace=かまど付きトロッコ
Minecart with Command Block=コマンドブロック付きトロッコ
Minecart with Hopper=ホッパー付きトロッコ
Minecart with TNT=TNT付きトロッコ
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.=地面に置いて線路を作ると、レール同士が自動的につながり、必要に応じてカーブや丁字路、踏切、坂道などに変化します。
Rail=レール
Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction.=レールを利用して、トロッコの輸送路が敷けます。普通のレールは、摩擦の関係でトロッコが少しずつ減速していきます。
Powered Rail=パワードレール
Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts.=レールを利用して、トロッコの輸送路が敷けます。パワードレールは、トロッコを加速させたり、ブレーキをかけたりできます。
Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power.=レッドストーン動力なしだと、このレールはトロッコにブレーキをかけます。このレールでトロッコを加速させるには、レッドストーン動力を供給してください。
Activator Rail=アクティベーターレール
Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts.=レールを利用して、トロッコの輸送路が敷けます。アクティベーターレールは、特殊なトロッコを作動させるために使われます。
To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail.=このレールでトロッコを作動させるには、レッドストーン動力を与えたレール上にトロッコを送り込みます。
Detector Rail=
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.=レールを利用して、トロッコの輸送路が敷けます。ディテクターレールは、その上にあるトロッコを検知でき、その際レッドストーン機構の動力源となります。
To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail.=トロッコを検知してレッドストーン動力を供給するには、レッドストーン導線またはレッドストーン機構に接続し、任意のトロッコをレール上に送り込みます。
Track for minecarts=トロッコ用の線路
Speed up when powered, slow down when not powered=稼動中は加速、非稼動中は減速
Activates minecarts when powered=稼動中はトロッコを作動
Emits redstone power when a minecart is detected=トロッコを検知するとレッドストーン動力を放出
Vehicle for fast travel on rails=レール上を快速移動するための車両
Can be ignited by tools or powered activator rail=道具や稼動中のアクティベーターレールにより着火が可能
Sneak to dismount=スニークで降りる

View File

@ -1,5 +1,6 @@
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
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons
optional_depends = doc_identifier, mcl_wip

View File

@ -21,7 +21,7 @@ local function register_rail(itemstring, tiles, def_extras, creative)
stack_max = 64,
groups = groups,
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_blast_resistance = 0.7,
_mcl_blast_resistance = 3.5,
_mcl_hardness = 0.7,
after_destruct = function(pos)
-- Scan for minecarts in this pos and force them to execute their "floating" check.

View File

@ -1,478 +0,0 @@
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: MineClone 2 Edition (MRM)
local MAX_MOB_NAME_LENGTH = 30
local DEFAULT_FALL_SPEED = -9.81*1.5
local PATHFINDING = "gowp"
-- Localize
local S = minetest.get_translator("mcl_mobs")
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
local function mcl_log (message)
if LOGGING_ON then
mcl_util.mcl_log (message, "[Mobs]", true)
end
end
-- Invisibility mod check
mcl_mobs.invis = {}
-- localize math functions
local atann = math.atan
local function atan(x)
if not x or x ~= x then
return 0
else
return atann(x)
end
end
local remove_far = true
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn",true)
-- Peaceful mode message so players will know there are no monsters
if minetest.settings:get_bool("only_peaceful_mobs", false) then
minetest.register_on_joinplayer(function(player)
minetest.chat_send_player(player:get_player_name(),
S("Peaceful mode active! No monsters will spawn."))
end)
end
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
function mob_class:update_tag() --update nametag and/or the debug box
local tag
if mobs_debug then
local name = self.name
if self.nametag and self.nametag ~= "" then
name = self.nametag
end
tag = "name = '"..tostring(name).."'\n"..
"state = '"..tostring(self.state).."'\n"..
"order = '"..tostring(self.order).."'\n"..
"attack = "..tostring(self.attack).."\n"..
"health = "..tostring(self.health).."\n"..
"breath = "..tostring(self.breath).."\n"..
"gotten = "..tostring(self.gotten).."\n"..
"tamed = "..tostring(self.tamed).."\n"..
"horny = "..tostring(self.horny).."\n"..
"hornytimer = "..tostring(self.hornytimer).."\n"..
"runaway_timer = "..tostring(self.runaway_timer).."\n"..
"following = "..tostring(self.following).."\n"..
"lifetimer = "..tostring(self.lifetimer)
else
tag = self.nametag
end
self.object:set_properties({
nametag = tag,
})
end
function mob_class:get_staticdata()
for _,p in pairs(minetest.get_connected_players()) do
self:remove_particlespawners(p:get_player_name())
end
-- remove mob when out of range unless tamed
if remove_far
and self.can_despawn
and self.remove_ok
and ((not self.nametag) or (self.nametag == ""))
and self.lifetimer <= 20 then
if spawn_logging then
minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range")
end
return "remove"-- nil
end
self.remove_ok = true
self.attack = nil
self.following = nil
self.state = "stand"
local tmp = {}
for _,stat in pairs(self) do
local t = type(stat)
if t ~= "function"
and t ~= "nil"
and t ~= "userdata"
and _ ~= "_cmi_components" then
tmp[_] = self[_]
end
end
return minetest.serialize(tmp)
end
function mob_class:mob_activate(staticdata, def, dtime)
if not self.object:get_pos() or staticdata == "remove" then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
if self.type == "monster"
and minetest.settings:get_bool("only_peaceful_mobs", false) then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
local tmp = minetest.deserialize(staticdata)
if tmp then
for _,stat in pairs(tmp) do
self[_] = stat
end
end
if not self.base_texture then
-- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then
def.textures = {def.textures}
end
local c = 1
if #def.textures > c then c = #def.textures end
self.base_texture = def.textures[math.random(c)]
self.base_mesh = def.mesh
self.base_size = self.visual_size
self.base_colbox = self.collisionbox
self.base_selbox = self.selectionbox
end
if not self.base_selbox then
self.base_selbox = self.selectionbox or self.base_colbox
end
local textures = self.base_texture
local mesh = self.base_mesh
local vis_size = self.base_size
local colbox = self.base_colbox
local selbox = self.base_selbox
if self.gotten == true
and def.gotten_texture then
textures = def.gotten_texture
end
if self.gotten == true
and def.gotten_mesh then
mesh = def.gotten_mesh
end
if self.child == true then
vis_size = {
x = self.base_size.x * .5,
y = self.base_size.y * .5,
}
if def.child_texture then
textures = def.child_texture[1]
end
colbox = {
self.base_colbox[1] * .5,
self.base_colbox[2] * .5,
self.base_colbox[3] * .5,
self.base_colbox[4] * .5,
self.base_colbox[5] * .5,
self.base_colbox[6] * .5
}
selbox = {
self.base_selbox[1] * .5,
self.base_selbox[2] * .5,
self.base_selbox[3] * .5,
self.base_selbox[4] * .5,
self.base_selbox[5] * .5,
self.base_selbox[6] * .5
}
end
if self.health == 0 then
self.health = math.random (self.hp_min, self.hp_max)
end
if self.breath == nil then
self.breath = self.breath_max
end
self.path = {}
self.path.way = {} -- path to follow, table of positions
self.path.lastpos = {x = 0, y = 0, z = 0}
self.path.stuck = false
self.path.following = false -- currently following path?
self.path.stuck_timer = 0 -- if stuck for too long search for path
-- Armor groups
-- immortal=1 because we use custom health
-- handling (using "health" property)
local armor
if type(self.armor) == "table" then
armor = table.copy(self.armor)
armor.immortal = 1
else
armor = {immortal=1, fleshy = self.armor}
end
self.object:set_armor_groups(armor)
self.old_y = self.object:get_pos().y
self.old_health = self.health
self.sounds.distance = self.sounds.distance or 10
self.textures = textures
self.mesh = mesh
self.collisionbox = colbox
self.selectionbox = selbox
self.visual_size = vis_size
self.standing_in = "ignore"
self.standing_on = "ignore"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
self.texture_mods = {}
self.object:set_texture_mod("")
self.v_start = false
self.timer = 0
self.blinktimer = 0
self.blinkstatus = false
if not self.nametag then
self.nametag = def.nametag
end
if not self.custom_visual_size then
self.visual_size = nil
self.base_size = self.visual_size
if self.child then
self.visual_size = {
x = self.visual_size.x * 0.5,
y = self.visual_size.y * 0.5,
}
end
end
self.object:set_properties(self)
self:set_yaw( (math.random(0, 360) - 180) / 180 * math.pi, 6)
self:update_tag()
self._current_animation = nil
self:set_animation( "stand")
if self.on_spawn and not self.on_spawn_run then
if self.on_spawn(self) then
self.on_spawn_run = true
end
end
if not self.wears_armor and self.armor_list then
self.armor_list = nil
end
if not self._run_armor_init and self.wears_armor then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
self:set_armor_texture()
self._run_armor_init = true
end
if def.after_activate then
def.after_activate(self, staticdata, def, dtime)
end
end
-- main mob function
function mob_class:on_step(dtime)
self.lifetimer = self.lifetimer - dtime
local pos = self.object:get_pos()
if not pos then return end
if self:check_despawn(pos) then return true end
local d = 0.85
if self:check_dying() then d = 0.92 end
local v = self.object:get_velocity()
if v then
--diffuse object velocity
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d})
end
if self:falling(pos) then return end
self:check_suspend()
self:check_water_flow()
local yaw = 0
if self:is_at_water_danger() and self.state ~= "attack" then
if math.random(1, 10) <= 6 then
self:set_velocity(0)
self.state = "stand"
self:set_animation( "stand")
yaw = yaw + math.random(-0.5, 0.5)
yaw = self:set_yaw( yaw, 8)
end
else
if self.move_in_group ~= false then
self:check_herd(dtime)
end
end
if self:is_at_cliff_or_danger() then
self:set_velocity(0)
self.state = "stand"
self:set_animation( "stand")
local yaw = self.object:get_yaw() or 0
yaw = self:set_yaw( yaw + 0.78, 8)
end
if not self.fire_resistant then
mcl_burning.tick(self.object, dtime, self)
-- mcl_burning.tick may remove object immediately
if not self.object:get_pos() then return end
end
if mobs_debug then self:update_tag() end
if self.state == "die" then return end
if self.jump_sound_cooloff > 0 then
self.jump_sound_cooloff = self.jump_sound_cooloff - dtime
end
if self.opinion_sound_cooloff > 0 then
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
end
--Mob following code.
self:follow_flop()
--set animation speed relitive to velocity
self:set_animation_speed()
self:check_smooth_rotation(dtime)
self:check_head_swivel(dtime)
self:do_jump()
self:set_armor_texture()
self:check_runaway_from()
self:monster_attack()
self:npc_attack()
self:check_breeding()
self:check_aggro(dtime)
-- run custom function (defined in mob lua file)
if self.do_custom then
-- when false skip going any further
if self.do_custom(self, dtime) == false then
return
end
end
-- knockback timer
if self.pause_timer > 0 then
self.pause_timer = self.pause_timer - dtime
return
end
-- attack timer
self.timer = self.timer + dtime
if self.state ~= "attack" and self.state ~= PATHFINDING then
if self.timer < 1 then
return
end
self.timer = 0
end
self:check_particlespawners(dtime)
self:check_item_pickup()
-- never go over 100
if self.timer > 100 then
self.timer = 1
end
-- mob plays random sound at times
if math.random(1, 70) == 1 then
self:mob_sound("random", true)
end
-- environmental damage timer (every 1 second)
self.env_damage_timer = self.env_damage_timer + dtime
if (self.state == "attack" and self.env_damage_timer > 1)
or self.state ~= "attack" then
self:check_entity_cramming()
self.env_damage_timer = 0
-- check for environmental damage (water, fire, lava etc.)
if self:do_env_damage() then
return
end
-- node replace check (cow eats grass etc.)
self:replace(pos)
end
if self:do_states(dtime) then
return
end
if not self.object:get_luaentity() then
return false
end
end
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < 1 then return end
for _, player in pairs(minetest.get_connected_players()) do
local pos = player:get_pos()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 47)) do
local lua = obj:get_luaentity()
if lua and lua.is_mob then
lua.lifetimer = math.max(20, lua.lifetimer)
lua.despawn_immediately = false
end
end
end
timer = 0
end)
minetest.register_chatcommand("clearmobs",{
privs={maphack=true},
params = "<all>|<nametagged>|<range>",
description=S("Removes all spawned mobs except nametagged and tamed ones. all removes all mobs, nametagged only nametagged ones and with the range paramter all mobs in a distance of the current player are removed."),
func=function(n,param)
local p = minetest.get_player_by_name(n)
local num=tonumber(param)
for _,o in pairs(minetest.luaentities) do
if o.is_mob then
if param == "all" or
( param == "nametagged" and o.nametag ) or
( param == "" and ( not o.nametag or o.nametag == "" ) and not o.tamed ) or
( num and num > 0 and vector.distance(p:get_pos(),o.object:get_pos()) <= num ) then
o.object:remove()
end
end
end
end})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,736 @@
-- API for Mobs Redo: MineClone 2 Delux 2.0 DRM Free Early Access Super Extreme Edition
-- mobs library
mobs = {}
-- lua locals - can grab from this to easily plop them into the api lua files
--localize minetest functions
local minetest_settings = minetest.settings
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local minetest_get_modpath = minetest.get_modpath
local minetest_registered_nodes = minetest.registered_nodes
local minetest_get_node = minetest.get_node
--local minetest_get_item_group = minetest.get_item_group
local minetest_registered_entities = minetest.registered_entities
--local minetest_line_of_sight = minetest.line_of_sight
--local minetest_after = minetest.after
--local minetest_sound_play = minetest.sound_play
--local minetest_add_particlespawner = minetest.add_particlespawner
--local minetest_registered_items = minetest.registered_items
--local minetest_set_node = minetest.set_node
local minetest_add_item = minetest.add_item
--local minetest_get_craft_result = minetest.get_craft_result
--local minetest_find_path = minetest.find_path
local minetest_is_creative_enabled = minetest.is_creative_enabled
--local minetest_find_node_near = minetest.find_node_near
--local minetest_find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
--local minetest_raycast = minetest.raycast
--local minetest_get_us_time = minetest.get_us_time
local minetest_add_entity = minetest.add_entity
--local minetest_get_natural_light = minetest.get_natural_light
--local minetest_get_node_or_nil = minetest.get_node_or_nil
-- localize math functions
local math = math
-- localize vector functions
local vector = vector
local string = string
-- mob constants
--local BREED_TIME = 30
--local BREED_TIME_AGAIN = 300
--local CHILD_GROW_TIME = 60*20
--local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -10
--local FLOP_HEIGHT = 5.0
--local FLOP_HOR_SPEED = 1.5
local GRAVITY = minetest_settings:get("movement_gravity")-- + 9.81
local MAX_MOB_NAME_LENGTH = 30
--[[local MOB_CAP = {}
MOB_CAP.hostile = 70
MOB_CAP.passive = 10
MOB_CAP.ambient = 15
MOB_CAP.water = 15
]]
-- Load main settings
--local damage_enabled = minetest_settings:get_bool("enable_damage")
--local disable_blood = minetest_settings:get_bool("mobs_disable_blood")
--local mobs_drop_items = minetest_settings:get_bool("mobs_drop_items") ~= false
--local mobs_griefing = minetest_settings:get_bool("mobs_griefing") ~= false
--local spawn_protected = minetest_settings:get_bool("mobs_spawn_protected") ~= false
--local remove_far = true
local difficulty = tonumber(minetest_settings:get("mob_difficulty")) or 1.0
--local show_health = false
--local max_per_block = tonumber(minetest_settings:get("max_objects_per_block") or 64)
---local mobs_spawn_chance = tonumber(minetest_settings:get("mobs_spawn_chance") or 2.5)
-- pathfinding settings
--local enable_pathfinding = true
--local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
--local stuck_path_timeout = 10 -- how long will mob follow path before giving up
-- default nodes
--local node_ice = "mcl_core:ice"
--local node_snowblock = "mcl_core:snowblock"
--local node_snow = "mcl_core:snow"
mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
--local mod_weather = minetest_get_modpath("mcl_weather")
--local mod_explosions = minetest_get_modpath("mcl_explosions")
local mod_mobspawners = minetest_get_modpath("mcl_mobspawners")
--local mod_hunger = minetest_get_modpath("mcl_hunger")
--local mod_worlds = minetest_get_modpath("mcl_worlds")
--local mod_armor = minetest_get_modpath("mcl_armor")
--local mod_experience = minetest_get_modpath("mcl_experience")
-- random locals I found
--local los_switcher = false
--local height_switcher = false
-- Get translator
local S = minetest.get_translator(minetest.get_current_modname())
-- CMI support check
--local use_cmi = minetest.global_exists("cmi")
-- creative check
function mobs.is_creative(name)
return minetest_is_creative_enabled(name)
end
--[[local function atan(x)
if not x or x ~= x then
return 0
else
return math.atan(x)
end
end]]
-- Shows helpful debug info above each mob
--local mobs_debug = minetest_settings:get_bool("mobs_debug", false)
-- Peaceful mode message so players will know there are no monsters
if minetest_settings:get_bool("only_peaceful_mobs", false) then
minetest.register_on_joinplayer(function(player)
minetest.chat_send_player(player:get_player_name(),
S("Peaceful mode active! No monsters will spawn."))
end)
end
local api_path = minetest.get_modpath(minetest.get_current_modname()).."/api/mob_functions/"
--ignite all parts of the api
dofile(api_path .. "flow_lib.lua")
dofile(api_path .. "ai.lua")
dofile(api_path .. "animation.lua")
dofile(api_path .. "collision.lua")
dofile(api_path .. "environment.lua")
dofile(api_path .. "interaction.lua")
dofile(api_path .. "movement.lua")
dofile(api_path .. "set_up.lua")
dofile(api_path .. "attack_type_instructions.lua")
dofile(api_path .. "sound_handling.lua")
dofile(api_path .. "death_logic.lua")
dofile(api_path .. "mob_effects.lua")
dofile(api_path .. "projectile_handling.lua")
dofile(api_path .. "breeding.lua")
dofile(api_path .. "head_logic.lua")
mobs.spawning_mobs = {}
-- register mob entity
function mobs:register_mob(name, def)
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
-- Workaround for <https://github.com/minetest/minetest/issues/5966>:
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
-- FIXME: Remove workaround if it's no longer needed.
if collisionbox[5] < 0.79 then
collisionbox[5] = 0.79
end
mobs.spawning_mobs[name] = true
local function scale_difficulty(value, default, min, special)
if (not value) or (value == default) or (value == special) then
return default
else
return math.max(min, value * difficulty)
end
end
minetest.register_entity(name, {
description = def.description,
use_texture_alpha = def.use_texture_alpha,
stepheight = def.stepheight or 0.6,
stepheight_backup = def.stepheight or 0.6,
name = name,
type = def.type,
attack_type = def.attack_type,
fly = def.fly,
fly_in = def.fly_in or {"air", "__airlike"},
owner = def.owner or "",
order = def.order or "",
on_die = def.on_die,
spawn_small_alternative = def.spawn_small_alternative,
do_custom = def.do_custom,
jump_height = def.jump_height or 4, -- was 6
rotate = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
hp_min = scale_difficulty(def.hp_min, 5, 1),
hp_max = scale_difficulty(def.hp_max, 10, 1),
xp_min = def.xp_min or 1,
xp_max = def.xp_max or 5,
breath_max = def.breath_max or 6,
breathes_in_water = def.breathes_in_water or false,
physical = true,
collisionbox = collisionbox,
collide_with_objects = def.collide_with_objects or false,
selectionbox = def.selectionbox or def.collisionbox,
visual = def.visual,
visual_size = def.visual_size or {x = 1, y = 1},
mesh = def.mesh,
makes_footstep_sound = def.makes_footstep_sound or false,
view_range = def.view_range or 16,
walk_velocity = def.walk_velocity or 1,
run_velocity = def.run_velocity or 2,
damage = scale_difficulty(def.damage, 0, 0),
light_damage = def.light_damage or 0,
sunlight_damage = def.sunlight_damage or 0,
water_damage = def.water_damage or 0,
lava_damage = def.lava_damage or 8,
fire_damage = def.fire_damage or 1,
suffocation = def.suffocation or true,
fall_damage = def.fall_damage or 1,
fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2
drops = def.drops or {},
armor = def.armor or 100,
on_rightclick = mobs.create_mob_on_rightclick(def.on_rightclick),
arrow = def.arrow,
shoot_interval = def.shoot_interval,
sounds = def.sounds or {},
animation = def.animation,
jump = def.jump ~= false,
walk_chance = def.walk_chance or 50,
attacks_monsters = def.attacks_monsters or false,
group_attack = def.group_attack or false,
passive = def.passive or false,
knock_back = def.knock_back ~= false,
shoot_offset = def.shoot_offset or 0,
floats = def.floats or 1, -- floats in water by default
floats_on_lava = def.floats_on_lava or 0,
replace_rate = def.replace_rate,
replace_what = def.replace_what,
replace_with = def.replace_with,
replace_offset = def.replace_offset or 0,
on_replace = def.on_replace,
timer = 0,
state_timer = 0,
env_damage_timer = 0,
tamed = false,
pause_timer = 0,
gotten = false,
reach = def.reach or 3,
htimer = 0,
texture_list = def.textures,
child_texture = def.child_texture,
docile_by_day = def.docile_by_day or false,
time_of_day = 0.5,
fear_height = def.fear_height or 0,
runaway = def.runaway,
runaway_timer = 0,
pathfinding = def.pathfinding,
immune_to = def.immune_to or {},
explosion_radius = def.explosion_radius, -- LEGACY
explosion_damage_radius = def.explosion_damage_radius, -- LEGACY
explosiontimer_reset_radius = def.explosiontimer_reset_radius,
explosion_timer = def.explosion_timer or 3,
allow_fuse_reset = def.allow_fuse_reset ~= false,
stop_to_explode = def.stop_to_explode ~= false,
custom_attack = def.custom_attack,
double_melee_attack = def.double_melee_attack,
dogshoot_switch = def.dogshoot_switch,
dogshoot_count = 0,
dogshoot_count_max = def.dogshoot_count_max or 5,
dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
attack_animals = def.attack_animals or false,
specific_attack = def.specific_attack,
runaway_from = def.runaway_from,
owner_loyal = def.owner_loyal,
facing_fence = false,
_cmi_is_mob = true,
pushable = def.pushable or true,
--j4i stuff
yaw = 0,
automatic_face_movement_dir = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
automatic_face_movement_max_rotation_per_sec = 360, --degrees
backface_culling = true,
walk_timer = 0,
stand_timer = 0,
current_animation = "",
gravity = GRAVITY,
swim = def.swim,
swim_in = def.swim_in or {mobs_mc.items.water_source, "mcl_core:water_flowing", mobs_mc.items.river_water_source},
pitch_switch = "static",
jump_only = def.jump_only,
hostile = def.hostile,
neutral = def.neutral,
attacking = nil,
visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1},
punch_timer_cooloff = def.punch_timer_cooloff or 0.5,
death_animation_timer = 0,
hostile_cooldown = def.hostile_cooldown or 15,
tilt_fly = def.tilt_fly,
tilt_swim = def.tilt_swim,
fall_slow = def.fall_slow,
projectile_cooldown_min = def.projectile_cooldown_min or 2,
projectile_cooldown_max = def.projectile_cooldown_max or 6,
skittish = def.skittish,
minimum_follow_distance = def.minimum_follow_distance or 0.5, --make mobs not freak out when underneath
memory = 0, -- memory timer if chasing/following
fly_random_while_attack = def.fly_random_while_attack,
--for spiders
always_climb = def.always_climb,
--despawn mechanic variables
lifetimer_reset = 30, --30 seconds
lifetimer = 30, --30 seconds
--breeding stuff
breed_timer = 0,
breed_lookout_timer = 0,
breed_distance = def.breed_distance or 1.5, --how far away mobs have to be to begin actual breeding
breed_lookout_timer_goal = 30, --30 seconds (this timer is for how long the mob looks for a mate)
breed_timer_cooloff = 5*60, -- 5 minutes (this timer is for how long the mob has to wait before being bred again)
bred = false,
follow = def.follow, --this item is also used for the breeding mechanism
follow_distance = def.follow_distance or 2,
baby_size = def.baby_size or 0.5,
baby = false,
grow_up_timer = 0,
grow_up_goal = 20*60, --in 20 minutes the mob grows up
special_breed_timer = 0, --this is used for the AHEM AHEM part of breeding
backup_visual_size = def.visual_size,
backup_collisionbox = collisionbox,
backup_selectionbox = def.selectionbox or def.collisionbox,
--fire timer
burn_timer = 0,
ignores_cobwebs = def.ignores_cobwebs,
breath = def.breath_max or 6,
random_sound_timer_min = 3,
random_sound_timer_max = 10,
--head code variables
--defaults are for the cow's default
--because I don't know what else to set them
--to :P
--you must use these to adjust the mob's head positions
--has_head is used as a logic gate (quick easy check)
has_head = def.has_head or false,
--head_bone is the actual bone in the model which the head
--is attached to for animation
head_bone = def.head_bone or "head",
--this part controls the base position of the head calculations
--localized to the mob's visual yaw when gotten (self.object:get_yaw())
--you can enable the debug in /mob_functions/head_logic.lua by uncommenting the
--particle spawner code
head_height_offset = def.head_height_offset or 1.0525,
head_direction_offset = def.head_direction_offset or 0.5,
--this part controls the visual of the head
head_bone_pos_y = def.head_bone_pos_y or 3.6,
head_bone_pos_z = def.head_bone_pos_z or -0.6,
head_pitch_modifier = def.head_pitch_modifier or 0,
--these variables are switches in case the model
--moves the wrong way
swap_y_with_x = def.swap_y_with_x or false,
reverse_head_yaw = def.reverse_head_yaw or false,
--END HEAD CODE VARIABLES
--end j4i stuff
-- MCL2 extensions
teleport = mobs.teleport,
do_teleport = def.do_teleport,
spawn_class = def.spawn_class,
ignores_nametag = def.ignores_nametag or false,
rain_damage = def.rain_damage or 0,
glow = def.glow,
--can_despawn = can_despawn,
child = def.child or false,
texture_mods = {},
shoot_arrow = def.shoot_arrow,
sounds_child = def.sounds_child,
explosion_strength = def.explosion_strength,
suffocation_timer = 0,
follow_velocity = def.follow_velocity or 2.4,
instant_death = def.instant_death or false,
fire_resistant = def.fire_resistant or false,
fire_damage_resistant = def.fire_damage_resistant or false,
ignited_by_sunlight = def.ignited_by_sunlight or false,
eye_height = def.eye_height or 1.5,
defuse_reach = def.defuse_reach or 4,
-- End of MCL2 extensions
on_spawn = def.on_spawn,
--on_blast = def.on_blast or do_tnt,
on_step = mobs.mob_step,
--do_punch = def.do_punch,
on_punch = mobs.mob_punch,
--on_breed = def.on_breed,
--on_grown = def.on_grown,
--on_detach_child = mob_detach_child,
on_activate = function(self, staticdata, dtime)
self.object:set_acceleration(vector.new(0,-GRAVITY, 0))
return mobs.mob_activate(self, staticdata, def, dtime)
end,
get_staticdata = function(self)
return mobs.mob_staticdata(self)
end,
--harmed_by_heal = def.harmed_by_heal,
})
if minetest_get_modpath("doc_identifier") then
doc.sub.identifier.register_object(name, "basics", "mobs")
end
end -- END mobs:register_mob function
-- register arrow for shoot attack
function mobs:register_arrow(name, def)
-- errorcheck
if not name or not def then
print("failed to register arrow entity")
return
end
minetest.register_entity(name.."_entity", {
physical = false,
visual = def.visual,
visual_size = def.visual_size,
textures = def.textures,
velocity = def.velocity,
hit_player = def.hit_player,
hit_node = def.hit_node,
hit_mob = def.hit_mob,
hit_object = def.hit_object,
drop = def.drop or false, -- drops arrow as registered item when true
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
timer = 0,
switch = 0,
owner_id = def.owner_id,
rotate = def.rotate,
speed = def.speed or nil,
on_step = function(self)
local vel = self.object:get_velocity()
local pos = self.object:get_pos()
if self.timer > 150
or not mobs.within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove();
return
end
-- does arrow have a tail (fireball)
if def.tail
and def.tail == 1
and def.tail_texture then
--do this to prevent clipping through main entity sprite
local pos_adjustment = vector.multiply(vector.normalize(vel), -1)
local divider = def.tail_distance_divider or 1
pos_adjustment = vector.divide(pos_adjustment, divider)
local new_pos = vector.add(pos, pos_adjustment)
minetest.add_particle({
pos = new_pos,
velocity = {x = 0, y = 0, z = 0},
acceleration = {x = 0, y = 0, z = 0},
expirationtime = def.expire or 0.25,
collisiondetection = false,
texture = def.tail_texture,
size = def.tail_size or 5,
glow = def.glow or 0,
})
end
if self.hit_node then
local node = minetest_get_node(pos).name
if minetest_registered_nodes[node].walkable then
self.hit_node(self, pos, node)
if self.drop == true then
pos.y = pos.y + 1
self.lastpos = (self.lastpos or pos)
minetest_add_item(self.lastpos, self.object:get_luaentity().name)
end
self.object:remove();
return
end
end
if self.hit_player or self.hit_mob or self.hit_object then
for _,player in pairs(minetest_get_objects_inside_radius(pos, 1.5)) do
if self.hit_player
and player:is_player() then
if self.hit_player then
self.hit_player(self, player)
else
mobs.arrow_hit(self, player)
end
self.object:remove();
return
end
--[[
local entity = player:get_luaentity()
if entity
and self.hit_mob
and entity._cmi_is_mob == true
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
--self.hit_mob(self, player)
self.object:remove();
return
end
]]--
--[[
if entity
and self.hit_object
and (not entity._cmi_is_mob)
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
--self.hit_object(self, player)
self.object:remove();
return
end
]]--
end
end
self.lastpos = pos
end
})
end
-- Register spawn eggs
-- Note: This also introduces the “spawn_egg” group:
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
function mobs:register_egg(mob, desc, background, addegg, no_creative)
local grp = {spawn_egg = 1}
-- do NOT add this egg to creative inventory (e.g. dungeon master)
if no_creative == true then
grp.not_in_creative_inventory = 1
end
local invimg = background
if addegg == 1 then
invimg = "mobs_chicken_egg.png^(" .. invimg ..
"^[mask:mobs_chicken_egg_overlay.png)"
end
-- register old stackable mob egg
minetest.register_craftitem(mob, {
description = desc,
inventory_image = invimg,
groups = grp,
_doc_items_longdesc = S("This allows you to place a single mob."),
_doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."),
on_place = function(itemstack, placer, pointed_thing)
local pos = pointed_thing.above
-- am I clicking on something with existing on_rightclick function?
local under = minetest_get_node(pointed_thing.under)
local def = minetest_registered_nodes[under.name]
if def and def.on_rightclick then
return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
end
if pos
--and within_limits(pos, 0)
and not minetest.is_protected(pos, placer:get_player_name()) then
local name = placer:get_player_name()
local privs = minetest.get_player_privs(name)
if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then
if minetest.is_protected(pointed_thing.under, name) then
minetest.record_protection_violation(pointed_thing.under, name)
return itemstack
end
if not privs.maphack then
minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner."))
return itemstack
end
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
if not mobs.is_creative(name) then
itemstack:take_item()
end
return itemstack
end
if not minetest_registered_entities[mob] then
return itemstack
end
if minetest_settings:get_bool("only_peaceful_mobs", false)
and minetest_registered_entities[mob].type == "monster" then
minetest.chat_send_player(name, S("Only peaceful mobs allowed!"))
return itemstack
end
local mob = minetest_add_entity(pos, mob)
minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos))
local ent = mob:get_luaentity()
-- don't set owner if monster or sneak pressed
--[[
if ent.type ~= "monster"
and not placer:get_player_control().sneak then
ent.owner = placer:get_player_name()
ent.tamed = true
end
]]--
-- set nametag
local nametag = itemstack:get_meta():get_string("name")
if nametag ~= "" then
if string.len(nametag) > MAX_MOB_NAME_LENGTH then
nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
end
ent.nametag = nametag
--update_tag(ent)
end
-- if not in creative then take item
if not mobs.is_creative(placer:get_player_name()) then
itemstack:take_item()
end
end
return itemstack
end,
})
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,257 @@
local math = math
local vector = vector
local HALF_PI = math.pi/2
local vector_direction = vector.direction
local vector_distance = vector.distance
local vector_new = vector.new
local minetest_dir_to_yaw = minetest.dir_to_yaw
-- set defined animation
mobs.set_mob_animation = function(self, anim, fixed_frame)
if not self.animation or not anim then
return
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
return
end
if (not self.animation[anim .. "_start"] or not self.animation[anim .. "_end"]) then
return
end
--animations break if they are constantly set
--so we put this return gate to check if it is
--already at the animation we are trying to implement
if self.current_animation == anim then
return
end
local a_start = self.animation[anim .. "_start"]
local a_end
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
self.object:set_animation({
x = a_start,
y = a_end},
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
0, self.animation[anim .. "_loop"] ~= false)
self.current_animation = anim
end
mobs.death_effect = function(pos, yaw, collisionbox, rotate)
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
else
min = { x = -0.5, y = 0, z = -0.5 }
max = { x = 0.5, y = 0.5, z = 0.5 }
end
if rotate then
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
min, max = vector.sort(min, max)
min = vector.multiply(min, 0.5)
max = vector.multiply(max, 0.5)
end
minetest.add_particlespawner({
amount = 50,
time = 0.001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector_new(-5,-5,-5),
maxvel = vector_new(5,5,5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png^[colorize:#000000:255",
})
minetest.sound_play("mcl_mobs_mob_poof", {
pos = pos,
gain = 1.0,
max_hear_distance = 8,
}, true)
end
--this allows auto facedir rotation while making it so mobs
--don't look like wet noodles flopping around
mobs.movement_rotation_lock = function(self)
local current_engine_yaw = self.object:get_yaw()
local current_lua_yaw = self.yaw
if current_engine_yaw > math.pi * 2 then
current_engine_yaw = current_engine_yaw - (math.pi * 2)
end
if math.abs(current_engine_yaw - current_lua_yaw) <= 0.05 and self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
elseif math.abs(current_engine_yaw - current_lua_yaw) > 0.05 and self.object:get_properties().automatic_face_movement_dir == false then
self.object:set_properties{automatic_face_movement_dir = self.rotate}
end
end
--this is used when a mob is chasing a player
mobs.set_yaw_while_attacking = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.attacking:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end
--this is used to unlock a mob's yaw after attacking
mobs.unlock_yaw = function(self)
if self.object:get_properties().automatic_face_movement_dir == false then
self.object:set_properties{automatic_face_movement_dir = self.rotate}
end
end
--this is used to lock a mob's yaw when they're standing
mobs.lock_yaw = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
end
local calculate_pitch = function(self)
local pos = self.object:get_pos()
local pos2 = self.old_pos
if pos == nil or pos2 == nil then
return false
end
return minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos.x,0,pos.z),vector_new(pos2.x,0,pos2.z)),0,pos.y - pos2.y)) + HALF_PI
end
--this is a helper function used to make mobs pitch rotation dynamically flow when flying/swimming
mobs.set_dynamic_pitch = function(self)
local pitch = calculate_pitch(self)
if not pitch then
return
end
local current_rotation = self.object:get_rotation()
current_rotation.x = pitch
self.object:set_rotation(current_rotation)
self.pitch_switch = "dynamic"
end
--this is a helper function used to make mobs pitch rotation reset when flying/swimming
mobs.set_static_pitch = function(self)
if self.pitch_switch == "static" then
return
end
local current_rotation = self.object:get_rotation()
current_rotation.x = 0
self.object:set_rotation(current_rotation)
self.pitch_switch = "static"
end
--this is a helper function for mobs explosion animation
mobs.handle_explosion_animation = function(self)
--secondary catch-all
if not self.explosion_animation then
self.explosion_animation = 0
end
--the timer works from 0 for sense of a 0 based counting
--but this just bumps it up so it's usable in here
local explosion_timer_adjust = self.explosion_animation + 1
local visual_size_modified = table.copy(self.visual_size_origin)
visual_size_modified.x = visual_size_modified.x * (explosion_timer_adjust ^ 3)
visual_size_modified.y = visual_size_modified.y * explosion_timer_adjust
self.object:set_properties({visual_size = visual_size_modified})
end
--this is used when a mob is following player
mobs.set_yaw_while_following = function(self)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.following_person:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end
--this is used for when mobs breed
mobs.set_yaw_while_breeding = function(self, mate)
if self.object:get_properties().automatic_face_movement_dir then
self.object:set_properties{automatic_face_movement_dir = false}
end
--turn positions into pseudo 2d vectors
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = mate:get_pos()
pos2.y = 0
local new_direction = vector_direction(pos1,pos2)
local new_yaw = minetest_dir_to_yaw(new_direction)
self.object:set_yaw(new_yaw)
self.yaw = new_yaw
end

View File

@ -0,0 +1,347 @@
local vector_direction = vector.direction
--local minetest_dir_to_yaw = minetest.dir_to_yaw
local vector_distance = vector.distance
local vector_multiply = vector.multiply
local math_random = math.random
--[[
_ _ _ _
| | | | | | | |
| | | | __ _ _ __ __| | | |
| | | | / _` | '_ \ / _` | | |
|_| | |___| (_| | | | | (_| | |_|
(_) \_____/\__,_|_| |_|\__,_| (_)
]]--
--[[
_____ _ _
| ___| | | | |
| |____ ___ __ | | ___ __| | ___
| __\ \/ / '_ \| |/ _ \ / _` |/ _ \
| |___> <| |_) | | (_) | (_| | __/
\____/_/\_\ .__/|_|\___/ \__,_|\___|
| |
|_|
]]--
mobs.explode_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
--make mob walk up to player within 2 nodes distance then start exploding
if distance_from_attacking >= self.reach and
--don't allow explosion to cancel unless out of the reach boundary
not (self.explosion_animation and self.explosion_animation > 0 and distance_from_attacking <= self.defuse_reach) then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
mobs.reverse_explosion_animation(self,dtime)
else
mobs.set_velocity(self,0)
--this is the only way I can reference this without dumping extra data on all mobs
if not self.explosion_animation then
self.explosion_animation = 0
end
--play ignite sound
if self.explosion_animation == 0 then
mobs.play_sound(self,"attack")
end
mobs.set_mob_animation(self,"stand")
mobs.handle_explosion_animation(self)
self.explosion_animation = self.explosion_animation + (dtime/2.5)
end
--make explosive mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--do biggening explosion thing
if self.explosion_animation and self.explosion_animation > self.explosion_timer then
mcl_explosions.explode(self.object:get_pos(), self.explosion_strength,{ drop_chance = 1.0 })
self.object:remove()
end
end
--this is a small helper function to make working with explosion animations easier
mobs.reverse_explosion_animation = function(self,dtime)
--if explosion animation was greater than 0 then reverse it
if self.explosion_animation and self.explosion_animation > 0 then
self.explosion_animation = self.explosion_animation - dtime
if self.explosion_animation < 0 then
self.explosion_animation = 0
end
end
mobs.handle_explosion_animation(self)
end
--[[
______ _
| ___ \ | |
| |_/ / _ _ __ ___| |__
| __/ | | | '_ \ / __| '_ \
| | | |_| | | | | (__| | | |
\_| \__,_|_| |_|\___|_| |_|
]]--
mobs.punch_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
local distance_from_attacking = mobs.get_2d_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.minimum_follow_distance then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self, "run")
else
mobs.set_velocity(self, 0)
mobs.set_mob_animation(self, "stand")
end
mobs.set_yaw_while_attacking(self)
--make punchy mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
--mobs that can climb over stuff
if self.always_climb and node_in_front_of > 0 then
mobs.climb(self)
end
--auto reset punch_timer
if not self.punch_timer then
self.punch_timer = 0
end
if self.punch_timer > 0 then
self.punch_timer = self.punch_timer - dtime
end
end
mobs.punch_attack = function(self)
self.attacking:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self.damage}
}, nil)
self.punch_timer = self.punch_timer_cooloff
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = self.attacking:get_pos()
pos2.y = 0
local dir = vector_direction(pos1,pos2)
dir = vector_multiply(dir,3)
if self.attacking:get_velocity().y <= 1 then
dir.y = 5
end
self.attacking:add_velocity(dir)
end
--[[
______ _ _ _ _
| ___ \ (_) | | (_) |
| |_/ / __ ___ _ ___ ___| |_ _| | ___
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
| | | | | (_) | | __/ (__| |_| | | __/
\_| |_| \___/| |\___|\___|\__|_|_|\___|
_/ |
|__/
]]--
mobs.projectile_attack_walk = function(self,dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_velocity(self,0)
mobs.set_mob_animation(self,"stand")
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
--reset timer
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
--make shooty mobs jump
--check for nodes to jump over
--explosive mobs will just ride against walls for now
local node_in_front_of = mobs.jump_check(self)
if node_in_front_of == 1 then
mobs.jump(self)
end
end
--[[
_ ______ _ _
| | | ___| | | |
| | | |_ | |_ _ | |
| | | _| | | | | | | |
|_| | | | | |_| | |_|
(_) \_| |_|\__, | (_)
__/ |
|___/
]]--
--[[
______ _ _ _ _
| ___ \ (_) | | (_) |
| |_/ / __ ___ _ ___ ___| |_ _| | ___
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
| | | | | (_) | | __/ (__| |_| | | __/
\_| |_| \___/| |\___|\___|\__|_|_|\___|
_/ |
|__/
]]--
local random_pitch_multiplier = {-1,1}
mobs.projectile_attack_fly = function(self, dtime)
--this needs an exception
if self.attacking == nil or not self.attacking:is_player() then
self.attacking = nil
return
end
--this is specifically for random ghast movement
if self.fly_random_while_attack then
--enable rotation locking
mobs.movement_rotation_lock(self)
self.walk_timer = self.walk_timer - dtime
--reset the walk timer
if self.walk_timer <= 0 then
--re-randomize the walk timer
self.walk_timer = math.random(1,6) + math.random()
--set the mob into a random direction
self.yaw = (math_random() * (math.pi * 2))
--create a truly random pitch, since there is no easy access to pitch math that I can find
self.pitch = math_random() * math.random(1,3) * random_pitch_multiplier[math_random(1,2)]
end
mobs.set_fly_velocity(self, self.run_velocity)
else
mobs.set_yaw_while_attacking(self)
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
if distance_from_attacking >= self.reach then
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, self.run_velocity)
mobs.set_mob_animation(self,"run")
else
mobs.set_pitch_while_attacking(self)
mobs.set_fly_velocity(self, 0)
mobs.set_mob_animation(self,"stand")
end
end
--do this to not load data into other mobs
if not self.projectile_timer then
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
end
--run projectile timer
if self.projectile_timer > 0 then
self.projectile_timer = self.projectile_timer - dtime
--shoot
if self.projectile_timer <= 0 then
if self.fly_random_while_attack then
mobs.set_yaw_while_attacking(self)
self.walk_timer = 0
end
--reset timer
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
mobs.shoot_projectile(self)
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,179 @@
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local vector = vector
--check to see if someone nearby has some tasty food
mobs.check_following = function(self) -- returns true or false
--ignore
if not self.follow then
self.following_person = nil
return false
end
--hey look, this thing works for passive mobs too!
local follower = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height)
--check if the follower is a player incase they log out
if follower and follower:is_player() then
local stack = follower:get_wielded_item()
--safety check
if not stack then
self.following_person = nil
return false
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
self.following_person = follower
return true
end
end
--everything failed
self.following_person = nil
return false
end
--a function which attempts to make mobs enter
--the breeding state
mobs.enter_breed_state = function(self,clicker)
--do not breed if baby
if self.baby then
return false
end
--do not do anything if looking for mate or
--if cooling off from breeding
if self.breed_lookout_timer > 0 or self.breed_timer > 0 then
return false
end
--if this is caught, that means something has gone
--seriously wrong
if not clicker or not clicker:is_player() then
return false
end
local stack = clicker:get_wielded_item()
--safety check
if not stack then
return false
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
if not minetest.is_creative_enabled(clicker:get_player_name()) then
stack:take_item()
clicker:set_wielded_item(stack)
end
self.breed_lookout_timer = self.breed_lookout_timer_goal
self.bred = true
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
return true
end
--everything failed
return false
end
--find the closest mate in the area
mobs.look_for_mate = function(self)
local pos1 = self.object:get_pos()
pos1.y = pos1.y + self.eye_height
local mates_in_area = {}
local winner_mate = nil
local mates_detected = 0
local radius = self.view_range
--get mates in radius
for _,mate in pairs(minetest_get_objects_inside_radius(pos1, radius)) do
--look for a breeding mate
if mate and mate:get_luaentity()
and mate:get_luaentity()._cmi_is_mob
and mate:get_luaentity().name == self.name
and mate:get_luaentity().breed_lookout_timer > 0
and mate:get_luaentity() ~= self then
local pos2 = mate:get_pos()
local distance = vector.distance(pos1,pos2)
if distance <= radius then
if minetest.line_of_sight then
--must add eye height or stuff breaks randomly because of
--seethrough nodes being a blocker (like grass)
if minetest.line_of_sight(
vector.new(pos1.x, pos1.y, pos1.z),
vector.new(pos2.x, pos2.y + mate:get_properties().eye_height, pos2.z)
) then
mates_detected = mates_detected + 1
mates_in_area[mate] = distance
end
else
mates_detected = mates_detected + 1
mates_in_area[mate] = distance
end
end
end
end
--return if there's no one near by
if mates_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
return nil
end
--do a default radius max
local shortest_distance = radius + 1
--sort through mates and find the closest mate
for mate,distance in pairs(mates_in_area) do
if distance < shortest_distance then
shortest_distance = distance
winner_mate = mate
end
end
return winner_mate
end
--make the baby grow up
mobs.baby_grow_up = function(self)
self.baby = nil
self.visual_size = self.backup_visual_size
self.collisionbox = self.backup_collisionbox
self.selectionbox = self.backup_selectionbox
self.object:set_properties(self)
end
--makes the baby grow up faster with diminishing returns
mobs.make_baby_grow_faster = function(self,clicker)
if clicker and clicker:is_player() then
local stack = clicker:get_wielded_item()
--safety check
if not stack then
return false
end
local item_name = stack:get_name()
--all checks have passed, that guy has some good looking food
if item_name == self.follow then
self.grow_up_timer = self.grow_up_timer - (self.grow_up_timer * 0.10) --take 10 percent off - diminishing returns
if not minetest.is_creative_enabled(clicker:get_player_name()) then
stack:take_item()
clicker:set_wielded_item(stack)
end
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
return true
end
end
return false
end

View File

@ -0,0 +1,135 @@
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local math_random = math.random
local vector_multiply = vector.multiply
local vector_direction = vector.direction
local integer_test = {-1,1}
mobs.collision = function(self)
local pos = self.object:get_pos()
if not self or not self.object or not self.object:get_luaentity() then
return
end
--do collision detection from the base of the mob
local collisionbox = self.object:get_properties().collisionbox
pos.y = pos.y + collisionbox[2]
local collision_boundary = collisionbox[4]
local radius = collision_boundary
if collisionbox[5] > collision_boundary then
radius = collisionbox[5]
end
local collision_count = 0
local check_for_attack = false
if self.attack_type == "punch" and self.hostile and self.attacking then
check_for_attack = true
end
for _,object in ipairs(minetest_get_objects_inside_radius(pos, radius*1.25)) do
if object and object ~= self.object and (object:is_player() or (object:get_luaentity() and object:get_luaentity()._cmi_is_mob == true and object:get_luaentity().health > 0)) and
--don't collide with rider, rider don't collide with thing
(not object:get_attach() or (object:get_attach() and object:get_attach() ~= self.object)) and
(not self.object:get_attach() or (self.object:get_attach() and self.object:get_attach() ~= object)) then
--stop infinite loop
collision_count = collision_count + 1
--mob cramming
if collision_count > 30 then
self.health = -20
break
end
local pos2 = object:get_pos()
local object_collisionbox = object:get_properties().collisionbox
pos2.y = pos2.y + object_collisionbox[2]
local object_collision_boundary = object_collisionbox[4]
--this is checking the difference of the object collided with's possision
--if positive top of other object is inside (y axis) of current object
local y_base_diff = (pos2.y + object_collisionbox[5]) - pos.y
local y_top_diff = (pos.y + collisionbox[5]) - pos2.y
local distance = vector.distance(vector.new(pos.x,0,pos.z),vector.new(pos2.x,0,pos2.z))
if distance <= collision_boundary + object_collision_boundary and y_base_diff >= 0 and y_top_diff >= 0 then
local dir = vector.direction(pos,pos2)
dir.y = 0
--eliminate mob being stuck in corners
if dir.x == 0 and dir.z == 0 then
--slightly adjust mob position to prevent equal length
--corner/wall sticking
dir.x = dir.x + ((math_random()/10)*integer_test[math.random(1,2)])
dir.z = dir.z + ((math_random()/10)*integer_test[math.random(1,2)])
end
local velocity = dir
--0.5 is the max force multiplier
local force = 0.5 - (0.5 * distance / (collision_boundary + object_collision_boundary))
local vel1 = vector.multiply(velocity, -1.5)
local vel2 = vector.multiply(velocity, 1.5)
vel1 = vector.multiply(vel1, force * 10)
vel2 = vector.multiply(vel2, force)
if object:is_player() then
vel2 = vector_multiply(vel2, 2.5)
--integrate mob punching into collision detection
if check_for_attack and self.punch_timer <= 0 then
if object == self.attacking then
mobs.punch_attack(self)
end
end
end
self.object:add_velocity(vel1)
object:add_velocity(vel2)
end
end
end
end
--this is used for arrow collisions
mobs.arrow_hit = function(self, player)
player:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = self._damage}
}, nil)
--knockback
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = player:get_pos()
pos2.y = 0
local dir = vector_direction(pos1,pos2)
dir = vector_multiply(dir,3)
if player:get_velocity().y <= 1 then
dir.y = 5
end
player:add_velocity(dir)
end

View File

@ -0,0 +1,161 @@
local minetest_add_item = minetest.add_item
--local minetest_sound_play = minetest.sound_play
local math_pi = math.pi
local math_random = math.random
local math_floor = math.floor
local HALF_PI = math_pi / 2
local vector_new = vector.new
-- drop items
local item_drop = function(self, cooked, looting_level)
looting_level = looting_level or 0
-- no drops for child mobs (except monster)
if (self.child and self.type ~= "monster") then
return
end
local obj, item
local pos = self.object:get_pos()
self.drops = self.drops or {} -- nil check
for n = 1, #self.drops do
local dropdef = self.drops[n]
local chance = 1 / dropdef.chance
local looting_type = dropdef.looting
if looting_level > 0 then
local chance_function = dropdef.looting_chance_function
if chance_function then
chance = chance_function(looting_level)
elseif looting_type == "rare" then
chance = chance + (dropdef.looting_factor or 0.01) * looting_level
end
end
local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common")
if math_random() < chance then
num = math_random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then
do_common_looting = false
end
if do_common_looting then
num = num + math_floor(math_random(0, looting_level) + 0.5)
end
if num > 0 then
item = dropdef.name
-- cook items when true
if cooked then
local output = minetest.get_craft_result({
method = "cooking",
width = 1,
items = {item},
})
if output and output.item and not output.item:is_empty() then
item = output.item:get_name()
end
end
-- add item if it exists
for x = 1, num do
obj = minetest_add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then
obj:set_velocity({
x = math_random(-10, 10) / 9,
y = 6,
z = math_random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end
end
end
self.drops = {}
end
mobs.death_logic = function(self, dtime)
--stop crashing game when object is nil
if not self or not self.object or not self.object:get_luaentity() then
return
end
self.death_animation_timer = self.death_animation_timer + dtime
--get all attached entities and sort through them
local attached_entities = self.object:get_children()
if #attached_entities > 0 then
for _,entity in pairs(attached_entities) do
--kick the player off
if entity:is_player() then
mobs.detach(entity)
--kick mobs off
--if there is scaling issues, this needs an additional check
else
entity:set_detach()
end
end
end
--stop mob from getting in the way of other mobs you're fighting
if self.object:get_properties().pointable then
self.object:set_properties({pointable = false})
end
--the final POOF of a mob despawning
if self.death_animation_timer >= 1.25 then
item_drop(self,false,1)
mobs.death_effect(self)
mcl_experience.throw_xp(self.object:get_pos(), math_random(self.xp_min, self.xp_max))
if self.on_die then
self.on_die(self, self.object:get_pos())
end
self.object:remove()
return
end
--I'm sure there's a more efficient way to do this
--but this is the easiest, easier to work with 1 variable synced
--this is also not smooth
local death_animation_roll = self.death_animation_timer * 2 -- * 2 to make it faster
if death_animation_roll > 1 then
death_animation_roll = 1
end
local rot = self.object:get_rotation() --(no pun intended)
rot.z = death_animation_roll * HALF_PI
self.object:set_rotation(rot)
mobs.set_mob_animation(self,"stand", true)
--flying and swimming mobs just fall down
if self.fly or self.swim then
if self.object:get_acceleration().y ~= -self.gravity then
self.object:set_acceleration(vector_new(0,-self.gravity,0))
end
end
--when landing allow mob to slow down and just fall if in air
if self.pause_timer <= 0 then
mobs.set_velocity(self,0)
end
end

View File

@ -0,0 +1,250 @@
local minetest_line_of_sight = minetest.line_of_sight
--local minetest_dir_to_yaw = minetest.dir_to_yaw
local minetest_yaw_to_dir = minetest.yaw_to_dir
local minetest_get_node = minetest.get_node
local minetest_get_item_group = minetest.get_item_group
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
local minetest_get_node_or_nil = minetest.get_node_or_nil
local minetest_registered_nodes = minetest.registered_nodes
local minetest_get_connected_players = minetest.get_connected_players
local vector_new = vector.new
local vector_add = vector.add
local vector_multiply = vector.multiply
local vector_distance = vector.distance
local table_copy = table.copy
local math_abs = math.abs
-- default function when mobs are blown up with TNT
--[[local function do_tnt(obj, damage)
obj.object:punch(obj.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, nil)
return false, true, {}
end]]
--a fast function to be able to detect only players without using objects_in_radius
mobs.detect_closest_player_within_radius = function(self, line_of_sight, radius, object_height_adder)
local pos1 = self.object:get_pos()
local players_in_area = {}
local winner_player = nil
local players_detected = 0
--get players in radius
for _,player in pairs(minetest.get_connected_players()) do
if player and player:get_hp() > 0 then
local pos2 = player:get_pos()
local distance = vector_distance(pos1,pos2)
if distance <= radius then
if line_of_sight then
--must add eye height or stuff breaks randomly because of
--seethrough nodes being a blocker (like grass)
if minetest_line_of_sight(
vector_new(pos1.x, pos1.y + object_height_adder, pos1.z),
vector_new(pos2.x, pos2.y + player:get_properties().eye_height, pos2.z)
) then
players_detected = players_detected + 1
players_in_area[player] = distance
end
else
players_detected = players_detected + 1
players_in_area[player] = distance
end
end
end
end
--return if there's no one near by
if players_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
return nil
end
--do a default radius max
local shortest_distance = radius + 1
--sort through players and find the closest player
for player,distance in pairs(players_in_area) do
if distance < shortest_distance then
shortest_distance = distance
winner_player = player
end
end
return winner_player
end
--check if a mob needs to jump
mobs.jump_check = function(self,dtime)
local pos = self.object:get_pos()
pos.y = pos.y + 0.1
local dir = minetest_yaw_to_dir(self.yaw)
local collisionbox = self.object:get_properties().collisionbox
local radius = collisionbox[4] + 0.5
vector_multiply(dir, radius)
--only jump if there's a node and a non-solid node above it
local test_dir = vector_add(pos,dir)
local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0
test_dir.y = test_dir.y + 1
local green_flag_2 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") == 0
if green_flag_1 and green_flag_2 then
--can jump over node
return 1
elseif green_flag_1 and not green_flag_2 then
--wall in front of mob
return 2
end
--nothing to jump over
return 0
end
-- a helper function to quickly turn neutral passive mobs hostile
local turn_hostile = function(self,detected_mob)
--drop in variables for attacking (stops crash)
detected_mob.punch_timer = 0
--set to hostile
detected_mob.hostile = true
--hostile_cooldown timer is initialized here
detected_mob.hostile_cooldown_timer = detected_mob.hostile_cooldown
--set target to the same
detected_mob.attacking = self.attacking
end
--allow hostile mobs to signal to other mobs
--to switch from neutal passive to neutral hostile
mobs.group_attack_initialization = function(self)
--get basic data
local friends_list
if self.group_attack == true then
friends_list = {self.name}
else
friends_list = table_copy(self.group_attack)
end
local objects_in_area = minetest_get_objects_inside_radius(self.object:get_pos(), self.view_range)
--get the player's name
local name = self.attacking:get_player_name()
--re-use local variable
local detected_mob
--run through mobs in viewing distance
for _,object in pairs(objects_in_area) do
if object and object:get_luaentity() then
detected_mob = object:get_luaentity()
-- only alert members of same mob or friends
if detected_mob._cmi_is_mob and detected_mob.state ~= "attack" and detected_mob.owner ~= name then
if detected_mob.name == self.name then
turn_hostile(self,detected_mob)
else
for _,id in pairs(friends_list) do
if detected_mob.name == id then
turn_hostile(self,detected_mob)
break
end
end
end
end
--THIS NEEDS TO BE RE-IMPLEMENTED AS A GLOBAL HIT IN MOB_PUNCH!!
-- have owned mobs attack player threat
--if obj.owner == name and obj.owner_loyal then
-- do_attack(obj, self.object)
--end
end
end
end
-- check if within physical map limits (-30911 to 30927)
-- within_limits, wmin, wmax = nil, -30913, 30928
mobs.within_limits = function(pos, radius)
local wmin, wmax
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
return pos
and (pos.x - radius) > wmin and (pos.x + radius) < wmax
and (pos.y - radius) > wmin and (pos.y + radius) < wmax
and (pos.z - radius) > wmin and (pos.z + radius) < wmax
end
-- get node but use fallback for nil or unknown
mobs.node_ok = function(pos, fallback)
fallback = fallback or mobs.fallback_node
local node = minetest_get_node_or_nil(pos)
if node and minetest_registered_nodes[node.name] then
return node
end
return minetest_registered_nodes[fallback]
end
--a teleport functoin
mobs.teleport = function(self, target)
if self.do_teleport then
if self.do_teleport(self, target) == false then
return
end
end
end
--a function used for despawning mobs
mobs.check_for_player_within_area = function(self, radius)
local pos1 = self.object:get_pos()
--get players in radius
for _,player in pairs(minetest_get_connected_players()) do
if player and player:get_hp() > 0 then
local pos2 = player:get_pos()
local distance = vector_distance(pos1,pos2)
if distance < radius then
--found a player
return true
end
end
end
--did not find a player
return false
end
--a simple helper function for mobs following
mobs.get_2d_distance = function(pos1,pos2)
pos1.y = 0
pos2.y = 0
return vector_distance(pos1, pos2)
end
-- fall damage onto solid ground
mobs.calculate_fall_damage = function(self)
if self.old_velocity and self.old_velocity.y < -7 and self.object:get_velocity().y == 0 then
local vel = self.object:get_velocity()
if vel then
local damage = math_abs(self.old_velocity.y + 7) * 2
self.pause_timer = 0.4
self.health = self.health - damage
end
end
end

View File

@ -0,0 +1,78 @@
--this is from https://github.com/HybridDog/builtin_item/blob/e6dfd9dce86503b3cbd1474257eca5f6f6ca71c2/init.lua#L50
local
minetest,vector,math,pairs,minetest_get_node,vector_subtract,minetest_registered_nodes
=
minetest,vector,math,pairs,minetest.get_node,vector.subtract,minetest.registered_nodes
local tab
local n
local function get_nodes(pos)
tab,n = {},1
for i = -1,1,2 do
for _,p in pairs({
{x=pos.x+i, y=pos.y, z=pos.z},
{x=pos.x, y=pos.y, z=pos.z+i}
}) do
tab[n] = {p, minetest_get_node(p)}
n = n+1
end
end
return tab
end
local data
local param2
local nd
local par2
local name
local tmp
local c_node
function mobs.get_flowing_dir(pos)
c_node = minetest_get_node(pos).name
if c_node ~= "mcl_core:water_flowing" and c_node ~= "mcl_core:water" then
return nil
end
data = get_nodes(pos)
param2 = minetest_get_node(pos).param2
if param2 > 7 then
return nil
end
if c_node == "mcl_core:water" then
for _,i in pairs(data) do
nd = i[2]
name = nd.name
par2 = nd.param2
if name == "mcl_core:water_flowing" and par2 == 7 then
return(vector_subtract(i[1],pos))
end
end
end
for _,i in pairs(data) do
nd = i[2]
name = nd.name
par2 = nd.param2
if name == "mcl_core:water_flowing" and par2 < param2 then
return(vector_subtract(i[1],pos))
end
end
for _,i in pairs(data) do
nd = i[2]
name = nd.name
par2 = nd.param2
if name == "mcl_core:water_flowing" and par2 >= 11 then
return(vector_subtract(i[1],pos))
end
end
for _,i in pairs(data) do
nd = i[2]
name = nd.name
par2 = nd.param2
tmp = minetest_registered_nodes[name]
if tmp and not tmp.walkable and name ~= "mcl_core:water_flowing" and name ~= "mcl_core:water" then
return(vector_subtract(i[1],pos))
end
end
return nil
end

View File

@ -0,0 +1,98 @@
local math = math
local vector = vector
--converts yaw to degrees
local degrees = function(yaw)
return yaw*180.0/math.pi
end
mobs.do_head_logic = function(self,dtime)
local player = minetest.get_player_by_name("singleplayer")
local look_at = player:get_pos()
look_at.y = look_at.y + player:get_properties().eye_height
local pos = self.object:get_pos()
local body_yaw = self.object:get_yaw()
local body_dir = minetest.yaw_to_dir(body_yaw)
pos.y = pos.y + self.head_height_offset
local head_offset = vector.multiply(body_dir, self.head_direction_offset)
pos = vector.add(pos, head_offset)
minetest.add_particle({
pos = pos,
velocity = {x=0, y=0, z=0},
acceleration = {x=0, y=0, z=0},
expirationtime = 0.2,
size = 1,
texture = "default_dirt.png",
})
local bone_pos = vector.new(0,0,0)
--(horizontal)
bone_pos.y = self.head_bone_pos_y
--(vertical)
bone_pos.z = self.head_bone_pos_z
--print(yaw)
--local _, bone_rot = self.object:get_bone_position("head")
--bone_rot.x = bone_rot.x + (dtime * 10)
--bone_rot.z = bone_rot.z + (dtime * 10)
local head_yaw = minetest.dir_to_yaw(vector.direction(pos,look_at)) - body_yaw
if self.reverse_head_yaw then
head_yaw = head_yaw * -1
end
--over rotation protection
--stops radians from going out of spec
if head_yaw > math.pi then
head_yaw = head_yaw - (math.pi * 2)
elseif head_yaw < -math.pi then
head_yaw = head_yaw + (math.pi * 2)
end
local check_failed = false
--upper check + 90 degrees or upper math.radians (3.14/2)
if head_yaw > math.pi - (math.pi/2) then
head_yaw = 0
check_failed = true
--lower check - 90 degrees or lower negative math.radians (-3.14/2)
elseif head_yaw < -math.pi + (math.pi/2) then
head_yaw = 0
check_failed = true
end
local head_pitch = 0
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
--head_yaw = 0
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
if not check_failed then
head_pitch = minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos.x,0,pos.z),vector.new(look_at.x,0,look_at.z)),0,pos.y-look_at.y))+(math.pi/2)
end
if self.head_pitch_modifier then
head_pitch = head_pitch + self.head_pitch_modifier
end
if self.swap_y_with_x then
self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),degrees(head_yaw),0))
else
self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),0,degrees(head_yaw)))
end
--set_bone_position([bone, position, rotation])
end

View File

@ -0,0 +1,276 @@
local minetest_after = minetest.after
local minetest_sound_play = minetest.sound_play
local minetest_dir_to_yaw = minetest.dir_to_yaw
local math = math
local vector = vector
local MAX_MOB_NAME_LENGTH = 30
local mod_hunger = minetest.get_modpath("mcl_hunger")
mobs.feed_tame = function(self)
return nil
end
-- Code to execute before custom on_rightclick handling
local function on_rightclick_prefix(self, clicker)
local item = clicker:get_wielded_item()
-- Name mob with nametag
if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then
local tag = item:get_meta():get_string("name")
if tag ~= "" then
if string.len(tag) > MAX_MOB_NAME_LENGTH then
tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
end
self.nametag = tag
mobs.update_tag(self)
if not mobs.is_creative(clicker:get_player_name()) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
end
return false
end
-- I have no idea what this does
mobs.create_mob_on_rightclick = function(on_rightclick)
return function(self, clicker)
--don't allow rightclicking dead mobs
if self.health <= 0 then
return
end
local stop = on_rightclick_prefix(self, clicker)
if (not stop) and (on_rightclick) then
on_rightclick(self, clicker)
end
end
end
-- deal damage and effects when mob punched
mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
--don't do anything if the mob is already dead
if self.health <= 0 then
return
end
--neutral passive mobs switch to neutral hostile
if self.neutral then
--drop in variables for attacking (stops crash)
self.attacking = hitter
self.punch_timer = 0
self.hostile = true
--hostile_cooldown timer is initialized here
self.hostile_cooldown_timer = self.hostile_cooldown
--initialize the group attack (check for other mobs in area, make them neutral hostile)
if self.group_attack then
mobs.group_attack_initialization(self)
end
end
--turn skittish mobs away and RUN
if self.skittish then
self.state = "run"
self.run_timer = 5 --arbitrary 5 seconds
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = hitter:get_pos()
pos2.y = 0
local dir = vector.direction(pos2,pos1)
local yaw = minetest_dir_to_yaw(dir)
self.yaw = yaw
end
-- custom punch function
if self.do_punch then
-- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return
end
end
--don't do damage until pause timer resets
if self.pause_timer > 0 then
return
end
-- error checking when mod profiling is enabled
if not tool_capabilities then
minetest.log("warning", "[mobs_mc] Mod profiling enabled, damage not enabled")
return
end
local is_player = hitter:is_player()
-- punch interval
local weapon = hitter:get_wielded_item()
--local punch_interval = 1.4
-- exhaust attacker
if mod_hunger and is_player then
mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK)
end
-- calculate mob damage
local damage = 0
local armor = self.object:get_armor_groups() or {}
--calculate damage groups
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
damage = damage + (tool_capabilities.damage_groups[group] or 0) * ((armor[group] or 0) / 100.0)
end
if weapon then
local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect")
if fire_aspect_level > 0 then
mcl_burning.set_on_fire(self.object, fire_aspect_level * 4)
end
end
-- check for tool immunity or special damage
for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0
break
end
end
-- healing
if damage <= -1 then
self.health = self.health - math.floor(damage)
return
end
--if tool_capabilities then
-- punch_interval = tool_capabilities.full_punch_interval or 1.4
--end
-- add weapon wear manually
-- Required because we have custom health handling ("health" property)
--minetest_is_creative_enabled("") ~= true --removed for now
if tool_capabilities then
if tool_capabilities.punch_attack_uses then
-- Without this delay, the wear does not work. Quite hacky ...
minetest_after(0, function(name)
local player = minetest.get_player_by_name(name)
if not player then return end
local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition()
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)
hitter:set_wielded_item(weapon)
end
end, hitter:get_player_name())
end
end
--if player is falling multiply damage by 1.5
--critical hit
if hitter:get_velocity().y < 0 then
damage = damage * 1.5
mobs.critical_effect(self)
end
-- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately.
if damage >= 0.1 then
minetest_sound_play("default_punch", {
object = self.object,
max_hear_distance = 16
}, true)
-- do damage
self.health = self.health - damage
--0.4 seconds until you can hurt the mob again
self.pause_timer = 0.4
--don't do knockback from a rider
for _,obj in pairs(self.object:get_children()) do
if obj == hitter then
return
end
end
-- knock back effect
local velocity = self.object:get_velocity()
--2d direction
local pos1 = self.object:get_pos()
pos1.y = 0
local pos2 = hitter:get_pos()
pos2.y = 0
local dir = vector.direction(pos2,pos1)
local up = 3
-- if already in air then dont go up anymore when hit
if velocity.y ~= 0 then
up = 0
end
--0.75 for perfect distance to not be too easy, and not be too hard
local multiplier = 0.75
-- check if tool already has specific knockback value
local knockback_enchant = mcl_enchanting.get_enchantment(hitter:get_wielded_item(), "knockback")
if knockback_enchant and knockback_enchant > 0 then
multiplier = knockback_enchant + 1 --(starts from 1, 1 would be no change)
end
--do this to sure you can punch a mob back when
--it's coming for you
if self.hostile then
multiplier = multiplier + 2
end
dir = vector.multiply(dir,multiplier)
dir.y = up
--add the velocity
self.object:add_velocity(dir)
end
end
--do internal per mob projectile calculations
mobs.shoot_projectile = function(self)
local pos1 = self.object:get_pos()
--add mob eye height
pos1.y = pos1.y + self.eye_height
local pos2 = self.attacking:get_pos()
--add player eye height
pos2.y = pos2.y + self.attacking:get_properties().eye_height
--get direction
local dir = vector.direction(pos1,pos2)
--call internal shoot_arrow function
self.shoot_arrow(self,pos1,dir)
end
mobs.update_tag = function(self)
self.object:set_properties({
nametag = self.nametag,
})
end

View File

@ -0,0 +1,150 @@
local minetest_add_particlespawner = minetest.add_particlespawner
mobs.death_effect = function(self)
local pos = self.object:get_pos()
--local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
end
minetest_add_particlespawner({
amount = 50,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-0.5,0.5,-0.5),
maxvel = vector.new(0.5,1,0.5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png", -- this particle looks strange
})
end
mobs.critical_effect = function(self)
local pos = self.object:get_pos()
--local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
end
minetest_add_particlespawner({
amount = 10,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png^[colorize:black:255",
})
end
--when feeding a mob
mobs.feed_effect = function(self)
local pos = self.object:get_pos()
--local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
end
minetest_add_particlespawner({
amount = 10,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png^[colorize:gray:255",
})
end
--hearts when tamed
mobs.tamed_effect = function(self)
local pos = self.object:get_pos()
--local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
end
minetest_add_particlespawner({
amount = 30,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png",
})
end
--hearts when breeding
mobs.breeding_effect = function(self)
local pos = self.object:get_pos()
--local yaw = self.object:get_yaw()
local collisionbox = self.object:get_properties().collisionbox
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
end
minetest_add_particlespawner({
amount = 2,
time = 0.0001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-1,1,-1),
maxvel = vector.new(1,3,1),
minexptime = 0.7,
maxexptime = 1,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "heart.png",
})
end

View File

@ -0,0 +1,390 @@
-- localize math functions
local math = math
local HALF_PI = math.pi / 2
local DOUBLE_PI = math.pi * 2
-- localize vector functions
local vector = vector
local minetest_yaw_to_dir = minetest.yaw_to_dir
local minetest_dir_to_yaw = minetest.dir_to_yaw
local DEFAULT_JUMP_HEIGHT = 5
local DEFAULT_FLOAT_SPEED = 4
local DEFAULT_CLIMB_SPEED = 3
mobs.stick_in_cobweb = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = vector.multiply(vector.normalize(current_velocity), 0.4)
goal_velocity.y = -0.5
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--this is a generic float function
mobs.float = function(self)
local acceleration = self.object:get_acceleration()
if not acceleration then
return
end
if acceleration.y ~= 0 then
self.object:set_acceleration({x=0, y=0, z=0})
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = DEFAULT_FLOAT_SPEED,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity, current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--this is a generic climb function
mobs.climb = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = DEFAULT_CLIMB_SPEED,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
_ _
| | | |
| | __ _ _ __ __| |
| | / _` | '_ \ / _` |
| |___| (_| | | | | (_| |
\_____/\__,_|_| |_|\__,_|
]]
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_velocity = function(self, v)
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = 0,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
new_velocity_addition.y = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
-- calculate mob velocity
mobs.get_velocity = function(self)
local v = self.object:get_velocity()
v.y = 0
if v then
return vector.length(v)
end
return 0
end
--make mobs jump
mobs.jump = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
self.object:add_velocity(vector.new(0,velocity,0))
end
--make mobs fall slowly
mobs.mob_fall_slow = function(self)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = 0,
y = -2,
z = 0,
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
new_velocity_addition.x = 0
new_velocity_addition.z = 0
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
new_velocity_addition.x = 0
new_velocity_addition.z = 0
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
_____ _
/ ___| (_)
\ `--.__ ___ _ __ ___
`--. \ \ /\ / / | '_ ` _ \
/\__/ /\ V V /| | | | | | |
\____/ \_/\_/ |_|_| |_| |_|
]]--
--make mobs flop
mobs.flop = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return false
end
mobs.set_velocity(self, 0)
--fallback velocity to allow modularity
velocity = velocity or DEFAULT_JUMP_HEIGHT
--create a random direction (2d yaw)
local dir = DOUBLE_PI * math.random()
--create a random force value
local force = math.random(0,3) + math.random()
--convert the yaw to a direction vector then multiply it times the force
local final_additional_force = vector.multiply(minetest_yaw_to_dir(dir), force)
--place in the "flop" velocity to make the mob flop
final_additional_force.y = velocity
self.object:add_velocity(final_additional_force)
return true
end
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_swim_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = pitch,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--[[
______ _
| ___| |
| |_ | |_ _
| _| | | | | |
| | | | |_| |
\_| |_|\__, |
__/ |
|___/
]]--
-- move mob in facing direction
--this has been modified to be internal
--internal = lua (self.yaw)
--engine = c++ (self.object:get_yaw())
mobs.set_fly_velocity = function(self, v)
local yaw = (self.yaw or 0)
local pitch = (self.pitch or 0)
if v == 0 then
pitch = 0
end
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -v),
y = pitch,
z = (math.cos(yaw) * v),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--a quick and simple pitch calculation between two vector positions
mobs.calculate_pitch = function(pos1, pos2)
if pos1 == nil or pos2 == nil then
return false
end
return minetest_dir_to_yaw(vector.new(vector.distance(vector.new(pos1.x,0,pos1.z),vector.new(pos2.x,0,pos2.z)),0,pos1.y - pos2.y)) + HALF_PI
end
--make mobs fly up or down based on their y difference
mobs.set_pitch_while_attacking = function(self)
local pos1 = self.object:get_pos()
local pos2 = self.attacking:get_pos()
local pitch = mobs.calculate_pitch(pos2,pos1)
self.pitch = pitch
end
--[[
___
|_ |
| |_ _ _ __ ___ _ __
| | | | | '_ ` _ \| '_ \
/\__/ / |_| | | | | | | |_) |
\____/ \__,_|_| |_| |_| .__/
| |
|_|
]]--
--special mob jump movement
mobs.jump_move = function(self, velocity)
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
return
end
--make the mob stick for a split second
mobs.set_velocity(self,0)
--fallback velocity to allow modularity
local jump_height = DEFAULT_JUMP_HEIGHT
local yaw = (self.yaw or 0)
local current_velocity = self.object:get_velocity()
local goal_velocity = {
x = (math.sin(yaw) * -velocity),
y = jump_height,
z = (math.cos(yaw) * velocity),
}
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
end
--smooths out mobs a bit
if vector.length(new_velocity_addition) >= 0.0001 then
self.object:add_velocity(new_velocity_addition)
end
end
--make it so mobs do not glitch out and freak out
--when moving around over nodes
mobs.swap_auto_step_height_adjust = function(self)
local y_vel = self.object:get_velocity().y
if y_vel == 0 and self.stepheight ~= self.stepheight_backup then
self.stepheight = self.stepheight_backup
elseif y_vel ~= 0 and self.stepheight ~= 0 then
self.stepheight = 0
end
end

View File

@ -0,0 +1,43 @@
local GRAVITY = minetest.settings:get("movement_gravity")-- + 9.81
mobs.shoot_projectile_handling = function(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable, gravity)
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
if power == nil then
power = 19
end
if damage == nil then
damage = 3
end
gravity = gravity or -GRAVITY
local knockback
if bow_stack then
local enchantments = mcl_enchanting.get_enchantments(bow_stack)
if enchantments.power then
damage = damage + (enchantments.power + 1) / 4
end
if enchantments.punch then
knockback = enchantments.punch * 3
end
if enchantments.flame then
mcl_burning.set_on_fire(obj, math.huge)
end
end
obj:set_velocity({x=dir.x*power, y=dir.y*power, z=dir.z*power})
obj:set_acceleration({x=0, y=gravity, z=0})
obj:set_yaw(yaw-math.pi/2)
local le = obj:get_luaentity()
le._shooter = shooter
le._damage = damage
le._is_critical = is_critical
le._startpos = pos
le._knockback = knockback
le._collectable = collectable
--play custom shoot sound
if shooter and shooter.shoot_sound then
minetest.sound_play(shooter.shoot_sound, {pos=pos, max_hear_distance=16}, true)
end
return obj
end

View File

@ -0,0 +1,224 @@
local math_random = math.random
local minetest_settings = minetest.settings
-- CMI support check
local use_cmi = minetest.global_exists("cmi")
-- get entity staticdata
mobs.mob_staticdata = function(self)
--despawn mechanism
--don't despawned tamed or bred mobs
if not self.tamed and not self.bred then
if not mobs.check_for_player_within_area(self, 64) then
--print("removing SERIALIZED!")
self.object:remove()
return
end
end
self.remove_ok = true
self.attack = nil
self.following = nil
if use_cmi then
self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
end
local tmp = {}
for _,stat in pairs(self) do
local t = type(stat)
if t ~= "function"
and t ~= "nil"
and t ~= "userdata"
and _ ~= "_cmi_components" then
tmp[_] = self[_]
end
end
return minetest.serialize(tmp)
end
-- activate mob and reload settings
mobs.mob_activate = function(self, staticdata, def, dtime)
-- remove monsters in peaceful mode
if self.type == "monster" and minetest_settings:get_bool("only_peaceful_mobs", false) then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
-- load entity variables
local tmp = minetest.deserialize(staticdata)
if tmp then
for _,stat in pairs(tmp) do
self[_] = stat
end
end
--set up wandering
if not self.wandering then
self.wandering = true
end
--clear animation
self.current_animation = nil
-- select random texture, set model and size
if not self.base_texture then
-- compatiblity with old simple mobs textures
if type(def.textures[1]) == "string" then
def.textures = {def.textures}
end
self.base_texture = def.textures[math_random(1, #def.textures)]
self.base_mesh = def.mesh
self.base_size = self.visual_size
self.base_colbox = self.collisionbox
self.base_selbox = self.selectionbox
end
-- for current mobs that dont have this set
if not self.base_selbox then
self.base_selbox = self.selectionbox or self.base_colbox
end
-- set texture, model and size
local textures = self.base_texture
local mesh = self.base_mesh
local vis_size = self.base_size
local colbox = self.base_colbox
local selbox = self.base_selbox
-- specific texture if gotten
if self.gotten == true
and def.gotten_texture then
textures = def.gotten_texture
end
-- specific mesh if gotten
if self.gotten == true
and def.gotten_mesh then
mesh = def.gotten_mesh
end
-- set baby mobs to half size
if self.baby == true then
vis_size = {
x = self.base_size.x * self.baby_size,
y = self.base_size.y * self.baby_size,
}
if def.child_texture then
textures = def.child_texture[1]
end
colbox = {
self.base_colbox[1] * self.baby_size,
self.base_colbox[2] * self.baby_size,
self.base_colbox[3] * self.baby_size,
self.base_colbox[4] * self.baby_size,
self.base_colbox[5] * self.baby_size,
self.base_colbox[6] * self.baby_size
}
selbox = {
self.base_selbox[1] * self.baby_size,
self.base_selbox[2] * self.baby_size,
self.base_selbox[3] * self.baby_size,
self.base_selbox[4] * self.baby_size,
self.base_selbox[5] * self.baby_size,
self.base_selbox[6] * self.baby_size
}
end
--stop mobs from reviving
if not self.dead and not self.health then
self.health = math_random (self.hp_min, self.hp_max)
end
if not self.random_sound_timer then
self.random_sound_timer = math_random(self.random_sound_timer_min,self.random_sound_timer_max)
end
if self.breath == nil then
self.breath = self.breath_max
end
-- pathfinding init
self.path = {}
self.path.way = {} -- path to follow, table of positions
self.path.lastpos = {x = 0, y = 0, z = 0}
self.path.stuck = false
self.path.following = false -- currently following path?
self.path.stuck_timer = 0 -- if stuck for too long search for path
-- Armor groups
-- immortal=1 because we use custom health
-- handling (using "health" property)
local armor
if type(self.armor) == "table" then
armor = table.copy(self.armor)
armor.immortal = 1
else
armor = {immortal=1, fleshy = self.armor}
end
self.object:set_armor_groups(armor)
self.old_y = self.object:get_pos().y
self.old_health = self.health
self.sounds.distance = self.sounds.distance or 10
self.textures = textures
self.mesh = mesh
self.collisionbox = colbox
self.selectionbox = selbox
self.visual_size = vis_size
self.standing_in = "ignore"
self.standing_on = "ignore"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
self.texture_mods = {}
self.v_start = false
self.timer = 0
self.blinktimer = 0
self.blinkstatus = false
--continue mob effect on server restart
if self.dead or self.health <= 0 then
self.object:set_texture_mod("^[colorize:red:120")
else
self.object:set_texture_mod("")
end
-- set anything changed above
self.object:set_properties(self)
--update_tag(self)
--mobs.set_animation(self, "stand")
-- run on_spawn function if found
if self.on_spawn and not self.on_spawn_run then
if self.on_spawn(self) then
self.on_spawn_run = true -- if true, set flag to run once only
end
end
-- run after_activate
if def.after_activate then
def.after_activate(self, staticdata, def, dtime)
end
if use_cmi then
self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
cmi.notify_activate(self.object, dtime)
end
end

View File

@ -0,0 +1,59 @@
local math_random = math.random
--generic call for sound handler for mobs (data access)
mobs.play_sound = function(self,sound)
local soundinfo = self.sounds
if not soundinfo then
return
end
local play_sound = soundinfo[sound]
if not play_sound then
return
end
mobs.play_sound_handler(self, play_sound)
end
--generic sound handler for mobs
mobs.play_sound_handler = function(self, sound)
local pitch = (100 + math_random(-15,15) + math_random()) / 100
local distance = self.sounds.distance or 16
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = distance,
pitch = pitch,
}, true)
end
--random sound timing handler
mobs.random_sound_handling = function(self,dtime)
self.random_sound_timer = self.random_sound_timer - dtime
--play sound and reset timer
if self.random_sound_timer <= 0 then
mobs.play_sound(self,"random")
self.random_sound_timer = math_random(self.random_sound_timer_min,self.random_sound_timer_max)
end
end
--used for playing a non-mob internal sound at random pitches
mobs.play_sound_specific = function(self,soundname)
local pitch = (100 + math_random(-15,15) + math_random()) / 100
local distance = self.sounds.distance or 16
minetest.sound_play(soundname, {
object = self.object,
gain = 1.0,
max_hear_distance = distance,
pitch = pitch,
}, true)
end

View File

@ -1,9 +1,11 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
-- lib_mount by Blert2112 (edited by TenPlus1)
local enable_crash = false
local crash_threshold = 6.5 -- ignored if enable_crash=false
--local enable_crash = false
--local crash_threshold = 6.5 -- ignored if enable_crash=false
local math = math
local vector = vector
------------------------------------------------------------------------------
@ -11,9 +13,9 @@ local crash_threshold = 6.5 -- ignored if enable_crash=false
-- Helper functions
--
local node_ok = function(pos, fallback)
--[[local function node_ok(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
fallback = fallback or mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
@ -22,10 +24,10 @@ local node_ok = function(pos, fallback)
end
return {name = fallback}
end
end]]
local function node_is(pos)
--[[local function node_is(pos)
local node = node_ok(pos)
@ -46,7 +48,7 @@ local function node_is(pos)
end
return "other"
end
end]]
local function get_sign(i)
@ -61,13 +63,11 @@ local function get_sign(i)
end
local function get_velocity(v, yaw, y)
--[[local function get_velocity(v, yaw, y)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end
end]]
local function get_v(v)
@ -120,7 +120,7 @@ end)
-------------------------------------------------------------------------------
function mcl_mobs.attach(entity, player)
function mobs.attach(entity, player)
local attach_at, eye_offset
@ -163,7 +163,7 @@ function mcl_mobs.attach(entity, player)
end
function mcl_mobs.detach(player, offset)
function mobs.detach(player, offset)
force_detach(player)
@ -173,7 +173,7 @@ function mcl_mobs.detach(player, offset)
--pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
player:add_velocity(vector.new(math.random(-6,6),math.random(5,8),math.random(-6,6))) --throw the rider off
player:add_velocity(vector.new(math.random(-6,6), math.random(5,8), math.random(-6,6))) --throw the rider off
--[[
minetest.after(0.1, function(name, pos)
@ -186,15 +186,15 @@ function mcl_mobs.detach(player, offset)
end
function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
local rot_view = 0
--local rot_view = 0
if entity.player_rotation.y == 90 then
rot_view = math.pi/2
end
--if entity.player_rotation.y == 90 then
-- rot_view = math.pi/2
--end
local acce_y = 0
--local acce_y = 0
local velo = entity.object:get_velocity()
entity.v = get_v(velo) * get_sign(entity.v)
@ -207,21 +207,30 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
-- move forwards
if ctrl.up then
entity.v = entity.v + entity.accel / 10 * entity.run_velocity / 2.6
mobs.set_velocity(entity, entity.run_velocity)
mobs.set_mob_animation(entity, moving_anim)
-- move backwards
elseif ctrl.down then
if entity.max_speed_reverse == 0 and entity.v == 0 then
return
end
mobs.set_velocity(entity, -entity.run_velocity)
entity.v = entity.v - entity.accel / 10
mobs.set_mob_animation(entity, moving_anim)
--halt
else
mobs.set_velocity(entity, 0)
mobs.set_mob_animation(entity, stand_anim)
end
-- fix mob rotation
-- mob rotation
entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
entity.yaw = entity.driver:get_look_horizontal() - entity.rotate
--[[
if can_fly then
-- fly up
@ -245,32 +254,21 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end
else
]]--
-- jump
if ctrl.jump then
-- jump
if ctrl.jump then
if velo.y == 0 then
velo.y = velo.y + entity.jump_height
acce_y = acce_y + (acce_y * 3) + 1
end
end
end
end
-- if not moving then set animation and return
if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
if stand_anim then
mcl_mobs:set_animation(entity, stand_anim)
mobs.jump(entity)
end
return
--end
end
--[[
-- set moving animation
if moving_anim then
mcl_mobs:set_animation(entity, moving_anim)
mobs:set_mob_animation(entity, moving_anim)
end
-- Stop!
@ -384,12 +382,17 @@ function mcl_mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
end
entity.v2 = v
]]--
end
-- directional flying routine by D00Med (edited by TenPlus1)
function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
if true then
print("succ")
return
end
local ctrl = entity.driver:get_player_control()
local velo = entity.object:get_velocity()
local dir = entity.driver:get_look_dir()
@ -440,24 +443,9 @@ function mcl_mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_an
-- change animation if stopped
if velo.x == 0 and velo.y == 0 and velo.z == 0 then
mcl_mobs:set_animation(entity, stand_anim)
mobs:set_mob_animation(entity, stand_anim)
else
-- moving animation
mcl_mobs:set_animation(entity, moving_anim)
end
end
mcl_mobs.mob_class.drive = mcl_mobs.drive
mcl_mobs.mob_class.fly = mcl_mobs.fly
mcl_mobs.mob_class.attach = mcl_mobs.attach
function mob_class:on_detach_child(child)
if self.detach_child then
if self.detach_child(self, child) then
return
end
end
if self.driver == child then
self.driver = nil
mobs:set_mob_animation(entity, moving_anim)
end
end

View File

@ -0,0 +1,709 @@
--lua locals
local get_node = minetest.get_node
local get_item_group = minetest.get_item_group
local get_node_light = minetest.get_node_light
local find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
local get_biome_name = minetest.get_biome_name
local get_objects_inside_radius = minetest.get_objects_inside_radius
local get_connected_players = minetest.get_connected_players
local math_random = math.random
local math_floor = math.floor
--local max = math.max
--local vector_distance = vector.distance
local vector_new = vector.new
local vector_floor = vector.floor
local table_copy = table.copy
local table_remove = table.remove
local pairs = pairs
-- range for mob count
local aoc_range = 48
--do mobs spawn?
local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false
--[[
THIS IS THE BIG LIST OF ALL BIOMES - used for programming/updating mobs
underground:
"FlowerForest_underground",
"JungleEdge_underground",local spawning_position = spawning_position_list[math.random(1,#spawning_position_list)]
"ColdTaiga_underground",
"IcePlains_underground",
"IcePlainsSpikes_underground",
"MegaTaiga_underground",
"Taiga_underground",
"ExtremeHills+_underground",
"JungleM_underground",
"ExtremeHillsM_underground",
"JungleEdgeM_underground",
ocean:
"RoofedForest_ocean",
"JungleEdgeM_ocean",
"BirchForestM_ocean",
"BirchForest_ocean",
"IcePlains_deep_ocean",
"Jungle_deep_ocean",
"Savanna_ocean",
"MesaPlateauF_ocean",
"ExtremeHillsM_deep_ocean",
"Savanna_deep_ocean",
"SunflowerPlains_ocean",
"Swampland_deep_ocean",
"Swampland_ocean",
"MegaSpruceTaiga_deep_ocean",
"ExtremeHillsM_ocean",
"JungleEdgeM_deep_ocean",
"SunflowerPlains_deep_ocean",
"BirchForest_deep_ocean",
"IcePlainsSpikes_ocean",
"Mesa_ocean",
"StoneBeach_ocean",
"Plains_deep_ocean",
"JungleEdge_deep_ocean",
"SavannaM_deep_ocean",
"Desert_deep_ocean",
"Mesa_deep_ocean",
"ColdTaiga_deep_ocean",
"Plains_ocean",
"MesaPlateauFM_ocean",
"Forest_deep_ocean",
"JungleM_deep_ocean",
"FlowerForest_deep_ocean",
"MushroomIsland_ocean",
"MegaTaiga_ocean",
"StoneBeach_deep_ocean",
"IcePlainsSpikes_deep_ocean",
"ColdTaiga_ocean",
"SavannaM_ocean",
"MesaPlateauF_deep_ocean",
"MesaBryce_deep_ocean",
"ExtremeHills+_deep_ocean",
"ExtremeHills_ocean",
"MushroomIsland_deep_ocean",
"Forest_ocean",
"MegaTaiga_deep_ocean",
"JungleEdge_ocean",
"MesaBryce_ocean",
"MegaSpruceTaiga_ocean",
"ExtremeHills+_ocean",
"Jungle_ocean",
"RoofedForest_deep_ocean",
"IcePlains_ocean",
"FlowerForest_ocean",
"ExtremeHills_deep_ocean",
"MesaPlateauFM_deep_ocean",
"Desert_ocean",
"Taiga_ocean",
"BirchForestM_deep_ocean",
"Taiga_deep_ocean",
"JungleM_ocean",
water or beach?
"MesaPlateauFM_sandlevel",
"MesaPlateauF_sandlevel",
"MesaBryce_sandlevel",
"Mesa_sandlevel",
beach:
"FlowerForest_beach",
"Forest_beach",
"StoneBeach",
"ColdTaiga_beach_water",
"Taiga_beach",
"Savanna_beach",
"Plains_beach",
"ExtremeHills_beach",
"ColdTaiga_beach",
"Swampland_shore",
"MushroomIslandShore",
"JungleM_shore",
"Jungle_shore",
dimension biome:
"Nether",
"End",
Overworld regular:
"Mesa",
"FlowerForest",
"Swampland",
"Taiga",
"ExtremeHills",
"Jungle",
"Savanna",
"BirchForest",
"MegaSpruceTaiga",
"MegaTaiga",
"ExtremeHills+",
"Forest",
"Plains",
"Desert",
"ColdTaiga",
"MushroomIsland",
"IcePlainsSpikes",
"SunflowerPlains",
"IcePlains",
"RoofedForest",
"ExtremeHills+_snowtop",
"MesaPlateauFM_grasstop",
"JungleEdgeM",
"ExtremeHillsM",
"JungleM",
"BirchForestM",
"MesaPlateauF",
"MesaPlateauFM",
"MesaPlateauF_grasstop",
"MesaBryce",
"JungleEdge",
"SavannaM",
]]--
-- count how many mobs are in an area
local function count_mobs(pos)
local num = 0
for _,object in pairs(get_objects_inside_radius(pos, aoc_range)) do
if object and object:get_luaentity() and object:get_luaentity()._cmi_is_mob then
num = num + 1
end
end
return num
end
-- global functions
function mobs:spawn_abm_check(pos, node, name)
-- global function to add additional spawn checks
-- return true to stop spawning mob
end
--[[
Custom elements changed:
name:
the mobs name
dimension:
"overworld"
"nether"
"end"
types of spawning:
"water"
"ground"
"lava"
biomes: tells the spawner to allow certain mobs to spawn in certain biomes
{"this", "that", "grasslands", "whatever"}
what is aoc??? objects in area
WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua??
]]--
--this is where all of the spawning information is kept
local spawn_dictionary = {}
function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
--print(dump(biomes))
-- Do mobs spawn at all?
if not mobs_spawn then
return
end
-- chance/spawn number override in minetest.conf for registered mob
local numbers = minetest.settings:get(name)
if numbers then
numbers = numbers:split(",")
chance = tonumber(numbers[1]) or chance
aoc = tonumber(numbers[2]) or aoc
if chance == 0 then
minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
return
end
minetest.log("action",
string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
end
--[[
local function spawn_action(pos, node, active_object_count, active_object_count_wider, name)
local orig_pos = table.copy(pos)
-- is mob actually registered?
if not mobs.spawning_mobs[name]
or not minetest.registered_entities[name] then
minetest.log("warning", "Mob spawn of "..name.." failed, unknown entity or mob is not registered for spawning!")
return
end
-- additional custom checks for spawning mob
if mobs:spawn_abm_check(pos, node, name) == true then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, ABM check rejected!")
return
end
-- count nearby mobs in same spawn class
local entdef = minetest.registered_entities[name]
local spawn_class = entdef and entdef.spawn_class
if not spawn_class then
if entdef.type == "monster" then
spawn_class = "hostile"
else
spawn_class = "passive"
end
end
local in_class_cap = count_mobs(pos, "!"..spawn_class) < MOB_CAP[spawn_class]
-- do not spawn if too many of same mob in area
if active_object_count_wider >= max_per_block -- large-range mob cap
or (not in_class_cap) -- spawn class mob cap
or count_mobs(pos, name) >= aoc then -- per-mob mob cap
-- too many entities
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too crowded!")
return
end
-- if toggle set to nil then ignore day/night check
if day_toggle then
local tod = (minetest.get_timeofday() or 0) * 24000
if tod > 4500 and tod < 19500 then
-- daylight, but mob wants night
if day_toggle == false then
-- mob needs night
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs light!")
return
end
else
-- night time but mob wants day
if day_toggle == true then
-- mob needs day
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, mob needs daylight!")
return
end
end
end
-- spawn above node
pos.y = pos.y + 1
-- only spawn away from player
local objs = minetest.get_objects_inside_radius(pos, 24)
for n = 1, #objs do
if objs[n]:is_player() then
-- player too close
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, player too close!")
return
end
end
-- mobs cannot spawn in protected areas when enabled
if not spawn_protected
and minetest.is_protected(pos, "") then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, position is protected!")
return
end
-- are we spawning within height limits?
if pos.y > max_height
or pos.y < min_height then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, out of height limit!")
return
end
-- are light levels ok?
local light = minetest.get_node_light(pos)
if not light
or light > max_light
or light < min_light then
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, bad light!")
return
end
-- do we have enough space to spawn mob?
local ent = minetest.registered_entities[name]
local width_x = max(1, math.ceil(ent.collisionbox[4] - ent.collisionbox[1]))
local min_x, max_x
if width_x % 2 == 0 then
max_x = math.floor(width_x/2)
min_x = -(max_x-1)
else
max_x = math.floor(width_x/2)
min_x = -max_x
end
local width_z = max(1, math.ceil(ent.collisionbox[6] - ent.collisionbox[3]))
local min_z, max_z
if width_z % 2 == 0 then
max_z = math.floor(width_z/2)
min_z = -(max_z-1)
else
max_z = math.floor(width_z/2)
min_z = -max_z
end
local max_y = max(0, math.ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
for y = 0, max_y do
for x = min_x, max_x do
for z = min_z, max_z do
local pos2 = {x = pos.x+x, y = pos.y+y, z = pos.z+z}
if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
-- inside block
minetest.log("info", "Mob spawn of "..name.." at "..minetest.pos_to_string(pos).." failed, too little space!")
if ent.spawn_small_alternative and (not minetest.registered_nodes[node_ok(pos).name].walkable) then
minetest.log("info", "Trying to spawn smaller alternative mob: "..ent.spawn_small_alternative)
spawn_action(orig_pos, node, active_object_count, active_object_count_wider, ent.spawn_small_alternative)
end
return
end
end
end
end
-- tweak X/Y/Z spawn pos
if width_x % 2 == 0 then
pos.x = pos.x + 0.5
end
if width_z % 2 == 0 then
pos.z = pos.z + 0.5
end
pos.y = pos.y - 0.5
local mob = minetest.add_entity(pos, name)
minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos))
if on_spawn then
local ent = mob:get_luaentity()
on_spawn(ent, pos)
end
end
local function spawn_abm_action(pos, node, active_object_count, active_object_count_wider)
spawn_action(pos, node, active_object_count, active_object_count_wider, name)
end
]]--
local entdef = minetest.registered_entities[name]
local spawn_class
if entdef.type == "monster" then
spawn_class = "hostile"
else
spawn_class = "passive"
end
--load information into the spawn dictionary
local key = #spawn_dictionary + 1
spawn_dictionary[key] = {}
spawn_dictionary[key]["name"] = name
spawn_dictionary[key]["dimension"] = dimension
spawn_dictionary[key]["type_of_spawning"] = type_of_spawning
spawn_dictionary[key]["biomes"] = biomes
spawn_dictionary[key]["min_light"] = min_light
spawn_dictionary[key]["max_light"] = max_light
spawn_dictionary[key]["interval"] = interval
spawn_dictionary[key]["chance"] = chance
spawn_dictionary[key]["aoc"] = aoc
spawn_dictionary[key]["min_height"] = min_height
spawn_dictionary[key]["max_height"] = max_height
spawn_dictionary[key]["day_toggle"] = day_toggle
--spawn_dictionary[key]["on_spawn"] = spawn_abm_action
spawn_dictionary[key]["spawn_class"] = spawn_class
--[[
minetest.register_abm({
label = name .. " spawning",
nodenames = nodes,
neighbors = neighbors,
interval = interval,
chance = floor(max(1, chance * mobs_spawn_chance)),
catch_up = false,
action = spawn_abm_action,
})
]]--
end
-- compatibility with older mob registration
-- we're going to forget about this for now -j4i
--[[
function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
chance, active_object_count, -31000, max_height, day_toggle)
end
]]--
--Don't disable this yet-j4i
-- MarkBu's spawn function
function mobs:spawn(def)
--does nothing for now
--[[
local name = def.name
local nodes = def.nodes or {"group:soil", "group:stone"}
local neighbors = def.neighbors or {"air"}
local min_light = def.min_light or 0
local max_light = def.max_light or 15
local interval = def.interval or 30
local chance = def.chance or 5000
local active_object_count = def.active_object_count or 1
local min_height = def.min_height or -31000
local max_height = def.max_height or 31000
local day_toggle = def.day_toggle
local on_spawn = def.on_spawn
mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
]]--
end
local axis
--inner and outer part of square donut radius
local inner = 15
local outer = 64
local int = {-1,1}
local function position_calculation(pos)
pos = vector_floor(pos)
--this is used to determine the axis buffer from the player
axis = math_random(0,1)
--cast towards the direction
if axis == 0 then --x
pos.x = pos.x + math_random(inner,outer)*int[math_random(1,2)]
pos.z = pos.z + math_random(-outer,outer)
else --z
pos.z = pos.z + math_random(inner,outer)*int[math_random(1,2)]
pos.x = pos.x + math_random(-outer,outer)
end
return pos
end
--[[
local decypher_limits_dictionary = {
["overworld"] = {mcl_vars.mg_overworld_min,mcl_vars.mg_overworld_max},
["nether"] = {mcl_vars.mg_nether_min, mcl_vars.mg_nether_max},
["end"] = {mcl_vars.mg_end_min, mcl_vars.mg_end_max}
}
]]--
local function decypher_limits(posy)
--local min_max_table = decypher_limits_dictionary[dimension]
--return min_max_table[1],min_max_table[2]
posy = math_floor(posy)
return posy - 32, posy + 32
end
--a simple helper function for mob_spawn
local function biome_check(biome_list, biome_goal)
for _,data in ipairs(biome_list) do
if data == biome_goal then
return true
end
end
return false
end
--todo mob limiting
--MAIN LOOP
if mobs_spawn then
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer >= 10 then
timer = 0
for _,player in pairs(get_connected_players()) do
-- after this line each "break" means "continue"
local do_mob_spawning = true
repeat
--don't need to get these variables more than once
--they happen in a single server step
local player_pos = player:get_pos()
local dimension = mcl_worlds.pos_to_dimension(player_pos)
if dimension == "void" or dimension == "default" then
break -- ignore void and unloaded area
end
local min, max = decypher_limits(player_pos.y)
for i = 1, math_random(1,4) do
-- after this line each "break" means "continue"
local do_mob_algorithm = true
repeat
local goal_pos = position_calculation(player_pos)
local spawning_position_list = find_nodes_in_area_under_air(vector_new(goal_pos.x,min,goal_pos.z), vector_new(goal_pos.x,max,goal_pos.z), {"group:solid", "group:water", "group:lava"})
--couldn't find node
if #spawning_position_list <= 0 then
break
end
local spawning_position = spawning_position_list[math_random(1,#spawning_position_list)]
--Prevent strange behavior --- this is commented out: /too close to player --fixed with inner circle
if not spawning_position then -- or vector_distance(player_pos, spawning_position) < 15
break
end
--hard code mob limit in area to 5 for now
if count_mobs(spawning_position) >= 5 then
break
end
local gotten_node = get_node(spawning_position).name
if not gotten_node or gotten_node == "air" then --skip air nodes
break
end
local gotten_biome = minetest.get_biome_data(spawning_position)
if not gotten_biome then
break --skip if in unloaded area
end
gotten_biome = get_biome_name(gotten_biome.biome) --makes it easier to work with
--add this so mobs don't spawn inside nodes
spawning_position.y = spawning_position.y + 1
--only need to poll for node light if everything else worked
local gotten_light = get_node_light(spawning_position)
local is_water = get_item_group(gotten_node, "water") ~= 0
local is_lava = get_item_group(gotten_node, "lava") ~= 0
local mob_def = nil
--create a disconnected clone of the spawn dictionary
--prevents memory leak
local mob_library_worker_table = table_copy(spawn_dictionary)
--grab mob that fits into the spawning location
--randomly grab a mob, don't exclude any possibilities
local repeat_mob_search = true
repeat
--do not infinite loop
if #mob_library_worker_table <= 0 then
--print("breaking infinite loop")
break
end
local skip = false
--use this for removing table elements of mobs that do not match
local temp_index = math_random(1,#mob_library_worker_table)
local temp_def = mob_library_worker_table[temp_index]
--skip if something ridiculous happens (nil mob def)
--something truly horrible has happened if skip gets
--activated at this point
if not temp_def then
skip = true
end
if not skip and (spawning_position.y < temp_def.min_height or spawning_position.y > temp_def.max_height) then
skip = true
end
--skip if not correct dimension
if not skip and (temp_def.dimension ~= dimension) then
skip = true
end
--skip if not in correct biome
if not skip and (not biome_check(temp_def.biomes, gotten_biome)) then
skip = true
end
--don't spawn if not in light limits
if not skip and (gotten_light < temp_def.min_light or gotten_light > temp_def.max_light) then
skip = true
end
--skip if not in correct spawning type
if not skip and (temp_def.type_of_spawning == "ground" and is_water) then
skip = true
end
if not skip and (temp_def.type_of_spawning == "ground" and is_lava) then
skip = true
end
--found a mob, exit out of loop
if not skip then
--minetest.log("warning", "found mob:"..temp_def.name)
--print("found mob:"..temp_def.name)
mob_def = table_copy(temp_def)
break
else
--minetest.log("warning", "deleting temp index "..temp_index)
--print("deleting temp index")
table_remove(mob_library_worker_table, temp_index)
end
until repeat_mob_search == false --this is needed to sort through mobs randomly
--catch if went through all mobs and something went horribly wrong
--could not find a valid mob to spawn that fits the environment
if not mob_def then
break
end
--adjust the position for water and lava mobs
if mob_def.type_of_spawning == "water" or mob_def.type_of_spawning == "lava" then
spawning_position.y = spawning_position.y - 1
end
--print("spawning: " .. mob_def.name)
--everything is correct, spawn mob
minetest.add_entity(spawning_position, mob_def.name)
break
until do_mob_algorithm == false --this is a safety catch
end
break
until do_mob_spawning == false --this is a performance catch
end
end
end)
end

View File

@ -1,352 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local MAX_MOB_NAME_LENGTH = 30
local HORNY_TIME = 30
local HORNY_AGAIN_TIME = 300
local CHILD_GROW_TIME = 60*20
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
local LOG_MODULE = "[mcl_mobs]"
local function mcl_log (message)
if LOGGING_ON and message then
minetest.log(LOG_MODULE .. " " .. message)
end
end
-- No-op in MCL2 (capturing mobs is not possible).
-- Provided for compability with Mobs Redo
function mcl_mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
return false
end
-- No-op in MCL2 (protecting mobs is not possible).
function mcl_mobs:protect(self, clicker)
return false
end
-- feeding, taming and breeding (thanks blert2112)
function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
if not self.follow then
return false
end
-- can eat/tame with item in hand
if self.nofollow or self:follow_holding(clicker) then
local consume_food = false
-- tame if not still a baby
if tame and not self.child then
if not self.owner or self.owner == "" then
self.tamed = true
self.owner = clicker:get_player_name()
consume_food = true
end
end
-- increase health
if self.health < self.hp_max and not consume_food then
consume_food = true
self.health = math.min(self.health + 4, self.hp_max)
if self.htimer < 1 then
self.htimer = 5
end
self.object:set_hp(self.health)
end
-- make children grow quicker
if not consume_food and self.child == true then
consume_food = true
-- deduct 10% of the time to adulthood
self.hornytimer = self.hornytimer + ((CHILD_GROW_TIME - self.hornytimer) * 0.1)
end
-- breed animals
if breed and not consume_food and self.hornytimer == 0 and not self.horny then
self.food = (self.food or 0) + 1
consume_food = true
if self.food >= feed_count then
self.food = 0
self.horny = true
end
end
self:update_tag()
-- play a sound if the animal used the item and take the item if not in creative
if consume_food then
-- don't consume food if clicker is in creative
if not minetest.is_creative_enabled(clicker:get_player_name()) and not notake then
local item = clicker:get_wielded_item()
item:take_item()
clicker:set_wielded_item(item)
end
-- always play the eat sound if food is used, even in creative
self:mob_sound("eat", nil, true)
else
-- make sound when the mob doesn't want food
self:mob_sound("random", true)
end
return true
end
return false
end
-- Spawn a child
function mcl_mobs.spawn_child(pos, mob_type)
local child = minetest.add_entity(pos, mob_type)
if not child then
return
end
local ent = child:get_luaentity()
mcl_mobs.effect(pos, 15, "mcl_particles_smoke.png", 1, 2, 2, 15, 5)
ent.child = true
local textures
-- using specific child texture (if found)
if ent.child_texture then
textures = ent.child_texture[1]
end
-- and resize to half height
child:set_properties({
textures = textures,
visual_size = {
x = ent.base_size.x * .5,
y = ent.base_size.y * .5,
},
collisionbox = {
ent.base_colbox[1] * .5,
ent.base_colbox[2] * .5,
ent.base_colbox[3] * .5,
ent.base_colbox[4] * .5,
ent.base_colbox[5] * .5,
ent.base_colbox[6] * .5,
},
selectionbox = {
ent.base_selbox[1] * .5,
ent.base_selbox[2] * .5,
ent.base_selbox[3] * .5,
ent.base_selbox[4] * .5,
ent.base_selbox[5] * .5,
ent.base_selbox[6] * .5,
},
})
ent.animation = ent._child_animations
ent._current_animation = nil
ent:set_animation("stand")
return child
end
-- find two animals of same type and breed if nearby and horny
function mob_class:check_breeding()
--mcl_log("In breed function")
-- child takes a long time before growing into adult
if self.child == true then
-- When a child, hornytimer is used to count age until adulthood
self.hornytimer = self.hornytimer + 1
if self.hornytimer >= CHILD_GROW_TIME then
self.child = false
self.hornytimer = 0
self.object:set_properties({
textures = self.base_texture,
mesh = self.base_mesh,
visual_size = self.base_size,
collisionbox = self.base_colbox,
selectionbox = self.base_selbox,
})
-- custom function when child grows up
if self.on_grown then
self.on_grown(self)
else
-- jump when fully grown so as not to fall into ground
self.object:set_velocity({
x = 0,
y = self.jump_height*3,
z = 0
})
end
self.animation = nil
local anim = self._current_animation
self._current_animation = nil -- Mobs Redo does nothing otherwise
self:set_animation(anim)
end
return
end
-- horny animal can mate for HORNY_TIME seconds,
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
if self.horny == true
and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
self.hornytimer = self.hornytimer + 1
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
self.hornytimer = 0
self.horny = false
end
end
-- find another same animal who is also horny and mate if nearby
if self.horny == true
and self.hornytimer <= HORNY_TIME then
mcl_log("In breed function. All good. Do the magic.")
local pos = self.object:get_pos()
mcl_mobs.effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
local objs = minetest.get_objects_inside_radius(pos, 3)
local num = 0
local ent = nil
for n = 1, #objs do
ent = objs[n]:get_luaentity()
-- check for same animal with different colour
local canmate = false
if ent then
if ent.name == self.name then
canmate = true
else
local entname = string.split(ent.name,":")
local selfname = string.split(self.name,":")
if entname[1] == selfname[1] then
entname = string.split(entname[2],"_")
selfname = string.split(selfname[2],"_")
if entname[1] == selfname[1] then
canmate = true
end
end
end
end
if canmate then mcl_log("In breed function. Can mate.") end
if ent
and canmate == true
and ent.horny == true
and ent.hornytimer <= HORNY_TIME then
num = num + 1
end
-- found your mate? then have a baby
if num > 1 then
self.hornytimer = HORNY_TIME + 1
ent.hornytimer = HORNY_TIME + 1
-- spawn baby
minetest.after(5, function(parent1, parent2, pos)
if not parent1.object:get_luaentity() then
return
end
if not parent2.object:get_luaentity() then
return
end
mcl_experience.throw_xp(pos, math.random(1, 7))
-- custom breed function
if parent1.on_breed then
-- when false, skip going any further
if parent1.on_breed(parent1, parent2) == false then
return
end
end
local child = mcl_mobs.spawn_child(pos, parent1.name)
local ent_c = child:get_luaentity()
-- Use texture of one of the parents
local p = math.random(1, 2)
if p == 1 then
ent_c.base_texture = parent1.base_texture
else
ent_c.base_texture = parent2.base_texture
end
child:set_properties({
textures = ent_c.base_texture
})
-- tamed and owned by parents' owner
ent_c.tamed = true
ent_c.owner = parent1.owner
end, self, ent, pos)
num = 0
break
end
end
end
end
function mob_class:toggle_sit(clicker,p)
if not self.tamed or self.child or self.owner ~= clicker:get_player_name() then
return
end
local pos = self.object:get_pos()
local particle
if not self.order or self.order == "" or self.order == "sit" then
particle = "mobs_mc_wolf_icon_roam.png"
self.order = "roam"
self.state = "stand"
self.walk_chance = default_walk_chance
self.jump = true
self:set_animation("stand")
-- TODO: Add sitting model
else
particle = "mobs_mc_wolf_icon_sit.png"
self.order = "sit"
self.state = "stand"
self.walk_chance = 0
self.jump = false
if self.animation.sit_start then
self:set_animation("sit")
else
self:set_animation("stand")
end
end
local pp = vector.new(0,1.4,0)
if p then pp = vector.offset(pp,0,p,0) end
-- Display icon to show current order (sit or roam)
minetest.add_particle({
pos = vector.add(pos, pp),
velocity = {x=0,y=0.2,z=0},
expirationtime = 1,
size = 4,
texture = particle,
playername = self.owner,
glow = minetest.LIGHT_MAX,
})
end

View File

@ -1,796 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local damage_enabled = minetest.settings:get_bool("enable_damage")
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
-- pathfinding settings
local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
local stuck_path_timeout = 10 -- how long will mob follow path before giving up
-- check if daytime and also if mob is docile during daylight hours
function mob_class:day_docile()
if self.docile_by_day == false then
return false
elseif self.docile_by_day == true
and self.time_of_day > 0.2
and self.time_of_day < 0.8 then
return true
end
end
-- attack player/mob
function mob_class:do_attack(player)
if self.state == "attack" or self.state == "die" then
return
end
self.attack = player
self.state = "attack"
-- TODO: Implement war_cry sound without being annoying
--if random(0, 100) < 90 then
--self:mob_sound("war_cry", true)
--end
end
-- blast damage to entities nearby
local function entity_physics(pos,radius)
radius = radius * 2
local objs = minetest.get_objects_inside_radius(pos, radius)
local obj_pos, dist
for n = 1, #objs do
obj_pos = objs[n]:get_pos()
dist = vector.distance(pos, obj_pos)
if dist < 1 then dist = 1 end
local damage = math.floor((4 / dist) * radius)
local ent = objs[n]:get_luaentity()
-- punches work on entities AND players
objs[n]:punch(objs[n], 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, pos)
end
end
function mob_class:entity_physics(pos,radius) return entity_physics(pos,radius) end
local los_switcher = false
local height_switcher = false
-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
function mob_class:smart_mobs(s, p, dist, dtime)
local s1 = self.path.lastpos
local target_pos = self.attack:get_pos()
-- is it becoming stuck?
if math.abs(s1.x - s.x) + math.abs(s1.z - s.z) < .5 then
self.path.stuck_timer = self.path.stuck_timer + dtime
else
self.path.stuck_timer = 0
end
self.path.lastpos = {x = s.x, y = s.y, z = s.z}
local use_pathfind = false
local has_lineofsight = minetest.line_of_sight(
{x = s.x, y = (s.y) + .5, z = s.z},
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
-- im stuck, search for path
if not has_lineofsight then
if los_switcher == true then
use_pathfind = true
los_switcher = false
end -- cannot see target!
else
if los_switcher == false then
los_switcher = true
use_pathfind = false
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end -- can see target!
end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if not self.object:get_luaentity() then
return
end
if has_lineofsight then self.path.following = false end
end, self)
end
if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
if height_switcher then
use_pathfind = true
height_switcher = false
end
else
if not height_switcher then
use_pathfind = false
height_switcher = true
end
end
if use_pathfind then
-- lets try find a path, first take care of positions
-- since pathfinder is very sensitive
local sheight = self.collisionbox[5] - self.collisionbox[2]
-- round position to center of node to avoid stuck in walls
-- also adjust height for player models!
s.x = math.floor(s.x + 0.5)
s.z = math.floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, {
x = s.x, y = s.y - 4, z = s.z}, 1)
-- determine node above ground
if not ssight then
s.y = sground.y + 1
end
local p1 = self.attack:get_pos()
p1.x = math.floor(p1.x + 0.5)
p1.y = math.floor(p1.y + 0.5)
p1.z = math.floor(p1.z + 0.5)
local dropheight = 12
if self.fear_height ~= 0 then dropheight = self.fear_height end
local jumpheight = 0
if self.jump and self.jump_height >= 4 then
jumpheight = math.min(math.ceil(self.jump_height / 4), 4)
elseif self.stepheight > 0.5 then
jumpheight = 1
end
self.path.way = minetest.find_path(s, p1, 16, jumpheight, dropheight, "A*_noprefetch")
self.state = ""
self:do_attack(self.attack)
-- no path found, try something else
if not self.path.way then
self.path.following = false
-- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob?
if s.y < p1.y then
-- build upwards
if not minetest.is_protected(s, "") then
local ndef1 = minetest.registered_nodes[self.standing_in]
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mcl_mobs.fallback_node})
end
end
local sheight = math.ceil(self.collisionbox[5]) + 1
-- assume mob is 2 blocks high so it digs above its head
s.y = s.y + sheight
-- remove one block above to make room to jump
if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1))
end
end
s.y = s.y - sheight
self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
else -- dig 2 blocks to make door toward player direction
local yaw1 = self.object:get_yaw() + math.pi / 2
local p1 = {
x = s.x + math.cos(yaw1),
y = s.y,
z = s.z + math.sin(yaw1)
}
if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
p1.y = p1.y + 1
node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
end
end
end
-- will try again in 2 seconds
self.path.stuck_timer = stuck_timeout - 2
elseif s.y < p1.y and (not self.fly) then
self:do_jump() --add jump to pathfinding
self.path.following = true
-- Yay, I found path!
-- TODO: Implement war_cry sound without being annoying
--self:mob_sound("war_cry", true)
else
self:set_velocity(self.walk_velocity)
-- follow path now that it has it
self.path.following = true
end
end
end
-- specific attacks
local specific_attack = function(list, what)
-- no list so attack default (player, animals etc.)
if list == nil then
return true
end
-- found entity on list to attack?
for no = 1, #list do
if list[no] == what then
return true
end
end
return false
end
-- find someone to attack
function mob_class:monster_attack()
if not damage_enabled
or self.passive ~= false
or self.state == "attack"
or self:day_docile() then
return
end
local s = self.object:get_pos()
local p, sp, dist
local player, obj, min_player
local type, name = "", ""
local min_dist = self.view_range + 1
local objs = minetest.get_objects_inside_radius(s, self.view_range)
local blacklist_attack = {}
for n = 1, #objs do
if not objs[n]:is_player() then
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
name = obj.name or ""
end
if obj and obj.type == self.type and obj.passive == false and obj.state == "attack" and obj.attack then
table.insert(blacklist_attack, obj.attack)
end
end
end
for n = 1, #objs do
if objs[n]:is_player() then
if mcl_mobs.invis[ objs[n]:get_player_name() ] or (not self:object_in_range(objs[n])) then
type = ""
elseif (self.type == "monster" or self._aggro) then
player = objs[n]
type = "player"
name = "player"
end
else
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
type = obj.type
name = obj.name or ""
end
end
-- find specific mob to attack, failing that attack player/npc/animal
if specific_attack(self.specific_attack, name)
and (type == "player" or ( type == "npc" and self.attack_npcs )
or (type == "animal" and self.attack_animals == true)) then
p = player:get_pos()
sp = s
dist = vector.distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
local attacked_p = false
for c=1, #blacklist_attack do
if blacklist_attack[c] == player then
attacked_p = true
end
end
-- choose closest player to attack
if dist < min_dist
and not attacked_p
and self:line_of_sight( sp, p, 2) == true then
min_dist = dist
min_player = player
end
end
end
if not min_player and #blacklist_attack > 0 then
min_player=blacklist_attack[math.random(#blacklist_attack)]
end
-- attack player
if min_player then
self:do_attack(min_player)
end
end
-- npc, find closest monster to attack
function mob_class:npc_attack()
if self.type ~= "npc"
or not self.attacks_monsters
or self.state == "attack" then
return
end
local p, sp, obj, min_player
local s = self.object:get_pos()
local min_dist = self.view_range + 1
local objs = minetest.get_objects_inside_radius(s, self.view_range)
for n = 1, #objs do
obj = objs[n]:get_luaentity()
if obj and obj.type == "monster" then
p = obj.object:get_pos()
sp = s
local dist = vector.distance(p, s)
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
if dist < min_dist
and self:line_of_sight( sp, p, 2) == true then
min_dist = dist
min_player = obj.object
end
end
end
if min_player then
self:do_attack(min_player)
end
end
-- dogshoot attack switch and counter function
function mob_class:dogswitch(dtime)
-- switch mode not activated
if not self.dogshoot_switch
or not dtime then
return 0
end
self.dogshoot_count = self.dogshoot_count + dtime
if (self.dogshoot_switch == 1
and self.dogshoot_count > self.dogshoot_count_max)
or (self.dogshoot_switch == 2
and self.dogshoot_count > self.dogshoot_count2_max) then
self.dogshoot_count = 0
if self.dogshoot_switch == 1 then
self.dogshoot_switch = 2
else
self.dogshoot_switch = 1
end
end
return self.dogshoot_switch
end
-- no damage to nodes explosion
function mob_class:safe_boom(pos, strength)
minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
pos = pos,
gain = 1.0,
max_hear_distance = self.sounds and self.sounds.distance or 32
}, true)
local radius = strength
entity_physics(pos, radius)
mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", radius * 3, radius * 5, radius, 1, 0)
end
-- make explosion with protection and tnt mod check
function mob_class:boom(pos, strength, fire)
if mobs_griefing and not minetest.is_protected(pos, "") then
mcl_explosions.explode(pos, strength, { drop_chance = 1.0, fire = fire }, self.object)
else
mcl_mobs.mob_class.safe_boom(self, pos, strength) --need to call it this way bc self is the "arrow" object here
end
-- delete the object after it punched the player to avoid nil entities in e.g. mcl_shields!!
self.object:remove()
end
-- deal damage and effects when mob punched
function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
-- custom punch function
if self.do_punch then
-- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return
end
end
-- error checking when mod profiling is enabled
if not tool_capabilities then
minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
return
end
local is_player = hitter:is_player()
if is_player then
-- is mob protected?
if self.protected and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
return
end
if minetest.is_creative_enabled(hitter:get_player_name()) then
self.health = 0
end
-- set/update 'drop xp' timestamp if hitted by player
self.xp_timestamp = minetest.get_us_time()
end
-- punch interval
local weapon = hitter:get_wielded_item()
local punch_interval = 1.4
-- exhaust attacker
if is_player then
mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK)
end
-- calculate mob damage
local damage = 0
local armor = self.object:get_armor_groups() or {}
local tmp
-- quick error check incase it ends up 0 (serialize.h check test)
if tflp == 0 then
tflp = 0.2
end
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
if tmp < 0 then
tmp = 0.0
elseif tmp > 1 then
tmp = 1.0
end
damage = damage + (tool_capabilities.damage_groups[group] or 0)
* tmp * ((armor[group] or 0) / 100.0)
end
if weapon then
local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect")
if fire_aspect_level > 0 then
mcl_burning.set_on_fire(self.object, fire_aspect_level * 4)
end
end
-- check for tool immunity or special damage
for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0
break
end
end
-- healing
if damage <= -1 then
self.health = self.health - math.floor(damage)
return
end
if tool_capabilities then
punch_interval = tool_capabilities.full_punch_interval or 1.4
end
-- add weapon wear manually
-- Required because we have custom health handling ("health" property)
if minetest.is_creative_enabled("") ~= true
and tool_capabilities then
if tool_capabilities.punch_attack_uses then
-- Without this delay, the wear does not work. Quite hacky ...
minetest.after(0, function(name)
local player = minetest.get_player_by_name(name)
if not player then return end
local weapon = hitter:get_wielded_item(player)
local def = weapon:get_definition()
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)
hitter:set_wielded_item(weapon)
end
end, hitter:get_player_name())
end
end
local die = false
if damage >= 0 then
-- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately.
if damage >= 0.1 then
-- weapon sounds
if weapon:get_definition().sounds ~= nil then
local s = math.random(0, #weapon:get_definition().sounds)
minetest.sound_play(weapon:get_definition().sounds[s], {
object = self.object, --hitter,
max_hear_distance = 8
}, true)
else
minetest.sound_play("default_punch", {
object = self.object,
max_hear_distance = 5
}, true)
end
self:damage_effect(damage)
-- do damage
self.health = self.health - damage
-- skip future functions if dead, except alerting others
if self:check_for_death( "hit", {type = "punch", puncher = hitter}) then
die = true
end
end
-- knock back effect (only on full punch)
if self.knock_back
and tflp >= punch_interval then
-- direction error check
dir = dir or {x = 0, y = 0, z = 0}
local v = self.object:get_velocity()
if not v then return end
local r = 1.4 - math.min(punch_interval, 1.4)
local kb = r * (math.abs(v.x)+math.abs(v.z))
local up = 2
if die==true then
kb=kb*2
end
-- if already in air then dont go up anymore when hit
if math.abs(v.y) > 0.1
or self.fly then
up = 0
end
-- check if tool already has specific knockback value
if tool_capabilities.damage_groups["knockback"] then
kb = tool_capabilities.damage_groups["knockback"]
else
kb = kb * 1.5
end
local luaentity
if hitter then
luaentity = hitter:get_luaentity()
end
if hitter and is_player then
local wielditem = hitter:get_wielded_item()
kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback")
elseif luaentity and luaentity._knockback then
kb = kb + luaentity._knockback
end
self._kb_turn = true
self._turn_to=self.object:get_yaw()-1.57
self.frame_speed_multiplier=2.3
if self.animation.run_end then
self:set_animation( "run")
elseif self.animation.walk_end then
self:set_animation( "walk")
end
minetest.after(0.2, function()
if self and self.object then
self.frame_speed_multiplier=1
self._kb_turn = false
end
end)
self.object:add_velocity({
x = dir.x * kb,
y = up*2,
z = dir.z * kb
})
self.pause_timer = 0.25
end
end -- END if damage
-- if skittish then run away
if hitter and is_player and hitter:get_pos() and not die and self.runaway == true and self.state ~= "flop" then
local yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos())))
minetest.after(0.2,function()
if self and self.object and self.object:get_pos() and hitter and is_player and hitter:get_pos() then
yaw = self:set_yaw( minetest.dir_to_yaw(vector.direction(hitter:get_pos(), self.object:get_pos())))
self:set_velocity( self.run_velocity)
end
end)
self.state = "runaway"
self.runaway_timer = 0
self.following = nil
end
local name = hitter:get_player_name() or ""
-- attack puncher and call other mobs for help
if self.passive == false
and self.state ~= "flop"
and (self.child == false or self.type == "monster")
and hitter:get_player_name() ~= self.owner
and not mcl_mobs.invis[ name ] then
if not die then
-- attack whoever punched mob
self.state = ""
self:do_attack(hitter)
self._aggro= true
end
-- alert others to the attack
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
local obj = nil
for n = 1, #objs do
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.name == obj.group_attack[i] 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
end
end
end
end
function mob_class:check_aggro(dtime)
if not self._aggro or not self.attack then return end
if not self._check_aggro_timer or self._check_aggro_timer > 5 then
self._check_aggro_timer = 0
if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then
self._aggro = nil
self.attack = nil
self.state = "stand"
end
end
self._check_aggro_timer = self._check_aggro_timer + dtime
end

View File

@ -1,32 +0,0 @@
-- this is to make the register_mob and register egg functions commonly used by mods not break
-- when they use the weird old : notation AND self as first argument
local oldregmob = mcl_mobs.register_mob
function mcl_mobs.register_mob(self,name,def)
if type(self) == "string" then
def = name
name = self
end
return oldregmob(name,def)
end
local oldregegg = mcl_mobs.register_egg
function mcl_mobs.register_egg(self, mob, desc, background_color, overlay_color, addegg, no_creative)
if type(self) == "string" then
no_creative = addegg
addegg = overlay_color
overlay_color = background_color
background_color = desc
desc = mob
mob = self
end
return oldregegg(mob, desc, background_color, overlay_color, addegg, no_creative)
end
local oldregarrow = mcl_mobs.register_mob
function mcl_mobs.register_mob(self,name,def)
if type(self) == "string" then
def = name
name = self
end
return oldregarrow(name,def)
end

View File

@ -1,5 +1,5 @@
local S = minetest.get_translator("mcl_mobs")
local S = minetest.get_translator(minetest.get_current_modname())
-- name tag
minetest.register_craftitem("mcl_mobs:nametag", {

View File

@ -1,384 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local active_particlespawners = {}
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
local DEFAULT_FALL_SPEED = -9.81*1.5
local player_transfer_distance = tonumber(minetest.settings:get("player_transfer_distance")) or 128
if player_transfer_distance == 0 then player_transfer_distance = math.huge end
-- custom particle effects
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
radius = radius or 2
min_size = min_size or 0.5
max_size = max_size or 1
gravity = gravity or DEFAULT_FALL_SPEED
glow = glow or 0
go_down = go_down or false
local ym
if go_down then
ym = 0
else
ym = -radius
end
minetest.add_particlespawner({
amount = amount,
time = 0.25,
minpos = pos,
maxpos = pos,
minvel = {x = -radius, y = ym, z = -radius},
maxvel = {x = radius, y = radius, z = radius},
minacc = {x = 0, y = gravity, z = 0},
maxacc = {x = 0, y = gravity, z = 0},
minexptime = 0.1,
maxexptime = 1,
minsize = min_size,
maxsize = max_size,
texture = texture,
glow = glow,
})
end
function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate)
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
else
min = { x = -0.5, y = 0, z = -0.5 }
max = { x = 0.5, y = 0.5, z = 0.5 }
end
if rotate then
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
min, max = vector.sort(min, max)
min = vector.multiply(min, 0.5)
max = vector.multiply(max, 0.5)
end
minetest.add_particlespawner({
amount = 50,
time = 0.001,
minpos = vector.add(pos, min),
maxpos = vector.add(pos, max),
minvel = vector.new(-5,-5,-5),
maxvel = vector.new(5,5,5),
minexptime = 1.1,
maxexptime = 1.5,
minsize = 1,
maxsize = 2,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_mob_death.png^[colorize:#000000:255",
})
minetest.sound_play("mcl_mobs_mob_poof", {
pos = pos,
gain = 1.0,
max_hear_distance = 8,
}, true)
end
-- play sound
function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
local soundinfo
if self.sounds_child and self.child then
soundinfo = self.sounds_child
elseif self.sounds then
soundinfo = self.sounds
end
if not soundinfo then
return
end
local sound = soundinfo[soundname]
if sound then
if is_opinion and self.opinion_sound_cooloff > 0 then
return
end
local pitch
if not fixed_pitch then
local base_pitch = soundinfo.base_pitch
if not base_pitch then
base_pitch = 1
end
if self.child and (not self.sounds_child) then
-- Children have higher pitch
pitch = base_pitch * 1.5
else
pitch = base_pitch
end
-- randomize the pitch a bit
pitch = pitch + math.random(-10, 10) * 0.005
end
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = self.sounds.distance,
pitch = pitch,
}, true)
self.opinion_sound_cooloff = 1
end
end
function mob_class:add_texture_mod(mod)
local full_mod = ""
local already_added = false
for i=1, #self.texture_mods do
if mod == self.texture_mods[i] then
already_added = true
end
full_mod = full_mod .. self.texture_mods[i]
end
if not already_added then
full_mod = full_mod .. mod
table.insert(self.texture_mods, mod)
end
self.object:set_texture_mod(full_mod)
end
function mob_class:remove_texture_mod(mod)
local full_mod = ""
local remove = {}
for i=1, #self.texture_mods do
if self.texture_mods[i] ~= mod then
full_mod = full_mod .. self.texture_mods[i]
else
table.insert(remove, i)
end
end
for i=#remove, 1 do
table.remove(self.texture_mods, remove[i])
end
self.object:set_texture_mod(full_mod)
end
function mob_class:damage_effect(damage)
-- damage particles
if (not disable_blood) and damage > 0 then
local amount_large = math.floor(damage / 2)
local amount_small = damage % 2
local pos = self.object:get_pos()
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
local texture = "mobs_blood.png"
-- full heart damage (one particle for each 2 HP damage)
if amount_large > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
end
-- half heart damage (one additional particle if damage is an odd number)
if amount_small > 0 then
-- TODO: Use "half heart"
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
end
end
end
function mob_class:remove_particlespawners(pn)
if not active_particlespawners[pn] then return end
if not active_particlespawners[pn][self.object] then return end
for k,v in pairs(active_particlespawners[pn][self.object]) do
minetest.delete_particlespawner(v)
end
end
function mob_class:add_particlespawners(pn)
if not active_particlespawners[pn] then active_particlespawners[pn] = {} end
if not active_particlespawners[pn][self.object] then active_particlespawners[pn][self.object] = {} end
for _,ps in pairs(self.particlespawners) do
ps.attached = self.object
ps.playername = pn
table.insert(active_particlespawners[pn][self.object],minetest.add_particlespawner(ps))
end
end
function mob_class:check_particlespawners(dtime)
if not self.particlespawners then return end
--minetest.log(dump(active_particlespawners))
if self._particle_timer and self._particle_timer >= 1 then
self._particle_timer = 0
local players = {}
for _,player in pairs(minetest.get_connected_players()) do
local pn = player:get_player_name()
table.insert(players,pn)
if not active_particlespawners[pn] then
active_particlespawners[pn] = {} end
local dst = vector.distance(player:get_pos(),self.object:get_pos())
if dst < player_transfer_distance and not active_particlespawners[pn][self.object] then
self:add_particlespawners(pn)
elseif dst >= player_transfer_distance and active_particlespawners[pn][self.object] then
self:remove_particlespawners(pn)
end
end
elseif not self._particle_timer then
self._particle_timer = 0
end
self._particle_timer = self._particle_timer + dtime
end
-- set defined animation
function mob_class:set_animation(anim, fixed_frame)
if not self.animation or not anim then
return
end
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
return
end
if self.jockey then
anim = "jockey"
end
if self:flight_check() and self.fly and anim == "walk" then anim = "fly" end
self._current_animation = self._current_animation or ""
if (anim == self._current_animation
or not self.animation[anim .. "_start"]
or not self.animation[anim .. "_end"]) and self.state ~= "die" then
return
end
self._current_animation = anim
local a_start = self.animation[anim .. "_start"]
local a_end
if fixed_frame then
a_end = a_start
else
a_end = self.animation[anim .. "_end"]
end
if a_start and a_end then
self.object:set_animation({
x = a_start,
y = a_end},
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
0, self.animation[anim .. "_loop"] ~= false)
end
end
-- above function exported for mount.lua
function mcl_mobs:set_animation(self, anim)
self:set_animation(anim)
end
local function dir_to_pitch(dir)
--local dir2 = vector.normalize(dir)
local xz = math.abs(dir.x) + math.abs(dir.z)
return -math.atan2(-dir.y, xz)
end
function mob_class:check_head_swivel(dtime)
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
local final_rotation = vector.new(0,0,0)
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
local pos = self.object:get_pos()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do
if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then
if not self._locked_object then
if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then
self._locked_object = obj
end
else
if math.random(10000/self.curiosity) == 1 then
self._locked_object = nil
end
end
end
end
if self.attack or self.following then
self._locked_object = self.attack or self.following
end
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
local _locked_object_eye_height = 1.5
if self._locked_object:get_luaentity() then
_locked_object_eye_height = self._locked_object:get_luaentity().head_eye_height
end
if self._locked_object:is_player() then
_locked_object_eye_height = self._locked_object:get_properties().eye_height
end
if _locked_object_eye_height then
local self_rot = self.object:get_rotation()
if self.object:get_attach() then
self_rot = self.object:get_attach():get_rotation()
end
if self.rot then
local player_pos = self._locked_object:get_pos()
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then
final_rotation = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then
if self.head_yaw == "y" then
final_rotation = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then
final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
end
else
if self.head_yaw == "y" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)
elseif self.head_yaw == "z" then
final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)
end
end
end
end
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
final_rotation = vector.multiply(oldr, 0.9)
else
final_rotation = vector.new(0,0,0)
end
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation)
end
function mob_class:set_animation_speed()
local v = self.object:get_velocity()
if v then
if self.frame_speed_multiplier then
local v2 = math.abs(v.x)+math.abs(v.z)*.833
if not self.animation.walk_speed then
self.animation.walk_speed = 25
end
if math.abs(v.x)+math.abs(v.z) > 0.5 then
self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
else
self.object:set_animation_frame_speed(25)
end
end
--set_speed
if self.acc then
self.object:add_velocity(self.acc)
end
end
end
minetest.register_on_leaveplayer(function(player)
local pn = player:get_player_name()
if not active_particlespawners[pn] then return end
for _,m in pairs(active_particlespawners[pn]) do
for k,v in pairs(m) do
minetest.delete_particlespawner(v)
end
end
active_particlespawners[pn] = nil
end)

View File

@ -1,566 +1,16 @@
mcl_mobs = {}
mcl_mobs.mob_class = {}
mcl_mobs.mob_class_meta = {__index = mcl_mobs.mob_class}
mcl_mobs.registered_mobs = {}
local modname = minetest.get_current_modname()
local path = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
--api and helpers
-- effects: sounds and particles mostly
dofile(path .. "/effects.lua")
-- physics: involuntary mob movement - particularly falling and death
dofile(path .. "/physics.lua")
-- movement: general voluntary mob movement, walking avoiding cliffs etc.
dofile(path .. "/movement.lua")
-- items: item management for mobs
dofile(path .. "/items.lua")
-- pathfinding: pathfinding to target positions
dofile(path .. "/pathfinding.lua")
-- combat: attack logic
dofile(path .. "/combat.lua")
-- the enity functions themselves
dofile(path .. "/api.lua")
local path = minetest.get_modpath(minetest.get_current_modname())
--utility functions
dofile(path .. "/breeding.lua")
dofile(path .. "/spawning.lua")
dofile(path .. "/mount.lua")
dofile(path .. "/crafts.lua")
dofile(path .. "/compat.lua")
local api_path = path.."/api"
local DEFAULT_FALL_SPEED = -9.81*1.5
local MAX_MOB_NAME_LENGTH = 30
-- Mob API
dofile(api_path .. "/api.lua")
local old_spawn_icons = minetest.settings:get_bool("mcl_old_spawn_icons",false)
local extended_pet_control = minetest.settings:get_bool("mcl_extended_pet_control",true)
local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
-- Spawning Algorithm
dofile(api_path .. "/spawning.lua")
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
-- Rideable Mobs
dofile(api_path .. "/mount.lua")
--#### REGISTER FUNCS
-- Code to execute before custom on_rightclick handling
local on_rightclick_prefix = function(self, clicker)
if not clicker:is_player() then return end
local item = clicker:get_wielded_item()
if extended_pet_control and self.tamed and self.owner == clicker:get_player_name() then
self:toggle_sit(clicker)
end
-- Name mob with nametag
if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then
local tag = item:get_meta():get_string("name")
if tag ~= "" then
if string.len(tag) > MAX_MOB_NAME_LENGTH then
tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
end
self.nametag = tag
self:update_tag()
if not minetest.is_creative_enabled(clicker:get_player_name()) then
item:take_item()
clicker:set_wielded_item(item)
end
return true
end
end
return false
end
local create_mob_on_rightclick = function(on_rightclick)
return function(self, clicker)
local stop = on_rightclick_prefix(self, clicker)
if (not stop) and (on_rightclick) then
on_rightclick(self, clicker)
end
end
end
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
mcl_mobs.spawning_mobs = {}
-- register mob entity
function mcl_mobs.register_mob(name, def)
mcl_mobs.spawning_mobs[name] = true
mcl_mobs.registered_mobs[name] = def
local can_despawn
if def.can_despawn ~= nil then
can_despawn = def.can_despawn
elseif def.spawn_class == "passive" then
can_despawn = false
else
can_despawn = true
end
local function scale_difficulty(value, default, min, special)
if (not value) or (value == default) or (value == special) then
return default
else
return math.max(min, value * difficulty)
end
end
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
-- Workaround for <https://github.com/minetest/minetest/issues/5966>:
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
-- FIXME: Remove workaround if it's no longer needed.
if collisionbox[5] < 0.79 then
collisionbox[5] = 0.79
end
local final_def = {
use_texture_alpha = def.use_texture_alpha,
head_swivel = def.head_swivel or nil, -- bool to activate this function
head_yaw_offset = def.head_yaw_offset or 0, -- for wonkey model bones
head_pitch_multiplier = def.head_pitch_multiplier or 1, --for inverted pitch
bone_eye_height = def.bone_eye_height or 1.4, -- head bone offset
head_eye_height = def.head_eye_height or def.bone_eye_height or 0, -- how hight aproximatly the mobs head is fromm the ground to tell the mob how high to look up at the player
curiosity = def.curiosity or 1, -- how often mob will look at player on idle
head_yaw = def.head_yaw or "y", -- axis to rotate head on
horrizonatal_head_height = def.horrizonatal_head_height or 0,
wears_armor = def.wears_armor, -- a number value used to index texture slot for armor
stepheight = def.stepheight or 0.6,
name = name,
description = def.description,
type = def.type,
attack_type = def.attack_type,
fly = def.fly or false,
fly_in = def.fly_in or {"air", "__airlike"},
owner = def.owner or "",
order = def.order or "",
on_die = def.on_die,
spawn_small_alternative = def.spawn_small_alternative,
do_custom = def.do_custom,
detach_child = def.detach_child,
jump_height = def.jump_height or 4, -- was 6
rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
lifetimer = def.lifetimer or 57.73,
hp_min = scale_difficulty(def.hp_min, 5, 1),
hp_max = scale_difficulty(def.hp_max, 10, 1),
xp_min = def.xp_min or 0,
xp_max = def.xp_max or 0,
xp_timestamp = 0,
breath_max = def.breath_max or 15,
breathes_in_water = def.breathes_in_water or false,
physical = true,
collisionbox = collisionbox,
selectionbox = def.selectionbox or def.collisionbox,
visual = def.visual,
visual_size = def.visual_size or {x = 1, y = 1},
mesh = def.mesh,
makes_footstep_sound = def.makes_footstep_sound or false,
view_range = def.view_range or 16,
walk_velocity = def.walk_velocity or 1,
run_velocity = def.run_velocity or 2,
damage = scale_difficulty(def.damage, 0, 0),
light_damage = def.light_damage or 0,
sunlight_damage = def.sunlight_damage or 0,
water_damage = def.water_damage or 0,
lava_damage = def.lava_damage or 8,
fire_damage = def.fire_damage or 1,
suffocation = def.suffocation or true,
fall_damage = def.fall_damage or 1,
fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2
drops = def.drops or {},
armor = def.armor or 100,
on_rightclick = create_mob_on_rightclick(def.on_rightclick),
arrow = def.arrow,
shoot_interval = def.shoot_interval,
sounds = def.sounds or {},
animation = def.animation or {},
follow = def.follow,
nofollow = def.nofollow,
can_open_doors = def.can_open_doors,
jump = def.jump ~= false,
automatic_face_movement_max_rotation_per_sec = 300,
walk_chance = def.walk_chance or 50,
attacks_monsters = def.attacks_monsters or false,
group_attack = def.group_attack or false,
passive = def.passive or false,
knock_back = def.knock_back ~= false,
shoot_offset = def.shoot_offset or 0,
floats = def.floats or 1, -- floats in water by default
floats_on_lava = def.floats_on_lava or 0,
replace_rate = def.replace_rate,
replace_what = def.replace_what,
replace_with = def.replace_with,
replace_offset = def.replace_offset or 0,
on_replace = def.on_replace,
timer = 0,
env_damage_timer = 0,
tamed = false,
pause_timer = 0,
horny = false,
hornytimer = 0,
gotten = false,
health = 0,
frame_speed_multiplier = 1,
reach = def.reach or 3,
htimer = 0,
texture_list = def.textures,
child_texture = def.child_texture,
docile_by_day = def.docile_by_day or false,
time_of_day = 0.5,
fear_height = def.fear_height or 0,
runaway = def.runaway,
runaway_timer = 0,
pathfinding = def.pathfinding,
immune_to = def.immune_to or {},
explosion_radius = def.explosion_radius, -- LEGACY
explosion_damage_radius = def.explosion_damage_radius, -- LEGACY
explosiontimer_reset_radius = def.explosiontimer_reset_radius,
explosion_timer = def.explosion_timer or 3,
allow_fuse_reset = def.allow_fuse_reset ~= false,
stop_to_explode = def.stop_to_explode ~= false,
custom_attack = def.custom_attack,
double_melee_attack = def.double_melee_attack,
dogshoot_switch = def.dogshoot_switch,
dogshoot_count = 0,
dogshoot_count_max = def.dogshoot_count_max or 5,
dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
attack_animals = def.attack_animals or false,
attack_npcs = def.attack_npcs or false,
specific_attack = def.specific_attack,
runaway_from = def.runaway_from,
owner_loyal = def.owner_loyal,
facing_fence = false,
is_mob = true,
pushable = def.pushable or true,
-- MCL2 extensions
shooter_avoid_enemy = def.shooter_avoid_enemy,
strafes = def.strafes,
avoid_distance = def.avoid_distance or 9,
do_teleport = def.do_teleport,
spawn_class = def.spawn_class,
can_spawn = def.can_spawn,
ignores_nametag = def.ignores_nametag or false,
rain_damage = def.rain_damage or 0,
glow = def.glow,
can_despawn = can_despawn,
child = def.child or false,
texture_mods = {},
shoot_arrow = def.shoot_arrow,
sounds_child = def.sounds_child,
_child_animations = def.child_animations,
pick_up = def.pick_up,
explosion_strength = def.explosion_strength,
suffocation_timer = 0,
follow_velocity = def.follow_velocity or 2.4,
instant_death = def.instant_death or false,
fire_resistant = def.fire_resistant or false,
fire_damage_resistant = def.fire_damage_resistant or false,
ignited_by_sunlight = def.ignited_by_sunlight or false,
spawn_in_group = def.spawn_in_group,
spawn_in_group_min = def.spawn_in_group_min,
noyaw = def.noyaw or false,
particlespawners = def.particlespawners,
-- End of MCL2 extensions
on_spawn = def.on_spawn,
on_blast = def.on_blast or function(self,damage)
self.object:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = damage},
}, nil)
return false, true, {}
end,
do_punch = def.do_punch,
on_breed = def.on_breed,
on_grown = def.on_grown,
on_pick_up = def.on_pick_up,
on_activate = function(self, staticdata, dtime)
--this is a temporary hack so mobs stop
--glitching and acting really weird with the
--default built in engine collision detection
self.is_mob = true
self.object:set_properties({
collide_with_objects = false,
})
return self:mob_activate(staticdata, def, dtime)
end,
harmed_by_heal = def.harmed_by_heal,
on_lightning_strike = def.on_lightning_strike
}
minetest.register_entity(name, setmetatable(final_def,mcl_mobs.mob_class_meta))
if minetest.get_modpath("doc_identifier") ~= nil then
doc.sub.identifier.register_object(name, "basics", "mobs")
end
end -- END mcl_mobs.register_mob function
-- register arrow for shoot attack
function mcl_mobs.register_arrow(name, def)
if not name or not def then return end -- errorcheck
minetest.register_entity(name, {
physical = false,
visual = def.visual,
visual_size = def.visual_size,
textures = def.textures,
velocity = def.velocity,
hit_player = def.hit_player,
hit_node = def.hit_node,
hit_mob = def.hit_mob,
hit_object = def.hit_object,
drop = def.drop or false, -- drops arrow as registered item when true
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
timer = 0,
switch = 0,
owner_id = def.owner_id,
rotate = def.rotate,
on_punch = function(self)
local vel = self.object:get_velocity()
self.object:set_velocity({x=vel.x * -1, y=vel.y * -1, z=vel.z * -1})
end,
collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
automatic_face_movement_dir = def.rotate
and (def.rotate - (math.pi / 180)) or false,
on_activate = def.on_activate,
on_step = def.on_step or function(self, dtime)
self.timer = self.timer + 1
local pos = self.object:get_pos()
if self.switch == 0
or self.timer > 150
or not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove();
return
end
-- does arrow have a tail (fireball)
if def.tail
and def.tail == 1
and def.tail_texture then
minetest.add_particle({
pos = pos,
velocity = {x = 0, y = 0, z = 0},
acceleration = {x = 0, y = 0, z = 0},
expirationtime = def.expire or 0.25,
collisiondetection = false,
texture = def.tail_texture,
size = def.tail_size or 5,
glow = def.glow or 0,
})
end
if self.hit_node then
local node = node_ok(pos).name
if minetest.registered_nodes[node].walkable then
self.hit_node(self, pos, node)
if self.drop == true then
pos.y = pos.y + 1
self.lastpos = (self.lastpos or pos)
minetest.add_item(self.lastpos, self.object:get_luaentity().name)
end
self.object:remove();
return
end
end
if self.hit_player or self.hit_mob or self.hit_object then
for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
if self.hit_player
and player:is_player() then
self.hit_player(self, player)
self.object:remove();
return
end
local entity = player:get_luaentity()
if entity
and self.hit_mob
and entity.is_mob == true
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name then
self.hit_mob(self, player)
self.object:remove();
return
end
if entity
and self.hit_object
and (not entity.is_mob)
and tostring(player) ~= self.owner_id
and entity.name ~= self.object:get_luaentity().name then
self.hit_object(self, player)
self.object:remove();
return
end
end
end
self.lastpos = pos
end
})
end
-- Register spawn eggs
-- Note: This also introduces the “spawn_egg” group:
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addegg, no_creative)
local grp = {spawn_egg = 1}
-- do NOT add this egg to creative inventory (e.g. dungeon master)
if no_creative == true then
grp.not_in_creative_inventory = 1
end
local invimg = "(spawn_egg.png^[multiply:" .. background_color ..")^(spawn_egg_overlay.png^[multiply:" .. overlay_color .. ")"
if old_spawn_icons then
local mobname = mob:gsub("mobs_mc:","")
local fn = "mobs_mc_spawn_icon_"..mobname..".png"
if mcl_util.file_exists(minetest.get_modpath("mobs_mc").."/textures/"..fn) then
invimg = fn
end
end
if addegg == 1 then
invimg = "mobs_chicken_egg.png^(" .. invimg ..
"^[mask:mobs_chicken_egg_overlay.png)"
end
-- register old stackable mob egg
minetest.register_craftitem(mob, {
description = desc,
inventory_image = invimg,
groups = grp,
_doc_items_longdesc = S("This allows you to place a single mob."),
_doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."),
on_place = function(itemstack, placer, pointed_thing)
local pos = pointed_thing.above
-- am I clicking on something with existing on_rightclick function?
local under = minetest.get_node(pointed_thing.under)
local def = minetest.registered_nodes[under.name]
if def and def.on_rightclick then
return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
end
if pos
and within_limits(pos, 0)
and not minetest.is_protected(pos, placer:get_player_name()) then
local name = placer:get_player_name()
local privs = minetest.get_player_privs(name)
if under.name == "mcl_mobspawners:spawner" then
if minetest.is_protected(pointed_thing.under, name) then
minetest.record_protection_violation(pointed_thing.under, name)
return itemstack
end
if not privs.maphack then
minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner."))
return itemstack
end
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
if not minetest.is_creative_enabled(name) then
itemstack:take_item()
end
return itemstack
end
if not minetest.registered_entities[mob] then
return itemstack
end
if minetest.settings:get_bool("only_peaceful_mobs", false)
and minetest.registered_entities[mob].type == "monster" then
minetest.chat_send_player(name, S("Only peaceful mobs allowed!"))
return itemstack
end
pos.y = pos.y - 0.5
local mob = minetest.add_entity(pos, mob)
local entityname = itemstack:get_name()
minetest.log("action", "Player " ..name.." spawned "..entityname.." at "..minetest.pos_to_string(pos))
local ent = mob:get_luaentity()
-- don't set owner if monster or sneak pressed
if ent.type ~= "monster"
and not placer:get_player_control().sneak then
ent.owner = placer:get_player_name()
ent.tamed = true
end
-- set nametag
local nametag = itemstack:get_meta():get_string("name")
if nametag ~= "" then
if string.len(nametag) > MAX_MOB_NAME_LENGTH then
nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
end
ent.nametag = nametag
update_tag(ent)
end
-- if not in creative then take item
if not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
end
return itemstack
end,
})
end
-- Mob Items
dofile(path .. "/crafts.lua")

View File

@ -1,103 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
--- Item and armor management
local function player_near(pos)
for _,o in pairs(minetest.get_objects_inside_radius(pos,2)) do
if o:is_player() then return true end
end
end
local function get_armor_texture(armor_name)
if armor_name == "" then
return ""
end
if armor_name=="blank.png" then
return "blank.png"
end
local seperator = string.find(armor_name, ":")
return "mcl_armor_"..string.sub(armor_name, seperator+1, -1)..".png^"
end
function mob_class:set_armor_texture()
if self.armor_list then
local chestplate=minetest.registered_items[self.armor_list.chestplate] or {name=""}
local boots=minetest.registered_items[self.armor_list.boots] or {name=""}
local leggings=minetest.registered_items[self.armor_list.leggings] or {name=""}
local helmet=minetest.registered_items[self.armor_list.helmet] or {name=""}
if helmet.name=="" and chestplate.name=="" and leggings.name=="" and boots.name=="" then
helmet={name="blank.png"}
end
local texture = get_armor_texture(chestplate.name)..get_armor_texture(helmet.name)..get_armor_texture(boots.name)..get_armor_texture(leggings.name)
if string.sub(texture, -1,-1) == "^" then
texture=string.sub(texture,1,-2)
end
if self.textures[self.wears_armor] then
self.textures[self.wears_armor]=texture
end
self.object:set_properties({textures=self.textures})
local armor_
if type(self.armor) == "table" then
armor_ = table.copy(self.armor)
armor_.immortal = 1
else
armor_ = {immortal=1, fleshy = self.armor}
end
for _,item in pairs(self.armor_list) do
if not item then return end
if type(minetest.get_item_group(item, "mcl_armor_points")) == "number" then
armor_.fleshy=armor_.fleshy-(minetest.get_item_group(item, "mcl_armor_points")*3.5)
end
end
self.object:set_armor_groups(armor_)
end
end
function mob_class:check_item_pickup()
if self.pick_up and #self.pick_up > 0 or self.wears_armor then
local p = self.object:get_pos()
if not p then return end
for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do
local l=o:get_luaentity()
if l and l.name == "__builtin:item" then
if not player_near(p) and l.itemstring:find("mcl_armor") and self.wears_armor then
local armor_type
if l.itemstring:find("chestplate") then
armor_type = "chestplate"
elseif l.itemstring:find("boots") then
armor_type = "boots"
elseif l.itemstring:find("leggings") then
armor_type = "leggings"
elseif l.itemstring:find("helmet") then
armor_type = "helmet"
end
if not armor_type then
return
end
if not self.armor_list then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
elseif self.armor_list[armor_type] and self.armor_list[armor_type] ~= "" then
return
end
self.armor_list[armor_type]=ItemStack(l.itemstring):get_name()
o:remove()
end
if self.pick_up then
for k,v in pairs(self.pick_up) do
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()
elseif r and r.is_empty and r:is_empty() then
o:remove()
end
end
end
end
end
end
end
end

View File

@ -1,11 +0,0 @@
# textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=ピースフルモード有効! モンスターは出現しません。
This allows you to place a single mob.=これにより、1体のMOBを配置できます。
Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns.=MOBを出現させたい場所に、置くだけです。スニークキーを押しながら配置しない限り、動物は飼いならされた状態でスポーンします。MOBスポナーに設置すると、スポーンするMOBが変わります。
You need the “maphack” privilege to change the mob spawner.=MOBスポナーを変更するには、"maphack"権限が必要です。
Name Tag=名札
A name tag is an item to name a mob.=名札は、MOBに名前をつけるためのアイテムです。
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=名札を使用する前に、金床で名前の設定をする必要があります。その後、MOBに名前をつけるために名札が使えます。これで名札は使い切ります。
Only peaceful mobs allowed!=平和的なMOBのみ許可
Give names to mobs=MOBに名前を付与
Set name at anvil=金床で名前の設定

View File

@ -0,0 +1,11 @@
# textdomain: mcl_mobs
Peaceful mode active! No monsters will spawn.=Tryb pokojowy aktywowany! Potwory nie będą się pojawiać.
This allows you to place a single mob.=To pozwala na przywołanie jednego moba.
Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns.=Postaw to w miejscu w którym chcesz aby pojawił się mob. Zwierzęta pojawią się jako oswojone chyba, że będziesz się skradał podczas stawiania. Jeśli postawisz to na spawnerze to zmienisz którego moba przywołuje.
You need the “maphack” privilege to change the mob spawner.=Potrzebujesz przywileju "maphack", aby zmienić spawner.
Name Tag=Znacznik
A name tag is an item to name a mob.=Znacznik jest przedmiotem pozwalającym nazwać moba.
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Zanim użyjesz znacznika musisz wybrać imię przy kowadle. Następnie możesz użyć znacznika by nazwać moba. To zużywa znacznik.
Only peaceful mobs allowed!=Tylko pokojowe moby są dozwolone!
Give names to mobs=Nazwij moby
Set name at anvil=Wybierz imię przy kowadle

View File

@ -1,8 +0,0 @@
if minetest.get_modpath("lucky_block") then
lucky_block:add_blocks({
{"dro", {"mcl_mobs:nametag"}, 1},
{"lig"},
})
end

View File

@ -2,4 +2,4 @@ name = mcl_mobs
author = PilzAdam
description = Adds a mob API for mods to add animals or monsters, etc.
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
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience

File diff suppressed because it is too large Load Diff

View File

@ -1,382 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
local PATHFINDING = "gowp"
local enable_pathfinding = true
local LOG_MODULE = "[Mobs]"
local function mcl_log (message)
if LOGGING_ON and message then
minetest.log(LOG_MODULE .. " " .. message)
end
end
function output_table (wp)
if not wp then return end
mcl_log("wp items: ".. tostring(#wp))
for a,b in pairs(wp) do
mcl_log(a.. ": ".. tostring(b))
end
end
function append_paths (wp1, wp2)
mcl_log("Start append")
if not wp1 or not wp2 then
mcl_log("Cannot append wp's")
return
end
output_table(wp1)
output_table(wp2)
for _,a in pairs (wp2) do
table.insert(wp1, a)
end
mcl_log("End append")
end
local function output_enriched (wp_out)
mcl_log("Output enriched path")
local i = 0
for _,outy in pairs (wp_out) do
i = i + 1
mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
local action = outy["action"]
if action then
mcl_log("type: " .. action["type"])
mcl_log("action: " .. action["action"])
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
end
mcl_log("failed attempts: " .. outy["failed_attempts"])
end
end
-- This function will take a list of paths, and enrich it with:
-- a var for failed attempts
-- an action, such as to open or close a door where we know that pos requires that action
local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
local wp_out = {}
for i, cur_pos in pairs(wp_in) do
local action = nil
local one_down = vector.new(0,-1,0)
local cur_pos_to_add = vector.add(cur_pos, one_down)
if door_open_pos and vector.equals (cur_pos, door_open_pos) then
mcl_log ("Door open match")
--action = {type = "door", action = "open"}
action = {}
action["type"] = "door"
action["action"] = "open"
action["target"] = cur_door_pos
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
mcl_log ("Door close match")
--action = {type = "door", action = "closed"}
action = {}
action["type"] = "door"
action["action"] = "close"
action["target"] = cur_door_pos
cur_pos_to_add = vector.add(cur_pos, one_down)
elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
mcl_log("Current door pos")
cur_pos_to_add = vector.add(cur_pos, one_down)
action = {}
action["type"] = "door"
action["action"] = "open"
action["target"] = cur_door_pos
else
cur_pos_to_add = cur_pos
--mcl_log ("Pos doesn't match")
end
wp_out[i] = {}
wp_out[i]["pos"] = cur_pos_to_add
wp_out[i]["failed_attempts"] = 0
wp_out[i]["action"] = action
--wp_out[i] = {"pos" = cur_pos, "failed_attempts" = 0, "action" = action}
--output_pos(cur_pos, i)
end
output_enriched(wp_out)
return wp_out
end
local plane_adjacents = {
vector.new(1,0,0),
vector.new(-1,0,0),
vector.new(0,0,1),
vector.new(0,0,-1),
}
-- This function is used to see if we can path. We could use to check a route, rather than making people move.
local function calculate_path_through_door (p, t, target)
-- target is the same as t, just 1 square difference. Maybe we don't need target
mcl_log("Plot route from mob: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t))
local enriched_path = nil
local cur_door_pos = nil
local pos_closest_to_door = nil
local other_side_of_door = nil
--Path to door first
local wp = minetest.find_path(p,t,150,1,4)
if not wp then
mcl_log("No direct path. Path through door")
-- This could improve. There could be multiple doors. Check you can path from door to target first.
local cur_door_pos = minetest.find_node_near(target,16,{"group:door"})
if cur_door_pos then
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
for _,v in pairs(plane_adjacents) do
pos_closest_to_door = vector.add(cur_door_pos,v)
local n = minetest.get_node(pos_closest_to_door)
if n.name == "air" then
wp = minetest.find_path(p,pos_closest_to_door,150,1,4)
if wp then
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
other_side_of_door = vector.add(cur_door_pos,-v)
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4)
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
table.insert(wp, cur_door_pos)
append_paths (wp, wp_otherside_door_to_target)
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
mcl_log("We have a path from outside door to target")
else
mcl_log("We cannot path from outside door to target")
end
break
else
mcl_log("This block next to door doesn't work.")
end
else
mcl_log("Block is not air, it is: ".. n.name)
end
end
else
mcl_log("No door found")
end
else
mcl_log("We have a direct route")
end
if wp and not enriched_path then
enriched_path = generate_enriched_path(wp)
end
return enriched_path
end
local gopath_last = os.time()
function mob_class:gopath(target,callback_arrived)
if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end
if self._pf_last_failed and (os.time() - self._pf_last_failed) < 30 then
mcl_log("We are not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
return
else
mcl_log("We are ready to pathfind, no previous fail or we are past threshold")
end
--if os.time() - gopath_last < 5 then
-- mcl_log("Not ready to path yet")
-- return
--end
--gopath_last = os.time()
self.order = nil
local p = self.object:get_pos()
local t = vector.offset(target,0,1,0)
local wp = calculate_path_through_door(p, t, target)
if not wp then
mcl_log("Could not calculate path")
self._pf_last_failed = os.time()
-- Cover for a flaw in pathfind where it chooses the wrong door and gets stuck. Take a break, allow others.
end
--output_table(wp)
if wp and #wp > 0 then
self._target = t
self.callback_arrived = callback_arrived
local current_location = table.remove(wp,1)
if current_location and current_location["pos"] then
mcl_log("Removing first co-ord? " .. tostring(current_location["pos"]))
else
mcl_log("Nil pos")
end
self.current_target = current_location
self.waypoints = wp
self.state = PATHFINDING
return true
else
self.state = "walk"
self.waypoints = nil
self.current_target = nil
-- minetest.log("no path found")
end
end
function mob_class:interact_with_door(action, target)
local p = self.object:get_pos()
--local t = minetest.get_timeofday()
--local dd = minetest.find_nodes_in_area(vector.offset(p,-1,-1,-1),vector.offset(p,1,1,1),{"group:door"})
--for _,d in pairs(dd) do
if target then
mcl_log("Door target is: ".. minetest.pos_to_string(target))
local n = minetest.get_node(target)
if n.name:find("_b_") or n.name:find("_t_") then
mcl_log("Door")
local def = minetest.registered_nodes[n.name]
local closed = n.name:find("_b_1") or n.name:find("_t_1")
--if self.state == PATHFINDING then
if closed and action == "open" and def.on_rightclick then
mcl_log("Open door")
def.on_rightclick(target,n,self)
end
if not closed and action == "close" and def.on_rightclick then
mcl_log("Close door")
def.on_rightclick(target,n,self)
end
--else
else
mcl_log("Not door")
end
else
mcl_log("no target. cannot try and open or close door")
end
--end
end
function mob_class:do_pathfind_action(action)
if action then
mcl_log("Action present")
local type = action["type"]
local action_val = action["action"]
local target = action["target"]
if target then
mcl_log("Target: ".. minetest.pos_to_string(target))
end
if type and type == "door" then
mcl_log("Type is door")
self:interact_with_door(action_val, target)
end
end
end
local gowp_etime = 0
function mob_class:check_gowp(dtime)
gowp_etime = gowp_etime + dtime
-- 0.1 is optimal.
--less frequently = villager will get sent back after passing a point.
--more frequently = villager will fail points they shouldn't they just didn't get there yet
--if gowp_etime < 0.05 then return end
--gowp_etime = 0
local p = self.object:get_pos()
-- no destination
if not p or not self._target then
mcl_log("p: ".. tostring(p))
mcl_log("self._target: ".. tostring(self._target))
return
end
-- arrived at location, finish gowp
local distance_to_targ = vector.distance(p,self._target)
--mcl_log("Distance to targ: ".. tostring(distance_to_targ))
if distance_to_targ < 2 then
mcl_log("Arrived at _target")
self.waypoints = nil
self._target = nil
self.current_target = nil
self.state = "stand"
self.order = "stand"
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
if self.callback_arrived then return self.callback_arrived(self) end
return true
end
-- More pathing to be done
local distance_to_current_target = 50
if self.current_target and self.current_target["pos"] then
distance_to_current_target = vector.distance(p,self.current_target["pos"])
end
-- 0.6 is working but too sensitive. sends villager back too frequently. 0.7 is quite good, but not with heights
-- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning
-- 0.9 and 1.0 is also good. Stick with unless door open or closing issues
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target["pos"] or distance_to_current_target < 0.9 ) then
-- We have waypoints, and no current target, or we're at it. We need a new current_target.
self:do_pathfind_action (self.current_target["action"])
local failed_attempts = self.current_target["failed_attempts"]
mcl_log("There after " .. failed_attempts .. " failed attempts. current target:".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: " .. distance_to_current_target)
self.current_target = table.remove(self.waypoints, 1)
self:go_to_pos(self.current_target["pos"])
return
elseif self.current_target and self.current_target["pos"] then
-- No waypoints left, but have current target. Potentially last waypoint to go to.
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
local failed_attempts = self.current_target["failed_attempts"]
if failed_attempts >= 50 then
mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts)
self.state = "stand"
self.current_target = nil
self.waypoints = nil
self._target = nil
self._pf_last_failed = os.time()
self.object:set_velocity({x = 0, y = 0, z = 0})
self.object:set_acceleration({x = 0, y = 0, z = 0})
return
end
--mcl_log("Not at pos with failed attempts ".. failed_attempts ..": ".. minetest.pos_to_string(p) .. "self.current_target: ".. minetest.pos_to_string(self.current_target["pos"]) .. ". Distance: ".. distance_to_current_target)
self:go_to_pos(self.current_target["pos"])
-- Do i just delete current_target, and return so we can find final path.
else
-- Not at target, no current waypoints or current_target. Through the door and should be able to path to target.
-- Is a little sensitive and could take 1 - 7 times. A 10 fail count might be a good exit condition.
mcl_log("We don't have waypoints or a current target. Let's try to path to target")
local final_wp = minetest.find_path(p,self._target,150,1,4)
if final_wp then
mcl_log("We might be able to get to target here.")
-- self.waypoints = final_wp
self:go_to_pos(self._target)
else
-- Abandon route?
mcl_log("Cannot plot final route to target")
end
end
-- I don't think we need the following anymore, but test first.
-- Maybe just need something to path to target if no waypoints left
if self.current_target and self.current_target["pos"] and (self.waypoints and #self.waypoints == 0) then
local updated_p = self.object:get_pos()
local distance_to_cur_targ = vector.distance(updated_p,self.current_target["pos"])
mcl_log("Distance to current target: ".. tostring(distance_to_cur_targ))
mcl_log("Current p: ".. minetest.pos_to_string(updated_p))
-- 1.6 is good. is 1.9 better? It could fail less, but will it path to door when it isn't after door
if distance_to_cur_targ > 1.9 then
mcl_log("not close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self:go_to_pos(self._current_target)
else
mcl_log("close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
self.current_target = nil
end
return
end
end

View File

@ -1,976 +0,0 @@
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -9.81*1.5
local FLOP_HEIGHT = 6
local FLOP_HOR_SPEED = 1.5
local PATHFINDING = "gowp"
local mobs_debug = minetest.settings:get_bool("mobs_debug", false)
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
local show_health = false
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
if not pos then return true end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
function mob_class:player_in_active_range()
for _,p in pairs(minetest.get_connected_players()) do
if vector.distance(self.object:get_pos(),p:get_pos()) <= mob_active_range then return true end
-- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning.
end
end
-- Return true if object is in view_range
function mob_class:object_in_range(object)
if not object then
return false
end
local factor
-- Apply view range reduction for special player armor
if object:is_player() then
local factors = mcl_armor.player_view_range_factors[object]
factor = factors and factors[self.name]
end
-- Distance check
local dist
if factor and factor == 0 then
return false
elseif factor then
dist = self.view_range * factor
else
dist = self.view_range
end
local p1, p2 = self.object:get_pos(), object:get_pos()
return p1 and p2 and (vector.distance(p1, p2) <= dist)
end
function mob_class:item_drop(cooked, looting_level)
if not mobs_drop_items then return end
looting_level = looting_level or 0
if (self.child and self.type ~= "monster") then
return
end
local obj, item, num
local pos = self.object:get_pos()
self.drops = self.drops or {}
for n = 1, #self.drops do
local dropdef = self.drops[n]
local chance = 1 / dropdef.chance
local looting_type = dropdef.looting
if looting_level > 0 then
local chance_function = dropdef.looting_chance_function
if chance_function then
chance = chance_function(looting_level)
elseif looting_type == "rare" then
chance = chance + (dropdef.looting_factor or 0.01) * looting_level
end
end
local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common")
if math.random() < chance then
num = math.random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then
do_common_looting = false
end
if do_common_looting then
num = num + math.floor(math.random(0, looting_level) + 0.5)
end
if num > 0 then
item = dropdef.name
if cooked then
local output = minetest.get_craft_result({
method = "cooking", width = 1, items = {item}})
if output and output.item and not output.item:is_empty() then
item = output.item:get_name()
end
end
for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then
obj:set_velocity({
x = math.random(-10, 10) / 9,
y = 6,
z = math.random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end
end
end
self.drops = {}
end
-- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return {0,0} end
local vel = self.object:get_velocity()
local x = 0
local z = 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
local ent = object:get_luaentity()
if object:is_player() or (ent and ent.is_mob and object ~= self.object) then
if object:is_player() and mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(object, 4)
end
local pos2 = object:get_pos()
local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
local force = (width + 0.5) - vector.distance(
{x = pos.x, y = 0, z = pos.z},
{x = pos2.x, y = 0, z = pos2.z})
x = x + (vec.x * force)
z = z + (vec.z * force)
end
end
return({x,z})
end
-- move mob in facing direction
function mob_class:set_velocity(v)
local c_x, c_y = 0, 0
-- can mob be pushed, if so calculate direction
if self.pushable then
c_x, c_y = unpack(self:collision())
end
-- halt mob if it has been ordered to stay
if self.order == "stand" or self.order == "sit" then
self.acc=vector.new(0,0,0)
return
end
local yaw = (self.object:get_yaw() or 0) + self.rotate
local vv = self.object:get_velocity()
if vv then
self.acc={
x = ((math.sin(yaw) * -v) + c_x)*.27,
y = 0,
z = ((math.cos(yaw) * v) + c_y)*.27,
}
end
end
-- calculate mob velocity
function mob_class:get_velocity()
local v = self.object:get_velocity()
if v then
return (v.x * v.x + v.z * v.z) ^ 0.5
end
return 0
end
function mob_class:update_roll()
local is_Fleckenstein = self.nametag == "Fleckenstein"
local was_Fleckenstein = false
local rot = self.object:get_rotation()
rot.z = is_Fleckenstein and pi or 0
self.object:set_rotation(rot)
local cbox = table.copy(self.collisionbox)
local acbox = self.object:get_properties().collisionbox
if math.abs(cbox[2] - acbox[2]) > 0.1 then
was_Fleckenstein = true
end
if is_Fleckenstein ~= was_Fleckenstein then
local pos = self.object:get_pos()
pos.y = pos.y + (acbox[2] + acbox[5])
self.object:set_pos(pos)
end
if is_Fleckenstein then
cbox[2], cbox[5] = -cbox[5], -cbox[2]
self.object:set_properties({collisionbox = cbox})
-- This leads to child mobs having the wrong collisionbox
-- and seeing as it seems to be nothing but an easter egg
-- i've put it inside the if. Which just makes it be upside
-- down lol.
end
end
local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums)
if not rot_origin or not rot_target then
return
end
rot_origin = math.deg(rot_origin)
rot_target = math.deg(rot_target)
if rot_origin < rot_target then
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return 1
end
else
if nums then
return -(rot_origin-(rot_target-360))
else
return -1
end
end
else
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return -1
end
else
if nums then
return (rot_target-(rot_origin-360))
else
return 1
end
end
end
end
-- set and return valid yaw
function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end
if self.state ~= PATHFINDING then
self._turn_to = yaw
end
--mcl_log("Yaw is: \t\t" .. tostring(math.deg(yaw)))
--mcl_log("self.object:get_yaw() is: \t" .. tostring(math.deg(self.object:get_yaw())))
--clamp our yaw to a 360 range
if math.deg(self.object:get_yaw()) > 360 then
self.object:set_yaw(math.rad(0))
elseif math.deg(self.object:get_yaw()) < 0 then
self.object:set_yaw(math.rad(360))
end
if math.deg(yaw) > 360 then
yaw=yaw%360
elseif math.deg(yaw) < 0 then
yaw=((360*5)-yaw)%360
end
--calculate the shortest way to turn to find our target
local target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, false)
local target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, true)
--turn in the shortest path possible toward our target. if we are attacking, don't dance.
if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then
if self.following then
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), false)
else
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), false)
end
end
local ddtime = 0.05 --set_tick_rate
if dtime then
ddtime = dtime
end
if math.abs(target_shortest_path_nums) > 5 then
self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime)))
if self.acc then
self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime))
end
end
delay = delay or 0
yaw = self.object:get_yaw()
if delay == 0 then
if self.shaking and dtime then
yaw = yaw + (math.random() * 2 - 1) * 5 * dtime
end
--self:update_roll()
return yaw
end
self.target_yaw = yaw
self.delay = delay
return self.target_yaw
end
-- global function to set mob yaw
function mcl_mobs.yaw(self, yaw, delay, dtime)
return mob_class.set_yaw(self, yaw, delay, dtime)
end
-- are we flying in what we are suppose to? (taikedz)
function mob_class:flight_check()
local nod = self.standing_in
local def = minetest.registered_nodes[nod]
if not def then return false end -- nil check
local fly_in
if type(self.fly_in) == "string" then
fly_in = { self.fly_in }
elseif type(self.fly_in) == "table" then
fly_in = self.fly_in
else
return false
end
for _,checknode in pairs(fly_in) do
if nod == checknode or nod == "ignore" then
return true
end
end
return false
end
-- check if mob is dead or only hurt
function mob_class:check_for_death(cause, cmi_cause)
if self.state == "die" then
return true
end
-- has health actually changed?
if self.health == self.old_health and self.health > 0 then
return false
end
local damaged = self.health < self.old_health
self.old_health = self.health
-- still got some health?
if self.health > 0 then
-- make sure health isn't higher than max
if self.health > self.hp_max then
self.health = self.hp_max
end
-- play damage sound if health was reduced and make mob flash red.
if damaged then
self:add_texture_mod("^[colorize:#d42222:175")
minetest.after(1, function(self)
if self and self.object then
self:remove_texture_mod("^[colorize:#d42222:175")
end
end, self)
self:mob_sound("damage")
end
-- backup nametag so we can show health stats
if not self.nametag2 then
self.nametag2 = self.nametag or ""
end
if show_health
and (cmi_cause and cmi_cause.type == "punch") then
self.htimer = 2
self.nametag = "" .. self.health .. " / " .. self.hp_max
self:update_tag()
end
return false
end
self:mob_sound("death")
local function death_handle(self)
-- dropped cooked item if mob died in fire or lava
if cause == "lava" or cause == "fire" then
self:item_drop(true, 0)
else
local wielditem = ItemStack()
if cause == "hit" then
local puncher = cmi_cause.puncher
if puncher then
wielditem = puncher:get_wielded_item()
end
end
local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect")
local looting = mcl_enchanting.get_enchantment(wielditem, "looting")
self:item_drop(cooked, looting)
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
mcl_experience.throw_xp(self.object:get_pos(), math.random(self.xp_min, self.xp_max))
end
end
end
-- execute custom death function
if self.on_die then
local pos = self.object:get_pos()
local on_die_exit = self.on_die(self, pos, cmi_cause)
if on_die_exit ~= true then
death_handle(self)
end
if on_die_exit == true then
self.state = "die"
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
end
local collisionbox
if self.collisionbox then
collisionbox = table.copy(self.collisionbox)
end
self.state = "die"
self.attack = nil
self.v_start = false
self.fall_speed = DEFAULT_FALL_SPEED
self.timer = 0
self.blinktimer = 0
self:remove_texture_mod("^[colorize:#FF000040")
self:remove_texture_mod("^[brighten")
self.passive = true
self.object:set_properties({
pointable = false,
collide_with_objects = false,
})
self:set_velocity(0)
local acc = self.object:get_acceleration()
acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0
self.object:set_acceleration(acc)
local length
-- default death function and die animation (if defined)
if self.instant_death then
length = 0
elseif self.animation
and self.animation.die_start
and self.animation.die_end then
local frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15
length = math.max(frames / speed, 0) + DEATH_DELAY
self:set_animation( "die")
else
length = 1 + DEATH_DELAY
self:set_animation( "stand", true)
end
-- Remove body after a few seconds and drop stuff
local kill = function(self)
if not self.object:get_luaentity() then
return
end
death_handle(self)
local dpos = self.object:get_pos()
local cbox = self.collisionbox
local yaw = self.object:get_rotation().y
mcl_burning.extinguish(self.object)
self.object:remove()
mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death)
end
if length <= 0 then
kill(self)
else
minetest.after(length, kill, self)
end
return true
end
-- Deal light damage to mob, returns true if mob died
function mob_class:deal_light_damage(pos, damage)
if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
self.health = self.health - damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("light", {type = "light"}) then
return true
end
end
end
-- environmental damage (water, lava, fire, light etc.)
function mob_class:do_env_damage()
-- feed/tame text timer (so mob 'full' messages dont spam chat)
if self.htimer > 0 then
self.htimer = self.htimer - 1
end
-- reset nametag after showing health stats
if self.htimer < 1 and self.nametag2 then
self.nametag = self.nametag2
self.nametag2 = nil
self:update_tag()
end
local pos = self.object:get_pos()
if not pos then return end
self.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits
if not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
local sunlight = minetest.get_natural_light(pos, self.time_of_day)
-- bright light harms mob
if self.light_damage ~= 0 and (sunlight or 0) > 12 then
if self:deal_light_damage(pos, self.light_damage) then
return true
end
end
local _, dim = mcl_worlds.y_to_layer(pos.y)
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
else
self:deal_light_damage(pos, self.sunlight_damage)
return true
end
end
end
local y_level = self.collisionbox[2]
if self.child then
y_level = self.collisionbox[2] * 0.5
end
-- what is mob standing in?
pos.y = pos.y + y_level + 0.25 -- foot level
local pos2 = {x=pos.x, y=pos.y-1, z=pos.z}
self.standing_in = node_ok(pos, "air").name
self.standing_on = node_ok(pos2, "air").name
-- don't fall when on ignore, just stand still
if self.standing_in == "ignore" then
self.object:set_velocity({x = 0, y = 0, z = 0})
end
local nodef = minetest.registered_nodes[self.standing_in]
-- rain
if self.rain_damage > 0 then
if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
self.health = self.health - self.rain_damage
if self:check_for_death("rain", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
end
pos.y = pos.y + 1 -- for particle effect position
-- water damage
if self.water_damage > 0
and nodef.groups.water then
if self.water_damage ~= 0 then
self.health = self.health - self.water_damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil)
if self:check_for_death("water", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- lava damage
elseif self.lava_damage > 0
and (nodef.groups.lava) then
if self.lava_damage ~= 0 then
self.health = self.health - self.lava_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 10)
if self:check_for_death("lava", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- fire damage
elseif self.fire_damage > 0
and (nodef.groups.fire) then
if self.fire_damage ~= 0 then
self.health = self.health - self.fire_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 5)
if self:check_for_death("fire", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- damage_per_second node check
elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then
self.health = self.health - nodef.damage_per_second
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("dps", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- Drowning damage
if self.breath_max ~= -1 then
local drowning = false
if self.breathes_in_water then
if minetest.get_item_group(self.standing_in, "water") == 0 then
drowning = true
end
elseif nodef.drowning > 0 then
drowning = true
end
if drowning then
self.breath = math.max(0, self.breath - 1)
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
if self.breath <= 0 then
local dmg
if nodef.drowning > 0 then
dmg = nodef.drowning
else
dmg = 4
end
self:damage_effect(dmg)
self.health = self.health - dmg
end
if self:check_for_death("drowning", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
else
self.breath = math.min(self.breath_max, self.breath + 1)
end
end
--- suffocation inside solid node
-- FIXME: Redundant with mcl_playerplus
if (self.suffocation == true)
and (nodef.walkable == nil or nodef.walkable == true)
and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
and (nodef.node_box == nil or nodef.node_box.type == "regular")
and (nodef.groups.disable_suffocation ~= 1)
and (nodef.groups.opaque == 1) then
-- Short grace period before starting to take suffocation damage.
-- This is different from players, who take damage instantly.
-- This has been done because mobs might briefly be inside solid nodes
-- when e.g. climbing up stairs.
-- This is a bit hacky because it assumes that do_env_damage
-- is called roughly every second only.
self.suffocation_timer = self.suffocation_timer + 1
if self.suffocation_timer >= 3 then
-- 2 damage per second
-- TODO: Deal this damage once every 1/2 second
self.health = self.health - 2
if self:check_for_death("suffocation", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
else
self.suffocation_timer = 0
end
return self:check_for_death("", {type = "unknown"})
end
function mob_class:damage_mob(reason,damage)
if not self.health then return end
damage = math.floor(damage)
if damage > 0 then
self.health = self.health - damage
mcl_mobs.effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil)
if self:check_for_death(reason, {type = reason}) then
return true
end
end
end
function mob_class:check_entity_cramming()
local p = self.object:get_pos()
if not p then return end
local oo = minetest.get_objects_inside_radius(p,1)
local mobs = {}
for _,o in pairs(oo) do
local l = o:get_luaentity()
if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end
end
local clear = #mobs < ENTITY_CRAMMING_MAX
local ncram = {}
for _,l in pairs(mobs) do
if l then
if clear then
l.cram = nil
elseif l.cram == nil and not self.child then
table.insert(ncram,l)
elseif l.cram then
l:damage_mob("cramming",CRAMMING_DAMAGE)
end
end
end
for i,l in pairs(ncram) do
if i > ENTITY_CRAMMING_MAX then
l.cram = true
else
l.cram = nil
end
end
end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos)
if self.fly and self.state ~= "die" then
return
end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end
if mcl_portals ~= nil then
if mcl_portals.nether_portal_cooloff(self.object) then
return false -- mob has teleported through Nether portal - it's 99% not falling
end
end
-- floating in water (or falling)
local v = self.object:get_velocity()
if v.y > 0 then
-- apply gravity when moving up
self.object:set_acceleration({
x = 0,
y = DEFAULT_FALL_SPEED,
z = 0
})
elseif v.y <= 0 and v.y > self.fall_speed then
-- fall downwards at set speed
self.object:set_acceleration({
x = 0,
y = self.fall_speed,
z = 0
})
else
-- stop accelerating once max fall speed hit
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
if minetest.registered_nodes[node_ok(pos).name].groups.lava then
if self.floats_on_lava == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
end
-- in water then float up
if minetest.registered_nodes[node_ok(pos).name].groups.water then
if self.floats == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
else
-- fall damage onto solid ground
if self.fall_damage == 1
and self.object:get_velocity().y == 0 then
local n = node_ok(vector.offset(pos,0,-1,0)).name
local d = (self.old_y or 0) - self.object:get_pos().y
if d > 5 and n ~= "air" and n ~= "ignore" then
local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent")
local damage = d - 5
if add ~= 0 then
damage = damage + damage * (add/100)
end
self:damage_mob("fall",damage)
end
self.old_y = self.object:get_pos().y
end
end
end
function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity
local p, node, nn, def
p = self.object:get_pos()
node = minetest.get_node_or_nil(p)
if node then
nn = node.name
def = minetest.registered_nodes[nn]
end
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
local vec = flowlib.quick_flow(p, node)
-- Just to make sure we don't manipulate the speed for no reason
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
local f = 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z})
self.physical_state = true
self._flowing = true
self.object:set_properties({
physical = true
})
return
end
elseif self._flowing == true then
-- Disable flowing physics if not on/in flowing liquid
self._flowing = false
return
end
end
function mob_class:check_dying()
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
local rot = self.object:get_rotation()
rot.z = ((math.pi/2-rot.z)*.2)+rot.z
self.object:set_rotation(rot)
return true
end
end
function mob_class:check_suspend()
if not self:player_in_active_range() then
local pos = self.object:get_pos()
local node_under = node_ok(vector.offset(pos,0,-1,0)).name
local acc = self.object:get_acceleration()
self:set_animation( "stand", true)
if acc.y > 0 or node_under ~= "air" then
self.object:set_acceleration(vector.new(0,0,0))
self.object:set_velocity(vector.new(0,0,0))
end
if acc.y == 0 and node_under == "air" then
self:falling(pos)
end
return true
end
end

View File

@ -0,0 +1,4 @@
default_punch.1 = https://freesound.org/people/Merrick079/sounds/566436/
default_punch.2 = https://freesound.org/people/Merrick079/sounds/566435/
default_punch.3 = https://freesound.org/people/Merrick079/sounds/566434/

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,764 +0,0 @@
--lua locals
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local get_node = minetest.get_node
local get_item_group = minetest.get_item_group
local get_node_light = minetest.get_node_light
local find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
local get_biome_name = minetest.get_biome_name
local get_objects_inside_radius = minetest.get_objects_inside_radius
local get_connected_players = minetest.get_connected_players
local minetest_get_perlin = minetest.get_perlin
local math_random = math.random
local math_floor = math.floor
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 vector_distance = vector.distance
local vector_new = vector.new
local vector_floor = vector.floor
local table_copy = table.copy
local table_remove = table.remove
local pairs = pairs
local dbg_spawn_attempts = 0
local dbg_spawn_succ = 0
local dbg_spawn_counts = {}
-- range for mob count
local aoc_range = 136
local remove_far = true
local mob_cap = {
monster = tonumber(minetest.settings:get("mcl_mob_cap_monster")) or 70,
animal = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10,
ambient = tonumber(minetest.settings:get("mcl_mob_cap_ambient")) or 15,
water = tonumber(minetest.settings:get("mcl_mob_cap_water")) or 5, --currently unused
water_ambient = tonumber(minetest.settings:get("mcl_mob_cap_water_ambient")) or 20, --currently unused
player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75,
total = tonumber(minetest.settings:get("mcl_mob_cap_total")) or 500,
}
--do mobs spawn?
local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
local logging = minetest.settings:get_bool("mcl_logging_mobs_spawn",true)
local noise_params = {
offset = 0,
scale = 3,
spread = {
x = 301,
y = 50,
z = 304,
},
seed = 100,
octaves = 3,
persistence = 0.5,
}
-- THIS IS THE BIG LIST OF ALL BIOMES - used for programming/updating mobs
-- Also used for missing parameter
-- Please update the list when adding new biomes!
local list_of_all_biomes = {
-- underground:
"FlowerForest_underground",
"JungleEdge_underground",
"ColdTaiga_underground",
"IcePlains_underground",
"IcePlainsSpikes_underground",
"MegaTaiga_underground",
"Taiga_underground",
"ExtremeHills+_underground",
"JungleM_underground",
"ExtremeHillsM_underground",
"JungleEdgeM_underground",
"MangroveSwamp_underground",
-- ocean:
"RoofedForest_ocean",
"JungleEdgeM_ocean",
"BirchForestM_ocean",
"BirchForest_ocean",
"IcePlains_deep_ocean",
"Jungle_deep_ocean",
"Savanna_ocean",
"MesaPlateauF_ocean",
"ExtremeHillsM_deep_ocean",
"Savanna_deep_ocean",
"SunflowerPlains_ocean",
"Swampland_deep_ocean",
"Swampland_ocean",
"MegaSpruceTaiga_deep_ocean",
"ExtremeHillsM_ocean",
"JungleEdgeM_deep_ocean",
"SunflowerPlains_deep_ocean",
"BirchForest_deep_ocean",
"IcePlainsSpikes_ocean",
"Mesa_ocean",
"StoneBeach_ocean",
"Plains_deep_ocean",
"JungleEdge_deep_ocean",
"SavannaM_deep_ocean",
"Desert_deep_ocean",
"Mesa_deep_ocean",
"ColdTaiga_deep_ocean",
"Plains_ocean",
"MesaPlateauFM_ocean",
"Forest_deep_ocean",
"JungleM_deep_ocean",
"FlowerForest_deep_ocean",
"MushroomIsland_ocean",
"MegaTaiga_ocean",
"StoneBeach_deep_ocean",
"IcePlainsSpikes_deep_ocean",
"ColdTaiga_ocean",
"SavannaM_ocean",
"MesaPlateauF_deep_ocean",
"MesaBryce_deep_ocean",
"ExtremeHills+_deep_ocean",
"ExtremeHills_ocean",
"MushroomIsland_deep_ocean",
"Forest_ocean",
"MegaTaiga_deep_ocean",
"JungleEdge_ocean",
"MesaBryce_ocean",
"MegaSpruceTaiga_ocean",
"ExtremeHills+_ocean",
"Jungle_ocean",
"RoofedForest_deep_ocean",
"IcePlains_ocean",
"FlowerForest_ocean",
"ExtremeHills_deep_ocean",
"MesaPlateauFM_deep_ocean",
"Desert_ocean",
"Taiga_ocean",
"BirchForestM_deep_ocean",
"Taiga_deep_ocean",
"JungleM_ocean",
"MangroveSwamp_ocean",
"MangroveSwamp_deep_ocean",
-- water or beach?
"MesaPlateauFM_sandlevel",
"MesaPlateauF_sandlevel",
"MesaBryce_sandlevel",
"Mesa_sandlevel",
-- beach:
"FlowerForest_beach",
"Forest_beach",
"StoneBeach",
"ColdTaiga_beach_water",
"Taiga_beach",
"Savanna_beach",
"Plains_beach",
"ExtremeHills_beach",
"ColdTaiga_beach",
"Swampland_shore",
"MushroomIslandShore",
"JungleM_shore",
"Jungle_shore",
"MangroveSwamp_shore",
-- dimension biome:
"Nether",
"BasaltDelta",
"CrimsonForest",
"WarpedForest",
"SoulsandValley",
"End",
-- Overworld regular:
"Mesa",
"FlowerForest",
"Swampland",
"Taiga",
"ExtremeHills",
"ExtremeHillsM",
"ExtremeHills+_snowtop",
"Jungle",
"Savanna",
"BirchForest",
"MegaSpruceTaiga",
"MegaTaiga",
"ExtremeHills+",
"Forest",
"Plains",
"Desert",
"ColdTaiga",
"MushroomIsland",
"IcePlainsSpikes",
"SunflowerPlains",
"IcePlains",
"RoofedForest",
"ExtremeHills+_snowtop",
"MesaPlateauFM_grasstop",
"JungleEdgeM",
"JungleM",
"BirchForestM",
"MesaPlateauF",
"MesaPlateauFM",
"MesaPlateauF_grasstop",
"MesaBryce",
"JungleEdge",
"SavannaM",
"MangroveSwamp",
}
-- count how many mobs are in an area
local function count_mobs(pos,r,mob_type)
local num = 0
for _,l in pairs(minetest.luaentities) do
if l and l.is_mob and (mob_type == nil or l.type == mob_type) then
local p = l.object:get_pos()
if p and vector_distance(p,pos) < r then
num = num + 1
end
end
end
return num
end
local function count_mobs_total(mob_type)
local num = 0
for _,l in pairs(minetest.luaentities) do
if l.is_mob then
if mob_type == nil or l.type == mob_type then
num = num + 1
end
end
end
return num
end
local function count_mobs_total_cap(mob_type)
local num = 0
for _,l in pairs(minetest.luaentities) do
if l.is_mob then
if ( mob_type == nil or l.type == mob_type ) and l.can_despawn and not l.nametag then
num = num + 1
end
end
end
return num
end
-- global functions
function mcl_mobs:spawn_abm_check(pos, node, name)
-- global function to add additional spawn checks
-- return true to stop spawning mob
end
--[[
Custom elements changed:
name:
the mobs name
dimension:
"overworld"
"nether"
"end"
types of spawning:
"water"
"ground"
"lava"
biomes: tells the spawner to allow certain mobs to spawn in certain biomes
{"this", "that", "grasslands", "whatever"}
what is aoc??? objects in area
WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua??
]]--
--this is where all of the spawning information is kept
local spawn_dictionary = {}
local summary_chance = 0
function mcl_mobs:spawn_setup(def)
if not mobs_spawn then return end
if not def then
minetest.log("warning", "Empty mob spawn setup definition")
return
end
local name = def.name
if not name then
minetest.log("warning", "Missing mob name")
return
end
local dimension = def.dimension or "overworld"
local type_of_spawning = def.type_of_spawning or "ground"
local biomes = def.biomes or list_of_all_biomes
local min_light = def.min_light or 0
local max_light = def.max_light or (minetest.LIGHT_MAX + 1)
local chance = def.chance or 1000
local aoc = def.aoc or aoc_range
local min_height = def.min_height or mcl_mapgen.overworld.min
local max_height = def.max_height or mcl_mapgen.overworld.max
local day_toggle = def.day_toggle
local on_spawn = def.on_spawn
local check_position = def.check_position
-- chance/spawn number override in minetest.conf for registered mob
local numbers = minetest.settings:get(name)
if numbers then
numbers = numbers:split(",")
chance = tonumber(numbers[1]) or chance
aoc = tonumber(numbers[2]) or aoc
if chance == 0 then
minetest.log("warning", string.format("[mcl_mobs] %s has spawning disabled", name))
return
end
minetest.log("action", string.format("[mcl_mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
end
if chance < 1 then
chance = 1
minetest.log("warning", "Chance shouldn't be less than 1 (mob name: " .. name ..")")
end
spawn_dictionary[#spawn_dictionary + 1] = {
name = name,
dimension = dimension,
type_of_spawning = type_of_spawning,
biomes = biomes,
min_light = min_light,
max_light = max_light,
chance = chance,
aoc = aoc,
min_height = min_height,
max_height = max_height,
day_toggle = day_toggle,
check_position = check_position,
on_spawn = on_spawn,
}
summary_chance = summary_chance + chance
end
function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
-- Do mobs spawn at all?
if not mobs_spawn then
return
end
-- chance/spawn number override in minetest.conf for registered mob
local numbers = minetest.settings:get(name)
if numbers then
numbers = numbers:split(",")
chance = tonumber(numbers[1]) or chance
aoc = tonumber(numbers[2]) or aoc
if chance == 0 then
minetest.log("warning", string.format("[mcl_mobs] %s has spawning disabled", name))
return
end
minetest.log("action", string.format("[mcl_mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
end
--load information into the spawn dictionary
local key = #spawn_dictionary + 1
spawn_dictionary[key] = {}
spawn_dictionary[key]["name"] = name
spawn_dictionary[key]["dimension"] = dimension
spawn_dictionary[key]["type_of_spawning"] = type_of_spawning
spawn_dictionary[key]["biomes"] = biomes
spawn_dictionary[key]["min_light"] = min_light
spawn_dictionary[key]["max_light"] = max_light
spawn_dictionary[key]["chance"] = chance
spawn_dictionary[key]["aoc"] = aoc
spawn_dictionary[key]["min_height"] = min_height
spawn_dictionary[key]["max_height"] = max_height
spawn_dictionary[key]["day_toggle"] = day_toggle
summary_chance = summary_chance + chance
end
local two_pi = 2 * math.pi
local function get_next_mob_spawn_pos(pos)
local distance = math_random(25, 32)
local angle = math_random() * two_pi
return {
x = math_round(pos.x + distance * math_cos(angle)),
y = pos.y,
z = math_round(pos.z + distance * math_sin(angle))
}
end
local function decypher_limits(posy)
posy = math_floor(posy)
return posy - 32, posy + 32
end
--a simple helper function for mob_spawn
local function biome_check(biome_list, biome_goal)
for _, data in pairs(biome_list) do
if data == biome_goal then
return true
end
end
return false
end
local function is_farm_animal(n)
return n == "mobs_mc:pig" or n == "mobs_mc:cow" or n == "mobs_mc:sheep" or n == "mobs_mc:chicken" or n == "mobs_mc:horse" or n == "mobs_mc:donkey"
end
local function get_water_spawn(p)
local nn = minetest.find_nodes_in_area(vector.offset(p,-2,-1,-2),vector.offset(p,2,-15,2),{"group:water"})
if nn and #nn > 0 then
return nn[math.random(#nn)]
end
end
local function has_room(self,pos)
local cb = self.collisionbox
local nodes = {}
if self.fly_in then
local t = type(self.fly_in)
if t == "table" then
nodes = table.copy(self.fly_in)
elseif t == "string" then
table.insert(nodes,self.fly_in)
end
end
table.insert(nodes,"air")
local x = cb[4] - cb[1]
local y = cb[5] - cb[2]
local z = cb[6] - cb[3]
local r = math.ceil(x * y * z)
local p1 = vector.offset(pos,cb[1],cb[2],cb[3])
local p2 = vector.offset(pos,cb[4],cb[5],cb[6])
local n = #minetest.find_nodes_in_area(p1,p2,nodes) or 0
if r > n then
minetest.log("warning","[mcl_mobs] No room for mob "..self.name.." at "..minetest.pos_to_string(vector.round(pos)))
return false
end
return true
end
local function spawn_check(pos,spawn_def,ignore_caps)
if not spawn_def then return end
dbg_spawn_attempts = dbg_spawn_attempts + 1
local dimension = mcl_worlds.pos_to_dimension(pos)
local mob_def = minetest.registered_entities[spawn_def.name]
local mob_type = mob_def.type
local gotten_node = get_node(pos).name
local gotten_biome = minetest.get_biome_data(pos)
if not gotten_node or not gotten_biome then return end
gotten_biome = get_biome_name(gotten_biome.biome) --makes it easier to work with
local is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
if not is_ground then
pos.y = pos.y - 1
gotten_node = get_node(pos).name
is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
end
pos.y = pos.y + 1
local is_water = get_item_group(gotten_node, "water") ~= 0
local is_lava = get_item_group(gotten_node, "lava") ~= 0
local is_leaf = get_item_group(gotten_node, "leaves") ~= 0
local is_bedrock = gotten_node == "mcl_core:bedrock"
local is_grass = minetest.get_item_group(gotten_node,"grass_block") ~= 0
local mob_count_wide = 0
local mob_count = 0
if not ignore_caps then
mob_count = count_mobs(pos,32,mob_type)
mob_count_wide = count_mobs(pos,aoc_range,mob_type)
end
if pos and spawn_def
and ( mob_count_wide < (mob_cap[mob_type] or 15) )
and ( mob_count < 5 )
and pos.y >= spawn_def.min_height
and pos.y <= spawn_def.max_height
and spawn_def.dimension == dimension
and biome_check(spawn_def.biomes, gotten_biome)
and (is_ground or spawn_def.type_of_spawning ~= "ground")
and (spawn_def.type_of_spawning ~= "ground" or not is_leaf)
and has_room(mob_def,pos)
and (spawn_def.check_position and spawn_def.check_position(pos) or true)
and (not is_farm_animal(spawn_def.name) or is_grass)
and (spawn_def.type_of_spawning ~= "water" or is_water)
and ( not spawn_protected or not minetest.is_protected(pos, "") )
and not is_bedrock then
--only need to poll for node light if everything else worked
local gotten_light = get_node_light(pos)
if gotten_light >= spawn_def.min_light and gotten_light <= spawn_def.max_light then
return true
end
end
return false
end
function mcl_mobs.spawn(pos,id)
local def = minetest.registered_entities[id] or minetest.registered_entities["mobs_mc:"..id] or minetest.registered_entities["extra_mobs:"..id]
if not def or (def.can_spawn and not def.can_spawn(pos)) or not def.is_mob then
return false
end
if not dbg_spawn_counts[def.name] then
dbg_spawn_counts[def.name] = 1
else
dbg_spawn_counts[def.name] = dbg_spawn_counts[def.name] + 1
end
return minetest.add_entity(pos, def.name)
end
local function spawn_group(p,mob,spawn_on,group_max,group_min)
if not group_min then group_min = 1 end
local nn= minetest.find_nodes_in_area_under_air(vector.offset(p,-5,-3,-5),vector.offset(p,5,3,5),spawn_on)
local o
table.shuffle(nn)
if not nn or #nn < 1 then
nn = {}
table.insert(nn,p)
end
for i = 1, math.random(group_min,group_max) do
local sp = vector.offset(nn[math.random(#nn)],0,1,0)
if spawn_check(nn[math.random(#nn)],mob,true) then
if mob.type_of_spawning == "water" then
sp = get_water_spawn(sp)
end
o = mcl_mobs.spawn(sp,mob.name)
if o then dbg_spawn_succ = dbg_spawn_succ + 1 end
end
end
return o
end
mcl_mobs.spawn_group = spawn_group
local S = minetest.get_translator("mcl_mobs")
minetest.register_chatcommand("spawn_mob",{
privs = { debug = true },
description=S("spawn_mob is a chatcommand that allows you to type in the name of a mob without 'typing mobs_mc:' all the time like so; 'spawn_mob spider'. however, there is more you can do with this special command, currently you can edit any number, boolian, and string variable you choose with this format: spawn_mob 'any_mob:var<mobs_variable=variable_value>:'. any_mob being your mob of choice, mobs_variable being the variable, and variable value being the value of the chosen variable. and example of this format: \n spawn_mob skeleton:var<passive=true>:\n this would spawn a skeleton that wouldn't attack you. REMEMBER-THIS> when changing a number value always prefix it with 'NUM', example: \n spawn_mob skeleton:var<jump_height=NUM10>:\n this setting the skelly's jump height to 10. if you want to make multiple changes to a mob, you can, example: \n spawn_mob skeleton:var<passive=true>::var<jump_height=NUM10>::var<fly_in=air>::var<fly=true>:\n etc."),
func = function(n,param)
local pos = minetest.get_player_by_name(n):get_pos()
local modifiers = {}
for capture in string.gmatch(param, "%:(.-)%:") do
table.insert(modifiers, ":"..capture)
end
local mod1 = string.find(param, ":")
local mobname = param
if mod1 then
mobname = string.sub(param, 1, mod1-1)
end
local mob = mcl_mobs.spawn(pos,mobname)
for c=1, #modifiers do
modifs = modifiers[c]
local mod1 = string.find(modifs, ":")
local mod_start = string.find(modifs, "<")
local mod_vals = string.find(modifs, "=")
local mod_end = string.find(modifs, ">")
local mod_end = string.find(modifs, ">")
if mob then
local mob_entity = mob:get_luaentity()
if string.sub(modifs, mod1+1, mod1+3) == "var" then
if mod1 and mod_start and mod_vals and mod_end then
local variable = string.sub(modifs, mod_start+1, mod_vals-1)
local value = string.sub(modifs, mod_vals+1, mod_end-1)
number_tag = string.find(value, "NUM")
if number_tag then
value = tonumber(string.sub(value, 4, -1))
end
if value == "true" then
value = true
elseif value == "false" then
value = false
end
if not mob_entity[variable] then
minetest.log("warning", n.." mob variable "..variable.." previously unset")
end
mob_entity[variable] = value
else
minetest.log("warning", n.." couldn't modify "..mobname.." at "..minetest.pos_to_string(pos).. ", missing paramaters")
end
else
minetest.log("warning", n.." couldn't modify "..mobname.." at "..minetest.pos_to_string(pos).. ", missing modification type")
end
end
end
if mob then
return true, mobname.." spawned at "..minetest.pos_to_string(pos),
minetest.log("action", n.." spawned "..mobname.." at "..minetest.pos_to_string(pos))
end
return false, "Couldn't spawn "..mobname
end
})
if mobs_spawn then
local perlin_noise
local function spawn_a_mob(pos, dimension, y_min, y_max)
--create a disconnected clone of the spawn dictionary
--prevents memory leak
local mob_library_worker_table = table_copy(spawn_dictionary)
local goal_pos = get_next_mob_spawn_pos(pos)
--grab mob that fits into the spawning location
--randomly grab a mob, don't exclude any possibilities
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 return end
local spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
perlin_noise = perlin_noise or minetest_get_perlin(noise_params)
local noise = perlin_noise:get_3d(spawning_position)
local current_summary_chance = summary_chance
table.shuffle(mob_library_worker_table)
while #mob_library_worker_table > 0 do
local mob_chance_offset = (math_round(noise * current_summary_chance + 12345) % current_summary_chance) + 1
local mob_index = 1
local mob_chance = mob_library_worker_table[mob_index].chance
local step_chance = mob_chance
while step_chance < mob_chance_offset do
mob_index = mob_index + 1
mob_chance = mob_library_worker_table[mob_index].chance
step_chance = step_chance + mob_chance
end
local mob_def = mob_library_worker_table[mob_index]
--minetest.log(mob_def.name.." "..step_chance.. " "..mob_chance)
if mob_def and mob_def.name and minetest.registered_entities[mob_def.name] then
local spawn_in_group = minetest.registered_entities[mob_def.name].spawn_in_group or 4
local spawn_in_group_min = minetest.registered_entities[mob_def.name].spawn_in_group_min or 1
local mob_type = minetest.registered_entities[mob_def.name].type
if spawn_check(spawning_position,mob_def) then
if mob_def.type_of_spawning == "water" then
spawning_position = get_water_spawn(spawning_position)
if not spawning_position then
minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos)))
return
end
end
if minetest.registered_entities[mob_def.name].can_spawn and not minetest.registered_entities[mob_def.name].can_spawn(spawning_position) then
minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position)))
return
end
--everything is correct, spawn mob
local object
if spawn_in_group and ( mob_type ~= "monster" or math.random(5) == 1 ) then
if logging then
minetest.log("action", "[mcl_mobs] A group of mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1))
end
object = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name},spawn_in_group,spawn_in_group_min)
else
if logging then
minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at ".. minetest.pos_to_string(spawning_position, 1))
end
object = mcl_mobs.spawn(spawning_position, mob_def.name)
end
end
end
current_summary_chance = current_summary_chance - mob_chance
table_remove(mob_library_worker_table, mob_index)
end
end
--MAIN LOOP
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < 10 then return end
timer = 0
local players = get_connected_players()
local total_mobs = count_mobs_total_cap()
if total_mobs > mob_cap.total or total_mobs > #players * mob_cap.player then
minetest.log("action","[mcl_mobs] global mob cap reached. no cycle spawning.")
return
end --mob cap per player
for _, player in pairs(players) do
local pos = player:get_pos()
local dimension = mcl_worlds.pos_to_dimension(pos)
-- ignore void and unloaded area
if dimension ~= "void" and dimension ~= "default" then
local y_min, y_max = decypher_limits(pos.y)
spawn_a_mob(pos, dimension, y_min, y_max)
end
end
end)
end
function mob_class:check_despawn(pos)
-- Despawning: when lifetimer expires, remove mob
if remove_far
and self.can_despawn == true
and ((not self.nametag) or (self.nametag == ""))
and self.state ~= "attack"
and self.following == nil then
if self.despawn_immediately or self.lifetimer <= 0 then
if logging then
minetest.log("action", "[mcl_mobs] Mob "..self.name.." despawns at "..minetest.pos_to_string(pos, 1) .. " lifetimer ran out")
end
mcl_burning.extinguish(self.object)
self.object:remove()
return true
elseif self.lifetimer <= 10 then
if math.random(10) < 4 then
self.despawn_immediately = true
else
self.lifetimer = 20
end
end
end
end
minetest.register_chatcommand("mobstats",{
privs = { debug = true },
func = function(n,param)
minetest.chat_send_player(n,dump(dbg_spawn_counts))
local pos = minetest.get_player_by_name(n):get_pos()
minetest.chat_send_player(n,"mobs within 32 radius of player:"..count_mobs(pos,32))
minetest.chat_send_player(n,"total mobs:"..count_mobs_total())
minetest.chat_send_player(n,"spawning attempts since server start:"..dbg_spawn_attempts)
minetest.chat_send_player(n,"successful spawns since server start:"..dbg_spawn_succ)
end
})

View File

@ -0,0 +1 @@
--use vector.distance to count down mob despawn timer

View File

@ -1,2 +0,0 @@
# textdomain:mcl_paintings
Painting=絵画

View File

@ -1,60 +0,0 @@
local dim = {"x", "z"}
local modpath = minetest.get_modpath(minetest.get_current_modname())
local function load_schem(filename)
local file = io.open(modpath .. "/schems/" .. filename, "r")
local data = minetest.deserialize(file:read())
file:close()
return data
end
local wither_spawn_schems = {}
for _, d in pairs(dim) do
wither_spawn_schems[d] = load_schem("wither_spawn_" .. d .. ".we")
end
local function check_schem(pos, schem)
for _, n in pairs(schem) do
if minetest.get_node(vector.add(pos, n)).name ~= n.name then
return false
end
end
return true
end
local function remove_schem(pos, schem)
for _, n in pairs(schem) do
minetest.remove_node(vector.add(pos, n))
end
end
local function wither_spawn(pos)
for _, d in pairs(dim) do
for i = 0, 2 do
local p = vector.add(pos, {x = 0, y = -2, z = 0, [d] = -i})
local schem = wither_spawn_schems[d]
if check_schem(p, schem) then
remove_schem(p, schem)
minetest.add_entity(vector.add(p, {x = 0, y = 1, z = 0, [d] = 1}), "mobs_mc:wither")
local objects = minetest.get_objects_inside_radius(pos, 20)
for _, players in ipairs(objects) do
if players:is_player() then
awards.unlock(players:get_player_name(), "mcl:witheringHeights")
end
end
end
end
end
end
local wither_head = minetest.registered_nodes["mcl_heads:wither_skeleton"]
local old_on_place = wither_head.on_place
function wither_head.on_place(itemstack, placer, pointed)
local n = minetest.get_node(vector.offset(pointed.above,0,-1,0))
if n and n.name == "mcl_nether:soul_sand" then
minetest.after(0, wither_spawn, pointed.above)
end
return old_on_place(itemstack, placer, pointed)
end

View File

@ -1,4 +0,0 @@
name = mcl_wither_spawning
description = Wither Spawning for MineClone2
author = Fleckenstein
depends = mobs_mc, mcl_heads

View File

@ -1 +0,0 @@
return {{["y"] = 1, ["x"] = 0, ["name"] = "mcl_nether:soul_sand", ["z"] = 0}, {["y"] = 2, ["x"] = 0, ["name"] = "mcl_heads:wither_skeleton", ["z"] = 0, ["param2"] = 2, ["param1"] = 15}, {["y"] = 0, ["x"] = 1, ["name"] = "mcl_nether:soul_sand", ["z"] = 0}, {["y"] = 1, ["x"] = 1, ["name"] = "mcl_nether:soul_sand", ["z"] = 0}, {["y"] = 2, ["x"] = 1, ["name"] = "mcl_heads:wither_skeleton", ["z"] = 0, ["param2"] = 2, ["param1"] = 15}, {["y"] = 1, ["x"] = 2, ["name"] = "mcl_nether:soul_sand", ["z"] = 0}, {["y"] = 2, ["x"] = 2, ["name"] = "mcl_heads:wither_skeleton", ["z"] = 0, ["param2"] = 2, ["param1"] = 15}}

Some files were not shown because too many files have changed in this diff Show More