Compare commits
997 Commits
chat-comma
...
master
Author | SHA1 | Date |
---|---|---|
Nils Dagsson Moskopp | 46394e7e1f | |
Lizzy Fleckenstein | b0aceae73d | |
NO11 | 9200c3fe44 | |
NO11 | 903d1777be | |
Lizzy Fleckenstein | 6ca5799565 | |
Lizzy Fleckenstein | 30528b0a2c | |
NO11 | 9919011aca | |
NO11 | c757e98b4f | |
NO11 | e2b22a62ce | |
NO11 | a9804879e2 | |
NO11 | d2a03ec0b9 | |
NO11 | 7ef6613f09 | |
NO11 | d27109d807 | |
iliekprogrammar | 79e1e2fa8f | |
NO11 | fdaaf0ec49 | |
NO11 | add97d39a6 | |
NO11 | bcb6251d20 | |
NO11 | 1483c45cc9 | |
NO11 | 642b9db035 | |
NO11 | d7e59f6a35 | |
NO11 | d4da855570 | |
NO11 | 30dd505985 | |
NO11 | ae8068cca6 | |
iliekprogrammar | bd14918543 | |
iliekprogrammar | 66c1b4a95f | |
iliekprogrammar | 96c4fb60d8 | |
NO11 | 5515e2baa1 | |
NO11 | be2e4614d0 | |
NO11 | 7ed964756e | |
NO11 | 0e5ad8637d | |
NO11 | 0cc0a7e01d | |
NO11 | 490e40d042 | |
NO11 | f233fb5d30 | |
NO11 | 9e4e7f0695 | |
NO11 | 2a08f31435 | |
iliekprogrammar | 740f7583ef | |
iliekprogrammar | 0564121183 | |
iliekprogrammar | bca5033fb2 | |
Dieter44 | 03d22852ee | |
Dieter44 | 10a5f8c4bf | |
Dieter44 | 68810a2a74 | |
Dieter44 | 0b6467d679 | |
NO11 | 8979230c42 | |
NO11 | fafa47d643 | |
iliekprogrammar | f61143758e | |
iliekprogrammar | fa22ec4dd0 | |
Dieter44 | 017bf705e9 | |
Lizzy Fleckenstein | a7bc460fae | |
Dieter44 | e82d21040c | |
iliekprogrammar | 0491b814dd | |
Dieter44 | 2f05388541 | |
Dieter44 | 976f522b9d | |
NO11 | 51dffc6c53 | |
NO11 | 30ce6f8a77 | |
Lizzy Fleckenstein | a34ae040c8 | |
Lizzy Fleckenstein | a410a7fabe | |
Lizzy Fleckenstein | ea46c8741b | |
NO11 | 5b52deaa8a | |
NO11 | 7bbc1e9951 | |
Lizzy Fleckenstein | 0dd780ee2a | |
NO11 | 1b259f928b | |
Dieter44 | d0d60804a3 | |
Dieter44 | d1d11f9740 | |
Nils Dagsson Moskopp | 4926c0560d | |
Nils Dagsson Moskopp | 23ca11c8e1 | |
Elias Fleckenstein | 88e084cbca | |
Lizzy Fleckenstein | be86b603f8 | |
iliekprogrammar | f4803a35c4 | |
Lizzy Fleckenstein | ce4c0ed4c1 | |
Lizzy Fleckenstein | 4a3a8841cd | |
Lizzy Fleckenstein | 649f481b51 | |
Lizzy Fleckenstein | b937b38b1c | |
Lizzy Fleckenstein | f9e7f58492 | |
Lizzy Fleckenstein | a77e79d985 | |
Lizzy Fleckenstein | 24ca8252a9 | |
Lizzy Fleckenstein | c6e9d763d6 | |
Lizzy Fleckenstein | 61d0dc8182 | |
Lizzy Fleckenstein | 46d1dd42d4 | |
Lizzy Fleckenstein | 39f66eb4a0 | |
Lizzy Fleckenstein | 18dd1cabd0 | |
Lizzy Fleckenstein | e70161501f | |
Lizzy Fleckenstein | bb6fe65aa5 | |
Lizzy Fleckenstein | cdf6533e0a | |
Lizzy Fleckenstein | d690797011 | |
Lizzy Fleckenstein | 6000c29171 | |
Lizzy Fleckenstein | faff9316e0 | |
Lizzy Fleckenstein | 7c1777c53a | |
Lizzy Fleckenstein | c2f0f0297b | |
Lizzy Fleckenstein | 6473494cbc | |
Lizzy Fleckenstein | 4d93e13f80 | |
Lizzy Fleckenstein | 47340386e2 | |
Lizzy Fleckenstein | 25f467c8ab | |
Elias Fleckenstein | c17fc81d1f | |
Lizzy Fleckenstein | 8f9f5cc936 | |
Elias Fleckenstein | 4af5d8e5e8 | |
Lizzy Fleckenstein | 643ded06da | |
Lizzy Fleckenstein | 970988cb39 | |
Lizzy Fleckenstein | 34f329a9d5 | |
Lizzy Fleckenstein | a4e73886d5 | |
Lizzy Fleckenstein | 148575a05b | |
iliekprogrammar | 0baffeced3 | |
Nils Dagsson Moskopp | 2607d40f1f | |
Artem Arbatsky | db696d0e2b | |
NO11 | f09923ded9 | |
NO11 | 0b9cbdb20a | |
NO11 | c52fda154b | |
NO11 | 28b7304211 | |
NO11 | 1518eb81cb | |
NO11 | 4e0bb0d120 | |
NO11 | 5b37f56005 | |
NO11 | aaee3b6de7 | |
NO11 | 6025a3d7d4 | |
Lizzy Fleckenstein | bbdd8f55eb | |
Lizzy Fleckenstein | 6fd8ff8865 | |
Lizzy Fleckenstein | 11e364b3ec | |
Lizzy Fleckenstein | 4db9952a84 | |
Lizzy Fleckenstein | fb2a501a9c | |
Lizzy Fleckenstein | 938911e7e3 | |
Lizzy Fleckenstein | c1934c4f3a | |
Lizzy Fleckenstein | 61dccfb9e5 | |
Lizzy Fleckenstein | 90796ec7b4 | |
Lizzy Fleckenstein | d30e014233 | |
NO11 | 19689dd857 | |
NO11 | b78e7b3e4d | |
Lizzy Fleckenstein | 835076ea4b | |
Lizzy Fleckenstein | 797da20fa7 | |
Lizzy Fleckenstein | c315d155e1 | |
Lizzy Fleckenstein | 0c567c7921 | |
Lizzy Fleckenstein | 64ebdd0f18 | |
Lizzy Fleckenstein | 1bd972bff7 | |
Lizzy Fleckenstein | ea0f52763c | |
Lizzy Fleckenstein | 70425e9f30 | |
Lizzy Fleckenstein | a0789e72f0 | |
Lizzy Fleckenstein | e341b2a6fe | |
Lizzy Fleckenstein | 4e8e6fbb51 | |
Lizzy Fleckenstein | efbe3c93b4 | |
Lizzy Fleckenstein | 0369465630 | |
Lizzy Fleckenstein | 30f7c638f3 | |
NO11 | eccba76732 | |
Lizzy Fleckenstein | f3d16d264c | |
Lizzy Fleckenstein | da5e703675 | |
Lizzy Fleckenstein | 6466061796 | |
Lizzy Fleckenstein | a80438d58e | |
Lizzy Fleckenstein | 47fbb0c176 | |
Lizzy Fleckenstein | fba30eccd6 | |
Lizzy Fleckenstein | 6e7827902c | |
Lizzy Fleckenstein | a877c615a5 | |
Lizzy Fleckenstein | 7f43ba6e36 | |
Lizzy Fleckenstein | 5edf27ac88 | |
Lizzy Fleckenstein | c998788435 | |
Lizzy Fleckenstein | 7707c3132c | |
Lizzy Fleckenstein | 756d28e2c6 | |
Lizzy Fleckenstein | 3a422e3afc | |
Lizzy Fleckenstein | 362de4c920 | |
Lizzy Fleckenstein | 6580bcab5a | |
Lizzy Fleckenstein | a6def5e9bb | |
NO11 | 6d22262c53 | |
NO11 | 7489010152 | |
Lizzy Fleckenstein | d224236388 | |
Lizzy Fleckenstein | 27f35fe422 | |
Lizzy Fleckenstein | 4b1606eaee | |
Lizzy Fleckenstein | 4776487f83 | |
Lizzy Fleckenstein | 664244d25c | |
Lizzy Fleckenstein | b9999195ec | |
Elias Fleckenstein | b18e077ba3 | |
Elias Fleckenstein | f3693138c8 | |
Elias Fleckenstein | 6e94550a12 | |
NO11 | d85f143fde | |
NO11 | dafe860e56 | |
Lizzy Fleckenstein | 832e634e3b | |
Lizzy Fleckenstein | 5c55ddff1e | |
Lizzy Fleckenstein | a28bfebb9b | |
Lizzy Fleckenstein | e5c5a78553 | |
epCode | 1c458a2e72 | |
NO11 | 94e1d82b45 | |
NO11 | 9ccf8de606 | |
NO11 | 19b5a5aac7 | |
NO11 | 6803d22dbd | |
NO11 | 0584d16569 | |
NO11 | 03829dd518 | |
NO11 | 72ea9069bd | |
NO11 | 9188467a6a | |
NO11 | fe91d7f3e0 | |
NO11 | 463fe2af5f | |
NO11 | e4af02ea52 | |
NO11 | 8e3f9d2169 | |
NO11 | a410d17306 | |
AFCMS | e9437e9e1e | |
AFCMS | e40ed91674 | |
NO11 | 16c73c1cb6 | |
NO11 | 03be736566 | |
NO11 | 8697b38d60 | |
NO11 | e83438e42c | |
NO11 | 71f448537c | |
NO11 | df1d8077e6 | |
NO11 | 89e90b13eb | |
AFCMS | b0cf07a020 | |
AFCMS | e36a8c5acf | |
AFCMS | b2af00db22 | |
AFCMS | e8134345d4 | |
AFCMS | e0c7e938de | |
AFCMS | 3e54acce9d | |
AFCMS | 47fff66a41 | |
ztianyang | ac8383dd4b | |
NO11 | c6003398b5 | |
NO11 | f10d579d9c | |
NO11 | fed43586f2 | |
NO11 | 18a83fa5d7 | |
NO11 | 0e15accada | |
NO11 | 4194a76db0 | |
Lizzy Fleckenstein | 27e4bd6d09 | |
Nils Dagsson Moskopp | 256de2bc36 | |
sfan5 | 693a5317ef | |
Nils Dagsson Moskopp | 95c4d6472b | |
Wuzzy | 1c192f4fbb | |
NO11 | c22c2063ad | |
NO11 | 410b7c7844 | |
NO11 | 9f9799d96e | |
NO11 | cd84c472dc | |
NO11 | 0bc9ab9233 | |
NO11 | 89ff666a6a | |
NO11 | 1a5339e907 | |
NO11 | aa930dc2b8 | |
NO11 | 79463738f6 | |
NO11 | 79d8593d34 | |
NO11 | dc8436fdf9 | |
NO11 | f41cea71fd | |
NO11 | 5fedd914fb | |
Elias Fleckenstein | a8d09338a9 | |
NO11 | 872e3e74d3 | |
NO11 | bf62eb33fa | |
NO11 | 0da7b3fbda | |
Elias Fleckenstein | 3669321828 | |
NO11 | ca086109bf | |
NO11 | 6c6d40eb9d | |
NO11 | 2b322a451f | |
NO11 | f0af15fcd8 | |
AFCMS | af8e88f44c | |
AFCMS | 2d1a43396f | |
AFCMS | df4b8e64cc | |
AFCMS | 40898d3e9d | |
AFCMS | 58a292a4f3 | |
AFCMS | fe62189019 | |
NO11 | 4802b610c3 | |
NO11 | cb55c36863 | |
NO11 | f8dcf05670 | |
NO11 | 7e0bb036f4 | |
NO11 | 664c238137 | |
Elias Fleckenstein | efab5a1cf5 | |
NO11 | d76a7daf2d | |
Emily | 5bb57a81ad | |
Emily | df0c1f1dd1 | |
NO11 | 5c563d6ffd | |
NO11 | 4aabd7d9e7 | |
Elias Fleckenstein | 88f253bf66 | |
NO11 | 289ba826ba | |
NO11 | 4846076c8f | |
NO11 | 5c5c405ccf | |
NO11 | 65d33b935a | |
NO11 | c05e57efb1 | |
NO11 | e44e9eaf62 | |
NO11 | 09a68443cd | |
NO11 | 75b425ffd7 | |
NO11 | 0a9ea7e46a | |
NO11 | a44d9643ae | |
AFCMS | a47630035d | |
AFCMS | 667ef5cad5 | |
AFCMS | fef23d0b6f | |
NO11 | a0d52010bf | |
AFCMS | 44063bca93 | |
AFCMS | e58bb6d859 | |
NO11 | 31d3ea8a87 | |
AFCMS | 13536baed8 | |
AFCMS | 6069bb4a38 | |
AFCMS | c31c852a6e | |
AFCMS | b5f7ae5458 | |
AFCMS | 9254bab971 | |
AFCMS | 999b82c94a | |
NO11 | a83dd8548f | |
Tianyang Zhang | 559cf85c94 | |
NO11 | 96e8e6a86f | |
NO11 | 801d9a2571 | |
NO11 | f2a4d6bd56 | |
NO11 | 48166625d4 | |
NO11 | a2e0cf83bc | |
AFCMS | b364faa7c7 | |
Tianyang Zhang | 60fcafeee4 | |
SmallJoker | 9d9e213012 | |
AFCMS | 49bde37a5e | |
AFCMS | dc17cc91a3 | |
AFCMS | 873a1e73dc | |
AFCMS | 8fff20eec9 | |
AFCMS | ec6086d8e6 | |
AFCMS | 6d7aafe0d4 | |
AFCMS | 91adfcbb1d | |
AFCMS | 8958aa7b12 | |
AFCMS | cf5703d528 | |
AFCMS | 88e59d3592 | |
AFCMS | cd08df175c | |
AFCMS | ca277b6769 | |
AFCMS | b0127fc1c3 | |
AFCMS | d26b1b1402 | |
NO11 | 549bdeb6e9 | |
NO11 | ecfb615c89 | |
epCode | 960b653979 | |
NO11 | 5ceb48fcb1 | |
NO11 | f894153b8a | |
NO11 | 848f1489e8 | |
NO11 | a0d3f51745 | |
NO11 | 46d48ccf2f | |
NO11 | f46c4ebad7 | |
NO11 | b7e6db537b | |
NO11 | 357474e32f | |
NO11 | b6eb0ab66c | |
1F616EMO | d2f7d31360 | |
AFCMS | 8e931e92f5 | |
AFCMS | cbfec5c5c8 | |
NO11 | 509568b4b0 | |
Lizzy Fleckenstein | 6e1758400e | |
cora | c558e30ea5 | |
Lizzy Fleckenstein | fb01e61946 | |
Lizzy Fleckenstein | 8445a9ddec | |
cora | 3b2a25a6cb | |
Lizzy Fleckenstein | 8f584be235 | |
Elias Fleckenstein | 94cbb2b802 | |
NO11 | 1471521709 | |
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. | 2f22ce79cc | |
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. | ae2c5ede30 | |
Elias Fleckenstein | f4a30959ce | |
Lizzy Fleckenstein | b22e4ae99d | |
Elias Fleckenstein | 27f4c210c7 | |
NO11 | 091622f855 | |
NO11 | 5cc4fe955f | |
NO11 | 4afdea56db | |
Nils Dagsson Moskopp | 0a474ee578 | |
NO11 | d45e1c07bd | |
NO11 | 21992dc265 | |
AFCMS | a9ceeabc4b | |
AFCMS | 16d79c38ce | |
kay27 | 7ff476b9b9 | |
kay27 | c6b662ce7a | |
kay27 | 586c18b00f | |
AFCMS | 61b0220951 | |
AFCMS | ef1c06e3d1 | |
AFCMS | a9ae6aa9c8 | |
NO11 | b03cd1fc8e | |
NO11 | cf3a2a42bf | |
NO11 | ff8176e252 | |
NO11 | a62d3ff2ee | |
Elias Fleckenstein | 8bae5dd874 | |
NO11 | 2361c1c797 | |
NO11 | 3b3922b2d6 | |
NO11 | 4987636913 | |
NO11 | c542ff998e | |
NO11 | f28a5b90fc | |
AFCMS | 7a130ff2b9 | |
pitchum | 73927cb0cb | |
AFCMS | 2624343c30 | |
AFCMS | 30e2e0d70a | |
Elias Fleckenstein | eea9fb7670 | |
Elias Fleckenstein | 64d3a28578 | |
basxto | 822071c66b | |
NO11 | eafb1a2797 | |
AFCMS | 40f4287ff2 | |
AFCMS | f70dbb8cfa | |
AFCMS | e11dc4b58e | |
AFCMS | e81da3c4ff | |
AFCMS | a896d7f5e4 | |
NO11 | 3577585d74 | |
NO11 | c350b5158b | |
NO11 | ee2fa60cae | |
NO11 | 0795862c5c | |
NO11 | 99ccd9ea4c | |
Elias Fleckenstein | 4324e97db0 | |
Elias Fleckenstein | 7ae4fb6ae7 | |
AFCMS | 2603c4768b | |
AFCMS | c642f9e2f7 | |
NO11 | 1e0ffed26b | |
NO11 | ee21a24fb6 | |
Elias Fleckenstein | 580f6f9629 | |
1F616EMO | 2fac67898e | |
NO11 | 28bfb3f141 | |
NO11 | f3322fd7cb | |
AFCMS | 881f0b0a71 | |
Elias Fleckenstein | e5bdf230d5 | |
Elias Fleckenstein | 0c21a51c45 | |
FinishedFragment | 9706c56964 | |
Elias Fleckenstein | 7ae777a01d | |
FinishedFragment | d391d50eb8 | |
NO11 | 92c25ef691 | |
NO11 | 7464b8ff48 | |
AFCMS | a21b14e707 | |
NO11 | dfd9737d18 | |
Marcin Serwin | 198d3bff53 | |
Marcin Serwin | 522553fcd0 | |
Marcin Serwin | 753a8ce488 | |
Marcin Serwin | f9dbfd8eb5 | |
Marcin Serwin | 0132d0569f | |
Marcin Serwin | a0d3e92b70 | |
marcin-serwin | cbe8583cef | |
Marcin Serwin | 6715aecd63 | |
Marcin Serwin | b06bc276dc | |
AFCMS | b9fd1ac227 | |
AFCMS | 7e64470f70 | |
Marcin Serwin | 06984fa486 | |
Marcin Serwin | 4bd49f7eb0 | |
Marcin Serwin | a4fc38ffcc | |
AFCMS | c8102838cb | |
AFCMS | 6b53dda79b | |
AFCMS | 640b0dc485 | |
Marcin Serwin | 7804de4230 | |
Marcin Serwin | 22bda9c770 | |
Marcin Serwin | 21bdf914bf | |
NO11 | 75e263debc | |
NO11 | a65db15b5c | |
AFCMS | 95627f4304 | |
AFCMS | cd33d406b2 | |
NO11 | ec542a142a | |
NO11 | 91ae3208bd | |
NO11 | 0d619ec6a8 | |
Marcin Serwin | 8ad953ab17 | |
Marcin Serwin | 362965f8fb | |
Marcin Serwin | 1de0009cc6 | |
Marcin Serwin | 7da32dcc36 | |
Marcin Serwin | 1929df492b | |
Marcin Serwin | 69f85a1aa6 | |
Marcin Serwin | c260ef8c6e | |
AFCMS | 5f82e76862 | |
AFCMS | 3bedc81068 | |
AFCMS | b8782d0af1 | |
AFCMS | f1c6c3b285 | |
AFCMS | f8ee9c79c2 | |
AFCMS | 00063cde73 | |
AFCMS | 0119793d7a | |
epCode | 138a3cd154 | |
Lizzy Fleckenstein | c57ead6985 | |
Lizzy Fleckenstein | 772f48ed58 | |
Lizzy Fleckenstein | 079cedaa34 | |
Lizzy Fleckenstein | 1b0d6a2c2d | |
NO11 | 656891ec61 | |
NO11 | 90bab9026b | |
NO11 | 5b43303873 | |
NO11 | 211b078d3a | |
NO11 | bb77443892 | |
AFCMS | 5fc3256930 | |
AFCMS | ee9577b625 | |
AFCMS | fd387a65af | |
AFCMS | 190d5cad33 | |
AFCMS | 4c9eeed499 | |
Marcin Serwin | 425c7505ec | |
Marcin Serwin | 1ad1bbef13 | |
Marcin Serwin | 5ef0c5bcb7 | |
Marcin Serwin | 4a396fde2c | |
Marcin Serwin | a4c21de153 | |
Marcin Serwin | a27833a0e1 | |
Marcin Serwin | d83c29f0c4 | |
Marcin Serwin | 2873c7f7ce | |
Marcin Serwin | e797a4ec9b | |
NO11 | 6105a9ef22 | |
Marcin Serwin | 37b1191c0a | |
Marcin Serwin | dcceafad41 | |
Marcin Serwin | f421e07d1c | |
AFCMS | 49044ac15e | |
AFCMS | 9e074af07f | |
NO11 | 0ea72ed1f1 | |
NO11 | 47b6a8d7f8 | |
AFCMS | 388ab6d8df | |
AFCMS | 981f3d5d74 | |
AFCMS | 3f3abbbee1 | |
AFCMS | 42d3830c48 | |
AFCMS | 8592670f9b | |
AFCMS | d3a237384d | |
AFCMS | 79cf98c8e3 | |
AFCMS | 00f4cfa5b5 | |
AFCMS | 0cc11d0bbb | |
AFCMS | f61102279f | |
AFCMS | f5a91f7c24 | |
AFCMS | 540508638c | |
AFCMS | 03c9c0b830 | |
AFCMS | 36f3c26139 | |
AFCMS | 0d13e1ffdd | |
AFCMS | be9fff4fe2 | |
AFCMS | ad16fe7072 | |
AFCMS | dd3b5a98ae | |
AFCMS | fddaacdeae | |
AFCMS | 8286fd2a4d | |
AFCMS | f76b66eec6 | |
NO11 | aeb8fa5b88 | |
NO11 | ba38f787e2 | |
cora | 66f132a645 | |
NO11 | 8b11e2fec8 | |
NO11 | 9b9ec13967 | |
NO11 | d5a2e5e514 | |
NO11 | dddaed42b6 | |
NO11 | 973a8201ad | |
AFCMS | 91ac70cf28 | |
AFCMS | b6dd8d5c44 | |
AFCMS | 335405f131 | |
AFCMS | 470f7b70bc | |
AFCMS | b0a3cffad0 | |
AFCMS | 9f41c6fc64 | |
AFCMS | 36dc18e9da | |
AFCMS | 5f8d79b37a | |
AFCMS | 21b7647731 | |
AFCMS | 4fd0bf2c3f | |
AFCMS | 5975b20cff | |
AFCMS | 58510b00ee | |
AFCMS | c19c05443e | |
AFCMS | ce9258b764 | |
AFCMS | 8536e2c033 | |
AFCMS | 8baea02f24 | |
AFCMS | 52b1473657 | |
AFCMS | 3fbcacac3b | |
AFCMS | d43b2149e6 | |
AFCMS | b814fe47f9 | |
AFCMS | 962ae359af | |
AFCMS | ddc157acbf | |
AFCMS | 4fd0ea8a88 | |
AFCMS | e6f72e0c60 | |
AFCMS | dc10448c6e | |
AFCMS | 43f418c6c0 | |
AFCMS | 21fdf492e2 | |
AFCMS | c580c1ccad | |
AFCMS | e75a2e3a67 | |
AFCMS | a9c49853aa | |
AFCMS | 4bc473bc29 | |
AFCMS | 8f6a97eefe | |
AFCMS | be5eb621fd | |
AFCMS | 203c7e2c84 | |
AFCMS | 892f4b96c9 | |
AFCMS | b7bf566190 | |
AFCMS | fdfb586b16 | |
AFCMS | ac459d3914 | |
AFCMS | 621a5a53a8 | |
AFCMS | 60736d04b8 | |
AFCMS | df1eeba6b6 | |
AFCMS | 8ac5d32416 | |
AFCMS | 402a1d0088 | |
AFCMS | b8488ce55e | |
AFCMS | f9a86947e5 | |
AFCMS | 30e83088d4 | |
AFCMS | b3c0e7c1bd | |
AFCMS | bbde3b551f | |
AFCMS | 56310a3624 | |
AFCMS | fcc2087b97 | |
AFCMS | a1acf6d3c1 | |
AFCMS | 82396bd994 | |
AFCMS | 45ab5c8e25 | |
AFCMS | b58e61b988 | |
AFCMS | a200252ab8 | |
AFCMS | 0cac8f3f44 | |
AFCMS | 9edcc60ba2 | |
AFCMS | 0113d290b7 | |
AFCMS | e91ee174a4 | |
AFCMS | c1a717238b | |
AFCMS | a71a0e9161 | |
AFCMS | 1870a89af0 | |
AFCMS | 1b3763654e | |
AFCMS | 84a800f22b | |
AFCMS | 17202115fa | |
AFCMS | 5d65c8a3aa | |
AFCMS | e9f38c6b90 | |
Lizzy Fleckenstein | fb60bd0253 | |
AFCMS | a9bf923df9 | |
Elias Åström | 067affcabb | |
Elias Åström | 4d13eddcd6 | |
Elias Åström | f7052943ec | |
Lizzy Fleckenstein | 04436ea5f7 | |
NO11 | cb82e3c9c5 | |
AFCMS | 4992a05c2b | |
kay27 | 9ae1119438 | |
NO11 | 9e095980c8 | |
NO11 | be9fb5be47 | |
NO11 | f4c302e582 | |
Code-Sploit | bcea5a35ef | |
AFCMS | 86d1e0ac12 | |
AFCMS | 866c097d56 | |
AFCMS | d5ec2ccbd8 | |
AFCMS | 0ad03acf46 | |
AFCMS | 3097df731d | |
jordan4ibanez | ad4e86deca | |
jordan4ibanez | 052bb540ba | |
AFCMS | 074e8c8389 | |
NO11 | 7e132866bf | |
NO11 | 11d700b785 | |
NO11 | 9fa51dc6b9 | |
NO11 | 6ffb7f525a | |
AFCMS | 1601437510 | |
AFCMS | 81ee51b0c2 | |
AFCMS | 45201481c1 | |
kay27 | f054eb1ce6 | |
kay27 | f3c540af5d | |
NO11 | a3d3055e8a | |
NO11 | 925e482362 | |
kay27 | 96be84161f | |
NO11 | 7b78d500fd | |
NO11 | 358ea695e1 | |
NO11 | f6b8133381 | |
NO11 | 96aec38947 | |
AFCMS | d221e5ab0a | |
kay27 | 31b56e77ee | |
kay27 | 4bb27a0d79 | |
jordan4ibanez | 37350e81c9 | |
Lizzy Fleckenstein | 4b327bcf99 | |
Lizzy Fleckenstein | 35a2a2b912 | |
Lizzy Fleckenstein | f53ff8418f | |
Lizzy Fleckenstein | 1bd647507b | |
Lizzy Fleckenstein | f5b2a5f5c1 | |
Lizzy Fleckenstein | be0cf5788d | |
Elias Fleckenstein | 52ebc3343f | |
Lizzy Fleckenstein | a9817fea4b | |
Lizzy Fleckenstein | 70db02306f | |
Elias Fleckenstein | 0ff4780066 | |
Lizzy Fleckenstein | f7280dc4f0 | |
Lizzy Fleckenstein | 75d9450002 | |
Lizzy Fleckenstein | cb2bc52cbb | |
Lizzy Fleckenstein | 0d29688b13 | |
Lizzy Fleckenstein | 78ce97aaf6 | |
Lizzy Fleckenstein | 3b3d67a99c | |
Lizzy Fleckenstein | 7513bc600a | |
Lizzy Fleckenstein | f96258ca5c | |
Lizzy Fleckenstein | 33c6e5cdad | |
Lizzy Fleckenstein | 2c5eca3999 | |
talamh | 2c434495c1 | |
AFCMS | f0ec64528c | |
AFCMS | cbb014ed38 | |
Lizzy Fleckenstein | 74e3c6555d | |
Lizzy Fleckenstein | ecc2010fe5 | |
Lizzy Fleckenstein | 5bb1657bd8 | |
Lizzy Fleckenstein | f063d14caf | |
Lizzy Fleckenstein | 02da1abcf9 | |
Lizzy Fleckenstein | 0d287001f7 | |
Benjamin Schötz | f528b31d48 | |
Benjamin Schötz | a77f99a1ec | |
AFCMS | 45965c0799 | |
Lizzy Fleckenstein | 1eaf662833 | |
Lizzy Fleckenstein | 63d4e57a8d | |
Lizzy Fleckenstein | ecea8f1662 | |
Lizzy Fleckenstein | ef08977112 | |
Lizzy Fleckenstein | 1f87874870 | |
Lizzy Fleckenstein | 9a3ae17564 | |
Lizzy Fleckenstein | bd74dbe321 | |
Lizzy Fleckenstein | ca9cd8cbe0 | |
Lizzy Fleckenstein | d9a670dcb8 | |
Lizzy Fleckenstein | f22b3fd457 | |
Lizzy Fleckenstein | 1079ab74d4 | |
talamh | e6b61a1551 | |
talamh | f1229c5401 | |
Lizzy Fleckenstein | 2228b30658 | |
Lizzy Fleckenstein | e11185638f | |
Lizzy Fleckenstein | 1ed6aeca5e | |
Lizzy Fleckenstein | 5d3e74c844 | |
Lizzy Fleckenstein | c6e80b5882 | |
NO11 | 1c05461e67 | |
NO11 | ee749c874b | |
Lizzy Fleckenstein | 8cccbd7caa | |
Lizzy Fleckenstein | ae1212be7f | |
kay27 | 7e3dd715af | |
jordan4ibanez | 55177c03ec | |
jordan4ibanez | b74078a06c | |
Marcin Serwin | e19d39f065 | |
Marcin Serwin | 397a05b651 | |
Marcin Serwin | 5b3a156c09 | |
Marcin Serwin | 604af04849 | |
Marcin Serwin | 0e8c002665 | |
Marcin Serwin | 77597f9bac | |
Marcin Serwin | 65846cb6d8 | |
Marcin Serwin | 97e50714fe | |
Marcin Serwin | 9666076cb4 | |
Marcin Serwin | 7f1cb7fc54 | |
Marcin Serwin | 87b5a7b7ad | |
Marcin Serwin | d74e1fd162 | |
Marcin Serwin | 0e52e09cc8 | |
Marcin Serwin | ffd28c259d | |
Lizzy Fleckenstein | ab4b6d214e | |
Lizzy Fleckenstein | f8b9f16799 | |
Lizzy Fleckenstein | 404097dcc0 | |
Lizzy Fleckenstein | db78c19880 | |
Lizzy Fleckenstein | 7be749a122 | |
Lizzy Fleckenstein | fed1410b7f | |
NO11 | 8b64022bfd | |
NO11 | 71ff38add8 | |
Lizzy Fleckenstein | 6550e3e8e2 | |
Lizzy Fleckenstein | 87e41cc9a9 | |
Lizzy Fleckenstein | ec08032b62 | |
Lizzy Fleckenstein | 199488cc74 | |
Lizzy Fleckenstein | 33c0aa23c5 | |
kay27 | d29c71c69a | |
Marcin Serwin | 6fac49550e | |
jordan4ibanez | cf46f0d8b8 | |
jordan4ibanez | e0c94ccb8a | |
kay27 | a6ac6f5c76 | |
NO11 | 32c03dc27e | |
NO11 | 3195df3864 | |
NO11 | f1b7476825 | |
NO11 | 7a00e8acf7 | |
Lizzy Fleckenstein | 13a0fa2314 | |
Lizzy Fleckenstein | 61c6d6e276 | |
Lizzy Fleckenstein | 528f5e6b86 | |
AFCMS | d0f4dd4309 | |
epCode | 61e4db6bcf | |
AFCMS | ae83e44149 | |
Lizzy Fleckenstein | 819d59dbe7 | |
AFCMS | 173f67870e | |
AFCMS | 0a2fcdc4e8 | |
Elias Fleckenstein | 79aac4c494 | |
Lizzy Fleckenstein | a41aace9d7 | |
epCode | 9f015f2c58 | |
AFCMS | 72e53a82d7 | |
Lizzy Fleckenstein | 0867d6e406 | |
Lizzy Fleckenstein | 347cc7ee7c | |
Lizzy Fleckenstein | 6dc6fc8ec3 | |
Lizzy Fleckenstein | b43f59332a | |
Lizzy Fleckenstein | 3b1d71382f | |
Lizzy Fleckenstein | 05be20fcb8 | |
Lizzy Fleckenstein | 5df1df8e21 | |
Lizzy Fleckenstein | fed4596cb4 | |
Lizzy Fleckenstein | a77a916cc2 | |
Lizzy Fleckenstein | 8a59d7b9c6 | |
Lizzy Fleckenstein | f6d34db76e | |
NO11 | 8df9fe76d8 | |
Lizzy Fleckenstein | a2983e1fda | |
Lizzy Fleckenstein | e710795eed | |
NO11 | 4d2210c4e5 | |
NO11 | 749f1a4a62 | |
NO11 | e5a44dcb75 | |
Lizzy Fleckenstein | dd68b79782 | |
epCode | aac9d6a105 | |
NO11 | dada1e9815 | |
jordan4ibanez | 8a78b1f8ef | |
Lizzy Fleckenstein | edc89898bb | |
Lizzy Fleckenstein | fede04eaa6 | |
Lizzy Fleckenstein | 57662b319c | |
Lizzy Fleckenstein | 3e3f53efbd | |
Lizzy Fleckenstein | 97e69e04aa | |
Lizzy Fleckenstein | cab2167520 | |
Lizzy Fleckenstein | b2407e407a | |
Lizzy Fleckenstein | aeaec68c1b | |
Lizzy Fleckenstein | 302175691a | |
AFCMS | 228759e49b | |
Marcin Serwin | eeaf4e09c6 | |
Marcin Serwin | 29001086c3 | |
Marcin Serwin | 1f07f5ba17 | |
Marcin Serwin | 095128b6b3 | |
Lizzy Fleckenstein | 6aecae6eea | |
Marcin Serwin | bcf5eef010 | |
Marcin Serwin | 3917bb51a2 | |
Marcin Serwin | dd0e381e39 | |
Lizzy Fleckenstein | 939229cb21 | |
Lizzy Fleckenstein | 58d67aace6 | |
Marcin Serwin | 4975a516f5 | |
Marcin Serwin | c0ce511ad0 | |
Lizzy Fleckenstein | ddb7e8388a | |
Lizzy Fleckenstein | 9eba2add3f | |
Marcin Serwin | b93d3cf93c | |
Marcin Serwin | a038908acd | |
Marcin Serwin | a613da65c5 | |
NO11 | 642351a1d2 | |
Marcin Serwin | 18903d5a09 | |
Marcin Serwin | c6a07b8007 | |
Marcin Serwin | 67780f2159 | |
Marcin Serwin | 7e9eb927f1 | |
Marcin Serwin | ae7ea8edef | |
Marcin Serwin | 0cf58ce98d | |
Marcin Serwin | 0ebf6b8f92 | |
Marcin Serwin | 0a8b6a63cb | |
Elias Åström | 73cd482e55 | |
AFCMS | 414adff8fb | |
Elias Åström | 7f3e5bda7e | |
Lizzy Fleckenstein | a03e7f6f3a | |
Lizzy Fleckenstein | 62ed39bd76 | |
Lizzy Fleckenstein | a836ac9879 | |
Lizzy Fleckenstein | 827cd2d7b1 | |
Lizzy Fleckenstein | 78355c5c57 | |
Lizzy Fleckenstein | 50b6f03977 | |
Elias Fleckenstein | 3669ccf907 | |
Lizzy Fleckenstein | 818f33ff74 | |
Sven792 | 038066f0c5 | |
NO11 | c46c07f836 | |
epCode | 62adbb9625 | |
Lizzy Fleckenstein | f9c2d710e2 | |
Lizzy Fleckenstein | 222104b3cb | |
Lizzy Fleckenstein | 9f7b371bfc | |
Lizzy Fleckenstein | 8b23388d63 | |
Lizzy Fleckenstein | acbdc937b4 | |
Lizzy Fleckenstein | 74a3b2654f | |
Lizzy Fleckenstein | dccb71e2fb | |
Lizzy Fleckenstein | 1e7ba2b605 | |
Lizzy Fleckenstein | fe5f61c747 | |
kay27 | 1e5ae0bbac | |
epCode | c7d91baadf | |
epCode | 9c3fcae9e1 | |
Lizzy Fleckenstein | 4c25091430 | |
Lizzy Fleckenstein | ca74f23094 | |
NO11 | 7a5ce1c717 | |
NO11 | e8c15867ad | |
Lizzy Fleckenstein | 20a4e038f5 | |
Lizzy Fleckenstein | c80b1c5140 | |
Lizzy Fleckenstein | 7efc177188 | |
Lizzy Fleckenstein | d8d4cb5def | |
Lizzy Fleckenstein | 52a49e640e | |
Lizzy Fleckenstein | 69485f8505 | |
NO11 | 0c5ca70187 | |
Elias Fleckenstein | ec1ef1affa | |
jordan4ibanez | c03717139b | |
jordan4ibanez | 44a25de12f | |
NO11 | aa533edda2 | |
Lizzy Fleckenstein | c9b4ddb923 | |
Lizzy Fleckenstein | 53b0ad7347 | |
Lizzy Fleckenstein | d9195cc520 | |
NO11 | 49f6ccaa4a | |
NO11 | 3668b2dee6 | |
Lizzy Fleckenstein | a2c3eb95bb | |
Lizzy Fleckenstein | d08a226a51 | |
NO11 | 9bc2f2d866 | |
Lizzy Fleckenstein | 2827542002 | |
Lizzy Fleckenstein | 3ad5b30ea5 | |
Lizzy Fleckenstein | 1cf53caa7a | |
Lizzy Fleckenstein | d1198e8d74 | |
Lizzy Fleckenstein | 4f0bb444fe | |
Lizzy Fleckenstein | f0d7715080 | |
Lizzy Fleckenstein | 5d9c3cd85b | |
Doloment | 956dab78ce | |
Elias Åström | fc9928c0b6 | |
Elias Åström | 573b1dc44b | |
Elias Åström | dd69dcfd9f | |
Lizzy Fleckenstein | 76271199c6 | |
kay27 | 11b66e83a2 | |
AFCMS | 4aa6421faf | |
AFCMS | f967aa3d51 | |
Lizzy Fleckenstein | a7e102426a | |
Lizzy Fleckenstein | bcd058feb1 | |
Lizzy Fleckenstein | baebe3c2d2 | |
Lizzy Fleckenstein | 205572cc69 | |
Lizzy Fleckenstein | f8b54db392 | |
Lizzy Fleckenstein | bbd115fde0 | |
Lizzy Fleckenstein | 7c7af7d664 | |
Lizzy Fleckenstein | f79e12c8ae | |
Lizzy Fleckenstein | 5c928575f8 | |
Lizzy Fleckenstein | 8e37b51cac | |
Lizzy Fleckenstein | 6724a8d0ed | |
NO11 | 06699c2cc4 | |
Lizzy Fleckenstein | 6bbea11fb8 | |
Lizzy Fleckenstein | c7c47c1ca7 | |
Lizzy Fleckenstein | d952423dda | |
Lizzy Fleckenstein | 4d515e95c6 | |
Lizzy Fleckenstein | ca75a893db | |
AFCMS | 6a55136517 | |
AFCMS | 02f393998f | |
AFCMS | 22a2fdbf5f | |
AFCMS | 5f076d372a | |
Lizzy Fleckenstein | bd3c08d367 | |
Lizzy Fleckenstein | 10f81dbf0c | |
Lizzy Fleckenstein | 01c79c5a18 | |
Lizzy Fleckenstein | 095f78b785 | |
AFCMS | 457f61a532 | |
AFCMS | 1a58b0c510 | |
AFCMS | 3dd490ed6b | |
Lizzy Fleckenstein | 5d5ed91519 | |
AFCMS | 2d6e0241cd | |
AFCMS | 1df916bd7b | |
jordan4ibanez | 044e9d22ad | |
epCode | 31dfeae445 | |
epCode | cd04f6d7ce | |
AFCMS | 60e74fc41d | |
AFCMS | a602f64ea5 | |
epCode | 9feaec95d9 | |
epCode | 0ca6c06198 | |
AFCMS | b497778192 | |
AFCMS | b1f7294578 | |
epCode | f0fab4e708 | |
Elias Fleckenstein | 0c5096db13 | |
NO11 | 3fbeab7c85 | |
AFCMS | d865d95639 | |
NO11 | a8760c0375 | |
AFCMS | 54f81b7740 | |
NO11 | bbffda3635 | |
Nils Dagsson Moskopp | 2575e20e7e | |
epCode | 5fe30d7287 | |
Benjamin Schötz | bffd032a8b | |
Benjamin Schötz | e4c8ba795a | |
Marcin Serwin | 2a06be02b3 | |
Lizzy Fleckenstein | 8931ffb7d3 | |
Lizzy Fleckenstein | 727d7be6ab | |
Lizzy Fleckenstein | ec6d68322a | |
epCode | 18cdd13c06 | |
Lizzy Fleckenstein | 9c75cd1a79 | |
Lizzy Fleckenstein | a3af1cdf6e | |
Lizzy Fleckenstein | 4e37cc114c | |
Lizzy Fleckenstein | 5d9bb7cacd | |
Lizzy Fleckenstein | 49e7def70a | |
Lizzy Fleckenstein | ad3defdfb3 | |
Lizzy Fleckenstein | c41bd3932c | |
Lizzy Fleckenstein | 875bb3db84 | |
Elias Fleckenstein | 76af838f4c | |
j1233 | 46b1762872 | |
j1233 | a069406f40 | |
Elias Fleckenstein | 97f8c92d95 | |
Lizzy Fleckenstein | 963adfb4e2 | |
Lizzy Fleckenstein | 6d0abb2973 | |
Lizzy Fleckenstein | e74838136d | |
Lizzy Fleckenstein | d0ddd38d4e | |
Lizzy Fleckenstein | ce0148d9a8 | |
Nicu | 36391d8583 | |
epCode | 5b93ee5e40 | |
epCode | 8d22102c66 | |
epCode | a93562a60c | |
epCode | 399ed85717 | |
jordan4ibanez | 67c1bc973a | |
NO11 | 1c50f9aed6 | |
NO11 | 311c4dc3fb | |
NO11 | 94e7bbfb80 | |
NO11 | 7a14d1d593 | |
NO11 | bc2a79cddf | |
NO11 | 52d7ffa78f | |
NO11 | f768972469 | |
NO11 | 9c68528b8b | |
NO11 | 1597e093b5 | |
NO11 | f15a28109c | |
NO11 | 81d0ce5ea7 | |
epCode | f498d3ae02 | |
Marcin Serwin | c6de8c0669 | |
NO11 | e334a71361 | |
NO11 | 67fff50fc4 | |
NO11 | e96eb79a3f | |
NO11 | de3f236bf2 | |
NO11 | 940621b676 | |
NO11 | 9519c8ebaa | |
NO11 | 28719264df | |
NO11 | 4ac115cf90 | |
NO11 | 1f4ac3d8e0 | |
Code-Sploit | cd0d2d2296 | |
kay27 | 3077498a63 | |
Code-Sploit | 84764389dc | |
Marcin Serwin | 3a88ff7cb3 | |
Lizzy Fleckenstein | 78d387e2df | |
Marcin Serwin | 0de2d3d707 | |
Marcin Serwin | 8934c65564 | |
Lizzy Fleckenstein | 421ab9f660 | |
Lizzy Fleckenstein | 422385ad02 | |
Lizzy Fleckenstein | 20bd91a14e | |
Lizzy Fleckenstein | 5ddd1d0193 | |
Lizzy Fleckenstein | 037832a177 | |
epCode | 424685d3d3 | |
Marcin Serwin | 51711385a1 | |
Marcin Serwin | 9472f86025 | |
Lizzy Fleckenstein | 5028728fbc | |
Lizzy Fleckenstein | 6f770bc962 | |
Lizzy Fleckenstein | 6887948718 | |
Lizzy Fleckenstein | f540bc2bb1 | |
Lizzy Fleckenstein | 5fb7913355 | |
Lizzy Fleckenstein | d3a3253563 | |
Lizzy Fleckenstein | c3868084af | |
Marcin Serwin | 6fb94fb411 | |
Marcin Serwin | 883bd0f437 | |
Marcin Serwin | 42d2dfed19 | |
Marcin Serwin | 33f22f4eed | |
Marcin Serwin | cec4011de2 | |
Marcin Serwin | 38d788998d | |
Marcin Serwin | 9e41d5874a | |
Lizzy Fleckenstein | e0feba115d | |
Marcin Serwin | 1be74d21d3 | |
Lizzy Fleckenstein | b914e088cc | |
Marcin Serwin | 32e33d3b21 | |
Marcin Serwin | 7d674e6e72 | |
Marcin Serwin | ec2e5f7430 | |
Marcin Serwin | 7a4d009648 | |
Marcin Serwin | 8f9f4f6093 | |
Marcin Serwin | 38741754e0 | |
Marcin Serwin | f1ce4f0df6 | |
Marcin Serwin | a598d94afd | |
Marcin Serwin | d553aa5e8c | |
Marcin Serwin | 23e52975fd | |
Marcin Serwin | baed298ce6 | |
Marcin Serwin | 6c5c4b0080 | |
Marcin Serwin | b14b08dc30 | |
Marcin Serwin | 2b6f31fe3b | |
Marcin Serwin | 321caa0632 | |
Marcin Serwin | 6f3bd95106 | |
Marcin Serwin | a3b46be64a | |
Marcin Serwin | 8e4ba0d3e1 | |
Marcin Serwin | a7c8bfc0d7 | |
Marcin Serwin | 504a0e5d2c | |
Marcin Serwin | 99c20421f4 | |
Marcin Serwin | cd8e695ca5 | |
Marcin Serwin | 2bf2766688 | |
Marcin Serwin | 7b4eb28160 | |
Marcin Serwin | 2221f89696 | |
Marcin Serwin | 50b209a945 | |
Marcin Serwin | 034a29edc0 | |
Marcin Serwin | 958fee0a73 | |
Marcin Serwin | d50e63d935 | |
Marcin Serwin | 1fd869b5fb | |
Marcin Serwin | 9f7f1f2c89 | |
Marcin Serwin | ee28bda893 | |
Doloment | 51f121d516 | |
Doloment | 1e17989ef2 |
|
@ -0,0 +1,2 @@
|
|||
# Text Editor TMP Files
|
||||
*.swp
|
|
@ -0,0 +1,55 @@
|
|||
unused_args = false
|
||||
allow_defined_top = true
|
||||
max_line_length = false
|
||||
redefined = false
|
||||
|
||||
globals = {
|
||||
"minetest", "core",
|
||||
}
|
||||
|
||||
read_globals = {
|
||||
"DIR_DELIM",
|
||||
"dump", "dump2",
|
||||
"vector",
|
||||
"VoxelManip", "VoxelArea",
|
||||
"PseudoRandom", "PcgRandom", "PerlinNoise", "PerlinNoiseMap",
|
||||
"ItemStack",
|
||||
"Settings",
|
||||
"unpack",
|
||||
|
||||
table = {
|
||||
fields = {
|
||||
"copy",
|
||||
"indexof",
|
||||
"insert_all",
|
||||
"key_value_swap",
|
||||
}
|
||||
},
|
||||
|
||||
string = {
|
||||
fields = {
|
||||
"split",
|
||||
"trim",
|
||||
}
|
||||
},
|
||||
|
||||
math = {
|
||||
fields = {
|
||||
"hypot",
|
||||
"sign",
|
||||
"factorial"
|
||||
}
|
||||
},
|
||||
------
|
||||
--MODS
|
||||
------
|
||||
|
||||
--GENERAL
|
||||
"default",
|
||||
|
||||
--ENTITIES
|
||||
"cmi",
|
||||
|
||||
--HUD
|
||||
"sfinv", "sfinv_buttons", "unified_inventory", "cmsg", "inventory_plus",
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
eliasfleckenstein@web.de.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
452
CONTRIBUTING.md
|
@ -1,96 +1,414 @@
|
|||
# Contributing to MineClone 2
|
||||
So you want to MineClone 2?
|
||||
# Contributing to MineClone2
|
||||
So you want to contribute to MineClone2?
|
||||
Wow, thank you! :-)
|
||||
|
||||
But first, some things to note:
|
||||
MineClone2 is maintained by Nicu and Fleckenstein. If you have any
|
||||
problems or questions, contact us (See Links section below).
|
||||
|
||||
MineClone 2's development target is to make a free software clone of Minecraft,
|
||||
***version 1.12***, ***PC edition***, *** + Optifine features supported by the Minetest Engine ***.
|
||||
You can help with MineClone2's development in many different ways,
|
||||
whether you're a programmer or not.
|
||||
|
||||
MineClone 2 is maintained by two persons. Namely, kay27 and EliasFleckenstein. You can find us
|
||||
in the Minetest forums (forums.minetest.net), in IRC in the #minetest
|
||||
channel on irc.freenode.net. And finally, you can send e-mails to
|
||||
<eliasfleckenstein@web.de> or <kay27@bk.ru>.
|
||||
## MineClone2's development target is to...
|
||||
- Crucially, create a stable, moddable, free/libre clone of Minecraft
|
||||
based on the Minetest engine with polished features, usable in both
|
||||
singleplayer and multiplayer. Currently, most of **Minecraft Java
|
||||
Edition 1.12.2** features are already implemented and polishing existing
|
||||
features are prioritized over new feature requests.
|
||||
- With lessened priority yet strictly, implement features targetting
|
||||
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
|
||||
by the Minetest Engine). This means features in parity with the listed
|
||||
Minecraft experiences are prioritized over those that don't fulfill this
|
||||
scope.
|
||||
- Optionally, create a performant experience that will run relatively
|
||||
well on really low spec computers. Unfortunately, due to Minecraft's
|
||||
mechanisms and Minetest engine's limitations along with a very small
|
||||
playerbase on low spec computers, optimizations are hard to investigate.
|
||||
|
||||
There is **no** guarantee we will accept anything from anybody.
|
||||
## Links
|
||||
* [Mesehub](https://git.minetest.land/MineClone2/MineClone2)
|
||||
* [Discord](https://discord.gg/xE4z8EEpDC)
|
||||
* [YouTube](https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A)
|
||||
* [IRC](https://web.libera.chat/#mineclone2)
|
||||
* [Matrix](https://app.element.io/#/room/#mc2:matrix.org)
|
||||
* [Reddit](https://www.reddit.com/r/MineClone2/)
|
||||
* [Minetest forums](https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* [ContentDB](https://content.minetest.net/packages/wuzzy/mineclone2/)
|
||||
* [OpenCollective](https://opencollective.com/mineclone2)
|
||||
|
||||
By sending us patches or asking us to include your changes in this game,
|
||||
you agree that they fall under the terms of the LGPLv2.1, which basically
|
||||
means they will become part of a free software.
|
||||
## Using git
|
||||
MineClone2 is developed using the version control system
|
||||
[git](https://git-scm.com/). If you want to contribute code to the
|
||||
project, it is **highly recommended** that you learn the git basics.
|
||||
For non-programmers and people who do not plan to contribute code to
|
||||
MineClone2, git is not required. However, git is a tool that will be
|
||||
referenced frequently because of its usefulness. As such, it is valuable
|
||||
in learning how git works and its terminology. It can also help you
|
||||
keeping your game updated, and easily test pull requests.
|
||||
|
||||
## The suggested workflow
|
||||
We don't **dictate** your workflow, but in order to work with us in an efficient
|
||||
way, you can follow these suggestions:
|
||||
## How you can help as a non-programmer
|
||||
|
||||
For small and medium changes:
|
||||
As someone who does not know how to write programs in Lua or does not
|
||||
know how to use the Minetest API, you can still help us out a lot. For
|
||||
example, by opening an issue in the
|
||||
[Issue tracker](https://git.minetest.land/MineClone2/MineClone2/issues),
|
||||
you can report a bug or request a feature.
|
||||
|
||||
* Fork the repository
|
||||
### Rules about both bugs and feature requests
|
||||
* Stay polite towards the developers and anyone else involved in the
|
||||
discussion.
|
||||
* Choose a descriptive title (e.g. not just "crash", "bug" or "question"
|
||||
).
|
||||
* Please write in plain, understandable English. It will be easier to
|
||||
communicate.
|
||||
* Please start the issue title with a capital letter.
|
||||
* Always check the currently opened issues before creating a new one.
|
||||
Don't report bugs that have already been reported or request features
|
||||
that already have been requested.
|
||||
* If you know about Minetest's inner workings, please think about
|
||||
whether the bug / the feature that you are reporting / requesting is
|
||||
actually an issue with Minetest itself, and if it is, head to the
|
||||
[Minetest issue tracker](https://github.com/minetest/minetest/issues)
|
||||
instead.
|
||||
* If you need any help regarding creating a Mesehub account or opening
|
||||
an issue, feel free to ask on the Discord / Matrix server or the IRC
|
||||
channel.
|
||||
|
||||
### Reporting bugs
|
||||
* A bug is an unintended behavior or, in the worst case, a crash.
|
||||
However, it is not a bug if you believe something is missing in the
|
||||
game. In this case, please read "Requesting features"
|
||||
* If you report a crash, always include the error message. If you play
|
||||
in singleplayer, post a screenshot of the message that Minetest showed
|
||||
when the crash happened (or copy the message into your issue). If you
|
||||
are a server admin, you can find error messages in the log file of the
|
||||
server.
|
||||
* Tell us which MineClone2 and Minetest versions you are using.
|
||||
* Tell us how to reproduce the problem: What you were doing to trigger
|
||||
the bug, e.g. before the crash happened or what causes the faulty
|
||||
behavior.
|
||||
|
||||
### Requesting features
|
||||
* Ensure the requested feature fulfills our development targets and
|
||||
goals.
|
||||
* Begging or excessive attention seeking does not help us in the
|
||||
slightest, and may very well disrupt MineClone2 development. It's better
|
||||
to put that energy into helping or researching the feature in question.
|
||||
After all, we're just volunteers working on our spare time.
|
||||
* Ensure the requested feature has not been implemented in MineClone2
|
||||
latest or development versions.
|
||||
|
||||
### Testing code
|
||||
If you want to help us with speeding up MineClone2 development and
|
||||
making the game more stable, a great way to do that is by testing out
|
||||
new features from contributors. For most new things that get into the
|
||||
game, a pull request is created. A pull request is essentially a
|
||||
programmer saying "Look, I modified the game, please apply my changes
|
||||
to the upstream version of the game". However, every programmer makes
|
||||
mistakes sometimes, some of which are hard to spot. You can help by
|
||||
downloading this modified version of the game and trying it out - then
|
||||
tell us if the code works as expected without any issues. Ideally, you
|
||||
would report issues will pull requests similar to when you were
|
||||
reporting bugs that are the mainline (See Reporting bugs section). You
|
||||
can find currently open pull requests here:
|
||||
<https://git.minetest.land/MineClone2/MineClone2/pulls>. Note that pull
|
||||
requests that start with a `WIP:` are not done yet, and therefore might
|
||||
not work, so it's not very useful to try them out yet.
|
||||
|
||||
### Contributing assets
|
||||
Due to license problems, MineClone2 unfortunately cannot use
|
||||
Minecraft's assets, therefore we are always looking for asset
|
||||
contributions. To contribute assets, it can be useful to learn git
|
||||
basics and read the section for Programmers of this document, however
|
||||
this is not required. It's also a good idea to join the Discord server
|
||||
(or alternatively IRC or Matrix).
|
||||
|
||||
#### Textures
|
||||
For textures we use the Pixel Perfection texture pack. This is mostly
|
||||
enough; however in some cases - e.g. for newer Minecraft features, it's
|
||||
useful to have texture artists around. If you want to make such
|
||||
contributions, join our Discord server. Demands for textures will be
|
||||
communicated there.
|
||||
|
||||
#### Sounds
|
||||
MineClone2 currently does not have a consistent way to handle sounds.
|
||||
The sounds in the game come from different sources, like the SnowZone
|
||||
resource pack or minetest_game. Unfortunately, MineClone2 does not play
|
||||
a sound in every situation you would get one in Minecraft. Any help with
|
||||
sounds is greatly appreciated, however if you add new sounds you should
|
||||
probably work together with a programmer, to write the code to actually
|
||||
play these sounds in game.
|
||||
|
||||
#### 3D Models
|
||||
Most of the 3D Models in MineClone2 come from
|
||||
[22i's repository](https://github.com/22i/minecraft-voxel-blender-models).
|
||||
Similar to the textures, we need people that can make 3D Models with
|
||||
Blender on demand. Many of the models have to be patched, some new
|
||||
animations have to be added etc.
|
||||
|
||||
#### Crediting
|
||||
Asset contributions will be credited in their own respective sections in
|
||||
CREDITS.md. If you have commited the results yourself, you will also be
|
||||
credited in the Contributors section.
|
||||
|
||||
### Contributing Translations
|
||||
|
||||
#### Workflow
|
||||
To add/update support for your language to MineClone2, you should take
|
||||
the steps documented in the section for Programmers, add/update the
|
||||
translation files of the mods that you want to update. You can add
|
||||
support for all mods, just some of them or only one mod; you can update
|
||||
the translation file entirely or only partly; basically any effort is
|
||||
valued. If your changes are small, you can also send them to developers
|
||||
via E-Mail, Discord, IRC or Matrix - they will credit you appropriately.
|
||||
|
||||
#### Things to note
|
||||
You can use the script at `tools/check_translate_files.py` to compare
|
||||
the translation files for the language you are working on with the
|
||||
template files, to see what is missing and what is out of date with
|
||||
the template file. However, template files are often incomplete and/or
|
||||
out of date, sometimes they don't match the code. You can update the
|
||||
translation files if that is required, you can also modify the code in
|
||||
your translation PR if it's related to translation. You can also work on
|
||||
multiple languages at the same time in one PR.
|
||||
|
||||
#### Crediting
|
||||
Translation contributions will be credited in their own in CREDITS.md.
|
||||
If you have commited the results yourself, you will also be credited in
|
||||
the Contributors section.
|
||||
|
||||
### Profiling
|
||||
If you own a server, a great way to help us improve MineClone2's code
|
||||
is by giving us profiler results. Profiler results give us detailed
|
||||
information about the game's performance and let us know places to
|
||||
investigate optimization issues. This way we can make the game faster.
|
||||
|
||||
#### Using Minetest's profiler
|
||||
Minetest has a built in profiler. Simply set `profiler.load = true` in
|
||||
your configuration file and restart the server. After running the server
|
||||
for some time, just run `/profiler save` in chat - then you will find a
|
||||
file in the world directory containing the results. Open a new issue and
|
||||
upload the file. You can name the issue "<Server name> profiler
|
||||
results".
|
||||
|
||||
### Let us know your opinion
|
||||
It is always encouraged to actively contribute to issue discussions on
|
||||
MeseHub, let us know what you think about a topic and help us make
|
||||
decisions. Also, note that a lot of discussion takes place on the
|
||||
Discord server, so it's definitely worth checking it out.
|
||||
|
||||
### Funding
|
||||
You can help pay for our infrastructure (Mesehub) by donating to our
|
||||
OpenCollective link (See Links section).
|
||||
|
||||
### Crediting
|
||||
If you opened or have contributed to an issue, you receive the
|
||||
`Community` role on our Discord (after asking for it).
|
||||
OpenCollective Funders are credited in their own section in
|
||||
`CREDITS.md` and receive a special role "Funder" on our discord (unless
|
||||
they have made their donation Incognito).
|
||||
|
||||
## How you can help as a programmer
|
||||
(Almost) all the MineClone2 development is done using pull requests.
|
||||
|
||||
### Recommended workflow
|
||||
* Fork the repository (in case you have not already)
|
||||
* Do your change in a new branch
|
||||
* Upload the repository somewhere where it can be accessed from the Internet and
|
||||
notify us
|
||||
* Create a pull request to get your changes merged into master
|
||||
* Keep your pull request up to date by regularly merging upstream. It is
|
||||
imperative that conflicts are resolved prior to merging the pull
|
||||
request.
|
||||
* After the pull request got merged, you can delete the branch
|
||||
|
||||
For small changes, sending us a patch is also good.
|
||||
### Discuss first
|
||||
If you feel like a problem needs to fixed or you want to make a new
|
||||
feature, you could start writing the code right away and notifying us
|
||||
when you're done, but it never hurts to discuss things first. If there
|
||||
is no issue on the topic, open one. If there is an issue, tell us that
|
||||
you'd like to take care of it, to avoid duplicate work.
|
||||
|
||||
For big changes: Same as above, but consider notifying us first to avoid
|
||||
duplicate work and possible tears of rejection. ;-)
|
||||
### Don't hesitate to ask for help
|
||||
We appreciate any contributing effort to MineClone2. If you are a
|
||||
relatively new programmer, you can reach us on Discord, Matrix or IRC
|
||||
for questions about git, Lua, Minetest API, MineClone2 codebase or
|
||||
anything related to MineClone2. We can help you avoid writing code that
|
||||
would be deemed inadequate, or help you become familiar with MineClone2
|
||||
better, or assist you use development tools.
|
||||
|
||||
For trusted people, we might give them direct commit access to this
|
||||
repository. In this case, you obviously don't need to fork, but you still
|
||||
need to show your contributions align with the project goals. We still
|
||||
reserve the right to revert everything that we don't like.
|
||||
For bigger changes, we strongly recommend to use feature branches and
|
||||
discuss with me first.
|
||||
### Maintain your own code, even if already got merged
|
||||
Sometimes, your code may cause crashes or bugs - we try to avoid such
|
||||
scenarios by testing every time before merging it, but if your merged
|
||||
work causes problems, we ask you fix the issues as soon as possible.
|
||||
|
||||
Contributors will be credited in `README.md`.
|
||||
### Changing Gameplay
|
||||
Pull Requests that change gameplay have to be properly researched and
|
||||
need to state their sources. These PRs also need Fleckenstein's approval
|
||||
before they are merged.
|
||||
You can use these sources:
|
||||
|
||||
## Quality remarks
|
||||
Again: There is ***no*** guarantee we will accept anything from anybody.
|
||||
But we will gladly take in code from others when we feel it saves us work
|
||||
in the long run.
|
||||
* Minecraft code (Name the source file and line, however DONT post any
|
||||
proprietary code). You can use
|
||||
[MCP](https://minecraft.fandom.com/wiki/Programs_and_editors/Mod_Coder_Pack)
|
||||
to decompile Minecraft or look at
|
||||
[Minestorm](https://github.com/Minestom/Minestom) code.
|
||||
* Testing things inside of Minecraft (Attach screenshots / video footage
|
||||
of the results)
|
||||
* [Official Minecraft Wiki](https://minecraft.fandom.com/wiki/Minecraft_Wiki)
|
||||
(Include a link to the specific page you used)
|
||||
|
||||
### Inclusion criteria
|
||||
Depending on what you add, the chances for inclusion vary:
|
||||
### Stick to our guidelines
|
||||
|
||||
### High chance for inclusion
|
||||
* Gameplay features in Minecraft which are missing in MineClone 2
|
||||
#### Git Guidelines
|
||||
* We use merge rather than rebase or squash merge
|
||||
* We don't use git submodules.
|
||||
* Your commit names should be relatively descriptive, e.g. when saying
|
||||
"Fix #issueid", the commit message should also contain the title of the
|
||||
issue.
|
||||
* Try to keep your commits as atomic as possible (advise, but completely
|
||||
optional)
|
||||
|
||||
### Medium chance for inclusion (discuss first)
|
||||
* Features which don't a impact on gameplay
|
||||
* GUI improvement
|
||||
* Features from pocket or console edition
|
||||
#### Code Guidelines
|
||||
* Each mod must provide `mod.conf`.
|
||||
* Mod names are snake case, and newly added mods start with `mcl_`, e.g.
|
||||
`mcl_core`, `mcl_farming`, `mcl_monster_eggs`. Keep in mind Minetest
|
||||
does not support capital letters in mod names.
|
||||
* To export functions, store them inside a global table named like the
|
||||
mod, e.g.
|
||||
|
||||
### Low chance for inclusion (discuss/optimize first)
|
||||
* Overhaul of architecture / mod structure
|
||||
* Mass-itemstring changes all over the place
|
||||
* Added files have a unusual high file size
|
||||
* Indentation looks like crazy
|
||||
* Single commits which add several unrelated things
|
||||
* Gameplay features which don't exist in Minecraft
|
||||
```lua
|
||||
mcl_example = {}
|
||||
|
||||
### Instant rejection
|
||||
* Proprietary **anything**
|
||||
* Code contains `minetest.env` anywhere
|
||||
function mcl_example.do_something()
|
||||
-- ...
|
||||
end
|
||||
|
||||
## Coding style guide
|
||||
* Indentations should reflect the code flow
|
||||
* Use tabs, not spaces for indentation (tab size = 8)
|
||||
* Never use `minetest.env`
|
||||
```
|
||||
|
||||
## Reporting bugs
|
||||
Report all bugs and missing Minecraft features here:
|
||||
* Public functions should not use self references but rather just access
|
||||
the table directly, e.g.
|
||||
|
||||
<https://git.minetest.land/MineClone2/MineClone2/issues>
|
||||
```lua
|
||||
-- bad
|
||||
function mcl_example:do_something()
|
||||
end
|
||||
|
||||
## Direct discussion
|
||||
We have an IRC channel! Join us on #mineclone2 in freenode.net.
|
||||
-- good
|
||||
function mcl_example.do_something()
|
||||
end
|
||||
```
|
||||
|
||||
<ircs://irc.freenode.net:6697/#mineclone2>
|
||||
* Use modern Minetest API, e.g. no usage of `minetest.env`
|
||||
* Tabs should be used for indent, spaces for alignment, e.g.
|
||||
|
||||
## Creating releases
|
||||
```lua
|
||||
|
||||
-- use tabs for indent
|
||||
|
||||
for i = 1, 10 do
|
||||
if i % 3 == 0 then
|
||||
print(i)
|
||||
end
|
||||
end
|
||||
|
||||
-- use tabs for indent and spaces to align things
|
||||
|
||||
some_table = {
|
||||
{"a string", 5},
|
||||
{"a very much longer string", 10},
|
||||
}
|
||||
```
|
||||
|
||||
* Use double quotes for strings, e.g. `"asdf"` rather than `'asdf'`
|
||||
* Use snake_case rather than CamelCase, e.g. `my_function` rather than
|
||||
`MyFunction`
|
||||
* Don't declare functions as an assignment, e.g.
|
||||
|
||||
```lua
|
||||
-- bad
|
||||
local some_local_func = function()
|
||||
-- ...
|
||||
end
|
||||
|
||||
my_mod.some_func = function()
|
||||
-- ...
|
||||
end
|
||||
|
||||
-- good
|
||||
local function some_local_func()
|
||||
-- ...
|
||||
end
|
||||
|
||||
function my_mod.some_func()
|
||||
-- ...
|
||||
end
|
||||
```
|
||||
|
||||
### Developer status
|
||||
Active and trusted contributors are often granted write access to the
|
||||
MineClone2 repository.
|
||||
|
||||
#### Developer responsibilities
|
||||
- You should not push things directly to
|
||||
MineClone2 master - rather, do your work on a branch on your private
|
||||
repository, then create a pull request. This way other people can review
|
||||
your changes and make sure they work before they get merged.
|
||||
- Merge PRs only when they have recieved the necessary feedback and have
|
||||
been tested by at least two different people (including the author of
|
||||
the pull request), to avoid crashes or the introduction of new bugs.
|
||||
- You may also be assigned to issues or pull
|
||||
requests as a developer. In this case it is your responsibility to fix
|
||||
the issue / review and merge the pull request when it is ready. You can
|
||||
also unassign yourself from the issue / PR if you have no time or don't
|
||||
want to take care of it for some other reason. After all, everyone is a
|
||||
volunteer and we can't expect you to do work that you are not interested
|
||||
in. **The important thing is that you make sure to inform us if you
|
||||
won't take care of something that has been assigned to you.**
|
||||
- Please assign yourself to something that you want to work on to avoid
|
||||
duplicate work.
|
||||
- As a developer, it should be easy to reach you about your work. You
|
||||
should be in at least one of the public MineClone2 discussion rooms -
|
||||
preferrably Discord, but if you really don't like Discord, Matrix
|
||||
or IRC are fine too.
|
||||
|
||||
### Maintainer status
|
||||
Maintainers carry the main responsibility for the project.
|
||||
|
||||
#### Maintainer responsibilities
|
||||
- Making sure issues are addressed and pull requests are reviewed and
|
||||
merged, by assigning either themselves or Developers to issues / PRs
|
||||
- Making releases
|
||||
- Making sure guidelines are kept
|
||||
- Making project decisions based on community feedback
|
||||
- Granting/revoking developer access
|
||||
- Enforcing the code of conduct (See CODE_OF_CONDUCT.md)
|
||||
- Moderating official community spaces (See Links section)
|
||||
- Resolving conflicts and problems within the community
|
||||
|
||||
#### Current maintainers
|
||||
* Fleckenstein - responsible for gameplay review, publishing releases,
|
||||
technical guidelines and issue/PR delegation
|
||||
* Nicu - responsible for community related issues
|
||||
|
||||
#### Release process
|
||||
* Run `tools/generate_ingame_credits.lua` to update the ingame credits
|
||||
from `CREDITS.md` and commit the result (if anything changed)
|
||||
* Launch MineClone2 to make sure it still runs
|
||||
* Update the version number in README.md
|
||||
* Use `git tag <version number>` to tag the latest commit with the version number
|
||||
* Push to repo (don't forget `--tags`!)
|
||||
* Update ContentDB (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Update first post in forum thread (https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* Use `git tag <version number>` to tag the latest commit with the
|
||||
version number
|
||||
* Push to repository (don't forget `--tags`!)
|
||||
* Update ContentDB
|
||||
(https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Update first post in forum thread
|
||||
(https://forum.minetest.net/viewtopic.php?f=50&t=16407)
|
||||
* Post release announcement and changelog in forums
|
||||
|
||||
### Licensing
|
||||
By asking us to include your changes in this game, you agree that they
|
||||
fall under the terms of the GPLv3, which basically means they will
|
||||
become part of a free/libre software.
|
||||
|
||||
### Crediting
|
||||
Contributors, Developers and Maintainers will be credited in
|
||||
`CREDITS.md`. If you make your first time contribution, please add
|
||||
yourself to this file. There are also Discord roles for Contributors,
|
||||
Developers and Maintainers.
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
# Credits
|
||||
|
||||
## Creator of MineClone
|
||||
* davedevils
|
||||
|
||||
## Creator of MineClone2
|
||||
* Wuzzy
|
||||
|
||||
## Maintainers
|
||||
* Fleckenstein
|
||||
* Nicu
|
||||
* kay27
|
||||
|
||||
## Developers
|
||||
* bzoss
|
||||
* AFCMS
|
||||
* epCode
|
||||
* ryvnf
|
||||
* iliekprogrammar
|
||||
* MysticTempest
|
||||
* Rootyjr
|
||||
* aligator
|
||||
* Code-Sploit
|
||||
* NO11
|
||||
* cora
|
||||
* jordan4ibanez
|
||||
|
||||
## Contributors
|
||||
* Laurent Rocher
|
||||
* HimbeerserverDE
|
||||
* TechDudie
|
||||
* Alexander Minges
|
||||
* ArTee3
|
||||
* ZeDique la Ruleta
|
||||
* pitchum
|
||||
* wuniversales
|
||||
* Bu-Gee
|
||||
* David McMackins II
|
||||
* Nicholas Niro
|
||||
* Wouters Dorian
|
||||
* Blue Blancmange
|
||||
* Jared Moody
|
||||
* Li0n
|
||||
* Midgard
|
||||
* Saku Laesvuori
|
||||
* Yukitty
|
||||
* ZedekThePD
|
||||
* aldum
|
||||
* dBeans
|
||||
* nickolas360
|
||||
* yutyo
|
||||
* Tianyang Zhang
|
||||
* j45
|
||||
* Marcin Serwin
|
||||
* erlehmann
|
||||
* E
|
||||
* Benjamin Schötz
|
||||
* Doloment
|
||||
* Sydney Gems
|
||||
* talamh
|
||||
* Emily2255
|
||||
* Emojigit
|
||||
* FinishedFragment
|
||||
* sfan5
|
||||
* Blue Blancmange
|
||||
* Jared Moody
|
||||
* SmallJoker
|
||||
* Sven792
|
||||
* aldum
|
||||
* Dieter44
|
||||
|
||||
## MineClone5
|
||||
* kay27
|
||||
* Debiankaios
|
||||
* epCode
|
||||
* NO11
|
||||
* j45
|
||||
|
||||
## Original Mod Authors
|
||||
* Wuzzy
|
||||
* Fleckenstein
|
||||
* BlockMen
|
||||
* TenPlus1
|
||||
* PilzAdam
|
||||
* ryvnf
|
||||
* stujones11
|
||||
* Arcelmi
|
||||
* celeron55
|
||||
* maikerumine
|
||||
* GunshipPenguin
|
||||
* Qwertymine3
|
||||
* Rochambeau
|
||||
* rubenwardy
|
||||
* stu
|
||||
* 4aiman
|
||||
* Kahrl
|
||||
* Krock
|
||||
* UgnilJoZ
|
||||
* lordfingle
|
||||
* 22i
|
||||
* bzoss
|
||||
* kilbith
|
||||
* xeranas
|
||||
* kddekadenz
|
||||
* sofar
|
||||
* 4Evergreen4
|
||||
* jordan4ibanez
|
||||
* paramat
|
||||
|
||||
## 3D Models
|
||||
* 22i
|
||||
* tobyplowy
|
||||
* epCode
|
||||
|
||||
## Textures
|
||||
* XSSheep
|
||||
* Wuzzy
|
||||
* kingoscargames
|
||||
* leorockway
|
||||
* xMrVizzy
|
||||
* yutyo
|
||||
* NO11
|
||||
* kay27
|
||||
|
||||
## Translations
|
||||
* Wuzzy
|
||||
* Rocher Laurent
|
||||
* wuniversales
|
||||
* kay27
|
||||
* pitchum
|
||||
* todoporlalibertad
|
||||
* Marcin Serwin
|
||||
|
||||
## Funders
|
||||
* 40W
|
||||
|
||||
## Special thanks
|
||||
* celeron55 for creating Minetest
|
||||
* Jordach for the jukebox music compilation from Big Freaking Dig
|
||||
* The workaholics who spent way too much time writing for the Minecraft Wiki. It's an invaluable resource for creating this game
|
||||
* Notch and Jeb for being the major forces behind Minecraft
|
|
@ -149,7 +149,7 @@ These groups are used mostly for informational purposes
|
|||
* `trapdoor=2`: Open trapdoor
|
||||
* `glass=1`: Glass (full cubes only)
|
||||
* `rail=1`: Rail
|
||||
* `music_record`: Music Disc (rating is track ID)
|
||||
* `music_record`: Item is Music Disc
|
||||
* `tnt=1`: Block is TNT
|
||||
* `boat=1`: Boat
|
||||
* `minecart=1`: Minecart
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# Legal information
|
||||
This is a fan game, not developed or endorsed by Mojang AB.
|
||||
|
||||
Copying is an act of love. Please copy and share! <3
|
||||
Here's the detailed legalese for those who need it:
|
||||
|
||||
## License of source code
|
||||
MineClone 2 (by kay27, EliasFleckenstein, Wuzzy, davedevils and countless others)
|
||||
is an imitation of Minecraft.
|
||||
|
||||
MineClone 2 is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License (in the LICENSE.txt file) for more
|
||||
details.
|
||||
|
||||
In the mods you might find in the read-me or license
|
||||
text files a different license. This counts as dual-licensing.
|
||||
You can choose which license applies to you: Either the
|
||||
license of MineClone 2 (GNU GPLv3) or the mod's license.
|
||||
|
||||
MineClone 2 is a direct continuation of the discontinued MineClone
|
||||
project by davedevils.
|
||||
|
||||
Mod credits:
|
||||
See `README.txt` or `README.md` in each mod directory for information about other authors.
|
||||
For mods that do not have such a file, the license is the source code license
|
||||
of MineClone 2 and the author is Wuzzy.
|
||||
|
||||
## License of media (textures and sounds)
|
||||
No non-free licenses are used anywhere.
|
||||
|
||||
The textures, unless otherwise noted, are based on the Pixel Perfection resource pack for Minecraft 1.11,
|
||||
authored by XSSheep. Most textures are verbatim copies, while some textures have been changed or redone
|
||||
from scratch.
|
||||
The glazed terracotta textures have been created by (MysticTempest)[https://github.com/MysticTempest].
|
||||
Source: <https://www.planetminecraft.com/texture_pack/131pixel-perfection/>
|
||||
License: [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
The main menu images are release under: [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||
|
||||
All other files, unless mentioned otherwise, fall under:
|
||||
Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
See README.txt in each mod directory for detailed information about other authors.
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# Missing features in Minetest to recreate Minecraft features
|
||||
|
||||
A side goal of the MineClone 2 project is to find any shortcomings of Minetest which make it impossible to recreate a Minecraft feature exactly.
|
||||
This file lists some of the missing features in Minetest which MineClone 2 would require.
|
||||
|
||||
## No workaround possible
|
||||
For these features, no easy Lua workaround could be found.
|
||||
|
||||
### Lua API
|
||||
#### Tools/wielded item
|
||||
- “Lock” hotbar for a brief time after using an item, making it impossible to switch item or to attach/mine/build until the delay is over (For eating with delay)
|
||||
- Tool charging: Holding down the mouse and releasing it, applying a “power level” (For bow and arrows, more charge = higher arrow range) ([issue 5212](https://github.com/minetest/minetest/issues/5212))
|
||||
- [Dual Wielding](http://minecraft.gamepedia.com/Dual_wield)
|
||||
- Eating/drinking animation ([issue 2811](https://github.com/minetest/minetest/issues/2811))
|
||||
|
||||
#### Nodes
|
||||
- Light level 15 for nodes (not sunlight)
|
||||
- Nodes makes light level drop by 2 or or more per node ([issue 5209](https://github.com/minetest/minetest/issues/5209))
|
||||
|
||||
## Interface
|
||||
- Inventory: Hold down right mouse button while holding an item stack to drop items into the slots as you move the mouse. Makes crafting MUCH faster
|
||||
- Sneak+Leftclick on crafting output crafts as many items as possible and immediately puts it into the player inventory ([issue 5211](https://github.com/minetest/minetest/issues/5211))
|
||||
- Sneak+click puts items in different inventories depending on the item type (maybe group-based)? Required for sneak-clicking to armor slots
|
||||
|
||||
## Workaround theoretically possible
|
||||
For these features, a workaround (or hack ;-)) by using Lua is theoretically possible. But engine support would be clearly better, more performant, more reliable, etc.
|
||||
|
||||
### Lua API
|
||||
#### Nodes
|
||||
- Change walking speed on block (soul sand)
|
||||
- Change jumping height on block (soul sand),
|
||||
- Change object movement speed *through* a block, but for non-liquids (for cobweb)
|
||||
- Add `on_walk_over` event
|
||||
- Set frequency in which players lose breath. 2 seconds are hardcoded in Minetest, in Minecraft it's 1 second
|
||||
- Set damage frequency of `damage_per_second`. In Minecraft many things damage players every half-second rather than every second
|
||||
- Possible to damage players directly when they are with the head inside. This allows to add Minecraft-like suffocation
|
||||
- Sneak+click on inventory slot should be able to put items into additional “fallback inventories” if the first inventory is full. Useful for large chests
|
||||
|
||||
#### Nice-to-haye
|
||||
- Utility function to rotate pillar-like nodes, requiring only 3 possible orientations (X, Y, Z). Basically this is `minetest.rotate_node` but with less orientations; the purpur pillar would mess up if a mirrored rotation would be possible. This is already implemented in MCL2, See `mcl_util` for more infos
|
216
README.md
|
@ -1,8 +1,8 @@
|
|||
# MineClone 2
|
||||
# MineClone2
|
||||
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
|
||||
Developed by many people. Not developed or endorsed by Mojang AB.
|
||||
|
||||
Version: 0.71.0
|
||||
Version: 0.72.0 (in development)
|
||||
|
||||
### Gameplay
|
||||
You start in a randomly-generated world made entirely of cubes. You can explore
|
||||
|
@ -65,41 +65,51 @@ map builders. They can not be obtained in-game or in the creative inventory.
|
|||
Use the `/giveme` chat command to obtain them. See the in-game help for
|
||||
an explanation.
|
||||
|
||||
#### Incomplete items
|
||||
These items do not work yet, but you can get them with `/giveme` for testing:
|
||||
|
||||
* Minecart with Chest: `mcl_minecarts:chest_minecart`
|
||||
* Minecart with Furnace: `mcl_minecarts:furnace_minecart`
|
||||
* Minecart with Hopper: `mcl_minecarts:hopper_minecart`
|
||||
* Minecart with Command Block: `mcl_minecarts:command_block_minecart`
|
||||
|
||||
## Installation
|
||||
This game requires [Minetest](http://minetest.net) to run (version 5.0.0 or
|
||||
This game requires [Minetest](http://minetest.net) to run (version 5.3.0 or
|
||||
later). So you need to install Minetest first. Only stable versions of Minetest
|
||||
are officially supported.
|
||||
There is no support for running MineClone 2 in development versions of Minetest.
|
||||
There is no support for running MineClone2 in development versions of Minetest.
|
||||
|
||||
To install MineClone 2 (if you haven't already), move this directory into the
|
||||
To install MineClone2 (if you haven't already), move this directory into the
|
||||
“games” directory of your Minetest data directory. Consult the help of
|
||||
Minetest to learn more.
|
||||
|
||||
## Project description
|
||||
The main goal of **MineClone 2** is to be a clone of Minecraft and to be released as free software.
|
||||
## Useful links
|
||||
The MineClone2 repository is hosted at Mesehub. To contribute or report issues, head there.
|
||||
|
||||
* **Target of development: Minecraft, PC Edition, version 1.12** (later known as “Java Edition”)
|
||||
* MineClone2 also includes Optifine features supported by the Minetest
|
||||
* Features of later Minecraft versions might sneak in, but they have a low priority
|
||||
* In general, Minecraft is aimed to be cloned as good as Minetest currently permits (no hacks)
|
||||
* Cloning the gameplay has highest priority
|
||||
* MineClone 2 will use different graphics and sounds, but with a similar style
|
||||
* Cloning the interface has no priority. It will only be roughly imitated
|
||||
* Limitations found in Minetest will be written down and reported in the course of development
|
||||
* Mesehub: <https://git.minetest.land/MineClone2/MineClone2>
|
||||
* Discord: <https://discord.gg/xE4z8EEpDC>
|
||||
* YouTube <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
|
||||
* IRC: <https://web.libera.chat/#mineclone2>
|
||||
* Matrix: <https://app.element.io/#/room/#mc2:matrix.org>
|
||||
* Reddit: <https://www.reddit.com/r/MineClone2/>
|
||||
* Minetest forums: <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* ContentDB: <https://content.minetest.net/packages/wuzzy/mineclone2/>
|
||||
* OpenCollective: <https://opencollective.com/mineclone2>
|
||||
|
||||
## Target
|
||||
- Crucially, create a stable, moddable, free/libre clone of Minecraft
|
||||
based on the Minetest engine with polished features, usable in both
|
||||
singleplayer and multiplayer. Currently, most of **Minecraft Java
|
||||
Edition 1.12.2** features are already implemented and polishing existing
|
||||
features are prioritized over new feature requests.
|
||||
- With lessened priority yet strictly, implement features targetting
|
||||
**Minecraft version 1.17 + OptiFine** (OptiFine only as far as supported
|
||||
by the Minetest Engine). This means features in parity with the listed
|
||||
Minecraft experiences are prioritized over those that don't fulfill this
|
||||
scope.
|
||||
- Optionally, create a performant experience that will run relatively
|
||||
well on really low spec computers. Unfortunately, due to Minecraft's
|
||||
mechanisms and Minetest engine's limitations along with a very small
|
||||
playerbase on low spec computers, optimizations are hard to investigate.
|
||||
|
||||
## Completion status
|
||||
This game is currently in **alpha** stage.
|
||||
It is playable, but unfinished, many bugs are to be expected.
|
||||
Backwards-compability is *not* guaranteed, updating your world might cause small and
|
||||
big bugs (such as “missing node” errors or even crashes).
|
||||
This game is currently in **beta** stage.
|
||||
It is playable, but not yet feature-complete.
|
||||
Backwards-compability is not entirely guaranteed, updating your world might cause small bugs.
|
||||
If you want to use the git version of MineClone2 in production, consider using the production branch.
|
||||
It is updated weekly and contains relatively stable code for servers.
|
||||
|
||||
The following main features are available:
|
||||
|
||||
|
@ -128,7 +138,7 @@ The following main features are available:
|
|||
* Clock
|
||||
* Compass
|
||||
* Sponge
|
||||
* Slime block (does not interact with redstone)
|
||||
* Slime block
|
||||
* Small plants and saplings
|
||||
* Dyes
|
||||
* Banners
|
||||
|
@ -140,19 +150,19 @@ The following main features are available:
|
|||
* Creative inventory
|
||||
* Farming
|
||||
* Writable books
|
||||
* A few server commands
|
||||
* Commands
|
||||
* Villages
|
||||
* The End
|
||||
* And more!
|
||||
|
||||
The following features are incomplete:
|
||||
|
||||
* Generated structures (especially villages)
|
||||
* Some monsters and animals
|
||||
* Redstone-related things
|
||||
* The End
|
||||
* Special minecarts
|
||||
* A couple of non-trivial blocks and items
|
||||
|
||||
Bonus features (not found in Minecraft 1.11):
|
||||
Bonus features (not found in Minecraft 1.12):
|
||||
|
||||
* Built-in crafting guide which shows you crafting and smelting recipes
|
||||
* In-game help system containing extensive help about gameplay basics, blocks, items and more
|
||||
|
@ -177,148 +187,14 @@ Technical differences from Minecraft:
|
|||
* Different textures (Pixel Perfection)
|
||||
* Different sounds (various sources)
|
||||
* Different engine (Minetest)
|
||||
* Different easter eggs
|
||||
|
||||
… and finally, MineClone 2 is free software (“free” as in “freedom”)!
|
||||
|
||||
## Reporting bugs
|
||||
Please report all bugs and missing Minecraft features here:
|
||||
|
||||
<https://git.minetest.land/MineClone2/MineClone2/issues>
|
||||
|
||||
## Chating with the community
|
||||
Join our discord server at:
|
||||
|
||||
<https://discord.gg/84GKcxczG3>
|
||||
… and finally, MineClone2 is free software (“free” as in “freedom”)!
|
||||
|
||||
## Other readme files
|
||||
|
||||
* `LICENSE.txt`: The GPLv3 license text
|
||||
* `CONTRIBUTING.md`: Information for those who want to contribute
|
||||
* `MISSING_ENGINE_FEATURES.md`: List of missing features in Minetest which MineClone 2 would need for improvement
|
||||
* `API.md`: For Minetest modders who want to mod this game
|
||||
|
||||
## Credits
|
||||
There are so many people to list (sorry). Check out the respective mod directories for details. This section is only a rough overview of the core authors of this game.
|
||||
|
||||
### Coding
|
||||
* [Wuzzy](https://forum.minetest.net/memberlist.php?mode=viewprofile&u=3082): Main programmer of most mods (retired)
|
||||
* davedevils: Creator of MineClone on which MineClone 2 is based on
|
||||
* [ex-bart](https://github.com/ex-bart): Redstone comparators
|
||||
* [Rootyjr](https://github.com/Rootyjr): Fishing rod and bugfixes
|
||||
* [aligator](https://github.com/aligator): Improvement of doors
|
||||
* [ryvnf](https://github.com/ryvnf): Explosion mechanics
|
||||
* MysticTempest: Bugfixes
|
||||
* [bzoss](https://github.com/bzoss): Status effects, potions, brewing stand
|
||||
* kay27 <kay27@bk.ru>: Experience system, bugfixes, optimizations (Current maintainer)
|
||||
* [EliasFleckenstein03](https://github.com/EliasFleckenstein03): End crystals, enchanting, burning mobs / players, animated chests, bugfixes (Current maintainer)
|
||||
* epCode: Better player animations, new logo
|
||||
* 2mac: Fix bug with powered rail
|
||||
* Lots of other people: TO BE WRITTEN (see mod directories for details)
|
||||
|
||||
#### Mod credits (summary)
|
||||
|
||||
* `controls`: Arcelmi
|
||||
* `flowlib`: Qwertymine13
|
||||
* `walkover`: lordfingle
|
||||
* `drippingwater`: kddekadenz
|
||||
* `mobs_mc`: maikerumine, 22i and others
|
||||
* `awards`: rubenwardy
|
||||
* `screwdriver`: RealBadAngel, Maciej Kastakin, Minetest contributors
|
||||
* `xpanes`: Minetest contributors
|
||||
* `mesecons` mods: Jeija and contributors
|
||||
* `wieldview`: Stuart Jones
|
||||
* `mcl_meshhand`: Based on `newhand` by jordan4ibanez
|
||||
* `mcl_mobs`: Based on Mobs Redo [`mobs`] by TenPlus1 and contributors
|
||||
* Most other mods: Wuzzy
|
||||
|
||||
Detailed credits for each mod can be found in the individual mod directories.
|
||||
|
||||
### Graphics
|
||||
* [XSSheep](http://www.minecraftforum.net/members/XSSheep): Main author; creator of the Pixel Perfection resource pack of Minecraft 1.11
|
||||
* [Wuzzy](https://forum.minetest.net/memberlist.php?mode=viewprofile&u=3082): Main menu imagery and various edits and additions of texture pack
|
||||
* [kingoscargames](https://github.com/kingoscargames): Various edits and additions of existing textures
|
||||
* [leorockway](https://github.com/leorockway): Some edits of mob textures
|
||||
* [xMrVizzy](https://minecraft.curseforge.com/members/xMrVizzy): Glazed terracotta (textures are subject to be replaced later)
|
||||
* yutyo <tanakinci2002@gmail.com>: MineClone 2 logo
|
||||
* Other authors: GUI images
|
||||
|
||||
### Translations
|
||||
* Wuzzy: German
|
||||
* Rocher Laurent <rocherl@club-internet.fr>: French
|
||||
* wuniversales: Spanish
|
||||
* kay27 <kay27@bk.ru>: Russian
|
||||
|
||||
### Models
|
||||
* [22i](https://github.com/22i): Creator of all models
|
||||
* [tobyplowy](https://github.com/tobyplowy): UV-mapping fixes to said models
|
||||
|
||||
### Sounds and music
|
||||
Various sources. See the respective mod directories for details.
|
||||
|
||||
### Special thanks
|
||||
|
||||
* davedevils for starting MineClone, the original version of this game
|
||||
* Wuzzy for starting and maintaining MineClone2 for several years
|
||||
* celeron55 for creating Minetest
|
||||
* Minetest's modding community for providing a huge selection of mods, some of which ended up in MineClone 2
|
||||
* Jordach for the jukebox music compilation from Big Freaking Dig
|
||||
* The workaholics who spent way too much time writing for the Minecraft Wiki. It's an invaluable resource for creating this game
|
||||
* Notch and Jeb for being the major forces behind Minecraft
|
||||
* XSSheep for creating the Pixel Perfection resource pack
|
||||
* [22i](https://github.com/22i) for providing great models and support
|
||||
* [maikerumine](http://github.com/maikerumine) for kicking off mobs and biomes
|
||||
|
||||
## Info for programmers
|
||||
You find interesting and useful infos in `API.md`.
|
||||
|
||||
## Legal information
|
||||
This is a fan game, not developed or endorsed by Mojang AB.
|
||||
|
||||
Copying is an act of love. Please copy and share! <3
|
||||
Here's the detailed legalese for those who need it:
|
||||
|
||||
### License of source code
|
||||
MineClone 2 (by kay27, EliasFleckenstein, Wuzzy, davedevils and countless others)
|
||||
is an imitation of Minecraft.
|
||||
|
||||
MineClone 2 is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License (in the LICENSE.txt file) for more
|
||||
details.
|
||||
|
||||
In the mods you might find in the read-me or license
|
||||
text files a different license. This counts as dual-licensing.
|
||||
You can choose which license applies to you: Either the
|
||||
license of MineClone 2 (GNU GPLv3) or the mod's license.
|
||||
|
||||
MineClone 2 is a direct continuation of the discontinued MineClone
|
||||
project by davedevils.
|
||||
|
||||
Mod credits:
|
||||
See `README.txt` or `README.md` in each mod directory for information about other authors.
|
||||
For mods that do not have such a file, the license is the source code license
|
||||
of MineClone 2 and the author is Wuzzy.
|
||||
|
||||
### License of media (textures and sounds)
|
||||
No non-free licenses are used anywhere.
|
||||
|
||||
The textures, unless otherwise noted, are based on the Pixel Perfection resource pack for Minecraft 1.11,
|
||||
authored by XSSheep. Most textures are verbatim copies, while some textures have been changed or redone
|
||||
from scratch.
|
||||
The glazed terracotta textures have been created by (MysticTempest)[https://github.com/MysticTempest].
|
||||
Source: <https://www.planetminecraft.com/texture_pack/131pixel-perfection/>
|
||||
License: [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
|
||||
The main menu images are release under: [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||
|
||||
All other files, unless mentioned otherwise, fall under:
|
||||
Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
See README.txt in each mod directory for detailed information about other authors.
|
||||
* `LEGAL.md`: Legal information
|
||||
* `CREDITS.md`: List of everyone who contributed
|
||||
|
|
|
@ -83,7 +83,7 @@ local function get_hardness_values_for_groups()
|
|||
|
||||
for _, ndef in pairs(minetest.registered_nodes) do
|
||||
for g, _ in pairs(mcl_autogroup.registered_diggroups) do
|
||||
if ndef.groups[g] ~= nil then
|
||||
if ndef.groups[g] then
|
||||
maps[g][ndef._mcl_hardness or 0] = true
|
||||
end
|
||||
end
|
||||
|
@ -121,7 +121,7 @@ local hardness_values = get_hardness_values_for_groups()
|
|||
-- hardness_value. Used for quick lookup.
|
||||
local hardness_lookup = get_hardness_lookup_for_groups(hardness_values)
|
||||
|
||||
local function compute_creativetimes(group)
|
||||
--[[local function compute_creativetimes(group)
|
||||
local creativetimes = {}
|
||||
|
||||
for index, hardness in pairs(hardness_values[group]) do
|
||||
|
@ -129,7 +129,7 @@ local function compute_creativetimes(group)
|
|||
end
|
||||
|
||||
return creativetimes
|
||||
end
|
||||
end]]
|
||||
|
||||
-- Get the list of digging times for using a specific tool on a specific
|
||||
-- diggroup.
|
||||
|
@ -207,6 +207,10 @@ end
|
|||
function mcl_autogroup.can_harvest(nodename, toolname)
|
||||
local ndef = minetest.registered_nodes[nodename]
|
||||
|
||||
if not ndef then
|
||||
return false
|
||||
end
|
||||
|
||||
if minetest.get_item_group(nodename, "dig_immediate") >= 2 then
|
||||
return true
|
||||
end
|
||||
|
@ -239,13 +243,13 @@ function mcl_autogroup.can_harvest(nodename, toolname)
|
|||
end
|
||||
|
||||
-- Get one groupcap field for using a specific tool on a specific group.
|
||||
local function get_groupcap(group, can_harvest, multiplier, efficiency, uses)
|
||||
--[[local function get_groupcap(group, can_harvest, multiplier, efficiency, uses)
|
||||
return {
|
||||
times = get_digtimes(group, can_harvest, multiplier, efficiency),
|
||||
uses = uses,
|
||||
maxlevel = 0,
|
||||
}
|
||||
end
|
||||
end]]
|
||||
|
||||
-- Returns the tool_capabilities from a tool definition or a default set of
|
||||
-- tool_capabilities
|
||||
|
@ -298,7 +302,7 @@ function mcl_autogroup.get_wear(toolname, diggroup)
|
|||
return math.ceil(65535 / uses)
|
||||
end
|
||||
|
||||
local overwrite = function()
|
||||
local function overwrite()
|
||||
for nname, ndef in pairs(minetest.registered_nodes) do
|
||||
local newgroups = table.copy(ndef.groups)
|
||||
if (nname ~= "ignore" and ndef.diggable) then
|
||||
|
@ -315,12 +319,12 @@ local overwrite = function()
|
|||
newgroups.opaque = 1
|
||||
end
|
||||
|
||||
local creative_breakable = false
|
||||
--local creative_breakable = false
|
||||
|
||||
-- Assign groups used for digging this node depending on
|
||||
-- the registered digging groups
|
||||
for g, gdef in pairs(mcl_autogroup.registered_diggroups) do
|
||||
creative_breakable = true
|
||||
--creative_breakable = true
|
||||
local index = hardness_lookup[g][ndef._mcl_hardness or 0]
|
||||
if ndef.groups[g] then
|
||||
if gdef.levels then
|
||||
|
|
|
@ -81,11 +81,11 @@ if v6_use_snow_biomes then
|
|||
end
|
||||
local v6_freq_desert = tonumber(minetest.get_mapgen_setting("mgv6_freq_desert") or 0.45)
|
||||
|
||||
local NOISE_MAGIC_X = 1619
|
||||
local NOISE_MAGIC_Y = 31337
|
||||
local NOISE_MAGIC_Z = 52591
|
||||
local NOISE_MAGIC_SEED = 1013
|
||||
local noise2d = function(x, y, seed)
|
||||
--local NOISE_MAGIC_X = 1619
|
||||
--local NOISE_MAGIC_Y = 31337
|
||||
--local NOISE_MAGIC_Z = 52591
|
||||
--local NOISE_MAGIC_SEED = 1013
|
||||
local function noise2d(x, y, seed)
|
||||
-- TODO: implement noise2d function for biome blend
|
||||
return 0
|
||||
--[[
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
local get_connected_players = minetest.get_connected_players
|
||||
local clock = os.clock
|
||||
|
||||
local pairs = pairs
|
||||
|
||||
controls = {}
|
||||
controls.players = {}
|
||||
|
||||
|
@ -20,15 +22,15 @@ function controls.register_on_hold(func)
|
|||
end
|
||||
|
||||
local known_controls = {
|
||||
jump=true,
|
||||
right=true,
|
||||
left=true,
|
||||
LMB=true,
|
||||
RMB=true,
|
||||
sneak=true,
|
||||
aux1=true,
|
||||
down=true,
|
||||
up=true,
|
||||
jump = true,
|
||||
right = true,
|
||||
left = true,
|
||||
LMB = true,
|
||||
RMB = true,
|
||||
sneak = true,
|
||||
aux1 = true,
|
||||
down = true,
|
||||
up = true,
|
||||
}
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
|
@ -49,27 +51,27 @@ minetest.register_globalstep(function(dtime)
|
|||
local player_name = player:get_player_name()
|
||||
local player_controls = player:get_player_control()
|
||||
if controls.players[player_name] then
|
||||
for cname, cbool in pairs(player_controls) do
|
||||
if known_controls[cname] == true then
|
||||
--Press a key
|
||||
if cbool==true and controls.players[player_name][cname][1]==false then
|
||||
for _, func in pairs(controls.registered_on_press) do
|
||||
func(player, cname)
|
||||
for cname, cbool in pairs(player_controls) do
|
||||
if known_controls[cname] == true then
|
||||
--Press a key
|
||||
if cbool == true and controls.players[player_name][cname][1] == false then
|
||||
for _, func in pairs(controls.registered_on_press) do
|
||||
func(player, cname)
|
||||
end
|
||||
controls.players[player_name][cname] = {true, clock()}
|
||||
elseif cbool == true and controls.players[player_name][cname][1] == true then
|
||||
for _, func in pairs(controls.registered_on_hold) do
|
||||
func(player, cname, clock()-controls.players[player_name][cname][2])
|
||||
end
|
||||
--Release a key
|
||||
elseif cbool == false and controls.players[player_name][cname][1] == true then
|
||||
for _, func in pairs(controls.registered_on_release) do
|
||||
func(player, cname, clock()-controls.players[player_name][cname][2])
|
||||
end
|
||||
controls.players[player_name][cname] = {false}
|
||||
end
|
||||
end
|
||||
controls.players[player_name][cname] = {true, clock()}
|
||||
elseif cbool==true and controls.players[player_name][cname][1]==true then
|
||||
for _, func in pairs(controls.registered_on_hold) do
|
||||
func(player, cname, clock()-controls.players[player_name][cname][2])
|
||||
end
|
||||
--Release a key
|
||||
elseif cbool==false and controls.players[player_name][cname][1]==true then
|
||||
for _, func in pairs(controls.registered_on_release) do
|
||||
func(player, cname, clock()-controls.players[player_name][cname][2])
|
||||
end
|
||||
controls.players[player_name][cname] = {false}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -1,95 +1,100 @@
|
|||
local math = math
|
||||
|
||||
local get_node = minetest.get_node
|
||||
local get_item_group = minetest.get_item_group
|
||||
|
||||
local registered_nodes = minetest.registered_nodes
|
||||
|
||||
flowlib = {}
|
||||
|
||||
--sum of direction vectors must match an array index
|
||||
|
||||
--(sum,root)
|
||||
--(0,1), (1,1+0=1), (2,1+1=2), (3,1+2^2=5), (4,2^2+2^2=8)
|
||||
|
||||
local inv_roots = {
|
||||
[0] = 1,
|
||||
[1] = 1,
|
||||
[2] = 0.70710678118655,
|
||||
[4] = 0.5,
|
||||
[5] = 0.44721359549996,
|
||||
[8] = 0.35355339059327,
|
||||
}
|
||||
|
||||
local function to_unit_vector(dir_vector)
|
||||
--(sum,root)
|
||||
-- (0,1), (1,1+0=1), (2,1+1=2), (3,1+2^2=5), (4,2^2+2^2=8)
|
||||
local inv_roots = {[0] = 1, [1] = 1, [2] = 0.70710678118655, [4] = 0.5
|
||||
, [5] = 0.44721359549996, [8] = 0.35355339059327}
|
||||
local sum = dir_vector.x*dir_vector.x + dir_vector.z*dir_vector.z
|
||||
return {x=dir_vector.x*inv_roots[sum],y=dir_vector.y
|
||||
,z=dir_vector.z*inv_roots[sum]}
|
||||
local sum = dir_vector.x * dir_vector.x + dir_vector.z * dir_vector.z
|
||||
return {x = dir_vector.x * inv_roots[sum], y = dir_vector.y, z = dir_vector.z * inv_roots[sum]}
|
||||
end
|
||||
|
||||
local is_touching = function(realpos,nodepos,radius)
|
||||
local function is_touching(realpos,nodepos,radius)
|
||||
local boarder = 0.5 - radius
|
||||
return (math.abs(realpos - nodepos) > (boarder))
|
||||
return math.abs(realpos - nodepos) > (boarder)
|
||||
end
|
||||
|
||||
flowlib.is_touching = is_touching
|
||||
|
||||
local is_water = function(pos)
|
||||
return (minetest.get_item_group(minetest.get_node(
|
||||
{x=pos.x,y=pos.y,z=pos.z}).name
|
||||
, "water") ~= 0)
|
||||
local function is_water(pos)
|
||||
return get_item_group(get_node(pos).name, "water") ~= 0
|
||||
end
|
||||
|
||||
flowlib.is_water = is_water
|
||||
|
||||
local node_is_water = function(node)
|
||||
return (minetest.get_item_group(node.name, "water") ~= 0)
|
||||
local function node_is_water(node)
|
||||
return get_item_group(node.name, "water") ~= 0
|
||||
end
|
||||
|
||||
flowlib.node_is_water = node_is_water
|
||||
|
||||
local is_lava = function(pos)
|
||||
return (minetest.get_item_group(minetest.get_node(
|
||||
{x=pos.x,y=pos.y,z=pos.z}).name
|
||||
, "lava") ~= 0)
|
||||
local function is_lava(pos)
|
||||
return get_item_group(get_node(pos).name, "lava") ~= 0
|
||||
end
|
||||
|
||||
flowlib.is_lava = is_lava
|
||||
|
||||
local node_is_lava = function(node)
|
||||
return (minetest.get_item_group(node.name, "lava") ~= 0)
|
||||
local function node_is_lava(node)
|
||||
return get_item_group(node.name, "lava") ~= 0
|
||||
end
|
||||
|
||||
flowlib.node_is_lava = node_is_lava
|
||||
|
||||
|
||||
local is_liquid = function(pos)
|
||||
return (minetest.get_item_group(minetest.get_node(
|
||||
{x=pos.x,y=pos.y,z=pos.z}).name
|
||||
, "liquid") ~= 0)
|
||||
local function is_liquid(pos)
|
||||
return get_item_group(get_node(pos).name, "liquid") ~= 0
|
||||
end
|
||||
|
||||
flowlib.is_liquid = is_liquid
|
||||
|
||||
local node_is_liquid = function(node)
|
||||
return (minetest.get_item_group(node.name, "liquid") ~= 0)
|
||||
local function node_is_liquid(node)
|
||||
return minetest.get_item_group(node.name, "liquid") ~= 0
|
||||
end
|
||||
|
||||
flowlib.node_is_liquid = node_is_liquid
|
||||
|
||||
--This code is more efficient
|
||||
local function quick_flow_logic(node,pos_testing,direction)
|
||||
local function quick_flow_logic(node, pos_testing, direction)
|
||||
local name = node.name
|
||||
if not minetest.registered_nodes[name] then
|
||||
if not registered_nodes[name] then
|
||||
return 0
|
||||
end
|
||||
if minetest.registered_nodes[name].liquidtype == "source" then
|
||||
local node_testing = minetest.get_node(pos_testing)
|
||||
local param2_testing = node_testing.param2
|
||||
if not minetest.registered_nodes[node_testing.name] then
|
||||
if registered_nodes[name].liquidtype == "source" then
|
||||
local node_testing = get_node(pos_testing)
|
||||
if not registered_nodes[node_testing.name] then
|
||||
return 0
|
||||
end
|
||||
if minetest.registered_nodes[node_testing.name].liquidtype
|
||||
~= "flowing" then
|
||||
if registered_nodes[node_testing.name].liquidtype ~= "flowing" then
|
||||
return 0
|
||||
else
|
||||
return direction
|
||||
end
|
||||
elseif minetest.registered_nodes[name].liquidtype == "flowing" then
|
||||
local node_testing = minetest.get_node(pos_testing)
|
||||
elseif registered_nodes[name].liquidtype == "flowing" then
|
||||
local node_testing = get_node(pos_testing)
|
||||
local param2_testing = node_testing.param2
|
||||
if not minetest.registered_nodes[node_testing.name] then
|
||||
if not registered_nodes[node_testing.name] then
|
||||
return 0
|
||||
end
|
||||
if minetest.registered_nodes[node_testing.name].liquidtype
|
||||
== "source" then
|
||||
if registered_nodes[node_testing.name].liquidtype == "source" then
|
||||
return -direction
|
||||
elseif minetest.registered_nodes[node_testing.name].liquidtype
|
||||
== "flowing" then
|
||||
elseif registered_nodes[node_testing.name].liquidtype == "flowing" then
|
||||
if param2_testing < node.param2 then
|
||||
if (node.param2 - param2_testing) > 6 then
|
||||
return -direction
|
||||
|
@ -108,48 +113,41 @@ local function quick_flow_logic(node,pos_testing,direction)
|
|||
return 0
|
||||
end
|
||||
|
||||
local quick_flow = function(pos,node)
|
||||
local x = 0
|
||||
local z = 0
|
||||
|
||||
local function quick_flow(pos, node)
|
||||
if not node_is_liquid(node) then
|
||||
return {x=0,y=0,z=0}
|
||||
return {x = 0, y = 0, z = 0}
|
||||
end
|
||||
|
||||
x = x + quick_flow_logic(node,{x=pos.x-1,y=pos.y,z=pos.z},-1)
|
||||
x = x + quick_flow_logic(node,{x=pos.x+1,y=pos.y,z=pos.z}, 1)
|
||||
z = z + quick_flow_logic(node,{x=pos.x,y=pos.y,z=pos.z-1},-1)
|
||||
z = z + quick_flow_logic(node,{x=pos.x,y=pos.y,z=pos.z+1}, 1)
|
||||
|
||||
return to_unit_vector({x=x,y=0,z=z})
|
||||
local x = quick_flow_logic(node,{x = pos.x-1, y = pos.y, z = pos.z},-1) + quick_flow_logic(node,{x = pos.x+1, y = pos.y, z = pos.z}, 1)
|
||||
local z = quick_flow_logic(node,{x = pos.x, y = pos.y, z = pos.z-1},-1) + quick_flow_logic(node,{x = pos.x, y = pos.y, z = pos.z+1}, 1)
|
||||
return to_unit_vector({x = x, y = 0, z = z})
|
||||
end
|
||||
|
||||
flowlib.quick_flow = quick_flow
|
||||
|
||||
--if not in water but touching, move centre to touching block
|
||||
--x has higher precedence than z
|
||||
--if pos changes with x, it affects z
|
||||
|
||||
--if not in water but touching, move centre to touching block
|
||||
--x has higher precedence than z
|
||||
--if pos changes with x, it affects z
|
||||
local move_centre = function(pos,realpos,node,radius)
|
||||
if is_touching(realpos.x,pos.x,radius) then
|
||||
if is_liquid({x=pos.x-1,y=pos.y,z=pos.z}) then
|
||||
node = minetest.get_node({x=pos.x-1,y=pos.y,z=pos.z})
|
||||
pos = {x=pos.x-1,y=pos.y,z=pos.z}
|
||||
elseif is_liquid({x=pos.x+1,y=pos.y,z=pos.z}) then
|
||||
node = minetest.get_node({x=pos.x+1,y=pos.y,z=pos.z})
|
||||
pos = {x=pos.x+1,y=pos.y,z=pos.z}
|
||||
local function move_centre(pos, realpos, node, radius)
|
||||
if is_touching(realpos.x, pos.x, radius) then
|
||||
if is_liquid({x = pos.x-1, y = pos.y, z = pos.z}) then
|
||||
node = get_node({x=pos.x-1, y = pos.y, z = pos.z})
|
||||
pos = {x = pos.x-1, y = pos.y, z = pos.z}
|
||||
elseif is_liquid({x = pos.x+1, y = pos.y, z = pos.z}) then
|
||||
node = get_node({x = pos.x+1, y = pos.y, z = pos.z})
|
||||
pos = {x = pos.x+1, y = pos.y, z = pos.z}
|
||||
end
|
||||
end
|
||||
if is_touching(realpos.z,pos.z,radius) then
|
||||
if is_liquid({x=pos.x,y=pos.y,z=pos.z-1}) then
|
||||
node = minetest.get_node({x=pos.x,y=pos.y,z=pos.z-1})
|
||||
pos = {x=pos.x,y=pos.y,z=pos.z-1}
|
||||
elseif is_liquid({x=pos.x,y=pos.y,z=pos.z+1}) then
|
||||
node = minetest.get_node({x=pos.x,y=pos.y,z=pos.z+1})
|
||||
pos = {x=pos.x,y=pos.y,z=pos.z+1}
|
||||
if is_touching(realpos.z, pos.z, radius) then
|
||||
if is_liquid({x = pos.x, y = pos.y, z = pos.z - 1}) then
|
||||
node = get_node({x = pos.x, y = pos.y, z = pos.z - 1})
|
||||
pos = {x = pos.x, y = pos.y, z = pos.z - 1}
|
||||
elseif is_liquid({x = pos.x, y = pos.y, z = pos.z + 1}) then
|
||||
node = get_node({x = pos.x, y = pos.y, z = pos.z + 1})
|
||||
pos = {x = pos.x, y = pos.y, z = pos.z + 1}
|
||||
end
|
||||
end
|
||||
return pos,node
|
||||
return pos, node
|
||||
end
|
||||
|
||||
flowlib.move_centre = move_centre
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
local vector = vector
|
||||
|
||||
local facedir_to_dir = minetest.facedir_to_dir
|
||||
local get_item_group = minetest.get_item_group
|
||||
local remove_node = minetest.remove_node
|
||||
local get_node = minetest.get_node
|
||||
|
||||
local original_function = minetest.check_single_for_falling
|
||||
|
||||
minetest.check_single_for_falling = function(pos)
|
||||
function minetest.check_single_for_falling(pos)
|
||||
local ret_o = original_function(pos)
|
||||
|
||||
local ret = false
|
||||
local node = minetest.get_node(pos)
|
||||
if minetest.get_item_group(node.name, "attached_node_facedir") ~= 0 then
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
if get_item_group(node.name, "attached_node_facedir") ~= 0 then
|
||||
local dir = facedir_to_dir(node.param2)
|
||||
if dir then
|
||||
local cpos = vector.add(pos, dir)
|
||||
local cnode = minetest.get_node(cpos)
|
||||
if minetest.get_item_group(cnode.name, "solid") == 0 then
|
||||
minetest.remove_node(pos)
|
||||
if get_item_group(get_node(vector.add(pos, dir)).name, "solid") == 0 then
|
||||
remove_node(pos)
|
||||
local drops = minetest.get_node_drops(node.name, "")
|
||||
for dr=1, #drops do
|
||||
minetest.add_item(pos, drops[dr])
|
||||
|
@ -20,7 +24,6 @@ minetest.check_single_for_falling = function(pos)
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ret_o or ret
|
||||
end
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
# API documentation of mcl_commands
|
||||
|
||||
The mcl_commands API allows you to register and overide complex commands.
|
||||
This mod is derivated from ChatCommandBuilder by rubenwardy
|
||||
|
||||
Some public functions are not documented here but they are for internal use only.
|
||||
|
||||
## Technical differences from ChatCommandBuilder
|
||||
|
||||
* subcommand aditional specific privs
|
||||
* new types: `json`, `color`, `nodename` an maybe more in the future
|
||||
|
||||
|
||||
## Public Functions
|
||||
|
||||
### `mcl_commands.register_command("exemple", def)`
|
||||
|
||||
This is a function which is called when an item is dispensed by the dispenser.
|
||||
These are the parameters:
|
||||
|
||||
```
|
||||
mcl_commands.register_command("exemple", {
|
||||
func = function(cmd) --function executed on registration
|
||||
cmd:sub(":name:username title :params:json", { --create a new subcommand called "title" with defined patterns
|
||||
func = function(name, target, json)
|
||||
return a_cool_function(target, json) --function executed if the params match the patterns
|
||||
end,
|
||||
privs = {settime = true}, --subcommand aditional specific privs
|
||||
})
|
||||
end,
|
||||
description = "Controls text displayed on the screen.", --the description of the command
|
||||
params = "<target> command <params>", --very basic explaination of the syntax of the command (will be semi-automatic in the future)
|
||||
privs = {server = true}, --global privs
|
||||
})
|
||||
```
|
||||
|
||||
Register a complex chatcommand. If a chat command with the same name is already registered, the program will fail.
|
||||
|
||||
### `mcl_commands.overide_command("exemple", def)`
|
||||
|
||||
Same as above but will overide existing command.
|
||||
|
||||
### `mcl_commands.register_chatcommand_alias(alias, cmd, [bypass])`
|
||||
|
||||
Register an alias called `alias` of the `cmd` command.
|
||||
If the setting `mcl_builtin_commands_overide` is set to `false`, the function will silently fail.
|
||||
If `bypass` is set to `true` the function will not take care of the above setting.
|
||||
Will warn if trying to alias to already existing command.
|
||||
|
||||
### `mcl_commands.rename_chatcommand(newname, cmd, [bypass])`
|
||||
|
||||
Rename `cmd` command to `newname`.
|
||||
If the setting `mcl_builtin_commands_overide` is set to `false`, the function will silently fail.
|
||||
If `bypass` is set to `true` the function will not take care of the above setting.
|
||||
Will warn if trying to rename to already existing command.
|
||||
|
||||
### paterns
|
||||
|
||||
mcl_commands adds many types for patterns
|
||||
If not specified, a value will be by default with the word pattern.
|
||||
|
||||
* pos value must be pos
|
||||
* text value must be text WARNING: this pattern must be the last pattern of the subcommand!!
|
||||
* number value must be number
|
||||
* int value must be integer
|
||||
* word value must be word
|
||||
* alpha value must be alphanumeric
|
||||
* modname value must be a valid modname
|
||||
* alphascore
|
||||
* alphanumeric
|
||||
* username: value must be a valid username
|
||||
* json: value must be a json string (will be parsed automaticaly)
|
||||
* color value must be a color string or a valid named color
|
||||
* nodename value must be a valid (existing) node or item name
|
|
@ -1,374 +0,0 @@
|
|||
local S = minetest.get_translator("mcl_commands")
|
||||
local C = minetest.colorize
|
||||
|
||||
local mod_death_messages = minetest.get_modpath("mcl_death_messages")
|
||||
|
||||
local parse_json = minetest.parse_json
|
||||
local get_modpath = minetest.get_modpath
|
||||
|
||||
mcl_commands.types = {
|
||||
pos = {"%(? *(%-?[%d.]+) *,? *(%-?[%d.]+) *,? *(%-?[%d.]+) *%)?",
|
||||
function(res, pointer)
|
||||
local pos = {
|
||||
x = tonumber(res[pointer]),
|
||||
y = tonumber(res[pointer + 1]),
|
||||
z = tonumber(res[pointer + 2])
|
||||
}
|
||||
if pos.x and pos.y and pos.z then
|
||||
return nil, pos, pointer+3
|
||||
else
|
||||
return S("Pos is invalid!")
|
||||
end
|
||||
end},
|
||||
text = {"(.+)",
|
||||
function(res, pointer)
|
||||
if res[pointer] == tostring(res[pointer]) then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Text is invalid!")
|
||||
end
|
||||
end},
|
||||
number = {"(%-?[%d.]+)}",
|
||||
function(res, pointer)
|
||||
if res[pointer] == tonumber(res[pointer]) then
|
||||
return nil, tonumber(res[pointer]), pointer+1
|
||||
else
|
||||
return S("Number is invalid!")
|
||||
end
|
||||
end},
|
||||
int = {"(%-?[%d]+)}",
|
||||
function(res, pointer)
|
||||
if res[pointer] == math.floor(tonumber(res[pointer])) then
|
||||
return nil, tonumber(res[pointer]), pointer+1
|
||||
else
|
||||
return S("Int is invalid!")
|
||||
end
|
||||
end},
|
||||
word = {"([^ ]+)",
|
||||
function(res, pointer)
|
||||
if res[pointer] == tostring(res[pointer]) then
|
||||
return nil, tostring(res[pointer]), pointer+1
|
||||
else
|
||||
return S("Word is invalid!")
|
||||
end
|
||||
end},
|
||||
quotestring = {'"+(.-)"+',
|
||||
function(res, pointer)
|
||||
if res[pointer] == tostring(res[pointer]) then
|
||||
return nil, string.sub(tostring(res[pointer]), 2, #res[pointer]-1), pointer+1
|
||||
else
|
||||
return S("String is invalid!")
|
||||
end
|
||||
end},
|
||||
alpha = {"([A-Za-z]+)}",
|
||||
function(res, pointer)
|
||||
if res[pointer] then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Alpha is invalid!")
|
||||
end
|
||||
end},
|
||||
modname = {"([a-z0-9_]+)}",
|
||||
function(res, pointer)
|
||||
if get_modpath(res[pointer]) then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Modname is invalid!")
|
||||
end
|
||||
end},
|
||||
alphascore = {"([A-Za-z_]+)",
|
||||
function(res, pointer)
|
||||
if res[pointer] then --What is alphascore?
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Alphascore is invalid!")
|
||||
end
|
||||
end},
|
||||
alphanumeric = {"([A-Za-z0-9]+)}",
|
||||
function(res, pointer)
|
||||
if res[pointer] then --What is alphanumerical?
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Alphanumerical is invalid!")
|
||||
end
|
||||
end},
|
||||
username = {"([A-Za-z0-9-_]+)",
|
||||
function(res, pointer)
|
||||
--if minetest.player_exists(res[pointer]) then
|
||||
if res[pointer] then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Player doesn't exist.")
|
||||
end
|
||||
end},
|
||||
target = {"([A-Za-z0-9-_]+)",
|
||||
function(res, pointer)
|
||||
--if minetest.player_exists(res[pointer]) then
|
||||
if res[pointer] then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Player doesn't exist.")
|
||||
end
|
||||
end},
|
||||
json = {"(.+)", --FIXME
|
||||
function(res, pointer)
|
||||
local parsed = parse_json(res[pointer])
|
||||
if parsed then
|
||||
return nil, parsed, pointer+1
|
||||
else
|
||||
return S("Json failed to parse!")
|
||||
end
|
||||
end},
|
||||
color = {"([^ ]+)", --FIXME
|
||||
function(res, pointer)
|
||||
local color = mcl_util.get_color(res[pointer])
|
||||
if color then
|
||||
return nil, color, pointer+1
|
||||
else
|
||||
return S("Color is not a valid color name or hexadecimal!")
|
||||
end
|
||||
end},
|
||||
nodename = {"(%l+[%w_]+%:?[_%l]+[%w_]*)",
|
||||
function(res, pointer)
|
||||
if minetest.registered_items[res[pointer]] then
|
||||
return nil, res[pointer], pointer+1
|
||||
else
|
||||
return S("Nodename is invalid")
|
||||
end
|
||||
end},
|
||||
}
|
||||
|
||||
function mcl_commands.register_command(name, def)
|
||||
def = def or {}
|
||||
local cmd = mcl_commands.build(name, def)
|
||||
if minetest.registered_chatcommands[name] then
|
||||
error("[mcl_commands] Failed to register command: ["..name.."] command already existing! Use mcl_commands.overide_command() if you want to overide existing command")
|
||||
end
|
||||
minetest.register_chatcommand(name, cmd)
|
||||
minetest.log("action", "[mcl_commands] ["..name.."] command registered successfully")
|
||||
return cmd
|
||||
end
|
||||
|
||||
function mcl_commands.override_command(name, def)
|
||||
def = def or {}
|
||||
local cmd = mcl_commands.build(name, def)
|
||||
if minetest.registered_chatcommands[name] then
|
||||
minetest.unregister_chatcommand(name)
|
||||
end
|
||||
minetest.register_chatcommand(name, cmd)
|
||||
minetest.log("action", "[mcl_commands] ["..name.."] command overridden successfully")
|
||||
return cmd
|
||||
end
|
||||
|
||||
local STATE_READY = 1
|
||||
local STATE_PARAM = 2
|
||||
local STATE_PARAM_TYPE = 3
|
||||
local bad_chars = {"(", ")", ".", "%", "+", "-", "*", "?", "[", "^", "$"}
|
||||
local function escape(char)
|
||||
if bad_chars[char] then
|
||||
return "%" .. char
|
||||
else
|
||||
return char
|
||||
end
|
||||
end
|
||||
|
||||
local dprint = function() end
|
||||
|
||||
function mcl_commands.build(name, chat_def)
|
||||
local cmd = {
|
||||
_subs = {}
|
||||
}
|
||||
function cmd:sub(route, def)
|
||||
dprint("Parsing " .. route)
|
||||
|
||||
if string.trim then
|
||||
route = string.trim(route)
|
||||
end
|
||||
|
||||
local sub = {
|
||||
pattern = "^",
|
||||
params = {},
|
||||
func = def.func,
|
||||
privs = def.privs or {},
|
||||
desc = def.desc,
|
||||
params_desc = def.params or "",
|
||||
}
|
||||
|
||||
-- End of param reached: add it to the pattern
|
||||
local param = ""
|
||||
local param_type = ""
|
||||
local should_be_eos = false
|
||||
local function finishParam()
|
||||
if param ~= "" and param_type ~= "" then
|
||||
dprint(" - Found param " .. param .. " type " .. param_type)
|
||||
|
||||
local pattern = mcl_commands.types[param_type][1]
|
||||
if not pattern then
|
||||
error("Unrecognised param_type=" .. param_type)
|
||||
end
|
||||
|
||||
sub.pattern = sub.pattern .. pattern
|
||||
|
||||
table.insert(sub.params, param_type)
|
||||
|
||||
param = ""
|
||||
param_type = ""
|
||||
end
|
||||
end
|
||||
|
||||
-- Iterate through the route to find params
|
||||
local state = STATE_READY
|
||||
local catching_space = false
|
||||
local match_space = " " -- change to "%s" to also catch tabs and newlines
|
||||
local catch_space = match_space.."+"
|
||||
for i = 1, #route do
|
||||
local c = route:sub(i, i)
|
||||
if should_be_eos then
|
||||
error("Should be end of string. Nothing is allowed after a param of type text.")
|
||||
end
|
||||
|
||||
if state == STATE_READY then
|
||||
if c == ":" then
|
||||
dprint(" - Found :, entering param")
|
||||
state = STATE_PARAM
|
||||
param_type = "word"
|
||||
catching_space = false
|
||||
elseif c:match(match_space) then
|
||||
print(" - Found space")
|
||||
if not catching_space then
|
||||
catching_space = true
|
||||
sub.pattern = sub.pattern .. catch_space
|
||||
end
|
||||
else
|
||||
catching_space = false
|
||||
sub.pattern = sub.pattern .. escape(c)
|
||||
end
|
||||
elseif state == STATE_PARAM then
|
||||
if c == ":" then
|
||||
dprint(" - Found :, entering param type")
|
||||
state = STATE_PARAM_TYPE
|
||||
param_type = ""
|
||||
elseif c:match(match_space) then
|
||||
print(" - Found whitespace, leaving param")
|
||||
state = STATE_READY
|
||||
finishParam()
|
||||
catching_space = true
|
||||
sub.pattern = sub.pattern .. catch_space
|
||||
elseif c:match("%W") then
|
||||
dprint(" - Found nonalphanum, leaving param")
|
||||
state = STATE_READY
|
||||
finishParam()
|
||||
sub.pattern = sub.pattern .. escape(c)
|
||||
else
|
||||
param = param .. c
|
||||
end
|
||||
elseif state == STATE_PARAM_TYPE then
|
||||
if c:match(match_space) then
|
||||
print(" - Found space, leaving param type")
|
||||
state = STATE_READY
|
||||
finishParam()
|
||||
catching_space = true
|
||||
sub.pattern = sub.pattern .. catch_space
|
||||
elseif c:match("%W") then
|
||||
dprint(" - Found nonalphanum, leaving param type")
|
||||
state = STATE_READY
|
||||
finishParam()
|
||||
sub.pattern = sub.pattern .. escape(c)
|
||||
else
|
||||
param_type = param_type .. c
|
||||
end
|
||||
end
|
||||
end
|
||||
dprint(" - End of route")
|
||||
finishParam()
|
||||
sub.pattern = sub.pattern .. "$"
|
||||
dprint("Pattern: " .. sub.pattern)
|
||||
|
||||
table.insert(self._subs, sub)
|
||||
end
|
||||
|
||||
if chat_def.func then
|
||||
chat_def.func(cmd)
|
||||
end
|
||||
|
||||
cmd.func = function(name, param)
|
||||
local msg
|
||||
for i = 1, #cmd._subs do
|
||||
local sub = cmd._subs[i]
|
||||
local res = { string.match(param, sub.pattern) }
|
||||
if #res > 0 then
|
||||
local pointer = 1
|
||||
local params = { name }
|
||||
for j = 1, #sub.params do
|
||||
local param = sub.params[j]
|
||||
local value
|
||||
if mcl_commands.types[param] then
|
||||
msg, value, pointer = mcl_commands.check_type(param, res, pointer)
|
||||
table.insert(params, value)
|
||||
else
|
||||
table.insert(params, res[pointer])
|
||||
pointer = pointer + 1
|
||||
end
|
||||
end
|
||||
local can_execute, missing_privs = minetest.check_player_privs(name, sub.privs)
|
||||
if can_execute then
|
||||
if table.unpack then
|
||||
-- lua 5.2 or later
|
||||
return sub.func(table.unpack(params))
|
||||
else
|
||||
-- lua 5.1 or earlier
|
||||
return sub.func(unpack(params))
|
||||
end
|
||||
else
|
||||
local missing_privs_str = ""
|
||||
for i = 1, #missing_privs do
|
||||
if not i == #missing_privs then
|
||||
missing_privs_str = missing_privs_str..missing_privs[i].." "
|
||||
else
|
||||
missing_privs_str = missing_privs_str..missing_privs[i]
|
||||
end
|
||||
end
|
||||
return false, C(mcl_colors.RED, S("You don't have permission to run this command (missing privilege: "..missing_privs_str..")"))
|
||||
end
|
||||
end
|
||||
end
|
||||
return false, C(mcl_colors.RED, msg)
|
||||
end
|
||||
if chat_def.params then
|
||||
cmd.params = chat_def.params
|
||||
else
|
||||
cmd.params = ""
|
||||
end
|
||||
cmd.privs = chat_def.privs
|
||||
cmd.description = chat_def.description
|
||||
return cmd
|
||||
end
|
||||
|
||||
function mcl_commands.check_type(type, res, pointer)
|
||||
return mcl_commands.types[type][2](res, pointer)
|
||||
end
|
||||
|
||||
function mcl_commands.register_chatcommand_alias(alias, cmd, bypass)
|
||||
if not bypass then bypass = false end
|
||||
if minetest.registered_chatcommands[alias] then
|
||||
minetest.log("warning", "[mcl_commands] trying to alias ["..cmd.."] to already existing ["..alias.."] command")
|
||||
elseif minetest.settings:get_bool("mcl_builtin_commands_overide", true) or bypass then
|
||||
minetest.register_chatcommand(alias, minetest.chatcommands[cmd])
|
||||
minetest.log("action", "[mcl_commands] ["..cmd.."] command aliased successfully to ["..alias.."]")
|
||||
else
|
||||
minetest.log("action", "[mcl_commands] ["..cmd.."] command not aliased to ["..alias.."]")
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_commands.rename_chatcommand(newname, cmd, bypass)
|
||||
if not bypass then bypass = false end
|
||||
if minetest.registered_chatcommands[newname] then
|
||||
minetest.log("warning", "[mcl_commands] trying to rename ["..cmd.."] to already existing ["..alias.."] command")
|
||||
elseif minetest.settings:get_bool("mcl_builtin_commands_overide", true) or bypass then
|
||||
minetest.register_chatcommand(newname, minetest.chatcommands[cmd])
|
||||
minetest.unregister_chatcommand(cmd)
|
||||
minetest.log("action", "[mcl_commands] ["..cmd.."] command renamed successfully to ["..newname.."]")
|
||||
else
|
||||
minetest.log("action", "[mcl_commands] ["..cmd.."] command not renamed to ["..newname.."]")
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
--mcl_commands
|
||||
--derivated from ChatCommandBuilder by @rubenwardy
|
||||
|
||||
local S = minetest.get_translator("mcl_commands")
|
||||
|
||||
local mod_death_messages = minetest.get_modpath("mcl_death_messages")
|
||||
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
mcl_commands = {}
|
||||
|
||||
dofile(modpath.."/api.lua")
|
||||
dofile(modpath.."/utils.lua")
|
|
@ -1,6 +0,0 @@
|
|||
name = mcl_commands
|
||||
author = AFCMS
|
||||
description = MCL2 commands API
|
||||
depends = mcl_colors, mcl_util
|
||||
optional_depends =
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
local S = minetest.get_translator("mcl_commands")
|
||||
|
||||
function mcl_commands.get_target_selector(target, pos, name)
|
||||
if minetest.player_exists(target) then
|
||||
return nil, minetest.get_player_by_name(target)
|
||||
elseif target == "@a" then
|
||||
return nil, minetest.get_connected_players()
|
||||
elseif target == "@r" then
|
||||
local connected = minetest.get_connected_players()
|
||||
return nil, connected[math.random(1, #connected)]
|
||||
elseif target == "@s" then
|
||||
if name then
|
||||
local player = minetest.get_player_by_name(name)
|
||||
end
|
||||
if player then
|
||||
return nil, player
|
||||
else
|
||||
return S("Not a valid player")
|
||||
end
|
||||
elseif target == "@p" then
|
||||
local smallest = math.huge
|
||||
local nearest
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
local distance = vector.distance(pos, player:get_pos())
|
||||
if distance < min_distance then
|
||||
min_distance = distance
|
||||
nearest = player
|
||||
end
|
||||
end
|
||||
if nearest then
|
||||
return nil, nearest
|
||||
else
|
||||
return S("No player online")
|
||||
end
|
||||
elseif target == "@e" then
|
||||
return minetest.luaentities --TODO: add filtering of not valid entities.
|
||||
end
|
||||
end
|
|
@ -0,0 +1,169 @@
|
|||
mcl_damage = {
|
||||
modifiers = {},
|
||||
damage_callbacks = {},
|
||||
death_callbacks = {},
|
||||
types = {
|
||||
in_fire = {is_fire = true},
|
||||
lightning_bolt = {is_lightning = true},
|
||||
on_fire = {is_fire = true, bypasses_armor = true},
|
||||
lava = {is_fire = true},
|
||||
hot_floor = {is_fire = true},
|
||||
in_wall = {bypasses_armor = true},
|
||||
drown = {bypasses_armor = true},
|
||||
starve = {bypasses_armor = true, bypasses_magic = true},
|
||||
cactus = {},
|
||||
fall = {bypasses_armor = true},
|
||||
fly_into_wall = {bypasses_armor = true}, -- unused
|
||||
out_of_world = {bypasses_armor = true, bypasses_magic = true, bypasses_invulnerability = true},
|
||||
generic = {bypasses_armor = true},
|
||||
magic = {is_magic = true, bypasses_armor = true},
|
||||
dragon_breath = {is_magic = true, bypasses_armor = true}, -- this is only used for dragon fireball; dragon fireball does not actually deal impact damage tho, so this is unreachable
|
||||
wither = {bypasses_armor = true}, -- unused
|
||||
wither_skull = {is_magic = true, is_explosion = true}, -- this is non-MC but a workaround to get the proper death message
|
||||
anvil = {},
|
||||
falling_node = {}, -- this is falling_block in MC
|
||||
mob = {},
|
||||
player = {},
|
||||
arrow = {is_projectile = true},
|
||||
fireball = {is_projectile = true, is_fire = true},
|
||||
thorns = {is_magic = true},
|
||||
explosion = {is_explosion = true},
|
||||
cramming = {bypasses_armor = true}, -- unused
|
||||
fireworks = {is_explosion = true}, -- unused
|
||||
}
|
||||
}
|
||||
|
||||
function mcl_damage.register_modifier(func, priority)
|
||||
table.insert(mcl_damage.modifiers, {func = func, priority = priority or 0})
|
||||
end
|
||||
|
||||
function mcl_damage.register_on_damage(func)
|
||||
table.insert(mcl_damage.damage_callbacks, func)
|
||||
end
|
||||
|
||||
function mcl_damage.register_on_death(func)
|
||||
table.insert(mcl_damage.death_callbacks, func)
|
||||
end
|
||||
|
||||
function mcl_damage.run_modifiers(obj, damage, reason)
|
||||
for _, modf in ipairs(mcl_damage.modifiers) do
|
||||
damage = modf.func(obj, damage, reason) or damage
|
||||
if damage == 0 then
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return damage
|
||||
end
|
||||
|
||||
local function run_callbacks(funcs, ...)
|
||||
for _, func in pairs(funcs) do
|
||||
func(...)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_damage.run_damage_callbacks(obj, damage, reason)
|
||||
run_callbacks(mcl_damage.damage_callbacks, obj, damage, reason)
|
||||
end
|
||||
|
||||
function mcl_damage.run_death_callbacks(obj, reason)
|
||||
run_callbacks(mcl_damage.death_callbacks, obj, reason)
|
||||
end
|
||||
|
||||
function mcl_damage.from_punch(mcl_reason, object)
|
||||
mcl_reason.direct = object
|
||||
local luaentity = mcl_reason.direct:get_luaentity()
|
||||
if luaentity then
|
||||
if luaentity._is_arrow then
|
||||
mcl_reason.type = "arrow"
|
||||
elseif luaentity._is_fireball then
|
||||
mcl_reason.type = "fireball"
|
||||
elseif luaentity._cmi_is_mob then
|
||||
mcl_reason.type = "mob"
|
||||
end
|
||||
mcl_reason.source = mcl_reason.source or luaentity._source_object
|
||||
else
|
||||
mcl_reason.type = "player"
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_damage.finish_reason(mcl_reason)
|
||||
mcl_reason.source = mcl_reason.source or mcl_reason.direct
|
||||
mcl_reason.flags = mcl_damage.types[mcl_reason.type]
|
||||
end
|
||||
|
||||
function mcl_damage.from_mt(mt_reason)
|
||||
if mt_reason._mcl_chached_reason then
|
||||
return mt_reason._mcl_chached_reason
|
||||
end
|
||||
|
||||
local mcl_reason
|
||||
|
||||
if mt_reason._mcl_reason then
|
||||
mcl_reason = mt_reason._mcl_reason
|
||||
else
|
||||
mcl_reason = {type = "generic"}
|
||||
|
||||
if mt_reason._mcl_type then
|
||||
mcl_reason.type = mt_reason._mcl_type
|
||||
elseif mt_reason.type == "fall" then
|
||||
mcl_reason.type = "fall"
|
||||
elseif mt_reason.type == "drown" then
|
||||
mcl_reason.type = "drown"
|
||||
elseif mt_reason.type == "punch" then
|
||||
mcl_damage.from_punch(mcl_reason, mt_reason.object)
|
||||
elseif mt_reason.type == "node_damage" and mt_reason.node then
|
||||
if minetest.get_item_group(mt_reason.node, "fire") > 0 then
|
||||
mcl_reason.type = "in_fire"
|
||||
end
|
||||
if minetest.get_item_group(mt_reason.node, "lava") > 0 then
|
||||
mcl_reason.type = "lava"
|
||||
end
|
||||
end
|
||||
|
||||
for key, value in pairs(mt_reason) do
|
||||
if key:find("_mcl_") == 1 then
|
||||
mcl_reason[key:sub(6, #key)] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mcl_damage.finish_reason(mcl_reason)
|
||||
mt_reason._mcl_cached_reason = mcl_reason
|
||||
|
||||
return mcl_reason
|
||||
end
|
||||
|
||||
function mcl_damage.register_type(name, def)
|
||||
mcl_damage.types[name] = def
|
||||
end
|
||||
|
||||
minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
|
||||
if hp_change < 0 then
|
||||
if player:get_hp() <= 0 then
|
||||
return 0
|
||||
end
|
||||
hp_change = -mcl_damage.run_modifiers(player, -hp_change, mcl_damage.from_mt(mt_reason))
|
||||
end
|
||||
return hp_change
|
||||
end, true)
|
||||
|
||||
minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
|
||||
if player:get_hp() > 0 then
|
||||
mt_reason.approved = true
|
||||
if hp_change < 0 then
|
||||
mcl_damage.run_damage_callbacks(player, -hp_change, mcl_damage.from_mt(mt_reason))
|
||||
end
|
||||
end
|
||||
end, false)
|
||||
|
||||
minetest.register_on_dieplayer(function(player, mt_reason)
|
||||
if mt_reason.approved then
|
||||
mcl_damage.run_death_callbacks(player, mcl_damage.from_mt(mt_reason))
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
table.sort(mcl_damage.modifiers, function(a, b) return a.priority < b.priority end)
|
||||
end)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
name = mcl_damage
|
||||
author = Fleckenstein
|
||||
description = Minecraft-like damage reason system
|
|
@ -12,11 +12,12 @@ under the LGPLv2.1 license.
|
|||
|
||||
mcl_explosions = {}
|
||||
|
||||
local mod_death_messages = minetest.get_modpath("mcl_death_messages") ~= nil
|
||||
local mod_fire = minetest.get_modpath("mcl_fire") ~= nil
|
||||
local CONTENT_FIRE = minetest.get_content_id("mcl_fire:fire")
|
||||
local mod_fire = minetest.get_modpath("mcl_fire")
|
||||
--local CONTENT_FIRE = minetest.get_content_id("mcl_fire:fire")
|
||||
|
||||
local S = minetest.get_translator("mcl_explosions")
|
||||
local math = math
|
||||
local vector = vector
|
||||
local table = table
|
||||
|
||||
local hash_node_position = minetest.hash_node_position
|
||||
local get_objects_inside_radius = minetest.get_objects_inside_radius
|
||||
|
@ -27,6 +28,7 @@ local get_voxel_manip = minetest.get_voxel_manip
|
|||
local bulk_set_node = minetest.bulk_set_node
|
||||
local check_for_falling = minetest.check_for_falling
|
||||
local add_item = minetest.add_item
|
||||
local pos_to_string = minetest.pos_to_string
|
||||
|
||||
-- Saved sphere explosion shapes for various radiuses
|
||||
local sphere_shapes = {}
|
||||
|
@ -67,46 +69,44 @@ local function compute_sphere_rays(radius)
|
|||
local rays = {}
|
||||
local sphere = {}
|
||||
|
||||
for i=1, 2 do
|
||||
local function add_ray(pos)
|
||||
sphere[hash_node_position(pos)] = pos
|
||||
end
|
||||
|
||||
for y = -radius, radius do
|
||||
for z = -radius, radius do
|
||||
for x = -radius, 0 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
add_ray(vector.new(x, y, z))
|
||||
add_ray(vector.new(-x, y, z))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for x = -radius, radius do
|
||||
for z = -radius, radius do
|
||||
for y = -radius, 0 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
add_ray(vector.new(x, y, z))
|
||||
add_ray(vector.new(x, -y, z))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for x = -radius, radius do
|
||||
for y = -radius, radius do
|
||||
for z = -radius, radius do
|
||||
for x = -radius, 0, 1 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
local pos = { x = x, y = y, z = z }
|
||||
sphere[hash_node_position(pos)] = pos
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i=1,2 do
|
||||
for x = -radius, radius do
|
||||
for z = -radius, radius do
|
||||
for y = -radius, 0, 1 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
local pos = { x = x, y = y, z = z }
|
||||
sphere[hash_node_position(pos)] = pos
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i=1,2 do
|
||||
for x = -radius, radius do
|
||||
for y = -radius, radius do
|
||||
for z = -radius, 0, 1 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
local pos = { x = x, y = y, z = z }
|
||||
sphere[hash_node_position(pos)] = pos
|
||||
break
|
||||
end
|
||||
for z = -radius, 0 do
|
||||
local d = x * x + y * y + z * z
|
||||
if d <= radius * radius then
|
||||
add_ray(vector.new(x, y, z))
|
||||
add_ray(vector.new(x, y, -z))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -150,7 +150,8 @@ end
|
|||
-- raydirs - The directions for each ray
|
||||
-- radius - The maximum distance each ray will go
|
||||
-- info - Table containing information about explosion
|
||||
-- puncher - object that punches other objects (optional)
|
||||
-- direct - direct source object of the damage (optional)
|
||||
-- source - indirect source object of the damage (optional)
|
||||
--
|
||||
-- Values in info:
|
||||
-- drop_chance - The chance that destroyed nodes will drop their items
|
||||
|
@ -165,7 +166,7 @@ end
|
|||
-- Note that this function has been optimized, it contains code which has been
|
||||
-- inlined to avoid function calls and unnecessary table creation. This was
|
||||
-- measured to give a significant performance increase.
|
||||
local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
||||
local function trace_explode(pos, strength, raydirs, radius, info, direct, source)
|
||||
local vm = get_voxel_manip()
|
||||
|
||||
local emin, emax = vm:read_from_map(vector.subtract(pos, radius),
|
||||
|
@ -176,14 +177,11 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
|
||||
local ystride = (emax.x - emin_x + 1)
|
||||
local zstride = ystride * (emax.y - emin_y + 1)
|
||||
local pos_x = pos.x
|
||||
local pos_y = pos.y
|
||||
local pos_z = pos.z
|
||||
|
||||
local area = VoxelArea:new {
|
||||
--[[local area = VoxelArea:new {
|
||||
MinEdge = emin,
|
||||
MaxEdge = emax
|
||||
}
|
||||
}]]
|
||||
local data = vm:get_data()
|
||||
local destroy = {}
|
||||
|
||||
|
@ -212,7 +210,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
npos_x - emin_x + 1
|
||||
|
||||
local cid = data[idx]
|
||||
local br = node_blastres[cid]
|
||||
local br = node_blastres[cid] or INDESTRUCT_BLASTRES
|
||||
if br < INDESTRUCT_BLASTRES and br > max_blast_resistance then
|
||||
br = max_blast_resistance
|
||||
end
|
||||
|
@ -247,7 +245,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
local ent = obj:get_luaentity()
|
||||
|
||||
-- Ignore items to lower lag
|
||||
if obj:is_player() or (ent and ent.name ~= '__builtin.item') then
|
||||
if (obj:is_player() or (ent and ent.name ~= "__builtin.item")) and obj:get_hp() > 0 then
|
||||
local opos = obj:get_pos()
|
||||
local collisionbox = nil
|
||||
|
||||
|
@ -260,12 +258,12 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
|
||||
if collisionbox then
|
||||
-- Create rays from random points in the collision box
|
||||
local x1 = collisionbox[1] * 2
|
||||
local y1 = collisionbox[2] * 2
|
||||
local z1 = collisionbox[3] * 2
|
||||
local x2 = collisionbox[4] * 2
|
||||
local y2 = collisionbox[5] * 2
|
||||
local z2 = collisionbox[6] * 2
|
||||
local x1 = collisionbox[1]
|
||||
local y1 = collisionbox[2]
|
||||
local z1 = collisionbox[3]
|
||||
local x2 = collisionbox[4]
|
||||
local y2 = collisionbox[5]
|
||||
local z2 = collisionbox[6]
|
||||
local x_len = math.abs(x2 - x1)
|
||||
local y_len = math.abs(y2 - y1)
|
||||
local z_len = math.abs(z2 - z1)
|
||||
|
@ -321,7 +319,6 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
impact = 0
|
||||
end
|
||||
local damage = math.floor((impact * impact + impact) * 7 * strength + 1)
|
||||
local source = puncher or obj
|
||||
|
||||
local sleep_formspec_doesnt_close_mt53 = false
|
||||
if obj:is_player() then
|
||||
|
@ -333,26 +330,22 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
sleep_formspec_doesnt_close_mt53 = true
|
||||
end
|
||||
end
|
||||
if mod_death_messages then
|
||||
mcl_death_messages.player_damage(obj, S("@1 was caught in an explosion.", name))
|
||||
end
|
||||
if rawget(_G, "armor") and armor.last_damage_types then
|
||||
armor.last_damage_types[name] = "explosion"
|
||||
end
|
||||
end
|
||||
|
||||
if sleep_formspec_doesnt_close_mt53 then
|
||||
minetest.after(0.3, function(obj, damage, impact, punch_dir) -- 0.2 is minimum delay for closing old formspec and open died formspec -- TODO: REMOVE THIS IN THE FUTURE
|
||||
if not obj then return end
|
||||
obj:punch(obj, 10, { damage_groups = { full_punch_interval = 1, fleshy = damage, knockback = impact * 20.0 } }, punch_dir)
|
||||
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
|
||||
end, obj, damage, impact, vector.new(punch_dir))
|
||||
else
|
||||
obj:punch(source, 10, { damage_groups = { full_punch_interval = 1, fleshy = damage, knockback = impact * 20.0 } }, punch_dir)
|
||||
minetest.after(0.3, function() -- 0.2 is minimum delay for closing old formspec and open died formspec -- TODO: REMOVE THIS IN THE FUTURE
|
||||
if not obj:is_player() then
|
||||
return
|
||||
end
|
||||
|
||||
mcl_util.deal_damage(obj, damage, {type = "explosion", direct = direct, source = source})
|
||||
|
||||
if obj:is_player() then
|
||||
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
|
||||
elseif ent.tnt_knockback then
|
||||
end)
|
||||
else
|
||||
mcl_util.deal_damage(obj, damage, {type = "explosion", direct = direct, source = source})
|
||||
|
||||
if obj:is_player() or ent.tnt_knockback then
|
||||
obj:add_velocity(vector.multiply(punch_dir, impact * 20))
|
||||
end
|
||||
end
|
||||
|
@ -368,9 +361,9 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
local on_blast = node_on_blast[data[idx]]
|
||||
local remove = true
|
||||
|
||||
if do_drop or on_blast ~= nil then
|
||||
if do_drop or on_blast then
|
||||
local npos = get_position_from_hash(hash)
|
||||
if on_blast ~= nil then
|
||||
if on_blast then
|
||||
on_blast(npos, 1.0, do_drop)
|
||||
remove = false
|
||||
else
|
||||
|
@ -412,8 +405,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, puncher)
|
|||
end
|
||||
|
||||
-- Log explosion
|
||||
minetest.log('action', 'Explosion at ' .. minetest.pos_to_string(pos) ..
|
||||
' with strength ' .. strength .. ' and radius ' .. radius)
|
||||
minetest.log("action", "Explosion at "..pos_to_string(pos).." with strength "..strength.." and radius "..radius)
|
||||
end
|
||||
|
||||
-- Create an explosion with strength at pos.
|
||||
|
@ -422,7 +414,8 @@ end
|
|||
-- pos - The position where the explosion originates from
|
||||
-- strength - The blast strength of the explosion (a TNT explosion uses 4)
|
||||
-- info - Table containing information about explosion
|
||||
-- puncher - object that is reported as source of punches/damage (optional)
|
||||
-- direct - direct source object of the damage (optional)
|
||||
-- source - indirect source object of the damage (optional)
|
||||
--
|
||||
-- Values in info:
|
||||
-- drop_chance - If specified becomes the drop chance of all nodes in the
|
||||
|
@ -436,7 +429,7 @@ end
|
|||
-- griefing - If true, the explosion will destroy nodes (default: true)
|
||||
-- grief_protected - If true, the explosion will also destroy nodes which have
|
||||
-- been protected (default: false)
|
||||
function mcl_explosions.explode(pos, strength, info, puncher)
|
||||
function mcl_explosions.explode(pos, strength, info, direct, source)
|
||||
if info == nil then
|
||||
info = {}
|
||||
end
|
||||
|
@ -465,7 +458,7 @@ function mcl_explosions.explode(pos, strength, info, puncher)
|
|||
info.drop_chance = 0
|
||||
end
|
||||
|
||||
trace_explode(pos, strength, shape, radius, info, puncher)
|
||||
trace_explode(pos, strength, shape, radius, info, direct, source)
|
||||
|
||||
if info.particles then
|
||||
add_particles(pos, radius)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=@1 wurde Opfer einer Explosion.
|
|
@ -1,2 +0,0 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=@1 a été pris dans une explosion.
|
|
@ -0,0 +1,2 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=@1 została wysadzona.
|
|
@ -1,2 +0,0 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=@1 не удалось пережить взрыва.
|
|
@ -1,2 +0,0 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=
|
|
@ -32,9 +32,9 @@ local singlenode = mg_name == "singlenode"
|
|||
|
||||
-- Calculate mapgen_edge_min/mapgen_edge_max
|
||||
mcl_vars.chunksize = math.max(1, tonumber(minetest.get_mapgen_setting("chunksize")) or 5)
|
||||
mcl_vars.MAP_BLOCKSIZE = math.max(1, core.MAP_BLOCKSIZE or 16)
|
||||
mcl_vars.MAP_BLOCKSIZE = math.max(1, minetest.MAP_BLOCKSIZE or 16)
|
||||
mcl_vars.mapgen_limit = math.max(1, tonumber(minetest.get_mapgen_setting("mapgen_limit")) or 31000)
|
||||
mcl_vars.MAX_MAP_GENERATION_LIMIT = math.max(1, core.MAX_MAP_GENERATION_LIMIT or 31000)
|
||||
mcl_vars.MAX_MAP_GENERATION_LIMIT = math.max(1, minetest.MAX_MAP_GENERATION_LIMIT or 31000)
|
||||
local central_chunk_offset = -math.floor(mcl_vars.chunksize / 2)
|
||||
mcl_vars.central_chunk_offset_in_nodes = central_chunk_offset * mcl_vars.MAP_BLOCKSIZE
|
||||
mcl_vars.chunk_size_in_nodes = mcl_vars.chunksize * mcl_vars.MAP_BLOCKSIZE
|
||||
|
|
|
@ -40,10 +40,9 @@ function mcl_loot.get_loot(loot_definitions, pr)
|
|||
total_weight = total_weight + (loot_definitions.items[i].weight or 1)
|
||||
end
|
||||
|
||||
local stacks_min = loot_definitions.stacks_min
|
||||
local stacks_max = loot_definitions.stacks_max
|
||||
if not stacks_min then stacks_min = 1 end
|
||||
if not stacks_max then stacks_max = 1 end
|
||||
--local stacks_min = loot_definitions.stacks_min or 1
|
||||
--local stacks_max = loot_definitions.stacks_max or 1
|
||||
|
||||
local stacks = pr:next(loot_definitions.stacks_min, loot_definitions.stacks_max)
|
||||
for s=1, stacks do
|
||||
local r = pr:next(1, total_weight)
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
local vector = vector
|
||||
local table = table
|
||||
|
||||
local hash_node_position = minetest.hash_node_position
|
||||
local add_particlespawner = minetest.add_particlespawner
|
||||
local delete_particlespawner = minetest.delete_particlespawner
|
||||
|
||||
local ipairs = ipairs
|
||||
|
||||
mcl_particles = {}
|
||||
|
||||
-- Table of particlespawner IDs on a per-node hash basis
|
||||
|
@ -32,11 +41,11 @@ function mcl_particles.add_node_particlespawner(pos, particlespawner_definition,
|
|||
if allowed_level == 0 or levels[level] > allowed_level then
|
||||
return
|
||||
end
|
||||
local poshash = minetest.hash_node_position(pos)
|
||||
local poshash = hash_node_position(pos)
|
||||
if not poshash then
|
||||
return
|
||||
end
|
||||
local id = minetest.add_particlespawner(particlespawner_definition)
|
||||
local id = add_particlespawner(particlespawner_definition)
|
||||
if id == -1 then
|
||||
return
|
||||
end
|
||||
|
@ -47,6 +56,8 @@ function mcl_particles.add_node_particlespawner(pos, particlespawner_definition,
|
|||
return id
|
||||
end
|
||||
|
||||
local add_node_particlespawner = mcl_particles.add_node_particlespawner
|
||||
|
||||
-- Deletes all particlespawners that are assigned to a node position.
|
||||
-- If no particlespawners exist for this position, nothing happens.
|
||||
-- pos: Node positon. MUST use integer values!
|
||||
|
@ -55,14 +66,66 @@ function mcl_particles.delete_node_particlespawners(pos)
|
|||
if allowed_level == 0 then
|
||||
return false
|
||||
end
|
||||
local poshash = minetest.hash_node_position(pos)
|
||||
local poshash = hash_node_position(pos)
|
||||
local ids = particle_nodes[poshash]
|
||||
if ids then
|
||||
for i=1, #ids do
|
||||
minetest.delete_particlespawner(ids[i])
|
||||
delete_particlespawner(ids[i])
|
||||
end
|
||||
particle_nodes[poshash] = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 3 exptime variants because the animation is not tied to particle expiration time.
|
||||
-- 3 colorized variants to imitate minecraft's
|
||||
local smoke_pdef_cached = {}
|
||||
|
||||
function mcl_particles.spawn_smoke(pos, name, smoke_pdef_base)
|
||||
local new_minpos = vector.add(pos, smoke_pdef_base.minrelpos)
|
||||
local new_maxpos = vector.add(pos, smoke_pdef_base.maxrelpos)
|
||||
|
||||
-- populate the cache
|
||||
if smoke_pdef_cached[name] then
|
||||
for i, smoke_pdef in ipairs(smoke_pdef_cached[name]) do
|
||||
smoke_pdef.minpos = new_minpos
|
||||
smoke_pdef.maxpos = new_maxpos
|
||||
add_node_particlespawner(pos, smoke_pdef, "high")
|
||||
end
|
||||
-- cache already populated
|
||||
else
|
||||
smoke_pdef_cached[name] = {}
|
||||
|
||||
local smoke_pdef = table.copy(smoke_pdef_base)
|
||||
smoke_pdef.amount = smoke_pdef_base.amount / 9
|
||||
smoke_pdef.time = 0
|
||||
smoke_pdef.animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 8,
|
||||
aspect_h = 8,
|
||||
-- length = 3 exptime variants
|
||||
}
|
||||
smoke_pdef.collisiondetection = true
|
||||
smoke_pdef.minpos = new_minpos
|
||||
smoke_pdef.maxpos = new_maxpos
|
||||
|
||||
-- the last frame plays for 1/8 * N seconds, so we can take advantage of it
|
||||
-- to have varying exptime for each variant.
|
||||
local exptimes = { 0.175, 0.375, 1.0 }
|
||||
local colorizes = { "199", "209", "243" } -- round(78%, 82%, 90% of 256) - 1
|
||||
|
||||
for _,exptime in ipairs(exptimes) do
|
||||
for _,colorize in ipairs(colorizes) do
|
||||
smoke_pdef.maxexptime = exptime * smoke_pdef_base.maxexptime
|
||||
smoke_pdef.animation.length = exptime + 0.1
|
||||
-- minexptime must be set such that the last frame is actully rendered,
|
||||
-- even if its very short. Larger exptime -> larger range
|
||||
smoke_pdef.minexptime = math.min(exptime, (7.0/8.0 * (exptime + 0.1) + 0.1))
|
||||
smoke_pdef.texture = "mcl_particles_smoke_anim.png^[colorize:#000000:" ..colorize
|
||||
add_node_particlespawner(pos, smoke_pdef, "high")
|
||||
table.insert(smoke_pdef_cached[name], table.copy(smoke_pdef))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 154 B |
After Width: | Height: | Size: 155 B |
After Width: | Height: | Size: 165 B |
|
@ -1,11 +0,0 @@
|
|||
mcl_util.registered_blacklisted_entities = {}
|
||||
function mcl_util.register_blacklisted_entities(type)
|
||||
mcl_util.registered_blacklisted_entities[type] = true
|
||||
end
|
||||
function mcl_util.get_real_entities()
|
||||
for key, val in pairs(minetest.luaentities) do
|
||||
local def = minetest.registered_entities[minetest.luaentities[key].name]
|
||||
if not mcl_util.registered_blacklisted_entities[def.type] then
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,27 @@
|
|||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
mcl_util = {}
|
||||
|
||||
-- Updates all values in t using values from to*.
|
||||
function table.update(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Updates nil values in t using values from to*.
|
||||
function table.update_nil(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
if t[k] == nil then
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Based on minetest.rotate_and_place
|
||||
|
||||
--[[
|
||||
|
@ -152,7 +172,7 @@ function mcl_util.get_eligible_transfer_item_slot(src_inventory, src_list, dst_i
|
|||
end
|
||||
|
||||
-- Returns true if itemstack is a shulker box
|
||||
local is_not_shulker_box = function(itemstack)
|
||||
local function is_not_shulker_box(itemstack)
|
||||
local g = minetest.get_item_group(itemstack:get_name(), "shulker_box")
|
||||
return g == 0 or g == nil
|
||||
end
|
||||
|
@ -214,7 +234,7 @@ function mcl_util.move_item_container(source_pos, destination_pos, source_list,
|
|||
end
|
||||
|
||||
-- Normalize double container by forcing to always use the left segment first
|
||||
local normalize_double_container = function(pos, node, ctype)
|
||||
local function normalize_double_container(pos, node, ctype)
|
||||
if ctype == 6 then
|
||||
pos = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
|
||||
if not pos then
|
||||
|
@ -421,4 +441,131 @@ function mcl_util.get_color(colorstr)
|
|||
end
|
||||
end
|
||||
|
||||
dofile(modpath.."/entities.lua")
|
||||
function mcl_util.call_on_rightclick(itemstack, player, pointed_thing)
|
||||
-- Call on_rightclick if the pointed node defines it
|
||||
if pointed_thing and pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.under
|
||||
local node = minetest.get_node(pos)
|
||||
if player and not player:get_player_control().sneak then
|
||||
local nodedef = minetest.registered_nodes[node.name]
|
||||
local on_rightclick = nodedef and nodedef.on_rightclick
|
||||
if on_rightclick then
|
||||
return on_rightclick(pos, node, player, itemstack, pointed_thing) or itemstack
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.calculate_durability(itemstack)
|
||||
local unbreaking_level = mcl_enchanting.get_enchantment(itemstack, "unbreaking")
|
||||
local armor_uses = minetest.get_item_group(itemstack:get_name(), "mcl_armor_uses")
|
||||
|
||||
local uses
|
||||
|
||||
if armor_uses > 0 then
|
||||
uses = armor_uses
|
||||
if unbreaking_level > 0 then
|
||||
uses = uses / (0.6 + 0.4 / (unbreaking_level + 1))
|
||||
end
|
||||
else
|
||||
local def = itemstack:get_definition()
|
||||
if def then
|
||||
local fixed_uses = def._mcl_uses
|
||||
if fixed_uses then
|
||||
uses = fixed_uses
|
||||
if unbreaking_level > 0 then
|
||||
uses = uses * (unbreaking_level + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
uses = uses or (next(itemstack:get_tool_capabilities().groupcaps) or {}).uses
|
||||
end
|
||||
|
||||
return uses or 0
|
||||
end
|
||||
|
||||
function mcl_util.use_item_durability(itemstack, n)
|
||||
local uses = mcl_util.calculate_durability(itemstack)
|
||||
itemstack:add_wear(65535 / uses * n)
|
||||
end
|
||||
|
||||
function mcl_util.deal_damage(target, damage, mcl_reason)
|
||||
local luaentity = target:get_luaentity()
|
||||
|
||||
if luaentity then
|
||||
if luaentity.deal_damage then
|
||||
luaentity:deal_damage(damage, mcl_reason or {type = "generic"})
|
||||
return
|
||||
elseif luaentity._cmi_is_mob then
|
||||
-- local puncher = mcl_reason and mcl_reason.direct or target
|
||||
-- target:punch(puncher, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy = damage}}, vector.direction(puncher:get_pos(), target:get_pos()), damage)
|
||||
if luaentity.health > 0 then
|
||||
luaentity.health = luaentity.health - damage
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local hp = target:get_hp()
|
||||
|
||||
if hp > 0 then
|
||||
target:set_hp(hp - damage, {_mcl_reason = mcl_reason})
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_hp(obj)
|
||||
local luaentity = obj:get_luaentity()
|
||||
|
||||
if luaentity and luaentity._cmi_is_mob then
|
||||
return luaentity.health
|
||||
else
|
||||
return obj:get_hp()
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_inventory(object, create)
|
||||
if object:is_player() then
|
||||
return object:get_inventory()
|
||||
else
|
||||
local luaentity = object:get_luaentity()
|
||||
local inventory = luaentity.inventory
|
||||
|
||||
if create and not inventory and luaentity.create_inventory then
|
||||
inventory = luaentity:create_inventory()
|
||||
end
|
||||
|
||||
return inventory
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_wielded_item(object)
|
||||
if object:is_player() then
|
||||
return object:get_wielded_item()
|
||||
else
|
||||
-- ToDo: implement getting wielditems from mobs as soon as mobs have wielditems
|
||||
return ItemStack()
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_object_name(object)
|
||||
if object:is_player() then
|
||||
return object:get_player_name()
|
||||
else
|
||||
local luaentity = object:get_luaentity()
|
||||
|
||||
if not luaentity then
|
||||
return tostring(object)
|
||||
end
|
||||
|
||||
return luaentity.nametag and luaentity.nametag ~= "" and luaentity.nametag or luaentity.description or luaentity.name
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.replace_mob(obj, mob)
|
||||
local rot = obj:get_yaw()
|
||||
local pos = obj:get_pos()
|
||||
obj:remove()
|
||||
obj = minetest.add_entity(pos, mob)
|
||||
obj:set_yaw(rot)
|
||||
return obj
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ Params:
|
|||
|
||||
* pos: position
|
||||
|
||||
## mcl_worlds.y_to_layer(y)
|
||||
## mcl_worlds.y_to_layer(y)
|
||||
This function is used to calculate the minetest y layer and dimension of the given <y> minecraft layer.
|
||||
Mainly used for ore generation.
|
||||
Takes an Y coordinate as input and returns:
|
||||
|
@ -61,20 +61,21 @@ In mc, you cant use clock in the nether and the end.
|
|||
|
||||
* pos: position
|
||||
|
||||
## mcl_worlds.register_on_dimension_change(function(player, dimension))
|
||||
## mcl_worlds.register_on_dimension_change(function(player, dimension, last_dimension))
|
||||
Register a callback function func(player, dimension).
|
||||
It will be called whenever a player changes between dimensions.
|
||||
The void counts as dimension.
|
||||
|
||||
* player: player, the player who changed the dimension
|
||||
* dimension: position, The new dimension of the player ("overworld", "nether", "end", "void").
|
||||
* player: player, the player who changed of dimension
|
||||
* dimension: string, The new dimension of the player ("overworld", "nether", "end", "void").
|
||||
* last_dimension: string, The dimension where the player was ("overworld", "nether", "end", "void").
|
||||
|
||||
|
||||
## mcl_worlds.registered_on_dimension_change
|
||||
Table containing all function registered with mcl_worlds.register_on_dimension_change()
|
||||
|
||||
## mcl_worlds.dimension_change(player, dimension)
|
||||
Notify this mod of a dimmension change of <player> to <dimension>
|
||||
Notify this mod of a dimension change of <player> to <dimension>
|
||||
|
||||
* player: player, player who changed the dimension
|
||||
* dimension: string, new dimension ("overworld", "nether", "end", "void")
|
|
@ -1,5 +1,7 @@
|
|||
mcl_worlds = {}
|
||||
|
||||
local get_connected_players = minetest.get_connected_players
|
||||
|
||||
-- For a given position, returns a 2-tuple:
|
||||
-- 1st return value: true if pos is in void
|
||||
-- 2nd return value: true if it is in the deadly part of the void
|
||||
|
@ -33,60 +35,64 @@ end
|
|||
-- If the Y coordinate is not located in any dimension, it will return:
|
||||
-- nil, "void"
|
||||
function mcl_worlds.y_to_layer(y)
|
||||
if y >= mcl_vars.mg_overworld_min then
|
||||
return y - mcl_vars.mg_overworld_min, "overworld"
|
||||
elseif y >= mcl_vars.mg_nether_min and y <= mcl_vars.mg_nether_max+128 then
|
||||
return y - mcl_vars.mg_nether_min, "nether"
|
||||
elseif y >= mcl_vars.mg_end_min and y <= mcl_vars.mg_end_max then
|
||||
return y - mcl_vars.mg_end_min, "end"
|
||||
else
|
||||
return nil, "void"
|
||||
end
|
||||
if y >= mcl_vars.mg_overworld_min then
|
||||
return y - mcl_vars.mg_overworld_min, "overworld"
|
||||
elseif y >= mcl_vars.mg_nether_min and y <= mcl_vars.mg_nether_max+128 then
|
||||
return y - mcl_vars.mg_nether_min, "nether"
|
||||
elseif y >= mcl_vars.mg_end_min and y <= mcl_vars.mg_end_max then
|
||||
return y - mcl_vars.mg_end_min, "end"
|
||||
else
|
||||
return nil, "void"
|
||||
end
|
||||
end
|
||||
|
||||
local y_to_layer = mcl_worlds.y_to_layer
|
||||
|
||||
-- Takes a pos and returns the dimension it belongs to (same as above)
|
||||
function mcl_worlds.pos_to_dimension(pos)
|
||||
local _, dim = mcl_worlds.y_to_layer(pos.y)
|
||||
local _, dim = y_to_layer(pos.y)
|
||||
return dim
|
||||
end
|
||||
|
||||
local pos_to_dimension = mcl_worlds.pos_to_dimension
|
||||
|
||||
-- Takes a Minecraft layer and a “dimension” name
|
||||
-- and returns the corresponding Y coordinate for
|
||||
-- MineClone 2.
|
||||
-- mc_dimension is one of "overworld", "nether", "end" (default: "overworld").
|
||||
function mcl_worlds.layer_to_y(layer, mc_dimension)
|
||||
if mc_dimension == "overworld" or mc_dimension == nil then
|
||||
return layer + mcl_vars.mg_overworld_min
|
||||
elseif mc_dimension == "nether" then
|
||||
return layer + mcl_vars.mg_nether_min
|
||||
elseif mc_dimension == "end" then
|
||||
return layer + mcl_vars.mg_end_min
|
||||
end
|
||||
if mc_dimension == "overworld" or mc_dimension == nil then
|
||||
return layer + mcl_vars.mg_overworld_min
|
||||
elseif mc_dimension == "nether" then
|
||||
return layer + mcl_vars.mg_nether_min
|
||||
elseif mc_dimension == "end" then
|
||||
return layer + mcl_vars.mg_end_min
|
||||
end
|
||||
end
|
||||
|
||||
-- Takes a position and returns true if this position can have weather
|
||||
function mcl_worlds.has_weather(pos)
|
||||
-- Weather in the Overworld and the high part of the void below
|
||||
return pos.y <= mcl_vars.mg_overworld_max and pos.y >= mcl_vars.mg_overworld_min - 64
|
||||
-- Weather in the Overworld and the high part of the void below
|
||||
return pos.y <= mcl_vars.mg_overworld_max and pos.y >= mcl_vars.mg_overworld_min - 64
|
||||
end
|
||||
|
||||
-- Takes a position and returns true if this position can have Nether dust
|
||||
function mcl_worlds.has_dust(pos)
|
||||
-- Weather in the Overworld and the high part of the void below
|
||||
return pos.y <= mcl_vars.mg_nether_max + 138 and pos.y >= mcl_vars.mg_nether_min - 10
|
||||
-- Weather in the Overworld and the high part of the void below
|
||||
return pos.y <= mcl_vars.mg_nether_max + 138 and pos.y >= mcl_vars.mg_nether_min - 10
|
||||
end
|
||||
|
||||
-- Takes a position (pos) and returns true if compasses are working here
|
||||
function mcl_worlds.compass_works(pos)
|
||||
-- It doesn't work in Nether and the End, but it works in the Overworld and in the high part of the void below
|
||||
local _, dim = mcl_worlds.y_to_layer(pos.y)
|
||||
if dim == "nether" or dim == "end" then
|
||||
return false
|
||||
elseif dim == "void" then
|
||||
return pos.y <= mcl_vars.mg_overworld_max and pos.y >= mcl_vars.mg_overworld_min - 64
|
||||
else
|
||||
return true
|
||||
end
|
||||
-- It doesn't work in Nether and the End, but it works in the Overworld and in the high part of the void below
|
||||
local _, dim = mcl_worlds.y_to_layer(pos.y)
|
||||
if dim == "nether" or dim == "end" then
|
||||
return false
|
||||
elseif dim == "void" then
|
||||
return pos.y <= mcl_vars.mg_overworld_max and pos.y >= mcl_vars.mg_overworld_min - 64
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Takes a position (pos) and returns true if clocks are working here
|
||||
|
@ -112,12 +118,15 @@ local last_dimension = {}
|
|||
-- * player: Player who changed the dimension
|
||||
-- * dimension: New dimension ("overworld", "nether", "end", "void")
|
||||
function mcl_worlds.dimension_change(player, dimension)
|
||||
local playername = player:get_player_name()
|
||||
for i=1, #mcl_worlds.registered_on_dimension_change do
|
||||
mcl_worlds.registered_on_dimension_change[i](player, dimension)
|
||||
last_dimension[player:get_player_name()] = dimension
|
||||
mcl_worlds.registered_on_dimension_change[i](player, dimension, last_dimension[playername])
|
||||
end
|
||||
last_dimension[playername] = dimension
|
||||
end
|
||||
|
||||
local dimension_change = mcl_worlds.dimension_change
|
||||
|
||||
----------------------- INTERNAL STUFF ----------------------
|
||||
|
||||
-- Update the dimension callbacks every DIM_UPDATE seconds
|
||||
|
@ -125,19 +134,19 @@ local DIM_UPDATE = 1
|
|||
local dimtimer = 0
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
last_dimension[player:get_player_name()] = mcl_worlds.pos_to_dimension(player:get_pos())
|
||||
last_dimension[player:get_player_name()] = pos_to_dimension(player:get_pos())
|
||||
end)
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
-- regular updates based on iterval
|
||||
dimtimer = dimtimer + dtime;
|
||||
if dimtimer >= DIM_UPDATE then
|
||||
local players = minetest.get_connected_players()
|
||||
for p=1, #players do
|
||||
local dim = mcl_worlds.pos_to_dimension(players[p]:get_pos())
|
||||
local players = get_connected_players()
|
||||
for p = 1, #players do
|
||||
local dim = pos_to_dimension(players[p]:get_pos())
|
||||
local name = players[p]:get_player_name()
|
||||
if dim ~= last_dimension[name] then
|
||||
mcl_worlds.dimension_change(players[p], dim)
|
||||
dimension_change(players[p], dim)
|
||||
end
|
||||
end
|
||||
dimtimer = 0
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# tga_encoder
|
||||
A TGA Encoder written in Lua without the use of external Libraries.
|
||||
|
||||
May be used as a Minetest mod.
|
|
@ -0,0 +1,92 @@
|
|||
tga_encoder = {}
|
||||
|
||||
local image = setmetatable({}, {
|
||||
__call = function(self, ...)
|
||||
local t = setmetatable({}, {__index = self})
|
||||
t:constructor(...)
|
||||
return t
|
||||
end,
|
||||
})
|
||||
|
||||
function image:constructor(pixels)
|
||||
self.data = ""
|
||||
self.pixels = pixels
|
||||
self.width = #pixels[1]
|
||||
self.height = #pixels
|
||||
|
||||
self:encode()
|
||||
end
|
||||
|
||||
function image:encode_colormap_spec()
|
||||
self.data = self.data
|
||||
.. string.char(0, 0) -- first entry index
|
||||
.. string.char(0, 0) -- number of entries
|
||||
.. string.char(0) -- bits per pixel
|
||||
end
|
||||
|
||||
function image:encode_image_spec()
|
||||
self.data = self.data
|
||||
.. string.char(0, 0) -- X-origin
|
||||
.. string.char(0, 0) -- Y-origin
|
||||
.. string.char(self.width % 256, math.floor(self.width / 256)) -- width
|
||||
.. string.char(self.height % 256, math.floor(self.height / 256)) -- height
|
||||
.. string.char(24) -- pixel depth (RGB = 3 bytes = 24 bits)
|
||||
.. string.char(0) -- image descriptor
|
||||
end
|
||||
|
||||
function image:encode_header()
|
||||
self.data = self.data
|
||||
.. string.char(0) -- image id
|
||||
.. string.char(0) -- color map type
|
||||
.. string.char(10) -- image type (RLE RGB = 10)
|
||||
self:encode_colormap_spec() -- color map specification
|
||||
self:encode_image_spec() -- image specification
|
||||
end
|
||||
|
||||
function image:encode_data()
|
||||
local current_pixel = ''
|
||||
local previous_pixel = ''
|
||||
local count = 1
|
||||
local packets = {}
|
||||
local rle_packet = ''
|
||||
for _, row in ipairs(self.pixels) do
|
||||
for _, pixel in ipairs(row) do
|
||||
current_pixel = string.char(pixel[3], pixel[2], pixel[1])
|
||||
if current_pixel ~= previous_pixel or count == 128 then
|
||||
packets[#packets +1] = rle_packet
|
||||
count = 1
|
||||
previous_pixel = current_pixel
|
||||
else
|
||||
count = count + 1
|
||||
end
|
||||
rle_packet = string.char(128 + count - 1) .. current_pixel
|
||||
end
|
||||
end
|
||||
packets[#packets +1] = rle_packet
|
||||
self.data = self.data .. table.concat(packets)
|
||||
end
|
||||
|
||||
function image:encode_footer()
|
||||
self.data = self.data
|
||||
.. string.char(0, 0, 0, 0) -- extension area offset
|
||||
.. string.char(0, 0, 0, 0) -- developer area offset
|
||||
.. "TRUEVISION-XFILE"
|
||||
.. "."
|
||||
.. string.char(0)
|
||||
end
|
||||
|
||||
function image:encode()
|
||||
self:encode_header() -- header
|
||||
-- no color map and image id data
|
||||
self:encode_data() -- encode data
|
||||
-- no extension or developer area
|
||||
self:encode_footer() -- footer
|
||||
end
|
||||
|
||||
function image:save(filename)
|
||||
local f = assert(io.open(filename, "w"))
|
||||
f:write(self.data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
tga_encoder.image = image
|
|
@ -0,0 +1,3 @@
|
|||
name = tga_encoder
|
||||
author = Fleckenstein
|
||||
description = A TGA Encoder written in Lua without the use of external Libraries.
|
|
@ -4,6 +4,7 @@ local get_connected_players = minetest.get_connected_players
|
|||
local get_node = minetest.get_node
|
||||
local vector_add = vector.add
|
||||
local ceil = math.ceil
|
||||
local pairs = pairs
|
||||
|
||||
walkover = {}
|
||||
walkover.registered_globals = {}
|
||||
|
@ -31,24 +32,21 @@ minetest.register_globalstep(function(dtime)
|
|||
timer = timer + dtime;
|
||||
if timer >= 0.3 then
|
||||
for _,player in pairs(get_connected_players()) do
|
||||
local pp = player:get_pos()
|
||||
pp.y = ceil(pp.y)
|
||||
local loc = vector_add(pp, {x=0,y=-1,z=0})
|
||||
if loc ~= nil then
|
||||
|
||||
local nodeiamon = get_node(loc)
|
||||
|
||||
if nodeiamon ~= nil then
|
||||
if on_walk[nodeiamon.name] then
|
||||
on_walk[nodeiamon.name](loc, nodeiamon, player)
|
||||
end
|
||||
for i = 1, #registered_globals do
|
||||
local pp = player:get_pos()
|
||||
pp.y = ceil(pp.y)
|
||||
local loc = vector_add(pp, {x=0,y=-1,z=0})
|
||||
if loc then
|
||||
local nodeiamon = get_node(loc)
|
||||
if nodeiamon then
|
||||
if on_walk[nodeiamon.name] then
|
||||
on_walk[nodeiamon.name](loc, nodeiamon, player)
|
||||
end
|
||||
for i = 1, #registered_globals do
|
||||
registered_globals[i](loc, nodeiamon, player)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
timer = 0
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
--Dripping Water Mod
|
||||
--by kddekadenz
|
||||
|
||||
-- License of code, textures & sounds: CC0
|
||||
|
||||
--Drop entities
|
||||
|
||||
--water
|
||||
|
||||
local water_tex = "default_water_source_animated.png^[verticalframe:16:0"
|
||||
minetest.register_entity("drippingwater:drop_water", {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.025,-0.05,-0.025,0.025,-0.01,0.025},
|
||||
pointable = false,
|
||||
visual = "cube",
|
||||
visual_size = {x=0.05, y=0.1},
|
||||
textures = {water_tex, water_tex, water_tex, water_tex, water_tex, water_tex},
|
||||
spritediv = {x=1, y=1},
|
||||
initial_sprite_basepos = {x=0, y=0},
|
||||
static_save = false,
|
||||
|
||||
on_activate = function(self, staticdata)
|
||||
self.object:set_sprite({x=0,y=0}, 1, 1, true)
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1,222)
|
||||
local ownpos = self.object:get_pos()
|
||||
|
||||
if k==1 then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y +0.5, z=ownpos.z}).name == "air" then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y -0.5, z=ownpos.z}).name ~= "air" then
|
||||
self.object:remove()
|
||||
minetest.sound_play({name="drippingwater_drip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
--lava
|
||||
|
||||
local lava_tex = "default_lava_source_animated.png^[verticalframe:16:0"
|
||||
minetest.register_entity("drippingwater:drop_lava", {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.025,-0.05,-0.025,0.025,-0.01,0.025},
|
||||
glow = math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3),
|
||||
pointable = false,
|
||||
visual = "cube",
|
||||
visual_size = {x=0.05, y=0.1},
|
||||
textures = {lava_tex, lava_tex, lava_tex, lava_tex, lava_tex, lava_tex},
|
||||
spritediv = {x=1, y=1},
|
||||
initial_sprite_basepos = {x=0, y=0},
|
||||
static_save = false,
|
||||
|
||||
on_activate = function(self, staticdata)
|
||||
self.object:set_sprite({x=0,y=0}, 1, 0, true)
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1,222)
|
||||
local ownpos = self.object:get_pos()
|
||||
|
||||
if k==1 then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y +0.5, z=ownpos.z}).name == "air" then
|
||||
self.object:set_acceleration({x=0, y=-5, z=0})
|
||||
end
|
||||
|
||||
|
||||
if minetest.get_node({x=ownpos.x, y=ownpos.y -0.5, z=ownpos.z}).name ~= "air" then
|
||||
self.object:remove()
|
||||
minetest.sound_play({name="drippingwater_lavadrip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
|
||||
--Create drop
|
||||
|
||||
minetest.register_abm(
|
||||
{
|
||||
label = "Create water drops",
|
||||
nodenames = {"group:opaque", "group:leaves"},
|
||||
neighbors = {"group:water"},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name, "water") ~= 0 and
|
||||
minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
|
||||
local i = math.random(-45,45) / 100
|
||||
minetest.add_entity({x=pos.x + i, y=pos.y - 0.501, z=pos.z + i}, "drippingwater:drop_water")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--Create lava drop
|
||||
|
||||
minetest.register_abm(
|
||||
{
|
||||
label = "Create lava drops",
|
||||
nodenames = {"group:opaque"},
|
||||
neighbors = {"group:lava"},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z}).name, "lava") ~= 0 and
|
||||
minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name == "air" then
|
||||
local i = math.random(-45,45) / 100
|
||||
minetest.add_entity({x=pos.x + i, y=pos.y - 0.501, z=pos.z + i}, "drippingwater:drop_lava")
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
local S = minetest.get_translator("mcl_boats")
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
local boat_visual_size = {x = 1, y = 1, z = 1}
|
||||
local paddling_speed = 22
|
||||
|
@ -84,7 +84,7 @@ local function attach_object(self, obj)
|
|||
end
|
||||
end, name)
|
||||
obj:set_look_horizontal(yaw)
|
||||
mcl_tmp_message.message(obj, S("Sneak to dismount"))
|
||||
mcl_title.set(obj, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
else
|
||||
obj:get_luaentity()._old_visual_size = visual_size
|
||||
end
|
||||
|
@ -115,7 +115,7 @@ local boat = {
|
|||
collisionbox = {-0.5, -0.35, -0.5, 0.5, 0.3, 0.5},
|
||||
visual = "mesh",
|
||||
mesh = "mcl_boats_boat.b3d",
|
||||
textures = {"mcl_boats_texture_oak_boat.png"},
|
||||
textures = {"mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png", "mcl_boats_texture_oak_boat.png"},
|
||||
visual_size = boat_visual_size,
|
||||
hp_max = boat_max_hp,
|
||||
damage_texture_modifier = "^[colorize:white:0",
|
||||
|
@ -148,6 +148,11 @@ function boat.on_activate(self, staticdata, dtime_s)
|
|||
self._v = data.v
|
||||
self._last_v = self._v
|
||||
self._itemstring = data.itemstring
|
||||
|
||||
while #data.textures < 5 do
|
||||
table.insert(data.textures, data.textures[1])
|
||||
end
|
||||
|
||||
self.object:set_properties({textures = data.textures})
|
||||
end
|
||||
end
|
||||
|
@ -188,7 +193,7 @@ function boat.on_punch(self, puncher, time_from_last_punch, tool_capabilities, d
|
|||
end
|
||||
|
||||
function boat.on_step(self, dtime, moveresult)
|
||||
mcl_burning.tick(self.object, dtime)
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
|
||||
self._v = get_v(self.object:get_velocity()) * get_sign(self._v)
|
||||
local v_factor = 1
|
||||
|
@ -328,16 +333,17 @@ function boat.on_step(self, dtime, moveresult)
|
|||
|
||||
p.y = p.y - boat_y_offset
|
||||
local new_velo
|
||||
local new_acce = {x = 0, y = 0, z = 0}
|
||||
local new_acce
|
||||
if not is_water(p) and not on_ice then
|
||||
-- Not on water or inside water: Free fall
|
||||
local nodedef = minetest.registered_nodes[minetest.get_node(p).name]
|
||||
--local nodedef = minetest.registered_nodes[minetest.get_node(p).name]
|
||||
new_acce = {x = 0, y = -9.8, z = 0}
|
||||
new_velo = get_velocity(self._v, self.object:get_yaw(),
|
||||
self.object:get_velocity().y)
|
||||
else
|
||||
p.y = p.y + 1
|
||||
if is_water(p) then
|
||||
local is_obsidian_boat = self.object:get_luaentity()._itemstring == "mcl_boats:boat_obsidian"
|
||||
if is_water(p) or is_obsidian_boat then
|
||||
-- Inside water: Slowly sink
|
||||
local y = self.object:get_velocity().y
|
||||
y = y - 0.01
|
||||
|
@ -377,13 +383,13 @@ end
|
|||
-- Register one entity for all boat types
|
||||
minetest.register_entity("mcl_boats:boat", boat)
|
||||
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat") }
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat") }
|
||||
local craftstuffs = {}
|
||||
if minetest.get_modpath("mcl_core") then
|
||||
craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood" }
|
||||
craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian" }
|
||||
end
|
||||
local images = { "oak", "spruce", "birch", "jungle", "acacia", "dark_oak" }
|
||||
local images = { "oak", "spruce", "birch", "jungle", "acacia", "dark_oak", "obsidian" }
|
||||
|
||||
for b=1, #boat_ids do
|
||||
local itemstring = "mcl_boats:"..boat_ids[b]
|
||||
|
@ -394,7 +400,7 @@ for b=1, #boat_ids do
|
|||
if b == 1 then
|
||||
help = true
|
||||
longdesc = S("Boats are used to travel on the surface of water.")
|
||||
usagehelp = S("Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item.")
|
||||
usagehelp = S("Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.")
|
||||
helpname = S("Boat")
|
||||
end
|
||||
tt_help = S("Water vehicle")
|
||||
|
@ -434,8 +440,9 @@ for b=1, #boat_ids do
|
|||
pos = vector.add(pos, vector.multiply(dir, boat_y_offset_ground))
|
||||
end
|
||||
local boat = minetest.add_entity(pos, "mcl_boats:boat")
|
||||
local texture = "mcl_boats_texture_"..images[b].."_boat.png"
|
||||
boat:get_luaentity()._itemstring = itemstring
|
||||
boat:set_properties({textures = { "mcl_boats_texture_"..images[b].."_boat.png" }})
|
||||
boat:set_properties({textures = { texture, texture, texture, texture, texture }})
|
||||
boat:set_yaw(placer:get_look_horizontal())
|
||||
if not minetest.is_creative_enabled(placer:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
|
@ -470,6 +477,6 @@ minetest.register_craft({
|
|||
burntime = 20,
|
||||
})
|
||||
|
||||
if minetest.get_modpath("doc_identifier") ~= nil then
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object("mcl_boats:boat", "craftitems", "mcl_boats:boat")
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ Boats are used to travel on the surface of water.=Boote werden benutzt, um sich
|
|||
Dark Oak Boat=Schwarzeichenboot
|
||||
Jungle Boat=Dschungelboot
|
||||
Oak Boat=Eichenboot
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item.=Rechtsklicken Sie auf eine Wasserquelle, um das Boot zu platzieren. Rechtsklicken Sie auf das Boot, um es zu betreten. Mit [Links] und [Rechts] lenken, mit [Vorwärts] und [Rückwärts] Geschwindigkeit regeln oder rückwärts fahren. Rechtsklicken Sie erneut auf das Boot, um es zu verlassen, schlagen Sie das Boot, um es als Gegenstand fallen zu lassen.
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=Rechtsklicken Sie auf eine Wasserquelle, um das Boot zu platzieren. Rechtsklicken Sie auf das Boot, um es zu betreten. Mit [Links] und [Rechts] lenken, mit [Vorwärts] und [Rückwärts] Geschwindigkeit regeln oder rückwärts fahren. Nutzen sie [Schleichen], um das Boot zu verlassen, schlagen Sie das Boot, um es als Gegenstand fallen zu lassen.
|
||||
Spruce Boat=Fichtenboot
|
||||
Water vehicle=Wasserfahrzeug
|
||||
Sneak to dismount=Zum Aussteigen schleichen
|
||||
|
|
|
@ -6,6 +6,7 @@ Boats are used to travel on the surface of water.=Les bateaux sont utilisés pou
|
|||
Dark Oak Boat=Bateau en Chêne Noir
|
||||
Jungle Boat=Bateau en Acajou
|
||||
Oak Boat=Bateau en Chêne
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item.=Faites un clic droit sur une source d'eau pour placer le bateau. Faites un clic droit sur le bateau pour y entrer. Utilisez [Gauche] et [Droite] pour diriger, [Avant] pour accélérer et [Arrière] pour ralentir ou reculer. Cliquez de nouveau avec le bouton droit sur le bateau pour le quitter, frappez le bateau pour le faire tomber en tant qu'objet.
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=Faites un clic droit sur une source d'eau pour placer le bateau. Faites un clic droit sur le bateau pour y entrer. Utilisez [Gauche] et [Droite] pour diriger, [Avant] pour accélérer et [Arrière] pour ralentir ou reculer. Utilisez [Sneak] pour le quitter, frappez le bateau pour le faire tomber en tant qu'objet.
|
||||
Spruce Boat=Bateau en Sapin
|
||||
Water vehicle=Véhicule aquatique
|
||||
Sneak to dismount=
|
|
@ -0,0 +1,12 @@
|
|||
# textdomain: mcl_boats
|
||||
Acacia Boat=Akacjowa łódź
|
||||
Birch Boat=Brzozowa łódź
|
||||
Boat=Łódź
|
||||
Boats are used to travel on the surface of water.=Łodzie są wykorzystywane do podróżowania po powierzchni wody.
|
||||
Dark Oak Boat=Ciemno-dębowa łódź
|
||||
Jungle Boat=Tropikalna łódź
|
||||
Oak Boat=Dębowa łódź
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=Kliknij prawym przyciskiem myszy na źródło wody by postawić łódź. Kliknij prawym przyciskiem myszy by w nią wsiąść. Użyj przycisków [Lewy] oraz [Prawy] by sterować, [Naprzód] by przyspieszyć i [W tył] by zwolnić lub się cofać. Kliknij [Skradanie] by z niej wyjść, uderz ją by wziąć ją jako przedmiot.
|
||||
Spruce Boat=Świerkowa łódź
|
||||
Water vehicle=Pojazd wodny
|
||||
Sneak to dismount=Skradaj się by opuścić łódź
|
|
@ -6,6 +6,7 @@ Boats are used to travel on the surface of water.=
|
|||
Dark Oak Boat=
|
||||
Jungle Boat=
|
||||
Oak Boat=
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Rightclick the boat again to leave it, punch the boat to make it drop as an item.=
|
||||
Rightclick on a water source to place the boat. Rightclick the boat to enter it. Use [Left] and [Right] to steer, [Forwards] to speed up and [Backwards] to slow down or move backwards. Use [Sneak] to leave the boat, punch the boat to make it drop as an item.=
|
||||
Spruce Boat=
|
||||
Water vehicle=
|
||||
Sneak to dismount=
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name = mcl_boats
|
||||
author = PilzAdam
|
||||
description = Adds drivable boats.
|
||||
depends = mcl_player, flowlib
|
||||
depends = mcl_player, flowlib, mcl_title
|
||||
optional_depends = mcl_core, doc_identifier
|
||||
|
||||
|
||||
|
|
After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 535 B |
|
@ -1,184 +1,81 @@
|
|||
local S = minetest.get_translator("mcl_burning")
|
||||
|
||||
function mcl_burning.get_default(datatype)
|
||||
local default_table = {string = "", float = 0.0, int = 0, bool = false}
|
||||
return default_table[datatype]
|
||||
end
|
||||
|
||||
function mcl_burning.get(obj, datatype, name)
|
||||
local key
|
||||
if obj:is_player() then
|
||||
local meta = obj:get_meta()
|
||||
return meta["get_" .. datatype](meta, "mcl_burning:" .. name)
|
||||
else
|
||||
local luaentity = obj:get_luaentity()
|
||||
return luaentity and luaentity["mcl_burning_" .. name] or mcl_burning.get_default(datatype)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.set(obj, datatype, name, value)
|
||||
if obj:is_player() then
|
||||
local meta = obj:get_meta()
|
||||
meta["set_" .. datatype](meta, "mcl_burning:" .. name, value or mcl_burning.get_default(datatype))
|
||||
else
|
||||
local luaentity = obj:get_luaentity()
|
||||
if mcl_burning.get_default(datatype) == value then
|
||||
value = nil
|
||||
end
|
||||
luaentity["mcl_burning_" .. name] = value
|
||||
end
|
||||
function mcl_burning.get_storage(obj)
|
||||
return obj:is_player() and mcl_burning.storage[obj] or obj:get_luaentity()
|
||||
end
|
||||
|
||||
function mcl_burning.is_burning(obj)
|
||||
return mcl_burning.get(obj, "float", "burn_time") > 0
|
||||
return mcl_burning.get_storage(obj).burn_time
|
||||
end
|
||||
|
||||
function mcl_burning.is_affected_by_rain(obj)
|
||||
return mcl_weather and mcl_weather.get_weather() == "rain" and mcl_weather.is_outdoor(obj:get_pos())
|
||||
return mcl_weather.get_weather() == "rain" and mcl_weather.is_outdoor(obj:get_pos())
|
||||
end
|
||||
|
||||
function mcl_burning.get_collisionbox(obj, smaller)
|
||||
local box = obj:get_properties().collisionbox
|
||||
local minp, maxp = vector.new(box[1], box[2], box[3]), vector.new(box[4], box[5], box[6])
|
||||
if smaller then
|
||||
function mcl_burning.get_collisionbox(obj, smaller, storage)
|
||||
local cache = storage.collisionbox_cache
|
||||
if cache then
|
||||
local box = cache[smaller and 2 or 1]
|
||||
return box[1], box[2]
|
||||
else
|
||||
local box = obj:get_properties().collisionbox
|
||||
local minp, maxp = vector.new(box[1], box[2], box[3]), vector.new(box[4], box[5], box[6])
|
||||
local s_vec = vector.new(0.1, 0.1, 0.1)
|
||||
minp = vector.add(minp, s_vec)
|
||||
maxp = vector.subtract(maxp, s_vec)
|
||||
local s_minp = vector.add(minp, s_vec)
|
||||
local s_maxp = vector.subtract(maxp, s_vec)
|
||||
storage.collisionbox_cache = {{minp, maxp}, {s_minp, s_maxp}}
|
||||
return minp, maxp
|
||||
end
|
||||
return minp, maxp
|
||||
end
|
||||
|
||||
function mcl_burning.get_touching_nodes(obj, nodenames)
|
||||
function mcl_burning.get_touching_nodes(obj, nodenames, storage)
|
||||
local pos = obj:get_pos()
|
||||
local box = obj:get_properties().collisionbox
|
||||
local minp, maxp = mcl_burning.get_collisionbox(obj, true)
|
||||
local minp, maxp = mcl_burning.get_collisionbox(obj, true, storage)
|
||||
local nodes = minetest.find_nodes_in_area(vector.add(pos, minp), vector.add(pos, maxp), nodenames)
|
||||
return nodes
|
||||
end
|
||||
|
||||
function mcl_burning.get_highest_group_value(obj, groupname)
|
||||
local nodes = mcl_burning.get_touching_nodes(obj, "group:" .. groupname, true)
|
||||
local highest_group_value = 0
|
||||
|
||||
for _, pos in pairs(nodes) do
|
||||
local node = minetest.get_node(pos)
|
||||
local group_value = minetest.get_item_group(node.name, groupname)
|
||||
if group_value > highest_group_value then
|
||||
highest_group_value = group_value
|
||||
end
|
||||
end
|
||||
|
||||
return highest_group_value
|
||||
end
|
||||
|
||||
function mcl_burning.damage(obj)
|
||||
local luaentity = obj:get_luaentity()
|
||||
local health
|
||||
|
||||
if luaentity then
|
||||
health = luaentity.health
|
||||
end
|
||||
|
||||
local hp = health or obj:get_hp()
|
||||
|
||||
if hp <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local do_damage = true
|
||||
|
||||
if obj:is_player() then
|
||||
if mcl_potions.player_has_effect(obj, "fire_proof") then
|
||||
do_damage = false
|
||||
else
|
||||
local name = obj:get_player_name()
|
||||
armor.last_damage_types[name] = "fire"
|
||||
local deathmsg = S("@1 burned to death.", name)
|
||||
local reason = mcl_burning.get(obj, "string", "reason")
|
||||
if reason ~= "" then
|
||||
deathmsg = S("@1 was burned by @2.", name, reason)
|
||||
end
|
||||
mcl_death_messages.player_damage(obj, deathmsg)
|
||||
end
|
||||
else
|
||||
if luaentity.fire_damage_resistant then
|
||||
do_damage = false
|
||||
end
|
||||
end
|
||||
|
||||
if do_damage then
|
||||
local new_hp = hp - 1
|
||||
if health then
|
||||
luaentity.health = new_hp
|
||||
else
|
||||
obj:set_hp(new_hp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.set_on_fire(obj, burn_time, reason)
|
||||
function mcl_burning.set_on_fire(obj, burn_time)
|
||||
if obj:get_hp() < 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local storage = mcl_burning.get_storage(obj)
|
||||
|
||||
local luaentity = obj:get_luaentity()
|
||||
if luaentity and luaentity.fire_resistant then
|
||||
return
|
||||
end
|
||||
|
||||
local old_burn_time = mcl_burning.get(obj, "float", "burn_time")
|
||||
local max_fire_prot_lvl = 0
|
||||
if obj:is_player() and minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
burn_time = 0
|
||||
else
|
||||
local max_fire_prot_lvl = 0
|
||||
local inv = mcl_util.get_inventory(obj)
|
||||
local armor_list = inv and inv:get_list("armor")
|
||||
|
||||
if obj:is_player() then
|
||||
if minetest.is_creative_enabled(obj:get_player_name()) then
|
||||
burn_time = burn_time / 100
|
||||
end
|
||||
|
||||
local inv = obj:get_inventory()
|
||||
|
||||
for i = 2, 5 do
|
||||
local stack = inv:get_stack("armor", i)
|
||||
|
||||
local fire_prot_lvl = mcl_enchanting.get_enchantment(stack, "fire_protection")
|
||||
max_fire_prot_lvl = math.max(max_fire_prot_lvl, fire_prot_lvl)
|
||||
end
|
||||
end
|
||||
|
||||
if max_fire_prot_lvl > 0 then
|
||||
burn_time = burn_time - math.floor(burn_time * max_fire_prot_lvl * 0.15)
|
||||
end
|
||||
|
||||
if old_burn_time <= burn_time then
|
||||
--[[local sound_id = mcl_burning.get(obj, "int", "sound_id")
|
||||
if sound_id == 0 then
|
||||
sound_id = minetest.sound_play("fire_fire", {
|
||||
object = obj,
|
||||
gain = 0.18,
|
||||
max_hear_distance = 16,
|
||||
loop = true,
|
||||
}) + 1
|
||||
end]]--
|
||||
|
||||
local hud_id
|
||||
if obj:is_player() then
|
||||
hud_id = mcl_burning.get(obj, "int", "hud_id")
|
||||
if hud_id == 0 then
|
||||
hud_id = obj:hud_add({
|
||||
hud_elem_type = "image",
|
||||
position = {x = 0.5, y = 0.5},
|
||||
scale = {x = -100, y = -100},
|
||||
text = "mcl_burning_entity_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. 1,
|
||||
z_index = 1000,
|
||||
}) + 1
|
||||
if armor_list then
|
||||
for _, stack in pairs(armor_list) do
|
||||
local fire_prot_lvl = mcl_enchanting.get_enchantment(stack, "fire_protection")
|
||||
if fire_prot_lvl > max_fire_prot_lvl then
|
||||
max_fire_prot_lvl = fire_prot_lvl
|
||||
end
|
||||
end
|
||||
end
|
||||
mcl_burning.set(obj, "float", "burn_time", burn_time)
|
||||
mcl_burning.set(obj, "string", "reason", reason)
|
||||
mcl_burning.set(obj, "int", "hud_id", hud_id)
|
||||
--mcl_burning.set(obj, "int", "sound_id", sound_id)
|
||||
|
||||
if max_fire_prot_lvl > 0 then
|
||||
burn_time = burn_time - math.floor(burn_time * max_fire_prot_lvl * 0.15)
|
||||
end
|
||||
end
|
||||
|
||||
if not storage.burn_time or burn_time >= storage.burn_time then
|
||||
if obj:is_player() then
|
||||
mcl_burning.channels[obj]:send_all(tostring(mcl_burning.animation_frames))
|
||||
mcl_burning.channels[obj]:send_all("start")
|
||||
end
|
||||
storage.burn_time = burn_time
|
||||
storage.fire_damage_timer = 0
|
||||
|
||||
local fire_entity = minetest.add_entity(obj:get_pos(), "mcl_burning:fire")
|
||||
local minp, maxp = mcl_burning.get_collisionbox(obj)
|
||||
local minp, maxp = mcl_burning.get_collisionbox(obj, false, storage)
|
||||
local obj_size = obj:get_properties().visual_size
|
||||
|
||||
local vertical_grow_factor = 1.2
|
||||
|
@ -192,111 +89,50 @@ function mcl_burning.set_on_fire(obj, burn_time, reason)
|
|||
|
||||
fire_entity:set_properties({visual_size = size})
|
||||
fire_entity:set_attach(obj, "", offset, {x = 0, y = 0, z = 0})
|
||||
mcl_burning.update_animation_frame(obj, fire_entity, 0)
|
||||
local fire_luaentity = fire_entity:get_luaentity()
|
||||
|
||||
for _, other in pairs(minetest.get_objects_inside_radius(fire_entity:get_pos(), 0)) do
|
||||
local other_luaentity = other:get_luaentity()
|
||||
if other_luaentity and other_luaentity.name == "mcl_burning:fire" and other_luaentity ~= fire_luaentity then
|
||||
other:remove()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.extinguish(obj)
|
||||
if mcl_burning.is_burning(obj) then
|
||||
--local sound_id = mcl_burning.get(obj, "int", "sound_id") - 1
|
||||
--minetest.sound_stop(sound_id)
|
||||
|
||||
local storage = mcl_burning.get_storage(obj)
|
||||
if obj:is_player() then
|
||||
local hud_id = mcl_burning.get(obj, "int", "hud_id") - 1
|
||||
obj:hud_remove(hud_id)
|
||||
end
|
||||
|
||||
mcl_burning.set(obj, "string", "reason")
|
||||
mcl_burning.set(obj, "float", "burn_time")
|
||||
mcl_burning.set(obj, "float", "damage_timer")
|
||||
mcl_burning.set(obj, "int", "hud_id")
|
||||
--mcl_burning.set(obj, "int", "sound_id")
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.catch_fire_tick(obj, dtime)
|
||||
if mcl_burning.is_affected_by_rain(obj) or #mcl_burning.get_touching_nodes(obj, "group:puts_out_fire") > 0 then
|
||||
mcl_burning.extinguish(obj)
|
||||
else
|
||||
local set_on_fire_value = mcl_burning.get_highest_group_value(obj, "set_on_fire")
|
||||
|
||||
if set_on_fire_value > 0 then
|
||||
mcl_burning.set_on_fire(obj, set_on_fire_value)
|
||||
mcl_burning.channels[obj]:send_all("stop")
|
||||
mcl_burning.storage[obj] = {}
|
||||
else
|
||||
storage.burn_time = nil
|
||||
storage.fire_damage_timer = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.tick(obj, dtime)
|
||||
local burn_time = mcl_burning.get(obj, "float", "burn_time") - dtime
|
||||
function mcl_burning.tick(obj, dtime, storage)
|
||||
if storage.burn_time then
|
||||
storage.burn_time = storage.burn_time - dtime
|
||||
|
||||
if burn_time <= 0 then
|
||||
mcl_burning.extinguish(obj)
|
||||
else
|
||||
mcl_burning.set(obj, "float", "burn_time", burn_time)
|
||||
if storage.burn_time <= 0 or mcl_burning.is_affected_by_rain(obj) or #mcl_burning.get_touching_nodes(obj, "group:puts_out_fire", storage) > 0 then
|
||||
mcl_burning.extinguish(obj)
|
||||
return true
|
||||
else
|
||||
storage.fire_damage_timer = storage.fire_damage_timer + dtime
|
||||
|
||||
local damage_timer = mcl_burning.get(obj, "float", "damage_timer") + dtime
|
||||
if storage.fire_damage_timer >= 1 then
|
||||
storage.fire_damage_timer = 0
|
||||
|
||||
if damage_timer >= 1 then
|
||||
damage_timer = 0
|
||||
mcl_burning.damage(obj)
|
||||
end
|
||||
local luaentity = obj:get_luaentity()
|
||||
|
||||
mcl_burning.set(obj, "float", "damage_timer", damage_timer)
|
||||
end
|
||||
|
||||
mcl_burning.catch_fire_tick(obj, dtime)
|
||||
end
|
||||
|
||||
function mcl_burning.update_animation_frame(obj, fire_entity, animation_frame)
|
||||
local fire_texture = "mcl_burning_entity_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. animation_frame
|
||||
local fire_HUD_texture = "mcl_burning_hud_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. animation_frame
|
||||
fire_entity:set_properties({textures = {"blank.png", "blank.png", fire_texture, fire_texture, fire_texture, fire_texture}})
|
||||
if obj:is_player() then
|
||||
local hud_id = mcl_burning.get(obj, "int", "hud_id") - 1
|
||||
obj:hud_change(hud_id, "text", fire_HUD_texture)
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.fire_entity_step(self, dtime)
|
||||
if self.removed then
|
||||
return
|
||||
end
|
||||
|
||||
local obj = self.object
|
||||
local parent = obj:get_attach()
|
||||
local do_remove
|
||||
|
||||
self.doing_step = true
|
||||
|
||||
if not parent or not mcl_burning.is_burning(parent) then
|
||||
do_remove = true
|
||||
else
|
||||
for _, other in pairs(minetest.get_objects_inside_radius(obj:get_pos(), 0)) do
|
||||
local luaentity = obj:get_luaentity()
|
||||
if luaentity and luaentity.name == "mcl_burning:fire" and not luaentity.doing_step and not luaentity.removed then
|
||||
do_remove = true
|
||||
break
|
||||
if not luaentity or not luaentity.fire_damage_resistant then
|
||||
mcl_util.deal_damage(obj, 1, {type = "on_fire"})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.doing_step = false
|
||||
|
||||
if do_remove then
|
||||
self.removed = true
|
||||
obj:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local animation_timer = self.animation_timer + dtime
|
||||
if animation_timer >= 0.015 then
|
||||
animation_timer = 0
|
||||
local animation_frame = self.animation_frame + 1
|
||||
if animation_frame > mcl_burning.animation_frames - 1 then
|
||||
animation_frame = 0
|
||||
end
|
||||
mcl_burning.update_animation_frame(parent, obj, animation_frame)
|
||||
self.animation_frame = animation_frame
|
||||
end
|
||||
self.animation_timer = animation_timer
|
||||
end
|
||||
|
|
|
@ -1,29 +1,42 @@
|
|||
local S = minetest.get_translator("mcl_burning")
|
||||
local modpath = minetest.get_modpath("mcl_burning")
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
local pairs = pairs
|
||||
|
||||
local get_connected_players = minetest.get_connected_players
|
||||
local get_item_group = minetest.get_item_group
|
||||
|
||||
mcl_burning = {
|
||||
storage = {},
|
||||
channels = {},
|
||||
animation_frames = tonumber(minetest.settings:get("fire_animation_frames")) or 8
|
||||
}
|
||||
|
||||
dofile(modpath .. "/api.lua")
|
||||
|
||||
minetest.register_entity("mcl_burning:fire", {
|
||||
initial_properties = {
|
||||
physical = false,
|
||||
collisionbox = {0, 0, 0, 0, 0, 0},
|
||||
visual = "cube",
|
||||
pointable = false,
|
||||
glow = -1,
|
||||
},
|
||||
|
||||
animation_frame = 0,
|
||||
animation_timer = 0,
|
||||
on_step = mcl_burning.fire_entity_step,
|
||||
})
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
mcl_burning.tick(player, dtime)
|
||||
for _, player in pairs(get_connected_players()) do
|
||||
local storage = mcl_burning.storage[player]
|
||||
if not mcl_burning.tick(player, dtime, storage) and not mcl_burning.is_affected_by_rain(player) then
|
||||
local nodes = mcl_burning.get_touching_nodes(player, {"group:puts_out_fire", "group:set_on_fire"}, storage)
|
||||
local burn_time = 0
|
||||
|
||||
for _, pos in pairs(nodes) do
|
||||
local node = minetest.get_node(pos)
|
||||
if get_item_group(node.name, "puts_out_fire") > 0 then
|
||||
burn_time = 0
|
||||
break
|
||||
end
|
||||
|
||||
local value = get_item_group(node.name, "set_on_fire")
|
||||
if value > burn_time then
|
||||
burn_time = value
|
||||
end
|
||||
end
|
||||
|
||||
if burn_time > 0 then
|
||||
mcl_burning.set_on_fire(player, burn_time)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -31,6 +44,68 @@ minetest.register_on_respawnplayer(function(player)
|
|||
mcl_burning.extinguish(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
mcl_burning.set(player, "int", "hud_id")
|
||||
function mcl_burning.init_player(player)
|
||||
local meta = player:get_meta()
|
||||
-- NOTE: mcl_burning:data may be "return nil" (which deserialize into nil) for reasons unknown.
|
||||
if meta:get_string("mcl_burning:data"):find("return nil", 1, true) then
|
||||
minetest.log("warning", "[mcl_burning] 'mcl_burning:data' player meta field is invalid! Please report this bug")
|
||||
end
|
||||
mcl_burning.storage[player] = meta:contains("mcl_burning:data") and minetest.deserialize(meta:get_string("mcl_burning:data")) or {}
|
||||
mcl_burning.channels[player] = minetest.mod_channel_join("mcl_burning:" .. player:get_player_name())
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
mcl_burning.init_player(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
player:get_meta():set_string("mcl_burning:data", minetest.serialize(mcl_burning.storage[player]))
|
||||
mcl_burning.storage[player] = nil
|
||||
end)
|
||||
|
||||
|
||||
minetest.register_entity("mcl_burning:fire", {
|
||||
initial_properties = {
|
||||
physical = false,
|
||||
collisionbox = {0, 0, 0, 0, 0, 0},
|
||||
visual = "upright_sprite",
|
||||
textures = {
|
||||
name = "mcl_burning_entity_flame_animated.png",
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 1.0,
|
||||
},
|
||||
},
|
||||
spritediv = {x = 1, y = mcl_burning.animation_frames},
|
||||
pointable = false,
|
||||
glow = -1,
|
||||
backface_culling = false,
|
||||
},
|
||||
animation_frame = 0,
|
||||
animation_timer = 0,
|
||||
on_activate = function(self)
|
||||
self.object:set_sprite({x = 0, y = 0}, mcl_burning.animation_frames, 1.0 / mcl_burning.animation_frames)
|
||||
end,
|
||||
on_step = function(self)
|
||||
if not self:sanity_check() then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
sanity_check = function(self)
|
||||
local parent = self.object:get_attach()
|
||||
|
||||
if not parent then
|
||||
return false
|
||||
end
|
||||
|
||||
local storage = mcl_burning.get_storage(parent)
|
||||
|
||||
if not storage or not storage.burn_time then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
-- Dripping Water Mod
|
||||
-- by kddekadenz
|
||||
|
||||
local math = math
|
||||
|
||||
-- License of code, textures & sounds: CC0
|
||||
|
||||
local function register_drop(liquid, glow, sound, nodes)
|
||||
minetest.register_entity("mcl_dripping:drop_" .. liquid, {
|
||||
hp_max = 1,
|
||||
physical = true,
|
||||
collide_with_objects = false,
|
||||
collisionbox = {-0.01, 0.01, -0.01, 0.01, 0.01, 0.01},
|
||||
glow = glow,
|
||||
pointable = false,
|
||||
visual = "sprite",
|
||||
visual_size = {x = 0.1, y = 0.1},
|
||||
textures = {""},
|
||||
spritediv = {x = 1, y = 1},
|
||||
initial_sprite_basepos = {x = 0, y = 0},
|
||||
static_save = false,
|
||||
_dropped = false,
|
||||
on_activate = function(self)
|
||||
self.object:set_properties({
|
||||
textures = {"[combine:2x2:" .. -math.random(1, 16) .. "," .. -math.random(1, 16) .. "=default_" .. liquid .. "_source_animated.png"}
|
||||
})
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
local k = math.random(1, 222)
|
||||
local ownpos = self.object:get_pos()
|
||||
if k == 1 then
|
||||
self.object:set_acceleration(vector.new(0, -5, 0))
|
||||
end
|
||||
if minetest.get_node(vector.offset(ownpos, 0, 0.5, 0)).name == "air" then
|
||||
self.object:set_acceleration(vector.new(0, -5, 0))
|
||||
end
|
||||
if minetest.get_node(vector.offset(ownpos, 0, -0.1, 0)).name ~= "air" then
|
||||
local ent = self.object:get_luaentity()
|
||||
if not ent._dropped then
|
||||
ent._dropped = true
|
||||
minetest.sound_play({name = "drippingwater_" .. sound .. "drip"}, {pos = ownpos, gain = 0.5, max_hear_distance = 8}, true)
|
||||
end
|
||||
if k < 3 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
minetest.register_abm({
|
||||
label = "Create drops",
|
||||
nodenames = nodes,
|
||||
neighbors = {"group:" .. liquid},
|
||||
interval = 2,
|
||||
chance = 22,
|
||||
action = function(pos)
|
||||
if minetest.get_item_group(minetest.get_node(vector.offset(pos, 0, 1, 0)).name, liquid) ~= 0
|
||||
and minetest.get_node(vector.offset(pos, 0, -1, 0)).name == "air" then
|
||||
local x, z = math.random(-45, 45) / 100, math.random(-45, 45) / 100
|
||||
minetest.add_entity(vector.offset(pos, x, -0.520, z), "mcl_dripping:drop_" .. liquid)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
register_drop("water", 1, "", {"group:opaque", "group:leaves"})
|
||||
register_drop("lava", math.max(7, minetest.registered_nodes["mcl_core:lava_source"].light_source - 3), "lava", {"group:opaque"})
|
|
@ -1,4 +1,4 @@
|
|||
name = drippingwater
|
||||
name = mcl_dripping
|
||||
author = kddekadenz
|
||||
description = Drops are generated rarely under solid nodes
|
||||
depends = mcl_core
|
|
@ -1,12 +1,12 @@
|
|||
Dripping Water Mod
|
||||
Dripping Mod
|
||||
by kddekadenz
|
||||
|
||||
modified for MineClone 2 by Wuzzy
|
||||
modified for MineClone 2 by Wuzzy and NO11
|
||||
|
||||
|
||||
Installing instructions:
|
||||
|
||||
1. Copy the drippingwater mod folder into games/gamemode/mods
|
||||
1. Copy the mcl_dripping mod folder into games/gamemode/mods
|
||||
|
||||
2. Start game and enjoy :)
|
||||
|
|
@ -1,10 +1,4 @@
|
|||
local S = minetest.get_translator("mcl_falling_nodes")
|
||||
local dmes = minetest.get_modpath("mcl_death_messages") ~= nil
|
||||
local has_mcl_armor = minetest.get_modpath("mcl_armor")
|
||||
|
||||
local is_creative_enabled = minetest.is_creative_enabled
|
||||
|
||||
local get_falling_depth = function(self)
|
||||
local function get_falling_depth(self)
|
||||
if not self._startpos then
|
||||
-- Fallback
|
||||
self._startpos = self.object:get_pos()
|
||||
|
@ -12,7 +6,7 @@ local get_falling_depth = function(self)
|
|||
return self._startpos.y - vector.round(self.object:get_pos()).y
|
||||
end
|
||||
|
||||
local deal_falling_damage = function(self, dtime)
|
||||
local function deal_falling_damage(self, dtime)
|
||||
if minetest.get_item_group(self.node.name, "falling_node_damage") == 0 then
|
||||
return
|
||||
end
|
||||
|
@ -23,80 +17,34 @@ local deal_falling_damage = function(self, dtime)
|
|||
-- Fallback
|
||||
self._startpos = pos
|
||||
end
|
||||
local objs = minetest.get_objects_inside_radius(pos, 1)
|
||||
for _,v in ipairs(objs) do
|
||||
if v:is_player() then
|
||||
local hp = v:get_hp()
|
||||
local name = v:get_player_name()
|
||||
if hp ~= 0 then
|
||||
if not self._hit_players then
|
||||
self._hit_players = {}
|
||||
end
|
||||
local hit = false
|
||||
for _,v in ipairs(self._hit_players) do
|
||||
if name == v then
|
||||
hit = true
|
||||
self._hit = self._hit or {}
|
||||
for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
local entity = obj:get_luaentity()
|
||||
if entity and entity.name == "__builtin:item" then
|
||||
obj:remove()
|
||||
elseif mcl_util.get_hp(obj) > 0 and not self._hit[obj] then
|
||||
self._hit[obj] = true
|
||||
local way = self._startpos.y - pos.y
|
||||
local damage = (way - 1) * 2
|
||||
damage = math.min(40, math.max(0, damage))
|
||||
if damage >= 1 then
|
||||
-- Reduce damage if wearing a helmet
|
||||
local inv = mcl_util.get_inventory(obj)
|
||||
if inv then
|
||||
local helmet = inv:get_stack("armor", 2)
|
||||
if minetest.get_item_group(helmet:get_name(), "combat_armor") > 0 then
|
||||
damage = damage / 4 * 3
|
||||
mcl_util.use_item_durability(helmet, 1)
|
||||
inv:set_stack("armor", 2, helmet)
|
||||
end
|
||||
end
|
||||
if not hit then
|
||||
table.insert(self._hit_players, name)
|
||||
local way = self._startpos.y - pos.y
|
||||
local damage = (way - 1) * 2
|
||||
damage = math.min(40, math.max(0, damage))
|
||||
if damage >= 1 then
|
||||
hp = hp - damage
|
||||
if hp < 0 then
|
||||
hp = 0
|
||||
end
|
||||
-- Reduce damage if wearing a helmet
|
||||
local inv = v:get_inventory()
|
||||
local helmet = inv:get_stack("armor", 2)
|
||||
if has_mcl_armor and not helmet:is_empty() then
|
||||
hp = hp/4*3
|
||||
if not is_creative_enabled(name) then
|
||||
helmet:add_wear(65535/helmet:get_definition().groups.mcl_armor_uses) --TODO: be sure damage is exactly like mc (informations are missing in the mc wiki)
|
||||
inv:set_stack("armor", 2, helmet)
|
||||
end
|
||||
end
|
||||
local msg
|
||||
if minetest.get_item_group(self.node.name, "anvil") ~= 0 then
|
||||
msg = S("@1 was smashed by a falling anvil.", v:get_player_name())
|
||||
else
|
||||
msg = S("@1 was smashed by a falling block.", v:get_player_name())
|
||||
end
|
||||
if dmes then
|
||||
mcl_death_messages.player_damage(v, msg)
|
||||
end
|
||||
v:set_hp(hp, { type = "punch", from = "mod" })
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local hp = v:get_luaentity().health
|
||||
if hp and hp ~= 0 then
|
||||
if not self._hit_mobs then
|
||||
self._hit_mobs = {}
|
||||
end
|
||||
local hit = false
|
||||
for _,mob in ipairs(self._hit_mobs) do
|
||||
if v == mob then
|
||||
hit = true
|
||||
end
|
||||
end
|
||||
--TODO: reduce damage for mobs then they will be able to wear armor
|
||||
if not hit then
|
||||
table.insert(self._hit_mobs, v)
|
||||
local way = self._startpos.y - pos.y
|
||||
local damage = (way - 1) * 2
|
||||
damage = math.min(40, math.max(0, damage))
|
||||
if damage >= 1 then
|
||||
hp = hp - damage
|
||||
if hp < 0 then
|
||||
hp = 0
|
||||
end
|
||||
v:get_luaentity().health = hp
|
||||
end
|
||||
local dmg_type
|
||||
if minetest.get_item_group(self.node.name, "anvil") ~= 0 then
|
||||
dmg_type = "anvil"
|
||||
else
|
||||
dmg_type = "falling_node"
|
||||
end
|
||||
mcl_util.deal_damage(obj, damage, {type = dmg_type})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -112,10 +60,8 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
collide_with_objects = false,
|
||||
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
},
|
||||
|
||||
node = {},
|
||||
meta = {},
|
||||
|
||||
set_node = function(self, node, meta)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
-- Change falling node if definition tells us to
|
||||
|
@ -142,7 +88,6 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
glow = glow,
|
||||
})
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
local meta = self.meta
|
||||
-- Workaround: Save inventory seperately from metadata.
|
||||
|
@ -163,7 +108,6 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
}
|
||||
return minetest.serialize(ds)
|
||||
end,
|
||||
|
||||
on_activate = function(self, staticdata)
|
||||
self.object:set_armor_groups({immortal = 1})
|
||||
|
||||
|
@ -186,7 +130,6 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
end
|
||||
self._startpos = vector.round(self._startpos)
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
-- Set gravity
|
||||
local acceleration = self.object:get_acceleration()
|
||||
|
@ -238,10 +181,9 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
return
|
||||
end
|
||||
local nd = minetest.registered_nodes[n2.name]
|
||||
if n2.name == "mcl_portals:portal_end" then
|
||||
-- TODO: Teleport falling node.
|
||||
|
||||
elseif (nd and nd.buildable_to == true) or minetest.get_item_group(self.node.name, "crush_after_fall") ~= 0 then
|
||||
--if n2.name == "mcl_portals:portal_end" then
|
||||
-- TODO: Teleport falling node.
|
||||
if (nd and nd.buildable_to == true) or minetest.get_item_group(self.node.name, "crush_after_fall") ~= 0 then
|
||||
-- Replace destination node if it's buildable to
|
||||
minetest.remove_node(np)
|
||||
-- Run script hook
|
||||
|
@ -308,7 +250,6 @@ minetest.register_entity(":__builtin:falling_node", {
|
|||
self.object:set_pos(npos)
|
||||
end
|
||||
end
|
||||
|
||||
deal_falling_damage(self, dtime)
|
||||
end
|
||||
})
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 wurde von einem fallenden Amboss zerschmettert.
|
||||
@1 was smashed by a falling block.=@1 wurde von einem fallenden Block zerschmettert.
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 fue aplastado por la caída de un yunque.
|
||||
@1 was smashed by a falling block.=@1 fue aplastado por la caída de un bloque.
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 a été écrasé par une enclume qui tombait.
|
||||
@1 was smashed by a falling block.=@1 a été écrasé par un bloc qui tombait.
|
|
@ -0,0 +1,3 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 została zmiażdżona przez spadające kowadło.
|
||||
@1 was smashed by a falling block.=@1 została zmiażdżona przez spadający blok.
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 придавило падающей наковальней.
|
||||
@1 was smashed by a falling block.=@1 раздавило падающим блоком.
|
|
@ -1,3 +0,0 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=
|
||||
@1 was smashed by a falling block.=
|
|
@ -1,5 +1,5 @@
|
|||
--these are lua locals, used for higher performance
|
||||
local minetest,math,vector,ipairs = minetest,math,vector,ipairs
|
||||
local minetest, math, vector, ipairs, pairs = minetest, math, vector, ipairs, pairs
|
||||
|
||||
--this is used for the player pool in the sound buffer
|
||||
local pool = {}
|
||||
|
@ -38,7 +38,7 @@ item_drop_settings.drop_single_item = false --if true, the drop control dro
|
|||
|
||||
item_drop_settings.magnet_time = 0.75 -- how many seconds an item follows the player before giving up
|
||||
|
||||
local get_gravity = function()
|
||||
local function get_gravity()
|
||||
return tonumber(minetest.settings:get("movement_gravity")) or 9.81
|
||||
end
|
||||
|
||||
|
@ -60,7 +60,7 @@ mcl_item_entity.register_pickup_achievement("mcl_mobitems:blaze_rod", "mcl:blaze
|
|||
mcl_item_entity.register_pickup_achievement("mcl_mobitems:leather", "mcl:killCow")
|
||||
mcl_item_entity.register_pickup_achievement("mcl_core:diamond", "mcl:diamonds")
|
||||
|
||||
local check_pickup_achievements = function(object, player)
|
||||
local function check_pickup_achievements(object, player)
|
||||
if has_awards then
|
||||
local itemname = ItemStack(object:get_luaentity().itemstring):get_name()
|
||||
local playername = player:get_player_name()
|
||||
|
@ -72,7 +72,7 @@ local check_pickup_achievements = function(object, player)
|
|||
end
|
||||
end
|
||||
|
||||
local enable_physics = function(object, luaentity, ignore_check)
|
||||
local function enable_physics(object, luaentity, ignore_check)
|
||||
if luaentity.physical_state == false or ignore_check == true then
|
||||
luaentity.physical_state = true
|
||||
object:set_properties({
|
||||
|
@ -83,7 +83,7 @@ local enable_physics = function(object, luaentity, ignore_check)
|
|||
end
|
||||
end
|
||||
|
||||
local disable_physics = function(object, luaentity, ignore_check, reset_movement)
|
||||
local function disable_physics(object, luaentity, ignore_check, reset_movement)
|
||||
if luaentity.physical_state == true or ignore_check == true then
|
||||
luaentity.physical_state = false
|
||||
object:set_properties({
|
||||
|
@ -98,13 +98,11 @@ end
|
|||
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
tick = not tick
|
||||
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
if player:get_hp() > 0 or not minetest.settings:get_bool("enable_damage") then
|
||||
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
local pos = player:get_pos()
|
||||
|
@ -235,7 +233,7 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
local dug_node = minetest.get_node(pos)
|
||||
local tooldef
|
||||
local tool
|
||||
if digger ~= nil then
|
||||
if digger then
|
||||
tool = digger:get_wielded_item()
|
||||
tooldef = minetest.registered_tools[tool:get_name()]
|
||||
|
||||
|
@ -292,10 +290,10 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
end
|
||||
end
|
||||
|
||||
if digger and mcl_experience.throw_experience and not silk_touch_drop then
|
||||
if digger and mcl_experience.throw_xp and not silk_touch_drop then
|
||||
local experience_amount = minetest.get_item_group(dug_node.name,"xp")
|
||||
if experience_amount > 0 then
|
||||
mcl_experience.throw_experience(pos, experience_amount)
|
||||
mcl_experience.throw_xp(pos, experience_amount)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -316,7 +314,7 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
end
|
||||
-- Spawn item and apply random speed
|
||||
local obj = minetest.add_item(dpos, drop_item)
|
||||
if obj ~= nil then
|
||||
if obj then
|
||||
local x = math.random(1, 5)
|
||||
if math.random(1,2) == 1 then
|
||||
x = -x
|
||||
|
@ -365,6 +363,17 @@ if not time_to_live then
|
|||
time_to_live = 300
|
||||
end
|
||||
|
||||
local function cxcz(o, cw, one, zero)
|
||||
if cw < 0 then
|
||||
table.insert(o, { [one]=1, y=0, [zero]=0 })
|
||||
table.insert(o, { [one]=-1, y=0, [zero]=0 })
|
||||
else
|
||||
table.insert(o, { [one]=-1, y=0, [zero]=0 })
|
||||
table.insert(o, { [one]=1, y=0, [zero]=0 })
|
||||
end
|
||||
return o
|
||||
end
|
||||
|
||||
minetest.register_entity(":__builtin:item", {
|
||||
initial_properties = {
|
||||
hp_max = 1,
|
||||
|
@ -385,7 +394,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
-- The itemstring MUST be set immediately to a non-empty string after creating the entity.
|
||||
-- The hand is NOT permitted as dropped item. ;-)
|
||||
-- Item entities will be deleted if they still have an empty itemstring on their first on_step tick.
|
||||
itemstring = '',
|
||||
itemstring = "",
|
||||
|
||||
-- If true, item will fall
|
||||
physical_state = true,
|
||||
|
@ -406,6 +415,14 @@ minetest.register_entity(":__builtin:item", {
|
|||
return
|
||||
end
|
||||
local stack = ItemStack(itemstring)
|
||||
if minetest.get_item_group(stack:get_name(), "compass") > 0 then
|
||||
stack:set_name("mcl_compass:16")
|
||||
itemstring = stack:to_string()
|
||||
self.itemstring = itemstring
|
||||
end
|
||||
if minetest.get_item_group(stack:get_name(), "clock") > 0 then
|
||||
self.is_clock = true
|
||||
end
|
||||
local count = stack:get_count()
|
||||
local max_count = stack:get_stack_max()
|
||||
if count > max_count then
|
||||
|
@ -418,13 +435,9 @@ minetest.register_entity(":__builtin:item", {
|
|||
if itemtable then
|
||||
itemname = stack:to_table().name
|
||||
end
|
||||
local item_texture = nil
|
||||
local item_type = ""
|
||||
local glow
|
||||
local def = minetest.registered_items[itemname]
|
||||
if def then
|
||||
item_texture = def.inventory_image
|
||||
item_type = def.type
|
||||
description = def.description
|
||||
glow = def.light_source
|
||||
end
|
||||
|
@ -467,7 +480,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
return minetest.serialize({
|
||||
local data = minetest.serialize({
|
||||
itemstring = self.itemstring,
|
||||
always_collect = self.always_collect,
|
||||
age = self.age,
|
||||
|
@ -475,6 +488,39 @@ minetest.register_entity(":__builtin:item", {
|
|||
_flowing = self._flowing,
|
||||
_removed = self._removed,
|
||||
})
|
||||
-- sfan5 guessed that the biggest serializable item
|
||||
-- entity would have a size of 65530 bytes. This has
|
||||
-- been experimentally verified to be still too large.
|
||||
--
|
||||
-- anon5 has calculated that the biggest serializable
|
||||
-- item entity has a size of exactly 65487 bytes:
|
||||
--
|
||||
-- 1. serializeString16 can handle max. 65535 bytes.
|
||||
-- 2. The following engine metadata is always saved:
|
||||
-- • 1 byte (version)
|
||||
-- • 2 byte (length prefix)
|
||||
-- • 14 byte “__builtin:item”
|
||||
-- • 4 byte (length prefix)
|
||||
-- • 2 byte (health)
|
||||
-- • 3 × 4 byte = 12 byte (position)
|
||||
-- • 4 byte (yaw)
|
||||
-- • 1 byte (version 2)
|
||||
-- • 2 × 4 byte = 8 byte (pitch and roll)
|
||||
-- 3. This leaves 65487 bytes for the serialization.
|
||||
if #data > 65487 then -- would crash the engine
|
||||
local stack = ItemStack(self.itemstring)
|
||||
stack:get_meta():from_table(nil)
|
||||
self.itemstring = stack:to_string()
|
||||
minetest.log(
|
||||
"warning",
|
||||
"Overlong item entity metadata removed: “" ..
|
||||
self.itemstring ..
|
||||
"” had serialized length of " ..
|
||||
#data
|
||||
)
|
||||
return self:get_staticdata()
|
||||
end
|
||||
return data
|
||||
end,
|
||||
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
|
@ -562,7 +608,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
return true
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
on_step = function(self, dtime, moveresult)
|
||||
if self._removed then
|
||||
self.object:set_properties({
|
||||
physical = false
|
||||
|
@ -572,7 +618,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
return
|
||||
end
|
||||
self.age = self.age + dtime
|
||||
if self._collector_timer ~= nil then
|
||||
if self._collector_timer then
|
||||
self._collector_timer = self._collector_timer + dtime
|
||||
end
|
||||
if time_to_live > 0 and self.age > time_to_live then
|
||||
|
@ -593,6 +639,12 @@ minetest.register_entity(":__builtin:item", {
|
|||
local node = minetest.get_node_or_nil(p)
|
||||
local in_unloaded = (node == nil)
|
||||
|
||||
if self.is_clock then
|
||||
self.object:set_properties({
|
||||
textures = {"mcl_clock:clock_" .. (mcl_worlds.clock_works(p) and mcl_clock.old_time or mcl_clock.random_frame)}
|
||||
})
|
||||
end
|
||||
|
||||
-- If no collector was found for a long enough time, declare the magnet as disabled
|
||||
if self._magnet_active and (self._collector_timer == nil or (self._collector_timer > item_drop_settings.magnet_time)) then
|
||||
self._magnet_active = false
|
||||
|
@ -623,6 +675,18 @@ minetest.register_entity(":__builtin:item", {
|
|||
end
|
||||
end
|
||||
|
||||
-- Destroy item when it collides with a cactus
|
||||
if moveresult and moveresult.collides then
|
||||
for _, collision in pairs(moveresult.collisions) do
|
||||
local pos = collision.node_pos
|
||||
if collision.type == "node" and minetest.get_node(pos).name == "mcl_core:cactus" then
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Push item out when stuck inside solid opaque node
|
||||
if def and def.walkable and def.groups and def.groups.opaque == 1 then
|
||||
local shootdir
|
||||
|
@ -634,16 +698,6 @@ minetest.register_entity(":__builtin:item", {
|
|||
-- 1st: closest
|
||||
-- 2nd: other direction
|
||||
-- 3rd and 4th: other axis
|
||||
local cxcz = function(o, cw, one, zero)
|
||||
if cw < 0 then
|
||||
table.insert(o, { [one]=1, y=0, [zero]=0 })
|
||||
table.insert(o, { [one]=-1, y=0, [zero]=0 })
|
||||
else
|
||||
table.insert(o, { [one]=-1, y=0, [zero]=0 })
|
||||
table.insert(o, { [one]=1, y=0, [zero]=0 })
|
||||
end
|
||||
return o
|
||||
end
|
||||
if math.abs(cx) < math.abs(cz) then
|
||||
order = cxcz(order, cx, "x", "z")
|
||||
order = cxcz(order, cz, "z", "x")
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
local vector = vector
|
||||
|
||||
function mcl_minecarts:get_sign(z)
|
||||
if z == 0 then
|
||||
return 0
|
||||
|
@ -38,11 +40,9 @@ end
|
|||
|
||||
function mcl_minecarts:check_front_up_down(pos, dir_, check_down, railtype)
|
||||
local dir = vector.new(dir_)
|
||||
local cur = nil
|
||||
|
||||
-- Front
|
||||
dir.y = 0
|
||||
cur = vector.add(pos, dir)
|
||||
local cur = vector.add(pos, dir)
|
||||
if mcl_minecarts:is_rail(cur, railtype) then
|
||||
return dir
|
||||
end
|
||||
|
@ -65,7 +65,7 @@ end
|
|||
|
||||
function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
||||
local pos = vector.round(pos_)
|
||||
local cur = nil
|
||||
local cur
|
||||
local left_check, right_check = true, true
|
||||
|
||||
-- Check left and right
|
||||
|
@ -122,7 +122,6 @@ function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
|||
return cur
|
||||
end
|
||||
end
|
||||
|
||||
-- Backwards
|
||||
if not old_switch then
|
||||
cur = mcl_minecarts:check_front_up_down(pos, {
|
||||
|
@ -134,7 +133,5 @@ function mcl_minecarts:get_rail_direction(pos_, dir, ctrl, old_switch, railtype)
|
|||
return cur
|
||||
end
|
||||
end
|
||||
|
||||
return {x=0, y=0, z=0}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
local S = minetest.get_translator("mcl_minecarts")
|
||||
local modname = minetest.get_current_modname()
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
local has_mcl_wip = minetest.get_modpath("mcl_wip")
|
||||
|
||||
mcl_minecarts = {}
|
||||
mcl_minecarts.modpath = minetest.get_modpath("mcl_minecarts")
|
||||
mcl_minecarts.modpath = minetest.get_modpath(modname)
|
||||
mcl_minecarts.speed_max = 10
|
||||
mcl_minecarts.check_float_time = 15
|
||||
|
||||
|
@ -197,14 +198,27 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
else
|
||||
self._last_float_check = self._last_float_check + dtime
|
||||
end
|
||||
local pos, rou_pos, node
|
||||
|
||||
local pos, rou_pos, node = self.object:get_pos()
|
||||
local r = 0.6
|
||||
for _, node_pos in pairs({{r, 0}, {0, r}, {-r, 0}, {0, -r}}) do
|
||||
if minetest.get_node(vector.offset(pos, node_pos[1], 0, node_pos[2])).name == "mcl_core:cactus" then
|
||||
detach_driver(self)
|
||||
for d = 1, #drop do
|
||||
minetest.add_item(pos, drop[d])
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Drop minecart if it isn't on a rail anymore
|
||||
if self._last_float_check >= mcl_minecarts.check_float_time then
|
||||
pos = self.object:get_pos()
|
||||
rou_pos = vector.round(pos)
|
||||
node = minetest.get_node(rou_pos)
|
||||
local g = minetest.get_item_group(node.name, "connect_to_raillike")
|
||||
if g ~= self._railtype and self._railtype ~= nil then
|
||||
if g ~= self._railtype and self._railtype then
|
||||
-- Detach driver
|
||||
if player then
|
||||
if self._old_pos then
|
||||
|
@ -486,7 +500,6 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
if update.pos then
|
||||
self.object:set_pos(pos)
|
||||
end
|
||||
update = nil
|
||||
end
|
||||
|
||||
function cart:get_staticdata()
|
||||
|
@ -497,7 +510,7 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
end
|
||||
|
||||
-- Place a minecart at pointed_thing
|
||||
mcl_minecarts.place_minecart = function(itemstack, pointed_thing, placer)
|
||||
function mcl_minecarts.place_minecart(itemstack, pointed_thing, placer)
|
||||
if not pointed_thing.type == "node" then
|
||||
return
|
||||
end
|
||||
|
@ -524,7 +537,7 @@ mcl_minecarts.place_minecart = function(itemstack, pointed_thing, placer)
|
|||
local cart = minetest.add_entity(railpos, entity_id)
|
||||
local railtype = minetest.get_item_group(node.name, "connect_to_raillike")
|
||||
local le = cart:get_luaentity()
|
||||
if le ~= nil then
|
||||
if le then
|
||||
le._railtype = railtype
|
||||
end
|
||||
local cart_dir = mcl_minecarts:get_rail_direction(railpos, {x=1, y=0, z=0}, nil, nil, railtype)
|
||||
|
@ -541,7 +554,7 @@ mcl_minecarts.place_minecart = function(itemstack, pointed_thing, placer)
|
|||
end
|
||||
|
||||
|
||||
local register_craftitem = function(itemstring, entity_id, description, tt_help, longdesc, usagehelp, icon, creative)
|
||||
local function register_craftitem(itemstring, entity_id, description, tt_help, longdesc, usagehelp, icon, creative)
|
||||
entity_mapping[itemstring] = entity_id
|
||||
|
||||
local groups = { minecart = 1, transport = 1 }
|
||||
|
@ -607,7 +620,7 @@ Register a minecart
|
|||
local function register_minecart(itemstring, entity_id, description, tt_help, longdesc, usagehelp, mesh, textures, icon, drop, on_rightclick, on_activate_by_rail, creative)
|
||||
register_entity(entity_id, mesh, textures, drop, on_rightclick, on_activate_by_rail)
|
||||
register_craftitem(itemstring, entity_id, description, tt_help, longdesc, usagehelp, icon, creative)
|
||||
if minetest.get_modpath("doc_identifier") ~= nil then
|
||||
if minetest.get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object(entity_id, "craftitems", itemstring)
|
||||
end
|
||||
end
|
||||
|
@ -646,7 +659,7 @@ register_minecart(
|
|||
if player then
|
||||
mcl_player.player_set_animation(player, "sit" , 30)
|
||||
player:set_eye_offset({x=0, y=-5.5, z=0},{x=0, y=-4, z=0})
|
||||
mcl_tmp_message.message(clicker, S("Sneak to dismount"))
|
||||
mcl_title.set(clicker, "actionbar", {text=S("Sneak to dismount"), color="white", stay=60})
|
||||
end
|
||||
end, name)
|
||||
end
|
||||
|
@ -817,31 +830,30 @@ minetest.register_craft({
|
|||
})
|
||||
|
||||
-- TODO: Re-enable crafting of special minecarts when they have been implemented
|
||||
if false then
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:furnace_minecart",
|
||||
recipe = {
|
||||
{"mcl_furnaces:furnace"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})
|
||||
--[[minetest.register_craft({
|
||||
output = "mcl_minecarts:furnace_minecart",
|
||||
recipe = {
|
||||
{"mcl_furnaces:furnace"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:hopper_minecart",
|
||||
recipe = {
|
||||
{"mcl_hoppers:hopper"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:hopper_minecart",
|
||||
recipe = {
|
||||
{"mcl_hoppers:hopper"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:chest_minecart",
|
||||
recipe = {
|
||||
{"mcl_chests:chest"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})]]
|
||||
|
||||
minetest.register_craft({
|
||||
output = "mcl_minecarts:chest_minecart",
|
||||
recipe = {
|
||||
{"mcl_chests:chest"},
|
||||
{"mcl_minecarts:minecart"},
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
if has_mcl_wip then
|
||||
mcl_wip.register_wip_item("mcl_minecarts:chest_minecart")
|
||||
|
|
|
@ -33,3 +33,4 @@ Activates minecarts when powered=Aktiviert Loren, wenn bestromt
|
|||
Emits redstone power when a minecart is detected=Gibt ein Redstonesignal aus, wenn eine Lore erfasst wird
|
||||
Vehicle for fast travel on rails=Fahrzeug zum schnellen Transport auf Schienen
|
||||
Can be ignited by tools or powered activator rail=Kann mit Werkzeugen oder bestromten Aktivierungsschienen angezündet werden
|
||||
Sneak to dismount=Zum Aussteigen schleichen
|
||||
|
|
|
@ -33,3 +33,4 @@ Activates minecarts when powered=Active les wagonnets lorsqu'il est alimenté
|
|||
Emits redstone power when a minecart is detected=Émet de l'énergie redstone lorsqu'un wagonnet est détecté
|
||||
Vehicle for fast travel on rails=Véhicule pour voyager rapidement sur rails
|
||||
Can be ignited by tools or powered activator rail=Peut être allumé par des outils ou un rail d'activation motorisé
|
||||
Sneak to dismount=
|
|
@ -0,0 +1,36 @@
|
|||
# textdomain: mcl_minecarts
|
||||
Minecart=Wagonik
|
||||
Minecarts can be used for a quick transportion on rails.=Wagoniki mogą być użyte do szybkiego transportu po torach.
|
||||
Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type.=Wagoniki mogą jeździć tylko po torach i zawsze podążają za wytyczoną ścieżką. W przypadku skrzyżowań typu T, gdzie nie ma prostej ścieżki, skręcają w lew. Ich szybkość zależy od typu torów.
|
||||
You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.=Możesz postawić wagonik na torach. Kliknij prawym przyciskiem myszy aby do niego wejść. Uderz go by zaczął się poruszać.
|
||||
To obtain the minecart, punch it while holding down the sneak key.=Aby odzyskać wagonik uderz go podczas skradania.
|
||||
A minecart with TNT is an explosive vehicle that travels on rail.=Wagonik z TNT jest wybuchowym pojazdem podróżującym po torach.
|
||||
Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail.=Postaw go na torach. Uderz by zaczął się poruszać. TNT zapala się krzesiwem lub gdy wagonik jest na zasilonych torach aktywacyjnych.
|
||||
To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited.=Aby odzyskać wagonik z TNT uderz go podczas skradania. Nie możesz tego zrobić gdy TNT jest zapalone.
|
||||
A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel.=Wagonik z piecem jest pojazdem podróżującym na torach. Napędza on samego siebie za pomocą paliwa.
|
||||
Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving.=Postaw go na torach. Jeśli dasz mu nieco węgla piec zacznie palić przez długi czas, a wagonik będzie się sam poruszał. Uderz go by zaczął się poruszać.
|
||||
To obtain the minecart and furnace, punch them while holding down the sneak key.=Aby odzyskać wagonik z piecem uderz go podczas skradania.
|
||||
Minecart with Chest=Wagonik ze skrzynią
|
||||
Minecart with Furnace=Wagonik z piecem
|
||||
Minecart with Command Block=Wagonik z blokiem poleceń
|
||||
Minecart with Hopper=Wagonik z lejem
|
||||
Minecart with TNT=Wagonik z TNT
|
||||
Place them on the ground to build your railway, the rails will automatically connect to each other and will turn into curves, T-junctions, crossings and slopes as needed.=Postaw je na ziemi by zbudować ścieżkę z torów. Tory automatycznie połączą się ze sobą i zamienią się w zakręty, skrzyżowania typu T, skrzyżowania i równie w zależności od potrzeb.
|
||||
Rail=Tor
|
||||
Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction.=Tory mogą być wykorzystane do zbudowania torów dla wagoników. Zwyczajne tory nieco spowalniają wagoniki ze względu na tarcie.
|
||||
Powered Rail=Zasilane tory
|
||||
Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts.=Tory mogą być wykorzystane do zbudowania torów dla wagoników. Zasilane tory mogą przyspieszać lub spowalniać wagoniki.
|
||||
Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power.=Bez zasilania czerwienitem tory będą spowalniać wagoniki. Aby sprawić by je przyspieszały zasil je czerwienitem.
|
||||
Activator Rail=Tory aktywacyjne
|
||||
Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts.=Tory mogą być wykorzystane do zbudowania torów dla wagoników. Tory aktywacyjne są wykorzystywane do aktywacji specjalnych wagoników.
|
||||
To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail.=Aby ten tor aktywował wagonik, zasil go czerwienitem i spraw by wagonik po nim przejechał.
|
||||
Detector Rail=Tory z czujnikiem
|
||||
Rails can be used to build transport tracks for minecarts. A detector rail is able to detect a minecart above it and powers redstone mechanisms.=Tory mogą być wykorzystane do zbudowania torów dla wagoników. Tory z czujnikiem są w stanie wykryć kiedy wagonik po nich przejeżdża i wysłać sygnał do czerwienitowych mechanizmów.
|
||||
To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail.=Aby wykryć wagonik i dostarczyć zasilanie czerwienitem podłącz go czerwienitem to mechanizmu i spraw by wagonik po nim przejechał.
|
||||
Track for minecarts=Tor dla wagoników
|
||||
Speed up when powered, slow down when not powered=Przyspiesza gdy zasilane, spowalnia gdy nie
|
||||
Activates minecarts when powered=Aktywuje wagoniki gdy zasilane
|
||||
Emits redstone power when a minecart is detected=Emituje zasilanie czerwienitem gdy wagonik jest wykryty
|
||||
Vehicle for fast travel on rails=Pojazd do szybkiej podróży na torach
|
||||
Can be ignited by tools or powered activator rail=Może być zapalony przez narzędzia, lub zasilane tor aktywacyjne
|
||||
Sneak to dismount=Zacznij się skradać by zejść
|
|
@ -1,6 +1,6 @@
|
|||
name = mcl_minecarts
|
||||
author = Krock
|
||||
description = Minecarts are vehicles to move players quickly on rails.
|
||||
depends = mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons
|
||||
depends = mcl_title, mcl_explosions, mcl_core, mcl_sounds, mcl_player, mcl_achievements, mcl_chests, mcl_furnaces, mesecons_commandblock, mcl_hoppers, mcl_tnt, mesecons
|
||||
optional_depends = doc_identifier, mcl_wip
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
local S = minetest.get_translator("mcl_minecarts")
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
-- Template rail function
|
||||
local register_rail = function(itemstring, tiles, def_extras, creative)
|
||||
local function register_rail(itemstring, tiles, def_extras, creative)
|
||||
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=1,destroy_by_lava_flow=1, transport=1}
|
||||
if creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
|
@ -206,11 +206,11 @@ register_rail("mcl_minecarts:detector_rail_on",
|
|||
|
||||
-- Crafting
|
||||
minetest.register_craft({
|
||||
output = 'mcl_minecarts:rail 16',
|
||||
output = "mcl_minecarts:rail 16",
|
||||
recipe = {
|
||||
{'mcl_core:iron_ingot', '', 'mcl_core:iron_ingot'},
|
||||
{'mcl_core:iron_ingot', 'mcl_core:stick', 'mcl_core:iron_ingot'},
|
||||
{'mcl_core:iron_ingot', '', 'mcl_core:iron_ingot'},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "mcl_core:stick", "mcl_core:iron_ingot"},
|
||||
{"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -502,20 +502,6 @@ and damages any entity caught inside the blast radius. Protection will limit
|
|||
node destruction but not entity damage.
|
||||
|
||||
|
||||
mobs:capture_mob
|
||||
----------------
|
||||
|
||||
mobs:capture_mob(...)
|
||||
|
||||
Does nothing and returns false.
|
||||
|
||||
This function is provided for compability with Mobs Redo for an attempt to
|
||||
capture a mob.
|
||||
Mobs cannot be captured in MineClone 2.
|
||||
|
||||
In Mobs Redo, this is generally called inside the on_rightclick section of the mob
|
||||
api code, it provides a chance of capturing the mob. See Mobs Redo documentation
|
||||
of parameters.
|
||||
|
||||
Feeding and Taming/Breeding
|
||||
---------------------------
|
||||
|
@ -535,19 +521,6 @@ Will return true when mob is fed with item it likes.
|
|||
them up
|
||||
|
||||
|
||||
Protecting Mobs
|
||||
---------------
|
||||
|
||||
mobs:protect(self, clicker)
|
||||
|
||||
This function can be used to right-click any tamed mob with mobs:protector item,
|
||||
this will protect the mob from harm inside of a protected area from other
|
||||
players. Will return true when mob right-clicked with mobs:protector item.
|
||||
|
||||
'self' mob information
|
||||
'clicker' player information
|
||||
|
||||
|
||||
Riding Mobs
|
||||
-----------
|
||||
|
||||
|
@ -605,7 +578,7 @@ Note: animation names above are from the pre-defined animation lists inside mob
|
|||
registry without extensions.
|
||||
|
||||
|
||||
mobs:set_animation(self, name)
|
||||
mobs.set_mob_animation(self, name)
|
||||
|
||||
This function sets the current animation for mob, defaulting to "stand" if not
|
||||
found.
|
||||
|
@ -781,8 +754,5 @@ mobs:register_mob("mob_horse:horse", {
|
|||
inv:remove_item("main", "mobs:saddle")
|
||||
end
|
||||
end
|
||||
|
||||
-- used to capture horse with magic lasso
|
||||
mobs:capture_mob(self, clicker, 0, 0, 80, false, nil)
|
||||
end
|
||||
})
|
||||
|
|
|
@ -0,0 +1,736 @@
|
|||
-- API for Mobs Redo: MineClone 2 Delux 2.0 DRM Free Early Access Super Extreme Edition
|
||||
|
||||
-- mobs library
|
||||
mobs = {}
|
||||
|
||||
-- lua locals - can grab from this to easily plop them into the api lua files
|
||||
|
||||
--localize minetest functions
|
||||
local minetest_settings = minetest.settings
|
||||
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
|
||||
local minetest_get_modpath = minetest.get_modpath
|
||||
local minetest_registered_nodes = minetest.registered_nodes
|
||||
local minetest_get_node = minetest.get_node
|
||||
--local minetest_get_item_group = minetest.get_item_group
|
||||
local minetest_registered_entities = minetest.registered_entities
|
||||
--local minetest_line_of_sight = minetest.line_of_sight
|
||||
--local minetest_after = minetest.after
|
||||
--local minetest_sound_play = minetest.sound_play
|
||||
--local minetest_add_particlespawner = minetest.add_particlespawner
|
||||
--local minetest_registered_items = minetest.registered_items
|
||||
--local minetest_set_node = minetest.set_node
|
||||
local minetest_add_item = minetest.add_item
|
||||
--local minetest_get_craft_result = minetest.get_craft_result
|
||||
--local minetest_find_path = minetest.find_path
|
||||
local minetest_is_creative_enabled = minetest.is_creative_enabled
|
||||
--local minetest_find_node_near = minetest.find_node_near
|
||||
--local minetest_find_nodes_in_area_under_air = minetest.find_nodes_in_area_under_air
|
||||
--local minetest_raycast = minetest.raycast
|
||||
--local minetest_get_us_time = minetest.get_us_time
|
||||
local minetest_add_entity = minetest.add_entity
|
||||
--local minetest_get_natural_light = minetest.get_natural_light
|
||||
--local minetest_get_node_or_nil = minetest.get_node_or_nil
|
||||
|
||||
-- localize math functions
|
||||
local math = math
|
||||
|
||||
-- localize vector functions
|
||||
local vector = vector
|
||||
|
||||
local string = string
|
||||
|
||||
-- mob constants
|
||||
--local BREED_TIME = 30
|
||||
--local BREED_TIME_AGAIN = 300
|
||||
--local CHILD_GROW_TIME = 60*20
|
||||
--local DEATH_DELAY = 0.5
|
||||
local DEFAULT_FALL_SPEED = -10
|
||||
--local FLOP_HEIGHT = 5.0
|
||||
--local FLOP_HOR_SPEED = 1.5
|
||||
local GRAVITY = minetest_settings:get("movement_gravity")-- + 9.81
|
||||
|
||||
local MAX_MOB_NAME_LENGTH = 30
|
||||
|
||||
|
||||
--[[local MOB_CAP = {}
|
||||
MOB_CAP.hostile = 70
|
||||
MOB_CAP.passive = 10
|
||||
MOB_CAP.ambient = 15
|
||||
MOB_CAP.water = 15
|
||||
]]
|
||||
|
||||
-- Load main settings
|
||||
--local damage_enabled = minetest_settings:get_bool("enable_damage")
|
||||
--local disable_blood = minetest_settings:get_bool("mobs_disable_blood")
|
||||
--local mobs_drop_items = minetest_settings:get_bool("mobs_drop_items") ~= false
|
||||
--local mobs_griefing = minetest_settings:get_bool("mobs_griefing") ~= false
|
||||
--local spawn_protected = minetest_settings:get_bool("mobs_spawn_protected") ~= false
|
||||
--local remove_far = true
|
||||
local difficulty = tonumber(minetest_settings:get("mob_difficulty")) or 1.0
|
||||
--local show_health = false
|
||||
--local max_per_block = tonumber(minetest_settings:get("max_objects_per_block") or 64)
|
||||
---local mobs_spawn_chance = tonumber(minetest_settings:get("mobs_spawn_chance") or 2.5)
|
||||
|
||||
-- pathfinding settings
|
||||
--local enable_pathfinding = true
|
||||
--local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
|
||||
--local stuck_path_timeout = 10 -- how long will mob follow path before giving up
|
||||
|
||||
-- default nodes
|
||||
--local node_ice = "mcl_core:ice"
|
||||
--local node_snowblock = "mcl_core:snowblock"
|
||||
--local node_snow = "mcl_core:snow"
|
||||
mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
|
||||
|
||||
--local mod_weather = minetest_get_modpath("mcl_weather")
|
||||
--local mod_explosions = minetest_get_modpath("mcl_explosions")
|
||||
local mod_mobspawners = minetest_get_modpath("mcl_mobspawners")
|
||||
--local mod_hunger = minetest_get_modpath("mcl_hunger")
|
||||
--local mod_worlds = minetest_get_modpath("mcl_worlds")
|
||||
--local mod_armor = minetest_get_modpath("mcl_armor")
|
||||
--local mod_experience = minetest_get_modpath("mcl_experience")
|
||||
|
||||
|
||||
-- random locals I found
|
||||
--local los_switcher = false
|
||||
--local height_switcher = false
|
||||
|
||||
-- Get translator
|
||||
local S = minetest.get_translator(minetest.get_current_modname())
|
||||
|
||||
-- CMI support check
|
||||
--local use_cmi = minetest.global_exists("cmi")
|
||||
|
||||
-- creative check
|
||||
function mobs.is_creative(name)
|
||||
return minetest_is_creative_enabled(name)
|
||||
end
|
||||
|
||||
--[[local function atan(x)
|
||||
if not x or x ~= x then
|
||||
return 0
|
||||
else
|
||||
return math.atan(x)
|
||||
end
|
||||
end]]
|
||||
|
||||
-- Shows helpful debug info above each mob
|
||||
--local mobs_debug = minetest_settings:get_bool("mobs_debug", false)
|
||||
|
||||
-- Peaceful mode message so players will know there are no monsters
|
||||
if minetest_settings:get_bool("only_peaceful_mobs", false) then
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
minetest.chat_send_player(player:get_player_name(),
|
||||
S("Peaceful mode active! No monsters will spawn."))
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
local api_path = minetest.get_modpath(minetest.get_current_modname()).."/api/mob_functions/"
|
||||
|
||||
--ignite all parts of the api
|
||||
dofile(api_path .. "flow_lib.lua")
|
||||
dofile(api_path .. "ai.lua")
|
||||
dofile(api_path .. "animation.lua")
|
||||
dofile(api_path .. "collision.lua")
|
||||
dofile(api_path .. "environment.lua")
|
||||
dofile(api_path .. "interaction.lua")
|
||||
dofile(api_path .. "movement.lua")
|
||||
dofile(api_path .. "set_up.lua")
|
||||
dofile(api_path .. "attack_type_instructions.lua")
|
||||
dofile(api_path .. "sound_handling.lua")
|
||||
dofile(api_path .. "death_logic.lua")
|
||||
dofile(api_path .. "mob_effects.lua")
|
||||
dofile(api_path .. "projectile_handling.lua")
|
||||
dofile(api_path .. "breeding.lua")
|
||||
dofile(api_path .. "head_logic.lua")
|
||||
|
||||
|
||||
mobs.spawning_mobs = {}
|
||||
|
||||
|
||||
|
||||
|
||||
-- register mob entity
|
||||
function mobs:register_mob(name, def)
|
||||
|
||||
local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
|
||||
|
||||
-- Workaround for <https://github.com/minetest/minetest/issues/5966>:
|
||||
-- Increase upper Y limit to avoid mobs glitching through solid nodes.
|
||||
-- FIXME: Remove workaround if it's no longer needed.
|
||||
|
||||
if collisionbox[5] < 0.79 then
|
||||
collisionbox[5] = 0.79
|
||||
end
|
||||
|
||||
mobs.spawning_mobs[name] = true
|
||||
|
||||
local function scale_difficulty(value, default, min, special)
|
||||
if (not value) or (value == default) or (value == special) then
|
||||
return default
|
||||
else
|
||||
return math.max(min, value * difficulty)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity(name, {
|
||||
description = def.description,
|
||||
use_texture_alpha = def.use_texture_alpha,
|
||||
stepheight = def.stepheight or 0.6,
|
||||
stepheight_backup = def.stepheight or 0.6,
|
||||
name = name,
|
||||
type = def.type,
|
||||
attack_type = def.attack_type,
|
||||
fly = def.fly,
|
||||
fly_in = def.fly_in or {"air", "__airlike"},
|
||||
owner = def.owner or "",
|
||||
order = def.order or "",
|
||||
on_die = def.on_die,
|
||||
spawn_small_alternative = def.spawn_small_alternative,
|
||||
do_custom = def.do_custom,
|
||||
jump_height = def.jump_height or 4, -- was 6
|
||||
rotate = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
|
||||
hp_min = scale_difficulty(def.hp_min, 5, 1),
|
||||
hp_max = scale_difficulty(def.hp_max, 10, 1),
|
||||
xp_min = def.xp_min or 1,
|
||||
xp_max = def.xp_max or 5,
|
||||
breath_max = def.breath_max or 6,
|
||||
breathes_in_water = def.breathes_in_water or false,
|
||||
physical = true,
|
||||
collisionbox = collisionbox,
|
||||
collide_with_objects = def.collide_with_objects or false,
|
||||
selectionbox = def.selectionbox or def.collisionbox,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size or {x = 1, y = 1},
|
||||
mesh = def.mesh,
|
||||
makes_footstep_sound = def.makes_footstep_sound or false,
|
||||
view_range = def.view_range or 16,
|
||||
walk_velocity = def.walk_velocity or 1,
|
||||
run_velocity = def.run_velocity or 2,
|
||||
damage = scale_difficulty(def.damage, 0, 0),
|
||||
light_damage = def.light_damage or 0,
|
||||
sunlight_damage = def.sunlight_damage or 0,
|
||||
water_damage = def.water_damage or 0,
|
||||
lava_damage = def.lava_damage or 8,
|
||||
fire_damage = def.fire_damage or 1,
|
||||
suffocation = def.suffocation or true,
|
||||
fall_damage = def.fall_damage or 1,
|
||||
fall_speed = def.fall_speed or DEFAULT_FALL_SPEED, -- must be lower than -2
|
||||
drops = def.drops or {},
|
||||
armor = def.armor or 100,
|
||||
on_rightclick = mobs.create_mob_on_rightclick(def.on_rightclick),
|
||||
arrow = def.arrow,
|
||||
shoot_interval = def.shoot_interval,
|
||||
sounds = def.sounds or {},
|
||||
animation = def.animation,
|
||||
jump = def.jump ~= false,
|
||||
walk_chance = def.walk_chance or 50,
|
||||
attacks_monsters = def.attacks_monsters or false,
|
||||
group_attack = def.group_attack or false,
|
||||
passive = def.passive or false,
|
||||
knock_back = def.knock_back ~= false,
|
||||
shoot_offset = def.shoot_offset or 0,
|
||||
floats = def.floats or 1, -- floats in water by default
|
||||
floats_on_lava = def.floats_on_lava or 0,
|
||||
replace_rate = def.replace_rate,
|
||||
replace_what = def.replace_what,
|
||||
replace_with = def.replace_with,
|
||||
replace_offset = def.replace_offset or 0,
|
||||
on_replace = def.on_replace,
|
||||
timer = 0,
|
||||
state_timer = 0,
|
||||
env_damage_timer = 0,
|
||||
tamed = false,
|
||||
pause_timer = 0,
|
||||
gotten = false,
|
||||
reach = def.reach or 3,
|
||||
htimer = 0,
|
||||
texture_list = def.textures,
|
||||
child_texture = def.child_texture,
|
||||
docile_by_day = def.docile_by_day or false,
|
||||
time_of_day = 0.5,
|
||||
fear_height = def.fear_height or 0,
|
||||
runaway = def.runaway,
|
||||
runaway_timer = 0,
|
||||
pathfinding = def.pathfinding,
|
||||
immune_to = def.immune_to or {},
|
||||
explosion_radius = def.explosion_radius, -- LEGACY
|
||||
explosion_damage_radius = def.explosion_damage_radius, -- LEGACY
|
||||
explosiontimer_reset_radius = def.explosiontimer_reset_radius,
|
||||
explosion_timer = def.explosion_timer or 3,
|
||||
allow_fuse_reset = def.allow_fuse_reset ~= false,
|
||||
stop_to_explode = def.stop_to_explode ~= false,
|
||||
custom_attack = def.custom_attack,
|
||||
double_melee_attack = def.double_melee_attack,
|
||||
dogshoot_switch = def.dogshoot_switch,
|
||||
dogshoot_count = 0,
|
||||
dogshoot_count_max = def.dogshoot_count_max or 5,
|
||||
dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
|
||||
attack_animals = def.attack_animals or false,
|
||||
specific_attack = def.specific_attack,
|
||||
runaway_from = def.runaway_from,
|
||||
owner_loyal = def.owner_loyal,
|
||||
facing_fence = false,
|
||||
|
||||
_cmi_is_mob = true,
|
||||
|
||||
pushable = def.pushable or true,
|
||||
|
||||
--j4i stuff
|
||||
yaw = 0,
|
||||
automatic_face_movement_dir = def.rotate or 0, -- 0=front, 90=side, 180=back, 270=side2
|
||||
automatic_face_movement_max_rotation_per_sec = 360, --degrees
|
||||
backface_culling = true,
|
||||
walk_timer = 0,
|
||||
stand_timer = 0,
|
||||
current_animation = "",
|
||||
gravity = GRAVITY,
|
||||
swim = def.swim,
|
||||
swim_in = def.swim_in or {mobs_mc.items.water_source, "mcl_core:water_flowing", mobs_mc.items.river_water_source},
|
||||
pitch_switch = "static",
|
||||
jump_only = def.jump_only,
|
||||
hostile = def.hostile,
|
||||
neutral = def.neutral,
|
||||
attacking = nil,
|
||||
visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1},
|
||||
punch_timer_cooloff = def.punch_timer_cooloff or 0.5,
|
||||
death_animation_timer = 0,
|
||||
hostile_cooldown = def.hostile_cooldown or 15,
|
||||
tilt_fly = def.tilt_fly,
|
||||
tilt_swim = def.tilt_swim,
|
||||
fall_slow = def.fall_slow,
|
||||
projectile_cooldown_min = def.projectile_cooldown_min or 2,
|
||||
projectile_cooldown_max = def.projectile_cooldown_max or 6,
|
||||
skittish = def.skittish,
|
||||
|
||||
minimum_follow_distance = def.minimum_follow_distance or 0.5, --make mobs not freak out when underneath
|
||||
|
||||
memory = 0, -- memory timer if chasing/following
|
||||
fly_random_while_attack = def.fly_random_while_attack,
|
||||
|
||||
--for spiders
|
||||
always_climb = def.always_climb,
|
||||
|
||||
--despawn mechanic variables
|
||||
lifetimer_reset = 30, --30 seconds
|
||||
lifetimer = 30, --30 seconds
|
||||
|
||||
--breeding stuff
|
||||
breed_timer = 0,
|
||||
breed_lookout_timer = 0,
|
||||
breed_distance = def.breed_distance or 1.5, --how far away mobs have to be to begin actual breeding
|
||||
breed_lookout_timer_goal = 30, --30 seconds (this timer is for how long the mob looks for a mate)
|
||||
breed_timer_cooloff = 5*60, -- 5 minutes (this timer is for how long the mob has to wait before being bred again)
|
||||
bred = false,
|
||||
follow = def.follow, --this item is also used for the breeding mechanism
|
||||
follow_distance = def.follow_distance or 2,
|
||||
baby_size = def.baby_size or 0.5,
|
||||
baby = false,
|
||||
grow_up_timer = 0,
|
||||
grow_up_goal = 20*60, --in 20 minutes the mob grows up
|
||||
special_breed_timer = 0, --this is used for the AHEM AHEM part of breeding
|
||||
|
||||
backup_visual_size = def.visual_size,
|
||||
backup_collisionbox = collisionbox,
|
||||
backup_selectionbox = def.selectionbox or def.collisionbox,
|
||||
|
||||
|
||||
--fire timer
|
||||
burn_timer = 0,
|
||||
|
||||
ignores_cobwebs = def.ignores_cobwebs,
|
||||
breath = def.breath_max or 6,
|
||||
|
||||
random_sound_timer_min = 3,
|
||||
random_sound_timer_max = 10,
|
||||
|
||||
--head code variables
|
||||
--defaults are for the cow's default
|
||||
--because I don't know what else to set them
|
||||
--to :P
|
||||
|
||||
--you must use these to adjust the mob's head positions
|
||||
|
||||
--has_head is used as a logic gate (quick easy check)
|
||||
has_head = def.has_head or false,
|
||||
--head_bone is the actual bone in the model which the head
|
||||
--is attached to for animation
|
||||
head_bone = def.head_bone or "head",
|
||||
|
||||
--this part controls the base position of the head calculations
|
||||
--localized to the mob's visual yaw when gotten (self.object:get_yaw())
|
||||
--you can enable the debug in /mob_functions/head_logic.lua by uncommenting the
|
||||
--particle spawner code
|
||||
head_height_offset = def.head_height_offset or 1.0525,
|
||||
head_direction_offset = def.head_direction_offset or 0.5,
|
||||
|
||||
--this part controls the visual of the head
|
||||
head_bone_pos_y = def.head_bone_pos_y or 3.6,
|
||||
head_bone_pos_z = def.head_bone_pos_z or -0.6,
|
||||
head_pitch_modifier = def.head_pitch_modifier or 0,
|
||||
|
||||
--these variables are switches in case the model
|
||||
--moves the wrong way
|
||||
swap_y_with_x = def.swap_y_with_x or false,
|
||||
reverse_head_yaw = def.reverse_head_yaw or false,
|
||||
|
||||
--END HEAD CODE VARIABLES
|
||||
|
||||
--end j4i stuff
|
||||
|
||||
-- MCL2 extensions
|
||||
teleport = mobs.teleport,
|
||||
do_teleport = def.do_teleport,
|
||||
spawn_class = def.spawn_class,
|
||||
ignores_nametag = def.ignores_nametag or false,
|
||||
rain_damage = def.rain_damage or 0,
|
||||
glow = def.glow,
|
||||
--can_despawn = can_despawn,
|
||||
child = def.child or false,
|
||||
texture_mods = {},
|
||||
shoot_arrow = def.shoot_arrow,
|
||||
sounds_child = def.sounds_child,
|
||||
explosion_strength = def.explosion_strength,
|
||||
suffocation_timer = 0,
|
||||
follow_velocity = def.follow_velocity or 2.4,
|
||||
instant_death = def.instant_death or false,
|
||||
fire_resistant = def.fire_resistant or false,
|
||||
fire_damage_resistant = def.fire_damage_resistant or false,
|
||||
ignited_by_sunlight = def.ignited_by_sunlight or false,
|
||||
eye_height = def.eye_height or 1.5,
|
||||
defuse_reach = def.defuse_reach or 4,
|
||||
-- End of MCL2 extensions
|
||||
|
||||
on_spawn = def.on_spawn,
|
||||
|
||||
--on_blast = def.on_blast or do_tnt,
|
||||
|
||||
on_step = mobs.mob_step,
|
||||
|
||||
--do_punch = def.do_punch,
|
||||
|
||||
on_punch = mobs.mob_punch,
|
||||
|
||||
--on_breed = def.on_breed,
|
||||
|
||||
--on_grown = def.on_grown,
|
||||
|
||||
--on_detach_child = mob_detach_child,
|
||||
|
||||
on_activate = function(self, staticdata, dtime)
|
||||
self.object:set_acceleration(vector.new(0,-GRAVITY, 0))
|
||||
return mobs.mob_activate(self, staticdata, def, dtime)
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
return mobs.mob_staticdata(self)
|
||||
end,
|
||||
|
||||
--harmed_by_heal = def.harmed_by_heal,
|
||||
})
|
||||
|
||||
if minetest_get_modpath("doc_identifier") then
|
||||
doc.sub.identifier.register_object(name, "basics", "mobs")
|
||||
end
|
||||
|
||||
end -- END mobs:register_mob function
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-- register arrow for shoot attack
|
||||
function mobs:register_arrow(name, def)
|
||||
|
||||
-- errorcheck
|
||||
if not name or not def then
|
||||
print("failed to register arrow entity")
|
||||
return
|
||||
end
|
||||
|
||||
minetest.register_entity(name.."_entity", {
|
||||
|
||||
physical = false,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size,
|
||||
textures = def.textures,
|
||||
velocity = def.velocity,
|
||||
hit_player = def.hit_player,
|
||||
hit_node = def.hit_node,
|
||||
hit_mob = def.hit_mob,
|
||||
hit_object = def.hit_object,
|
||||
drop = def.drop or false, -- drops arrow as registered item when true
|
||||
collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
|
||||
timer = 0,
|
||||
switch = 0,
|
||||
owner_id = def.owner_id,
|
||||
rotate = def.rotate,
|
||||
speed = def.speed or nil,
|
||||
on_step = function(self)
|
||||
|
||||
local vel = self.object:get_velocity()
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if self.timer > 150
|
||||
or not mobs.within_limits(pos, 0) then
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
|
||||
-- does arrow have a tail (fireball)
|
||||
if def.tail
|
||||
and def.tail == 1
|
||||
and def.tail_texture then
|
||||
|
||||
--do this to prevent clipping through main entity sprite
|
||||
local pos_adjustment = vector.multiply(vector.normalize(vel), -1)
|
||||
local divider = def.tail_distance_divider or 1
|
||||
pos_adjustment = vector.divide(pos_adjustment, divider)
|
||||
local new_pos = vector.add(pos, pos_adjustment)
|
||||
minetest.add_particle({
|
||||
pos = new_pos,
|
||||
velocity = {x = 0, y = 0, z = 0},
|
||||
acceleration = {x = 0, y = 0, z = 0},
|
||||
expirationtime = def.expire or 0.25,
|
||||
collisiondetection = false,
|
||||
texture = def.tail_texture,
|
||||
size = def.tail_size or 5,
|
||||
glow = def.glow or 0,
|
||||
})
|
||||
end
|
||||
|
||||
if self.hit_node then
|
||||
|
||||
local node = minetest_get_node(pos).name
|
||||
|
||||
if minetest_registered_nodes[node].walkable then
|
||||
|
||||
self.hit_node(self, pos, node)
|
||||
|
||||
if self.drop == true then
|
||||
|
||||
pos.y = pos.y + 1
|
||||
|
||||
self.lastpos = (self.lastpos or pos)
|
||||
|
||||
minetest_add_item(self.lastpos, self.object:get_luaentity().name)
|
||||
end
|
||||
|
||||
self.object:remove();
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if self.hit_player or self.hit_mob or self.hit_object then
|
||||
|
||||
for _,player in pairs(minetest_get_objects_inside_radius(pos, 1.5)) do
|
||||
|
||||
if self.hit_player
|
||||
and player:is_player() then
|
||||
|
||||
if self.hit_player then
|
||||
self.hit_player(self, player)
|
||||
else
|
||||
mobs.arrow_hit(self, player)
|
||||
end
|
||||
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
|
||||
--[[
|
||||
local entity = player:get_luaentity()
|
||||
|
||||
if entity
|
||||
and self.hit_mob
|
||||
and entity._cmi_is_mob == true
|
||||
and tostring(player) ~= self.owner_id
|
||||
and entity.name ~= self.object:get_luaentity().name
|
||||
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
|
||||
|
||||
--self.hit_mob(self, player)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
]]--
|
||||
|
||||
--[[
|
||||
if entity
|
||||
and self.hit_object
|
||||
and (not entity._cmi_is_mob)
|
||||
and tostring(player) ~= self.owner_id
|
||||
and entity.name ~= self.object:get_luaentity().name
|
||||
and (self._shooter and entity.name ~= self._shooter:get_luaentity().name) then
|
||||
|
||||
--self.hit_object(self, player)
|
||||
self.object:remove();
|
||||
return
|
||||
end
|
||||
]]--
|
||||
end
|
||||
end
|
||||
|
||||
self.lastpos = pos
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
-- Register spawn eggs
|
||||
|
||||
-- Note: This also introduces the “spawn_egg” group:
|
||||
-- * spawn_egg=1: Spawn egg (generic mob, no metadata)
|
||||
-- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
|
||||
function mobs:register_egg(mob, desc, background, addegg, no_creative)
|
||||
|
||||
local grp = {spawn_egg = 1}
|
||||
|
||||
-- do NOT add this egg to creative inventory (e.g. dungeon master)
|
||||
if no_creative == true then
|
||||
grp.not_in_creative_inventory = 1
|
||||
end
|
||||
|
||||
local invimg = background
|
||||
|
||||
if addegg == 1 then
|
||||
invimg = "mobs_chicken_egg.png^(" .. invimg ..
|
||||
"^[mask:mobs_chicken_egg_overlay.png)"
|
||||
end
|
||||
|
||||
-- register old stackable mob egg
|
||||
minetest.register_craftitem(mob, {
|
||||
|
||||
description = desc,
|
||||
inventory_image = invimg,
|
||||
groups = grp,
|
||||
|
||||
_doc_items_longdesc = S("This allows you to place a single mob."),
|
||||
_doc_items_usagehelp = S("Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns."),
|
||||
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
|
||||
local pos = pointed_thing.above
|
||||
|
||||
-- am I clicking on something with existing on_rightclick function?
|
||||
local under = minetest_get_node(pointed_thing.under)
|
||||
local def = minetest_registered_nodes[under.name]
|
||||
if def and def.on_rightclick then
|
||||
return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
|
||||
end
|
||||
|
||||
if pos
|
||||
--and within_limits(pos, 0)
|
||||
and not minetest.is_protected(pos, placer:get_player_name()) then
|
||||
|
||||
local name = placer:get_player_name()
|
||||
local privs = minetest.get_player_privs(name)
|
||||
if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then
|
||||
if minetest.is_protected(pointed_thing.under, name) then
|
||||
minetest.record_protection_violation(pointed_thing.under, name)
|
||||
return itemstack
|
||||
end
|
||||
if not privs.maphack then
|
||||
minetest.chat_send_player(name, S("You need the “maphack” privilege to change the mob spawner."))
|
||||
return itemstack
|
||||
end
|
||||
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
|
||||
if not mobs.is_creative(name) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
|
||||
if not minetest_registered_entities[mob] then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
if minetest_settings:get_bool("only_peaceful_mobs", false)
|
||||
and minetest_registered_entities[mob].type == "monster" then
|
||||
minetest.chat_send_player(name, S("Only peaceful mobs allowed!"))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local mob = minetest_add_entity(pos, mob)
|
||||
minetest.log("action", "Mob spawned: "..name.." at "..minetest.pos_to_string(pos))
|
||||
local ent = mob:get_luaentity()
|
||||
|
||||
-- don't set owner if monster or sneak pressed
|
||||
--[[
|
||||
if ent.type ~= "monster"
|
||||
and not placer:get_player_control().sneak then
|
||||
ent.owner = placer:get_player_name()
|
||||
ent.tamed = true
|
||||
end
|
||||
]]--
|
||||
|
||||
-- set nametag
|
||||
local nametag = itemstack:get_meta():get_string("name")
|
||||
if nametag ~= "" then
|
||||
if string.len(nametag) > MAX_MOB_NAME_LENGTH then
|
||||
nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
|
||||
end
|
||||
ent.nametag = nametag
|
||||
--update_tag(ent)
|
||||
end
|
||||
|
||||
-- if not in creative then take item
|
||||
if not mobs.is_creative(placer:get_player_name()) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end,
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
local math = math
|
||||
local vector = vector
|
||||
|
||||
local HALF_PI = math.pi/2
|
||||
|
||||
|
||||
local vector_direction = vector.direction
|
||||
local vector_distance = vector.distance
|
||||
local vector_new = vector.new
|
||||
|
||||
local minetest_dir_to_yaw = minetest.dir_to_yaw
|
||||
|
||||
-- set defined animation
|
||||
mobs.set_mob_animation = function(self, anim, fixed_frame)
|
||||
|
||||
if not self.animation or not anim then
|
||||
return
|
||||
end
|
||||
|
||||
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if (not self.animation[anim .. "_start"] or not self.animation[anim .. "_end"]) then
|
||||
return
|
||||
end
|
||||
|
||||
--animations break if they are constantly set
|
||||
--so we put this return gate to check if it is
|
||||
--already at the animation we are trying to implement
|
||||
if self.current_animation == anim then
|
||||
return
|
||||
end
|
||||
|
||||
local a_start = self.animation[anim .. "_start"]
|
||||
local a_end
|
||||
|
||||
if fixed_frame then
|
||||
a_end = a_start
|
||||
else
|
||||
a_end = self.animation[anim .. "_end"]
|
||||
end
|
||||
|
||||
self.object:set_animation({
|
||||
x = a_start,
|
||||
y = a_end},
|
||||
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
|
||||
0, self.animation[anim .. "_loop"] ~= false)
|
||||
|
||||
self.current_animation = anim
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
mobs.death_effect = function(pos, yaw, collisionbox, rotate)
|
||||
local min, max
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
else
|
||||
min = { x = -0.5, y = 0, z = -0.5 }
|
||||
max = { x = 0.5, y = 0.5, z = 0.5 }
|
||||
end
|
||||
if rotate then
|
||||
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
|
||||
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
|
||||
min, max = vector.sort(min, max)
|
||||
min = vector.multiply(min, 0.5)
|
||||
max = vector.multiply(max, 0.5)
|
||||
end
|
||||
|
||||
minetest.add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector_new(-5,-5,-5),
|
||||
maxvel = vector_new(5,5,5),
|
||||
minexptime = 1.1,
|
||||
maxexptime = 1.5,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_mob_death.png^[colorize:#000000:255",
|
||||
})
|
||||
|
||||
minetest.sound_play("mcl_mobs_mob_poof", {
|
||||
pos = pos,
|
||||
gain = 1.0,
|
||||
max_hear_distance = 8,
|
||||
}, true)
|
||||
end
|
||||
|
||||
|
||||
--this allows auto facedir rotation while making it so mobs
|
||||
--don't look like wet noodles flopping around
|
||||
mobs.movement_rotation_lock = function(self)
|
||||
local current_engine_yaw = self.object:get_yaw()
|
||||
local current_lua_yaw = self.yaw
|
||||
|
||||
if current_engine_yaw > math.pi * 2 then
|
||||
current_engine_yaw = current_engine_yaw - (math.pi * 2)
|
||||
end
|
||||
|
||||
if math.abs(current_engine_yaw - current_lua_yaw) <= 0.05 and self.object:get_properties().automatic_face_movement_dir then
|
||||
self.object:set_properties{automatic_face_movement_dir = false}
|
||||
elseif math.abs(current_engine_yaw - current_lua_yaw) > 0.05 and self.object:get_properties().automatic_face_movement_dir == false then
|
||||
self.object:set_properties{automatic_face_movement_dir = self.rotate}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--this is used when a mob is chasing a player
|
||||
mobs.set_yaw_while_attacking = function(self)
|
||||
|
||||
if self.object:get_properties().automatic_face_movement_dir then
|
||||
self.object:set_properties{automatic_face_movement_dir = false}
|
||||
end
|
||||
|
||||
--turn positions into pseudo 2d vectors
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
|
||||
local pos2 = self.attacking:get_pos()
|
||||
pos2.y = 0
|
||||
|
||||
local new_direction = vector_direction(pos1,pos2)
|
||||
local new_yaw = minetest_dir_to_yaw(new_direction)
|
||||
|
||||
self.object:set_yaw(new_yaw)
|
||||
self.yaw = new_yaw
|
||||
end
|
||||
|
||||
--this is used to unlock a mob's yaw after attacking
|
||||
mobs.unlock_yaw = function(self)
|
||||
if self.object:get_properties().automatic_face_movement_dir == false then
|
||||
self.object:set_properties{automatic_face_movement_dir = self.rotate}
|
||||
end
|
||||
end
|
||||
|
||||
--this is used to lock a mob's yaw when they're standing
|
||||
mobs.lock_yaw = function(self)
|
||||
if self.object:get_properties().automatic_face_movement_dir then
|
||||
self.object:set_properties{automatic_face_movement_dir = false}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local calculate_pitch = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
local pos2 = self.old_pos
|
||||
|
||||
if pos == nil or pos2 == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return minetest_dir_to_yaw(vector_new(vector_distance(vector_new(pos.x,0,pos.z),vector_new(pos2.x,0,pos2.z)),0,pos.y - pos2.y)) + HALF_PI
|
||||
end
|
||||
|
||||
--this is a helper function used to make mobs pitch rotation dynamically flow when flying/swimming
|
||||
mobs.set_dynamic_pitch = function(self)
|
||||
local pitch = calculate_pitch(self)
|
||||
|
||||
if not pitch then
|
||||
return
|
||||
end
|
||||
|
||||
local current_rotation = self.object:get_rotation()
|
||||
|
||||
current_rotation.x = pitch
|
||||
|
||||
self.object:set_rotation(current_rotation)
|
||||
|
||||
self.pitch_switch = "dynamic"
|
||||
end
|
||||
|
||||
--this is a helper function used to make mobs pitch rotation reset when flying/swimming
|
||||
mobs.set_static_pitch = function(self)
|
||||
|
||||
if self.pitch_switch == "static" then
|
||||
return
|
||||
end
|
||||
|
||||
local current_rotation = self.object:get_rotation()
|
||||
|
||||
current_rotation.x = 0
|
||||
|
||||
self.object:set_rotation(current_rotation)
|
||||
self.pitch_switch = "static"
|
||||
end
|
||||
|
||||
--this is a helper function for mobs explosion animation
|
||||
mobs.handle_explosion_animation = function(self)
|
||||
|
||||
--secondary catch-all
|
||||
if not self.explosion_animation then
|
||||
self.explosion_animation = 0
|
||||
end
|
||||
|
||||
--the timer works from 0 for sense of a 0 based counting
|
||||
--but this just bumps it up so it's usable in here
|
||||
local explosion_timer_adjust = self.explosion_animation + 1
|
||||
|
||||
|
||||
local visual_size_modified = table.copy(self.visual_size_origin)
|
||||
|
||||
visual_size_modified.x = visual_size_modified.x * (explosion_timer_adjust ^ 3)
|
||||
visual_size_modified.y = visual_size_modified.y * explosion_timer_adjust
|
||||
|
||||
self.object:set_properties({visual_size = visual_size_modified})
|
||||
end
|
||||
|
||||
|
||||
--this is used when a mob is following player
|
||||
mobs.set_yaw_while_following = function(self)
|
||||
|
||||
if self.object:get_properties().automatic_face_movement_dir then
|
||||
self.object:set_properties{automatic_face_movement_dir = false}
|
||||
end
|
||||
|
||||
--turn positions into pseudo 2d vectors
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
|
||||
local pos2 = self.following_person:get_pos()
|
||||
pos2.y = 0
|
||||
|
||||
local new_direction = vector_direction(pos1,pos2)
|
||||
local new_yaw = minetest_dir_to_yaw(new_direction)
|
||||
|
||||
self.object:set_yaw(new_yaw)
|
||||
self.yaw = new_yaw
|
||||
end
|
||||
|
||||
--this is used for when mobs breed
|
||||
mobs.set_yaw_while_breeding = function(self, mate)
|
||||
|
||||
if self.object:get_properties().automatic_face_movement_dir then
|
||||
self.object:set_properties{automatic_face_movement_dir = false}
|
||||
end
|
||||
|
||||
--turn positions into pseudo 2d vectors
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
|
||||
local pos2 = mate:get_pos()
|
||||
pos2.y = 0
|
||||
|
||||
local new_direction = vector_direction(pos1,pos2)
|
||||
local new_yaw = minetest_dir_to_yaw(new_direction)
|
||||
|
||||
self.object:set_yaw(new_yaw)
|
||||
self.yaw = new_yaw
|
||||
end
|
|
@ -0,0 +1,347 @@
|
|||
local vector_direction = vector.direction
|
||||
--local minetest_dir_to_yaw = minetest.dir_to_yaw
|
||||
local vector_distance = vector.distance
|
||||
local vector_multiply = vector.multiply
|
||||
local math_random = math.random
|
||||
|
||||
--[[
|
||||
_ _ _ _
|
||||
| | | | | | | |
|
||||
| | | | __ _ _ __ __| | | |
|
||||
| | | | / _` | '_ \ / _` | | |
|
||||
|_| | |___| (_| | | | | (_| | |_|
|
||||
(_) \_____/\__,_|_| |_|\__,_| (_)
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
_____ _ _
|
||||
| ___| | | | |
|
||||
| |____ ___ __ | | ___ __| | ___
|
||||
| __\ \/ / '_ \| |/ _ \ / _` |/ _ \
|
||||
| |___> <| |_) | | (_) | (_| | __/
|
||||
\____/_/\_\ .__/|_|\___/ \__,_|\___|
|
||||
| |
|
||||
|_|
|
||||
]]--
|
||||
|
||||
mobs.explode_attack_walk = function(self,dtime)
|
||||
|
||||
--this needs an exception
|
||||
if self.attacking == nil or not self.attacking:is_player() then
|
||||
self.attacking = nil
|
||||
return
|
||||
end
|
||||
|
||||
mobs.set_yaw_while_attacking(self)
|
||||
|
||||
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
|
||||
|
||||
--make mob walk up to player within 2 nodes distance then start exploding
|
||||
if distance_from_attacking >= self.reach and
|
||||
--don't allow explosion to cancel unless out of the reach boundary
|
||||
not (self.explosion_animation and self.explosion_animation > 0 and distance_from_attacking <= self.defuse_reach) then
|
||||
|
||||
mobs.set_velocity(self, self.run_velocity)
|
||||
mobs.set_mob_animation(self,"run")
|
||||
|
||||
mobs.reverse_explosion_animation(self,dtime)
|
||||
else
|
||||
mobs.set_velocity(self,0)
|
||||
|
||||
--this is the only way I can reference this without dumping extra data on all mobs
|
||||
if not self.explosion_animation then
|
||||
self.explosion_animation = 0
|
||||
end
|
||||
|
||||
--play ignite sound
|
||||
if self.explosion_animation == 0 then
|
||||
mobs.play_sound(self,"attack")
|
||||
end
|
||||
|
||||
mobs.set_mob_animation(self,"stand")
|
||||
|
||||
mobs.handle_explosion_animation(self)
|
||||
|
||||
self.explosion_animation = self.explosion_animation + (dtime/2.5)
|
||||
end
|
||||
|
||||
--make explosive mobs jump
|
||||
--check for nodes to jump over
|
||||
--explosive mobs will just ride against walls for now
|
||||
local node_in_front_of = mobs.jump_check(self)
|
||||
if node_in_front_of == 1 then
|
||||
mobs.jump(self)
|
||||
end
|
||||
|
||||
--do biggening explosion thing
|
||||
if self.explosion_animation and self.explosion_animation > self.explosion_timer then
|
||||
mcl_explosions.explode(self.object:get_pos(), self.explosion_strength,{ drop_chance = 1.0 })
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--this is a small helper function to make working with explosion animations easier
|
||||
mobs.reverse_explosion_animation = function(self,dtime)
|
||||
--if explosion animation was greater than 0 then reverse it
|
||||
if self.explosion_animation and self.explosion_animation > 0 then
|
||||
self.explosion_animation = self.explosion_animation - dtime
|
||||
if self.explosion_animation < 0 then
|
||||
self.explosion_animation = 0
|
||||
end
|
||||
end
|
||||
|
||||
mobs.handle_explosion_animation(self)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
______ _
|
||||
| ___ \ | |
|
||||
| |_/ / _ _ __ ___| |__
|
||||
| __/ | | | '_ \ / __| '_ \
|
||||
| | | |_| | | | | (__| | | |
|
||||
\_| \__,_|_| |_|\___|_| |_|
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
mobs.punch_attack_walk = function(self,dtime)
|
||||
--this needs an exception
|
||||
if self.attacking == nil or not self.attacking:is_player() then
|
||||
self.attacking = nil
|
||||
return
|
||||
end
|
||||
|
||||
local distance_from_attacking = mobs.get_2d_distance(self.object:get_pos(), self.attacking:get_pos())
|
||||
|
||||
if distance_from_attacking >= self.minimum_follow_distance then
|
||||
mobs.set_velocity(self, self.run_velocity)
|
||||
mobs.set_mob_animation(self, "run")
|
||||
else
|
||||
mobs.set_velocity(self, 0)
|
||||
mobs.set_mob_animation(self, "stand")
|
||||
end
|
||||
|
||||
mobs.set_yaw_while_attacking(self)
|
||||
|
||||
--make punchy mobs jump
|
||||
--check for nodes to jump over
|
||||
--explosive mobs will just ride against walls for now
|
||||
local node_in_front_of = mobs.jump_check(self)
|
||||
|
||||
if node_in_front_of == 1 then
|
||||
mobs.jump(self)
|
||||
end
|
||||
|
||||
--mobs that can climb over stuff
|
||||
if self.always_climb and node_in_front_of > 0 then
|
||||
mobs.climb(self)
|
||||
end
|
||||
|
||||
|
||||
--auto reset punch_timer
|
||||
if not self.punch_timer then
|
||||
self.punch_timer = 0
|
||||
end
|
||||
|
||||
if self.punch_timer > 0 then
|
||||
self.punch_timer = self.punch_timer - dtime
|
||||
end
|
||||
end
|
||||
|
||||
mobs.punch_attack = function(self)
|
||||
|
||||
self.attacking:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self.damage}
|
||||
}, nil)
|
||||
|
||||
self.punch_timer = self.punch_timer_cooloff
|
||||
|
||||
|
||||
--knockback
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
local pos2 = self.attacking:get_pos()
|
||||
pos2.y = 0
|
||||
local dir = vector_direction(pos1,pos2)
|
||||
|
||||
dir = vector_multiply(dir,3)
|
||||
|
||||
if self.attacking:get_velocity().y <= 1 then
|
||||
dir.y = 5
|
||||
end
|
||||
|
||||
self.attacking:add_velocity(dir)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
______ _ _ _ _
|
||||
| ___ \ (_) | | (_) |
|
||||
| |_/ / __ ___ _ ___ ___| |_ _| | ___
|
||||
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
|
||||
| | | | | (_) | | __/ (__| |_| | | __/
|
||||
\_| |_| \___/| |\___|\___|\__|_|_|\___|
|
||||
_/ |
|
||||
|__/
|
||||
]]--
|
||||
|
||||
|
||||
mobs.projectile_attack_walk = function(self,dtime)
|
||||
|
||||
--this needs an exception
|
||||
if self.attacking == nil or not self.attacking:is_player() then
|
||||
self.attacking = nil
|
||||
return
|
||||
end
|
||||
|
||||
mobs.set_yaw_while_attacking(self)
|
||||
|
||||
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
|
||||
|
||||
|
||||
if distance_from_attacking >= self.reach then
|
||||
mobs.set_velocity(self, self.run_velocity)
|
||||
mobs.set_mob_animation(self,"run")
|
||||
else
|
||||
mobs.set_velocity(self,0)
|
||||
mobs.set_mob_animation(self,"stand")
|
||||
end
|
||||
|
||||
--do this to not load data into other mobs
|
||||
if not self.projectile_timer then
|
||||
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
|
||||
end
|
||||
|
||||
--run projectile timer
|
||||
if self.projectile_timer > 0 then
|
||||
self.projectile_timer = self.projectile_timer - dtime
|
||||
|
||||
--shoot
|
||||
if self.projectile_timer <= 0 then
|
||||
--reset timer
|
||||
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
|
||||
mobs.shoot_projectile(self)
|
||||
end
|
||||
end
|
||||
|
||||
--make shooty mobs jump
|
||||
--check for nodes to jump over
|
||||
--explosive mobs will just ride against walls for now
|
||||
local node_in_front_of = mobs.jump_check(self)
|
||||
if node_in_front_of == 1 then
|
||||
mobs.jump(self)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
_ ______ _ _
|
||||
| | | ___| | | |
|
||||
| | | |_ | |_ _ | |
|
||||
| | | _| | | | | | | |
|
||||
|_| | | | | |_| | |_|
|
||||
(_) \_| |_|\__, | (_)
|
||||
__/ |
|
||||
|___/
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
______ _ _ _ _
|
||||
| ___ \ (_) | | (_) |
|
||||
| |_/ / __ ___ _ ___ ___| |_ _| | ___
|
||||
| __/ '__/ _ \| |/ _ \/ __| __| | |/ _ \
|
||||
| | | | | (_) | | __/ (__| |_| | | __/
|
||||
\_| |_| \___/| |\___|\___|\__|_|_|\___|
|
||||
_/ |
|
||||
|__/
|
||||
]]--
|
||||
|
||||
local random_pitch_multiplier = {-1,1}
|
||||
|
||||
mobs.projectile_attack_fly = function(self, dtime)
|
||||
--this needs an exception
|
||||
if self.attacking == nil or not self.attacking:is_player() then
|
||||
self.attacking = nil
|
||||
return
|
||||
end
|
||||
|
||||
--this is specifically for random ghast movement
|
||||
if self.fly_random_while_attack then
|
||||
|
||||
--enable rotation locking
|
||||
mobs.movement_rotation_lock(self)
|
||||
|
||||
self.walk_timer = self.walk_timer - dtime
|
||||
|
||||
--reset the walk timer
|
||||
if self.walk_timer <= 0 then
|
||||
--re-randomize the walk timer
|
||||
self.walk_timer = math.random(1,6) + math.random()
|
||||
--set the mob into a random direction
|
||||
self.yaw = (math_random() * (math.pi * 2))
|
||||
--create a truly random pitch, since there is no easy access to pitch math that I can find
|
||||
self.pitch = math_random() * math.random(1,3) * random_pitch_multiplier[math_random(1,2)]
|
||||
end
|
||||
|
||||
mobs.set_fly_velocity(self, self.run_velocity)
|
||||
|
||||
else
|
||||
|
||||
mobs.set_yaw_while_attacking(self)
|
||||
|
||||
local distance_from_attacking = vector_distance(self.object:get_pos(), self.attacking:get_pos())
|
||||
|
||||
if distance_from_attacking >= self.reach then
|
||||
mobs.set_pitch_while_attacking(self)
|
||||
mobs.set_fly_velocity(self, self.run_velocity)
|
||||
mobs.set_mob_animation(self,"run")
|
||||
else
|
||||
mobs.set_pitch_while_attacking(self)
|
||||
mobs.set_fly_velocity(self, 0)
|
||||
mobs.set_mob_animation(self,"stand")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--do this to not load data into other mobs
|
||||
if not self.projectile_timer then
|
||||
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
|
||||
end
|
||||
|
||||
--run projectile timer
|
||||
if self.projectile_timer > 0 then
|
||||
self.projectile_timer = self.projectile_timer - dtime
|
||||
|
||||
--shoot
|
||||
if self.projectile_timer <= 0 then
|
||||
|
||||
if self.fly_random_while_attack then
|
||||
mobs.set_yaw_while_attacking(self)
|
||||
self.walk_timer = 0
|
||||
end
|
||||
--reset timer
|
||||
self.projectile_timer = math_random(self.projectile_cooldown_min, self.projectile_cooldown_max)
|
||||
mobs.shoot_projectile(self)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,179 @@
|
|||
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
|
||||
|
||||
local vector = vector
|
||||
|
||||
--check to see if someone nearby has some tasty food
|
||||
mobs.check_following = function(self) -- returns true or false
|
||||
--ignore
|
||||
if not self.follow then
|
||||
self.following_person = nil
|
||||
return false
|
||||
end
|
||||
|
||||
--hey look, this thing works for passive mobs too!
|
||||
local follower = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height)
|
||||
|
||||
--check if the follower is a player incase they log out
|
||||
if follower and follower:is_player() then
|
||||
local stack = follower:get_wielded_item()
|
||||
--safety check
|
||||
if not stack then
|
||||
self.following_person = nil
|
||||
return false
|
||||
end
|
||||
|
||||
local item_name = stack:get_name()
|
||||
--all checks have passed, that guy has some good looking food
|
||||
if item_name == self.follow then
|
||||
self.following_person = follower
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--everything failed
|
||||
self.following_person = nil
|
||||
return false
|
||||
end
|
||||
|
||||
--a function which attempts to make mobs enter
|
||||
--the breeding state
|
||||
mobs.enter_breed_state = function(self,clicker)
|
||||
|
||||
--do not breed if baby
|
||||
if self.baby then
|
||||
return false
|
||||
end
|
||||
|
||||
--do not do anything if looking for mate or
|
||||
--if cooling off from breeding
|
||||
if self.breed_lookout_timer > 0 or self.breed_timer > 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
--if this is caught, that means something has gone
|
||||
--seriously wrong
|
||||
if not clicker or not clicker:is_player() then
|
||||
return false
|
||||
end
|
||||
|
||||
local stack = clicker:get_wielded_item()
|
||||
--safety check
|
||||
if not stack then
|
||||
return false
|
||||
end
|
||||
|
||||
local item_name = stack:get_name()
|
||||
--all checks have passed, that guy has some good looking food
|
||||
if item_name == self.follow then
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
stack:take_item()
|
||||
clicker:set_wielded_item(stack)
|
||||
end
|
||||
self.breed_lookout_timer = self.breed_lookout_timer_goal
|
||||
self.bred = true
|
||||
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
|
||||
return true
|
||||
end
|
||||
|
||||
--everything failed
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--find the closest mate in the area
|
||||
mobs.look_for_mate = function(self)
|
||||
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = pos1.y + self.eye_height
|
||||
|
||||
local mates_in_area = {}
|
||||
local winner_mate = nil
|
||||
local mates_detected = 0
|
||||
local radius = self.view_range
|
||||
|
||||
--get mates in radius
|
||||
for _,mate in pairs(minetest_get_objects_inside_radius(pos1, radius)) do
|
||||
|
||||
--look for a breeding mate
|
||||
if mate and mate:get_luaentity()
|
||||
and mate:get_luaentity()._cmi_is_mob
|
||||
and mate:get_luaentity().name == self.name
|
||||
and mate:get_luaentity().breed_lookout_timer > 0
|
||||
and mate:get_luaentity() ~= self then
|
||||
|
||||
local pos2 = mate:get_pos()
|
||||
|
||||
local distance = vector.distance(pos1,pos2)
|
||||
|
||||
if distance <= radius then
|
||||
if minetest.line_of_sight then
|
||||
--must add eye height or stuff breaks randomly because of
|
||||
--seethrough nodes being a blocker (like grass)
|
||||
if minetest.line_of_sight(
|
||||
vector.new(pos1.x, pos1.y, pos1.z),
|
||||
vector.new(pos2.x, pos2.y + mate:get_properties().eye_height, pos2.z)
|
||||
) then
|
||||
mates_detected = mates_detected + 1
|
||||
mates_in_area[mate] = distance
|
||||
end
|
||||
else
|
||||
mates_detected = mates_detected + 1
|
||||
mates_in_area[mate] = distance
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--return if there's no one near by
|
||||
if mates_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
|
||||
return nil
|
||||
end
|
||||
|
||||
--do a default radius max
|
||||
local shortest_distance = radius + 1
|
||||
|
||||
--sort through mates and find the closest mate
|
||||
for mate,distance in pairs(mates_in_area) do
|
||||
if distance < shortest_distance then
|
||||
shortest_distance = distance
|
||||
winner_mate = mate
|
||||
end
|
||||
end
|
||||
return winner_mate
|
||||
end
|
||||
|
||||
--make the baby grow up
|
||||
mobs.baby_grow_up = function(self)
|
||||
self.baby = nil
|
||||
self.visual_size = self.backup_visual_size
|
||||
self.collisionbox = self.backup_collisionbox
|
||||
self.selectionbox = self.backup_selectionbox
|
||||
self.object:set_properties(self)
|
||||
end
|
||||
|
||||
--makes the baby grow up faster with diminishing returns
|
||||
mobs.make_baby_grow_faster = function(self,clicker)
|
||||
if clicker and clicker:is_player() then
|
||||
local stack = clicker:get_wielded_item()
|
||||
--safety check
|
||||
if not stack then
|
||||
return false
|
||||
end
|
||||
|
||||
local item_name = stack:get_name()
|
||||
--all checks have passed, that guy has some good looking food
|
||||
if item_name == self.follow then
|
||||
self.grow_up_timer = self.grow_up_timer - (self.grow_up_timer * 0.10) --take 10 percent off - diminishing returns
|
||||
|
||||
if not minetest.is_creative_enabled(clicker:get_player_name()) then
|
||||
stack:take_item()
|
||||
clicker:set_wielded_item(stack)
|
||||
end
|
||||
|
||||
mobs.play_sound_specific(self,"mobs_mc_animal_eat_generic")
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
|
@ -0,0 +1,135 @@
|
|||
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
|
||||
|
||||
local math_random = math.random
|
||||
local vector_multiply = vector.multiply
|
||||
|
||||
local vector_direction = vector.direction
|
||||
|
||||
local integer_test = {-1,1}
|
||||
|
||||
mobs.collision = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if not self or not self.object or not self.object:get_luaentity() then
|
||||
return
|
||||
end
|
||||
|
||||
--do collision detection from the base of the mob
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
pos.y = pos.y + collisionbox[2]
|
||||
|
||||
local collision_boundary = collisionbox[4]
|
||||
|
||||
local radius = collision_boundary
|
||||
|
||||
if collisionbox[5] > collision_boundary then
|
||||
radius = collisionbox[5]
|
||||
end
|
||||
|
||||
local collision_count = 0
|
||||
|
||||
|
||||
local check_for_attack = false
|
||||
|
||||
if self.attack_type == "punch" and self.hostile and self.attacking then
|
||||
check_for_attack = true
|
||||
end
|
||||
|
||||
for _,object in ipairs(minetest_get_objects_inside_radius(pos, radius*1.25)) do
|
||||
if object and object ~= self.object and (object:is_player() or (object:get_luaentity() and object:get_luaentity()._cmi_is_mob == true and object:get_luaentity().health > 0)) and
|
||||
--don't collide with rider, rider don't collide with thing
|
||||
(not object:get_attach() or (object:get_attach() and object:get_attach() ~= self.object)) and
|
||||
(not self.object:get_attach() or (self.object:get_attach() and self.object:get_attach() ~= object)) then
|
||||
--stop infinite loop
|
||||
collision_count = collision_count + 1
|
||||
--mob cramming
|
||||
if collision_count > 30 then
|
||||
self.health = -20
|
||||
break
|
||||
end
|
||||
|
||||
local pos2 = object:get_pos()
|
||||
|
||||
local object_collisionbox = object:get_properties().collisionbox
|
||||
|
||||
pos2.y = pos2.y + object_collisionbox[2]
|
||||
|
||||
local object_collision_boundary = object_collisionbox[4]
|
||||
|
||||
|
||||
--this is checking the difference of the object collided with's possision
|
||||
--if positive top of other object is inside (y axis) of current object
|
||||
local y_base_diff = (pos2.y + object_collisionbox[5]) - pos.y
|
||||
|
||||
local y_top_diff = (pos.y + collisionbox[5]) - pos2.y
|
||||
|
||||
|
||||
local distance = vector.distance(vector.new(pos.x,0,pos.z),vector.new(pos2.x,0,pos2.z))
|
||||
|
||||
if distance <= collision_boundary + object_collision_boundary and y_base_diff >= 0 and y_top_diff >= 0 then
|
||||
|
||||
local dir = vector.direction(pos,pos2)
|
||||
|
||||
dir.y = 0
|
||||
|
||||
--eliminate mob being stuck in corners
|
||||
if dir.x == 0 and dir.z == 0 then
|
||||
--slightly adjust mob position to prevent equal length
|
||||
--corner/wall sticking
|
||||
dir.x = dir.x + ((math_random()/10)*integer_test[math.random(1,2)])
|
||||
dir.z = dir.z + ((math_random()/10)*integer_test[math.random(1,2)])
|
||||
end
|
||||
|
||||
local velocity = dir
|
||||
|
||||
--0.5 is the max force multiplier
|
||||
local force = 0.5 - (0.5 * distance / (collision_boundary + object_collision_boundary))
|
||||
|
||||
local vel1 = vector.multiply(velocity, -1.5)
|
||||
local vel2 = vector.multiply(velocity, 1.5)
|
||||
|
||||
vel1 = vector.multiply(vel1, force * 10)
|
||||
vel2 = vector.multiply(vel2, force)
|
||||
|
||||
if object:is_player() then
|
||||
vel2 = vector_multiply(vel2, 2.5)
|
||||
|
||||
--integrate mob punching into collision detection
|
||||
if check_for_attack and self.punch_timer <= 0 then
|
||||
if object == self.attacking then
|
||||
mobs.punch_attack(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.object:add_velocity(vel1)
|
||||
object:add_velocity(vel2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--this is used for arrow collisions
|
||||
mobs.arrow_hit = function(self, player)
|
||||
player:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self._damage}
|
||||
}, nil)
|
||||
|
||||
|
||||
--knockback
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
local pos2 = player:get_pos()
|
||||
pos2.y = 0
|
||||
local dir = vector_direction(pos1,pos2)
|
||||
|
||||
dir = vector_multiply(dir,3)
|
||||
|
||||
if player:get_velocity().y <= 1 then
|
||||
dir.y = 5
|
||||
end
|
||||
|
||||
player:add_velocity(dir)
|
||||
end
|
|
@ -0,0 +1,161 @@
|
|||
local minetest_add_item = minetest.add_item
|
||||
--local minetest_sound_play = minetest.sound_play
|
||||
|
||||
local math_pi = math.pi
|
||||
local math_random = math.random
|
||||
local math_floor = math.floor
|
||||
local HALF_PI = math_pi / 2
|
||||
|
||||
local vector_new = vector.new
|
||||
|
||||
|
||||
-- drop items
|
||||
local item_drop = function(self, cooked, looting_level)
|
||||
|
||||
looting_level = looting_level or 0
|
||||
|
||||
-- no drops for child mobs (except monster)
|
||||
if (self.child and self.type ~= "monster") then
|
||||
return
|
||||
end
|
||||
|
||||
local obj, item
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
self.drops = self.drops or {} -- nil check
|
||||
|
||||
for n = 1, #self.drops do
|
||||
local dropdef = self.drops[n]
|
||||
local chance = 1 / dropdef.chance
|
||||
local looting_type = dropdef.looting
|
||||
|
||||
if looting_level > 0 then
|
||||
local chance_function = dropdef.looting_chance_function
|
||||
if chance_function then
|
||||
chance = chance_function(looting_level)
|
||||
elseif looting_type == "rare" then
|
||||
chance = chance + (dropdef.looting_factor or 0.01) * looting_level
|
||||
end
|
||||
end
|
||||
|
||||
local num = 0
|
||||
local do_common_looting = (looting_level > 0 and looting_type == "common")
|
||||
if math_random() < chance then
|
||||
num = math_random(dropdef.min or 1, dropdef.max or 1)
|
||||
elseif not dropdef.looting_ignore_chance then
|
||||
do_common_looting = false
|
||||
end
|
||||
|
||||
if do_common_looting then
|
||||
num = num + math_floor(math_random(0, looting_level) + 0.5)
|
||||
end
|
||||
|
||||
if num > 0 then
|
||||
item = dropdef.name
|
||||
|
||||
-- cook items when true
|
||||
if cooked then
|
||||
|
||||
local output = minetest.get_craft_result({
|
||||
method = "cooking",
|
||||
width = 1,
|
||||
items = {item},
|
||||
})
|
||||
|
||||
if output and output.item and not output.item:is_empty() then
|
||||
item = output.item:get_name()
|
||||
end
|
||||
end
|
||||
|
||||
-- add item if it exists
|
||||
for x = 1, num do
|
||||
obj = minetest_add_item(pos, ItemStack(item .. " " .. 1))
|
||||
end
|
||||
|
||||
if obj and obj:get_luaentity() then
|
||||
|
||||
obj:set_velocity({
|
||||
x = math_random(-10, 10) / 9,
|
||||
y = 6,
|
||||
z = math_random(-10, 10) / 9,
|
||||
})
|
||||
elseif obj then
|
||||
obj:remove() -- item does not exist
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.drops = {}
|
||||
end
|
||||
|
||||
|
||||
mobs.death_logic = function(self, dtime)
|
||||
|
||||
--stop crashing game when object is nil
|
||||
if not self or not self.object or not self.object:get_luaentity() then
|
||||
return
|
||||
end
|
||||
|
||||
self.death_animation_timer = self.death_animation_timer + dtime
|
||||
|
||||
--get all attached entities and sort through them
|
||||
local attached_entities = self.object:get_children()
|
||||
if #attached_entities > 0 then
|
||||
for _,entity in pairs(attached_entities) do
|
||||
--kick the player off
|
||||
if entity:is_player() then
|
||||
mobs.detach(entity)
|
||||
--kick mobs off
|
||||
--if there is scaling issues, this needs an additional check
|
||||
else
|
||||
entity:set_detach()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--stop mob from getting in the way of other mobs you're fighting
|
||||
if self.object:get_properties().pointable then
|
||||
self.object:set_properties({pointable = false})
|
||||
end
|
||||
|
||||
--the final POOF of a mob despawning
|
||||
if self.death_animation_timer >= 1.25 then
|
||||
item_drop(self,false,1)
|
||||
mobs.death_effect(self)
|
||||
mcl_experience.throw_xp(self.object:get_pos(), math_random(self.xp_min, self.xp_max))
|
||||
if self.on_die then
|
||||
self.on_die(self, self.object:get_pos())
|
||||
end
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
--I'm sure there's a more efficient way to do this
|
||||
--but this is the easiest, easier to work with 1 variable synced
|
||||
--this is also not smooth
|
||||
local death_animation_roll = self.death_animation_timer * 2 -- * 2 to make it faster
|
||||
if death_animation_roll > 1 then
|
||||
death_animation_roll = 1
|
||||
end
|
||||
|
||||
local rot = self.object:get_rotation() --(no pun intended)
|
||||
|
||||
rot.z = death_animation_roll * HALF_PI
|
||||
|
||||
self.object:set_rotation(rot)
|
||||
|
||||
mobs.set_mob_animation(self,"stand", true)
|
||||
|
||||
|
||||
--flying and swimming mobs just fall down
|
||||
if self.fly or self.swim then
|
||||
if self.object:get_acceleration().y ~= -self.gravity then
|
||||
self.object:set_acceleration(vector_new(0,-self.gravity,0))
|
||||
end
|
||||
end
|
||||
|
||||
--when landing allow mob to slow down and just fall if in air
|
||||
if self.pause_timer <= 0 then
|
||||
mobs.set_velocity(self,0)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,250 @@
|
|||
local minetest_line_of_sight = minetest.line_of_sight
|
||||
--local minetest_dir_to_yaw = minetest.dir_to_yaw
|
||||
local minetest_yaw_to_dir = minetest.yaw_to_dir
|
||||
local minetest_get_node = minetest.get_node
|
||||
local minetest_get_item_group = minetest.get_item_group
|
||||
local minetest_get_objects_inside_radius = minetest.get_objects_inside_radius
|
||||
local minetest_get_node_or_nil = minetest.get_node_or_nil
|
||||
local minetest_registered_nodes = minetest.registered_nodes
|
||||
local minetest_get_connected_players = minetest.get_connected_players
|
||||
|
||||
local vector_new = vector.new
|
||||
local vector_add = vector.add
|
||||
local vector_multiply = vector.multiply
|
||||
local vector_distance = vector.distance
|
||||
|
||||
local table_copy = table.copy
|
||||
|
||||
local math_abs = math.abs
|
||||
|
||||
-- default function when mobs are blown up with TNT
|
||||
--[[local function do_tnt(obj, damage)
|
||||
obj.object:punch(obj.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = damage},
|
||||
}, nil)
|
||||
return false, true, {}
|
||||
end]]
|
||||
|
||||
--a fast function to be able to detect only players without using objects_in_radius
|
||||
mobs.detect_closest_player_within_radius = function(self, line_of_sight, radius, object_height_adder)
|
||||
local pos1 = self.object:get_pos()
|
||||
local players_in_area = {}
|
||||
local winner_player = nil
|
||||
local players_detected = 0
|
||||
|
||||
--get players in radius
|
||||
for _,player in pairs(minetest.get_connected_players()) do
|
||||
if player and player:get_hp() > 0 then
|
||||
|
||||
local pos2 = player:get_pos()
|
||||
|
||||
local distance = vector_distance(pos1,pos2)
|
||||
|
||||
if distance <= radius then
|
||||
if line_of_sight then
|
||||
--must add eye height or stuff breaks randomly because of
|
||||
--seethrough nodes being a blocker (like grass)
|
||||
if minetest_line_of_sight(
|
||||
vector_new(pos1.x, pos1.y + object_height_adder, pos1.z),
|
||||
vector_new(pos2.x, pos2.y + player:get_properties().eye_height, pos2.z)
|
||||
) then
|
||||
players_detected = players_detected + 1
|
||||
players_in_area[player] = distance
|
||||
end
|
||||
else
|
||||
players_detected = players_detected + 1
|
||||
players_in_area[player] = distance
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--return if there's no one near by
|
||||
if players_detected <= 0 then --handle negative numbers for some crazy error that could possibly happen
|
||||
return nil
|
||||
end
|
||||
|
||||
--do a default radius max
|
||||
local shortest_distance = radius + 1
|
||||
|
||||
--sort through players and find the closest player
|
||||
for player,distance in pairs(players_in_area) do
|
||||
if distance < shortest_distance then
|
||||
shortest_distance = distance
|
||||
winner_player = player
|
||||
end
|
||||
end
|
||||
return winner_player
|
||||
end
|
||||
|
||||
|
||||
--check if a mob needs to jump
|
||||
mobs.jump_check = function(self,dtime)
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = pos.y + 0.1
|
||||
local dir = minetest_yaw_to_dir(self.yaw)
|
||||
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
local radius = collisionbox[4] + 0.5
|
||||
|
||||
vector_multiply(dir, radius)
|
||||
|
||||
--only jump if there's a node and a non-solid node above it
|
||||
local test_dir = vector_add(pos,dir)
|
||||
|
||||
local green_flag_1 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") ~= 0
|
||||
|
||||
test_dir.y = test_dir.y + 1
|
||||
|
||||
local green_flag_2 = minetest_get_item_group(minetest_get_node(test_dir).name, "solid") == 0
|
||||
|
||||
if green_flag_1 and green_flag_2 then
|
||||
--can jump over node
|
||||
return 1
|
||||
elseif green_flag_1 and not green_flag_2 then
|
||||
--wall in front of mob
|
||||
return 2
|
||||
end
|
||||
--nothing to jump over
|
||||
return 0
|
||||
end
|
||||
|
||||
-- a helper function to quickly turn neutral passive mobs hostile
|
||||
local turn_hostile = function(self,detected_mob)
|
||||
--drop in variables for attacking (stops crash)
|
||||
detected_mob.punch_timer = 0
|
||||
--set to hostile
|
||||
detected_mob.hostile = true
|
||||
--hostile_cooldown timer is initialized here
|
||||
detected_mob.hostile_cooldown_timer = detected_mob.hostile_cooldown
|
||||
--set target to the same
|
||||
detected_mob.attacking = self.attacking
|
||||
end
|
||||
|
||||
--allow hostile mobs to signal to other mobs
|
||||
--to switch from neutal passive to neutral hostile
|
||||
mobs.group_attack_initialization = function(self)
|
||||
|
||||
--get basic data
|
||||
local friends_list
|
||||
|
||||
if self.group_attack == true then
|
||||
friends_list = {self.name}
|
||||
else
|
||||
friends_list = table_copy(self.group_attack)
|
||||
end
|
||||
|
||||
local objects_in_area = minetest_get_objects_inside_radius(self.object:get_pos(), self.view_range)
|
||||
|
||||
--get the player's name
|
||||
local name = self.attacking:get_player_name()
|
||||
|
||||
--re-use local variable
|
||||
local detected_mob
|
||||
|
||||
--run through mobs in viewing distance
|
||||
for _,object in pairs(objects_in_area) do
|
||||
if object and object:get_luaentity() then
|
||||
detected_mob = object:get_luaentity()
|
||||
-- only alert members of same mob or friends
|
||||
if detected_mob._cmi_is_mob and detected_mob.state ~= "attack" and detected_mob.owner ~= name then
|
||||
if detected_mob.name == self.name then
|
||||
turn_hostile(self,detected_mob)
|
||||
else
|
||||
for _,id in pairs(friends_list) do
|
||||
if detected_mob.name == id then
|
||||
turn_hostile(self,detected_mob)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--THIS NEEDS TO BE RE-IMPLEMENTED AS A GLOBAL HIT IN MOB_PUNCH!!
|
||||
-- have owned mobs attack player threat
|
||||
--if obj.owner == name and obj.owner_loyal then
|
||||
-- do_attack(obj, self.object)
|
||||
--end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check if within physical map limits (-30911 to 30927)
|
||||
-- within_limits, wmin, wmax = nil, -30913, 30928
|
||||
mobs.within_limits = function(pos, radius)
|
||||
local wmin, wmax
|
||||
if mcl_vars then
|
||||
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
|
||||
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
|
||||
end
|
||||
end
|
||||
return pos
|
||||
and (pos.x - radius) > wmin and (pos.x + radius) < wmax
|
||||
and (pos.y - radius) > wmin and (pos.y + radius) < wmax
|
||||
and (pos.z - radius) > wmin and (pos.z + radius) < wmax
|
||||
end
|
||||
|
||||
-- get node but use fallback for nil or unknown
|
||||
mobs.node_ok = function(pos, fallback)
|
||||
|
||||
fallback = fallback or mobs.fallback_node
|
||||
|
||||
local node = minetest_get_node_or_nil(pos)
|
||||
|
||||
if node and minetest_registered_nodes[node.name] then
|
||||
return node
|
||||
end
|
||||
|
||||
return minetest_registered_nodes[fallback]
|
||||
end
|
||||
|
||||
|
||||
--a teleport functoin
|
||||
mobs.teleport = function(self, target)
|
||||
if self.do_teleport then
|
||||
if self.do_teleport(self, target) == false then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--a function used for despawning mobs
|
||||
mobs.check_for_player_within_area = function(self, radius)
|
||||
local pos1 = self.object:get_pos()
|
||||
--get players in radius
|
||||
for _,player in pairs(minetest_get_connected_players()) do
|
||||
if player and player:get_hp() > 0 then
|
||||
local pos2 = player:get_pos()
|
||||
local distance = vector_distance(pos1,pos2)
|
||||
if distance < radius then
|
||||
--found a player
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
--did not find a player
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--a simple helper function for mobs following
|
||||
mobs.get_2d_distance = function(pos1,pos2)
|
||||
pos1.y = 0
|
||||
pos2.y = 0
|
||||
return vector_distance(pos1, pos2)
|
||||
end
|
||||
|
||||
-- fall damage onto solid ground
|
||||
mobs.calculate_fall_damage = function(self)
|
||||
if self.old_velocity and self.old_velocity.y < -7 and self.object:get_velocity().y == 0 then
|
||||
local vel = self.object:get_velocity()
|
||||
if vel then
|
||||
local damage = math_abs(self.old_velocity.y + 7) * 2
|
||||
self.pause_timer = 0.4
|
||||
self.health = self.health - damage
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
--this is from https://github.com/HybridDog/builtin_item/blob/e6dfd9dce86503b3cbd1474257eca5f6f6ca71c2/init.lua#L50
|
||||
local
|
||||
minetest,vector,math,pairs,minetest_get_node,vector_subtract,minetest_registered_nodes
|
||||
=
|
||||
minetest,vector,math,pairs,minetest.get_node,vector.subtract,minetest.registered_nodes
|
||||
|
||||
local tab
|
||||
local n
|
||||
local function get_nodes(pos)
|
||||
tab,n = {},1
|
||||
for i = -1,1,2 do
|
||||
for _,p in pairs({
|
||||
{x=pos.x+i, y=pos.y, z=pos.z},
|
||||
{x=pos.x, y=pos.y, z=pos.z+i}
|
||||
}) do
|
||||
tab[n] = {p, minetest_get_node(p)}
|
||||
n = n+1
|
||||
end
|
||||
end
|
||||
return tab
|
||||
end
|
||||
|
||||
|
||||
local data
|
||||
local param2
|
||||
local nd
|
||||
local par2
|
||||
local name
|
||||
local tmp
|
||||
local c_node
|
||||
function mobs.get_flowing_dir(pos)
|
||||
c_node = minetest_get_node(pos).name
|
||||
if c_node ~= "mcl_core:water_flowing" and c_node ~= "mcl_core:water" then
|
||||
return nil
|
||||
end
|
||||
data = get_nodes(pos)
|
||||
param2 = minetest_get_node(pos).param2
|
||||
if param2 > 7 then
|
||||
return nil
|
||||
end
|
||||
if c_node == "mcl_core:water" then
|
||||
for _,i in pairs(data) do
|
||||
nd = i[2]
|
||||
name = nd.name
|
||||
par2 = nd.param2
|
||||
if name == "mcl_core:water_flowing" and par2 == 7 then
|
||||
return(vector_subtract(i[1],pos))
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,i in pairs(data) do
|
||||
nd = i[2]
|
||||
name = nd.name
|
||||
par2 = nd.param2
|
||||
if name == "mcl_core:water_flowing" and par2 < param2 then
|
||||
return(vector_subtract(i[1],pos))
|
||||
end
|
||||
end
|
||||
for _,i in pairs(data) do
|
||||
nd = i[2]
|
||||
name = nd.name
|
||||
par2 = nd.param2
|
||||
if name == "mcl_core:water_flowing" and par2 >= 11 then
|
||||
return(vector_subtract(i[1],pos))
|
||||
end
|
||||
end
|
||||
for _,i in pairs(data) do
|
||||
nd = i[2]
|
||||
name = nd.name
|
||||
par2 = nd.param2
|
||||
tmp = minetest_registered_nodes[name]
|
||||
if tmp and not tmp.walkable and name ~= "mcl_core:water_flowing" and name ~= "mcl_core:water" then
|
||||
return(vector_subtract(i[1],pos))
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
|
@ -0,0 +1,98 @@
|
|||
local math = math
|
||||
local vector = vector
|
||||
|
||||
--converts yaw to degrees
|
||||
local degrees = function(yaw)
|
||||
return yaw*180.0/math.pi
|
||||
end
|
||||
|
||||
mobs.do_head_logic = function(self,dtime)
|
||||
|
||||
local player = minetest.get_player_by_name("singleplayer")
|
||||
|
||||
local look_at = player:get_pos()
|
||||
look_at.y = look_at.y + player:get_properties().eye_height
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
local body_yaw = self.object:get_yaw()
|
||||
|
||||
local body_dir = minetest.yaw_to_dir(body_yaw)
|
||||
|
||||
pos.y = pos.y + self.head_height_offset
|
||||
|
||||
local head_offset = vector.multiply(body_dir, self.head_direction_offset)
|
||||
|
||||
pos = vector.add(pos, head_offset)
|
||||
|
||||
minetest.add_particle({
|
||||
pos = pos,
|
||||
velocity = {x=0, y=0, z=0},
|
||||
acceleration = {x=0, y=0, z=0},
|
||||
expirationtime = 0.2,
|
||||
size = 1,
|
||||
texture = "default_dirt.png",
|
||||
})
|
||||
|
||||
local bone_pos = vector.new(0,0,0)
|
||||
|
||||
--(horizontal)
|
||||
bone_pos.y = self.head_bone_pos_y
|
||||
|
||||
--(vertical)
|
||||
bone_pos.z = self.head_bone_pos_z
|
||||
|
||||
--print(yaw)
|
||||
|
||||
--local _, bone_rot = self.object:get_bone_position("head")
|
||||
|
||||
--bone_rot.x = bone_rot.x + (dtime * 10)
|
||||
--bone_rot.z = bone_rot.z + (dtime * 10)
|
||||
|
||||
local head_yaw = minetest.dir_to_yaw(vector.direction(pos,look_at)) - body_yaw
|
||||
|
||||
if self.reverse_head_yaw then
|
||||
head_yaw = head_yaw * -1
|
||||
end
|
||||
|
||||
--over rotation protection
|
||||
--stops radians from going out of spec
|
||||
if head_yaw > math.pi then
|
||||
head_yaw = head_yaw - (math.pi * 2)
|
||||
elseif head_yaw < -math.pi then
|
||||
head_yaw = head_yaw + (math.pi * 2)
|
||||
end
|
||||
|
||||
|
||||
local check_failed = false
|
||||
--upper check + 90 degrees or upper math.radians (3.14/2)
|
||||
if head_yaw > math.pi - (math.pi/2) then
|
||||
head_yaw = 0
|
||||
check_failed = true
|
||||
--lower check - 90 degrees or lower negative math.radians (-3.14/2)
|
||||
elseif head_yaw < -math.pi + (math.pi/2) then
|
||||
head_yaw = 0
|
||||
check_failed = true
|
||||
end
|
||||
|
||||
local head_pitch = 0
|
||||
|
||||
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
|
||||
--head_yaw = 0
|
||||
--DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
|
||||
|
||||
if not check_failed then
|
||||
head_pitch = minetest.dir_to_yaw(vector.new(vector.distance(vector.new(pos.x,0,pos.z),vector.new(look_at.x,0,look_at.z)),0,pos.y-look_at.y))+(math.pi/2)
|
||||
end
|
||||
|
||||
if self.head_pitch_modifier then
|
||||
head_pitch = head_pitch + self.head_pitch_modifier
|
||||
end
|
||||
|
||||
if self.swap_y_with_x then
|
||||
self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),degrees(head_yaw),0))
|
||||
else
|
||||
self.object:set_bone_position(self.head_bone, bone_pos, vector.new(degrees(head_pitch),0,degrees(head_yaw)))
|
||||
end
|
||||
--set_bone_position([bone, position, rotation])
|
||||
end
|
|
@ -0,0 +1,276 @@
|
|||
local minetest_after = minetest.after
|
||||
local minetest_sound_play = minetest.sound_play
|
||||
local minetest_dir_to_yaw = minetest.dir_to_yaw
|
||||
|
||||
local math = math
|
||||
local vector = vector
|
||||
|
||||
local MAX_MOB_NAME_LENGTH = 30
|
||||
|
||||
local mod_hunger = minetest.get_modpath("mcl_hunger")
|
||||
|
||||
mobs.feed_tame = function(self)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Code to execute before custom on_rightclick handling
|
||||
local function on_rightclick_prefix(self, clicker)
|
||||
local item = clicker:get_wielded_item()
|
||||
|
||||
-- Name mob with nametag
|
||||
if not self.ignores_nametag and item:get_name() == "mcl_mobs:nametag" then
|
||||
|
||||
local tag = item:get_meta():get_string("name")
|
||||
if tag ~= "" then
|
||||
if string.len(tag) > MAX_MOB_NAME_LENGTH then
|
||||
tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
|
||||
end
|
||||
self.nametag = tag
|
||||
|
||||
mobs.update_tag(self)
|
||||
|
||||
if not mobs.is_creative(clicker:get_player_name()) then
|
||||
item:take_item()
|
||||
clicker:set_wielded_item(item)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- I have no idea what this does
|
||||
mobs.create_mob_on_rightclick = function(on_rightclick)
|
||||
return function(self, clicker)
|
||||
--don't allow rightclicking dead mobs
|
||||
if self.health <= 0 then
|
||||
return
|
||||
end
|
||||
local stop = on_rightclick_prefix(self, clicker)
|
||||
if (not stop) and (on_rightclick) then
|
||||
on_rightclick(self, clicker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- deal damage and effects when mob punched
|
||||
mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
|
||||
--don't do anything if the mob is already dead
|
||||
if self.health <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
--neutral passive mobs switch to neutral hostile
|
||||
if self.neutral then
|
||||
--drop in variables for attacking (stops crash)
|
||||
self.attacking = hitter
|
||||
self.punch_timer = 0
|
||||
self.hostile = true
|
||||
--hostile_cooldown timer is initialized here
|
||||
self.hostile_cooldown_timer = self.hostile_cooldown
|
||||
|
||||
--initialize the group attack (check for other mobs in area, make them neutral hostile)
|
||||
if self.group_attack then
|
||||
mobs.group_attack_initialization(self)
|
||||
end
|
||||
end
|
||||
|
||||
--turn skittish mobs away and RUN
|
||||
if self.skittish then
|
||||
|
||||
self.state = "run"
|
||||
|
||||
self.run_timer = 5 --arbitrary 5 seconds
|
||||
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
local pos2 = hitter:get_pos()
|
||||
pos2.y = 0
|
||||
|
||||
|
||||
local dir = vector.direction(pos2,pos1)
|
||||
|
||||
local yaw = minetest_dir_to_yaw(dir)
|
||||
|
||||
self.yaw = yaw
|
||||
end
|
||||
|
||||
-- custom punch function
|
||||
if self.do_punch then
|
||||
-- when false skip going any further
|
||||
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--don't do damage until pause timer resets
|
||||
if self.pause_timer > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- error checking when mod profiling is enabled
|
||||
if not tool_capabilities then
|
||||
minetest.log("warning", "[mobs_mc] Mod profiling enabled, damage not enabled")
|
||||
return
|
||||
end
|
||||
|
||||
local is_player = hitter:is_player()
|
||||
|
||||
-- punch interval
|
||||
local weapon = hitter:get_wielded_item()
|
||||
|
||||
--local punch_interval = 1.4
|
||||
|
||||
-- exhaust attacker
|
||||
if mod_hunger and is_player then
|
||||
mcl_hunger.exhaust(hitter:get_player_name(), mcl_hunger.EXHAUST_ATTACK)
|
||||
end
|
||||
|
||||
-- calculate mob damage
|
||||
local damage = 0
|
||||
local armor = self.object:get_armor_groups() or {}
|
||||
|
||||
--calculate damage groups
|
||||
for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
|
||||
damage = damage + (tool_capabilities.damage_groups[group] or 0) * ((armor[group] or 0) / 100.0)
|
||||
end
|
||||
|
||||
if weapon then
|
||||
local fire_aspect_level = mcl_enchanting.get_enchantment(weapon, "fire_aspect")
|
||||
if fire_aspect_level > 0 then
|
||||
mcl_burning.set_on_fire(self.object, fire_aspect_level * 4)
|
||||
end
|
||||
end
|
||||
|
||||
-- check for tool immunity or special damage
|
||||
for n = 1, #self.immune_to do
|
||||
if self.immune_to[n][1] == weapon:get_name() then
|
||||
damage = self.immune_to[n][2] or 0
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- healing
|
||||
if damage <= -1 then
|
||||
self.health = self.health - math.floor(damage)
|
||||
return
|
||||
end
|
||||
|
||||
--if tool_capabilities then
|
||||
-- punch_interval = tool_capabilities.full_punch_interval or 1.4
|
||||
--end
|
||||
|
||||
-- add weapon wear manually
|
||||
-- Required because we have custom health handling ("health" property)
|
||||
--minetest_is_creative_enabled("") ~= true --removed for now
|
||||
if tool_capabilities then
|
||||
if tool_capabilities.punch_attack_uses then
|
||||
-- Without this delay, the wear does not work. Quite hacky ...
|
||||
minetest_after(0, function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return end
|
||||
local weapon = hitter:get_wielded_item(player)
|
||||
local def = weapon:get_definition()
|
||||
if def.tool_capabilities and def.tool_capabilities.punch_attack_uses then
|
||||
local wear = math.floor(65535/tool_capabilities.punch_attack_uses)
|
||||
weapon:add_wear(wear)
|
||||
hitter:set_wielded_item(weapon)
|
||||
end
|
||||
end, hitter:get_player_name())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--if player is falling multiply damage by 1.5
|
||||
--critical hit
|
||||
if hitter:get_velocity().y < 0 then
|
||||
damage = damage * 1.5
|
||||
mobs.critical_effect(self)
|
||||
end
|
||||
|
||||
|
||||
-- only play hit sound and show blood effects if damage is 1 or over; lower to 0.1 to ensure armor works appropriately.
|
||||
if damage >= 0.1 then
|
||||
|
||||
minetest_sound_play("default_punch", {
|
||||
object = self.object,
|
||||
max_hear_distance = 16
|
||||
}, true)
|
||||
|
||||
-- do damage
|
||||
self.health = self.health - damage
|
||||
|
||||
|
||||
--0.4 seconds until you can hurt the mob again
|
||||
self.pause_timer = 0.4
|
||||
|
||||
--don't do knockback from a rider
|
||||
for _,obj in pairs(self.object:get_children()) do
|
||||
if obj == hitter then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- knock back effect
|
||||
local velocity = self.object:get_velocity()
|
||||
|
||||
--2d direction
|
||||
local pos1 = self.object:get_pos()
|
||||
pos1.y = 0
|
||||
local pos2 = hitter:get_pos()
|
||||
pos2.y = 0
|
||||
|
||||
local dir = vector.direction(pos2,pos1)
|
||||
|
||||
local up = 3
|
||||
|
||||
-- if already in air then dont go up anymore when hit
|
||||
if velocity.y ~= 0 then
|
||||
up = 0
|
||||
end
|
||||
|
||||
--0.75 for perfect distance to not be too easy, and not be too hard
|
||||
local multiplier = 0.75
|
||||
|
||||
-- check if tool already has specific knockback value
|
||||
local knockback_enchant = mcl_enchanting.get_enchantment(hitter:get_wielded_item(), "knockback")
|
||||
if knockback_enchant and knockback_enchant > 0 then
|
||||
multiplier = knockback_enchant + 1 --(starts from 1, 1 would be no change)
|
||||
end
|
||||
|
||||
--do this to sure you can punch a mob back when
|
||||
--it's coming for you
|
||||
if self.hostile then
|
||||
multiplier = multiplier + 2
|
||||
end
|
||||
dir = vector.multiply(dir,multiplier)
|
||||
dir.y = up
|
||||
--add the velocity
|
||||
self.object:add_velocity(dir)
|
||||
end
|
||||
end
|
||||
|
||||
--do internal per mob projectile calculations
|
||||
mobs.shoot_projectile = function(self)
|
||||
local pos1 = self.object:get_pos()
|
||||
--add mob eye height
|
||||
pos1.y = pos1.y + self.eye_height
|
||||
|
||||
local pos2 = self.attacking:get_pos()
|
||||
--add player eye height
|
||||
pos2.y = pos2.y + self.attacking:get_properties().eye_height
|
||||
|
||||
--get direction
|
||||
local dir = vector.direction(pos1,pos2)
|
||||
|
||||
--call internal shoot_arrow function
|
||||
self.shoot_arrow(self,pos1,dir)
|
||||
end
|
||||
|
||||
mobs.update_tag = function(self)
|
||||
self.object:set_properties({
|
||||
nametag = self.nametag,
|
||||
})
|
||||
end
|
|
@ -0,0 +1,150 @@
|
|||
local minetest_add_particlespawner = minetest.add_particlespawner
|
||||
|
||||
mobs.death_effect = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
--local yaw = self.object:get_yaw()
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
local min, max
|
||||
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
end
|
||||
|
||||
minetest_add_particlespawner({
|
||||
amount = 50,
|
||||
time = 0.0001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector.new(-0.5,0.5,-0.5),
|
||||
maxvel = vector.new(0.5,1,0.5),
|
||||
minexptime = 1.1,
|
||||
maxexptime = 1.5,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "mcl_particles_mob_death.png", -- this particle looks strange
|
||||
})
|
||||
end
|
||||
|
||||
mobs.critical_effect = function(self)
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
--local yaw = self.object:get_yaw()
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
local min, max
|
||||
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
end
|
||||
|
||||
minetest_add_particlespawner({
|
||||
amount = 10,
|
||||
time = 0.0001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector.new(-1,1,-1),
|
||||
maxvel = vector.new(1,3,1),
|
||||
minexptime = 0.7,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "heart.png^[colorize:black:255",
|
||||
})
|
||||
end
|
||||
|
||||
--when feeding a mob
|
||||
mobs.feed_effect = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
--local yaw = self.object:get_yaw()
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
local min, max
|
||||
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
end
|
||||
|
||||
minetest_add_particlespawner({
|
||||
amount = 10,
|
||||
time = 0.0001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector.new(-1,1,-1),
|
||||
maxvel = vector.new(1,3,1),
|
||||
minexptime = 0.7,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "heart.png^[colorize:gray:255",
|
||||
})
|
||||
end
|
||||
|
||||
--hearts when tamed
|
||||
mobs.tamed_effect = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
--local yaw = self.object:get_yaw()
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
local min, max
|
||||
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
end
|
||||
|
||||
minetest_add_particlespawner({
|
||||
amount = 30,
|
||||
time = 0.0001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector.new(-1,1,-1),
|
||||
maxvel = vector.new(1,3,1),
|
||||
minexptime = 0.7,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "heart.png",
|
||||
})
|
||||
end
|
||||
|
||||
--hearts when breeding
|
||||
mobs.breeding_effect = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
--local yaw = self.object:get_yaw()
|
||||
local collisionbox = self.object:get_properties().collisionbox
|
||||
|
||||
local min, max
|
||||
|
||||
if collisionbox then
|
||||
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
|
||||
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
|
||||
end
|
||||
|
||||
minetest_add_particlespawner({
|
||||
amount = 2,
|
||||
time = 0.0001,
|
||||
minpos = vector.add(pos, min),
|
||||
maxpos = vector.add(pos, max),
|
||||
minvel = vector.new(-1,1,-1),
|
||||
maxvel = vector.new(1,3,1),
|
||||
minexptime = 0.7,
|
||||
maxexptime = 1,
|
||||
minsize = 1,
|
||||
maxsize = 2,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "heart.png",
|
||||
})
|
||||
end
|
|
@ -0,0 +1,390 @@
|
|||
-- localize math functions
|
||||
local math = math
|
||||
local HALF_PI = math.pi / 2
|
||||
local DOUBLE_PI = math.pi * 2
|
||||
|
||||
-- localize vector functions
|
||||
local vector = vector
|
||||
|
||||
local minetest_yaw_to_dir = minetest.yaw_to_dir
|
||||
local minetest_dir_to_yaw = minetest.dir_to_yaw
|
||||
|
||||
local DEFAULT_JUMP_HEIGHT = 5
|
||||
local DEFAULT_FLOAT_SPEED = 4
|
||||
local DEFAULT_CLIMB_SPEED = 3
|
||||
|
||||
mobs.stick_in_cobweb = function(self)
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = vector.multiply(vector.normalize(current_velocity), 0.4)
|
||||
|
||||
goal_velocity.y = -0.5
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
--this is a generic float function
|
||||
mobs.float = function(self)
|
||||
|
||||
local acceleration = self.object:get_acceleration()
|
||||
|
||||
if not acceleration then
|
||||
return
|
||||
end
|
||||
|
||||
if acceleration.y ~= 0 then
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
end
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = 0,
|
||||
y = DEFAULT_FLOAT_SPEED,
|
||||
z = 0,
|
||||
}
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity, current_velocity)
|
||||
|
||||
new_velocity_addition.x = 0
|
||||
new_velocity_addition.z = 0
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
--this is a generic climb function
|
||||
mobs.climb = function(self)
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = 0,
|
||||
y = DEFAULT_CLIMB_SPEED,
|
||||
z = 0,
|
||||
}
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
new_velocity_addition.x = 0
|
||||
new_velocity_addition.z = 0
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
_ _
|
||||
| | | |
|
||||
| | __ _ _ __ __| |
|
||||
| | / _` | '_ \ / _` |
|
||||
| |___| (_| | | | | (_| |
|
||||
\_____/\__,_|_| |_|\__,_|
|
||||
]]
|
||||
|
||||
|
||||
-- move mob in facing direction
|
||||
--this has been modified to be internal
|
||||
--internal = lua (self.yaw)
|
||||
--engine = c++ (self.object:get_yaw())
|
||||
mobs.set_velocity = function(self, v)
|
||||
|
||||
local yaw = (self.yaw or 0)
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = (math.sin(yaw) * -v),
|
||||
y = 0,
|
||||
z = (math.cos(yaw) * v),
|
||||
}
|
||||
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
|
||||
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
|
||||
end
|
||||
|
||||
new_velocity_addition.y = 0
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- calculate mob velocity
|
||||
mobs.get_velocity = function(self)
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
|
||||
v.y = 0
|
||||
|
||||
if v then
|
||||
return vector.length(v)
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--make mobs jump
|
||||
mobs.jump = function(self, velocity)
|
||||
|
||||
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
|
||||
return
|
||||
end
|
||||
|
||||
--fallback velocity to allow modularity
|
||||
velocity = velocity or DEFAULT_JUMP_HEIGHT
|
||||
|
||||
self.object:add_velocity(vector.new(0,velocity,0))
|
||||
end
|
||||
|
||||
--make mobs fall slowly
|
||||
mobs.mob_fall_slow = function(self)
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = 0,
|
||||
y = -2,
|
||||
z = 0,
|
||||
}
|
||||
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
new_velocity_addition.x = 0
|
||||
new_velocity_addition.z = 0
|
||||
|
||||
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
|
||||
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
|
||||
end
|
||||
|
||||
new_velocity_addition.x = 0
|
||||
new_velocity_addition.z = 0
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
_____ _
|
||||
/ ___| (_)
|
||||
\ `--.__ ___ _ __ ___
|
||||
`--. \ \ /\ / / | '_ ` _ \
|
||||
/\__/ /\ V V /| | | | | | |
|
||||
\____/ \_/\_/ |_|_| |_| |_|
|
||||
]]--
|
||||
|
||||
|
||||
|
||||
|
||||
--make mobs flop
|
||||
mobs.flop = function(self, velocity)
|
||||
|
||||
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
|
||||
return false
|
||||
end
|
||||
|
||||
mobs.set_velocity(self, 0)
|
||||
|
||||
--fallback velocity to allow modularity
|
||||
velocity = velocity or DEFAULT_JUMP_HEIGHT
|
||||
|
||||
--create a random direction (2d yaw)
|
||||
local dir = DOUBLE_PI * math.random()
|
||||
|
||||
--create a random force value
|
||||
local force = math.random(0,3) + math.random()
|
||||
|
||||
--convert the yaw to a direction vector then multiply it times the force
|
||||
local final_additional_force = vector.multiply(minetest_yaw_to_dir(dir), force)
|
||||
|
||||
--place in the "flop" velocity to make the mob flop
|
||||
final_additional_force.y = velocity
|
||||
|
||||
self.object:add_velocity(final_additional_force)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- move mob in facing direction
|
||||
--this has been modified to be internal
|
||||
--internal = lua (self.yaw)
|
||||
--engine = c++ (self.object:get_yaw())
|
||||
mobs.set_swim_velocity = function(self, v)
|
||||
|
||||
local yaw = (self.yaw or 0)
|
||||
local pitch = (self.pitch or 0)
|
||||
|
||||
if v == 0 then
|
||||
pitch = 0
|
||||
end
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = (math.sin(yaw) * -v),
|
||||
y = pitch,
|
||||
z = (math.cos(yaw) * v),
|
||||
}
|
||||
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
|
||||
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
|
||||
end
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
______ _
|
||||
| ___| |
|
||||
| |_ | |_ _
|
||||
| _| | | | | |
|
||||
| | | | |_| |
|
||||
\_| |_|\__, |
|
||||
__/ |
|
||||
|___/
|
||||
]]--
|
||||
|
||||
-- move mob in facing direction
|
||||
--this has been modified to be internal
|
||||
--internal = lua (self.yaw)
|
||||
--engine = c++ (self.object:get_yaw())
|
||||
mobs.set_fly_velocity = function(self, v)
|
||||
|
||||
local yaw = (self.yaw or 0)
|
||||
local pitch = (self.pitch or 0)
|
||||
|
||||
if v == 0 then
|
||||
pitch = 0
|
||||
end
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = (math.sin(yaw) * -v),
|
||||
y = pitch,
|
||||
z = (math.cos(yaw) * v),
|
||||
}
|
||||
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
|
||||
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
|
||||
end
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
--a quick and simple pitch calculation between two vector positions
|
||||
mobs.calculate_pitch = function(pos1, pos2)
|
||||
|
||||
if pos1 == nil or pos2 == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return minetest_dir_to_yaw(vector.new(vector.distance(vector.new(pos1.x,0,pos1.z),vector.new(pos2.x,0,pos2.z)),0,pos1.y - pos2.y)) + HALF_PI
|
||||
end
|
||||
|
||||
--make mobs fly up or down based on their y difference
|
||||
mobs.set_pitch_while_attacking = function(self)
|
||||
local pos1 = self.object:get_pos()
|
||||
local pos2 = self.attacking:get_pos()
|
||||
|
||||
local pitch = mobs.calculate_pitch(pos2,pos1)
|
||||
|
||||
self.pitch = pitch
|
||||
end
|
||||
|
||||
|
||||
|
||||
--[[
|
||||
___
|
||||
|_ |
|
||||
| |_ _ _ __ ___ _ __
|
||||
| | | | | '_ ` _ \| '_ \
|
||||
/\__/ / |_| | | | | | | |_) |
|
||||
\____/ \__,_|_| |_| |_| .__/
|
||||
| |
|
||||
|_|
|
||||
]]--
|
||||
|
||||
--special mob jump movement
|
||||
mobs.jump_move = function(self, velocity)
|
||||
|
||||
if self.object:get_velocity().y ~= 0 or not self.old_velocity or (self.old_velocity and self.old_velocity.y > 0) then
|
||||
return
|
||||
end
|
||||
|
||||
--make the mob stick for a split second
|
||||
mobs.set_velocity(self,0)
|
||||
|
||||
--fallback velocity to allow modularity
|
||||
local jump_height = DEFAULT_JUMP_HEIGHT
|
||||
|
||||
local yaw = (self.yaw or 0)
|
||||
|
||||
local current_velocity = self.object:get_velocity()
|
||||
|
||||
local goal_velocity = {
|
||||
x = (math.sin(yaw) * -velocity),
|
||||
y = jump_height,
|
||||
z = (math.cos(yaw) * velocity),
|
||||
}
|
||||
|
||||
|
||||
local new_velocity_addition = vector.subtract(goal_velocity,current_velocity)
|
||||
|
||||
if vector.length(new_velocity_addition) > vector.length(goal_velocity) then
|
||||
vector.multiply(new_velocity_addition, (vector.length(goal_velocity) / vector.length(new_velocity_addition)))
|
||||
end
|
||||
|
||||
--smooths out mobs a bit
|
||||
if vector.length(new_velocity_addition) >= 0.0001 then
|
||||
self.object:add_velocity(new_velocity_addition)
|
||||
end
|
||||
end
|
||||
|
||||
--make it so mobs do not glitch out and freak out
|
||||
--when moving around over nodes
|
||||
mobs.swap_auto_step_height_adjust = function(self)
|
||||
local y_vel = self.object:get_velocity().y
|
||||
|
||||
if y_vel == 0 and self.stepheight ~= self.stepheight_backup then
|
||||
self.stepheight = self.stepheight_backup
|
||||
elseif y_vel ~= 0 and self.stepheight ~= 0 then
|
||||
self.stepheight = 0
|
||||
end
|
||||
end
|