1
0
Fork 0

Compare commits

..

8 Commits

58 changed files with 1661 additions and 2020 deletions

View File

@ -3,7 +3,7 @@
## Creator of MineClone
* davedevils
## Creator of VoxeLibre
## Creator of MineClone2
* Wuzzy
## Maintainers
@ -24,8 +24,6 @@
* SmokeyDope
* Faerraven / Michieal
* Codiac
* rudzik8
* teknomunk
## Past Developers
* jordan4ibanez
@ -39,6 +37,7 @@
## Contributors
* RandomLegoBrick
* rudzik8
* Code-Sploit
* aligator
* Rootyjr
@ -130,16 +129,6 @@
* Bakawun
* JoseDouglas26
* Zasco
* PrWalterB
* michaljmalinowski
* nixnoxus
* Potiron
* Tuxilio
* Impulse
* Doods
* SOS-Games
* Bram
* qoheniac
## Music
* Jordach for the jukebox music compilation from Big Freaking Dig

View File

@ -64,12 +64,9 @@ Use the `/giveme` chat command to obtain them. See the in-game help for
an explanation.
## Installation
To run the game with the best performance and support, we recommend the latest
stable version of [Minetest](http://minetest.net), be we always make an effort
to support one version behind the latest stable version. In some cases, older
versions might still be good enough but you would be missing out on important
Minetest features that enable important features for our game.
This game requires [Minetest](http://minetest.net) to run (version 5.4.1 or
later). So you need to install Minetest first. Only stable versions of Minetest
are officially supported.
There is no support for running VoxeLibre in development versions of Minetest.
To install VoxeLibre (if you haven't already), move this directory into the

View File

@ -1,8 +1,7 @@
# VoxeLibre
Un jeu inspiré de Minecraft pour Minetest. Forké depuis Mineclone par davedevils.
Développé par de nombreuses personnes, voir CREDITS.md pour une liste complète.
Un jeu non-officiel similaire à Minecraft pour Minetest. Forké depuis Mineclone par davedevils. Développé par de nombreuses personnes. Pas développé ni supporté par Mojang AB.
### Gameplay
### 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
@ -22,7 +21,7 @@ Vous atterissez dans un monde fait entièrement de cubes et généré aléatoire
### Commencer
* **Frappez un arbre** jusqu'à ce qu'il casse et donne du bois
* Placez le **bois dans la grille 2x2** (la "grille de fabrication" de votre menu d'inventaire) et fabriquez 4 planches de bois
* Placez les 4 planches de bois dans la grille 2x2 et **fabriquez un établi**
* Placer les 4 planches de bois dans la grille 2x2 et **fabriquez un établi**
* **Faites un clic droit sur l'établi** (icone livre) pour apprendre toutes les recettes possibles
* **Fabriquez une pioche de bois** pour miner la pierre
* Différents outils minent différentes sortes de blocs. Essayez-les !
@ -31,10 +30,10 @@ Vous atterissez dans un monde fait entièrement de cubes et généré aléatoire
### Agriculture
* Trouvez des graines
* Fabriquez une houe
* Faites un clic droit sur la terre ou un bloc similaire avec la houe pour créer des terres agricoles
* Placez des graines sur des terres agricoles et regardez les pousser
* Faites un clic droit sur la terre ou des blocs similaires avec la houe pour créer des terres agricoles
* Placer des graines sur des terres agricoles et regardez les pousser
* Récoltez les plantes une fois matûres
* Les terres agricoles proches de l'eau deviennent humides et accélèrent la croissance
* Les terres agricoles proche de l'eau deviennent humides et accélèrent la croissance
### Four
* Fabriquez un four
@ -47,7 +46,7 @@ Vous atterissez dans un monde fait entièrement de cubes et généré aléatoire
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.
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`
@ -59,31 +58,29 @@ Il n'y a pas de support de VoxeLibre dans les versions développement de Minetes
Pour installer VoxeLibre (si ce n'est pas déjà fait), déplacez ce dossier dans le dossier “games” de Minetest. Consultez l'aide de Minetest pour en apprendre plus.
## Liens utiles
## Liens utiles
Le dépôt de VoxeLibre est hébergé sur Mesehub. Pour contribuer ou signaler des problèmes, allez là-bas.
* Mesehub : <https://git.minetest.land/VoxeLibre/VoxeLibre>
* Mesehub : <https://git.minetest.land/MineClone2/MineClone2>
* Discord : <https://discord.gg/xE4z8EEpDC>
* YouTube : <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
* IRC : <https://web.libera.chat/#mineclone2>
* Matrix : <https://app.element.io/#/room/#mc2:matrix.org>
* Reddit : <https://www.reddit.com/r/MineClone2/>
* Forums Minetest : <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
* ContentDB : <https://content.minetest.net/packages/wuzzy/mineclone2/>
* OpenCollective : <https://opencollective.com/mineclone2>
* Mastodon : <https://fosstodon.org/@VoxeLibre>
* Lemmy : <https://lemm.ee/c/voxelibre>
* Espace Matrix : <https://app.element.io/#/room/#voxelibre:matrix.org>
* Forums Minetest : <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
* Reddit : <https://www.reddit.com/r/VoxeLibre/>
* IRC (peu utilisé) : <https://web.libera.chat/#mineclone2>
## Objectif
* Créer un jeu stable, performant, moddable et libre inspiré de Minecraft en utilisant le moteur de jeu Minetest, utilisable à la fois en mode solo et multijoueur.
* Actuellement, un grand nombre de fonctionnalités sont déjà implémentées.
L'amélioration des fonctionnalités existantes est toujours la bienvenue.
* Essentiellement, créer un clone de Minecraft stable, moddable, libre et gratuit basé sur le moteur de jeu Minetest avec des fonctionnalités abouties, utilisable à la fois en mode solo et multijoueur. Actuellement, beaucoup des fonctionnalités de **Minecraft Java Edition** sont déjà implémentées et leur amélioration est prioritaire sur les nouvelles demandes.
* Avec une priorité moindre, implémenter les fonctionnalités des versions **Minecraft + OptiFine** (OptiFine autant que supporté par le moteur Minetest). Cela signifie que les fonctionnalités présentes dans les versions listées sont priorisées.
* Dans l'idéal, créer une expérience performante qui tourne bien sur des ordinateurs à basse performance. Malheureusement, en raison des mécanismes de Minecraft et des limitations du moteur Minetest ainsi que de la petite taille de la communauté de joueurs sur des ordinateurs à basses performances, les optimisations sont difficiles à explorer.
## Statut de complétion
Ce jeu est actuellement au stade **beta**.
Il est jouable mais incomplet en fonctionnalités.
La rétro-compatibilité n'est pas entièrement garantie, mettre votre monde à jour peut causer de petits bugs.
Si vous voulez utiliser la version de développement de VoxeLibre en production, la branche master est habituellement relativement stable.
Si vous voulez utiliser la version de développement de VoxeLibre en production, la branche master est habituellement relativement stable. Les branches de test fusionnent souvent des pull requests expérimentales et doivent être considérées comme moins stable.
Les principales fonctionnalités suivantes sont disponibles :
@ -111,12 +108,12 @@ Les principales fonctionnalités suivantes sont disponibles :
* Horloge
* Boussole
* Éponge
* Bloc de slime
* 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
* Cadres d'objets
* Juke-boxes
* Lits
* Menu d'inventaire
@ -125,7 +122,7 @@ Les principales fonctionnalités suivantes sont disponibles :
* Livres pour écrire
* Commandes
* Villages
* L'End
* L'End
* et plus !
Les fonctionnalités suivantes sont incomplètes :

View File

@ -1,6 +1,4 @@
This file is severely out of date. If you can help updating this translation, please reach out to us (contact in README.md - the English version).
# VoxeLibre
# MineClone 2
一個非官方的Minetest遊戲遊玩方式和Minecraft類似。由davedevils從MineClone分拆。
由許多人開發。並非由Mojang Studios開發。<!-- "Mojang AB"'s Name changed at 2020/05, main README should change too -->

View File

@ -28,16 +28,16 @@ git commit -m "Updated release credits and set version for v0.87"
git add releasenotes/0_87-the_prismatic_release.md
git commit -m "Add release notes for v0.87"
```
7. **Tag and push to the tag:**
5. **Tag and push to the tag:**
```
git tag 0.87.0
git push origin 0.87.0
```
8. Update version in game.conf to the next version with -SNAPSHOT suffix:
6. Update version in game.conf to the next version with -SNAPSHOT suffix:
```
git commit -m "Post-release set version 0.88.0-SNAPSHOT"
git commit -m "Post-release set version 0.87.0-SNAPSHOT"
```
9. Push the above to a new branch, and make the release PR. Merge to finalize release process.
7. Push the above to a new branch, and make the release PR. Merge to finalize release process.
### Release via ContentDB
@ -68,100 +68,46 @@ git commit -m "Post-release set version 0.88.0-SNAPSHOT"
## Hotfix Release
### Before releasing
First, determine if the current state of the master branch is fine for the Hotfix.
In general, Hotfixes shouldn't contain new features to minimize the risk of regressions.
* If it hasn't been long since the release, and the only PRs merged so far are bugfixes and/or documentation changes,
it is certainly fine to use it as a base for the release.
* If there are some features merged, but they are aimed at fixing/alleviating important issues with the last released version, it may still be fine.
* If there are some simple QoL features merged that are irrelevant to the last release, it may still be fine to use it as a base for the Hotfix.
* If there are major features or large overhauls merged, it *most probably* is **not** fine to use as a base for the Hotfix.
If you decided that the current state of the master branch can be used as the Hotfix version, make sure that all the PRs merged since the last release
are in the Hotfix milestone and you are working on a clean branch based on the master branch, up-to-date with the one on the repo.
In this case, **skip** the following section.
The below is not up-to-date. At the next hotfix the process should be finalized and updated.
### Prepare release branch
If you decided that the current state of the master branch shouldn't be used as the Hotfix version, you must prepare a release branch.
When hotfixing, you should never release new features. Any new code increases risk of new bugs which has additional testing/release concerns.
To mitigate this, you just release the last release, and the relevant bug fix. For this, we do the following:
* Create release branch from the last release tag, push it:
1. Create release branch from the last release tag, push it:
```
git checkout -b release/0.82.1 0.82.0
git push origin release/0.82.1
```
2. Cherry-pick the relevant commits from the master branch, or merge them from other (PR) branches.
3. Make sure your local copy of the branch contains all the relevant changes, **do not rebase**.
### Release process
#### Prepare feature branch and fix
1. Update CREDITS.md if it is needed
2. Update version in game.conf
3. If you've changed CREDITS.md, run the script:
```
lua tools/generate_ingame_credits.lua
```
4. Make a commit for the above:
```
git add game.conf
git commit -m "Set version for hotfix v0.87.1"
```
or, if credits got updated:
```
git add CREDITS.md
git add mods/HUD/mcl_credits/people.lua
git add game.conf
git commit -m "Updated release credits and set version for hotfix v0.87.1"
```
5. Add a section in the last releasnotes, like this:
```
## 0.87.1 hotfix
```
and describe the changes there
* Create feature branch from that release branch (can review it to check only fix is there, nothing else, and use to also merge into master separately)
`git checkout -b hotfix_bug_1_branch`
* Fix crash/serious bug and commit
* Push branch and create pr to the release and also the master branch (Do not rebase, to reduce merge conflict risk. Do not delete after first merge or it needs to be repushed)
#### Update version and tag the release
* After all fixes are in release branch, pull it locally (best to avoid a merge conflict as feature branch will need to be merged into master also, which already changed version):
* Update version in game.conf to hotfix version and commit it. Example: version=0.82.1
* Tag it, push tag and branch:
6. Make a commit for the releasenotes changes:
```
git add releasenotes/0_87-the_prismatic_release.md
git commit -m "Update release notes for hotfix v0.87.1"
```
7. **Tag and push to the tag:**
```
git tag 0.87.1
git push origin 0.87.1
```
8. If you are skipping some changes from the master branch (and thus are using a prepared master branch from the previous section),
push to the remote and skip the next two steps:
```
git tag 0.82.1
git push origin 0.82.1
git push origin release/0.82.1
```
9. If you're releasing master branch, update version in game.conf to the next version with -SNAPSHOT suffix:
```
git commit -m "Post-hotfix reset version 0.88.0-SNAPSHOT"
```
10. If you're releasing master branch, push the above to a new branch, and make the release PR. Merge to finalize release process.
### Release via ContentDB
Note: If you have to do more than 1 hotfix release, can do it on the same release branch.
1. Go to VoxeLibre page (https://content.minetest.net/packages/Wuzzy/mineclone2/)
2. Click [+Release] button
3. Enter the release tag number in the title and Git reference box. For example (without quotes): "0.87.1"
4. In the minimum minetest version, put the oldest supported version (as of 19/05/2024 it is 5.6), leave the Maximum minetest version blank
5. Click save. Hotfix is now live.
### After releasing
...inform people.
* Add a comment to the forum post with the release number and change log. Maintainer will update the main post with code link.
* Add a Discord announcement post and @everyone with link to the release issue and release notes, and describe briefly what the hotfix does.
* Add a Matrix announcement post and @room with content like above.
* Share the news on reddit + Lemmy. Good subs to share with:
* r/linux_gaming
* r/opensourcegames
* r/opensource
* r/freesoftware
* r/linuxmasterrace
* r/VoxeLibre
* r/MineClone2 (*for now*)

View File

@ -1,4 +1,4 @@
title = VoxeLibre
description = A survival sandbox game. Survive, gather, hunt, build, explore, and do much more.
disallowed_mapgens = v6
version=0.88.0-SNAPSHOT
version=0.87.0-SNAPSHOT

View File

@ -94,16 +94,5 @@ function minetest.check_single_for_falling(pos)
end
end
if get_item_group(node.name, "supported_node_facedir") ~= 0 then
local dir = facedir_to_dir(node.param2)
if dir then
local def = registered_nodes[get_node(vector.add(pos, dir)).name]
if def and def.drawtype == "airlike" then
drop_attached_node(pos)
return true
end
end
end
return false
end

View File

@ -1,6 +1,9 @@
mcl_util = {}
dofile(minetest.get_modpath(minetest.get_current_modname()).."/roman_numerals.lua")
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
dofile(modpath.."/roman_numerals.lua")
dofile(modpath.."/nodes.lua")
-- Updates all values in t using values from to*.
function table.update(t, ...)
@ -112,24 +115,6 @@ function mcl_util.validate_vector (vect)
return false
end
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
-- debugging. See:
-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392
function mcl_util.get_natural_light (pos, time)
local status, retVal = pcall(minetest.get_natural_light, pos, time)
if status then
return retVal
else
minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time))
if (pos) then
local node = minetest.get_node(pos)
minetest.log("warning", "Node at pos: " .. dump(node.name))
end
end
return 0
end
function mcl_util.file_exists(name)
if type(name) ~= "string" then return end
local f = io.open(name)
@ -140,119 +125,6 @@ function mcl_util.file_exists(name)
return true
end
-- Based on minetest.rotate_and_place
--[[
Attempt to predict the desired orientation of the pillar-like node
defined by `itemstack`, and place it accordingly in one of 3 possible
orientations (X, Y or Z).
Stacks are handled normally if the `infinitestacks`
field is false or omitted (else, the itemstack is not changed).
* `invert_wall`: if `true`, place wall-orientation on the ground and ground-
orientation on wall
This function is a simplified version of minetest.rotate_and_place.
The Minetest function is seen as inappropriate because this includes mirror
images of possible orientations, causing problems with pillar shadings.
]]
function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall)
local unode = minetest.get_node_or_nil(pointed_thing.under)
if not unode then
return
end
local undef = minetest.registered_nodes[unode.name]
if undef and undef.on_rightclick and not invert_wall then
undef.on_rightclick(pointed_thing.under, unode, placer,
itemstack, pointed_thing)
return
end
local fdir = minetest.dir_to_facedir(placer:get_look_dir())
local wield_name = itemstack:get_name()
local above = pointed_thing.above
local under = pointed_thing.under
local is_x = (above.x ~= under.x)
local is_y = (above.y ~= under.y)
local is_z = (above.z ~= under.z)
local anode = minetest.get_node_or_nil(above)
if not anode then
return
end
local pos = pointed_thing.above
local node = anode
if undef and undef.buildable_to then
pos = pointed_thing.under
node = unode
end
if minetest.is_protected(pos, placer:get_player_name()) then
minetest.record_protection_violation(pos, placer:get_player_name())
return
end
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to then
return
end
local p2
if is_y then
p2 = 0
elseif is_x then
p2 = 12
elseif is_z then
p2 = 6
end
minetest.set_node(pos, {name = wield_name, param2 = p2})
if not infinitestacks then
itemstack:take_item()
return itemstack
end
end
-- Wrapper of above function for use as `on_place` callback (Recommended).
-- Similar to minetest.rotate_node.
function mcl_util.rotate_axis(itemstack, placer, pointed_thing)
mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing,
minetest.is_creative_enabled(placer:get_player_name()),
placer:get_player_control().sneak)
return itemstack
end
-- Returns position of the neighbor of a double chest node
-- or nil if node is invalid.
-- This function assumes that the large chest is actually intact
-- * pos: Position of the node to investigate
-- * param2: param2 of that node
-- * side: Which "half" the investigated node is. "left" or "right"
function mcl_util.get_double_container_neighbor_pos(pos, param2, side)
if side == "right" then
if param2 == 0 then
return {x = pos.x - 1, y = pos.y, z = pos.z}
elseif param2 == 1 then
return {x = pos.x, y = pos.y, z = pos.z + 1}
elseif param2 == 2 then
return {x = pos.x + 1, y = pos.y, z = pos.z}
elseif param2 == 3 then
return {x = pos.x, y = pos.y, z = pos.z - 1}
end
else
if param2 == 0 then
return {x = pos.x + 1, y = pos.y, z = pos.z}
elseif param2 == 1 then
return {x = pos.x, y = pos.y, z = pos.z - 1}
elseif param2 == 2 then
return {x = pos.x - 1, y = pos.y, z = pos.z}
elseif param2 == 3 then
return {x = pos.x, y = pos.y, z = pos.z + 1}
end
end
end
--- Selects item stack to transfer from
---@param src_inventory InvRef Source innentory to pull from
---@param src_list string Name of source inventory list to pull from
@ -410,61 +282,6 @@ function mcl_util.is_fuel(item)
return minetest.get_craft_result({method = "fuel", width = 1, items = {item}}).time ~= 0
end
-- Returns a on_place function for plants
-- * condition: function(pos, node, itemstack)
-- * A function which is called by the on_place function to check if the node can be placed
-- * Must return true, if placement is allowed, false otherwise.
-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead
-- * pos, node: Position and node table of plant node
-- * itemstack: Itemstack to place
function mcl_util.generate_on_place_plant_function(condition)
return function(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
-- no interaction possible with entities
return itemstack
end
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node(pointed_thing.under)
if placer and not placer:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
local place_pos
local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name]
if not def_under or not def_above then
return itemstack
end
if def_under.buildable_to and def_under.name ~= itemstack:get_name() then
place_pos = pointed_thing.under
elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then
place_pos = pointed_thing.above
pointed_thing.under = pointed_thing.above
else
return itemstack
end
-- Check placement rules
local result, param2 = condition(place_pos, node, itemstack)
if result == true then
local idef = itemstack:get_definition()
local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2)
if success then
if idef.sounds and idef.sounds.place then
minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true)
end
end
itemstack = new_itemstack
end
return itemstack
end
end
-- adjust the y level of an object to the center of its collisionbox
-- used to get the origin position of entity explosions
function mcl_util.get_object_center(obj)
@ -736,243 +553,6 @@ function mcl_util.set_bone_position(obj, bone, pos, rot)
end
end
---Return a function to use in `on_place`.
---
---Allow to bypass the `buildable_to` node field in a `on_place` callback.
---
---You have to make sure that the nodes you return true for have `buildable_to = true`.
---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name`
---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack?
function mcl_util.bypass_buildable_to(func)
--------------------------
-- MINETEST CODE: UTILS --
--------------------------
local function copy_pointed_thing(pointed_thing)
return {
type = pointed_thing.type,
above = pointed_thing.above and vector.copy(pointed_thing.above),
under = pointed_thing.under and vector.copy(pointed_thing.under),
ref = pointed_thing.ref,
}
end
local function user_name(user)
return user and user:get_player_name() or ""
end
-- Returns a logging function. For empty names, does not log.
local function make_log(name)
return name ~= "" and minetest.log or function() end
end
local function check_attached_node(p, n, group_rating)
local def = core.registered_nodes[n.name]
local d = vector.zero()
if group_rating == 3 then
-- always attach to floor
d.y = -1
elseif group_rating == 4 then
-- always attach to ceiling
d.y = 1
elseif group_rating == 2 then
-- attach to facedir or 4dir direction
if (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") then
-- Attach to whatever facedir is "mounted to".
-- For facedir, this is where tile no. 5 point at.
-- The fallback vector here is in case 'facedir to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for facedir.
-- The fallback vector corresponds to param2 = 0.
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
elseif (def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") then
-- Similar to facedir handling
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
end
elseif def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- Attach to whatever this node is "mounted to".
-- This where tile no. 2 points at.
-- The fallback vector here is used for the same reason as
-- for facedir nodes.
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
else
d.y = -1
end
local p2 = vector.add(p, d)
local nn = core.get_node(p2).name
local def2 = core.registered_nodes[nn]
if def2 and not def2.walkable then
return false
end
return true
end
return function(itemstack, placer, pointed_thing, param2)
-------------------
-- MINETEST CODE --
-------------------
local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then
return itemstack
end
local under = pointed_thing.under
local oldnode_under = minetest.get_node_or_nil(under)
local above = pointed_thing.above
local oldnode_above = minetest.get_node_or_nil(above)
local playername = user_name(placer)
local log = make_log(playername)
if not oldnode_under or not oldnode_above then
log("info", playername .. " tried to place"
.. " node in unloaded position " .. minetest.pos_to_string(above))
return itemstack
end
local olddef_under = minetest.registered_nodes[oldnode_under.name]
olddef_under = olddef_under or minetest.nodedef_default
local olddef_above = minetest.registered_nodes[oldnode_above.name]
olddef_above = olddef_above or minetest.nodedef_default
if not olddef_above.buildable_to and not olddef_under.buildable_to then
log("info", playername .. " tried to place"
.. " node in invalid position " .. minetest.pos_to_string(above)
.. ", replacing " .. oldnode_above.name)
return itemstack
end
---------------------
-- CUSTOMIZED CODE --
---------------------
-- Place above pointed node
local place_to = vector.copy(above)
-- If node under is buildable_to, check for callback result and place into it instead
if olddef_under.buildable_to and not func(oldnode_under.name) then
log("info", "node under is buildable to")
place_to = vector.copy(under)
end
-------------------
-- MINETEST CODE --
-------------------
if minetest.is_protected(place_to, playername) then
log("action", playername
.. " tried to place " .. def.name
.. " at protected position "
.. minetest.pos_to_string(place_to))
minetest.record_protection_violation(place_to, playername)
return itemstack
end
local oldnode = minetest.get_node(place_to)
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
-- Calculate direction for wall mounted stuff like torches and signs
if def.place_param2 ~= nil then
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
local dir = vector.subtract(under, above)
newnode.param2 = minetest.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir" or
def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
local dir = vector.subtract(above, placer_pos)
newnode.param2 = minetest.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
end
local metatable = itemstack:get_meta():to_table().fields
-- Transfer color information
if metatable.palette_index and not def.place_param2 then
local color_divisor = nil
if def.paramtype2 == "color" then
color_divisor = 1
elseif def.paramtype2 == "colorwallmounted" then
color_divisor = 8
elseif def.paramtype2 == "colorfacedir" then
color_divisor = 32
elseif def.paramtype2 == "color4dir" then
color_divisor = 4
elseif def.paramtype2 == "colordegrotate" then
color_divisor = 32
end
if color_divisor then
local color = math.floor(metatable.palette_index / color_divisor)
local other = newnode.param2 % color_divisor
newnode.param2 = color * color_divisor + other
end
end
-- Check if the node is attached and if it can be placed there
local an = minetest.get_item_group(def.name, "attached_node")
if an ~= 0 and
not check_attached_node(place_to, newnode, an) then
log("action", "attached node " .. def.name ..
" cannot be placed at " .. minetest.pos_to_string(place_to))
return itemstack
end
log("action", playername .. " places node "
.. def.name .. " at " .. minetest.pos_to_string(place_to))
-- Add node and update
minetest.add_node(place_to, newnode)
-- Play sound if it was done by a player
if playername ~= "" and def.sounds and def.sounds.place then
minetest.sound_play(def.sounds.place, {
pos = place_to,
exclude_player = playername,
}, true)
end
local take_item = true
-- Run callback
if def.after_place_node then
-- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = vector.copy(place_to)
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
take_item = false
end
end
-- Run script hook
for _, callback in ipairs(minetest.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = vector.copy(place_to)
local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
take_item = false
end
end
if take_item then
itemstack:take_item()
end
return itemstack
end
end
--[[Check for a protection violation in a given area.
--
-- Applies is_protected() to a 3D lattice of points in the defined volume. The points are spaced
@ -1018,33 +598,6 @@ function mcl_util.check_position_protection(position, player)
return false
end
local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0}
function mcl_util.get_palette_indexes_from_pos(pos)
local biome_data = minetest.get_biome_data(pos)
local biome = biome_data.biome
local biome_name = minetest.get_biome_name(biome)
local reg_biome = minetest.registered_biomes[biome_name]
if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then
local gpi = reg_biome._mcl_grass_palette_index
local fpi = reg_biome._mcl_foliage_palette_index
local wpi = reg_biome._mcl_water_palette_index
local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi}
return palette_indexes
else
return palette_indexes
end
end
function mcl_util.get_colorwallmounted_rotation(pos)
local colorwallmounted_node = minetest.get_node(pos)
for i = 0, 32, 1 do
local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8)
if colorwallmounted_rotation < 6 then
return colorwallmounted_rotation
end
end
end
---Move items from one inventory list to another, drop items that do not fit in provided pos and direction.
---@param src_inv mt.InvRef
---@param src_listname string

View File

@ -0,0 +1,462 @@
-- Functions related to nodes and node definitions
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
-- debugging. See:
-- https://git.minetest.land/VoxeLibre/VoxeLibre/issues/1392
function mcl_util.get_natural_light (pos, time)
local status, retVal = pcall(minetest.get_natural_light, pos, time)
if status then
return retVal
else
minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time))
if (pos) then
local node = minetest.get_node(pos)
minetest.log("warning", "Node at pos: " .. dump(node.name))
end
end
return 0
end
-- Based on minetest.rotate_and_place
--[[
Attempt to predict the desired orientation of the pillar-like node
defined by `itemstack`, and place it accordingly in one of 3 possible
orientations (X, Y or Z).
Stacks are handled normally if the `infinitestacks`
field is false or omitted (else, the itemstack is not changed).
* `invert_wall`: if `true`, place wall-orientation on the ground and ground-
orientation on wall
This function is a simplified version of minetest.rotate_and_place.
The Minetest function is seen as inappropriate because this includes mirror
images of possible orientations, causing problems with pillar shadings.
]]
function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infinitestacks, invert_wall)
local unode = minetest.get_node_or_nil(pointed_thing.under)
if not unode then
return
end
local undef = minetest.registered_nodes[unode.name]
if undef and undef.on_rightclick and not invert_wall then
undef.on_rightclick(pointed_thing.under, unode, placer,
itemstack, pointed_thing)
return
end
local fdir = minetest.dir_to_facedir(placer:get_look_dir())
local wield_name = itemstack:get_name()
local above = pointed_thing.above
local under = pointed_thing.under
local is_x = (above.x ~= under.x)
local is_y = (above.y ~= under.y)
local is_z = (above.z ~= under.z)
local anode = minetest.get_node_or_nil(above)
if not anode then
return
end
local pos = pointed_thing.above
local node = anode
if undef and undef.buildable_to then
pos = pointed_thing.under
node = unode
end
if minetest.is_protected(pos, placer:get_player_name()) then
minetest.record_protection_violation(pos, placer:get_player_name())
return
end
local ndef = minetest.registered_nodes[node.name]
if not ndef or not ndef.buildable_to then
return
end
local p2
if is_y then
p2 = 0
elseif is_x then
p2 = 12
elseif is_z then
p2 = 6
end
minetest.set_node(pos, {name = wield_name, param2 = p2})
if not infinitestacks then
itemstack:take_item()
return itemstack
end
end
-- Wrapper of above function for use as `on_place` callback (Recommended).
-- Similar to minetest.rotate_node.
function mcl_util.rotate_axis(itemstack, placer, pointed_thing)
mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing,
minetest.is_creative_enabled(placer:get_player_name()),
placer:get_player_control().sneak)
return itemstack
end
-- Returns position of the neighbor of a double chest node
-- or nil if node is invalid.
-- This function assumes that the large chest is actually intact
-- * pos: Position of the node to investigate
-- * param2: param2 of that node
-- * side: Which "half" the investigated node is. "left" or "right"
function mcl_util.get_double_container_neighbor_pos(pos, param2, side)
if side == "right" then
if param2 == 0 then
return {x = pos.x - 1, y = pos.y, z = pos.z}
elseif param2 == 1 then
return {x = pos.x, y = pos.y, z = pos.z + 1}
elseif param2 == 2 then
return {x = pos.x + 1, y = pos.y, z = pos.z}
elseif param2 == 3 then
return {x = pos.x, y = pos.y, z = pos.z - 1}
end
else
if param2 == 0 then
return {x = pos.x + 1, y = pos.y, z = pos.z}
elseif param2 == 1 then
return {x = pos.x, y = pos.y, z = pos.z - 1}
elseif param2 == 2 then
return {x = pos.x - 1, y = pos.y, z = pos.z}
elseif param2 == 3 then
return {x = pos.x, y = pos.y, z = pos.z + 1}
end
end
end
-- Returns a on_place function for plants
-- * condition: function(pos, node, itemstack)
-- * A function which is called by the on_place function to check if the node can be placed
-- * Must return true, if placement is allowed, false otherwise.
-- * If it returns a string, placement is allowed, but will place this itemstring as a node instead
-- * pos, node: Position and node table of plant node
-- * itemstack: Itemstack to place
function mcl_util.generate_on_place_plant_function(condition)
return function(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
-- no interaction possible with entities
return itemstack
end
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node(pointed_thing.under)
if placer and not placer:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
local place_pos
local def_under = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
local def_above = minetest.registered_nodes[minetest.get_node(pointed_thing.above).name]
if not def_under or not def_above then
return itemstack
end
if def_under.buildable_to and def_under.name ~= itemstack:get_name() then
place_pos = pointed_thing.under
elseif def_above.buildable_to and def_above.name ~= itemstack:get_name() then
place_pos = pointed_thing.above
pointed_thing.under = pointed_thing.above
else
return itemstack
end
-- Check placement rules
local result, param2 = condition(place_pos, node, itemstack)
if result == true then
local idef = itemstack:get_definition()
local new_itemstack, success = minetest.item_place_node(itemstack, placer, pointed_thing, param2)
if success then
if idef.sounds and idef.sounds.place then
minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true)
end
end
itemstack = new_itemstack
end
return itemstack
end
end
---Return a function to use in `on_place`.
---
---Allow to bypass the `buildable_to` node field in a `on_place` callback.
---
---You have to make sure that the nodes you return true for have `buildable_to = true`.
---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name`
---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack?
function mcl_util.bypass_buildable_to(func)
--------------------------
-- MINETEST CODE: UTILS --
--------------------------
local function copy_pointed_thing(pointed_thing)
return {
type = pointed_thing.type,
above = pointed_thing.above and vector.copy(pointed_thing.above),
under = pointed_thing.under and vector.copy(pointed_thing.under),
ref = pointed_thing.ref,
}
end
local function user_name(user)
return user and user:get_player_name() or ""
end
-- Returns a logging function. For empty names, does not log.
local function make_log(name)
return name ~= "" and minetest.log or function() end
end
local function check_attached_node(p, n, group_rating)
local def = core.registered_nodes[n.name]
local d = vector.zero()
if group_rating == 3 then
-- always attach to floor
d.y = -1
elseif group_rating == 4 then
-- always attach to ceiling
d.y = 1
elseif group_rating == 2 then
-- attach to facedir or 4dir direction
if (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") then
-- Attach to whatever facedir is "mounted to".
-- For facedir, this is where tile no. 5 point at.
-- The fallback vector here is in case 'facedir to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for facedir.
-- The fallback vector corresponds to param2 = 0.
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
elseif (def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") then
-- Similar to facedir handling
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
end
elseif def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- Attach to whatever this node is "mounted to".
-- This where tile no. 2 points at.
-- The fallback vector here is used for the same reason as
-- for facedir nodes.
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
else
d.y = -1
end
local p2 = vector.add(p, d)
local nn = core.get_node(p2).name
local def2 = core.registered_nodes[nn]
if def2 and not def2.walkable then
return false
end
return true
end
return function(itemstack, placer, pointed_thing, param2)
-------------------
-- MINETEST CODE --
-------------------
local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then
return itemstack
end
local under = pointed_thing.under
local oldnode_under = minetest.get_node_or_nil(under)
local above = pointed_thing.above
local oldnode_above = minetest.get_node_or_nil(above)
local playername = user_name(placer)
local log = make_log(playername)
if not oldnode_under or not oldnode_above then
log("info", playername .. " tried to place"
.. " node in unloaded position " .. minetest.pos_to_string(above))
return itemstack
end
local olddef_under = minetest.registered_nodes[oldnode_under.name]
olddef_under = olddef_under or minetest.nodedef_default
local olddef_above = minetest.registered_nodes[oldnode_above.name]
olddef_above = olddef_above or minetest.nodedef_default
if not olddef_above.buildable_to and not olddef_under.buildable_to then
log("info", playername .. " tried to place"
.. " node in invalid position " .. minetest.pos_to_string(above)
.. ", replacing " .. oldnode_above.name)
return itemstack
end
---------------------
-- CUSTOMIZED CODE --
---------------------
-- Place above pointed node
local place_to = vector.copy(above)
-- If node under is buildable_to, check for callback result and place into it instead
if olddef_under.buildable_to and not func(oldnode_under.name) then
log("info", "node under is buildable to")
place_to = vector.copy(under)
end
-------------------
-- MINETEST CODE --
-------------------
if minetest.is_protected(place_to, playername) then
log("action", playername
.. " tried to place " .. def.name
.. " at protected position "
.. minetest.pos_to_string(place_to))
minetest.record_protection_violation(place_to, playername)
return itemstack
end
local oldnode = minetest.get_node(place_to)
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
-- Calculate direction for wall mounted stuff like torches and signs
if def.place_param2 ~= nil then
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
local dir = vector.subtract(under, above)
newnode.param2 = minetest.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir" or
def.paramtype2 == "4dir" or
def.paramtype2 == "color4dir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
local dir = vector.subtract(above, placer_pos)
newnode.param2 = minetest.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
end
local metatable = itemstack:get_meta():to_table().fields
-- Transfer color information
if metatable.palette_index and not def.place_param2 then
local color_divisor = nil
if def.paramtype2 == "color" then
color_divisor = 1
elseif def.paramtype2 == "colorwallmounted" then
color_divisor = 8
elseif def.paramtype2 == "colorfacedir" then
color_divisor = 32
elseif def.paramtype2 == "color4dir" then
color_divisor = 4
elseif def.paramtype2 == "colordegrotate" then
color_divisor = 32
end
if color_divisor then
local color = math.floor(metatable.palette_index / color_divisor)
local other = newnode.param2 % color_divisor
newnode.param2 = color * color_divisor + other
end
end
-- Check if the node is attached and if it can be placed there
local an = minetest.get_item_group(def.name, "attached_node")
if an ~= 0 and
not check_attached_node(place_to, newnode, an) then
log("action", "attached node " .. def.name ..
" cannot be placed at " .. minetest.pos_to_string(place_to))
return itemstack
end
log("action", playername .. " places node "
.. def.name .. " at " .. minetest.pos_to_string(place_to))
-- Add node and update
minetest.add_node(place_to, newnode)
-- Play sound if it was done by a player
if playername ~= "" and def.sounds and def.sounds.place then
minetest.sound_play(def.sounds.place, {
pos = place_to,
exclude_player = playername,
}, true)
end
local take_item = true
-- Run callback
if def.after_place_node then
-- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = vector.copy(place_to)
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
take_item = false
end
end
-- Run script hook
for _, callback in ipairs(minetest.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = vector.copy(place_to)
local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
take_item = false
end
end
if take_item then
itemstack:take_item()
end
return itemstack
end
end
local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0}
function mcl_util.get_palette_indexes_from_pos(pos)
local biome_data = minetest.get_biome_data(pos)
local biome = biome_data.biome
local biome_name = minetest.get_biome_name(biome)
local reg_biome = minetest.registered_biomes[biome_name]
if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then
local gpi = reg_biome._mcl_grass_palette_index
local fpi = reg_biome._mcl_foliage_palette_index
local wpi = reg_biome._mcl_water_palette_index
local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi}
return palette_indexes
else
return palette_indexes
end
end
function mcl_util.get_colorwallmounted_rotation(pos)
local colorwallmounted_node = minetest.get_node(pos)
for i = 0, 32, 1 do
local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8)
if colorwallmounted_rotation < 6 then
return colorwallmounted_rotation
end
end
end
function mcl_util.match_node_to_filter(node_name, filters)
for i = 1,#filters do
local filter = filters[i]
if node_name == filter then return true end
if string.sub(filter,1,6) == "group:" and minetest.get_item_group(node_name, string.sub(filter,7)) ~= 0 then return true end
end
return false
end

View File

@ -671,13 +671,6 @@ mob will spawn e.g.
mobs_animal:sheep_chance 11000
mobs_monster:sand_monster_chance 100
Registering Mob Conversion
----------------
Sometimes you need to completely replace one mob with a different version. To do this, use:
mcl_mobs.register_conversion(old_name, new_name)
Rideable Horse Example Mob
--------------------------

View File

@ -802,37 +802,34 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
end
-- alert others to the attack
local alert_pos = hitter:get_pos()
if alert_pos then
local objs = minetest.get_objects_inside_radius(alert_pos, self.view_range)
local obj = nil
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
local obj = nil
for n = 1, #objs do
for n = 1, #objs do
obj = objs[n]:get_luaentity()
obj = objs[n]:get_luaentity()
if obj then
-- only alert members of same mob or friends
if obj.group_attack
and obj.state ~= "attack"
and obj.owner ~= name then
if obj.name == self.name then
obj:do_attack(hitter)
elseif type(obj.group_attack) == "table" then
for i=1, #obj.group_attack do
if obj.group_attack[i] == self.name then
obj._aggro = true
obj:do_attack(hitter)
break
end
if obj then
-- only alert members of same mob or friends
if obj.group_attack
and obj.state ~= "attack"
and obj.owner ~= name then
if obj.name == self.name then
obj:do_attack(hitter)
elseif type(obj.group_attack) == "table" then
for i=1, #obj.group_attack do
if obj.group_attack[i] == self.name then
obj._aggro = true
obj:do_attack(hitter)
break
end
end
end
end
-- have owned mobs attack player threat
if obj.owner == name and obj.owner_loyal then
obj:do_attack(self.object)
end
-- have owned mobs attack player threat
if obj.owner == name and obj.owner_loyal then
obj:do_attack(self.object)
end
end
end

View File

@ -342,17 +342,6 @@ function mcl_mobs.register_mob(name, def)
minetest.register_entity(name, setmetatable(final_def,mcl_mobs.mob_class_meta))
end -- END mcl_mobs.register_mob function
function mcl_mobs.register_conversion(old_name, new_name)
minetest.register_entity(old_name, {
on_activate = function(self, staticdata, dtime)
local obj = minetest.add_entity(self.object:get_pos(), new_name, staticdata)
local hook = (obj:get_luaentity() or {})._on_after_convert
if hook then hook(obj) end
self.object:remove()
end,
_convert_to = new_name,
})
end
function mcl_mobs.get_arrow_damage_func(damage, typ)
local typ = mcl_damage.types[typ] and typ or "arrow"

View File

@ -633,7 +633,7 @@ local function get_next_mob_spawn_pos(pos)
-- those further away from the player.
local fx = (math_random(1,10000)-1) / 10000
local x = inverse_pwl(fx, SPAWN_DISTANCE_CDF_PWL)
local distance = x * (MOB_SPAWN_ZONE_OUTER - MOB_SPAWN_ZONE_INNER) + MOB_SPAWN_ZONE_INNER
distance = x * (MOB_SPAWN_ZONE_OUTER - MOB_SPAWN_ZONE_INNER) + MOB_SPAWN_ZONE_INNER
--print("Using spawn distance of "..tostring(distance).." fx="..tostring(fx)..",x="..tostring(x))
-- TODO Floor xoff and zoff and add 0.5 so it tries to spawn in the middle of the square. Less failed attempts.

View File

@ -491,17 +491,20 @@ mcl_mobs.register_mob("mobs_mc:rover", {
view_range = 64,
fear_height = 4,
attack_type = "dogfight",
_on_after_convert = function(obj)
})
-- compat
minetest.register_entity("mobs_mc:enderman", {
on_activate = function(self, staticdata, dtime)
local obj = minetest.add_entity(self.object:get_pos(), "mobs_mc:rover", staticdata)
obj:set_properties({
mesh = "vl_rover.b3d",
textures = { "vl_mobs_rover.png^vl_mobs_rover_face.png" },
visual_size = {x=10, y=10},
})
end
}) -- END mcl_mobs.register_mob("mobs_mc:rover", {
-- compat
mcl_mobs.register_conversion("mobs_mc:enderman", "mobs_mc:rover")
self.object:remove()
end,
})
-- End spawn
mcl_mobs:spawn_specific(

View File

@ -32,11 +32,10 @@ local function get_texture(self)
end
end
end
if not texture or texture == "" then
if not texture then
texture = "vl_stalker_default.png"
end
texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers
texture = "([combine:16x24:0,0=(" .. texture .. "):0,16=(" .. texture ..")".. texture_suff
texture = "([combine:16x24:0,0=" .. texture .. ":0,16=" .. texture .. texture_suff
if self.attack then
texture = texture .. ")^vl_mobs_stalker_overlay_angry.png"
else
@ -132,11 +131,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
self:boom(mcl_util.get_object_center(self.object), self.explosion_strength)
end
end
local new_texture = get_texture(self)
if self._stalker_texture ~= new_texture then
self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}})
self._stalker_texture = new_texture
end
self.object:set_properties({textures={get_texture(self)}})
end,
on_die = function(self, pos, cmi_cause)
-- Drop a random music disc when killed by skeleton or stray
@ -180,18 +175,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
floats = 1,
fear_height = 4,
view_range = 16,
_on_after_convert = function(obj)
obj:set_properties({
visual_size = {x=2, y=2},
mesh = "vl_stalker.b3d",
textures = {
{get_texture({}),
"mobs_mc_empty.png"},
},
})
end,
}) -- END mcl_mobs.register_mob("mobs_mc:stalker", {
})
mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
description = S("Overloaded Stalker"),
@ -320,8 +304,26 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
--Having trouble when fire is placed with lightning
fire_resistant = true,
glow = 3,
})
_on_after_convert = function(obj)
-- compat
minetest.register_entity("mobs_mc:creeper", {
on_activate = function(self, staticdata, dtime)
local obj = minetest.add_entity(self.object:get_pos(), "mobs_mc:stalker", staticdata)
obj:set_properties({
visual_size = {x=2, y=2},
mesh = "vl_stalker.b3d",
textures = {
{get_texture({}),
"mobs_mc_empty.png"},
},
})
self.object:remove()
end,
})
minetest.register_entity("mobs_mc:creeper_charged", {
on_activate = function(self, staticdata, dtime)
local obj = minetest.add_entity(self.object:get_pos(), "mobs_mc:stalker_overloaded", staticdata)
obj:set_properties({
visual_size = {x=2, y=2},
mesh = "vl_stalker.b3d",
@ -330,12 +332,9 @@ mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
AURA},
},
})
self.object:remove()
end,
}) -- END mcl_mobs.register_mob("mobs_mc:stalker_overloaded", {
-- compat
mcl_mobs.register_conversion("mobs_mc:creeper", "mobs_mc:stalker")
mcl_mobs.register_conversion("mobs_mc:creeper_charged", "mobs_mc:stalker_overloaded")
})
mcl_mobs:spawn_specific(
"mobs_mc:stalker",

View File

@ -105,6 +105,8 @@ function mcl_raids.promote_to_raidcaptain(c) -- object
if cmi_cause and cmi_cause.type == "punch" and cmi_cause.puncher:is_player() then
awards.unlock(cmi_cause.puncher:get_player_name(), "mcl:voluntary_exile")
local lv = mcl_potions.get_effect_level(cmi_cause.puncher, "bad_omen")
if not lv then lv = 0
else lv = lv.factor end
lv = math.max(5,lv + 1)
mcl_potions.give_effect_by_level("bad_omen", cmi_cause.puncher, lv, 6000)
end
@ -309,7 +311,7 @@ mcl_events.register_event("raid",{
self.health_max = 1
self.health = 0
local lv = mcl_potions.get_effect_level(minetest.get_player_by_name(self.player), "bad_omen")
if lv > 1 then self.max_stage = 6 end
if lv and lv.factor and lv.factor > 1 then self.max_stage = 6 end
end,
cond_progress = function(self)
if not is_player_near(self) then return false end

View File

@ -126,9 +126,6 @@ S("• I: Show/hide inventory menu").."\n\n"..
S("Inventory interaction:").."\n"..
S("See the entry “Basics > Inventory”.").."\n\n"..
S("Hunger/Eating:").."\n"..
S("• While holding food, hold the right mouse button (PC) or double-tap and hold the second tap (Android) to eat").."\n\n"..
S("Camera:").."\n"..
S("• Z: Zoom").."\n"..
S("• F7: Toggle camera mode").."\n\n"..

View File

@ -78,8 +78,6 @@ World interaction:=
• I: Show/hide inventory menu=
Inventory interaction:=
See the entry “Basics > Inventory”.=
Hunger/Eating:=
• While holding food, hold the right mouse button (PC) or double-tap and hold the second tap (Android) to eat=
Camera:=
• Z: Zoom=
• F7: Toggle camera mode=

View File

@ -26,8 +26,6 @@ return {
"SmokeyDope",
"Faerraven / Michieal",
"Codiac",
"rudzik8",
"teknomunk",
}},
{S("Past Developers"), 0xF84355, {
"jordan4ibanez",
@ -41,6 +39,7 @@ return {
}},
{S("Contributors"), 0x52FF00, {
"RandomLegoBrick",
"rudzik8",
"Code-Sploit",
"aligator",
"Rootyjr",
@ -132,16 +131,6 @@ return {
"Bakawun",
"JoseDouglas26",
"Zasco",
"PrWalterB",
"michaljmalinowski",
"nixnoxus",
"Potiron",
"Tuxilio",
"Impulse",
"Doods",
"SOS-Games",
"Bram",
"qoheniac",
}},
{S("Music"), 0xA60014, {
"Jordach for the jukebox music compilation from Big Freaking Dig",

View File

@ -106,6 +106,11 @@ minetest.register_on_mods_loaded(function()
end
if def.groups.brewitem then
local str = name
if def.groups._mcl_potion == 1 then
local stack = ItemStack(name)
tt.reload_itemstack_description(stack)
str = stack:to_string()
end
table.insert(inventory_lists["brew"], str)
nonmisc = true
end
@ -123,12 +128,14 @@ minetest.register_on_mods_loaded(function()
local stack = ItemStack(name)
local potency = def._default_potent_level - 1
stack:get_meta():set_int("mcl_potions:potion_potent", potency)
tt.reload_itemstack_description(stack)
table.insert(inventory_lists["brew"], stack:to_string())
end
if def.has_plus then
local stack = ItemStack(name)
local extend = def._default_extend_level
stack:get_meta():set_int("mcl_potions:potion_plus", extend)
tt.reload_itemstack_description(stack)
table.insert(inventory_lists["brew"], stack:to_string())
end
end
@ -137,17 +144,6 @@ minetest.register_on_mods_loaded(function()
end
end
-- Itemstack descriptions need to be reloaded separately, because tt invalidates minetest.registered_items iterators, somehow
-- (and pairs() uses said iterators internally)
-- TODO investigate the iterator invalidation, where does it happen?
for name, list in pairs(inventory_lists) do
for i=1, #list do
local stack = ItemStack(list[i])
tt.reload_itemstack_description(stack)
list[i] = stack:to_string()
end
end
for ench, def in pairs(mcl_enchanting.enchantments) do
local str = "mcl_enchanting:book_enchanted " .. ench .. " " .. def.max_level
if def.inv_tool_tab then

View File

@ -84,9 +84,6 @@ local pallete_order = {
pane_magenta_flat = 16,
pane_magenta = 16
}
local EFFECT_CONVERSIONS = {
strenght = "strength"
}
local function get_beacon_beam(glass_nodename)
if glass_nodename == "air" then return 0 end
@ -162,7 +159,7 @@ local function remove_beacon_beam(pos)
minetest.get_voxel_manip():read_from_map({x=pos.x,y=y,z=pos.z}, {x=pos.x,y=y,z=pos.z})
node = minetest.get_node({x=pos.x,y=y,z=pos.z})
end
if node.name == "mcl_beacons:beacon_beam" then
minetest.remove_node({x=pos.x,y=y,z=pos.z})
end
@ -215,30 +212,24 @@ local function effect_player(effect,pos,power_level, effect_level,player)
end
local function apply_effects_to_all_players(pos)
local meta = minetest.get_meta(pos)
local meta = minetest.get_meta(pos)
local effect_string = meta:get_string("effect")
local effect_level = meta:get_int("effect_level")
local effect_level = meta:get_int("effect_level")
local power_level = beacon_blockcheck(pos)
local power_level = beacon_blockcheck(pos)
local new_effect_string = EFFECT_CONVERSIONS[effect_string]
if new_effect_string then
effect_string = new_effect_string
meta:set_string("effect", effect_string)
end
if effect_string == "" or ( effect_level == 2 and power_level < 4 ) then --no need to run loops when beacon is in an invalid setup :P
if effect_level == 2 and power_level < 4 then --no need to run loops when beacon is in an invalid setup :P
return
end
local beacon_distance = (power_level + 1) * 10
local beacon_distance = (power_level + 1) * 10
for _, player in pairs(minetest.get_connected_players()) do
if vector.distance(pos, player:get_pos()) <= beacon_distance then
if not clear_obstructed_beam(pos) then
effect_player(effect_string, pos, power_level, effect_level, player)
end
end
if vector.distance(pos, player:get_pos()) <= beacon_distance then
if not clear_obstructed_beam(pos) then
effect_player(effect_string, pos, power_level, effect_level, player)
end
end
end
end
@ -253,7 +244,8 @@ minetest.register_node("mcl_beacons:beacon", {
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("input", 1)
meta:set_string("formspec", formspec_string)
local form = formspec_string
meta:set_string("formspec", form)
end,
on_destruct = function(pos)
local meta = minetest.get_meta(pos)
@ -265,7 +257,7 @@ minetest.register_node("mcl_beacons:beacon", {
remove_beacon_beam(pos)
end,
on_receive_fields = function(pos, formname, fields, sender)
if fields.swiftness or fields.regeneration or fields.leaping or fields.strength
if fields.swiftness or fields.regeneration or fields.leaping or fields.strenght
or fields.haste or fields.resistance or fields.absorption or fields.slow_falling then
local sender_name = sender:get_player_name()
local power_level = beacon_blockcheck(pos)
@ -279,7 +271,7 @@ minetest.register_node("mcl_beacons:beacon", {
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local input = inv:get_stack("input",1)
if input:is_empty() then
return
end
@ -329,7 +321,7 @@ minetest.register_node("mcl_beacons:beacon", {
end
minetest.get_meta(pos):set_string("effect","resistance")
successful = true
elseif fields.strength and power_level >= 3 then
elseif fields.strenght and power_level >= 3 then
if power_level == 4 then
minetest.get_meta(pos):set_int("effect_level",2)
else
@ -361,7 +353,7 @@ minetest.register_node("mcl_beacons:beacon", {
awards.unlock(sender:get_player_name(),"mcl:beacon")
input:take_item()
inv:set_stack("input",1,input)
local beam_palette_index = 0
remove_beacon_beam(pos)
for y = pos.y +1, pos.y + 201 do
@ -370,6 +362,7 @@ minetest.register_node("mcl_beacons:beacon", {
minetest.get_voxel_manip():read_from_map({x=pos.x,y=y,z=pos.z}, {x=pos.x,y=y,z=pos.z})
node = minetest.get_node({x=pos.x,y=y,z=pos.z})
end
if minetest.get_item_group(node.name, "glass") ~= 0 or minetest.get_item_group(node.name,"material_glass") ~= 0 then
beam_palette_index = get_beacon_beam(node.name)
@ -433,19 +426,10 @@ minetest.register_abm{
apply_effects_to_all_players(pos)
end,
}
minetest.register_lbm({
label = "Update beacon formspecs (0.87.1)",
name = "mcl_beacons:update_beacon_formspecs_0_87_1",
nodenames = { "mcl_beacons:beacon" },
action = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", formspec_string)
end
})
minetest.register_craft({
output = "mcl_beacons:beacon",
recipe = {
recipe = {
{"mcl_core:glass", "mcl_core:glass", "mcl_core:glass"},
{"mcl_core:glass", "mcl_mobitems:nether_star", "mcl_core:glass"},
{"mcl_core:obsidian", "mcl_core:obsidian", "mcl_core:obsidian"}

View File

@ -225,9 +225,11 @@ local function update_formspecs(finished, players)
local button_abort = "button_exit[4,3;4,0.75;leave;"..F(S("Abort sleep")).."]"
local bg_presleep = "bgcolor[#00000080;true]"
local bg_sleep = "bgcolor[#000000FF;true]"
local chatbox = "field[0.3,4.5;10,1;chatmessage;"..F(S("Chat:"))..";]"
local chatsubmit = "button[10,3.73;2,2;chatsubmit;"..F(S("Send")).."]"
local defaultmessagebutton = "button[10.98,2.93;1,2;defaultmessage;zzZ]"
local chatbox = "field[0.2,4.5;9,1;chatmessage;"..F(S("Chat:"))..";]"
local chatsubmit = "button[9.2,3.75;1,2;chatsubmit;"..F(S("send!")).."]"
local defaultmessagebutton = "button[10.2,3.75;1,2;defaultmessage;zzZzzZ]"
form_n = form_n .. chatbox .. chatsubmit --because these should be in the formspec in ANY case, they might as well be added here already
if finished then
for name,_ in pairs(mcl_beds.player) do
@ -235,7 +237,6 @@ local function update_formspecs(finished, players)
end
return
elseif not is_sp then
form_n = form_n .. chatbox .. chatsubmit --because these should be in the formspec in ANY case, they might as well be added here already
local text = S("Players in bed: @1/@2", player_in_bed, ges)
if not night_skip then
text = text .. "\n" .. S("Note: Night skip is disabled.")

View File

@ -56,7 +56,65 @@ S("Arrows might get stuck on solid blocks and can be retrieved again. They are a
end,
})
local ARROW_ENTITY={
-- Destroy arrow entity self at pos and drops it as an item
local function spawn_item(self, pos)
if not minetest.is_creative_enabled("") then
local item = minetest.add_item(pos, "mcl_bows:arrow")
item:set_velocity(vector.new(0, 0, 0))
item:set_yaw(self.object:get_yaw())
end
mcl_burning.extinguish(self.object)
self.object:remove()
end
local function stuck_arrow_on_step(self, dtime)
self._stucktimer = self._stucktimer + dtime
self._stuckrechecktimer = self._stuckrechecktimer + dtime
if self._stucktimer > ARROW_TIMEOUT then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
local pos = self.object:get_pos()
-- Drop arrow as item when it is no longer stuck
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
if self._stuckrechecktimer > STUCK_RECHECK_TIME then
local stuckin_def
if self._stuckin then
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
end
-- TODO: In MC, arrow just falls down without turning into an item
if stuckin_def and stuckin_def.walkable == false then
spawn_item(self, pos)
return
end
self._stuckrechecktimer = 0
end
-- Pickup arrow if player is nearby (not in Creative Mode)
local objects = minetest.get_objects_inside_radius(pos, 1)
for _,obj in ipairs(objects) do
if obj:is_player() then
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
if obj:get_inventory():room_for_item("main", "mcl_bows:arrow") then
obj:get_inventory():add_item("main", "mcl_bows:arrow")
minetest.sound_play("item_drop_pickup", {
pos = pos,
max_hear_distance = 16,
gain = 1.0,
}, true)
end
end
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
end
end
vl_projectile.register("mcl_bows:arrow_entity", {
physical = true,
pointable = false,
visual = "mesh",
@ -67,7 +125,10 @@ local ARROW_ENTITY={
collide_with_objects = false,
_fire_damage_resistant = true,
_lastpos={},
_save_fields = {
"last_pos", "startpos", "damage", "is_critical", "stuck", "stuckin", "stuckin_player",
},
_startpos=nil,
_damage=1, -- Damage on impact
_is_critical=false, -- Whether this arrow would deal critical damage
@ -81,282 +142,41 @@ local ARROW_ENTITY={
_blocked = false,
_viscosity=0, -- Viscosity of node the arrow is currently in
_deflection_cooloff=0, -- Cooloff timer after an arrow deflection, to prevent many deflections in quick succession
}
-- Destroy arrow entity self at pos and drops it as an item
local function spawn_item(self, pos)
if not minetest.is_creative_enabled("") then
local item = minetest.add_item(pos, "mcl_bows:arrow")
item:set_velocity(vector.new(0, 0, 0))
item:set_yaw(self.object:get_yaw())
end
mcl_burning.extinguish(self.object)
self.object:remove()
end
_vl_projectile = {
survive_collision = true,
sticks_in_players = true,
damage_groups = function(self)
return { fleshy = self._damage }
end,
behaviors = {
vl_projectile.collides_with_solids,
vl_projectile.raycast_collides_with_entities,
},
allow_punching = function(self, entity_def, projectile_def, entity)
local lua = entity:get_luaentity()
if lua and lua.name == "mobs_mc:rover" then return false end
local function damage_particles(pos, is_critical)
if is_critical then
minetest.add_particlespawner({
amount = 15,
time = 0.1,
minpos = vector.offset(pos, -0.5, -0.5, -0.5),
maxpos = vector.offset(pos, 0.5, 0.5, 0.5),
minvel = vector.new(-0.1, -0.1, -0.1),
maxvel = vector.new(0.1, 0.1, 0.1),
minexptime = 1,
maxexptime = 2,
minsize = 1.5,
maxsize = 1.5,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
})
end
end
function ARROW_ENTITY.on_step(self, dtime)
mcl_burning.tick(self.object, dtime, self)
-- mcl_burning.tick may remove object immediately
if not self.object:get_pos() then return end
self._time_in_air = self._time_in_air + .001
local pos = self.object:get_pos()
local dpos = vector.round(vector.new(pos)) -- digital pos
local node = minetest.get_node(dpos)
if self._stuck then
self._stucktimer = self._stucktimer + dtime
self._stuckrechecktimer = self._stuckrechecktimer + dtime
if self._stucktimer > ARROW_TIMEOUT then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
-- Drop arrow as item when it is no longer stuck
-- FIXME: Arrows are a bit slow to react and continue to float in mid air for a few seconds.
if self._stuckrechecktimer > STUCK_RECHECK_TIME then
local stuckin_def
if self._stuckin then
stuckin_def = minetest.registered_nodes[minetest.get_node(self._stuckin).name]
end
-- TODO: In MC, arrow just falls down without turning into an item
if stuckin_def and stuckin_def.walkable == false then
spawn_item(self, pos)
return
end
self._stuckrechecktimer = 0
end
-- Pickup arrow if player is nearby (not in Creative Mode)
local objects = minetest.get_objects_inside_radius(pos, 1)
for _,obj in ipairs(objects) do
if obj:is_player() then
if self._collectable and not minetest.is_creative_enabled(obj:get_player_name()) then
if obj:get_inventory():room_for_item("main", "mcl_bows:arrow") then
obj:get_inventory():add_item("main", "mcl_bows:arrow")
minetest.sound_play("item_drop_pickup", {
pos = pos,
max_hear_distance = 16,
gain = 1.0,
}, true)
end
return true
end,
sounds = {
on_entity_collision = function(self, _, _, obj)
if obj:is_player() then
return {{name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true}
end
mcl_burning.extinguish(self.object)
self.object:remove()
return
return {{name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true}
end
end
},
on_collide_with_solid = function(self, pos, node, node_def)
local def = node_def
local vel = self.object:get_velocity()
local dpos = vector.round(vector.new(pos)) -- digital pos
-- Check for object "collision". Done every tick (hopefully this is not too stressing)
else
if self._damage >= 9 and self._in_player == false then
minetest.add_particlespawner({
amount = 20,
time = .2,
minpos = vector.new(0,0,0),
maxpos = vector.new(0,0,0),
minvel = vector.new(-0.1,-0.1,-0.1),
maxvel = vector.new(0.1,0.1,0.1),
minexptime = 0.5,
maxexptime = 0.5,
minsize = 2,
maxsize = 2,
attached = self.object,
collisiondetection = false,
vertical = false,
texture = "mobs_mc_arrow_particle.png",
glow = 1,
})
end
local closest_object
local closest_distance
if self._deflection_cooloff > 0 then
self._deflection_cooloff = self._deflection_cooloff - dtime
end
local arrow_dir = self.object:get_velocity()
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
for hitpoint in raycast do
if hitpoint.type == "object" then
-- find the closest object that is in the way of the arrow
local ok = false
if hitpoint.ref:is_player() and enable_pvp then
ok = true
elseif not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
ok = true
end
end
if ok then
local dist = vector.distance(hitpoint.ref:get_pos(), pos)
if not closest_object or not closest_distance then
closest_object = hitpoint.ref
closest_distance = dist
elseif dist < closest_distance then
closest_object = hitpoint.ref
closest_distance = dist
end
end
end
end
if closest_object then
local obj = closest_object
local is_player = obj:is_player()
local lua = obj:get_luaentity()
if obj == self._shooter and self._time_in_air > 1.02 or obj ~= self._shooter and (is_player or (lua and (lua.is_mob or lua._hittable_by_projectile))) then
if obj:get_hp() > 0 then
-- Check if there is no solid node between arrow and object
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
for pointed_thing in ray do
if pointed_thing.type == "object" and pointed_thing.ref == closest_object then
-- Target reached! We can proceed now.
break
elseif pointed_thing.type == "node" then
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
local def = minetest.registered_nodes[nn]
if (not def) or def.walkable then
-- There's a node in the way. Delete arrow without damage
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
end
end
-- Punch target object but avoid hurting enderman.
if not lua or lua.name ~= "mobs_mc:rover" then
if not self._in_player then
damage_particles(vector.add(pos, vector.multiply(self.object:get_velocity(), 0.1)), self._is_critical)
end
if mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(obj, 5)
end
if not self._in_player and not self._blocked then
obj:punch(self.object, 1.0, {
full_punch_interval=1.0,
damage_groups={fleshy=self._damage},
}, self.object:get_velocity())
if obj:is_player() then
if not mcl_shields.is_blocking(obj) then
local placement
self._placement = math.random(1, 2)
if self._placement == 1 then
placement = "front"
else
placement = "back"
end
self._in_player = true
if self._placement == 2 then
self._rotation_station = 90
else
self._rotation_station = -90
end
self._y_position = random_arrow_positions("y", placement)
self._x_position = random_arrow_positions("x", placement)
if self._y_position > 6 and self._x_position < 2 and self._x_position > -2 then
self._attach_parent = "Head"
self._y_position = self._y_position - 6
elseif self._x_position > 2 then
self._attach_parent = "Arm_Right"
self._y_position = self._y_position - 3
self._x_position = self._x_position - 2
elseif self._x_position < -2 then
self._attach_parent = "Arm_Left"
self._y_position = self._y_position - 3
self._x_position = self._x_position + 2
else
self._attach_parent = "Body"
end
self._z_rotation = math.random(-30, 30)
self._y_rotation = math.random( -30, 30)
self.object:set_attach(
obj, self._attach_parent,
vector.new(self._x_position, self._y_position, random_arrow_positions("z", placement)),
vector.new(0, self._rotation_station + self._y_rotation, self._z_rotation)
)
else
self._blocked = true
self.object:set_velocity(vector.multiply(self.object:get_velocity(), -0.25))
end
minetest.after(150, function()
self.object:remove()
end)
else
self.object:remove()
end
end
end
if is_player then
if self._shooter and self._shooter:is_player() and not self._in_player and not self._blocked then
-- “Ding” sound for hitting another player
minetest.sound_play({name="mcl_bows_hit_player", gain=0.1}, {to_player=self._shooter:get_player_name()}, true)
end
end
if lua then
local entity_name = lua.name
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
-- TODO: This achievement should be given for the kill, not just a hit
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
end
end
end
if not self._in_player and not self._blocked then
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
end
end
if not obj:is_player() then
mcl_burning.extinguish(self.object)
if self._piercing == 0 then
self.object:remove()
end
end
return
end
end
end
-- Check for node collision
if self._lastpos.x~=nil and not self._stuck then
local def = minetest.registered_nodes[node.name]
local vel = self.object:get_velocity()
-- Arrow has stopped in one axis, so it probably hit something.
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
if (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) then
-- Check for the node to which the arrow is pointing
local dir
if math.abs(vel.y) < 0.00001 then
if self._lastpos.y < pos.y then
if self._last_pos.y < pos.y then
dir = vector.new(0, 1, 0)
else
dir = vector.new(0, -1, 0)
@ -380,50 +200,147 @@ function ARROW_ENTITY.on_step(self, dtime)
-- Reset deflection cooloff timer to prevent many deflections happening in quick succession
self._deflection_cooloff = 1.0
end
return
end
-- Node was walkable, make arrow stuck
self._stuck = true
self._stucktimer = 0
self._stuckrechecktimer = 0
self.object:set_velocity(vector.new(0, 0, 0))
self.object:set_acceleration(vector.new(0, 0, 0))
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
tnt.ignite(self._stuckin)
end
-- Ignite Campfires
if mod_campfire and mcl_burning.is_burning(self.object) and minetest.get_item_group(snode.name, "campfire") ~= 0 then
mcl_campfires.light_campfire(self._stuckin)
end
-- Activate target
if mod_target and snode.name == "mcl_target:target_off" then
mcl_target.hit(self._stuckin, 1) --10 redstone ticks
end
-- Push the button! Push, push, push the button!
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
local bdir = minetest.wallmounted_to_dir(node.param2)
-- Check the button orientation
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
mesecon.push_button(dpos, node)
end
end
end,
on_collide_with_entity = function(self, pos, obj)
local is_player = obj:is_player()
local lua = obj:get_luaentity()
-- Make sure collision is valid
if obj == self._shooter then
if self._time_in_air < 1.02 then return end
else
if not (is_player or (lua and (lua.is_mob or lua._hittable_by_projectile))) then
return
end
end
-- Node was walkable, make arrow stuck
self._stuck = true
self._stucktimer = 0
self._stuckrechecktimer = 0
self.object:set_velocity(vector.new(0, 0, 0))
self.object:set_acceleration(vector.new(0, 0, 0))
minetest.sound_play({name="mcl_bows_hit_other", gain=0.3}, {pos=self.object:get_pos(), max_hear_distance=16}, true)
if mcl_burning.is_burning(self.object) and snode.name == "mcl_tnt:tnt" then
tnt.ignite(self._stuckin)
if obj:get_hp() > 0 then
-- Check if there is no solid node between arrow and object
-- TODO: remove. this code should never occur if vl_projectile is working correctly
local ray = minetest.raycast(self.object:get_pos(), obj:get_pos(), true)
for pointed_thing in ray do
if pointed_thing.type == "object" and pointed_thing.ref == obj then
-- Target reached! We can proceed now.
break
elseif pointed_thing.type == "node" then
local nn = minetest.get_node(minetest.get_pointed_thing_position(pointed_thing)).name
local def = minetest.registered_nodes[nn]
if (not def) or def.walkable then
-- There's a node in the way. Delete arrow without damage
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
end
end
-- Ignite Campfires
if mod_campfire and mcl_burning.is_burning(self.object) and minetest.get_item_group(snode.name, "campfire") ~= 0 then
mcl_campfires.light_campfire(self._stuckin)
end
-- Activate target
if mod_target and snode.name == "mcl_target:target_off" then
mcl_target.hit(self._stuckin, 1) --10 redstone ticks
end
-- Push the button! Push, push, push the button!
if mod_button and minetest.get_item_group(node.name, "button") > 0 and minetest.get_item_group(node.name, "button_push_by_arrow") == 1 then
local bdir = minetest.wallmounted_to_dir(node.param2)
-- Check the button orientation
if vector.equals(vector.add(dpos, bdir), self._stuckin) then
mesecon.push_button(dpos, node)
if lua then
local entity_name = lua.name
-- Achievement for hitting skeleton, wither skeleton or stray (TODO) with an arrow at least 50 meters away
-- NOTE: Range has been reduced because mobs unload much earlier than that ... >_>
-- TODO: This achievement should be given for the kill, not just a hit
if self._shooter and self._shooter:is_player() and vector.distance(pos, self._startpos) >= 20 then
if mod_awards and (entity_name == "mobs_mc:skeleton" or entity_name == "mobs_mc:stray" or entity_name == "mobs_mc:witherskeleton") then
awards.unlock(self._shooter:get_player_name(), "mcl:snipeSkeleton")
end
end
end
end
elseif (def and def.liquidtype ~= "none") then
-- Slow down arrow in liquids
local v = def.liquid_viscosity
if not v then
v = 0
if not obj:is_player() then
mcl_burning.extinguish(self.object)
if self._piercing == 0 then
self.object:remove()
end
end
--local old_v = self._viscosity
end
},
on_step = function(self, dtime)
mcl_burning.tick(self.object, dtime, self)
-- mcl_burning.tick may remove object immediately
if not self.object:get_pos() then return end
self._time_in_air = self._time_in_air + dtime
local pos = self.object:get_pos()
--local dpos = vector.round(vector.new(pos)) -- digital pos
--local node = minetest.get_node(dpos)
if self._stuck then
return stuck_arrow_on_step(self, dtime)
end
-- Add tracer
if self._damage >= 9 and self._in_player == false then
minetest.add_particlespawner({
amount = 20,
time = .2,
minpos = vector.new(0,0,0),
maxpos = vector.new(0,0,0),
minvel = vector.new(-0.1,-0.1,-0.1),
maxvel = vector.new(0.1,0.1,0.1),
minexptime = 0.5,
maxexptime = 0.5,
minsize = 2,
maxsize = 2,
attached = self.object,
collisiondetection = false,
vertical = false,
texture = "mobs_mc_arrow_particle.png",
glow = 1,
})
end
if self._deflection_cooloff > 0 then
self._deflection_cooloff = self._deflection_cooloff - dtime
end
-- TODO: change to use vl_physics
-- TODO: move to vl_projectile
local def = minetest.registered_nodes[minetest.get_node(pos).name]
if def and def.liquidtype ~= "none" then
-- Slow down arrow in liquids
local v = def.liquid_viscosity or 0
self._viscosity = v
local vpenalty = math.max(0.1, 0.98 - 0.1 * v)
local vel = self.object:get_velocity()
if math.abs(vel.x) > 0.001 then
vel.x = vel.x * vpenalty
end
@ -432,79 +349,70 @@ function ARROW_ENTITY.on_step(self, dtime)
end
self.object:set_velocity(vel)
end
end
-- Update yaw
if not self._stuck then
-- Process as projectile
vl_projectile.update_projectile(self, dtime)
-- Update yaw
local vel = self.object:get_velocity()
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
local pitch = dir_to_pitch(vel)
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
end
-- Update internal variable
self._lastpos = pos
end
-- Force recheck of stuck arrows when punched.
-- Otherwise, punching has no effect.
function ARROW_ENTITY.on_punch(self)
if self._stuck then
self._stuckrechecktimer = STUCK_RECHECK_TIME
end
end
function ARROW_ENTITY.get_staticdata(self)
local out = {
lastpos = self._lastpos,
startpos = self._startpos,
damage = self._damage,
is_critical = self._is_critical,
stuck = self._stuck,
stuckin = self._stuckin,
stuckin_player = self._in_player,
}
if self._stuck then
-- If _stucktimer is missing for some reason, assume the maximum
if not self._stucktimer then
self._stucktimer = ARROW_TIMEOUT
if vel and not self._stuck then
local yaw = minetest.dir_to_yaw(vel)+YAW_OFFSET
local pitch = dir_to_pitch(vel)
self.object:set_rotation({ x = 0, y = yaw, z = pitch })
end
out.stuckstarttime = minetest.get_gametime() - self._stucktimer
end
if self._shooter and self._shooter:is_player() then
out.shootername = self._shooter:get_player_name()
end
return minetest.serialize(out)
end
end,
function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
self._time_in_air = 1.0
local data = minetest.deserialize(staticdata)
if data then
self._stuck = data.stuck
if data.stuck then
if data.stuckstarttime then
-- First, check if the stuck arrow is aleady past its life timer.
-- If yes, delete it.
self._stucktimer = minetest.get_gametime() - data.stuckstarttime
if self._stucktimer > ARROW_TIMEOUT then
mcl_burning.extinguish(self.object)
self.object:remove()
return
end
end
-- Perform a stuck recheck on the next step.
-- Force recheck of stuck arrows when punched.
-- Otherwise, punching has no effect.
on_punch = function(self)
if self._stuck then
self._stuckrechecktimer = STUCK_RECHECK_TIME
self._stuckin = data.stuckin
end
end,
get_staticdata = function(self)
local out = {}
local save_fields = self._save_fields
for i = 1,#save_fields do
local field = save_fields[i]
out[field] = self["_"..field]
end
-- Get the remaining arrow state
self._lastpos = data.lastpos
self._startpos = data.startpos
self._damage = data.damage
self._is_critical = data.is_critical
if self._stuck then
-- If _stucktimer is missing for some reason, assume the maximum
if not self._stucktimer then
self._stucktimer = ARROW_TIMEOUT
end
out.stuckstarttime = minetest.get_gametime() - self._stucktimer
end
if self._shooter and self._shooter:is_player() then
out.shootername = self._shooter:get_player_name()
end
return minetest.serialize(out)
end,
on_activate = function(self, staticdata, dtime_s)
self.object:set_armor_groups({ immortal = 1 })
self._time_in_air = 1.0
local data = minetest.deserialize(staticdata)
if not data then return end
-- Restore arrow state
local save_fields = self._save_fields
for i = 1,#save_fields do
local field = save_fields[i]
self["_"..field] = data[field]
end
if data.stuckstarttime then
-- First, check if the stuck arrow is aleady past its life timer.
-- If yes, delete it.
self._stucktimer = minetest.get_gametime() - data.stuckstarttime
end
-- Perform a stuck recheck on the next step.
self._stuckrechecktimer = STUCK_RECHECK_TIME
if data.shootername then
local shooter = minetest.get_player_by_name(data.shootername)
if shooter and shooter:is_player() then
@ -515,9 +423,8 @@ function ARROW_ENTITY.on_activate(self, staticdata, dtime_s)
if data.stuckin_player then
self.object:remove()
end
end
self.object:set_armor_groups({ immortal = 1 })
end
end,
})
minetest.register_on_respawnplayer(function(player)
for _, obj in pairs(player:get_children()) do
@ -528,8 +435,6 @@ minetest.register_on_respawnplayer(function(player)
end
end)
minetest.register_entity("mcl_bows:arrow_entity", ARROW_ENTITY)
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_mobitems") then
minetest.register_craft({
output = "mcl_bows:arrow 4",
@ -544,3 +449,4 @@ end
if minetest.get_modpath("doc_identifier") then
doc.sub.identifier.register_object("mcl_bows:arrow_entity", "craftitems", "mcl_bows:arrow")
end

View File

@ -76,6 +76,7 @@ function mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage,
le._startpos = pos
le._knockback = knockback
le._collectable = collectable
le._arrow_item = arrow_item
minetest.sound_play("mcl_bows_bow_shoot", {pos=pos, max_hear_distance=16}, true)
if shooter and shooter:is_player() then
if obj:get_luaentity().player == "" then

View File

@ -1,6 +1,6 @@
name = mcl_bows
author = Arcelmi
description = This mod adds bows and arrows for MineClone 2.
depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields, mcl_fovapi, mcl_luck
depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields, mcl_fovapi, mcl_luck, vl_projectile
optional_depends = awards, mcl_achievements, mcl_core, mcl_mobitems, playerphysics, doc, doc_identifier, mesecons_button

View File

@ -340,7 +340,6 @@ local function on_put(pos, listname, index, stack, player)
local inv = meta:get_inventory()
local str = ""
local stack
local oldparam2 = minetest.get_node(pos).param2
for i=1, inv:get_size("stand") do
stack = inv:get_stack("stand", i)
if not stack:is_empty() then
@ -348,7 +347,7 @@ local function on_put(pos, listname, index, stack, player)
else str = str.."0"
end
end
minetest.swap_node(pos, {name = "mcl_brewing:stand_"..str, param2 = oldparam2})
minetest.swap_node(pos, {name = "mcl_brewing:stand_"..str})
minetest.get_node_timer(pos):start(1.0)
--some code here to enforce only potions getting placed on stands
end

View File

@ -205,8 +205,7 @@ walkover.register_global(function(pos, _, player)
if frost_walker <= 0 then
return
end
-- 1011 = sqrt(4096000)/2; 4096000 is the max number of nodes for find_nodes_in_area_under_air
local radius = math.min(frost_walker + 2, 1011)
local radius = frost_walker + 2
local minp = {x = pos.x - radius, y = pos.y, z = pos.z - radius}
local maxp = {x = pos.x + radius, y = pos.y, z = pos.z + radius}
local positions = minetest.find_nodes_in_area_under_air(minp, maxp, "mcl_core:water_source")

View File

@ -22,7 +22,6 @@ minetest.register_entity("mcl_end:ender_eye", {
self._phase = 0
end
end
if not self._luck then self._luck = 0 end
end,
on_step = function(self, dtime)

View File

@ -53,7 +53,7 @@ for i=0, 3 do
end
if 3 ~= i and mcl_dye and
clicker:get_wielded_item():get_name() == "mcl_bone_meal:bone_meal" then
mcl_dye.apply_bone_meal({under=pos, above=vector.offset(pos,0,1,0)},clicker)
mcl_dye.apply_bone_meal({under=pos},clicker)
if not minetest.is_creative_enabled(pn) then
itemstack:take_item()
end

View File

@ -654,7 +654,7 @@ end
minetest.register_alias("mcl_hoppers:hopper_item", "mcl_hoppers:hopper")
minetest.register_lbm({
label = "Update hopper formspecs (0.60.0)",
label = "Update hopper formspecs (0.60.0",
name = "mcl_hoppers:update_formspec_0_60_0",
nodenames = {"group:hopper"},
run_at_every_load = false,

View File

@ -638,7 +638,7 @@ function mcl_itemframes.create_base_definitions()
paramtype = "light",
paramtype2 = "facedir",
sunlight_propagates = true,
groups = { dig_immediate = 3, deco_block = 1, dig_by_piston = 1, container = 1, supported_node_facedir = 1 },
groups = { dig_immediate = 3, deco_block = 1, dig_by_piston = 1, container = 1, attached_node_facedir = 1 },
sounds = mcl_sounds.node_sound_defaults(),
node_placement_prediction = "",

View File

@ -266,7 +266,7 @@ minetest.register_node("mcl_mangrove:river_water_logged_roots",rwlroots)
minetest.register_node("mcl_mangrove:mangrove_mud_roots", {
description = S("Muddy Mangrove Roots"),
_tt_help = S("Crafted with Mud and Mangrove roots"),
_tt_help = S("crafted with Mud and Mangrove roots"),
_doc_items_longdesc = S("Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it."),
tiles = {
"mcl_mud.png^mcl_mangrove_roots_top.png",

View File

@ -21,7 +21,7 @@ water logged mangrove roots=racines de palétuvier immergées
Mangrove roots, despite being a full block, can be waterlogged and do not flow water out=Les racines de palétuvier sont un bloc plein mais qui peut être immergé et ne remplace pas l'eau.
These cannot be crafted yet only occure when get in contact of water.=Elles ne peuvent être fabriquées mais se forment au contact de l'eau.
Muddy Mangrove Roots=Racines de palétuvier boueuses
Crafted with Mud and Mangrove roots=Fabriqué avec de la boue et des racines de palétuvier
crafted with Mud and Mangrove roots=fabriqué avec de la boue et des racines de palétuvier
Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it.=Les racines de palétuvier boueuses sont un bloc du marécage de la mangrove. Il noie un joueur à l'intérieur.
Mangrove Door=Porte en palétuvier
Wooden doors are 2-block high barriers which can be opened or closed by hand and by a redstone signal.=Les portes en bois sont des barrières hautes à 2 blocs qui peuvent être ouvertes ou fermées à la main et par un signal redstone.

View File

@ -21,7 +21,7 @@ water logged mangrove roots=水没したマングローブの根
Mangrove roots, despite being a full block, can be waterlogged and do not flow water out=マングローブの根は、フルブロックであるにもかかわらず水没することがあり、水が流出しない
These cannot be crafted yet only occure when get in contact of water.=これはクラフトできないものの、水と接触したときだけ発生します。
Muddy Mangrove Roots=泥に塗れたマングローブの根
Crafted with Mud and Mangrove roots=泥とマングローブの根で作られたもの
crafted with Mud and Mangrove roots=泥とマングローブの根で作られたもの
Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it.=泥に塗れたマングローブの根は、マングローブの沼地から1ブロックの場所にあります。
Mangrove Door=マングローブのドア
Wooden doors are 2-block high barriers which can be opened or closed by hand and by a redstone signal.=木製のドアは、高さ2ブロックの障壁で、手やレッドストーンの信号で開閉できます。

View File

@ -21,7 +21,7 @@ water logged mangrove roots=Raízes de Mangue Alagadas
Mangrove roots, despite being a full block, can be waterlogged and do not flow water out=Raízes de mangue, mesmo sendo um bloco inteiro, podem ser alagadas e não escorre água delas.
These cannot be crafted yet only occure when get in contact of water.=Essas não podem ser fabricadas ainda ocorrendo apenas quando tem contato com a água.
Muddy Mangrove Roots=Raízes Barrentas de Mangue
Crafted with Mud and Mangrove roots=Fabricadas com barro e raízes de mangue
crafted with Mud and Mangrove roots=Fabricadas com barro e raízes de mangue
Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it.=Raízes barrentas de mangue é um bloco dos pântanos de mangue. Afunda o jogador um pouco para dentro de si.
Mangrove Door=Porta de Mangue
Wooden doors are 2-block high barriers which can be opened or closed by hand and by a redstone signal.=Portas de madeira são barreiras de 2 blocos de altura as quais podem ser abertas ou fechadas pela mão e por um sinal de redstone.

View File

@ -21,7 +21,7 @@ water logged mangrove roots=Затопленные мангровые корни
Mangrove roots, despite being a full block, can be waterlogged and do not flow water out=Мангровые корни, не смотря на то что это полный блок, может быть затоплен и не выпускать воду наружу.
These cannot be crafted yet only occure when get in contact of water.=Нельзя скрафтить, появляется только при контакте с водой.
Muddy Mangrove Roots=Грязные мангровые корни
Crafted with Mud and Mangrove roots=Крафтится с помощью грязи и мангровых корней
crafted with Mud and Mangrove roots=Крафтится с помощью грязи и мангровых корней
Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it.=Грязные мангровые корни это блок из мангровых болот. Игрок немного погружается внутрь них.
Mangrove Door=Мангровая дверь
Wooden doors are 2-block high barriers which can be opened or closed by hand and by a redstone signal.=Деревянные двери это преграды высотой в 2 блока, которые можно открывать и закрывать вручную и по сигналу редстоуна.

View File

@ -21,7 +21,7 @@ water logged mangrove roots=
Mangrove roots, despite being a full block, can be waterlogged and do not flow water out=
These cannot be crafted yet only occure when get in contact of water.=
Muddy Mangrove Roots=
Crafted with Mud and Mangrove roots=
crafted with Mud and Mangrove roots=
Muddy Mangrove Roots is a block from mangrove swamp.It drowns player a bit inside it.=
Mangrove Door=
Wooden doors are 2-block high barriers which can be opened or closed by hand and by a redstone signal.=

View File

@ -63,8 +63,8 @@ local function set_doll_properties(doll, mob)
xs = doll_size_overrides[mob].x
ys = doll_size_overrides[mob].y
else
xs = (mobinfo.visual_size.x or 0) * 0.33333
ys = (mobinfo.visual_size.y or 0) * 0.33333
xs = mobinfo.visual_size.x * 0.33333
ys = mobinfo.visual_size.y * 0.33333
end
local prop = {
mesh = mobinfo.mesh,
@ -358,11 +358,6 @@ doll_def.on_activate = function(self, staticdata, dtime_s)
if mob == "" or mob == nil then
mob = default_mob
end
-- Handle conversion of mob spawners
local convert_to = (minetest.registered_entities[mob] or {})._convert_to
if convert_to then mob = convert_to end
set_doll_properties(self.object, mob)
self.object:set_velocity({x=0, y=0, z=0})
self.object:set_acceleration({x=0, y=0, z=0})
@ -395,11 +390,3 @@ minetest.register_lbm({
respawn_doll(pos)
end,
})
minetest.register_on_mods_loaded(function()
for name,mobinfo in pairs(minetest.registered_entities) do
if ( mobinfo.is_mob or name:find("mobs_mc") ) and not ( mobinfo.visual_size or mobinfo._convert_to ) then
minetest.log("warning", "Definition for "..tostring(name).." is missing field 'visual_size', mob spawners will not work properly")
end
end
end)

View File

@ -66,33 +66,6 @@ This section describes parts of the API related to defining and managing effects
`mcl_potions.get_effect(object, effect_name)` - returns a table containing values of the effect of the ID `effect_name` on the `object` if the object has the named effect, `false` otherwise.
* table returned by the above function is like this:
```lua
effect = {
dur = float -- duration of the effect in seconds, may be infinite
timer = float -- how much of the duration (in seconds) has already elapsed
no_particles = bool -- if this is true, no particles signifying this effect will appear
-- player-only fields
hud_index = int -- position in the HUD used by this effect (icon, level, timer) - probably meaningless outside mcl_potions
-- optional fields
factor = float -- power of the effect if the effect uses factor; this may mean different things depending on the effect
step = float -- how often (in seconds) the on_step() function of the effect is executed, if it exists
hit_timer = float -- how much of the step (in seconds) has already elapsed
-- effect-specific fields
-- effects in mcl_potions have their own fields here, for now external effects can't add any here
blocked = bool -- used by conduit power
high = bool -- used by nausea
vignette = int -- handle to the HUD vignette of the effect, used by effects that use one
absorb = float -- "HP" of the absorption effect
waypoints = table -- used by glowing, indexed by player ObjectRef, contains HUD handles for the glowing waypoints
flash = float -- used by darkness, denotes vision range modifier
flashdir = bool -- used by darkness, denotes whether vision range is increasing (or decreasing)
}
```
`mcl_potions.get_effect_level(object, effect_name)` returns the level of the effect of the ID `effect_name` on the `object`. If the effect has no levels, returns `1`. If the object doesn't have the effect, returns `0`. If the effect is not registered, returns `nil`.
@ -147,7 +120,6 @@ This section describes parts of the API related to defining and managing effects
#### Internally registered effects
You can't register effects going by these names, because they are already used:
* `invisibility`
* `poison`
* `regeneration`

View File

@ -372,9 +372,7 @@ mcl_potions.register_effect({
playerphysics.add_physics_factor(object, "gravity", "mcl_potions:slow_falling", 0.5)
end,
on_step = function(dtime, object, factor, duration)
local vel = object:get_velocity()
if not vel then return end
vel = vel.y
local vel = object:get_velocity().y
if vel < -3 then object:add_velocity(vector.new(0,-3-vel,0)) end
end,
on_end = function(object)
@ -432,9 +430,7 @@ mcl_potions.register_effect({
return S("moves body upwards at @1 nodes/s", factor)
end,
on_step = function(dtime, object, factor, duration)
local vel = object:get_velocity()
if not vel then return end
vel = vel.y
local vel = object:get_velocity().y
if vel<factor then object:add_velocity(vector.new(0,factor,0)) end
end,
particle_color = "#420E7E",
@ -456,16 +452,11 @@ mcl_potions.register_effect({
object:get_meta():set_int("night_vision", 1)
mcl_weather.skycolor.update_sky_color({object})
end,
on_load = function(object, factor)
object:get_meta():set_int("night_vision", 1)
mcl_weather.skycolor.update_sky_color({object})
end,
on_step = function(dtime, object, factor, duration)
mcl_weather.skycolor.update_sky_color({object})
end,
on_end = function(object)
local meta = object:get_meta()
if not meta then return end
meta:set_int("night_vision", 0)
mcl_weather.skycolor.update_sky_color({object})
end,
@ -508,9 +499,7 @@ mcl_potions.register_effect({
mcl_weather.skycolor.update_sky_color({object})
end,
on_end = function(object)
local meta = object:get_meta()
if not meta then return end
meta:set_int("darkness", 0)
object:get_meta():set_int("darkness", 0)
mcl_weather.skycolor.update_sky_color({object})
object:set_sky({fog = {
fog_distance = -1,
@ -1095,7 +1084,6 @@ function mcl_potions.update_haste_and_fatigue(player)
if f_fac ~= 1 then meta:set_float("mcl_potions:fatigue", 1 - f_fac)
else meta:set_string("mcl_potions:fatigue", "") end
meta:set_tool_capabilities()
meta:set_string("groupcaps_hash","")
mcl_enchanting.update_groupcaps(item)
if h_fac == 0 and f_fac == 1 then
player:set_wielded_item(item)
@ -1356,7 +1344,7 @@ minetest.register_globalstep(function(dtime)
potions_set_hud(object)
else
local ent = object:get_luaentity()
if ent and ent._mcl_potions then
if ent then
ent._mcl_potions["_EF_"..name] = nil
end
end
@ -1371,7 +1359,7 @@ minetest.register_globalstep(function(dtime)
end
else
local ent = object:get_luaentity()
if ent and ent._mcl_potions then
if ent then
ent._mcl_potions["_EF_"..name] = EF[name][object]
end
end
@ -1404,7 +1392,6 @@ function mcl_potions._reset_haste_fatigue_item_meta(player)
meta:set_string("mcl_potions:haste", "")
meta:set_string("mcl_potions:fatigue", "")
meta:set_tool_capabilities()
meta:set_string("groupcaps_hash","")
mcl_enchanting.update_groupcaps(item)
end
end
@ -1529,11 +1516,6 @@ function mcl_potions._load_player_effects(player)
local loaded = minetest.deserialize(meta:get_string("mcl_potions:_EF_"..name))
if loaded then
EF[name][player] = loaded
end
if EF[name][player] then -- this is needed because of legacy effects loaded separately
if effect.uses_factor and type(EF[name][player].factor) ~= "number" then
EF[name][player].factor = effect.level_to_factor(1)
end
if effect.on_load then
effect.on_load(player, EF[name][player].factor)
end
@ -1551,9 +1533,6 @@ function mcl_potions._load_entity_effects(entity)
local loaded = entity._mcl_potions["_EF_"..name]
if loaded then
EF[name][object] = loaded
if effect.uses_factor and not loaded.factor then
EF[name][object].factor = effect.level_to_factor(1)
end
if effect.on_load then
effect.on_load(object, EF[name][object].factor)
end
@ -1792,14 +1771,9 @@ end
local function target_valid(object, name)
if not object or object:get_hp() <= 0 then return false end
-- Don't apply effects to anything other than players and entities that have mcl_potions support
-- but are not bosses
local entity = object:get_luaentity()
if not object:is_player() and (not entity or entity.is_boss or not entity._mcl_potions) then
return false
end
if entity and entity.is_boss then return false end
-- Check resistances
for i=1, #registered_res_predicates do
if registered_res_predicates[i](object, name) then return false end
end
@ -1851,10 +1825,6 @@ end
function mcl_potions.give_effect_by_level(name, object, level, duration, no_particles)
if level == 0 then return false end
if not registered_effects[name] then
minetest.log("warning", "[mcl_potions] Trying to give unknown effect "..tostring(name))
return false
end
if not registered_effects[name].uses_factor then
return mcl_potions.give_effect(name, object, 0, duration, no_particles)
end

View File

@ -173,8 +173,8 @@ function mcl_potions.register_lingering(name, descr, color, def)
obj:set_velocity({x=dropdir.x*velocity,y=dropdir.y*velocity,z=dropdir.z*velocity})
obj:set_acceleration({x=dropdir.x*-3, y=-9.8, z=dropdir.z*-3})
local ent = obj:get_luaentity()
ent._potency = stack:get_meta():get_int("mcl_potions:potion_potent")
ent._plus = stack:get_meta():get_int("mcl_potions:potion_plus")
ent._potency = item:get_meta():get_int("mcl_potions:potion_potent")
ent._plus = item:get_meta():get_int("mcl_potions:potion_plus")
ent._effect_list = def._effect_list
end
})

View File

@ -1,283 +1,27 @@
# textdomain: mcl_potions
Invisibility=Invisibilité
body is invisible=le corps est invisible
Poison=Poison
-1 HP / @1 s=-1 PV / @1 s
Regeneration=Régénération
+1 HP / @1 s=+1 PV / @1 s
Strength=Force
+@1% melee damage=+@1% dégâts de mêlée
Weakness=Faiblesse
-@1% melee damage=-@1% dégâts de mêlée
Water Breathing=Respiration aquatique
limitless breathing under water=respiration illimitée sous l'eau
Dolphin's Grace=Grâce du dauphin
swimming gracefully=nage gracieuse
Leaping=Saut
+@1% jumping power=+@1% puissance de saut
-@1% jumping power=-@1% puissance de saut
Slow Falling=Chute lente
decreases gravity effects=diminue les effets de la gravité
Swiftness=Rapidité
+@1% running speed=+@1% vitesse de course
Slowness=Lenteur
-@1% running speed=-@1% vitesse de course
Levitation=Lévitation
moves body upwards at @1 nodes/s=déplace le corps vers le haut à @1 nœuds/s
Night Vision=Vision nocturne
improved vision during the night=améliore la vision durant la nuit
Darkness=Obscurité
surrounded by darkness=entouré d'obscurité
not seeing anything beyond @1 nodes=ne vois rien au-delà de @1 nœuds
Glowing=Surbrillance
more visible at all times=plus visible en permanence
Health Boost=Bonus de santé
HP increased by @1=PV augmentés de @1
Absorption=Absorption
absorbs up to @1 incoming damage=absorbe jusqu'à @1 dégâts reçus
Fire Resistance=Résistance au feu
resistance to fire damage=resistance aux dégâts du feu
Resistance=Résistance
resist @1% of incoming damage=résiste à @1% des dégâts reçus
Luck=Chance
Bad Luck=Malchance
Bad Omen=Mauvais présage
danger is imminent=un danger est imminent
Hero of the Village=Héros du village
Withering=Dépérissement
-1 HP / @1 s, can kill=-1 PV / @1 s, peut tuer
Frost=Gel
-1 HP / 1 s, can kill, -@1% running speed=-1 PV / @1 s, peut tuer, -@1% vitesse de course
Blindness=Cécité
impaired sight=déficience visuelle
Nausea=Nausée
not feeling very well...=ne se sent pas très bien
frequency: @1 / 1 s=fréquence : @1 / 1 s
Food Poisoning=Intoxication alimentaire
exhausts by @1 per second=s'épuise de @1 par seconde
Saturation=Saturation
saturates by @1 per second=sature de @1 par seconde
Haste=Célérité
+@1% mining and attack speed=+@1% de vitesse d'attaque et de minage
Fatigue=Fatigue
-@1% mining and attack speed=-@1% de vitesse d'attaque et de minage
Conduit Power=Force de conduit
+@1% mining and attack speed in water=+@1% de vitesse d'attaque et de minage sous l'eau
<effect>|heal|list|clear|remove <duration|heal-amount|effect>|INF [<level>] [<factor>] [NOPART]=<effet>|heal|list|clear|remove <durée|quantité-soin|effet>|INF [<niveau>] [<facteur>] [NOPART]
Add a status effect to yourself. Arguments: <effect>: name of status effect. Passing "list" as effect name lists available effects. Passing "heal" as effect name heals (or harms) by amount designed by the next parameter. Passing "clear" as effect name removes all effects. Passing "remove" as effect name removes the effect named by the next parameter. <duration>: duration in seconds. Passing "INF" as duration makes the effect infinite. (<heal-amount>: amount of healing when the effect is "heal", passing a negative value subtracts health. <effect>: name of a status effect to be removed when using "remove" as the previous parameter.) <level>: effect power determinant, bigger level results in more powerful effect for effects that depend on the level (no changes for other effects), defaults to 1, pass F to use low-level factor instead. <factor>: effect strength modifier, can mean different things depending on the effect, no changes for effects that do not depend on level/factor. NOPART at the end means no particles will be shown for this effect.=Ajoute un effet de statut à vous-même. Arguments : <effet> : nom de l'effet de statut. Utiliser "list" comme nom d'effet liste les effets disponibles. Utiliser "heal" comme nom d'effet soigne (ou blesse) d'un nombre défini par le paramètre suivant. Utiliser "clear" comme nom d'effet enleve tous les effets. Utiliser "remove" comme nom d'effet enlève l'effet nommé dans le paramètre suivant. <durée> : durée en secondes. Utiliser "INF" comme durée rends l'effet infini. (<quantité-soin> : quantité de soin lorsque l'effet est "heal", utiliser une valeur négative enlève de la santé. <effet> : nom de l'effet de statut à enlever lors de l'utilisation de "remove" comme paramètre précédent.) <niveau> : détermine la puissance de l'effet, un niveau plus élevé se traduit par un effet plus puissant pour les effets qui dépendent du niveau (aucun changement pour les autres effets), défaut à 1, utiliser F pour utiliser un facteur de bas niveau à la place. <facteur> : effet modificateur de force, peut avoir différentes significations en fonction de l'effet, pas de changement pour les effets qui ne dépendent pas du niveau/facteur. NOPART à la fin signifie qu'aucune particule ne sera affichée pour cet effet.
Missing effect parameter!=Paramètre d'effet manquant !
Missing or invalid heal amount parameter!=Paramètre de quantité de soin manquant ou invalide !
Player @1 healed by @2 HP.=Joueur @1 soigné de @2 PV.
Player @1 harmed by @2 HP.=Joueur @1 blessé de @2 PV.
Effects cleared for player @1=Effets effacés pour le joueur @1
Removed effect @1 from player @2=Effet @1 enlevé pour le joueur @2
@1 is not an available status effect.=@1 n'est pas un effet de statut disponible.
Missing or invalid duration parameter!=Paramètre de durée manquant ou invalide !
Invalid level parameter!=Paramètre de niveau invalide !
Missing or invalid factor parameter when level is F!=Paramètre de facteur manquant ou invalide quand le niveau est F!
@1 effect given to player @2 for @3 seconds with factor of @4.=Effet @1 donné au joueur @2 pour @3 seconds avec un facteur de @4.
@1 effect given to player @2 for @3 seconds.=Effet @1 donné au joueur @2 pour @3 seconds.
Giving effect @1 to player @2 failed.=L'attribution de l'effet @1 au joueur @2 a échoué.
@1 effect on level @2 given to player @3 for @4 seconds.=Effet @1 de niveau @2 donné au joueur @3 pendant @4 secondes.
A throwable potion that will shatter on impact, where it gives all nearby players and mobs a status effect or a set of status effects.=Une potion jetable qui se brisera à l'impact, où elle donne à tous les joueurs et créatures proches un effet de statut ou un ensemble d'effets de statut.
Use the “Punch” key to throw it.=Utilisez la touche "Frapper" pour la lancer.
A throwable potion that will shatter on impact, where it creates a magic cloud that lingers around for a while. Any player or mob inside the cloud will receive the potion's effect or set of effects, possibly repeatedly.=Une potion jetable qui se brisera à l'impact, où elle crée un nuage magique qui persiste pendant un moment. Tout joueur ou mob à l'intérieur du nuage recevra l'effet de la potion ou un ensemble d'effets, peut-être à plusieurs reprises.
This particular arrow is tipped and will give an effect when it hits a player or mob.=Cette flèche particulière est enchantée et donnera un effet lorsqu'elle touche un joueur ou un mob.
Use the “Place” key to drink it.=Utilisez la touche "Utiliser" pour la boire.
Drinking a potion gives you a particular effect or set of effects.=Boire une potion vous donne un effet particulier ou un ensemble d'effets.
@1 Potion @2=@1 potion @2
@1 Potion=Potion @1
Potion @1=Potion @1
Strange Potion=Potion étrange
Splash @1=@1 jetable
Lingering @1=@1 persistante
@1 Arrow @2=@1 flèche @2
@1 Arrow=Flèche @1
Arrow @1=Flèche @1
Strange Tipped Arrow=Flèche à pointe étrange
Mighty=puissante
of Trolling=de trollage
Dragon's Breath=Souffle du dragon
This item is used in brewing and can be combined with splash potions to create lingering potions.=Cet objet est utilisé dans le brassage et peut être combiné avec des potions jetables pour créer des potions persistantes.
Awkward=étrange
No effect=Aucun effet
Has an awkward taste and is used for brewing potions.=A un goût étrange et est utilisée pour préparer des potions.
Mundane=banale
Has a terrible taste and is not really useful for brewing potions.=A un goût terrible et n'est pas vraiment utile pour préparer des potions.
Thick=épaisse
Has a bitter taste and may be useful for brewing potions.=A un goût amer et peut être utile pour préparer des potions.
of Healing=de guérison
+@1 HP=+@1 PV
Instantly heals.=Guérit instantanément
of Harming=de dégâts
-@1 HP=-@1 PV
Instantly deals damage.=Inflige des dégâts instantanément.
of Night Vision=de vision nocturne
Increases the perceived brightness of light under a dark sky.=Augmente la luminosité de la lumière perçue sous un ciel sombre.
of Swiftness=de rapidité
Increases walking speed.=Augmente la vitesse de marche.
of Slowness=de lenteur
Decreases walking speed.=Diminue la vitesse de marche.
of Leaping=de saut
Increases jump strength.=Augmente la force de saut.
of Withering=de dépérissement
Applies the withering effect which deals damage at a regular interval and can kill.=Applique l'effet de dépérissement qui inflige des dégâts à intervalles réguliers et peut tuer.
of Poison=de poison
Applies the poison effect which deals damage at a regular interval.=Applique l'effet de poison qui inflige des dégâts à intervalles réguliers.
of Regeneration=de régénération
Regenerates health over time.=Régénère la santé au fil du temps.
of Invisibility=d'invisibilité
Grants invisibility.=Confère l'invisibilité.
of Water Breathing=de respiration aquatique
Grants limitless breath underwater.=Confère une respiration illimitée sous l'eau.
of Fire Resistance=de résistance au feu
Grants immunity to damage from heat sources like fire.=Confère une immunité aux dégâts causés par des sources de chaleur comme le feu.
of Strength=de force
Increases attack power.=Augmente la puissance d'attaque.
of Weakness=de faiblesse
Decreases attack power.=Diminue la puissance d'attaque.
of Slow Falling=de chute lente
Instead of falling, you descend gracefully.=Au lieu de tomber, vous descendez avec grâce.
of Levitation=de lévitation
Floats body slowly upwards.=Le corps flotte lentement vers le haut.
of Darkness=d'obscurité
Surrounds with darkness.=Entoure d'obscurité.
of Glowing=de surbrillance
Highlights for others to see.=Mise en valeur pour que les autres voient.
of Health Boost=de bonus de santé
Increases health.=Augmente la santé.
of Absorption=d'absorption
Absorbs some incoming damage.=Absorbe les dégâts reçus.
of Resistance=de résistance
Decreases damage taken.=Diminue les dégâts subis.
of Stone Cloak=de manteau de pierre
Decreases damage taken at the cost of speed.=Diminue les dégâts subis au détriment de la vitesse.
of Luck=de chance
Increases luck.=Augmente la chance.
of Bad Luck=de malchance
Decreases luck.=Diminue la chance.
of Frost=de gel
Freezes...=Gèle...
of Blindness=de cécité
Impairs sight.=Altére la vue.
of Nausea=de nausée
Disintegrates senses.=Désintègre les sens.
of Food Poisoning=d'intoxication alimentaire
Moves bowels too fast.=Déplace les intestins trop rapidement.
of Saturation=de saturation
Satisfies hunger.=Satisfait la faim.
of Haste=de célérité
Increases digging and attack speed.=Augmente la vitesse de creusage et d'attaque.
of Fatigue=de fatigue
Decreases digging and attack speed.=Diminue la vitesse de creusage et d'attaque.
Ominous=funeste
Attracts danger.=Attire le danger.
Unknown Potion=Potion inconnue
Right-click to identify=Clic droit pour identifier
Unknown Tipped Arrow=Flèche à pointe inconnue
<effect> <duration> [<factor>]=<effet> <durée> [<facteur>]
Add a status effect to yourself. Arguments: <effect>: name of status effect, e.g. poison. <duration>: duration in seconds. <factor>: effect strength multiplier (1 @= 100%)=Ajoutez-vous un effet de statut. Arguments: <effet>: nom de l'effet de statut, par ex. poison. <duration>: durée en secondes. <facteur>: multiplicateur de force d'effet (1 @= 100%)
Missing effect parameter!=Paramètre d'effet manquant!
Missing or invalid duration parameter!=Paramètre durée manquant ou invalide!
Invalid factor parameter!=Paramètre facteur invalide!
@1 is not an available status effect.=@1 n'est pas un effet disponible.
Fermented Spider Eye=Oeil d'araignée fermenté
Try different combinations to create potions.=Essayez différentes combinaisons pour créer des potions.
Glass Bottle=Bouteille en verre
Liquid container=Récipient de liquide
A glass bottle is used as a container for liquids and can be used to collect water directly.=Une bouteille en verre est utilisée comme récipient pour les liquides et peut être utilisée pour recueillir l'eau directement.
A glass bottle is used as a container for liquids and can be used to collect water directly.=Une bouteille en verre est utilisée comme récipient pour les liquides et peut être utilisée pour collecter l'eau directement.
To collect water, use it on a cauldron with water (which removes a level of water) or any water source (which removes no water).=Pour récolter l'eau, utilisez la sur un chaudron avec de l'eau (qui enlève un niveau d'eau) ou toute source d'eau (qui n'enlève pas d'eau).
To collect water, use it on a cauldron with water (which removes a level of water) or any water source (which removes no water).=Pour collecter l'eau, poser la sur un chaudron avec de l'eau (qui enlève un niveau d'eau) ou toute source d'eau (qui n'enlève pas d'eau).
Water Bottle=Bouteille d'eau
Water bottles can be used to fill cauldrons. Drinking water has no effect.=Les bouteilles d'eau peuvent être utilisées pour remplir les chaudrons. Boire l'eau n'a aucun effet.
Water bottles can be used to fill cauldrons. Drinking water has no effect.=Les bouteilles d'eau peuvent être utilisées pour remplir les chaudrons. L'eau potable n'a aucun effet.
Use the “Place” key to drink. Place this item on a cauldron to pour the water into the cauldron.=Utilisez la touche "Utiliser" pour boire. Placez cet article sur un chaudron pour verser l'eau dans le chaudron.
River Water Bottle=Bouteille d'eau de rivière
River water bottles can be used to fill cauldrons. Drinking it has no effect.=Les bouteilles d'eau de rivière peuvent être utilisées pour remplir les chaudrons. La boire n'a aucun effet.
River water bottles can be used to fill cauldrons. Drinking it has no effect.=Les bouteilles d'eau de rivière peuvent être utilisées pour remplir les chaudrons. Le boire n'a aucun effet.
Use the “Place” key to drink. Place this item on a cauldron to pour the river water into the cauldron.=Utilisez la touche "Utiliser" pour boire. Placez cet objet sur un chaudron pour verser l'eau de la rivière dans le chaudron.
@ -293,3 +37,79 @@ A throwable water bottle that will shatter on impact, where it creates a cloud o
Glistering Melon=Melon étincelant
This shiny melon is full of tiny gold nuggets and would be nice in an item frame. It isn't edible and not useful for anything else.=Ce melon brillant est plein de minuscules pépites d'or et serait bien dans un cadre d'objet. Il n'est pas comestible et n'est utile à rien d'autre.
A throwable potion that will shatter on impact, where it creates a magic cloud that lingers around for a while. Any player or mob inside the cloud will receive the potion's effect, possibly repeatedly.=Une potion jetable qui se brisera à l'impact, où elle crée un nuage magique qui persiste pendant un moment. Tout joueur ou mob à l'intérieur du nuage recevra l'effet de la potion, peut-être à plusieurs reprises.
Use the “Punch” key to throw it.=Utilisez la touche "Frapper" pour le lancer.
Use the “Place” key to drink it.=Utilisez la touche "Utiliser" pour le boire.
Drinking a potion gives you a particular effect.=Boire une potion vous donne un effet particulier.
1 HP/@1s | @2=1 PV/@1s | @2
@1 HP=@1 PV
@1 Potion=Potion @1
Splash @1 Potion=Potion @1 jetable
Lingering @1 Potion=Potion @1 persistante
Arrow of @1=Flêche de @1
II= II
IV= IV
@1 Potion@2=@1 Potion@2
Splash @1@2 Potion=Potion @1@2 jetable
Lingering @1@2 Potion=Potion @1@2 persistante
Arrow of @1@2=Flêche de @1@2
@1 + Potion=@1 + Potion
Splash @1 + Potion=Potion @1 + jetable
Lingering @1 + Potion=Potion @1 + persistante
Arrow of @1 +=Flêche de @1 +
Awkward Potion=Potion étrange
Awkward Splash Potion=Potion étrange jetable
Awkward Lingering Potion=Potion étrange persistante
Has an awkward taste and is used for brewing potions.=A un goût étrange et est utilisé pour préparer des potions.
Mundane Potion=Potion banale
Mundane Splash Potion=Potion banale jetable
Mundane Lingering Potion=Potion banale persistante
Has a terrible taste and is not useful for brewing potions.=A un goût terrible et n'est pas utile pour préparer des potions.
Thick Potion=Potion épaisse
Thick Splash Potion=Potion épaisse jetable
Thick Lingering Potion=Potion épaisse persistante
Has a bitter taste and is not useful for brewing potions.=A un goût amer et n'est pas utile pour préparer des potions.
Dragon's Breath=Souffle du dragon
This item is used in brewing and can be combined with splash potions to create lingering potions.=Cet objet est utilisé dans le brassage et peut être combiné avec des potions d'éclaboussures pour créer des potions persistantes.
Healing=Guérison
+4 HP=+4 PV
+8 HP=+8 PV
Instantly heals.=Guérit instantanément.
Harming=Dégâts
-6 HP=-6 PV
-12 HP=-12 PV
Instantly deals damage.=Donne des dégâts instantanément.
Night Vision=Vision Nocturne
Increases the perceived brightness of light under a dark sky.=Augmente la luminosité perçue de la lumière sous un ciel sombre.
Swiftness=Rapidité
Increases walking speed.=Augmente la vitesse de marche.
Slowness=Lenteur
Decreases walking speed.=Diminue la vitesse de marche.
Leaping=Saut
Increases jump strength.=Augmente la force de saut.
Poison=Poison
Applies the poison effect which deals damage at a regular interval.=Applique l'effet de poison qui inflige des dégâts à intervalle régulier.
Regeneration=Régénération
Regenerates health over time.=Régénère la santé au fil du temps.
Invisibility=Invisibilité
Grants invisibility.=Accorde l'invisibilité.
Water Breathing=Respiration aquatique
Grants limitless breath underwater.=Donne une respiration illimitée sous l'eau.
Fire Resistance=Résistance au feu
Grants immunity to damage from heat sources like fire.=Confère une immunité aux dégâts causés par des sources de chaleur comme le feu.
Weakness=Faiblesse
Weakness +=Faiblesse +
Strength=Force
Strength II=Force II
Strength +=Force +
Try different combinations to create potions.=Essayez différentes combinaisons pour créer des potions.
No effect=Aucun effet
A throwable potion that will shatter on impact, where it gives all nearby players and mobs a status effect.=Une potion jetable qui se brisera à l'impact, où elle donne à tous les joueurs et créatures proches un effet de statut.
This particular arrow is tipped and will give an effect when it hits a player or mob.=Cette flèche particulière est enchantée et donnera un effet lorsqu'elle touche un joueur ou un mob.

View File

@ -841,22 +841,20 @@ local function replace_legacy_potion(itemstack)
end
local compat = "mcl_potions:compat_potion"
local compat_arrow = "mcl_potions:compat_arrow"
local compat_def = {
description = S("Unknown Potion") .. "\n" .. minetest.colorize("#ff0", S("Right-click to identify")),
minetest.register_craftitem(compat, {
description = S("Unknown Potion"),
_tt_help = S("Right-click to identify"),
image = "mcl_potions_potion_overlay.png^[colorize:#00F:127^mcl_potions_potion_bottle.png^vl_unknown.png",
groups = {not_in_creative_inventory = 1},
on_secondary_use = replace_legacy_potion,
on_place = replace_legacy_potion,
}
local compat_arrow_def = {
description = S("Unknown Tipped Arrow") .. "\n" .. minetest.colorize("#ff0", S("Right-click to identify")),
})
minetest.register_craftitem(compat_arrow, {
description = S("Unknown Tipped Arrow"),
_tt_help = S("Right-click to identify"),
image = "mcl_bows_arrow_inv.png^(mcl_potions_arrow_inv.png^[colorize:#FFF:100)^vl_unknown.png",
groups = {not_in_creative_inventory = 1},
on_secondary_use = replace_legacy_potion,
on_place = replace_legacy_potion,
}
minetest.register_craftitem(compat, compat_def)
minetest.register_craftitem(compat_arrow, compat_arrow_def)
})
local old_potions_plus = {
"fire_resistance", "water_breathing", "invisibility", "regeneration", "poison",
@ -868,14 +866,14 @@ local old_potions_2 = {
}
for _, name in pairs(old_potions_2) do
minetest.register_craftitem("mcl_potions:" .. name .. "_2", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_2_splash", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_2_lingering", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_2_arrow", compat_arrow_def)
minetest.register_alias("mcl_potions:" .. name .. "_2", compat)
minetest.register_alias("mcl_potions:" .. name .. "_2_splash", compat)
minetest.register_alias("mcl_potions:" .. name .. "_2_lingering", compat)
minetest.register_alias("mcl_potions:" .. name .. "_2_arrow", compat_arrow)
end
for _, name in pairs(old_potions_plus) do
minetest.register_craftitem("mcl_potions:" .. name .. "_plus", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_plus_splash", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_plus_lingering", compat_def)
minetest.register_craftitem("mcl_potions:" .. name .. "_plus_arrow", compat_arrow_def)
minetest.register_alias("mcl_potions:" .. name .. "_plus", compat)
minetest.register_alias("mcl_potions:" .. name .. "_plus_splash", compat)
minetest.register_alias("mcl_potions:" .. name .. "_plus_lingering", compat)
minetest.register_alias("mcl_potions:" .. name .. "_plus_arrow", compat_arrow)
end

View File

@ -65,8 +65,8 @@ function mcl_potions.register_splash(name, descr, color, def)
obj:set_velocity({x=dropdir.x*velocity,y=dropdir.y*velocity,z=dropdir.z*velocity})
obj:set_acceleration({x=dropdir.x*-3, y=-9.8, z=dropdir.z*-3})
local ent = obj:get_luaentity()
ent._potency = stack:get_meta():get_int("mcl_potions:potion_potent")
ent._plus = stack:get_meta():get_int("mcl_potions:potion_plus")
ent._potency = item:get_meta():get_int("mcl_potions:potion_potent")
ent._plus = item:get_meta():get_int("mcl_potions:potion_plus")
ent._effect_list = def._effect_list
end
})

View File

@ -66,15 +66,13 @@ function mcl_stonecutter.register_recipe(input, output, count)
local fallthrough = mcl_stonecutter.registered_recipes[output]
if fallthrough then
for o, c in pairs(fallthrough) do
if not mcl_stonecutter.registered_recipes[input][o] then
mcl_stonecutter.register_recipe(input, o, c * count)
end
mcl_stonecutter.register_recipe(input, o, c * count)
end
end
for i, recipes in pairs(mcl_stonecutter.registered_recipes) do
for name, c in pairs(recipes) do
if name == input and not mcl_stonecutter.registered_recipes[i][output] then
if name == input then
mcl_stonecutter.register_recipe(i, output, c * count)
end
end

View File

@ -0,0 +1,69 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
local mod_target = minetest.get_modpath("mcl_target")
local how_to_throw = S("Use the punch key to throw.")
-- Egg
minetest.register_craftitem("mcl_throwing:egg", {
description = S("Egg"),
_tt_help = S("Throwable").."\n"..S("Chance to hatch chicks when broken"),
_doc_items_longdesc = S("Eggs can be thrown or launched from a dispenser and breaks on impact. There is a small chance that 1 or even 4 chicks will pop out of the egg."),
_doc_items_usagehelp = how_to_throw,
inventory_image = "mcl_throwing_egg.png",
stack_max = 16,
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:egg_entity"),
_on_dispense = mcl_throwing.dispense_function,
groups = { craftitem = 1 },
})
mcl_throwing.register_throwable_object("mcl_throwing:egg", "mcl_throwing:egg_entity", 22)
minetest.register_entity("mcl_throwing:egg_entity",{
physical = false,
timer=0,
textures = {"mcl_throwing_egg.png"},
visual_size = {x=0.45, y=0.45},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
on_step = vl_projectile.update_projectile,
_lastpos={},
_thrower = nil,
_vl_projectile = {
behaviors = {
vl_projectile.collides_with_solids,
},
on_collide_with_solid = function(self, pos, node)
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
-- 1/8 chance to spawn a chick
-- FIXME: Chicks have a quite good chance to spawn in walls
if math.random(1,8) ~= 1 then return end
mcl_mobs.spawn_child(self._lastpos, "mobs_mc:chicken")
-- BONUS ROUND: 1/32 chance to spawn 3 additional chicks
if math.random(1,32) ~= 1 then return end
local offsets = {
{ x=0.7, y=0, z=0 },
{ x=-0.7, y=0, z=-0.7 },
{ x=-0.7, y=0, z=0.7 },
}
for o=1, 3 do
local pos = vector.add(self._lastpos, offsets[o])
mcl_mobs.spawn_child(pos, "mobs_mc:chicken")
end
end,
sounds = {
on_collision = {"mcl_throwing_egg_impact", {max_hear_distance=10, gain=0.5}, true}
},
},
})

View File

@ -0,0 +1,131 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
local math = math
local vector = vector
local mod_target = minetest.get_modpath("mcl_target")
local how_to_throw = S("Use the punch key to throw.")
-- Ender Pearl
minetest.register_craftitem("mcl_throwing:ender_pearl", {
description = S("Ender Pearl"),
_tt_help = S("Throwable").."\n"..minetest.colorize(mcl_colors.YELLOW, S("Teleports you on impact for cost of 5 HP")),
_doc_items_longdesc = S("An ender pearl is an item which can be used for teleportation at the cost of health. It can be thrown and teleport the thrower to its impact location when it hits a solid block or a plant. Each teleportation hurts the user by 5 hit points."),
_doc_items_usagehelp = how_to_throw,
wield_image = "mcl_throwing_ender_pearl.png",
inventory_image = "mcl_throwing_ender_pearl.png",
stack_max = 16,
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:ender_pearl_entity"),
groups = { transport = 1 },
})
mcl_throwing.register_throwable_object("mcl_throwing:ender_pearl", "mcl_throwing:ender_pearl_entity", 22)
-- Ender pearl entity
minetest.register_entity("mcl_throwing:ender_pearl_entity",{
physical = false,
timer=0,
textures = {"mcl_throwing_ender_pearl.png"},
visual_size = {x=0.9, y=0.9},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
on_step = vl_projectile.update_projectile,
_lastpos={},
_thrower = nil, -- Player ObjectRef of the player who threw the ender pearl
_vl_projectile = {
behaviors = {
vl_projectile.collides_with_solids,
},
collides_with = {
"mcl_core:vine", "mcl_core:deadbush",
"group:flower", "group:sapling",
"group:plant", "group:mushroom",
},
on_collide_with_solid = function(self, pos, node)
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
if node.name == "ignore" then
-- FIXME: This also means the player loses an ender pearl for throwing into unloaded areas
return
end
-- Make sure we have a reference to the player
local player = self._thrower and minetest.get_player_by_name(self._thrower)
if not player then return end
-- Teleport and hurt player
-- First determine good teleport position
local dir = {x=0, y=0, z=0}
local v = self.object:get_velocity()
if node_def and node_def.walkable then
local vc = table.copy(v) -- vector for calculating
-- Node is walkable, we have to find a place somewhere outside of that node
vc = vector.normalize(vc)
-- Zero-out the two axes with a lower absolute value than
-- the axis with the strongest force
local lv, ld
lv, ld = math.abs(vc.y), "y"
if math.abs(vc.x) > lv then
lv, ld = math.abs(vc.x), "x"
end
if math.abs(vc.z) > lv then
ld = "z" --math.abs(vc.z)
end
if ld ~= "x" then vc.x = 0 end
if ld ~= "y" then vc.y = 0 end
if ld ~= "z" then vc.z = 0 end
-- Final tweaks to the teleporting pos, based on direction
-- Impact from the side
dir.x = vc.x * -1
dir.z = vc.z * -1
-- Special case: top or bottom of node
if vc.y > 0 then
-- We need more space when impact is from below
dir.y = -2.3
elseif vc.y < 0 then
-- Standing on top
dir.y = 0.5
end
end
-- If node was not walkable, no modification to pos is made.
-- Final teleportation position
local telepos = vector.add(pos, dir)
local telenode = minetest.get_node(telepos)
--[[ It may be possible that telepos is walkable due to the algorithm.
Especially when the ender pearl is faster horizontally than vertical.
This applies final fixing, just to be sure we're not in a walkable node ]]
if not minetest.registered_nodes[telenode.name] or minetest.registered_nodes[telenode.name].walkable then
if v.y < 0 then
telepos.y = telepos.y + 0.5
else
telepos.y = telepos.y - 2.3
end
end
local oldpos = player:get_pos()
-- Teleport and hurt player
player:set_pos(telepos)
player:set_hp(player:get_hp() - 5, { type = "fall", from = "mod" })
-- 5% chance to spawn endermite at the player's origin
local r = math.random(1,20)
if r == 1 then
minetest.add_entity(oldpos, "mobs_mc:endermite")
end
end
},
})

View File

@ -17,21 +17,18 @@ function mcl_throwing.register_throwable_object(name, entity, velocity)
end
function mcl_throwing.throw(throw_item, pos, dir, velocity, thrower)
if velocity == nil then
velocity = velocities[throw_item]
end
if velocity == nil then
velocity = 22
end
velocity = velocity or velocities[throw_item] or 22
minetest.sound_play("mcl_throwing_throw", {pos=pos, gain=0.4, max_hear_distance=16}, true)
local itemstring = ItemStack(throw_item):get_name()
local obj = minetest.add_entity(pos, entity_mapping[itemstring])
obj:set_velocity({x=dir.x*velocity, y=dir.y*velocity, z=dir.z*velocity})
obj:set_acceleration({x=dir.x*-3, y=-GRAVITY, z=dir.z*-3})
if thrower then
obj:get_luaentity()._thrower = thrower
end
local obj = vl_projectile.create(entity_mapping[itemstring], {
pos = pos,
owner = thrower,
dir = dir,
velocity = velocity,
drag = 3,
})
obj:get_luaentity()._thrower = thrower
return obj
end
@ -71,10 +68,11 @@ end
function mcl_throwing.on_activate(self, staticdata, dtime_s)
local data = minetest.deserialize(staticdata)
self._staticdata = data
if data then
self._lastpos = data._lastpos
self._thrower = data._thrower
end
end
dofile(modpath.."/register.lua")
dofile(modpath.."/register.lua")

View File

@ -1,3 +1,3 @@
name = mcl_throwing
depends = mcl_colors
depends = mcl_colors, vl_projectile
optional_depends = mcl_core, mcl_mobitems, doc, mcl_target

View File

@ -1,330 +1,7 @@
local S = minetest.get_translator(minetest.get_current_modname())
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local math = math
local vector = vector
dofile(modpath.."/snowball.lua")
dofile(modpath.."/egg.lua")
dofile(modpath.."/ender_pearl.lua")
local mod_target = minetest.get_modpath("mcl_target")
-- The snowball entity
local snowball_ENTITY={
physical = false,
timer=0,
textures = {"mcl_throwing_snowball.png"},
visual_size = {x=0.5, y=0.5},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
_thrower = nil,
_lastpos={},
}
local egg_ENTITY={
physical = false,
timer=0,
textures = {"mcl_throwing_egg.png"},
visual_size = {x=0.45, y=0.45},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
_thrower = nil,
_lastpos={},
}
-- Ender pearl entity
local pearl_ENTITY={
physical = false,
timer=0,
textures = {"mcl_throwing_ender_pearl.png"},
visual_size = {x=0.9, y=0.9},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
_lastpos={},
_thrower = nil, -- Player ObjectRef of the player who threw the ender pearl
}
local function check_object_hit(self, pos, dmg)
for _,object in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
local entity = object:get_luaentity()
if entity
and entity.name ~= self.object:get_luaentity().name then
if object:is_player() and self._thrower ~= object:get_player_name() then
self.object:remove()
return true
elseif (entity.is_mob == true or entity._hittable_by_projectile) and (self._thrower ~= object) then
object:punch(self.object, 1.0, {
full_punch_interval = 1.0,
damage_groups = dmg,
}, nil)
return true
end
end
end
return false
end
local function snowball_particles(pos, vel)
local vel = vector.normalize(vector.multiply(vel, -1))
minetest.add_particlespawner({
amount = 20,
time = 0.001,
minpos = pos,
maxpos = pos,
minvel = vector.add({x=-2, y=3, z=-2}, vel),
maxvel = vector.add({x=2, y=5, z=2}, vel),
minacc = {x=0, y=-9.81, z=0},
maxacc = {x=0, y=-9.81, z=0},
minexptime = 1,
maxexptime = 3,
minsize = 0.7,
maxsize = 0.7,
collisiondetection = true,
collision_removal = true,
object_collision = false,
texture = "weather_pack_snow_snowflake"..math.random(1,2)..".png",
})
end
-- Snowball on_step()--> called when snowball is moving.
local function snowball_on_step(self, dtime)
self.timer = self.timer + dtime
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local node = minetest.get_node(pos)
local def = minetest.registered_nodes[node.name]
-- Destroy when hitting a solid node
if self._lastpos.x~=nil then
if (def and def.walkable) or not def then
minetest.sound_play("mcl_throwing_snowball_impact_hard", { pos = pos, max_hear_distance=16, gain=0.7 }, true)
snowball_particles(self._lastpos, vel)
self.object:remove()
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
return
end
end
if check_object_hit(self, pos, {snowball_vulnerable = 3}) then
minetest.sound_play("mcl_throwing_snowball_impact_soft", { pos = pos, max_hear_distance=16, gain=0.7 }, true)
snowball_particles(pos, vel)
self.object:remove()
return
end
self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set _lastpos-->Node will be added at last pos outside the node
end
-- Movement function of egg
local function egg_on_step(self, dtime)
self.timer = self.timer + dtime
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
local def = minetest.registered_nodes[node.name]
-- Destroy when hitting a solid node with chance to spawn chicks
if self._lastpos.x then
if (def and def.walkable) or not def then
-- 1/8 chance to spawn a chick
-- FIXME: Chicks have a quite good chance to spawn in walls
local r = math.random(1,8)
if r == 1 then
mcl_mobs.spawn_child(self._lastpos, "mobs_mc:chicken")
-- BONUS ROUND: 1/32 chance to spawn 3 additional chicks
local r = math.random(1,32)
if r == 1 then
local offsets = {
{ x=0.7, y=0, z=0 },
{ x=-0.7, y=0, z=-0.7 },
{ x=-0.7, y=0, z=0.7 },
}
for o=1, 3 do
local pos = vector.add(self._lastpos, offsets[o])
mcl_mobs.spawn_child(pos, "mobs_mc:chicken")
end
end
end
minetest.sound_play("mcl_throwing_egg_impact", { pos = self.object:get_pos(), max_hear_distance=10, gain=0.5 }, true)
self.object:remove()
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
return
end
end
-- Destroy when hitting a mob or player (no chick spawning)
if check_object_hit(self, pos, 0) then
minetest.sound_play("mcl_throwing_egg_impact", { pos = self.object:get_pos(), max_hear_distance=10, gain=0.5 }, true)
self.object:remove()
return
end
self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
end
-- Movement function of ender pearl
local function pearl_on_step(self, dtime)
self.timer = self.timer + dtime
local pos = self.object:get_pos()
pos.y = math.floor(pos.y)
local node = minetest.get_node(pos)
local nn = node.name
local def = minetest.registered_nodes[node.name]
-- Destroy when hitting a solid node
if self._lastpos.x~=nil then
local walkable = (def and def.walkable)
-- No teleport for hitting ignore for now. Otherwise the player could get stuck.
-- FIXME: This also means the player loses an ender pearl for throwing into unloaded areas
if node.name == "ignore" then
self.object:remove()
-- Activate when hitting a solid node or a plant
elseif walkable or nn == "mcl_core:vine" or nn == "mcl_core:deadbush" or minetest.get_item_group(nn, "flower") ~= 0 or minetest.get_item_group(nn, "sapling") ~= 0 or minetest.get_item_group(nn, "plant") ~= 0 or minetest.get_item_group(nn, "mushroom") ~= 0 or not def then
local player = self._thrower and minetest.get_player_by_name(self._thrower)
if player then
-- Teleport and hurt player
-- First determine good teleport position
local dir = {x=0, y=0, z=0}
local v = self.object:get_velocity()
if walkable then
local vc = table.copy(v) -- vector for calculating
-- Node is walkable, we have to find a place somewhere outside of that node
vc = vector.normalize(vc)
-- Zero-out the two axes with a lower absolute value than
-- the axis with the strongest force
local lv, ld
lv, ld = math.abs(vc.y), "y"
if math.abs(vc.x) > lv then
lv, ld = math.abs(vc.x), "x"
end
if math.abs(vc.z) > lv then
ld = "z" --math.abs(vc.z)
end
if ld ~= "x" then vc.x = 0 end
if ld ~= "y" then vc.y = 0 end
if ld ~= "z" then vc.z = 0 end
-- Final tweaks to the teleporting pos, based on direction
-- Impact from the side
dir.x = vc.x * -1
dir.z = vc.z * -1
-- Special case: top or bottom of node
if vc.y > 0 then
-- We need more space when impact is from below
dir.y = -2.3
elseif vc.y < 0 then
-- Standing on top
dir.y = 0.5
end
end
-- If node was not walkable, no modification to pos is made.
-- Final teleportation position
local telepos = vector.add(pos, dir)
local telenode = minetest.get_node(telepos)
--[[ It may be possible that telepos is walkable due to the algorithm.
Especially when the ender pearl is faster horizontally than vertical.
This applies final fixing, just to be sure we're not in a walkable node ]]
if not minetest.registered_nodes[telenode.name] or minetest.registered_nodes[telenode.name].walkable then
if v.y < 0 then
telepos.y = telepos.y + 0.5
else
telepos.y = telepos.y - 2.3
end
end
local oldpos = player:get_pos()
-- Teleport and hurt player
player:set_pos(telepos)
player:set_hp(player:get_hp() - 5, { type = "fall", from = "mod" })
-- 5% chance to spawn endermite at the player's origin
local r = math.random(1,20)
if r == 1 then
minetest.add_entity(oldpos, "mobs_mc:endermite")
end
end
self.object:remove()
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
return
end
end
self._lastpos={x=pos.x, y=pos.y, z=pos.z} -- Set lastpos-->Node will be added at last pos outside the node
end
snowball_ENTITY.on_step = snowball_on_step
egg_ENTITY.on_step = egg_on_step
pearl_ENTITY.on_step = pearl_on_step
minetest.register_entity("mcl_throwing:snowball_entity", snowball_ENTITY)
minetest.register_entity("mcl_throwing:egg_entity", egg_ENTITY)
minetest.register_entity("mcl_throwing:ender_pearl_entity", pearl_ENTITY)
local how_to_throw = S("Use the punch key to throw.")
-- Snowball
minetest.register_craftitem("mcl_throwing:snowball", {
description = S("Snowball"),
_tt_help = S("Throwable"),
_doc_items_longdesc = S("Snowballs can be thrown or launched from a dispenser for fun. Hitting something with a snowball does nothing."),
_doc_items_usagehelp = how_to_throw,
inventory_image = "mcl_throwing_snowball.png",
stack_max = 64,
groups = { weapon_ranged = 1 },
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:snowball_entity"),
_on_dispense = mcl_throwing.dispense_function,
})
-- Egg
minetest.register_craftitem("mcl_throwing:egg", {
description = S("Egg"),
_tt_help = S("Throwable").."\n"..S("Chance to hatch chicks when broken"),
_doc_items_longdesc = S("Eggs can be thrown or launched from a dispenser and breaks on impact. There is a small chance that 1 or even 4 chicks will pop out of the egg."),
_doc_items_usagehelp = how_to_throw,
inventory_image = "mcl_throwing_egg.png",
stack_max = 64,
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:egg_entity"),
_on_dispense = mcl_throwing.dispense_function,
groups = { craftitem = 1 },
})
-- Ender Pearl
minetest.register_craftitem("mcl_throwing:ender_pearl", {
description = S("Ender Pearl"),
_tt_help = S("Throwable").."\n"..minetest.colorize(mcl_colors.YELLOW, S("Teleports you on impact for cost of 5 HP")),
_doc_items_longdesc = S("An ender pearl is an item which can be used for teleportation at the cost of health. It can be thrown and teleport the thrower to its impact location when it hits a solid block or a plant. Each teleportation hurts the user by 5 hit points."),
_doc_items_usagehelp = how_to_throw,
wield_image = "mcl_throwing_ender_pearl.png",
inventory_image = "mcl_throwing_ender_pearl.png",
stack_max = 16,
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:ender_pearl_entity"),
groups = { transport = 1 },
})
mcl_throwing.register_throwable_object("mcl_throwing:snowball", "mcl_throwing:snowball_entity", 22)
mcl_throwing.register_throwable_object("mcl_throwing:egg", "mcl_throwing:egg_entity", 22)
mcl_throwing.register_throwable_object("mcl_throwing:ender_pearl", "mcl_throwing:ender_pearl_entity", 22)

View File

@ -0,0 +1,75 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
local how_to_throw = S("Use the punch key to throw.")
-- Snowball
minetest.register_craftitem("mcl_throwing:snowball", {
description = S("Snowball"),
_tt_help = S("Throwable"),
_doc_items_longdesc = S("Snowballs can be thrown or launched from a dispenser for fun. Hitting something with a snowball does nothing."),
_doc_items_usagehelp = how_to_throw,
inventory_image = "mcl_throwing_snowball.png",
stack_max = 16,
groups = { weapon_ranged = 1 },
on_use = mcl_throwing.get_player_throw_function("mcl_throwing:snowball_entity"),
_on_dispense = mcl_throwing.dispense_function,
})
mcl_throwing.register_throwable_object("mcl_throwing:snowball", "mcl_throwing:snowball_entity", 22)
-- The snowball entity
local function snowball_particles(pos, vel)
local vel = vector.normalize(vector.multiply(vel, -1))
minetest.add_particlespawner({
amount = 20,
time = 0.001,
minpos = pos,
maxpos = pos,
minvel = vector.add({x=-2, y=3, z=-2}, vel),
maxvel = vector.add({x=2, y=5, z=2}, vel),
minacc = {x=0, y=-9.81, z=0},
maxacc = {x=0, y=-9.81, z=0},
minexptime = 1,
maxexptime = 3,
minsize = 0.7,
maxsize = 0.7,
collisiondetection = true,
collision_removal = true,
object_collision = false,
texture = "weather_pack_snow_snowflake"..math.random(1,2)..".png",
})
end
vl_projectile.register("mcl_throwing:snowball_entity", {
physical = false,
timer=0,
textures = {"mcl_throwing_snowball.png"},
visual_size = {x=0.5, y=0.5},
collisionbox = {0,0,0,0,0,0},
pointable = false,
get_staticdata = mcl_throwing.get_staticdata,
on_activate = mcl_throwing.on_activate,
_vl_projectile = {
behaviors = {
vl_projectile.collides_with_solids,
vl_projectile.collides_with_entities,
},
on_collide_with_solid = function(self, pos, node)
if mod_target and node.name == "mcl_target:target_off" then
mcl_target.hit(vector.round(pos), 0.4) --4 redstone ticks
end
snowball_particles(self._last_pos or pos, self.object:get_velocity())
end,
on_collide_with_entity = function(self, pos, entity)
snowball_particles(self._last_pos or pos, self.object:get_velocity())
end,
sounds = {
on_solid_collision = {"mcl_throwing_snowball_impact_hard", { max_hear_distance=16, gain=0.7 }, true},
on_entity_collision = {"mcl_throwing_snowball_impact_soft", { max_hear_distance=16, gain=0.7 }, true}
},
damage_groups = { snowball_vulnerable = 3 },
},
})

View File

@ -43,7 +43,7 @@ function vl_hollow_logs.register_hollow_log(defs)
local groups = {axey = 1, building_block = 1, handy = 1, hollow_log = 1}
if not defs[5] then
table.update(groups, {fire_encouragement = 5, fire_flammability = 5, flammable = 2, hollow_log_burnable = 1})
groups = table.insert(groups, {fire_encouragement = 5, fire_flammability = 5, flammable = 2, hollow_log_burnable = 1})
end
minetest.register_node(modname .. ":"..name.."_hollow", {

View File

@ -0,0 +1,341 @@
local mod = {}
vl_projectile = mod
local GRAVITY = tonumber(minetest.settings:get("movement_gravity"))
local enable_pvp = minetest.settings:get_bool("enable_pvp")
function mod.update_projectile(self, dtime)
local entity_name = self.name
local entity_def = minetest.registered_entities[entity_name] or {}
local entity_vl_projectile = entity_def._vl_projectile or {}
-- Update entity timer
self.timer = (self.timer or 0) + dtime
-- Run behaviors
local behaviors = entity_vl_projectile.behaviors or {}
for i=1,#behaviors do
local behavior = behaviors[i]
if behavior(self, dtime, entity_def, entity_vl_projectile) then
return
end
end
end
local function no_op()
end
local function damage_particles(pos, is_critical)
if is_critical then
minetest.add_particlespawner({
amount = 15,
time = 0.1,
minpos = vector.offset(pos, -0.5, -0.5, -0.5),
maxpos = vector.offset(pos, 0.5, 0.5, 0.5),
minvel = vector.new(-0.1, -0.1, -0.1),
maxvel = vector.new(0.1, 0.1, 0.1),
minexptime = 1,
maxexptime = 2,
minsize = 1.5,
maxsize = 1.5,
collisiondetection = false,
vertical = false,
texture = "mcl_particles_crit.png^[colorize:#bc7a57:127",
})
end
end
local function random_hit_positions(positions, placement)
if positions == "x" then
return math.random(-4, 4)
elseif positions == "y" then
return math.random(0, 10)
end
if placement == "front" and positions == "z" then
return 3
elseif placement == "back" and positions == "z" then
return -3
end
return 0
end
local function check_hitpoint(hitpoint)
if hitpoint.type ~= "object" then return false end
-- find the closest object that is in the way of the arrow
if hitpoint.ref:is_player() and enable_pvp then
return true
end
if not hitpoint.ref:is_player() and hitpoint.ref:get_luaentity() then
if (hitpoint.ref:get_luaentity().is_mob or hitpoint.ref:get_luaentity()._hittable_by_projectile) then
return true
end
end
return false
end
local function handle_player_sticking(self, entity_def, projectile_def, entity)
if self._in_player or self._blocked then return end
if not projectile_def.sticks_in_players then return end
minetest.after(150, function()
self.object:remove()
end)
-- Handle blocking projectiles
if mcl_shields.is_blocking(obj) then
self._blocked = true
self.object:set_velocity(vector.multiply(self.object:get_velocity(), -0.25))
return
end
-- Handle when the projectile hits the player
local placement
self._placement = math.random(1, 2)
if self._placement == 1 then
placement = "front"
else
placement = "back"
end
self._in_player = true
if self._placement == 2 then
self._rotation_station = 90
else
self._rotation_station = -90
end
self._y_position = random_arrow_positions("y", placement)
self._x_position = random_arrow_positions("x", placement)
if self._y_position > 6 and self._x_position < 2 and self._x_position > -2 then
self._attach_parent = "Head"
self._y_position = self._y_position - 6
elseif self._x_position > 2 then
self._attach_parent = "Arm_Right"
self._y_position = self._y_position - 3
self._x_position = self._x_position - 2
elseif self._x_position < -2 then
self._attach_parent = "Arm_Left"
self._y_position = self._y_position - 3
self._x_position = self._x_position + 2
else
self._attach_parent = "Body"
end
self._z_rotation = math.random(-30, 30)
self._y_rotation = math.random( -30, 30)
self.object:set_attach(
obj, self._attach_parent,
vector.new(self._x_position, self._y_position, random_arrow_positions("z", placement)),
vector.new(0, self._rotation_station + self._y_rotation, self._z_rotation)
)
end
function mod.collides_with_solids(self, dtime, entity_def, projectile_def)
local pos = self.object:get_pos()
-- Don't try to do anything on first update
if not self._last_pos then
self._last_pos = pos
return
end
-- Check if the object can collide with this node
local node = minetest.get_node(pos)
local node_def = minetest.registered_nodes[node.name]
local collides_with = projectile_def.collides_with
if entity_def.physical then
-- Projectile has stopped in one axis, so it probably hit something.
-- This detection is a bit clunky, but sadly, MT does not offer a direct collision detection for us. :-(
local vel = self.object:get_velocity()
if not( (math.abs(vel.x) < 0.0001) or (math.abs(vel.z) < 0.0001) or (math.abs(vel.y) < 0.00001) ) then
self._last_pos = pos
return
end
else
if node_def and not node_def.walkable and (not collides_with or not mcl_util.match_node_to_filter(node.name, collides_with)) then
self._last_pos = pos
return
end
end
-- Call entity collied hook
local hook = projectile_def.on_collide_with_solid or no_op
hook(self, pos, node, node_def)
-- Call node collided hook
local hook = (node_def._vl_projectile or {}).on_collide or no_op
hook(self, pos, node, node_def)
-- Play sounds
local sounds = projectile_def.sounds or {}
local sound = sounds.on_solid_collision or sounds.on_collision
if sound then
local arg2 = table.copy(sound[2])
arg2.pos = pos
minetest.sound_play(sound[1], arg2, sound[3])
end
-- Normally objects should be removed on collision with solids
if not projectile_def.survive_collision then
self.object:remove()
end
-- Done with behaviors
return true
end
local function handle_entity_collision(self, entity_def, projectile_def, entity)
local pos = self.object:get_pos()
local dir = vector.normalize(self.object:get_velocity())
local self_vl_projectile = self._vl_projectile
-- Allow punching
local allow_punching = projectile_def.allow_punching or true
if type(allow_punching) == "function" then
allow_punching = allow_punching(self, entity_def, projectile_def, entity)
end
print("allow_punching="..tostring(allow_punching))
if allow_punching then
-- Get damage
local dmg = projectile_def.damage_groups or 0
if type(dmg) == "function" then
dmg = dmg(self, entity_def, projectile_def, entity)
end
local entity_lua = entity:get_luaentity()
-- Apply damage
-- Note: Damage blocking for shields is handled in mcl_shields with an mcl_damage modifier
local do_damage = false
if entity:is_player() and projectile_def.hits_players and self_vl_projectile.owner ~= hit:get_player_name() then
do_damage = true
handle_player_sticking(self, entity_def, projectile_def, entity)
elseif entity_lua and (entity_lua.is_mob == true or entity_lua._hittable_by_projectile) and (self_vl_projectile.owner ~= entity) then
do_damage = true
entity:punch(self.object, 1.0, projectile_def.tool or { full_punch_interval = 1.0, damage_groups = dmg }, dir )
end
if do_damage then
entity:punch(self.object, 1.0, projectile_def.tool or { full_punch_interval = 1.0, damage_groups = dmg }, dir )
-- Indicate damage
damage_particles(vector.add(pos, vector.multiply(self.object:get_velocity(), 0.1)), self._is_critical)
-- Light things on fire
if mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(obj, 5)
end
end
end
-- Call entity collision hook
(projectile_def.on_collide_with_entity or no_op)(self, pos, entity)
-- Call reverse entity collision hook
local other_entity_def = minetest.registered_entities[entity.name] or {}
local other_entity_vl_projectile = other_entity_def._vl_projectile or {}
local hook = (other_entity_vl_projectile or {}).on_collide or no_op
hook(entity, self)
-- Play sounds
local sounds = (projectile_def.sounds or {})
local sound = sounds.on_entity_collide or sounds.on_collision
if type(sound) == "function" then sound = sound(self, entity_def, projectile_def, entity)
if sound then
local arg2 = table.copy(sound[2])
arg2.pos = pos
minetest.sound_play(sound[1], arg2, sound[3])
end
-- Normally objects should be removed on collision with entities
if not projectile_def.survive_collision then
self.object:remove()
end
return true
end
function mod.collides_with_entities(self, dtime, entity_def, projectile_def)
local pos = self.object:get_pos()
local hit = nil
local owner = self._vl_projectile.owner
local objects = minetest.get_objects_inside_radius(pos, 1.5)
for i = 1,#objects do
local object = objects[i]
local entity = object:get_luaentity()
if entity and entity.name ~= self.object:get_luaentity().name then
if object:is_player() and owner ~= object:get_player_name() then
return handle_entity_collision(self, entity_def, projectile_def, object)
elseif (entity.is_mob == true or entity._hittable_by_projectile) and (owner ~= object) then
return handle_entity_collision(self, entity_def, projectile_def, object)
end
end
end
end
function mod.raycast_collides_with_entities(self, dtime, entity_def, projectile_def)
local closest_object
local closest_distance
local pos = self.object:get_pos()
local arrow_dir = self.object:get_velocity()
--create a raycast from the arrow based on the velocity of the arrow to deal with lag
local raycast = minetest.raycast(pos, vector.add(pos, vector.multiply(arrow_dir, 0.1)), true, false)
for hitpoint in raycast do
if check_hitpoint(hitpoint) then
local hitpoint_ref = hitpoint.ref
local dist = vector.distance(hitpoint_ref:get_pos(), pos)
if not closest_distance or dist < closest_distance then
closest_object = hitpoint_ref
closest_distance = dist
end
end
end
if closest_object then
return handle_entity_collision(self, entity_def, projectile_def, closest_object)
end
end
function mod.create(entity_id, options)
local obj = minetest.add_entity(options.pos, entity_id, options.staticdata)
-- Set initial velocoty and acceleration
obj:set_velocity(vector.multiply(options.dir or vector.zero(), options.velocity or 0))
obj:set_acceleration(vector.add(
vector.multiply(options.dir or vector.zero(), -math.abs(options.drag)),
vector.new(0,-GRAVITY,0)
))
-- Update projectile parameters
local luaentity = obj:get_luaentity()
luaentity._vl_projectile = {
owner = options.owner,
extra = options.extra,
}
-- Make the update function easy to get to
luaentity.update_projectile = mod.update_projectile
-- And provide the caller with the created object
return obj
end
function mod.register(name, def)
assert(def._vl_projectile)
if not def.on_step then
def.on_step = mod.update_projectile
end
def._thrower = nil
def._shooter = nil
def._last_pos = nil
minetest.register_entity(name, def)
end

View File

@ -0,0 +1,2 @@
name = vl_projectile
depends = mcl_util

View File

@ -1,180 +0,0 @@
## 0.87 The Prismatic release
### Contributors
#### New Developers
* rudzik8
* teknomunk
#### New Contributors
* PrWalterB
* michaljmalinowski
* nixnoxus
* Potiron
* Tuxilio
* Impulse
* Doods
* SOS-Games
* Bram
* qoheniac
### Game rename
Based on months of collecting suggestions, analysis and vetting of possible names, community voting and discussion between developers, the rename of the game has reached its conclusion! The project has been renamed to **VoxeLibre**.
Along with this, a documentation update has been conducted by Herowl, teknomunk and rudzik8. Make sure to check out the updated Contributing Guidelines!
### Potions and Effects redo
After more than half a year of work, it is finally here! The whole system has been rewritten from the ground up by Herowl. New effects, potions and brewing recipes have been added. Potion tooltips have been reworked. In the HUD you can now see more information about what effects you have at the moment, including level and duration. Beacon has also received more effects and is now quite functional.
A few new items to be used as brewing ingredients have been added. For now you can obtain them from fishing and trading with villagers. Those items will be made obtainable from other sources and will get more uses, hopefully in the next release. The functionality of some effects is not complete and they are also not obtainable yet (hero of the village and conduit power).
Some of the old potions and tipped arrows don't work with the new API and have to be converted. To avoid constant rechecking of all inventories, they have been bound to placeholder definitions. What this means is that if you notice weird potions or arrows marked with question marks, you will have to right-click them to run the conversion. A small price to pay for less lag, right?
Improved support of mobs by the effects and potions, including effects being properly saved on mobs. Despite that, some effects still don't work with mobs, because the mobs' code doesn't support them properly:
* the following effects don't work with mobs at all: water breathing, dolphin's grace, leaping, swiftness, slowness, slow falling, night vision, darkness, frost, health boost, absorption, fire resistance, resistance, luck, bad luck, blindness, nausea, food poisoning, saturation, haste, fatigue, conduit power
* the following effects should work with mobs: invisibility, regeneration, poison, withering, strength, weakness, levitation, glowing
* the following effects have no effect on mobs (but can be applied with the API): bad omen, hero of the village
While not everything is available in game, a great API (documented in the module) has been exposed for modders, allowing adding new effects and potions. Potions can now have all sorts of custom effects and multiple effects at once. Effects and potions can now have indefinitely many levels and potentially infinite duration, available with the `/effect` command and the modding API. Effects can still (and even more so than before!) be fine-tuned with factors and abnormal levels, which can sometimes give unexpected results, but it's all left up to modders.
### Nether Portals rewrite
Another large rework! Thanks to emptyshore, portals to (and from) the Nether now work better than ever, connecting properly to each other. They shouldn't cause unwelcome surprises either, stranding you where you never expected to go, and shouldn't teleport you up into the skies.
### Mob spawning system
An update by Bakawun improved the mob spawning, optimizing it and making the randomness work better, as well as properly taking into account set spawn chances. This update also changed the mob spawn chances and ratios.
Another improvement to the system was made by teknomunk, who wrote a new system for spawn position calculation. This enables overhead spawning, among other things allowing for some mob farms to work.
Also, light and height checking of Slimes has been fixed by Codiac, so they should no longer spawn in large numbers in inappropriate places.
### Mob improvements
Not only did mob spawning get improved, but mobs themselves did too.
Rover is a new mob, replacing the enderman. Along with this rework by Herowl and teknomunk, node picking code was refactored and generalized, paving the way for more mobs visibly holding actual items in the future.
Stalker is another new mob, replacing the creeper. This rework completed by Herowl contains a new camouflage mechanic and otherwise a new look at a well-known concept.
Ghast received a great update by Bakawun and Herowl. Its hitbox should now work properly, and deflecting the fireballs should work properly again too. Also the achievement for killing a Ghast with a ghast fireball should now be granted properly.
Sounds for Hoglin/Zoglin, Piglin and skeletons have been updated by Bakawun and should now be used properly.
Strider received a few fixes by nixnoxus. Breeding, attracting and riding should now work.
### Eating animation
Eating is no longer instant in survival mode, but delayed instead with the new system designed by Eliy21.
To signify it properly, Herowl added an animation visible in the first-person mode.
### New blocks
* Colored End Rod variants by Herowl.
* Colored Redstone Lamps by Herowl.
* Glazed Terracotta Pillars by Potiron.
* Compressed Cobblestone by SmokeyDope.
* Clovers and Four-leaf Clovers by Herowl.
* Hollow logs by JoseDouglas26 and Herowl
### Capes
Thanks to the changes by chmodsayshello and rudzik8, you can now pick a cape in the character skin customization UI. Thanks for the "Minetest" cape texture to QwertyDragon.
### Colored leather armor
Leather armor can now be colored (and washed) thanks to AFCMS and Herowl. Aside of the crafting recipes, this added a command and a modding API for this.
### Cherry blossom particles
The particles of the cherry blossom, which fall from the cherry leaves, have been vastly improved by Wbjitscool and Herowl. Plant some cherry trees and behold the new animation and the wind direction changing 3 times a game day.
### Signs text editing
Now you can edit the text on signs by right-clicking ("place") on a sign placed in the world, all thanks to Araca.
### Tool durability tooltips
Yet another feature from Araca, tooltips for tools (and weapons) will now display how much durability they have remaining. Now you have more precise info than just the wearbar!
### Creative inventory fixes
Items can't be moved around in the creative inventory, tabs there have proper tooltips and searching works on Android with Minetest 5.8+ all thanks to rudzik8.
### Help UI Mobs section
A "Mobs" section added to the Help UI by SOS-Games. Translations may be missing.
### Texture pack converter
One of our tools, the Python script allowing conversion of Minecraft resource packs (texture-wise) to the Minetest format to work with our game, has received a great update by Impulse and Doods. It should now work with packs from newer versions, but keep in mind that not everything can be automated and the packs may still require some manual fixes (and additions, if you want to cover all of our own features that have no equivalents in Minecraft).
### New Translation
* Occitan by PrWalterB
### Translation updates
* Spanish by megustanlosfrijoles
* French by syl
* Polish by Herowl
* German by Tuxilio, Herowl and qoheniac
* Syntax fixes in translation files by megustanlosfrijoles
### Other changes
* Melon and pumpkin generation by michaljmalinowski
* Golden rails accelerate carts properly by nixnoxus
* Elytra Animation works again by MrRar
* Mobs aggro disabled when damage is disabled by emptyshore
* Fortune enchantment on hoes works by JoseDouglas26
* Typo in pumpkin.lua fixed by SmokeyDope
* Node rotation at placement improvements by JoseDouglas26
* Nylium reverting to netherrack by JoseDouglas26
* Hunger debug setting exposed properly by SmokeyDope
* Nether vine placement fixes by SmokeyDope
* Survival inventory tabs API fixes by Impulse
* Cactus damaging mobs by Eliy21
* Sweet berry bush slowdown decreased by Eliy21
* Fixed scaffolding placement replacing other blocks without a trace by JoseDouglas26
* Nodes fireproofing and missing plank recipes added by Doods
* End Rods now use a proper mesh model by Herowl
* Piglin bartering improvements by nixnoxus
* Hopper item movement improved by teknomunk
* Partial item stack pickup by teknomunk
* Bone meal node protection check by CyberMango
* Undeclared variable usage fixed by nixnoxus
* Biome check when spawning override (API, Skyblock support) by AncientMariner
* Reimported tga_encoder as subtree (this allows support of some mods) by Herowl
* Bed placement and destruction fixes by teknomunk
* Item tooltip shouldn't be modified needlessly (this fixes some bugs causing items to not stack properly) by Herowl
* Beds now properly ignore players in other dimensions by nixnoxus
* Stray pixels in leather cap texture removed by SmokeyDope
* Allow lecterns to be placed on sides of blocks by JoseDouglas26
* Fix warnings by JoseDouglas26
* Experience from trading by nixnoxus
* Boats easier to destroy with punching by Eliy21
* Horse and Donkey animation fix by Bakawun
* Villagers won't eat shulker boxes (independent of their food content) anymore by teknomunk
* Beds now properly ignore players in the wrong dimensions when counting by nixnoxus
* Shears now wear properly when harvesting comb from a beehive by teknomunk
* Improved compatibility with mapgen mods by Bram
* Item frame attachment fixed by rudzik8
* Stray pixels in sweet berry textures removed by rudzik8
* Warning related to milk bucket fixed by teknomunk
* Seed is now logged when entering a world by Nicu
* Startup warnings from mcl_stonecutter fixed by Herowl
* Sleeping GUI improved by Nicu
* Description capitalization fix by syl
### Special thanks
* To emptyshore, for the in-depth research and testing of the Mob Spawning System rework, as well as his aforementioned Nether Portals system rework.
### Crash fixes
* Damage animation related crash by Herowl
* Shields-related crash by Impulse
* Elytra-related crash by Herowl
* Damage animation and player invulnerability related crash by Eliy21
* Rocket explosion related crash by Herowl
* New game load crash by AncientMariner
* XP orbs related crash by teknomunk
* Ghast fireball related crash by Araca
* Crash related to server restart while a player is dead by teknomunk
* Crashes related to the new effects API - by teknomunk and Herowl
## 0.87.1 hotfix
* Fixed crash when shooting potions from a dispenser - by teknomunk
* Fixed crash related to custom mobspawners - by teknomunk
* Fixed beacon crash - by teknomunk
* Fixed eye of ender crash - by Herowl
* Fixed Stalker texture generation - by teknomunk
* Correctly refresh enchanted tool capabilities - by teknomunk
* Fixed creative inventory misbehaving - by Herowl
* Fixed variable definition in mob spawning code - by teknomunk
* Updated documentation - by Herowl and teknomunk
* Increased stack size for snowballs and eggs - by JoseDouglas26

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 B

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -4,7 +4,7 @@
local colors = {
["Creator of MineClone"] = "0x0A9400",
["Creator of VoxeLibre"] = "0xFBF837",
["Creator of MineClone2"] = "0xFBF837",
["Maintainers"] = "0xFF51D5",
["Developers"] = "0xF84355",
["Past Developers"] = "0xF84355",