Compare commits
1115 Commits
move_physi
...
master
Author | SHA1 | Date |
---|---|---|
ancientmarinerdev | f335171965 | |
ancientmarinerdev | 8530f4d863 | |
Kristian | d4e348a4bd | |
Kristian | 590cbc0c3c | |
Kristian | 6d767b7d11 | |
Kristian | 7ba0773769 | |
Kristian | 0f82364b3e | |
Kristian | 49a899170f | |
Kristian | b8887b079a | |
Kristian | 9c77cbc011 | |
Kristian | cc21b74125 | |
ancientmarinerdev | 5c69daf5ba | |
José Muñoz | 09732acf77 | |
José Muñoz | 008dd95f1f | |
José Muñoz | f6fb1d1121 | |
José Muñoz | c61df75370 | |
PrairieWind | 158a5311f9 | |
cora | d0e981e4db | |
PrairieWind | 70caacd369 | |
ancientmarinerdev | 03341ade2e | |
ancientmarinerdev | 3c4fb9abb2 | |
ancientmarinerdev | 852c2c5710 | |
ancientmarinerdev | 1dfbd612be | |
Wbjitscool | 81569a6917 | |
PrairieWind | 17371ad60b | |
Nicu | 36f5372fe8 | |
PrairieWind | 0902e137e3 | |
PrairieWind | 67078ba3b1 | |
PrairieWind | da71e7b521 | |
PrairieWind | 960f9adda8 | |
PrairieWind | 74742f7fc8 | |
PrairieWind | c5c35ca786 | |
PrairieWind | bba440b617 | |
PrairieWind | 101cde2a94 | |
ancientmarinerdev | 0f8b709677 | |
ancientmarinerdev | dd5d1dad29 | |
chmodsayshello | 378b413986 | |
ancientmarinerdev | a286cb5046 | |
ancientmarinerdev | 8b9b4b00e5 | |
ancientmarinerdev | 7d51519f4d | |
ancientmarinerdev | 088f8dec2f | |
ancientmarinerdev | 5806dd6017 | |
ancientmarinerdev | 658f244ae4 | |
PrairieWind | ed13590bb0 | |
ancientmarinerdev | 23711950c1 | |
ancientmarinerdev | baf6ae65e7 | |
chmodsayshello | 9ba503f99d | |
ancientmarinerdev | 8c8b3be0f5 | |
chmodsayshello | 75d6509c3e | |
Temak | f7f8a72d08 | |
Niterux | 766c9efe33 | |
ancientmarinerdev | 52fba55910 | |
Nicu | 358432c52a | |
ancientmarinerdev | 4a17c8abc1 | |
ancientmarinerdev | da19aceb06 | |
ancientmarinerdev | 67260b16be | |
chmodsayshello | 7219f70d77 | |
seventeenthShulker | 758d38894d | |
ancientmarinerdev | 1192a46b9c | |
SmokeyDope | fd07cbdd5a | |
SmokeyDope | b409610537 | |
SmokeyDope | deb703fbc2 | |
FossFanatic | ac31642ec9 | |
ancientmarinerdev | f57f73681a | |
ancientmarinerdev | 60c996b5ac | |
ancientmarinerdev | 47f64f63a3 | |
ancientmarinerdev | cc5a0971ac | |
epCode | df8592df41 | |
epCode | 5e4fa30aae | |
epCode | e49eac6d85 | |
epCode | e53b6c124c | |
epCode | 92887f5501 | |
epCode | 86cd5711ca | |
epCode | 908ba9fba6 | |
epCode | 91d94800d7 | |
epCode | 74e55ca361 | |
epCode | c049113f26 | |
epCode | 1f5247df06 | |
epCode | 2e2f56122d | |
ancientmarinerdev | 46d486c7cb | |
PrairieWind | 75e4000b30 | |
PrairieWind | 46f6731cf5 | |
PrairieWind | 0fba7eaed4 | |
Michieal | 7c46826958 | |
Michieal | d6858b7e2a | |
Michieal | 9e5a45e3fd | |
PrairieWind | 4b9fc7046b | |
ancientmarinerdev | 3eb2f745e2 | |
ancientmarinerdev | 4287a261c8 | |
ancientmarinerdev | 5cbb56d71b | |
ancientmarinerdev | c65f8c9e51 | |
ancientmarinerdev | 812269264b | |
ancientmarinerdev | afb4540408 | |
ancientmarinerdev | 4d3e8e25e5 | |
ancientmarinerdev | ae32ce4a0a | |
ancientmarinerdev | 4c3d726882 | |
ancientmarinerdev | 90842c5f25 | |
ancientmarinerdev | 428ae9ac0d | |
SmokeyDope | adee1a49af | |
megustanlosfrijoles | 06077d1633 | |
ancientmarinerdev | ae486fa525 | |
Lars Mueller | b866d5d98e | |
uqers | 7133031caf | |
ancientmarinerdev | 019717cab0 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | f1d17e2c69 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 26f033932e | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 1e63f3931a | |
ancientmarinerdev | bd37ed178c | |
PrairieWind | 5ed92a2695 | |
ancientmarinerdev | 8f60fb08d3 | |
ancientmarinerdev | 10a3d06360 | |
ancientmarinerdev | 08cbd95a5e | |
PrairieWind | a5a035d9bb | |
uqers | 9c8b942e26 | |
ancientmarinerdev | 4651bd7e7d | |
ancientmarinerdev | 62ca6faab1 | |
ancientmarinerdev | ccf063999e | |
ancientmarinerdev | 4a7a50e78c | |
ancientmarinerdev | 00a950721d | |
PrairieWind | 6294a61d0d | |
ancientmarinerdev | 393c24d32a | |
PrairieWind | 873297d2cf | |
ancientmarinerdev | 681ea9b515 | |
ancientmarinerdev | e2688c03e3 | |
ancientmarinerdev | d6192dda67 | |
PrairieWind | e2963f88a7 | |
ancientmarinerdev | 11b371a107 | |
PrairieWind | 5071a7c789 | |
PrairieWind | 0903ac60e4 | |
ancientmarinerdev | f093050c76 | |
PrairieWind | d467b80491 | |
ancientmarinerdev | 6ded4d2322 | |
ancientmarinerdev | 5a059379b2 | |
ancientmarinerdev | 952a90bfde | |
ancientmarinerdev | f326fa620f | |
ancientmarinerdev | 3d1fb8cf4a | |
ancientmarinerdev | 519b237ba8 | |
ancientmarinerdev | 72c3f87925 | |
ancientmarinerdev | 818052dc6c | |
ancientmarinerdev | b3a6970370 | |
ancientmarinerdev | 53637bbc19 | |
ancientmarinerdev | 92a8f23d22 | |
ancientmarinerdev | 2d89440972 | |
ancientmarinerdev | 92c0809dbe | |
ancientmarinerdev | 530c8ec0da | |
ancientmarinerdev | 47e26bbfe6 | |
PrairieWind | f019f4ae45 | |
FossFanatic | 998983445b | |
ancientmarinerdev | 0a0bb3ff9a | |
PrairieWind | be32ffde6b | |
PrairieWind | 0364c8d2a6 | |
ancientmarinerdev | 3aed71fb85 | |
PrairieWind | 8a2c90406f | |
ancientmarinerdev | d42260cea3 | |
3raven | 9518d47662 | |
ancientmarinerdev | ede98cda80 | |
ancientmarinerdev | 09619a62ee | |
ancientmarinerdev | 31e6e38013 | |
ancientmarinerdev | 8092fd573c | |
ancientmarinerdev | 0185609b01 | |
ancientmarinerdev | 7726e576f0 | |
ancientmarinerdev | d920441a01 | |
ancientmarinerdev | c62694f9e4 | |
ancientmarinerdev | 39872f8ef6 | |
ancientmarinerdev | 0787d7a988 | |
ancientmarinerdev | 8a771ebfce | |
megustanlosfrijoles | d14c074d6c | |
ancientmarinerdev | 42c70ee622 | |
ancientmarinerdev | ff426412ef | |
ancientmarinerdev | 6151507442 | |
ancientmarinerdev | 3785dcda48 | |
ancientmarinerdev | 1694780d3f | |
ancientmarinerdev | ea4ea3f05e | |
ancientmarinerdev | 453e90741d | |
ancientmarinerdev | 6f75932a4b | |
ancientmarinerdev | 2d00e1e203 | |
ancientmarinerdev | 8b45cb2672 | |
PrairieWind | 848003de85 | |
ancientmarinerdev | be8d9122ee | |
ancientmarinerdev | 5507e99582 | |
ancientmarinerdev | fb9a630a5b | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 10459f51b1 | |
Mikita Wiśniewski | 387b79e582 | |
Mikita Wiśniewski | 21694879be | |
SmokeyDope | 3b64ceb5b2 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 3e768c6a6f | |
SmokeyDope | 1448a5e098 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 8ef653fb77 | |
ancientmarinerdev | 938ee7a832 | |
ancientmarinerdev | c48510244e | |
ancientmarinerdev | 52e64a6f75 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 0568c18081 | |
cora | 13df9ec46c | |
ancientmarinerdev | 2c01240a56 | |
PrairieWind | b001e4e06f | |
FlamingRCCars | 1a7f9fe8ec | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | ec5b0903be | |
megustanlosfrijoles | 7dc09e3ebd | |
ancientmarinerdev | 0d80f1126c | |
ancientmarinerdev | b15482012e | |
chmodsayshello | a8c989edd8 | |
chmodsayshello | 1a1473e8c0 | |
chmodsayshello | 80cde37e65 | |
chmodsayshello | 181cb73e45 | |
chmodsayshello | 198eb630e1 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 6c99a63419 | |
chmodsayshello | 5db1e1876c | |
ancientmarinerdev | 32a60c888b | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | cdb2bc20b0 | |
ancientmarinerdev | becf98875f | |
ancientmarinerdev | b374301ebf | |
chmodsayshello | 326e805798 | |
chmodsayshello | 65aa185ffa | |
ancientmarinerdev | 90b9263f03 | |
ancientmarinerdev | 3b1464ecc8 | |
ancientmarinerdev | 1015f5a569 | |
ancientmarinerdev | 31b65bac91 | |
FossFanatic | 01ac9ad685 | |
FossFanatic | d28dcb1b10 | |
FossFanatic | aac6af4c20 | |
FossFanatic | d8d83dd21c | |
FossFanatic | 76bf98b26c | |
FossFanatic | d53ea65da8 | |
ancientmarinerdev | cf174c110a | |
ancientmarinerdev | 0de9685914 | |
ancientmarinerdev | 3ed3f16702 | |
chmodsayshello | 99918c192c | |
ancientmarinerdev | 20638c482d | |
PrairieWind | f8d338d731 | |
PrairieWind | 5165730da8 | |
ancientmarinerdev | d0f7d7c90d | |
chmodsayshello | 0719d6038c | |
chmodsayshello | bf9989beb9 | |
chmodsayshello | 62c014363d | |
chmodsayshello | 75595115a0 | |
chmodsayshello | d9ac803f5b | |
chmodsayshello | 899d619624 | |
chmodsayshello | 145be4c830 | |
chmodsayshello | a55cbaadd6 | |
ancientmarinerdev | 56175d839f | |
ancientmarinerdev | 7b748efa64 | |
syl | ac4073a259 | |
chmodsayshello | 2366969e00 | |
chmodsayshello | fca23ba47f | |
chmodsayshello | 4b1cc017a9 | |
chmodsayshello | 786aaf7a6d | |
ancientmarinerdev | 1b4d9cfab7 | |
ancientmarinerdev | 7fe1be2c18 | |
chmodsayshello | 645e20afa4 | |
chmodsayshello | a026bbd3c4 | |
chmodsayshello | 9c8463d2e3 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 93bc5bb5f8 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | b19cc8a5e6 | |
ancientmarinerdev | 6817a8535b | |
ancientmarinerdev | c83574802f | |
PrairieWind | 0f64fb79c5 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 87bf3aed59 | |
ancientmarinerdev | 0ee31bd322 | |
ancientmarinerdev | 3e6b43ad47 | |
ancientmarinerdev | ed8953dfe3 | |
ancientmarinerdev | 8edffeb40d | |
ancientmarinerdev | 80f038da4a | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 952c72c414 | |
PrairieWind | 088922dc33 | |
PrairieWind | a98f79763b | |
PrairieWind | 9c83755f53 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | acfcb7528c | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 771044236f | |
ancientmarinerdev | bfea20dd00 | |
ancientmarinerdev | 0abc0ffc48 | |
ancientmarinerdev | 4205915a0b | |
PrairieWind | f7bdf7481c | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 6e107a77b5 | |
ancientmarinerdev | 53923dd5fa | |
ancientmarinerdev | 59f4a62fd6 | |
ancientmarinerdev | f8e994ae78 | |
ancientmarinerdev | deb393ae32 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | bef204f548 | |
ancientmarinerdev | 80225bb8df | |
SmokeyDope | 8a1c885faf | |
SmokeyDope | c3e37cb840 | |
SmokeyDope | 4226b2a8ba | |
SmokeyDope | b08b6034ac | |
PrairieWind | 2a3fbdead0 | |
PrairieWind | f284b1ca0e | |
PrairieWind | 2bd36b5d66 | |
ancientmarinerdev | d5c150d46c | |
PrairieWind | 8a129fa00c | |
ancientmarinerdev | 12b9def9ad | |
ancientmarinerdev | 61a4595c1b | |
ancientmarinerdev | 2426570871 | |
PrairieWind | ce1393af0c | |
Wbjitscool | bfa5662421 | |
Wbjitscool | 87ca144c4e | |
Wbjitscool | a10235f330 | |
Wbjitscool | e3978c4d22 | |
Wbjitscool | c0fa2c38bd | |
Wbjitscool | 049a4a11f4 | |
PrairieWind | 114e640132 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 692758bd6d | |
ancientmarinerdev | 08990207cf | |
ancientmarinerdev | 3bdc77a1aa | |
ancientmarinerdev | 7f3734b6cb | |
ancientmarinerdev | b7079e8b19 | |
ancientmarinerdev | 9887958e4e | |
ancientmarinerdev | a806833e4a | |
ancientmarinerdev | 46eb4d11dc | |
ancientmarinerdev | 6c8113dec8 | |
ancientmarinerdev | b72dbf17a6 | |
ancientmarinerdev | 049406162e | |
ancientmarinerdev | 924d999ec2 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | a52931f986 | |
ancientmarinerdev | c499d20d25 | |
SmokeyDope | 47b6e54dad | |
SmokeyDope | 257e5f993b | |
ancientmarinerdev | f02afca62c | |
PrairieWind | 802ff3d984 | |
ancientmarinerdev | 9367c96525 | |
ancientmarinerdev | f319a8d903 | |
ancientmarinerdev | 5e8bc16a0c | |
ancientmarinerdev | c5a15192b3 | |
ancientmarinerdev | e41b166c1b | |
ancientmarinerdev | c393b15965 | |
ancientmarinerdev | f5ea3894cf | |
ancientmarinerdev | 0fbdc07402 | |
ancientmarinerdev | 41882e9e1f | |
ancientmarinerdev | 15fce1130e | |
ancientmarinerdev | 2440b73e67 | |
ancientmarinerdev | 109bf17723 | |
ancientmarinerdev | a513547958 | |
ancientmarinerdev | 1c0387b898 | |
ancientmarinerdev | 5409a382f9 | |
ancientmarinerdev | ecf72db684 | |
ancientmarinerdev | bcd7f38fa7 | |
ancientmarinerdev | 3eab946889 | |
anarquimico | f817fe7f72 | |
anarquimico | d5f01e88c4 | |
ancientmarinerdev | b4ea9f048b | |
ancientmarinerdev | 6989e00af7 | |
ancientmarinerdev | 23f0c9a83e | |
ancientmarinerdev | 81a258d33b | |
ancientmarinerdev | bd579314ba | |
ancientmarinerdev | e7449a65d8 | |
ancientmarinerdev | b8b47e55e1 | |
ancientmarinerdev | 1182ce349a | |
SmokeyDope | 3f80cc8f26 | |
SmokeyDope | 68a44d7824 | |
SmokeyDope | ced3b4dff1 | |
SmokeyDope | 44711369d9 | |
SmokeyDope | 3839250b38 | |
ancientmarinerdev | a4b4e4de69 | |
ancientmarinerdev | 002a63585f | |
SmokeyDope | 811f8ac516 | |
SmokeyDope | 89f2c06af2 | |
SmokeyDope | 20f8e18098 | |
ancientmarinerdev | 3b4fc04330 | |
ancientmarinerdev | d437f45f4a | |
ancientmarinerdev | ef633ce617 | |
ancientmarinerdev | a3f23d0b35 | |
ancientmarinerdev | 2c7039fdda | |
ancientmarinerdev | c6db032674 | |
ancientmarinerdev | 9f182dc63a | |
ancientmarinerdev | ff1b941d19 | |
ancientmarinerdev | be269b2034 | |
ancientmarinerdev | 97091de67f | |
ancientmarinerdev | ae7cfdff69 | |
ancientmarinerdev | 0dee7792f4 | |
ancientmarinerdev | 583d066587 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 150222583a | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | a3e6f90b09 | |
ancientmarinerdev | 9697591dd6 | |
FossFanatic | ae811ed703 | |
ancientmarinerdev | fa0a595009 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 75dffe5059 | |
ancientmarinerdev | 3063cc97f7 | |
ancientmarinerdev | db53e41ebd | |
ancientmarinerdev | 4917ea1478 | |
syl | 1d981876d9 | |
ancientmarinerdev | 7b53b6e45f | |
ancientmarinerdev | 270820125d | |
ancientmarinerdev | fc7eb135e6 | |
ancientmarinerdev | c97fef03b9 | |
ancientmarinerdev | 37ca0efe05 | |
syl | 8a7a8ce1bf | |
syl | 2d1c904368 | |
ancientmarinerdev | b49828e9cb | |
ancientmarinerdev | 03d8363ed1 | |
ancientmarinerdev | ede3123b1a | |
ancientmarinerdev | 32ffa32009 | |
ancientmarinerdev | 0f7efcf9c1 | |
ancientmarinerdev | 4fe4559e6d | |
ancientmarinerdev | 4e363049e1 | |
ancientmarinerdev | d5cb745def | |
ancientmarinerdev | 639cec4989 | |
ancientmarinerdev | 547973877a | |
ancientmarinerdev | 994f720385 | |
grorp | a766a6ac85 | |
grorp | 2b0c5549f4 | |
grorp | 79996f143e | |
grorp | 166f15f5c6 | |
grorp | c706d44d55 | |
grorp | feca77c1d7 | |
grorp | 9463d12a35 | |
grorp | fa07fab325 | |
grorp | df16065617 | |
grorp | 735904ce61 | |
grorp | 49a5e28398 | |
FossFanatic | f012dbf50a | |
FossFanatic | d8d44fce0f | |
ancientmarinerdev | 987760749f | |
ancientmarinerdev | 038d9ec014 | |
ancientmarinerdev | 6097dacbab | |
ancientmarinerdev | 5b7132ac5e | |
ancientmarinerdev | 5fc78bf6b0 | |
ancientmarinerdev | b47ef9275b | |
ancientmarinerdev | ce6d9d561f | |
syl | 21dbf583ee | |
syl | 96b2f6a01d | |
ancientmarinerdev | 85fe29e5d3 | |
ancientmarinerdev | 655c130956 | |
ancientmarinerdev | 325de7f2cd | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 3c9b3497bb | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 8d318dfaa2 | |
FossFanatic | 6304502173 | |
FossFanatic | b1a52a3243 | |
FossFanatic | 1a1ea29f6e | |
FossFanatic | af8c4ded93 | |
FossFanatic | 8b0d195906 | |
ancientmarinerdev | e15d0cd5a0 | |
ancientmarinerdev | 1a24df5073 | |
SmokeyDope | 38b8f551d4 | |
SmokeyDope | c18a851dd3 | |
ancientmarinerdev | 6e6aa83fd8 | |
ancientmarinerdev | 695cbc54ae | |
ancientmarinerdev | 4c5b057038 | |
ancientmarinerdev | 1dcdbbef79 | |
ancientmarinerdev | 37b42a1033 | |
ancientmarinerdev | d32e968682 | |
FossFanatic | 887269f6de | |
ancientmarinerdev | 67ddad987d | |
ancientmarinerdev | 25aff57076 | |
FossFanatic | 261b5dda98 | |
FossFanatic | b80dd0294c | |
FossFanatic | 260a76e5ee | |
FossFanatic | b4d41d1284 | |
FossFanatic | e82ff4781a | |
ancientmarinerdev | 22e7200ea0 | |
ancientmarinerdev | a116c67dcd | |
ancientmarinerdev | 1937241a71 | |
ancientmarinerdev | 10fa91cc42 | |
ancientmarinerdev | 83ff2f1754 | |
PrairieWind | 0e68014e23 | |
SmokeyDope | d4be5dd88c | |
SmokeyDope | 256803303d | |
SmokeyDope | b70d870004 | |
SmokeyDope | 75fb64d6ea | |
SmokeyDope | f6d6a59c01 | |
SmokeyDope | 551b0c31bb | |
SmokeyDope | 4939ce3e75 | |
SmokeyDope | 108e369e0c | |
SmokeyDope | 8a48729aad | |
SmokeyDope | f5fad2e8f5 | |
SmokeyDope | ab6abc3876 | |
SmokeyDope | 1855fa2b64 | |
ancientmarinerdev | b4422402c9 | |
FossFanatic | dbbac7962d | |
ancientmarinerdev | 3ba77e408c | |
FossFanatic | c0f52ad344 | |
FossFanatic | 8a408a5c7f | |
ancientmarinerdev | 5478c8f44f | |
ancientmarinerdev | a25cced40e | |
ancientmarinerdev | 86a85bb487 | |
anarquimico | 8c4f2fdd7b | |
ancientmarinerdev | 48fa2608ed | |
PrairieWind | cfaa6aa8c2 | |
PrairieWind | 2f10a8767b | |
PrairieWind | c71aec6f9d | |
PrairieWind | 493839cf11 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 4c7c1fe176 | |
anarquimico | c37aad26c3 | |
anarquimico | a3ab67cb55 | |
anarquimico | 7cbeae00cc | |
anarquimico | 16a4448c5f | |
anarquimico | 5093f31f28 | |
anarquimico | 8825fb9860 | |
anarquimico | d0022db2a5 | |
PrairieWind | 0a93d67814 | |
anarquimico | 6bdc0f032f | |
anarquimico | 0aa3c52263 | |
anarquimico | f4c26fb578 | |
PrairieWind | 85457cd3bf | |
epCode | dc9407701e | |
epCode | e48dabc8be | |
PrairieWind | 51c374b31f | |
FossFanatic | a457853938 | |
FossFanatic | 4659d1a0f1 | |
PrairieWind | 9972b055c5 | |
FossFanatic | fedae4fdd0 | |
FossFanatic | 64d6fdaec5 | |
GuyLiner | cafc16c41c | |
GuyLiner | 14039290a9 | |
SmokeyDope | f174b733ce | |
SmokeyDope | b4f747bd84 | |
SmokeyDope | 0351556a2e | |
SmokeyDope | 182f9b5dc2 | |
SmokeyDope | bd4eb01a46 | |
SmokeyDope | 5c25901433 | |
SmokeyDope | 2d6e1c61a2 | |
FossFanatic | d83c6fe906 | |
FossFanatic | e7c6043f06 | |
FossFanatic | 65b1fd163b | |
FossFanatic | eff0a546e5 | |
FossFanatic | 61ee14b1a6 | |
FossFanatic | 968f6ae963 | |
FossFanatic | 2e2bbf0b17 | |
FossFanatic | 8060b02cbd | |
FossFanatic | 693d40b6c4 | |
FossFanatic | ecfbb1ae07 | |
FossFanatic | 14e630a1e2 | |
FossFanatic | 62afbb4509 | |
FossFanatic | f02764bc08 | |
FossFanatic | aa1a928898 | |
FossFanatic | 45952a6fd6 | |
FossFanatic | 33bbeb1a4d | |
FossFanatic | d3253ecf4f | |
FossFanatic | 0abda8ff20 | |
FossFanatic | 9bb3d8311b | |
FossFanatic | 686bb38546 | |
FossFanatic | c4f6944a03 | |
FossFanatic | c1647a5cce | |
FossFanatic | 624c853cb3 | |
ancientmarinerdev | e8641c3c20 | |
GuyLiner | f39cec2442 | |
GuyLiner | e99a46b52d | |
GuyLiner | cd1c8bd92e | |
GuyLiner | 3830171347 | |
GuyLiner | 48b16a23b6 | |
GuyLiner | 0a9be4b79c | |
GuyLiner | 540319d948 | |
GuyLiner | fbb2923a0b | |
ancientmarinerdev | b2e0b9b08b | |
ancientmarinerdev | 4d61e32021 | |
ancientmarinerdev | 7c602ce82c | |
ancientmarinerdev | ae92c51155 | |
ancientmarinerdev | db7f4699eb | |
ancientmarinerdev | b23fc6d23d | |
FossFanatic | 68e6965dd0 | |
FossFanatic | 7d642c9929 | |
FossFanatic | 406b27852b | |
ancientmarinerdev | d00f183456 | |
emptyshore | b121d0f804 | |
FossFanatic | 8f8385e4a1 | |
FossFanatic | 17fc7a3256 | |
ancientmarinerdev | c08153fa7f | |
ancientmarinerdev | ba708010a2 | |
Michieal | 6601ecf788 | |
Michieal | 660c85dddc | |
Michieal | 2d0b4dd8f7 | |
FossFanatic | 10d9eb2980 | |
FossFanatic | 3d9df5c797 | |
Michieal | 8cc90b6714 | |
ancientmarinerdev | db4ff13aeb | |
FossFanatic | f919f73c24 | |
FossFanatic | 319beb91ce | |
FossFanatic | bea9bbe212 | |
FossFanatic | 8fd40eaf46 | |
FossFanatic | fcaa2c4ce1 | |
FossFanatic | 55478f6953 | |
FossFanatic | 57882ae56a | |
FossFanatic | fee4ccf096 | |
FossFanatic | f653f47f26 | |
FossFanatic | 7b7e213925 | |
ancientmarinerdev | cf829b6481 | |
PrairieWind | f62546a54f | |
ancientmarinerdev | 7b6d946d7b | |
PrairieWind | 1da272a941 | |
PrairieWind | e3f14a3a99 | |
ancientmarinerdev | a9a61a034c | |
ancientmarinerdev | 6f98ba5118 | |
ancientmarinerdev | f6f812257e | |
Michieal | f05e976d18 | |
Michieal | 5533ec7bac | |
Michieal | b66e2a117f | |
Michieal | 09ef5fd96b | |
Michieal | 1e61fc3069 | |
Michieal | ef30646788 | |
Michieal | 70e4aaa3a7 | |
Michieal | 8bfa55fa7a | |
Michieal | 2ba6a60e55 | |
FossFanatic | aeddb7675f | |
FossFanatic | 2ccfd89379 | |
FossFanatic | 3a24903a01 | |
FossFanatic | abdd5e11c0 | |
FossFanatic | e17fab385b | |
FossFanatic | 17cae6896e | |
FossFanatic | 1c84bc6572 | |
FossFanatic | 4d29fab932 | |
FossFanatic | a2087b19a5 | |
PrairieWind | 84119d8f34 | |
PrairieWind | 5da6c25ccf | |
PrairieWind | b79f74c390 | |
PrairieWind | 06f47a0756 | |
PrairieWind | ff79af26e4 | |
ancientmarinerdev | de56c72e15 | |
ancientmarinerdev | e3c8d995b5 | |
ancientmarinerdev | b8b45210e9 | |
ancientmarinerdev | a1f10205f6 | |
ancientmarinerdev | 6f7505b6c6 | |
PrairieWind | e323ab6e88 | |
FossFanatic | c88f0047c1 | |
FossFanatic | 0400d9a2a5 | |
FossFanatic | 3590ff6dfb | |
FossFanatic | d1ceacf7ba | |
FossFanatic | a70a6ef13d | |
FossFanatic | b4e84dadb0 | |
PrairieWind | 194ff53400 | |
ancientmarinerdev | e7e0b082c4 | |
FossFanatic | 18b888da8b | |
ancientmarinerdev | 15a15158b8 | |
ancientmarinerdev | 9445e10834 | |
ancientmarinerdev | 35a75491f4 | |
FossFanatic | 81dbdaab94 | |
ancientmarinerdev | 2fd7f8c69f | |
ancientmarinerdev | 6a34a30941 | |
ancientmarinerdev | d4c2802afb | |
ancientmarinerdev | b2dbf48e92 | |
ancientmarinerdev | bf50a17f2e | |
ancientmarinerdev | b817c079ba | |
ancientmarinerdev | cfa276f722 | |
ancientmarinerdev | 86b1d8bc3e | |
ancientmarinerdev | 9c2f43a242 | |
ancientmarinerdev | 044a91e831 | |
ancientmarinerdev | 106979e64a | |
ancientmarinerdev | 4482068211 | |
ancientmarinerdev | 1dc7cab6be | |
ancientmarinerdev | 634379dfe9 | |
ancientmarinerdev | c2ac33ac61 | |
ancientmarinerdev | b5c0830060 | |
ancientmarinerdev | c43c723e08 | |
ancientmarinerdev | bd3a4ff0df | |
ancientmarinerdev | 0267ad2f31 | |
ancientmarinerdev | a2f9ea81cc | |
ancientmarinerdev | 7c7f4b930c | |
ancientmarinerdev | 60529d3d5d | |
ancientmarinerdev | 6ea4b43249 | |
FossFanatic | 1fb300ef18 | |
FossFanatic | b789845f62 | |
FossFanatic | f368fb3e43 | |
FossFanatic | d63ecac7cb | |
FossFanatic | 7fbc84971e | |
FossFanatic | 953044cc4b | |
FossFanatic | 6744967d02 | |
FossFanatic | fdb2333493 | |
FossFanatic | 1defd03408 | |
FossFanatic | d163faeb42 | |
FossFanatic | 197d3ae00b | |
ancientmarinerdev | 6b8226dae3 | |
Michieal | f6b77aa458 | |
Michieal | 1558c852d8 | |
Michieal | 6c0525f00e | |
ancientmarinerdev | 3c13ce1d6b | |
ancientmarinerdev | c1d5d4280e | |
PrairieWind | 70fb4f8a0d | |
ancientmarinerdev | 620a0af483 | |
ancientmarinerdev | 428b1b2c12 | |
FossFanatic | 4ddab8bdf1 | |
FossFanatic | bfe4b2fd31 | |
FossFanatic | cc8675602a | |
FossFanatic | aff1d5f667 | |
FossFanatic | 169019096e | |
FossFanatic | 049db13b28 | |
SmokeyDope | dfbee5cb93 | |
SmokeyDope | 378af531a9 | |
ancientmarinerdev | 088cda4f1c | |
SmokeyDope | 71671f8b5f | |
SmokeyDope | c4d60e50cc | |
ancientmarinerdev | 9421c7ca6b | |
FossFanatic | d9982e20d2 | |
ancientmarinerdev | 30bb1ab075 | |
ancientmarinerdev | b834e790a7 | |
ancientmarinerdev | 39d4434df1 | |
ancientmarinerdev | 4b9482cb09 | |
FossFanatic | 4757e62968 | |
FossFanatic | b95ebb3fca | |
FossFanatic | 07f332f0cd | |
FossFanatic | f4e8088e60 | |
FossFanatic | 198375a18e | |
FossFanatic | c26cddeafc | |
FossFanatic | 714d159072 | |
emptyshore | bdc82b76b5 | |
ancientmarinerdev | 6f67f0e095 | |
ancientmarinerdev | d5687716a0 | |
ancientmarinerdev | 5f0d91cf26 | |
anarquimico | e2de5908c7 | |
anarquimico | 439c879508 | |
anarquimico | 67977b0d26 | |
anarquimico | ed8c3e7d26 | |
anarquimico | 7add59992f | |
ancientmarinerdev | d72a674097 | |
Michieal | ef4e4b1b7b | |
Michieal | 0e000e6491 | |
Michieal | ec7a97ba3e | |
PrairieWind | 290680fb3d | |
SmokeyDope | 48eb934d64 | |
SmokeyDope | 88cf3a5900 | |
SmokeyDope | 1401652015 | |
SmokeyDope | cac6e15736 | |
SmokeyDope | 156d096a78 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | db3483a6cb | |
ancientmarinerdev | ace6f8db43 | |
ancientmarinerdev | 9aeafe6053 | |
Michieal | de00b7228e | |
ancientmarinerdev | d454697953 | |
FossFanatic | ce2011218c | |
FossFanatic | ccef2d1147 | |
ancientmarinerdev | 5fbec2f6b5 | |
GuyLiner | 4d922d9edf | |
Michieal | 46ca650ea4 | |
Michieal | c4e4a8e4d2 | |
Michieal | 08610296fc | |
ancientmarinerdev | 7d46fa3362 | |
ancientmarinerdev | 58a08ea697 | |
Michieal | 50effaa807 | |
SmokeyDope | 08df1fe02f | |
SmokeyDope | bc4d9211cb | |
SmokeyDope | cf05771e30 | |
SmokeyDope | 16a6dcb66c | |
SmokeyDope | 89eb458df7 | |
SmokeyDope | eae1ee7cee | |
SmokeyDope | 27f5a94c6a | |
SmokeyDope | b4719599b5 | |
SmokeyDope | 659f11bf6f | |
SmokeyDope | 88a7a8afab | |
ancientmarinerdev | cdc922d4ac | |
ancientmarinerdev | 060732cd66 | |
Michieal | ad43fbd237 | |
Michieal | 618313f0af | |
ancientmarinerdev | 31fb7faf16 | |
Michieal | a3bc3fd9b9 | |
Michieal | c370f1884b | |
Michieal | b771f314fe | |
ancientmarinerdev | f6a5004eab | |
ancientmarinerdev | 7daa79a3f8 | |
Michieal | 4b0b3217e2 | |
ancientmarinerdev | e363435134 | |
Michieal | 5b1c7f2d3a | |
ancientmarinerdev | 63ace64cae | |
FossFanatic | 0c0845c1bd | |
FossFanatic | 24b1078b61 | |
FossFanatic | 3546d04aaa | |
FossFanatic | 1abd226e07 | |
ancientmarinerdev | f4b1116e21 | |
FossFanatic | cbb3260471 | |
ancientmarinerdev | 855659a2a1 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 8793ed211e | |
mrminer | 01f814267e | |
mrminer | 8f4c69084d | |
Michieal | 425cf53194 | |
Michieal | d04f162d8f | |
Michieal | 7f84922403 | |
Michieal | 025f97187a | |
Michieal | 06effcd786 | |
Michieal | 6265148727 | |
ancientmarinerdev | 2ad60b06ea | |
ancientmarinerdev | 245ce99223 | |
ancientmarinerdev | 1f107ec0c7 | |
ancientmarinerdev | c209537cfe | |
ancientmarinerdev | d583ccb986 | |
ancientmarinerdev | 7d803e174c | |
Michieal | 319602508d | |
Michieal | 8c703f1352 | |
Michieal | 95b1429922 | |
SmokeyDope | 32b5b435c5 | |
SmokeyDope | a3fe41128f | |
SmokeyDope | 1a495dd63a | |
Michieal | 29597af738 | |
Michieal | f3e840a0ff | |
Adam Macumber | 7dd0293616 | |
ancientmarinerdev | 63360bcab2 | |
ancientmarinerdev | 0b1881d1d4 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 8cf6269dda | |
SmokeyDope | 1f09c0a128 | |
SmokeyDope | fb8060c366 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | da7320b14c | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 66ff8ab8a9 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | a1cc24f8d3 | |
SmokeyDope | 6f22d7daa4 | |
SmokeyDope | dc3eedac5f | |
FossFanatic | f3bed9f599 | |
FossFanatic | f84bdbd777 | |
FossFanatic | 7712c45c4e | |
ancientmarinerdev | 02ee8b757d | |
SmokeyDope | 39b6e41a62 | |
SmokeyDope | ef1f7b8e20 | |
PrairieWind | 5c32d1c106 | |
SmokeyDope | 70b56d7126 | |
ancientmarinerdev | c4ea504123 | |
ancientmarinerdev | 6d60fb4160 | |
ancientmarinerdev | e4f26a4688 | |
ancientmarinerdev | af86e73280 | |
ancientmarinerdev | 32be8f9602 | |
ancientmarinerdev | 15560d969c | |
ancientmarinerdev | 6bbf3af97b | |
ancientmarinerdev | 84317afc93 | |
ancientmarinerdev | 2cd6629ae1 | |
SmokeyDope | f971283798 | |
FossFanatic | 9afdd09d9d | |
SmokeyDope | 62bb7a85eb | |
SmokeyDope | 42eb541eaa | |
SmokeyDope | 9e8ed7efc3 | |
SmokeyDope | 36231241ba | |
ancientmarinerdev | 9a276489d1 | |
CyberMango | e2cbd4267c | |
CyberMango | 4ec506b534 | |
CyberMango | 06435e0f4c | |
CyberMango | a1d98c080f | |
CyberMango | 96cd2657db | |
CyberMango | cd63f32cdd | |
FossFanatic | 2d81d153bd | |
FossFanatic | dc7a46df4e | |
FossFanatic | ad25b0bc4b | |
FossFanatic | b77260253a | |
ancientmarinerdev | ad9beebc70 | |
FossFanatic | b6951d00b9 | |
ancientmarinerdev | 86e480aad0 | |
ancientmarinerdev | ce7d493890 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 995679d7ae | |
CyberMango | c57f797226 | |
CyberMango | 78f32d2dd4 | |
FossFanatic | a1ad84370c | |
FossFanatic | f6a3fe2128 | |
ancientmarinerdev | 1c65e0ad68 | |
ancientmarinerdev | 6900748429 | |
ancientmarinerdev | f09b723885 | |
ancientmarinerdev | efd3420d52 | |
ancientmarinerdev | 62be5a06f6 | |
ancientmarinerdev | 683799aea5 | |
ancientmarinerdev | 383cbf96a6 | |
ancientmarinerdev | a7632e767d | |
ancientmarinerdev | 99d09c76ae | |
ancientmarinerdev | c7ebe1b8cd | |
ancientmarinerdev | 5c464f1c1d | |
ancientmarinerdev | 87e6842c00 | |
Michieal | b258ccffdc | |
ancientmarinerdev | 83b0807218 | |
Michieal | a00ef4500d | |
PrairieWind | c49aef3251 | |
ancientmarinerdev | f3ba32d739 | |
Michieal | ed03cb470d | |
FossFanatic | 9746dbc376 | |
FossFanatic | d89a7e0326 | |
FossFanatic | 5fd5cc9f1c | |
FossFanatic | c75ec6916c | |
FossFanatic | 4b6b4d8398 | |
FossFanatic | 513d148eaf | |
FossFanatic | 26e032687a | |
Michieal | b99487b6ab | |
ancientmarinerdev | 8adc1d48b2 | |
Michieal | ac30e25065 | |
Michieal | 0931af21c8 | |
Michieal | 77a8ca689f | |
ancientmarinerdev | 30d3b7ee23 | |
Michieal | 55bb9800f4 | |
Michieal | b805ae9926 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 90c74f4a6a | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 6e6c11cde2 | |
Michieal | d48b3dcaee | |
Michieal | 88493091b6 | |
Michieal | d68667beb9 | |
Michieal | 181628e539 | |
Michieal | c57a757510 | |
Michieal | 26f3275b7c | |
Michieal | 45b4b4a458 | |
Michieal | 5424ca8c2b | |
ancientmarinerdev | 8feefcdd7b | |
ancientmarinerdev | 9b28baaad6 | |
PrairieWind | 60a1a364dc | |
b3nderman | f9b021e4c6 | |
ancientmarinerdev | 65fb911e5f | |
ancientmarinerdev | 38732e801d | |
ancientmarinerdev | 5ad2a990d4 | |
ancientmarinerdev | 8f6c932e88 | |
Michieal | e9c202ae45 | |
Michieal | d6ee21d192 | |
PrairieWind | 876fc2fb50 | |
Michieal | 0c61035df0 | |
b3nderman | a7fac7c550 | |
ancientmarinerdev | 93c9fdfaae | |
AFCMS | 3027965314 | |
AFCMS | e9c9f151de | |
AFCMS | fb8262bf57 | |
AFCMS | 726358da4b | |
AFCMS | 515204d296 | |
AFCMS | eb5f5678d6 | |
AFCMS | 33e2b79b2b | |
AFCMS | 7c497d9604 | |
AFCMS | 7c20896d5e | |
AFCMS | cd70dd5b88 | |
AFCMS | 302970d220 | |
AFCMS | 79876635af | |
AFCMS | b9238b4069 | |
AFCMS | bf25fca47d | |
AFCMS | 92261e5fb5 | |
AFCMS | 1e31e383d4 | |
AFCMS | 4457432d32 | |
ancientmarinerdev | fa96f9d593 | |
FossFanatic | fbb4cf084f | |
FossFanatic | 60b19b31ab | |
FossFanatic | 871c4f24c2 | |
FossFanatic | 18c0e1f050 | |
FossFanatic | fb8e41047d | |
FossFanatic | 2bd5e6a84f | |
FossFanatic | 23a13f14e0 | |
gldrk | 5412206743 | |
gldrk | 30521a8b2f | |
gldrk | 1536338199 | |
gldrk | 3697ef8069 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | dd15092052 | |
ancientmarinerdev | ce485e89cd | |
ancientmarinerdev | 09a7348256 | |
FossFanatic | a500528613 | |
FossFanatic | 292e7129f2 | |
Michieal | 3091e85b1c | |
Michieal | da277f9dc7 | |
Michieal | 27a487195a | |
Michieal | 2fdc8cbdd4 | |
Michieal | 0079cf807e | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 899cfd5157 | |
CyberMango | 64a7f76d5b | |
CyberMango | b0d9eed3e1 | |
CyberMango | 685a7ff256 | |
CyberMango | 949a2b787e | |
FossFanatic | 8849b2e98f | |
Michieal | 9e76cd963d | |
Michieal | 522a7577de | |
Michieal | 7912239562 | |
Michieal | 396fedd8e7 | |
Michieal | f81eec6fff | |
Michieal | d2625d3ace | |
Michieal | a04d946879 | |
ancientmarinerdev | 482a6071f5 | |
Michieal | 6993559330 | |
Michieal | b572363b8e | |
Michieal | 00408b998c | |
Michieal | e7c238c17e | |
Michieal | 1de806f6f8 | |
Michieal | b07e6fccdc | |
Michieal | 04efa74115 | |
Michieal | 2e8f56d098 | |
Michieal | ff7299a444 | |
Michieal | aad2d114f9 | |
ancientmarinerdev | 2ed3c1c480 | |
ancientmarinerdev | 111c885417 | |
ancientmarinerdev | 8e1f00d428 | |
ancientmarinerdev | 4fd4425aae | |
ancientmarinerdev | ac4cd2c325 | |
ancientmarinerdev | 01c8009c6a | |
ancientmarinerdev | ef90820f67 | |
ancientmarinerdev | d6d11b9526 | |
ancientmarinerdev | b0264b2736 | |
Michieal | c12f2cc565 | |
Michieal | 9f94f12127 | |
Michieal | 784f3ec226 | |
Michieal | 44a769397d | |
PrairieWind | b3da85be64 | |
ancientmarinerdev | 3ed32abc4e | |
kay27 | 9db6050638 | |
kay27 | 7ceb953a56 | |
kay27 | 1894d8c5f0 | |
kay27 | 419d61edde | |
PrairieWind | 9820309762 | |
Michieal | 726eba7ed8 | |
Michieal | f46581905a | |
Michieal | 4f6de581dd | |
Michieal | eb8f7360a2 | |
ancientmarinerdev | db62631540 | |
ancientmarinerdev | fb30564827 | |
Michieal | 8c355db3ce | |
Michieal | eae08f3010 | |
Michieal | 4701c4d6e7 | |
Michieal | 8df0b2a48e | |
ancientmarinerdev | 66f368531d | |
PrairieWind | 7cb5c55d68 | |
Michieal | 34faca3f2c | |
Michieal | d5dda679eb | |
Michieal | c65bbcd5a5 | |
Michieal | 533f59bf27 | |
Michieal | 3074c15d14 | |
Michieal | 132178b8c7 | |
Michieal | ede21a8627 | |
Michieal | 82fae13b47 | |
Michieal | f92ea77849 | |
Michieal | 7ebf9b7ab8 | |
Michieal | 07a56165ed | |
Michieal | ee0b355f51 | |
PrairieWind | 6fd799ac42 | |
ancientmarinerdev | 01bb753549 | |
ancientmarinerdev | 59694ebc7c | |
Michieal | cc24144bc6 | |
Michieal | e1cd16c971 | |
ancientmarinerdev | 00c4ecf643 | |
ancientmarinerdev | e4db91d35c | |
ancientmarinerdev | 2527479401 | |
ancientmarinerdev | 85f7bbdb80 | |
ancientmarinerdev | 465a919f6b | |
ancientmarinerdev | 325a666c62 | |
ancientmarinerdev | e9b54e85c2 | |
ancientmarinerdev | 4324fe2489 | |
ancientmarinerdev | d6804bf4b7 | |
ancientmarinerdev | 87f04bdd9f | |
ancientmarinerdev | e3307d647b | |
ancientmarinerdev | 29cd73cb84 | |
ancientmarinerdev | 5c0a763b83 | |
ancientmarinerdev | 9b1ceebf0d | |
Michieal | ed64e7f733 | |
ancientmarinerdev | 856a60bcc2 | |
Michieal | 08f9c0074a | |
FossFanatic | 09db6017c3 | |
Michieal | c4969c13cd | |
Michieal | f70176da34 | |
Michieal | e314addeb3 | |
Michieal | df8fc65e1e | |
Michieal | 687887fe27 | |
Michieal | 65aa956d9c | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | ddd004c0f0 | |
ancientmarinerdev | 609ef220ad | |
ancientmarinerdev | 32a7eb4087 | |
CyberMango | 5e969ba928 | |
CyberMango | 17e02aec3c | |
CyberMango | fb28e192e6 | |
CyberMango | 0f569fdbaa | |
CyberMango | 8a7fcfde82 | |
CyberMango | fb51067c78 | |
Michieal | 8dd4a2611e | |
Michieal | f86ee99abf | |
FossFanatic | 6cf1fa7b81 | |
ancientmarinerdev | 7ea41a2f21 | |
FossFanatic | 39bcf2f961 | |
𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 | 0b1916b807 | |
Michieal | d1a017f6b2 | |
Michieal | 50e50e2904 | |
Michieal | 966c914a8e | |
Michieal | 9351f72c15 | |
Michieal | ef7fb0d2e3 | |
Michieal | 5ef7d9f7a0 | |
ancientmarinerdev | 0fca1ce469 | |
Michieal | 388632cd46 | |
Michieal | dd4a0a4172 | |
ancientmarinerdev | df6d1c026a | |
Michieal | d586b3fecc | |
Michieal | a7a0c4791a | |
Michieal | 23b62c296a | |
ancientmarinerdev | f8ae702ce4 | |
ancientmarinerdev | 5faf060122 | |
ancientmarinerdev | 8c648d1fc3 | |
ancientmarinerdev | 46052e5b7e | |
iliekprogrammar | 6430fcf103 | |
iliekprogrammar | 7376b08c61 | |
iliekprogrammar | 2e28a3386b | |
iliekprogrammar | 98dac6dcd7 | |
iliekprogrammar | 3026808a71 | |
test1 | 7c7521ff15 | |
test1 | 50d8e95c16 | |
iliekprogrammar | fc74bd5cfe | |
kabou | 2a37d38f6c | |
kabou | 89821a8329 | |
iliekprogrammar | 3249c13752 | |
iliekprogrammar | 2ea72ccda8 | |
iliekprogrammar | 5237eca31e | |
iliekprogrammar | c2ba70a601 | |
iliekprogrammar | 723fe9c532 | |
iliekprogrammar | 661afed46c | |
FossFanatic | 3d2955c394 | |
Michieal | 87327abfb9 | |
Michieal | 34263c2ef3 | |
Michieal | 38c6969292 | |
FossFanatic | 54548ecfdf | |
FossFanatic | 5ec7b8ed89 | |
FossFanatic | 137179ac8e | |
FossFanatic | 3afb42b2f7 | |
Michieal | 1d0968f4f5 | |
Michieal | 0f9d263e02 | |
Michieal | 26c1be3722 | |
Michieal | acc2be7253 | |
Michieal | 3015bfb8c4 | |
Michieal | 28cb06421d | |
Michieal | 731468cf5a | |
Michieal | ff7693937a | |
FossFanatic | c1cde073c8 | |
Michieal | 76669e1f3c | |
FossFanatic | ef15477e37 | |
FossFanatic | a0c5a04d26 | |
FossFanatic | 18e932997d | |
FossFanatic | 1f601c68c5 | |
FossFanatic | 9cf6c9ed5e | |
FossFanatic | 9f9cc3a629 | |
FossFanatic | ac90350257 | |
FossFanatic | a6841fe38b | |
Michieal | 6f05992c8b | |
Michieal | c55332bf42 | |
Michieal | 42f8fbf9b8 | |
Michieal | b0978d275d | |
Michieal | 2b11b5c17a | |
Michieal | 9445b02dcc | |
Michieal | e0d48400a7 | |
Michieal | 742a625c42 | |
Michieal | 95d3f9be07 | |
Michieal | 16d94a88db | |
Michieal | b14f055154 | |
Michieal | 773c760727 | |
AFCMS | 96e83e866c | |
AFCMS | 3bb86fd436 | |
AFCMS | 4acee8e64e | |
AFCMS | bacec2c7e6 | |
AFCMS | a3d459f020 |
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
|
||||
name: "Bug report"
|
||||
about: "File a bug report"
|
||||
labels:
|
||||
|
||||
- unconfirmed
|
||||
- bug
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
<!--
|
||||
What version of MineClone2 are you using? We do not provide support for outdated versions of MineClone2.
|
||||
Current latest version is listed here, at the top:
|
||||
https://git.minetest.land/MineClone2/MineClone2/tags
|
||||
-->
|
||||
MineClone2 version:
|
||||
|
||||
### What happened?
|
||||
Report about the bug! Please send large log snippets as an attachement file.
|
||||
|
||||
### What should happen:
|
||||
Tell us what should happen!
|
||||
|
||||
### Steps to reproduce
|
||||
Tell us how we can reproduce the bug!
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
|
||||
name: "Feature request"
|
||||
about: "File a feature request not in Minecraft"
|
||||
labels:
|
||||
|
||||
- "non-Minecraft feature"
|
||||
- "needs discussion"
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Got a new non-Minecraft feature request? Explain to us why we should consider your idea.
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
### Feature
|
||||
Tell us about your requested feature not in Minecraft!
|
||||
|
||||
### Why
|
||||
Tell us why should we implement it!
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
|
||||
name: "Missing Feature request"
|
||||
about: "File a missing feature request in Minecraft but not in MineClone2"
|
||||
labels:
|
||||
|
||||
- "missing feature"
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thanks for taking the time to fill out this missing feature request!
|
||||
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#rules-about-both-bugs-and-feature-requests
|
||||
|
||||
By submitting this issue, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
### Current feature in Minecraft
|
||||
Tell us about the feature currently in Minecraft! What is it like on Minecraft?
|
||||
|
||||
### Current feature in MineClone2
|
||||
Tell us about the feature currently in MineClone2! What is different?
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
|
||||
name: "Pull request"
|
||||
about: "Submit a pull request"
|
||||
labels:
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please follow our contributing guidelines first:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CONTRIBUTING.md#how-you-can-help-as-a-programmer
|
||||
|
||||
By submitting this pull request, you agree to follow our Code of Conduct:
|
||||
https://git.minetest.land/MineClone2/MineClone2/src/branch/master/CODE_OF_CONDUCT.md
|
||||
-->
|
||||
|
||||
Tell us about your pull request! Reference related issues, if necessary
|
||||
|
||||
### Testing
|
||||
Tell us how to test your changes!
|
|
@ -4,3 +4,5 @@
|
|||
*.blend2
|
||||
*.blend3
|
||||
/.idea/
|
||||
*.xcf
|
||||
.Rproj.user
|
1
API.md
|
@ -41,6 +41,7 @@ A lot of things are possible by using one of the APIs in the mods. Note that not
|
|||
* Beds: `ITEMS/mcl_beds`
|
||||
* Buckets: `ITEMS/mcl_buckets`
|
||||
* Dispenser support: `ITEMS/REDSTONE/mcl_dispensers`
|
||||
* Campfires: `ITEMS/mcl_campfires`
|
||||
|
||||
### Mobs
|
||||
* Mobs: `ENTITIES/mcl_mobs`
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
So you want to contribute to MineClone2?
|
||||
Wow, thank you! :-)
|
||||
|
||||
MineClone2 is maintained by Nicu and Cora. If you have any
|
||||
problems or questions, contact us (See Links section below).
|
||||
MineClone2 is maintained by AncientMariner and Nicu. If you have any
|
||||
problems or questions, contact us on Discord/Matrix (See Links section below).
|
||||
|
||||
You can help with MineClone2's development in many different ways,
|
||||
whether you're a programmer or not.
|
||||
|
@ -76,7 +76,7 @@ in singleplayer, post a screenshot of the message that Minetest showed
|
|||
when the crash happened (or copy the message into your issue). If you
|
||||
are a server admin, you can find error messages in the log file of the
|
||||
server.
|
||||
* Tell us which MineClone2 and Minetest versions you are using.
|
||||
* Tell us which MineClone2 and Minetest versions you are using (from Minetest 5.7 type /ver, for previous versions, check the game.conf or README.md file).
|
||||
* Tell us how to reproduce the problem: What you were doing to trigger
|
||||
the bug, e.g. before the crash happened or what causes the faulty
|
||||
behavior.
|
||||
|
@ -240,7 +240,7 @@ work causes problems, we ask you fix the issues as soon as possible.
|
|||
|
||||
### Changing Gameplay
|
||||
Pull Requests that change gameplay have to be properly researched and
|
||||
need to state their sources. These PRs also need Fleckenstein's approval
|
||||
need to state their sources. These PRs also need the maintainer's approval
|
||||
before they are merged.
|
||||
You can use these sources:
|
||||
|
||||
|
@ -375,7 +375,7 @@ merged.
|
|||
- Resolving conflicts and problems within the community
|
||||
|
||||
#### Current maintainers
|
||||
* Cora - responsible for gameplay review, publishing releases,
|
||||
* AncientMariner - responsible for gameplay review, publishing releases,
|
||||
technical guidelines
|
||||
* Nicu - responsible for community related issues
|
||||
|
||||
|
|
26
CREDITS.md
|
@ -35,7 +35,9 @@
|
|||
* SumianVoice
|
||||
* MrRar
|
||||
* talamh
|
||||
* Faerraven
|
||||
* Faerraven / Michieal
|
||||
* FossFanatic
|
||||
* SmokeyDope
|
||||
|
||||
## Contributors
|
||||
* Laurent Rocher
|
||||
|
@ -80,15 +82,12 @@
|
|||
* aldum
|
||||
* Dieter44
|
||||
* Pepebotella
|
||||
* MrRar
|
||||
* Lazerbeak12345
|
||||
* mrminer
|
||||
* Thunder1035
|
||||
* opfromthestart
|
||||
* snowyu
|
||||
* FaceDeer
|
||||
* Faerraven / Michieal
|
||||
* FossFanatic
|
||||
* Herbert West
|
||||
* GuyLiner
|
||||
* 3raven
|
||||
|
@ -96,6 +95,12 @@
|
|||
* TheOnlyJoeEnderman
|
||||
* Ranko Saotome
|
||||
* Gregor Parzefall
|
||||
* Wbjitscool
|
||||
* b3nderman
|
||||
* CyberMango
|
||||
* gldrk
|
||||
* atomdmac
|
||||
* emptyshore
|
||||
|
||||
## MineClone5
|
||||
* kay27
|
||||
|
@ -147,11 +152,13 @@
|
|||
* jordan4ibanez
|
||||
* paramat
|
||||
* cora
|
||||
* Faerraven / Michieal
|
||||
|
||||
## 3D Models
|
||||
* 22i
|
||||
* tobyplowy
|
||||
* epCode
|
||||
* Faerraven / Michieal
|
||||
|
||||
## Textures
|
||||
* XSSheep
|
||||
|
@ -166,6 +173,8 @@
|
|||
* RandomLegoBrick
|
||||
* cora
|
||||
* Faerraven / Michieal
|
||||
* Nicu
|
||||
* Exhale
|
||||
|
||||
## Translations
|
||||
* Wuzzy
|
||||
|
@ -180,9 +189,13 @@
|
|||
* snowyu
|
||||
* 3raven
|
||||
* SakuraRiu
|
||||
* anarquimico
|
||||
* syl
|
||||
|
||||
## Funders
|
||||
* 40W
|
||||
* bauknecht
|
||||
* Cora
|
||||
|
||||
## Special thanks
|
||||
* celeron55 for creating Minetest
|
||||
|
@ -190,4 +203,7 @@
|
|||
* wsor for working tirelessly in the shadows for the good of all of us, particularly helping with solving contentDB and copyright issues.
|
||||
* The workaholics who spent way too much time writing for the Minecraft Wiki. It's an invaluable resource for creating this game
|
||||
* Notch and Jeb for being the major forces behind Minecraft
|
||||
* Dark Reaven Music (https://soundcloud.com/dark-reaven-music) for the main menu theme (Calmed Cube), which is licensed under https://creativecommons.org/licenses/by-sa/3.0/
|
||||
* Dark Reaven Music (https://soundcloud.com/dark-reaven-music) for the main menu theme (Calmed Cube) and Traitor (horizonchris96), which is licensed under https://creativecommons.org/licenses/by-sa/3.0/
|
||||
* Jester for helping to finely tune MineClone2 (https://www.youtube.com/@Jester-8-bit). Songs: Hailing Forest, Gift, 0dd BL0ck, Flock of One (License CC BY-SA 4.0)
|
||||
* Exhale & Tim Unwin for some wonderful MineClone2 tracks (https://www.youtube.com/channel/UClFo_JDWoG4NGrPQY0JPD_g). Songs: Valley of Ghosts, Lonely Blossom, Farmer (License CC BY-SA 4.0)
|
||||
* Diminixed for 3 fantastic tracks and remastering and leveling volumes. Songs: Afternoon Lullaby (pianowtune02), Spooled (ambientwip02), Never Grow Up (License CC BY-SA 4.0)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
Survive, farm, build, explore, play with friends, and do much more. Inspired by a well known block game, pushing beyond.
|
||||
|
||||
How to play:
|
||||
|
||||
#### Download Minetest
|
||||
- Navigate to https://www.minetest.net/ to download the client.
|
||||
- Once installed, open and select the "Content" tab
|
||||
|
||||
#### Install MineClone2 from ContentDB
|
||||
- Click "Browse Online Content" and filter by Games (select "Games" from the dropdown box)
|
||||
- Find "MineClone2" (should be first on the list or on the first page)
|
||||
- Click the [+] button next to MineClone2 and wait for download to finish
|
||||
- Click "Back to Main Menu"
|
||||
|
||||
#### Create new world and play
|
||||
- Click "Start Game" tab
|
||||
- At the bottom click the MineClone2 icon (the 2 dirt with grass blocks)
|
||||
- Click "New", give your world a name
|
||||
- You can leave seed blank or put in a word of your choice
|
||||
- Select your new world
|
||||
- Click "Play Game" and enjoy!
|
|
@ -0,0 +1,70 @@
|
|||
#Models in Minetest/Mineclone2
|
||||
|
||||
Models are an important part of all entities & unique nodes in Mineclone2. They provide a 3 dimensional map of an object for which textures are then applied to. This document is for modders, it quickly highlights some important information for the software needed to open models in Mineclone2.
|
||||
|
||||
## Minetest Wiki
|
||||
For more detailed information on actually using blender to create and modify models for Minetest/Mineclone2, please visit the Minetest wiki's page on using Blender [Here](https://wiki.minetest.net/Using_Blender)
|
||||
|
||||
##Recommended software
|
||||
|
||||
###Blender
|
||||
|
||||
Blender is a very popular and free modeling software supported on Windows, MacOS, and most Linux distributions. It is recommended to use Blender to create and modify 3D models within the minetest engine.
|
||||
|
||||
Download blender [Here](https://www.blender.org/download/)
|
||||
|
||||
### .b3d addon for blender
|
||||
|
||||
Blitz 3D (.b3d) Is one of the main animated model formats used for entities in the minetest engine. It cannot be imported to blender without a plugin called "Import-Export:Bitz 3D format (.b3d)".
|
||||
|
||||
The most up to date version of this Blender plugin can be downloaded [Here](https://github.com/GreenXenith/io_scene_b3d/releases/tag/f189786)
|
||||
|
||||
##Types of model formats
|
||||
|
||||
###Animated, skinned models
|
||||
* Blitz 3D files (.b3d)
|
||||
|
||||
* Microsoft DirectX (.x) (binary & text, compression is not supported)
|
||||
|
||||
###Static meshes
|
||||
* Wavefront OBJ (.obj)
|
||||
|
||||
Note: The sometimes accompanying .mtl files are not supported and can safely be deleted.
|
||||
|
||||
Note: Do not use .b3d and .x files for static meshes at the moment, Minetest currently spawns animated mesh scene nodes for these, which may result in reduced performance.
|
||||
|
||||
### Supported texture formats
|
||||
|
||||
* .png
|
||||
|
||||
* .jpg
|
||||
|
||||
* .bmp (depreciated, please use .png or .jpg)
|
||||
|
||||
* .tga (depreciated, please use .png or .jpg)
|
||||
|
||||
Note: Any formats not mentioned above but known to work in the past were removed in 5.5.0 and aren't supported anymore.
|
||||
|
||||
##Pros & Cons of .b3d vs .x
|
||||
|
||||
###B3D
|
||||
* [+] Binary format means a small size
|
||||
|
||||
* [-] Difficult to postprocess after exporting
|
||||
|
||||
* [-] Difficult to debug problems
|
||||
|
||||
###X (text version)
|
||||
* [+] Can be parsed easily with lua scripts
|
||||
|
||||
* [+] Can be easily generated by scripts
|
||||
|
||||
* [+] Easy to debug issues (you can just read it)
|
||||
|
||||
* [+] Can be optimized by quantizing some data
|
||||
|
||||
* [-] Blender exporter is kinda buggy and inefficient
|
||||
|
||||
* [-] Probably still bigger than an equivalent .b3d
|
||||
|
||||
Note: Avoid using the binary X format! It's actually just a tokenized version of the ASCII representation, and may actually be less efficient than a sufficiently optimized text .x file!
|
|
@ -2,8 +2,6 @@
|
|||
An unofficial Minecraft-like game for Minetest. Forked from MineClone by davedevils.
|
||||
Developed by many people. Not developed or endorsed by Mojang AB.
|
||||
|
||||
Version: 0.82 (in development)
|
||||
|
||||
### Gameplay
|
||||
You start in a randomly-generated world made entirely of cubes. You can explore
|
||||
the world and dig and build almost every block in the world to create new
|
||||
|
@ -80,7 +78,7 @@ The MineClone2 repository is hosted at Mesehub. To contribute or report issues,
|
|||
|
||||
* Mesehub: <https://git.minetest.land/MineClone2/MineClone2>
|
||||
* Discord: <https://discord.gg/xE4z8EEpDC>
|
||||
* YouTube <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
|
||||
* 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/>
|
||||
|
@ -158,7 +156,7 @@ The following features are incomplete:
|
|||
|
||||
* Some monsters and animals
|
||||
* Redstone-related things
|
||||
* Special minecarts
|
||||
* Some special minecarts (hopper and chest minecarts work)
|
||||
* A couple of non-trivial blocks and items
|
||||
|
||||
Bonus features (not found in Minecraft):
|
||||
|
|
|
@ -1,48 +1,45 @@
|
|||
# MineClone2
|
||||
Un jeu non-officiel similaire à Minecraft pour Minetest. Forké depuis Mineclone par davedevils. Développé par de nombreuses personnes. Ni développé ou supporté par Mojang AB.
|
||||
|
||||
Version: 0.79 (en dévelopment)
|
||||
Un jeu non-officiel similaire à Minecraft pour Minetest. Forké depuis Mineclone par davedevils. Développé par de nombreuses personnes. Pas développé ni supporté par Mojang AB.
|
||||
|
||||
### Gameplay
|
||||
|
||||
Vous atterissez dans un monde fait entièrement de cubes et généré aléatoirement. Vous pouvez explorer le monde, miner et construire presque n'importe quel bloc pour créer de nouvelles structures. Vous pouvez choisir de jouer en "mode survie" dans lequel vous devez combattre des monstres et la faim et progresser lentement dans différents aspects du jeu, comme l'extraction de minerai, l'agriculture, la construction de machines et ainsi de suite. Ou alors vous pouvez jouer en "mode créatif" où vous pouvez construire à peu près n'importe quoi instantanément.
|
||||
|
||||
### Résumé du Gameplay
|
||||
|
||||
* Jeu de type bac-à-sable, sans objetifs
|
||||
* Survie : combattre des monstres hostiles et la faim
|
||||
* Creuser pour du minerai et d'autres trésors
|
||||
* Magie : gagner de l'expérience et enchanter les outils
|
||||
* Utiliser les blocs ramassés pour construire de magnifiques bâtiments, votre imagination est la limite
|
||||
* Ramasser des fleurs (et d'autres sources de teinture) et colorez votre monde
|
||||
* Survie : combattez des monstres hostiles et la faim
|
||||
* Creusez pour du minerai et d'autres trésors
|
||||
* Magie : gagnez de l'expérience et enchantez les outils
|
||||
* Utilisez les blocs ramassés pour construire de magnifiques bâtiments, votre imagination est la seule limite
|
||||
* Ramassez des fleurs (et d'autres sources de teinture) et colorez votre monde
|
||||
* Trouvez des graines et commencez à cultiver
|
||||
* Trouvez ou fabriquez des centaines d'objets
|
||||
* Construisez un réseau ferroviaire complexe et amusez vous avec les wagonnets
|
||||
* Construisez un réseau ferroviaire complexe et amusez-vous avec les wagonnets
|
||||
* En mode créatif vous pouvez construire presque n'importe quoi gratuitement et sans limite
|
||||
|
||||
## Comment jouer (démarrer rapidement)
|
||||
### Commencer
|
||||
* **Frappez un arbre** jusqu'à ce qu'il casse et donne du bois
|
||||
* Placez le **bois dans la grille 2x2** (la "grille de fabrication" de votre menu d'inventaire) et fabriquez 4 planches de bois
|
||||
* Placer les 4 planches de bois dans la grille 2x2 et **fabriquez une table d'artisanat**
|
||||
* **Cliquez droit la table d'artisanat** (icone livre) pour apprendre toutes les recettes possibles
|
||||
* Placer les 4 planches de bois dans la grille 2x2 et **fabriquez un établi**
|
||||
* **Faites un clic droit sur l'établi** (icone livre) pour apprendre toutes les recettes possibles
|
||||
* **Fabriquez une pioche de bois** pour miner la pierre
|
||||
* Différents outils minent différentes sortes de blocs. Essayez les !
|
||||
* Continuez à jouer comme vous voulez. Amusez vous !
|
||||
* Différents outils minent différentes sortes de blocs. Essayez-les !
|
||||
* Continuez à jouer comme vous voulez. Amusez-vous !
|
||||
|
||||
### Agriculture
|
||||
* Trouvez des graines
|
||||
* Fabriquez une houe
|
||||
* Cliquez droit la terre ou des blocs similaires avec la houe pour créer des terres agricoles
|
||||
* Faites un clic droit sur la terre ou des blocs similaires avec la houe pour créer des terres agricoles
|
||||
* Placer des graines sur des terres agricoles et regardez les pousser
|
||||
* Récoltez les plantes une fois matûres
|
||||
* Les terres agricoles proche de l'eau deviennent humides et accélèrent la croissance
|
||||
|
||||
### Four
|
||||
* Fabriquer un Four
|
||||
* Fabriquez un four
|
||||
* Le four permet d'obtenir plus d'objets
|
||||
* L'emplacement du haut doit contienir un objet fondable (par ex : minerai de fer)
|
||||
* L'emplacement du bas doit contienir un objet combustible (par ex : charbon)
|
||||
* L'emplacement du haut doit contenir un objet fondable (par ex : minerai de fer)
|
||||
* L'emplacement du bas doit contenir un objet combustible (par ex : charbon)
|
||||
* Voir le guide d'artisanat pour en apprendre plus sur les objets fondables et combustibles
|
||||
|
||||
### Aide supplémentaire
|
||||
|
@ -62,43 +59,43 @@ Il n'y a pas de support de MineClone2 dans les versions développement de Minete
|
|||
Pour installer MineClone2 (si ce n'est pas déjà fait), déplacez ce dossier dans le dossier “games” de Minetest. Consultez l'aide de Minetest pour en apprendre plus.
|
||||
|
||||
## Liens utiles
|
||||
Le dépôt de MineClone2 est hébergé sur Mesehub. Pour contribuer ou rapporter des problèmes, aller là-bas.
|
||||
Le dépôt de MineClone2 est hébergé sur Mesehub. Pour contribuer ou signaler des problèmes, allez là-bas.
|
||||
|
||||
* Mesehub : <https://git.minetest.land/MineClone2/MineClone2>
|
||||
* Discord : <https://discord.gg/xE4z8EEpDC>
|
||||
* YouTube <https://www.youtube.com/channel/UClI_YcsXMF3KNeJtoBfnk9A>
|
||||
* 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>
|
||||
* Forums Minetest : <https://forum.minetest.net/viewtopic.php?f=50&t=16407>
|
||||
* ContentDB : <https://content.minetest.net/packages/wuzzy/mineclone2/>
|
||||
* OpenCollective : <https://opencollective.com/mineclone2>
|
||||
|
||||
## Objectif
|
||||
* Créer un clone stable, moddable, libre et gratuit basé sur le moteur de jeu Minetest avec des fonctionalités abouties, utilisable à la fois en mode solo et multijoueur. Actuellement, beaucoup des fonctionalités de **Minecraft Java Edition** sont déjà implémentées et leur amélioration est prioritaire sur les nouvelles demandes.
|
||||
* Avec une priorité moindre, implémenter les fonctionalités des versions **Minecraft + OptiFine** (OtiFine autant que supporté par le moteur Minetest). Cela signifie que les fonctionalités présentes dans les versions listées sont priorisées.
|
||||
* Dans l'idéal, créer une expérience performante qui tourne bien sur des ordinateurs à basse performance. Malheureusement, en raison des mécanismes de Minecraft et des limitations du moteur Minetest ainsi que de la petite taille de la communauté de joueurs sur des ordinateurs à basse performances, les optimisations sont difficiles à explorer.
|
||||
* Essentiellement, créer un clone de Minecraft stable, moddable, libre et gratuit basé sur le moteur de jeu Minetest avec des fonctionnalités abouties, utilisable à la fois en mode solo et multijoueur. Actuellement, beaucoup des fonctionnalités de **Minecraft Java Edition** sont déjà implémentées et leur amélioration est prioritaire sur les nouvelles demandes.
|
||||
* Avec une priorité moindre, implémenter les fonctionnalités des versions **Minecraft + OptiFine** (OptiFine autant que supporté par le moteur Minetest). Cela signifie que les fonctionnalités présentes dans les versions listées sont priorisées.
|
||||
* Dans l'idéal, créer une expérience performante qui tourne bien sur des ordinateurs à basse performance. Malheureusement, en raison des mécanismes de Minecraft et des limitations du moteur Minetest ainsi que de la petite taille de la communauté de joueurs sur des ordinateurs à basses performances, les optimisations sont difficiles à explorer.
|
||||
|
||||
## Statut de complétion
|
||||
Ce jeu est actuellement au stade **beta**.
|
||||
Il est jouable mais incomplet en fonctionalités.
|
||||
Il est jouable mais incomplet en fonctionnalités.
|
||||
La rétro-compatibilité n'est pas entièrement garantie, mettre votre monde à jour peut causer de petits bugs.
|
||||
Si vous voulez utiliser la version de développement de MineClone2 en production, la branche master est habituellement relativement stable. Les branches de test fusionnent souvent des pull requests expérimentales et doivent être considérées comme moins stable.
|
||||
|
||||
Les principales fonctionalités suivantes sont disponibles :
|
||||
Les principales fonctionnalités suivantes sont disponibles :
|
||||
|
||||
* Outils, armes
|
||||
* Armure
|
||||
* Système de fabrication : grille 2x2, table d'artisanat (grille 3x3), four, incluant un guide de fabrication
|
||||
* Coffres, grands coffres, coffre ender, boite de shulker
|
||||
* Système de fabrication : grille 2x2, établi (grille 3x3), four, incluant un guide de fabrication
|
||||
* Coffres, grands coffres, coffre ender, boites de Shulker
|
||||
* Fours, entonnoirs
|
||||
* Faim
|
||||
* La plupart des monstres et animaux
|
||||
* Tout les minerais de Minecraft
|
||||
* Tous les minerais de Minecraft
|
||||
* La plupart des blocs de l'overworld
|
||||
* Eau et lave
|
||||
* Météo
|
||||
* 28 biomes + 5 biomes du nether
|
||||
* 28 biomes + 5 biomes du Nether
|
||||
* Le Nether, monde souterrain brûlant dans une autre dimension
|
||||
* Circuits Redstone (partiel)
|
||||
* Effets de Statut (partiel)
|
||||
|
@ -107,10 +104,10 @@ Les principales fonctionalités suivantes sont disponibles :
|
|||
* Brassage, potions, flèches trempées (partiel)
|
||||
* Bâteaux
|
||||
* Feu
|
||||
* Blocs de construction : escaliers, dalles, portes, trappes, barrière, portillon, muret
|
||||
* Blocs de construction : escaliers, dalles, portes, trappes, barrières, portillons, murets
|
||||
* Horloge
|
||||
* Boussole
|
||||
* Eponge
|
||||
* Éponge
|
||||
* Bloc de slime
|
||||
* Petites plantes et pousses
|
||||
* Teintures
|
||||
|
@ -118,26 +115,30 @@ Les principales fonctionalités suivantes sont disponibles :
|
|||
* Blocs de décoration : verre, verre teinté, vitres, barres de fer, terre cuites (et couleurs), têtes et plus
|
||||
* Cadres d'objets
|
||||
* Juke-boxes
|
||||
* Lits
|
||||
* Menu d'inventaire
|
||||
* Inventaire créatif
|
||||
* Agriculture
|
||||
* Livres pour écrire
|
||||
* Commandes
|
||||
* Villages
|
||||
* L'End
|
||||
* et plus !
|
||||
|
||||
Les fonctionalités suivantes sont incomplètes :
|
||||
Les fonctionnalités suivantes sont incomplètes :
|
||||
|
||||
* certains monstres et animaux
|
||||
* certains composants de Redstone
|
||||
* Certains monstres et animaux
|
||||
* Certains composants de Redstone
|
||||
* Wagonnets spéciaux
|
||||
* quelques blocs et objets non-triviaux
|
||||
* Quelques blocs et objets non-triviaux
|
||||
|
||||
Fonctionalités bonus (absentes de Minecraft) :
|
||||
Fonctionnalités bonus (absentes de Minecraft) :
|
||||
|
||||
* Guide d'artisanat intégré au jeu qui montre les recettes d'artisanat et de cuisson
|
||||
* Système d'aide intégré au jeu contenant des informations à propos des techniques de base, blocs, objets et plus
|
||||
* Recettes d'artisanat temporaires. Elles existent uniquement pour rendre des objets accessibles qui ne le seraient pas autrement sauf en mode créatif. Elles seront retirées au cours de l'avancement du développement et de l'ajout de nouvelles fonctionalités.
|
||||
* Recettes d'artisanat temporaires. Elles existent uniquement pour rendre des objets accessibles qui ne le seraient pas autrement sauf en mode créatif. Elles seront retirées au cours de l'avancement du développement et de l'ajout de nouvelles fonctionnalités.
|
||||
* Pousses dans les coffres en mapgen v6
|
||||
* Entièrement moddable (grâce la puissante API lua de Minetest)
|
||||
* Entièrement moddable (grâce la puissante API Lua de Minetest)
|
||||
* Nouveaux blocs et objets :
|
||||
* Outil de recherche, montre l'aide de ce qu'il touche
|
||||
* Plus de dalles et d'escaliers
|
||||
|
@ -149,22 +150,24 @@ Fonctionalités bonus (absentes de Minecraft) :
|
|||
* Avant-poste du Nether (Forteresse)
|
||||
|
||||
Différences techniques avec Minecraft :
|
||||
|
||||
* Limite en hauteur de 31000 blocs (bien plus grand que Minecraft)
|
||||
* Taille horizontale du monde 62000×62000 blocs (bien plus petit que Minecraft mais toujours très grand)
|
||||
* Toujours assez incomplet et buggé
|
||||
* Des blocs, objets, ennemis et fonctionalités manquent
|
||||
* Des blocs, objets, ennemis et fonctionnalités manquent
|
||||
* Quelques objets ont des noms légèrement différents pour être plus faciles à distinguer
|
||||
* Des musiques différentes pour le juke-boxe
|
||||
* Des textures différentes (Pixel Perfection)
|
||||
* Des sons différents (sources diverses)
|
||||
* Un moteur de jeu différent (Minetest)
|
||||
* Des bonus cachés différents
|
||||
|
||||
...et enfin MineClone2 est un logiciel libre !
|
||||
|
||||
## Autres fichiers readme
|
||||
|
||||
* `LICENSE.txt`: Le texte de la license GPLv3
|
||||
* `LICENSE.txt` : Le texte de la licence GPLv3
|
||||
* `CONTRIBUTING.md` : Information pour ceux qui veulent contribuer
|
||||
* `API.md` : Pour les modders Minetest qui veulent modder ce jeu
|
||||
* `LEGAL.md` : Information légale
|
||||
* `CREDITS.md`: Liste des contributeurs
|
||||
* `CREDITS.md` : Liste de toutes les personnes qui ont contribué
|
||||
|
|
70
RELEASE.md
|
@ -1,19 +1,75 @@
|
|||
### Standard Release
|
||||
|
||||
#File to document release steps with a view to evolving into a script
|
||||
|
||||
#Update CREDITS.md
|
||||
#Update version in README.md (soon to be game.conf from of 0.82.0)
|
||||
#Update version in game.conf
|
||||
|
||||
lua tools/generate_ingame_credits.lua
|
||||
|
||||
git add CREDITS.md
|
||||
git add mods/HUD/mcl_credits/people.lua
|
||||
git add game.conf
|
||||
|
||||
git add README.md
|
||||
# To uncomment when applicable
|
||||
#git add game.conf
|
||||
#git add RELEASE.md
|
||||
|
||||
git commit -m "Pre-release update credits and set version 0.81.1"
|
||||
git commit -m "Pre-release update credits and set version 0.83.0"
|
||||
|
||||
git tag 0.81.1
|
||||
git tag 0.83.0
|
||||
|
||||
git push origin 0.81.1
|
||||
git push origin 0.83.0
|
||||
|
||||
#Update version in game.conf to the next version with -SNAPSHOT suffix
|
||||
|
||||
git commit -m "Post-release set version 0.84.0-SNAPSHOT"
|
||||
|
||||
### Hotfix Release
|
||||
|
||||
##### Prepare release branch
|
||||
|
||||
When hotfixing, you should never release new features. Any new code increases risk of new bugs which has additional testing/release concerns.
|
||||
To mitigate this, you just release the last release, and the relevant bug fix. For this, we do the following:
|
||||
|
||||
* Create release branch from the last release tag, push it:
|
||||
|
||||
git checkout -b release/0.82.1 0.82.0
|
||||
|
||||
git push origin release/0.82.1
|
||||
|
||||
##### Prepare feature branch and fix
|
||||
|
||||
* Create feature branch from that release branch (can review it to check only fix is there, nothing else, and use to also merge into master separately)
|
||||
|
||||
git checkout -b hotfix_bug_1_branch
|
||||
|
||||
* Fix crash/serious bug and commit
|
||||
* Push branch and create pr to the release and also the master branch (Do not rebase, to reduce merge conflict risk. Do not delete after first merge or it needs to be repushed)
|
||||
|
||||
##### Update version and tag the release
|
||||
|
||||
* After all fixes are in release branch, pull it locally (best to avoid a merge conflict as feature branch will need to be merged into master also, which already changed version):
|
||||
|
||||
* Update version in game.conf to hotfix version and commit it. Example: version=0.82.1
|
||||
|
||||
* Tag it, push tag and branch:
|
||||
|
||||
git tag 0.82.1
|
||||
|
||||
git push origin 0.82.1
|
||||
|
||||
git push origin release/0.82.1
|
||||
|
||||
Note: If you have to do more than 1 hotfix release, can do it on the same release branch.
|
||||
|
||||
### Release via ContentDB
|
||||
|
||||
* Go to MineClone2 page (https://content.minetest.net/packages/Wuzzy/mineclone2/)
|
||||
* Click +Release
|
||||
* Enter the release tag number in the title and Git reference box. For example (without quotes): "0.82.1"
|
||||
* In the minimum minetest version, put the oldest supported version (as of 14/02/2023 it is 5.5), leave the Maximum minetest version blank
|
||||
* Click save. Release is now live.
|
||||
|
||||
##### Inform people
|
||||
|
||||
* Add a comment to the forum post with the release number and what is involved, and maintainer will update main post.
|
||||
* Add a comment in Discord announcement
|
|
@ -0,0 +1,57 @@
|
|||
# Making Textures In Mineclone2
|
||||
|
||||
Textures are a crucial asset for all items, nodes, and models in mineclone2. This document is for artist who would like to make and modify textures for mineclone2. While no means comprehensive, this document contains the basic important information for beginners to get started with texture curation and optimization.
|
||||
|
||||
## Minetest Wiki
|
||||
For more detailed information on creating and modifing texture packs for Minetest/Mineclone2, please visit the Minetest wiki's page on creating a texture pack. Click [here](https://wiki.minetest.net/Creating_texture_packs) to view the wiki page on creating texture packs.
|
||||
|
||||
## GIMP Tutorials Pixel Art Guide
|
||||
GIMP Tutorials has an excellent guide to making pixel art in GIMP. If you would like further clarification as well as screenshots for what we are about to cover, it is an excellent resource to turn to. Click [here](https://thegimptutorials.com/how-to-make-pixel-art/) to view the guide
|
||||
|
||||
## Recommended Software
|
||||
|
||||
### GIMP
|
||||
|
||||
GIMP (Gnu Image Manipulation Program) is a very popular and free image editing software supported on Windows, MacOS, and most Linux distributions. It is recommended to use GIMP to create and modify textures within the minetest engine.
|
||||
|
||||
Download GIMP [here](http://gimp.org/)
|
||||
|
||||
# Getting Started
|
||||
## Creating a new file
|
||||
the first thing to do is open GIMP and create a new file to work in by opening the File menu and choosing New.
|
||||
|
||||
Choose width of 16 and height of 16 for the image size. While higher resolution textures are possible, The default size is 16x16. It is recommended you use this size as well, as it is universally supported on all systems.
|
||||
|
||||
## Zoom In
|
||||
Next, you'll want to zoom in as the canvas is very small at the default zoom level. To do this either use CTRL + mousewheel, +/-, or navigate to the View menu > zoom > zoom in
|
||||
|
||||
## Configure Grid
|
||||
Now, we'll want to turn on the grid. Open the edit menu and enable the 'show grid' option.
|
||||
|
||||
The default grid size is 10 pixels, we want to change it to a 1 pixel grid. Go to the Image menu and choose 'configure grid.
|
||||
|
||||
In the Spacing section, change both the Horizontal and Vertical pixel settings to 1.00 then click ok and the grid will update.
|
||||
|
||||
## Pencil Tool & Color Picking
|
||||
The most useful brush type for pixel art is the Pencil tool. Its nested under the paintbrush tool in the toolbox, or you can use the keyboard shortcut 'N'.
|
||||
|
||||
Once the pencil tool is selected, navigate to the sliders on the left side of the canvas and change brush size to 1 pixel.
|
||||
|
||||
Now choose a color! You can do this by clicking on the two colored squares under the toolbox. The Color Picker tool is also a good option if you already have a reference image for color palette.
|
||||
|
||||
## How to export optimally
|
||||
|
||||
Once you have finished up a texture and are ready to export it, navigate to the file menu > export as... and make sure the file name extention is .png
|
||||
|
||||
After clicking 'Export', a menu will appear with a bunch of options checked. Make sure to uncheck all of these options!!! This will drastically reduce the file size from multiple kilobytes to a couple of hundred bytes. Finally click 'Export' one more time.
|
||||
|
||||
### Further optimization with OptiPNG
|
||||
For those running a GNU/linux distribution, you most likely have the 'optipng' command available to you. If it does not come with your system by default, the software homepage can be found [here](https://optipng.sourceforge.net/) where you can download and install from source.
|
||||
|
||||
First, Open up the terminal in the directory where your exported texture is located (or navigate to the directory with the 'cd your/directory/path/to/textures'), then run this command
|
||||
```
|
||||
optipng -o7 -zm1-9 -nc -clobber -strip all *.png
|
||||
```
|
||||
This will further optimize all the textures in the directory.
|
||||
|
||||
NOTE: If you would like to further edit a texture that has been optipng'd in GIMP, you must manually set the color palette back to RBG after opening. Navigate to Image menu > Mode > select RGB
|
|
@ -1,4 +1,4 @@
|
|||
title = MineClone 2
|
||||
description = A survival sandbox game. Survive, gather, hunt, build, explore, and do much more.
|
||||
disallowed_mapgens = v6
|
||||
version=MCL2-0.82-indev
|
||||
version=0.84.0-SNAPSHOT
|
BIN
menu/footer.png
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -204,7 +204,7 @@ end
|
|||
|
||||
-- Checks if the given node would drop its useful drop if dug by a given tool.
|
||||
-- Returns true if it will yield its useful drop, false otherwise.
|
||||
function mcl_autogroup.can_harvest(nodename, toolname)
|
||||
function mcl_autogroup.can_harvest(nodename, toolname, player)
|
||||
local ndef = minetest.registered_nodes[nodename]
|
||||
|
||||
if not ndef then
|
||||
|
@ -215,6 +215,10 @@ function mcl_autogroup.can_harvest(nodename, toolname)
|
|||
return true
|
||||
end
|
||||
|
||||
if minetest.get_item_group(nodename, "dig_immediate_piston") >= 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check if it can be dug by tool
|
||||
local tdef = minetest.registered_tools[toolname]
|
||||
if tdef and tdef._mcl_diggroups then
|
||||
|
@ -228,7 +232,9 @@ function mcl_autogroup.can_harvest(nodename, toolname)
|
|||
end
|
||||
|
||||
-- Check if it can be dug by hand
|
||||
local tdef = minetest.registered_tools[""]
|
||||
if not player or not player:is_player() then return false end
|
||||
local name = player:get_inventory():get_stack("hand", 1):get_name()
|
||||
local tdef = minetest.registered_items[name]
|
||||
if tdef then
|
||||
for g, gdef in pairs(tdef._mcl_diggroups) do
|
||||
if ndef.groups[g] then
|
||||
|
@ -260,7 +266,7 @@ local function get_tool_capabilities(tdef)
|
|||
|
||||
-- If the damage group and punch interval from hand is not included,
|
||||
-- then the user will not be able to attack with the tool.
|
||||
local hand_toolcaps = minetest.registered_tools[""].tool_capabilities
|
||||
local hand_toolcaps = mcl_meshhand.survival_hand_tool_caps
|
||||
return {
|
||||
full_punch_interval = hand_toolcaps.full_punch_interval,
|
||||
damage_groups = hand_toolcaps.damage_groups
|
||||
|
@ -280,7 +286,7 @@ end
|
|||
-- would have to add _mcl_autogroup as a dependency which would break the mod
|
||||
-- loading order.
|
||||
function mcl_autogroup.get_groupcaps(toolname, efficiency)
|
||||
local tdef = minetest.registered_tools[toolname]
|
||||
local tdef = minetest.registered_items[toolname]
|
||||
local groupcaps = table.copy(get_tool_capabilities(tdef).groupcaps or {})
|
||||
add_groupcaps(toolname, groupcaps, tdef._mcl_diggroups, efficiency)
|
||||
return groupcaps
|
||||
|
@ -298,6 +304,10 @@ end
|
|||
-- loading order.
|
||||
function mcl_autogroup.get_wear(toolname, diggroup)
|
||||
local tdef = minetest.registered_tools[toolname]
|
||||
if not tdef then
|
||||
minetest.log("warning", "Adding wear for tool: " .. tostring(toolname) .. " failed with diggroup: " .. tostring(diggroup))
|
||||
return nil
|
||||
end
|
||||
local uses = tdef._mcl_diggroups[diggroup].uses
|
||||
return math.ceil(65535 / uses)
|
||||
end
|
||||
|
@ -350,7 +360,7 @@ local function overwrite()
|
|||
end
|
||||
end
|
||||
|
||||
for tname, tdef in pairs(minetest.registered_tools) do
|
||||
for tname, tdef in pairs(minetest.registered_items) do
|
||||
-- Assign groupcaps for digging the registered digging groups
|
||||
-- depending on the _mcl_diggroups in the tool definition
|
||||
if tdef._mcl_diggroups then
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
# mcl_autogroup
|
||||
This mod emulate digging times from mc.
|
||||
|
||||
## mcl_autogroup.can_harvest(nodename, toolname)
|
||||
Return true if <nodename> can be dig with <toolname>.
|
||||
## mcl_autogroup.can_harvest(nodename, toolname, player)
|
||||
Return true if <nodename> can be dig with <toolname> by <player>.
|
||||
* nodename: string, valid nodename
|
||||
* toolname: (optional) string, valid toolname
|
||||
* player: (optinal) ObjectRef, valid player
|
||||
|
||||
## mcl_autogroup.get_groupcaps(toolname, efficiency)
|
||||
This function is used to calculate diggroups for tools.
|
||||
|
|
|
@ -155,7 +155,6 @@ end, true)
|
|||
minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
|
||||
if not damage_enabled then return 0 end
|
||||
if player:get_hp() > 0 then
|
||||
mt_reason.approved = true
|
||||
if hp_change < 0 then
|
||||
mcl_damage.run_damage_callbacks(player, -hp_change, mcl_damage.from_mt(mt_reason))
|
||||
end
|
||||
|
@ -163,9 +162,7 @@ minetest.register_on_player_hpchange(function(player, hp_change, mt_reason)
|
|||
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
|
||||
minetest.log("action","Player "..player:get_player_name().." died at "..minetest.pos_to_string(vector.round(player:get_pos())))
|
||||
end)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ under the LGPLv2.1 license.
|
|||
mcl_explosions = {}
|
||||
|
||||
local mod_fire = minetest.get_modpath("mcl_fire")
|
||||
local explosions_griefing = minetest.settings:get_bool("mcl_explosions_griefing", true)
|
||||
--local CONTENT_FIRE = minetest.get_content_id("mcl_fire:fire")
|
||||
|
||||
local math = math
|
||||
|
@ -191,7 +192,7 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
|
|||
local grief_protected = info.grief_protected
|
||||
|
||||
-- Trace rays for environment destruction
|
||||
if info.griefing then
|
||||
if info.griefing and explosions_griefing then
|
||||
for i = 1, #raydirs do
|
||||
local rpos_x = pos.x
|
||||
local rpos_y = pos.y
|
||||
|
@ -351,6 +352,23 @@ local function trace_explode(pos, strength, raydirs, radius, info, direct, sourc
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Punch End Crystals to make them explode
|
||||
if ent and ent.name == "mcl_end:crystal" then
|
||||
if direct then
|
||||
local puncher = direct:get_luaentity()
|
||||
if puncher and puncher.name == "mcl_end:crystal" then
|
||||
ent.object:punch(direct, 1.0, { -- End Crystal nearby, trigger it.
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = 1},
|
||||
}, nil, nil)
|
||||
else
|
||||
ent.object:remove() -- Direct Exists, but it is not an end crystal, remove crystal.
|
||||
end
|
||||
else
|
||||
ent.object:remove() -- Node exploded the end crystal, remove it.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local airs, fires = {}, {}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=@1 est mort dans une explosion
|
|
@ -0,0 +1,2 @@
|
|||
# textdomain:mcl_explosions
|
||||
@1 was caught in an explosion.=
|
|
@ -3,26 +3,26 @@ mcl_vars = {}
|
|||
|
||||
mcl_vars.redstone_tick = 0.1
|
||||
|
||||
--- GUI / inventory menu settings
|
||||
-- GUI / inventory menu settings
|
||||
mcl_vars.gui_slots = "listcolors[#9990;#FFF7;#FFF0;#000;#FFF]"
|
||||
|
||||
-- nonbg is added as formspec prepend in mcl_formspec_prepend
|
||||
mcl_vars.gui_nonbg = mcl_vars.gui_slots ..
|
||||
"style_type[image_button;border=false;bgimg=mcl_inventory_button9.png;bgimg_pressed=mcl_inventory_button9_pressed.png;bgimg_middle=2,2]"..
|
||||
"style_type[button;border=false;bgimg=mcl_inventory_button9.png;bgimg_pressed=mcl_inventory_button9_pressed.png;bgimg_middle=2,2]"..
|
||||
"style_type[field;textcolor=#323232]"..
|
||||
"style_type[label;textcolor=#323232]"..
|
||||
"style_type[textarea;textcolor=#323232]"..
|
||||
"style_type[checkbox;textcolor=#323232]"
|
||||
mcl_vars.gui_nonbg = table.concat({
|
||||
mcl_vars.gui_slots,
|
||||
"style_type[image_button;border=false;bgimg=mcl_inventory_button9.png;bgimg_pressed=mcl_inventory_button9_pressed.png;bgimg_middle=2,2]",
|
||||
"style_type[button;border=false;bgimg=mcl_inventory_button9.png;bgimg_pressed=mcl_inventory_button9_pressed.png;bgimg_middle=2,2]",
|
||||
"style_type[field;textcolor=#323232]",
|
||||
"style_type[label;textcolor=#323232]",
|
||||
"style_type[textarea;textcolor=#323232]",
|
||||
"style_type[checkbox;textcolor=#323232]",
|
||||
})
|
||||
|
||||
-- Background stuff must be manually added by mods (no formspec prepend)
|
||||
mcl_vars.gui_bg_color = "bgcolor[#00000000]"
|
||||
mcl_vars.gui_bg_img = "background9[1,1;1,1;mcl_base_textures_background9.png;true;7]"
|
||||
|
||||
-- Legacy
|
||||
mcl_vars.inventory_header = ""
|
||||
|
||||
-- Tool wield size
|
||||
mcl_vars.tool_wield_scale = { x = 1.8, y = 1.8, z = 1 }
|
||||
mcl_vars.tool_wield_scale = vector.new(1.8, 1.8, 1)
|
||||
|
||||
-- Mapgen variables
|
||||
local mg_name = minetest.get_mapgen_setting("mg_name")
|
||||
|
@ -35,53 +35,67 @@ mcl_vars.chunksize = math.max(1, tonumber(minetest.get_mapgen_setting("chunksize
|
|||
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, 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
|
||||
|
||||
local central_chunk_min_pos = central_chunk_offset * mcl_vars.MAP_BLOCKSIZE
|
||||
local central_chunk_max_pos = central_chunk_min_pos + mcl_vars.chunk_size_in_nodes - 1
|
||||
local ccfmin = central_chunk_min_pos - mcl_vars.MAP_BLOCKSIZE -- Fullminp/fullmaxp of central chunk, in nodes
|
||||
local ccfmax = central_chunk_max_pos + mcl_vars.MAP_BLOCKSIZE
|
||||
local mapgen_limit_b = math.floor(math.min(mcl_vars.mapgen_limit, mcl_vars.MAX_MAP_GENERATION_LIMIT) / mcl_vars.MAP_BLOCKSIZE)
|
||||
local mapgen_limit_b = math.floor(math.min(mcl_vars.mapgen_limit, mcl_vars.MAX_MAP_GENERATION_LIMIT) /
|
||||
mcl_vars.MAP_BLOCKSIZE)
|
||||
local mapgen_limit_min = -mapgen_limit_b * mcl_vars.MAP_BLOCKSIZE
|
||||
local mapgen_limit_max = (mapgen_limit_b + 1) * mcl_vars.MAP_BLOCKSIZE - 1
|
||||
local numcmin = math.max(math.floor((ccfmin - mapgen_limit_min) / mcl_vars.chunk_size_in_nodes), 0) -- Number of complete chunks from central chunk
|
||||
local numcmax = math.max(math.floor((mapgen_limit_max - ccfmax) / mcl_vars.chunk_size_in_nodes), 0) -- fullminp/fullmaxp to effective mapgen limits.
|
||||
|
||||
mcl_vars.mapgen_edge_min = central_chunk_min_pos - numcmin * mcl_vars.chunk_size_in_nodes
|
||||
mcl_vars.mapgen_edge_max = central_chunk_max_pos + numcmax * mcl_vars.chunk_size_in_nodes
|
||||
|
||||
---@param x integer
|
||||
---@return integer
|
||||
local function coordinate_to_block(x)
|
||||
return math.floor(x / mcl_vars.MAP_BLOCKSIZE)
|
||||
end
|
||||
|
||||
---@param x integer
|
||||
---@return integer
|
||||
local function coordinate_to_chunk(x)
|
||||
return math.floor((coordinate_to_block(x) - central_chunk_offset) / mcl_vars.chunksize)
|
||||
end
|
||||
|
||||
---@param pos Vector
|
||||
---@return Vector
|
||||
function mcl_vars.pos_to_block(pos)
|
||||
return {
|
||||
x = coordinate_to_block(pos.x),
|
||||
y = coordinate_to_block(pos.y),
|
||||
z = coordinate_to_block(pos.z)
|
||||
}
|
||||
return vector.new(
|
||||
coordinate_to_block(pos.x),
|
||||
coordinate_to_block(pos.y),
|
||||
coordinate_to_block(pos.z)
|
||||
)
|
||||
end
|
||||
|
||||
---@param pos Vector
|
||||
---@return Vector
|
||||
function mcl_vars.pos_to_chunk(pos)
|
||||
return {
|
||||
x = coordinate_to_chunk(pos.x),
|
||||
y = coordinate_to_chunk(pos.y),
|
||||
z = coordinate_to_chunk(pos.z)
|
||||
}
|
||||
return vector.new(
|
||||
coordinate_to_chunk(pos.x),
|
||||
coordinate_to_chunk(pos.y),
|
||||
coordinate_to_chunk(pos.z)
|
||||
)
|
||||
end
|
||||
|
||||
local k_positive = math.ceil(mcl_vars.MAX_MAP_GENERATION_LIMIT / mcl_vars.chunk_size_in_nodes)
|
||||
local k_positive_z = k_positive * 2
|
||||
local k_positive_y = k_positive_z * k_positive_z
|
||||
|
||||
---@param pos Vector
|
||||
---@return integer
|
||||
function mcl_vars.get_chunk_number(pos) -- unsigned int
|
||||
local c = mcl_vars.pos_to_chunk(pos)
|
||||
return
|
||||
(c.y + k_positive) * k_positive_y +
|
||||
return (c.y + k_positive) * k_positive_y +
|
||||
(c.z + k_positive) * k_positive_z +
|
||||
c.x + k_positive
|
||||
end
|
||||
|
@ -117,11 +131,8 @@ elseif singlenode then
|
|||
mcl_vars.mg_bedrock_is_rough = false
|
||||
else
|
||||
-- Classic superflat
|
||||
local ground = minetest.get_mapgen_setting("mgflat_ground_level")
|
||||
ground = tonumber(ground)
|
||||
if not ground then
|
||||
ground = 8
|
||||
end
|
||||
local ground = tonumber(minetest.get_mapgen_setting("mgflat_ground_level")) or 8
|
||||
|
||||
mcl_vars.mg_overworld_min = ground - 3
|
||||
mcl_vars.mg_overworld_max_official = mcl_vars.mg_overworld_min + minecraft_height_limit
|
||||
mcl_vars.mg_bedrock_overworld_min = mcl_vars.mg_overworld_min
|
||||
|
@ -181,6 +192,8 @@ minetest.craftitemdef_default.stack_max = 64
|
|||
math.randomseed(os.time())
|
||||
|
||||
local chunks = {} -- intervals of chunks generated
|
||||
|
||||
---@param pos Vector
|
||||
function mcl_vars.add_chunk(pos)
|
||||
local n = mcl_vars.get_chunk_number(pos) -- unsigned int
|
||||
local prev
|
||||
|
@ -207,6 +220,9 @@ function mcl_vars.add_chunk(pos)
|
|||
end
|
||||
chunks[#chunks + 1] = { n, n }
|
||||
end
|
||||
|
||||
---@param pos Vector
|
||||
---@return boolean
|
||||
function mcl_vars.is_generated(pos)
|
||||
local n = mcl_vars.get_chunk_number(pos) -- unsigned int
|
||||
for i, d in pairs(chunks) do
|
||||
|
@ -217,47 +233,46 @@ function mcl_vars.is_generated(pos)
|
|||
return false
|
||||
end
|
||||
|
||||
-- "Trivial" (actually NOT) function to just read the node and some stuff to not just return "ignore", like mt 5.4 does.
|
||||
-- p: Position, if it's wrong, {name="error"} node will return.
|
||||
-- force: optional (default: false) - Do the maximum to still read the node within us_timeout.
|
||||
-- us_timeout: optional (default: 244 = 0.000244 s = 1/80/80/80), set it at least to 3000000 to let mapgen to finish its job.
|
||||
--
|
||||
-- returns node definition, eg. {name="air"}. Unfortunately still can return {name="ignore"}.
|
||||
function mcl_vars.get_node(p, force, us_timeout)
|
||||
---"Trivial" (actually NOT) function to just read the node and some stuff to not just return "ignore", like mt 5.4 does.
|
||||
---@param pos Vector Position, if it's wrong, `{name="error"}` node will return.
|
||||
---@param force? boolean Optional (default: `false`), Do the maximum to still read the node within us_timeout.
|
||||
---@param us_timeout? number Optional (default: `244 = 0.000244 s = 1/80/80/80`), set it at least to `3000000` to let mapgen to finish its job
|
||||
---@return node # Node definition, eg. `{name="air"}`. Unfortunately still can return `{name="ignore"}`.
|
||||
---@nodiscard
|
||||
function mcl_vars.get_node(pos, force, us_timeout)
|
||||
-- check initial circumstances
|
||||
if not p or not p.x or not p.y or not p.z then return {name="error"} end
|
||||
if not pos or not pos.x or not pos.y or not pos.z then return { name = "error" } end
|
||||
|
||||
-- try common way
|
||||
local node = minetest.get_node(p)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name ~= "ignore" then
|
||||
return node
|
||||
end
|
||||
|
||||
-- copy table to get sure it won't changed by other threads
|
||||
local pos = {x=p.x,y=p.y,z=p.z}
|
||||
-- copy vector to get sure it won't changed by other threads
|
||||
local pos_copy = vector.copy(pos)
|
||||
|
||||
-- try LVM
|
||||
minetest.get_voxel_manip():read_from_map(pos, pos)
|
||||
node = minetest.get_node(pos)
|
||||
minetest.get_voxel_manip():read_from_map(pos_copy, pos_copy)
|
||||
node = minetest.get_node(pos_copy)
|
||||
if node.name ~= "ignore" or not force then
|
||||
return node
|
||||
end
|
||||
|
||||
-- all ways failed - need to emerge (or forceload if generated)
|
||||
local us_timeout = us_timeout or 244
|
||||
if mcl_vars.is_generated(pos) then
|
||||
if mcl_vars.is_generated(pos_copy) then
|
||||
minetest.chat_send_all("IMPOSSIBLE! Please report this to MCL2 issue tracker!")
|
||||
minetest.forceload_block(pos)
|
||||
minetest.forceload_block(pos_copy)
|
||||
else
|
||||
minetest.emerge_area(pos, pos)
|
||||
minetest.emerge_area(pos_copy, pos_copy)
|
||||
end
|
||||
|
||||
local t = minetest.get_us_time()
|
||||
|
||||
node = minetest.get_node(pos)
|
||||
node = minetest.get_node(pos_copy)
|
||||
|
||||
while (not node or node.name == "ignore") and (minetest.get_us_time() - t < us_timeout) do
|
||||
node = minetest.get_node(pos)
|
||||
while (not node or node.name == "ignore") and (minetest.get_us_time() - t < (us_timeout or 244)) do
|
||||
node = minetest.get_node(pos_copy)
|
||||
end
|
||||
|
||||
return node
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Oxidization API for MineClone 2
|
||||
This mods adds the oxidization api, so that modders can easily use the same features that copper uses.
|
||||
|
||||
## API
|
||||
To take advantage of the actual oxidization, put `oxidizable = 1` into the list of groups for the oxidizable node.
|
||||
You would also need to put `_mcl_oxidized_variant = itemstring of node this node will oxidize into` into the node definition.
|
||||
For example, a copper block oxidizes into exposed copper, so the defintion would be `_mcl_oxidized_variant = "mcl_copper:block_exposed"`.
|
||||
|
||||
To utilize the ability to wax the block for protection from oxidization, put `mcl_waxed_variant = item string of waxed variant of node` into the node definition table.
|
||||
For example, Copper Blocks have the definition arguement of `_mcl_waxed_variant = "mcl_copper:waxed_block"`.
|
||||
|
||||
For waxed nodes, scraping is easy. Start by putting `waxed = 1` into the list of groups of the waxed node.
|
||||
Next put `_mcl_stripped_variant = item string of the unwaxed variant of the node` into the defintion table.
|
||||
Wxaed Copper Blocks can be scrapped into normal Copper Blocks because of the definition `_mcl_stripped_variant = "mcl_copper:block"`.
|
|
@ -0,0 +1,12 @@
|
|||
minetest.register_abm({
|
||||
label = "Oxidatize Nodes",
|
||||
nodenames = { "group:oxidizable" },
|
||||
interval = 500,
|
||||
chance = 3,
|
||||
action = function(pos, node)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
if def and def._mcl_oxidized_variant then
|
||||
minetest.set_node(pos, { name = def._mcl_oxidized_variant, param2 = node.param2 })
|
||||
end
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,4 @@
|
|||
name = mcl_oxidation
|
||||
title = Oxidation API for MineClone 2
|
||||
author = PrairieWind, N011, Michael
|
||||
description = API to allow oxidizing different nodes.
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 126 B |
Before Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 148 B |
Before Width: | Height: | Size: 154 B |
Before Width: | Height: | Size: 155 B |
Before Width: | Height: | Size: 165 B |
|
@ -34,6 +34,63 @@ function mcl_util.mcl_log (message, module, bypass_default_logger)
|
|||
end
|
||||
end
|
||||
|
||||
local player_timers = {}
|
||||
|
||||
-- This is a dtime timer than can be used in on_step functions so it works every x seconds
|
||||
-- self - Object you want to store timer data on. E.g. mob or a minecart, or player_name
|
||||
-- dtime - The time since last run of on_step, should be passed in to function
|
||||
-- timer_name - This is the name of the timer and also the key to store the data. No spaces + lowercase.
|
||||
-- threshold - The time before it returns successful. 0.2 if you want to run it 5 times a second.
|
||||
function mcl_util.check_dtime_timer(self, dtime, timer_name, threshold)
|
||||
if not self or not threshold or not dtime then return end
|
||||
if not timer_name or timer_name == "" then return end
|
||||
|
||||
if type(self) == "string" then
|
||||
local player_name = self
|
||||
if not player_timers[player_name] then
|
||||
player_timers[player_name] = {}
|
||||
end
|
||||
self = player_timers[player_name]
|
||||
end
|
||||
|
||||
if not self._timers then
|
||||
self._timers = {}
|
||||
end
|
||||
|
||||
if not self._timers[timer_name] then
|
||||
self._timers[timer_name] = 0
|
||||
else
|
||||
self._timers[timer_name] = self._timers[timer_name] + dtime
|
||||
--minetest.log("dtime: " .. tostring(self._timers[timer_name]))
|
||||
end
|
||||
|
||||
if self._timers[timer_name] > threshold then
|
||||
--minetest.log("Over threshold")
|
||||
self._timers[timer_name] = 0
|
||||
return true
|
||||
--else
|
||||
--minetest.log("Not over threshold")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Minetest 5.3.0 or less can only measure the light level. This came in at 5.4
|
||||
-- This function has been known to fail in multiple places so the error handling is added increase safety and improve
|
||||
-- debugging. See:
|
||||
-- https://git.minetest.land/MineClone2/MineClone2/issues/1392
|
||||
function mcl_util.get_natural_light (pos, time)
|
||||
local status, retVal = pcall(minetest.get_natural_light, pos, time)
|
||||
if status then
|
||||
return retVal
|
||||
else
|
||||
minetest.log("warning", "Failed to get natural light at pos: " .. dump(pos) .. ", time: " .. dump(time))
|
||||
if (pos) then
|
||||
local node = minetest.get_node(pos)
|
||||
minetest.log("warning", "Node at pos: " .. dump(node.name))
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function mcl_util.file_exists(name)
|
||||
if type(name) ~= "string" then return end
|
||||
|
@ -557,6 +614,11 @@ function mcl_util.deal_damage(target, damage, mcl_reason)
|
|||
end
|
||||
end
|
||||
|
||||
local is_immortal = target:get_armor_groups().immortal or 0
|
||||
if is_immortal>0 then
|
||||
return
|
||||
end
|
||||
|
||||
local hp = target:get_hp()
|
||||
|
||||
if hp > 0 then
|
||||
|
@ -704,8 +766,10 @@ assert(not close_enough(test_eh,test_eh_diff))
|
|||
assert(not close_enough(test_nt, test_nt_diff)) --no floats involved here
|
||||
|
||||
--tests for properties_changed
|
||||
local test_properties_set1={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
|
||||
local test_properties_set2={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
|
||||
local test_properties_set1 = {collisionbox = {-0.35, 0, -0.35, 0.35, 0.8, 0.35}, eye_height = 0.65,
|
||||
nametag_color = {r = 225, b = 225, a = 225, g = 225}}
|
||||
local test_properties_set2 = {collisionbox = {-0.35, 0, -0.35, 0.35, 0.8, 0.35}, eye_height = 1.35,
|
||||
nametag_color = {r = 225, b = 225, a = 225, g = 225}}
|
||||
|
||||
local test_p1, _ = props_changed(test_properties_set1, test_properties_set1)
|
||||
local test_p2, _ = props_changed(test_properties_set1, test_properties_set2)
|
||||
|
@ -728,3 +792,312 @@ function mcl_util.set_bone_position(obj, bone, pos, rot)
|
|||
obj:set_bone_position(bone, pos or current_pos, rot or current_rot)
|
||||
end
|
||||
end
|
||||
|
||||
---Return a function to use in `on_place`.
|
||||
---
|
||||
---Allow to bypass the `buildable_to` node field in a `on_place` callback.
|
||||
---
|
||||
---You have to make sure that the nodes you return true for have `buildable_to = true`.
|
||||
---@param func fun(node_name: string): boolean Return `true` if node must not replace the buildable_to node which have `node_name`
|
||||
---@return fun(itemstack: ItemStack, placer: ObjectRef, pointed_thing: pointed_thing, param2: integer): ItemStack?
|
||||
function mcl_util.bypass_buildable_to(func)
|
||||
--------------------------
|
||||
-- MINETEST CODE: UTILS --
|
||||
--------------------------
|
||||
|
||||
local function copy_pointed_thing(pointed_thing)
|
||||
return {
|
||||
type = pointed_thing.type,
|
||||
above = pointed_thing.above and vector.copy(pointed_thing.above),
|
||||
under = pointed_thing.under and vector.copy(pointed_thing.under),
|
||||
ref = pointed_thing.ref,
|
||||
}
|
||||
end
|
||||
|
||||
local function user_name(user)
|
||||
return user and user:get_player_name() or ""
|
||||
end
|
||||
|
||||
-- Returns a logging function. For empty names, does not log.
|
||||
local function make_log(name)
|
||||
return name ~= "" and minetest.log or function() end
|
||||
end
|
||||
|
||||
local function check_attached_node(p, n, group_rating)
|
||||
local def = core.registered_nodes[n.name]
|
||||
local d = vector.zero()
|
||||
if group_rating == 3 then
|
||||
-- always attach to floor
|
||||
d.y = -1
|
||||
elseif group_rating == 4 then
|
||||
-- always attach to ceiling
|
||||
d.y = 1
|
||||
elseif group_rating == 2 then
|
||||
-- attach to facedir or 4dir direction
|
||||
if (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir") then
|
||||
-- Attach to whatever facedir is "mounted to".
|
||||
-- For facedir, this is where tile no. 5 point at.
|
||||
|
||||
-- The fallback vector here is in case 'facedir to dir' is nil due
|
||||
-- to voxelmanip placing a wallmounted node without resetting a
|
||||
-- pre-existing param2 value that is out-of-range for facedir.
|
||||
-- The fallback vector corresponds to param2 = 0.
|
||||
d = core.facedir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
elseif (def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") then
|
||||
-- Similar to facedir handling
|
||||
d = core.fourdir_to_dir(n.param2) or vector.new(0, 0, 1)
|
||||
end
|
||||
elseif def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted" then
|
||||
-- Attach to whatever this node is "mounted to".
|
||||
-- This where tile no. 2 points at.
|
||||
|
||||
-- The fallback vector here is used for the same reason as
|
||||
-- for facedir nodes.
|
||||
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
|
||||
else
|
||||
d.y = -1
|
||||
end
|
||||
local p2 = vector.add(p, d)
|
||||
local nn = core.get_node(p2).name
|
||||
local def2 = core.registered_nodes[nn]
|
||||
if def2 and not def2.walkable then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return function(itemstack, placer, pointed_thing, param2)
|
||||
-------------------
|
||||
-- MINETEST CODE --
|
||||
-------------------
|
||||
local def = itemstack:get_definition()
|
||||
if def.type ~= "node" or pointed_thing.type ~= "node" then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local under = pointed_thing.under
|
||||
local oldnode_under = minetest.get_node_or_nil(under)
|
||||
local above = pointed_thing.above
|
||||
local oldnode_above = minetest.get_node_or_nil(above)
|
||||
local playername = user_name(placer)
|
||||
local log = make_log(playername)
|
||||
|
||||
if not oldnode_under or not oldnode_above then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in unloaded position " .. minetest.pos_to_string(above))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local olddef_under = minetest.registered_nodes[oldnode_under.name]
|
||||
olddef_under = olddef_under or minetest.nodedef_default
|
||||
local olddef_above = minetest.registered_nodes[oldnode_above.name]
|
||||
olddef_above = olddef_above or minetest.nodedef_default
|
||||
|
||||
if not olddef_above.buildable_to and not olddef_under.buildable_to then
|
||||
log("info", playername .. " tried to place"
|
||||
.. " node in invalid position " .. minetest.pos_to_string(above)
|
||||
.. ", replacing " .. oldnode_above.name)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
---------------------
|
||||
-- CUSTOMIZED CODE --
|
||||
---------------------
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = vector.copy(above)
|
||||
|
||||
-- If node under is buildable_to, check for callback result and place into it instead
|
||||
if olddef_under.buildable_to and not func(oldnode_under.name) then
|
||||
log("info", "node under is buildable to")
|
||||
place_to = vector.copy(under)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- MINETEST CODE --
|
||||
-------------------
|
||||
|
||||
if minetest.is_protected(place_to, playername) then
|
||||
log("action", playername
|
||||
.. " tried to place " .. def.name
|
||||
.. " at protected position "
|
||||
.. minetest.pos_to_string(place_to))
|
||||
minetest.record_protection_violation(place_to, playername)
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local oldnode = minetest.get_node(place_to)
|
||||
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
|
||||
|
||||
-- Calculate direction for wall mounted stuff like torches and signs
|
||||
if def.place_param2 ~= nil then
|
||||
newnode.param2 = def.place_param2
|
||||
elseif (def.paramtype2 == "wallmounted" or
|
||||
def.paramtype2 == "colorwallmounted") and not param2 then
|
||||
local dir = vector.subtract(under, above)
|
||||
newnode.param2 = minetest.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif (def.paramtype2 == "facedir" or
|
||||
def.paramtype2 == "colorfacedir" or
|
||||
def.paramtype2 == "4dir" or
|
||||
def.paramtype2 == "color4dir") and not param2 then
|
||||
local placer_pos = placer and placer:get_pos()
|
||||
if placer_pos then
|
||||
local dir = vector.subtract(above, placer_pos)
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
log("info", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
local metatable = itemstack:get_meta():to_table().fields
|
||||
|
||||
-- Transfer color information
|
||||
if metatable.palette_index and not def.place_param2 then
|
||||
local color_divisor = nil
|
||||
if def.paramtype2 == "color" then
|
||||
color_divisor = 1
|
||||
elseif def.paramtype2 == "colorwallmounted" then
|
||||
color_divisor = 8
|
||||
elseif def.paramtype2 == "colorfacedir" then
|
||||
color_divisor = 32
|
||||
elseif def.paramtype2 == "color4dir" then
|
||||
color_divisor = 4
|
||||
elseif def.paramtype2 == "colordegrotate" then
|
||||
color_divisor = 32
|
||||
end
|
||||
if color_divisor then
|
||||
local color = math.floor(metatable.palette_index / color_divisor)
|
||||
local other = newnode.param2 % color_divisor
|
||||
newnode.param2 = color * color_divisor + other
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the node is attached and if it can be placed there
|
||||
local an = minetest.get_item_group(def.name, "attached_node")
|
||||
if an ~= 0 and
|
||||
not check_attached_node(place_to, newnode, an) then
|
||||
log("action", "attached node " .. def.name ..
|
||||
" cannot be placed at " .. minetest.pos_to_string(place_to))
|
||||
return itemstack
|
||||
end
|
||||
|
||||
log("action", playername .. " places node "
|
||||
.. def.name .. " at " .. minetest.pos_to_string(place_to))
|
||||
|
||||
-- Add node and update
|
||||
minetest.add_node(place_to, newnode)
|
||||
|
||||
-- Play sound if it was done by a player
|
||||
if playername ~= "" and def.sounds and def.sounds.place then
|
||||
minetest.sound_play(def.sounds.place, {
|
||||
pos = place_to,
|
||||
exclude_player = playername,
|
||||
}, true)
|
||||
end
|
||||
|
||||
local take_item = true
|
||||
|
||||
-- Run callback
|
||||
if def.after_place_node then
|
||||
-- Deepcopy place_to and pointed_thing because callback can modify it
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if def.after_place_node(place_to_copy, placer, itemstack,
|
||||
pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
-- Run script hook
|
||||
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
||||
-- Deepcopy pos, node and pointed_thing because callback can modify them
|
||||
local place_to_copy = vector.copy(place_to)
|
||||
local newnode_copy = {name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
|
||||
local oldnode_copy = {name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
|
||||
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
|
||||
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
|
||||
take_item = false
|
||||
end
|
||||
end
|
||||
|
||||
if take_item then
|
||||
itemstack:take_item()
|
||||
end
|
||||
return itemstack
|
||||
end
|
||||
end
|
||||
|
||||
--[[Check for a protection violation in a given area.
|
||||
--
|
||||
-- Applies is_protected() to a 3D lattice of points in the defined volume. The points are spaced
|
||||
-- evenly throughout the volume and have a spacing similar to, but no larger than, "interval".
|
||||
--
|
||||
-- @param pos1 A position table of the area volume's first edge.
|
||||
-- @param pos2 A position table of the area volume's second edge.
|
||||
-- @param player The player performing the action.
|
||||
-- @param interval Optional. Max spacing between checked points at the volume.
|
||||
-- Default: Same as minetest.is_area_protected.
|
||||
--
|
||||
-- @return true on protection violation detection. false otherwise.
|
||||
--
|
||||
-- @notes *All corners and edges of the defined volume are checked.
|
||||
]]
|
||||
function mcl_util.check_area_protection(pos1, pos2, player, interval)
|
||||
local name = player and player:get_player_name() or ""
|
||||
|
||||
local protected_pos = minetest.is_area_protected(pos1, pos2, name, interval)
|
||||
if protected_pos then
|
||||
minetest.record_protection_violation(protected_pos, name)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--[[Check for a protection violation on a single position.
|
||||
--
|
||||
-- @param position A position table to check for protection violation.
|
||||
-- @param player The player performing the action.
|
||||
--
|
||||
-- @return true on protection violation detection. false otherwise.
|
||||
]]
|
||||
function mcl_util.check_position_protection(position, player)
|
||||
local name = player and player:get_player_name() or ""
|
||||
|
||||
if minetest.is_protected(position, name) then
|
||||
minetest.record_protection_violation(position, name)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local palette_indexes = {grass_palette_index = 0, foliage_palette_index = 0, water_palette_index = 0}
|
||||
function mcl_util.get_palette_indexes_from_pos(pos)
|
||||
local biome_data = minetest.get_biome_data(pos)
|
||||
local biome = biome_data.biome
|
||||
local biome_name = minetest.get_biome_name(biome)
|
||||
local reg_biome = minetest.registered_biomes[biome_name]
|
||||
if reg_biome and reg_biome._mcl_grass_palette_index and reg_biome._mcl_foliage_palette_index and reg_biome._mcl_water_palette_index then
|
||||
local gpi = reg_biome._mcl_grass_palette_index
|
||||
local fpi = reg_biome._mcl_foliage_palette_index
|
||||
local wpi = reg_biome._mcl_water_palette_index
|
||||
local palette_indexes = {grass_palette_index = gpi, foliage_palette_index = fpi, water_palette_index = wpi}
|
||||
return palette_indexes
|
||||
else
|
||||
return palette_indexes
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_util.get_colorwallmounted_rotation(pos)
|
||||
local colorwallmounted_node = minetest.get_node(pos)
|
||||
for i = 0, 32, 1 do
|
||||
local colorwallmounted_rotation = colorwallmounted_node.param2 - (i * 8)
|
||||
if colorwallmounted_rotation < 6 then
|
||||
return colorwallmounted_rotation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -437,9 +437,9 @@ cboat.selectionbox = {-0.7, -0.15, -0.7, 0.7, 0.75, 0.7}
|
|||
minetest.register_entity("mcl_boats:chest_boat", cboat)
|
||||
mcl_entity_invs.register_inv("mcl_boats:chest_boat","Boat",27)
|
||||
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian", "boat_mangrove", "chest_boat", "chest_boat_spruce", "chest_boat_birch", "chest_boat_jungle", "chest_boat_acacia", "chest_boat_dark_oak", "chest_boat_mangrove" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat"), S("Mangrove Boat"), S("Oak Chest Boat"), S("Spruce Chest Boat"), S("Birch Chest Boat"), S("Jungle Chest Boat"), S("Acacia Chest Boat"), S("Dark Oak Chest Boat"), S("Mangrove Chest Boat") }
|
||||
local craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian", "mcl_mangrove:mangrove_wood" }
|
||||
local boat_ids = { "boat", "boat_spruce", "boat_birch", "boat_jungle", "boat_acacia", "boat_dark_oak", "boat_obsidian", "boat_mangrove", "boat_cherry", "chest_boat", "chest_boat_spruce", "chest_boat_birch", "chest_boat_jungle", "chest_boat_acacia", "chest_boat_dark_oak", "chest_boat_mangrove", "chest_boat_cherry" }
|
||||
local names = { S("Oak Boat"), S("Spruce Boat"), S("Birch Boat"), S("Jungle Boat"), S("Acacia Boat"), S("Dark Oak Boat"), S("Obsidian Boat"), S("Mangrove Boat"), S("Cherry Boat"), S("Oak Chest Boat"), S("Spruce Chest Boat"), S("Birch Chest Boat"), S("Jungle Chest Boat"), S("Acacia Chest Boat"), S("Dark Oak Chest Boat"), S("Mangrove Chest Boat"), S("Cherry Chest Boat") }
|
||||
local craftstuffs = { "mcl_core:wood", "mcl_core:sprucewood", "mcl_core:birchwood", "mcl_core:junglewood", "mcl_core:acaciawood", "mcl_core:darkwood", "mcl_core:obsidian", "mcl_mangrove:mangrove_wood", "mcl_cherry_blossom:cherrywood" }
|
||||
|
||||
for b=1, #boat_ids do
|
||||
local itemstring = "mcl_boats:"..boat_ids[b]
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# textdomain: mcl_boats
|
||||
Acacia Boat=Akaciebåd
|
||||
Birch Boat=Birkebåd
|
||||
Boat=Båd
|
||||
Boats are used to travel on the surface of water.=Både blier brugt til at rejse på vandoverflader.
|
||||
Dark Oak Boat=Mørk egetræsbåd
|
||||
Jungle Boat=Junglebåd
|
||||
Oak Boat=Egetræsbå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.=Højre-klik på en vand for at placere båden. Højre-klik på båden for at gå ombord. Brug [Left] og [Right] til at styre. [Forwards] for at øge hastigheden, og [Backwards] for at sænke farten eller sejle bagud. Brug [Sneak] for at forlade båden, slå båden for at lave den om til en genstand.
|
||||
Spruce Boat=Granbåd
|
||||
Water vehicle=Vandfartøj
|
||||
Sneak to dismount=Snig for at stige ud
|
||||
Obsidian Boat=Obsidianbåd
|
|
@ -8,3 +8,6 @@ Jungle Boat=Barca de la selva
|
|||
Oak Boat=Barca de roble
|
||||
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.=Haga clic derecho en una fuente de agua para colocar el barco. Haga clic derecho en el barco para entrar. Utilice [Izquierda] y [Derecha] para dirigir, [Adelante] para acelerar y [Atrás] para reducir la velocidad o retroceder. Haga clic derecho en el barco nuevamente para dejarlo, golpee el barco para que se caiga como un artículo.
|
||||
Spruce Boat=Barca de abeto
|
||||
Water vehicle=Vehículo acuático
|
||||
Sneak to dismount=Agáchate para bajar
|
||||
Obsidian Boat=Barca de obsidiana
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
# textdomain: mcl_boats
|
||||
Acacia Boat=Bateau en Acacia
|
||||
Birch Boat=Bateau en Bouleau
|
||||
Acacia Boat=Bateau en acacia
|
||||
Birch Boat=Bateau en bouleau
|
||||
Boat=Bateau
|
||||
Boats are used to travel on the surface of water.=Les bateaux sont utilisés pour voyager à la surface de l'eau.
|
||||
Dark Oak Boat=Bateau en Chêne Noir
|
||||
Jungle Boat=Bateau en Acajou
|
||||
Oak Boat=Bateau en Chêne
|
||||
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. 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
|
||||
Spruce Boat=Bateau en sapin
|
||||
Water vehicle=Véhicule aquatique
|
||||
Sneak to dismount=Se baisser pour descendre
|
||||
Obsidian Boat=Bateau en Obsidienne
|
||||
Obsidian Boat=Bateau en obsidienne
|
||||
Mangrove Boat=Bateau en palétuvier
|
||||
Oak Chest Boat=Bateau en chêne avec coffre
|
||||
Spruce Chest Boat=Bateau en sapin avec coffre
|
||||
Birch Chest Boat=Bateau en bouleau avec coffre
|
||||
Jungle Chest Boat=Bateau en acajou avec coffre
|
||||
Acacia Chest Boat=Bateau en acacia avec coffre
|
||||
Dark Oak Chest Boat=Bateau en chêne noir avec coffre
|
||||
Mangrove Chest Boat=Bateau en palétuvier avec coffre
|
||||
|
|
|
@ -11,3 +11,13 @@ Spruce Boat=
|
|||
Water vehicle=
|
||||
Sneak to dismount=
|
||||
Obsidian Boat=
|
||||
Mangrove Boat=
|
||||
Cherry Boat=
|
||||
Oak Chest Boat=
|
||||
Spruce Chest Boat=
|
||||
Birch Chest Boat=
|
||||
Jungle Chest Boat=
|
||||
Acacia Chest Boat=
|
||||
Dark Oak Chest Boat=
|
||||
Mangrove Chest Boat=
|
||||
Cherry Chest Boat=
|
Before Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 267 B |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -5,7 +5,12 @@ function mcl_burning.get_storage(obj)
|
|||
end
|
||||
|
||||
function mcl_burning.is_burning(obj)
|
||||
local storage = mcl_burning.get_storage(obj)
|
||||
if storage then
|
||||
return mcl_burning.get_storage(obj).burn_time
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function mcl_burning.is_affected_by_rain(obj)
|
||||
|
@ -153,6 +158,11 @@ function mcl_burning.extinguish(obj)
|
|||
end
|
||||
|
||||
function mcl_burning.tick(obj, dtime, storage)
|
||||
if not storage then
|
||||
minetest.log("warning", "No storage for burning tick. Should not happen: " .. dump(obj))
|
||||
return
|
||||
end
|
||||
|
||||
if storage.burn_time then
|
||||
storage.burn_time = storage.burn_time - dtime
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 blev smadret af en nedfaldende ambolt.
|
||||
@1 was smashed by a falling block.=@1 blev smadret af en nedfaldende blok.
|
|
@ -0,0 +1,3 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 fue aplastado por un yunque.
|
||||
@1 was smashed by a falling block.=@1 fue aplastado por un bloque.
|
|
@ -0,0 +1,3 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=@1 a été écrasé par une enclume
|
||||
@1 was smashed by a falling block.=@1 a été écrasé par un bloc
|
|
@ -0,0 +1,3 @@
|
|||
# textdomain: mcl_falling_nodes
|
||||
@1 was smashed by a falling anvil.=
|
||||
@1 was smashed by a falling block.=
|
|
@ -7,31 +7,19 @@ local pool = {}
|
|||
local tick = false
|
||||
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_item_entities",false)
|
||||
local function mcl_log (message)
|
||||
if LOGGING_ON then
|
||||
mcl_util.mcl_log (message, "[Item Entities]", true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local name
|
||||
name = player:get_player_name()
|
||||
pool[name] = 0
|
||||
pool[player:get_player_name()] = 0
|
||||
end)
|
||||
|
||||
minetest.register_on_leaveplayer(function(player)
|
||||
local name
|
||||
name = player:get_player_name()
|
||||
pool[name] = nil
|
||||
pool[player:get_player_name()] = nil
|
||||
end)
|
||||
|
||||
|
||||
local has_awards = minetest.get_modpath("awards")
|
||||
|
||||
local mcl_item_entity = {}
|
||||
mcl_item_entity = {}
|
||||
|
||||
--basic settings
|
||||
local item_drop_settings = {} --settings table
|
||||
|
@ -52,16 +40,23 @@ local function get_gravity()
|
|||
return tonumber(minetest.settings:get("movement_gravity")) or 9.81
|
||||
end
|
||||
|
||||
local registered_pickup_achievement = {}
|
||||
mcl_item_entity.registered_pickup_achievement = {}
|
||||
|
||||
--TODO: remove limitation of 1 award per itemname
|
||||
---Register an achievement that will be unlocked on pickup.
|
||||
---
|
||||
---TODO: remove limitation of 1 award per itemname
|
||||
---@param itemname string
|
||||
---@param award string
|
||||
function mcl_item_entity.register_pickup_achievement(itemname, award)
|
||||
if not has_awards then
|
||||
minetest.log("warning", "[mcl_item_entity] Trying to register pickup achievement ["..award.."] for ["..itemname.."] while awards missing")
|
||||
elseif registered_pickup_achievement[itemname] then
|
||||
minetest.log("error", "[mcl_item_entity] Trying to register already existing pickup achievement ["..award.."] for ["..itemname.."]")
|
||||
minetest.log("warning",
|
||||
"[mcl_item_entity] Trying to register pickup achievement [" .. award .. "] for [" ..
|
||||
itemname .. "] while awards missing")
|
||||
elseif mcl_item_entity.registered_pickup_achievement[itemname] then
|
||||
minetest.log("error",
|
||||
"[mcl_item_entity] Trying to register already existing pickup achievement [" .. award .. "] for [" .. itemname .. "]")
|
||||
else
|
||||
registered_pickup_achievement[itemname] = award
|
||||
mcl_item_entity.registered_pickup_achievement[itemname] = award
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -74,11 +69,13 @@ mcl_item_entity.register_pickup_achievement("mcl_nether:ancient_debris", "mcl:hi
|
|||
mcl_item_entity.register_pickup_achievement("mcl_end:dragon_egg", "mcl:PickUpDragonEgg")
|
||||
mcl_item_entity.register_pickup_achievement("mcl_armor:elytra", "mcl:skysTheLimit")
|
||||
|
||||
---@param object ObjectRef
|
||||
---@param player ObjectRef
|
||||
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()
|
||||
for name,award in pairs(registered_pickup_achievement) do
|
||||
for name, award in pairs(mcl_item_entity.registered_pickup_achievement) do
|
||||
if itemname == name or minetest.get_item_group(itemname, name) ~= 0 then
|
||||
awards.unlock(playername, award)
|
||||
end
|
||||
|
@ -86,16 +83,23 @@ local function check_pickup_achievements(object, player)
|
|||
end
|
||||
end
|
||||
|
||||
---@param object ObjectRef
|
||||
---@param luaentity Luaentity
|
||||
---@param ignore_check? boolean
|
||||
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({
|
||||
physical = true
|
||||
})
|
||||
object:set_acceleration({x=0,y=-get_gravity(),z=0})
|
||||
object:set_acceleration(vector.new(0, -get_gravity(), 0))
|
||||
end
|
||||
end
|
||||
|
||||
---@param object ObjectRef
|
||||
---@param luaentity Luaentity
|
||||
---@param ignore_check? boolean
|
||||
---@param reset_movement? boolean
|
||||
local function disable_physics(object, luaentity, ignore_check, reset_movement)
|
||||
if luaentity.physical_state == true or ignore_check == true then
|
||||
luaentity.physical_state = false
|
||||
|
@ -103,14 +107,13 @@ local function disable_physics(object, luaentity, ignore_check, reset_movement)
|
|||
physical = false
|
||||
})
|
||||
if reset_movement ~= false then
|
||||
object:set_velocity({x=0,y=0,z=0})
|
||||
object:set_acceleration({x=0,y=0,z=0})
|
||||
object:set_velocity(vector.zero())
|
||||
object:set_acceleration(vector.zero())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
minetest.register_globalstep(function(_)
|
||||
tick = not tick
|
||||
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
|
@ -135,15 +138,18 @@ minetest.register_globalstep(function(dtime)
|
|||
end
|
||||
|
||||
|
||||
|
||||
local inv = player:get_inventory()
|
||||
local checkpos = {x=pos.x,y=pos.y + item_drop_settings.player_collect_height,z=pos.z}
|
||||
local checkpos = vector.offset(pos, 0, item_drop_settings.player_collect_height, 0)
|
||||
|
||||
--magnet and collection
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(checkpos, item_drop_settings.xp_radius_magnet)) do
|
||||
if not object:is_player() and vector.distance(checkpos, object:get_pos()) < item_drop_settings.radius_magnet and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" and object:get_luaentity()._magnet_timer and (object:get_luaentity()._insta_collect or (object:get_luaentity().age > item_drop_settings.age)) then
|
||||
if not object:is_player() and vector.distance(checkpos, object:get_pos()) < item_drop_settings.radius_magnet and
|
||||
object:get_luaentity() and object:get_luaentity().name == "__builtin:item" and object:get_luaentity()._magnet_timer
|
||||
and (object:get_luaentity()._insta_collect or (object:get_luaentity().age > item_drop_settings.age)) then
|
||||
|
||||
if object:get_luaentity()._magnet_timer >= 0 and object:get_luaentity()._magnet_timer < item_drop_settings.magnet_time and inv and inv:room_for_item("main", ItemStack(object:get_luaentity().itemstring)) then
|
||||
if object:get_luaentity()._magnet_timer >= 0 and
|
||||
object:get_luaentity()._magnet_timer < item_drop_settings.magnet_time and inv and
|
||||
inv:room_for_item("main", ItemStack(object:get_luaentity().itemstring)) then
|
||||
|
||||
-- Collection
|
||||
if not object:get_luaentity()._removed then
|
||||
|
@ -158,8 +164,8 @@ minetest.register_globalstep(function(dtime)
|
|||
object:get_luaentity().target = checkpos
|
||||
object:get_luaentity()._removed = true
|
||||
|
||||
object:set_velocity({x=0,y=0,z=0})
|
||||
object:set_acceleration({x=0,y=0,z=0})
|
||||
object:set_velocity(vector.zero())
|
||||
object:set_acceleration(vector.zero())
|
||||
|
||||
object:move_to(checkpos)
|
||||
|
||||
|
@ -179,7 +185,6 @@ minetest.register_globalstep(function(dtime)
|
|||
local entity = object:get_luaentity()
|
||||
entity.collector = player:get_player_name()
|
||||
entity.collected = true
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -194,6 +199,11 @@ end)
|
|||
|
||||
local tmp_id = 0
|
||||
|
||||
---@param drop string|drop_definition
|
||||
---@param toolname string
|
||||
---@param param2 integer
|
||||
---@param paramtype2 paramtype2
|
||||
---@return string[]
|
||||
local function get_drops(drop, toolname, param2, paramtype2)
|
||||
tmp_id = tmp_id + 1
|
||||
local tmp_node_name = "mcl_item_entity:" .. tmp_id
|
||||
|
@ -237,10 +247,17 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
-- NOTE: This function override allows digger to be nil.
|
||||
-- This means there is no digger. This is a special case which allows this function to be called
|
||||
-- by hand. Creative Mode is intentionally ignored in this case.
|
||||
|
||||
if (digger and digger:is_player() and minetest.is_creative_enabled(digger:get_player_name())) or doTileDrops == false then
|
||||
return
|
||||
if digger and digger:is_player() and minetest.is_creative_enabled(digger:get_player_name()) then
|
||||
local inv = digger:get_inventory()
|
||||
if inv then
|
||||
for _, item in ipairs(drops) do
|
||||
if not inv:contains_item("main", item, true) then
|
||||
inv:add_item("main", item)
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
elseif not doTileDrops then return end
|
||||
|
||||
-- Check if node will yield its useful drop by the digger's tool
|
||||
local dug_node = minetest.get_node(pos)
|
||||
|
@ -248,9 +265,9 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
local tool
|
||||
if digger then
|
||||
tool = digger:get_wielded_item()
|
||||
tooldef = minetest.registered_tools[tool:get_name()]
|
||||
tooldef = minetest.registered_items[tool:get_name()]
|
||||
|
||||
if not mcl_autogroup.can_harvest(dug_node.name, tool:get_name()) then
|
||||
if not mcl_autogroup.can_harvest(dug_node.name, tool:get_name(), digger) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
@ -265,7 +282,7 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
* table: Drop every itemstring in this table when dug by shears _mcl_silk_touch_drop
|
||||
]]
|
||||
|
||||
local enchantments = tool and mcl_enchanting.get_enchantments(tool, "silk_touch")
|
||||
local enchantments = tool and mcl_enchanting.get_enchantments(tool)
|
||||
|
||||
local silk_touch_drop = false
|
||||
local nodedef = minetest.registered_nodes[dug_node.name]
|
||||
|
@ -294,7 +311,8 @@ function minetest.handle_node_drops(pos, drops, digger)
|
|||
local max_count = fortune_drop.max_count + fortune_level * (fortune_drop.factor or 1)
|
||||
local chance = fortune_drop.chance or fortune_drop.get_chance and fortune_drop.get_chance(fortune_level)
|
||||
if not chance or math.random() < chance then
|
||||
drops = discrete_uniform_distribution(fortune_drop.multiply and drops or fortune_drop.items, min_count, max_count, fortune_drop.cap)
|
||||
drops = discrete_uniform_distribution(fortune_drop.multiply and drops or fortune_drop.items, min_count, max_count,
|
||||
fortune_drop.cap)
|
||||
elseif fortune_drop.override then
|
||||
drops = {}
|
||||
end
|
||||
|
@ -348,7 +366,7 @@ end
|
|||
function minetest.item_drop(itemstack, dropper, pos)
|
||||
if dropper and dropper:is_player() then
|
||||
local v = dropper:get_look_dir()
|
||||
local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
|
||||
local p = vector.offset(pos, 0, 1.2, 0)
|
||||
local cs = itemstack:get_count()
|
||||
if dropper:get_player_control().sneak then
|
||||
cs = 1
|
||||
|
@ -385,114 +403,176 @@ local function cxcz(o, cw, one, zero)
|
|||
return o
|
||||
end
|
||||
|
||||
local function hopper_take_item (self, pos)
|
||||
--mcl_log("self.itemstring: ".. self.itemstring)
|
||||
--mcl_log("self.itemstring: ".. minetest.pos_to_string(pos))
|
||||
local function nodes_destroy_items (self, moveresult, def, nn)
|
||||
local lg = minetest.get_item_group(nn, "lava")
|
||||
local fg = minetest.get_item_group(nn, "fire")
|
||||
local dg = minetest.get_item_group(nn, "destroys_items")
|
||||
|
||||
local objs = minetest.get_objects_inside_radius(pos, 2)
|
||||
if (def and (lg ~= 0 or fg ~= 0 or dg == 1)) then
|
||||
local item_string = self.itemstring
|
||||
local item_name = ItemStack(item_string):get_name()
|
||||
|
||||
if objs and self.itemstring then
|
||||
--mcl_log("there is an itemstring. Number of objs: ".. #objs)
|
||||
|
||||
for k,v in pairs(objs) do
|
||||
local ent = v:get_luaentity()
|
||||
|
||||
-- Don't forget actual hoppers
|
||||
if ent and ent.name == "mcl_minecarts:hopper_minecart" then
|
||||
local taken_items = false
|
||||
|
||||
mcl_log("ent.name: ".. tostring(ent.name))
|
||||
mcl_log("ent pos: ".. tostring(ent.object:get_pos()))
|
||||
|
||||
local inv = mcl_entity_invs.load_inv(ent,5)
|
||||
|
||||
if not inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
--Wait 2 seconds to allow mob drops to be cooked, & picked up instead of instantly destroyed.
|
||||
if self.age > 2 and minetest.get_item_group(item_name, "fire_immune") == 0 then
|
||||
if dg ~= 2 then
|
||||
minetest.sound_play("builtin_item_lava", { pos = self.object:get_pos(), gain = 0.5 })
|
||||
end
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local current_itemstack = ItemStack(self.itemstring)
|
||||
|
||||
mcl_log("inv. size: " .. ent._inv_size)
|
||||
if inv:room_for_item("main", current_itemstack) then
|
||||
mcl_log("Room")
|
||||
inv:add_item("main", current_itemstack)
|
||||
self.object:get_luaentity().itemstring = ""
|
||||
-- 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
|
||||
-- TODO We need to play a sound when it gets destroyed
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
taken_items = true
|
||||
else
|
||||
mcl_log("no Room")
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not taken_items then
|
||||
local items_remaining = current_itemstack:get_count()
|
||||
local function push_out_item_stuck_in_solid(self, dtime, p, def, is_in_water)
|
||||
if not is_in_water and def and def.walkable and def.groups and def.groups.opaque == 1 then
|
||||
local shootdir
|
||||
local cx = (p.x % 1) - 0.5
|
||||
local cz = (p.z % 1) - 0.5
|
||||
local order = {}
|
||||
|
||||
-- This will take part of a floating item stack if no slot can hold the full amount
|
||||
for i = 1, ent._inv_size,1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Items remaining: " .. items_remaining)
|
||||
mcl_log("Name: " .. tostring(stack:get_name()))
|
||||
|
||||
if current_itemstack:get_name() == stack:get_name() then
|
||||
mcl_log("We have a match. Name: " .. tostring(stack:get_name()))
|
||||
|
||||
local room_for = stack:get_stack_max() - stack:get_count()
|
||||
mcl_log("Room for: " .. tostring(room_for))
|
||||
|
||||
if room_for == 0 then
|
||||
-- Do nothing
|
||||
mcl_log("No room")
|
||||
elseif room_for < items_remaining then
|
||||
mcl_log("We have more items remaining than space")
|
||||
|
||||
items_remaining = items_remaining - room_for
|
||||
stack:set_count(stack:get_stack_max())
|
||||
inv:set_stack("main", i, stack)
|
||||
taken_items = true
|
||||
-- First prepare the order in which the 4 sides are to be checked.
|
||||
-- 1st: closest
|
||||
-- 2nd: other direction
|
||||
-- 3rd and 4th: other axis
|
||||
if math.abs(cx) < math.abs(cz) then
|
||||
order = cxcz(order, cx, "x", "z")
|
||||
order = cxcz(order, cz, "z", "x")
|
||||
else
|
||||
local new_stack_size = stack:get_count() + items_remaining
|
||||
stack:set_count(new_stack_size)
|
||||
mcl_log("We have more than enough space. Now holds: " .. new_stack_size)
|
||||
order = cxcz(order, cz, "z", "x")
|
||||
order = cxcz(order, cx, "x", "z")
|
||||
end
|
||||
|
||||
inv:set_stack("main", i, stack)
|
||||
items_remaining = 0
|
||||
|
||||
self.object:get_luaentity().itemstring = ""
|
||||
self.object:remove()
|
||||
|
||||
taken_items = true
|
||||
-- Check which one of the 4 sides is free
|
||||
for o = 1, #order do
|
||||
local nn = minetest.get_node(vector.add(p, order[o])).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if def and def.walkable == false and nn ~= "ignore" then
|
||||
shootdir = order[o]
|
||||
break
|
||||
end
|
||||
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
--mcl_log("Is it empty: " .. stack:to_string())
|
||||
end
|
||||
|
||||
if i == ent._inv_size and taken_items then
|
||||
mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining)
|
||||
current_itemstack:set_count(items_remaining)
|
||||
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
|
||||
self.itemstring = current_itemstack:to_string()
|
||||
end
|
||||
-- If none of the 4 sides is free, shoot upwards
|
||||
if shootdir == nil then
|
||||
shootdir = vector.new(0, 1, 0)
|
||||
local nn = minetest.get_node(vector.add(p, shootdir)).name
|
||||
if nn == "ignore" then
|
||||
-- Do not push into ignore
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--Add in, and delete
|
||||
if taken_items then
|
||||
mcl_log("Saving")
|
||||
mcl_entity_invs.save_inv(ent)
|
||||
return taken_items
|
||||
-- Set new item moving speed accordingly
|
||||
local newv = vector.multiply(shootdir, 3)
|
||||
self.object:set_acceleration(vector.zero())
|
||||
self.object:set_velocity(newv)
|
||||
disable_physics(self.object, self, false, false)
|
||||
|
||||
|
||||
if shootdir.y == 0 then
|
||||
self._force = newv
|
||||
p.x = math.floor(p.x)
|
||||
p.y = math.floor(p.y)
|
||||
p.z = math.floor(p.z)
|
||||
self._forcestart = p
|
||||
self._forcetimer = 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- This code is run after the entity got a push from above “push away” code.
|
||||
-- It is responsible for making sure the entity is entirely outside the solid node
|
||||
-- (with its full collision box), not just its center.
|
||||
if self._forcetimer > 0 then
|
||||
local cbox = self.object:get_properties().collisionbox
|
||||
local ok = false
|
||||
if self._force.x > 0 and (p.x > (self._forcestart.x + 0.5 + (cbox[4] - cbox[1]) / 2)) then ok = true
|
||||
elseif self._force.x < 0 and (p.x < (self._forcestart.x + 0.5 - (cbox[4] - cbox[1]) / 2)) then ok = true
|
||||
elseif self._force.z > 0 and (p.z > (self._forcestart.z + 0.5 + (cbox[6] - cbox[3]) / 2)) then ok = true
|
||||
elseif self._force.z < 0 and (p.z < (self._forcestart.z + 0.5 - (cbox[6] - cbox[3]) / 2)) then ok = true end
|
||||
-- Item was successfully forced out. No more pushing
|
||||
if ok then
|
||||
self._forcetimer = -1
|
||||
self._force = nil
|
||||
enable_physics(self.object, self)
|
||||
else
|
||||
mcl_log("No need to save")
|
||||
end
|
||||
self._forcetimer = self._forcetimer - dtime
|
||||
end
|
||||
return true
|
||||
elseif self._force then
|
||||
self._force = nil
|
||||
enable_physics(self.object, self)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
local function move_items_in_water (self, p, def, node, is_floating, is_in_water)
|
||||
-- Move item around on flowing liquids; add 'source' check to allow items to continue flowing a bit in the source block of flowing water.
|
||||
if def and not is_floating and (def.liquidtype == "flowing" or def.liquidtype == "source") then
|
||||
self._flowing = true
|
||||
|
||||
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
|
||||
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
|
||||
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
|
||||
local vec = flowlib.quick_flow(p, node)
|
||||
-- Just to make sure we don't manipulate the speed for no reason
|
||||
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
|
||||
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
|
||||
local f = 1.2
|
||||
-- Set new item moving speed into the direciton of the liquid
|
||||
local newv = vector.multiply(vec, f)
|
||||
-- Swap to acceleration instead of a static speed to better mimic MC mechanics.
|
||||
self.object:set_acceleration(vector.new(newv.x, -0.22, newv.z))
|
||||
|
||||
self.physical_state = true
|
||||
self._flowing = true
|
||||
self.object:set_properties({
|
||||
physical = true
|
||||
})
|
||||
return true
|
||||
end
|
||||
if is_in_water and def.liquidtype == "source" then
|
||||
local cur_vec = self.object:get_velocity()
|
||||
-- apply some acceleration in the opposite direction so it doesn't slide forever
|
||||
local vec = {
|
||||
x = 0 - cur_vec.x * 0.9,
|
||||
y = 3 - cur_vec.y * 0.9,
|
||||
z = 0 - cur_vec.z * 0.9
|
||||
}
|
||||
self.object:set_acceleration(vec)
|
||||
-- slow down the item in water
|
||||
local vel = self.object:get_velocity()
|
||||
if vel.y < 0 then
|
||||
vel.y = vel.y * 0.9
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
if self.physical_state ~= false or self._flowing ~= true then
|
||||
self.physical_state = true
|
||||
self._flowing = true
|
||||
self.object:set_properties({
|
||||
physical = true
|
||||
})
|
||||
end
|
||||
end
|
||||
elseif self._flowing == true and not is_in_water and not is_floating then
|
||||
-- Disable flowing physics if not on/in flowing liquid
|
||||
self._flowing = false
|
||||
enable_physics(self.object, self, true)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity(":__builtin:item", {
|
||||
|
@ -541,14 +621,18 @@ minetest.register_entity(":__builtin:item", {
|
|||
if speed ~= nil then self.random_velocity = speed end
|
||||
|
||||
local vel = self.object:get_velocity()
|
||||
|
||||
-- There is perhaps a cleverer way of making this physical so it bounces off the wall like swords.
|
||||
local max_vel = 6.5 -- Faster than this and it throws it into the wall / floor and turns black because of clipping.
|
||||
|
||||
if vel and vel.x == 0 and vel.z == 0 and self.random_velocity > 0 then
|
||||
local v = self.random_velocity
|
||||
local x = math.random(5, 10) / 10 * v
|
||||
local x = math.random(5, max_vel) / 10 * v
|
||||
if math.random(0, 10) < 5 then x = -x end
|
||||
local z = math.random(5, 10) / 10 * v
|
||||
local z = math.random(5, max_vel) / 10 * v
|
||||
if math.random(0, 10) < 5 then z = -z end
|
||||
local y = math.random(2,4)
|
||||
self.object:set_velocity({x=x, y=y, z=z})
|
||||
local y = math.random(1, 2)
|
||||
self.object:set_velocity(vector.new(x, y, z))
|
||||
end
|
||||
self.random_velocity = 0
|
||||
end,
|
||||
|
@ -696,8 +780,8 @@ minetest.register_entity(":__builtin:item", {
|
|||
self._forcetimer = 0
|
||||
|
||||
self.object:set_armor_groups({ immortal = 1 })
|
||||
-- self.object:set_velocity({x = 0, y = 2, z = 0})
|
||||
self.object:set_acceleration({x = 0, y = -get_gravity(), z = 0})
|
||||
-- self.object:set_velocity(vector.new(0, 2, 0))
|
||||
self.object:set_acceleration(vector.new(0, -get_gravity(), 0))
|
||||
self:set_item(self.itemstring)
|
||||
end,
|
||||
|
||||
|
@ -724,11 +808,19 @@ minetest.register_entity(":__builtin:item", {
|
|||
if total_count > max_count then
|
||||
return false
|
||||
end
|
||||
-- Merge the remote stack into this one
|
||||
|
||||
-- local pos = object:get_pos()
|
||||
-- pos.y = pos.y + ((total_count - count) / max_count) * 0.15
|
||||
-- self.object:move_to(pos)
|
||||
-- Merge the remote stack into this one
|
||||
local self_pos = self.object:get_pos()
|
||||
local pos = object:get_pos()
|
||||
|
||||
--local y = pos.y + ((total_count - count) / max_count) * 0.15
|
||||
local x_diff = (self_pos.x - pos.x) / 2
|
||||
local z_diff = (self_pos.z - pos.z) / 2
|
||||
|
||||
local new_pos = vector.offset(pos, x_diff, 0, z_diff)
|
||||
new_pos.y = math.max(self_pos.y, pos.y) + 0.1
|
||||
|
||||
self.object:move_to(new_pos)
|
||||
|
||||
self.age = 0 -- Handle as new entity
|
||||
own_stack:set_count(total_count)
|
||||
|
@ -745,10 +837,11 @@ minetest.register_entity(":__builtin:item", {
|
|||
self.object:set_properties({
|
||||
physical = false
|
||||
})
|
||||
self.object:set_velocity({x=0,y=0,z=0})
|
||||
self.object:set_acceleration({x=0,y=0,z=0})
|
||||
self.object:set_velocity(vector.zero())
|
||||
self.object:set_acceleration(vector.zero())
|
||||
return
|
||||
end
|
||||
|
||||
self.age = self.age + dtime
|
||||
if self._collector_timer then
|
||||
self._collector_timer = self._collector_timer + dtime
|
||||
|
@ -761,21 +854,16 @@ minetest.register_entity(":__builtin:item", {
|
|||
-- Delete corrupted item entities. The itemstring MUST be non-empty on its first step,
|
||||
-- otherwise there might have some data corruption.
|
||||
if self.itemstring == "" then
|
||||
minetest.log("warning", "Item entity with empty itemstring found at "..minetest.pos_to_string(self.object:get_pos()).. "! Deleting it now.")
|
||||
minetest.log("warning",
|
||||
"Item entity with empty itemstring found and being deleted at: " .. minetest.pos_to_string(self.object:get_pos()))
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
|
||||
-- If hopper has taken item, it has gone, and no operations should be conducted on this item
|
||||
if hopper_take_item(self, p) then
|
||||
return
|
||||
end
|
||||
|
||||
local node = minetest.get_node_or_nil(p)
|
||||
local in_unloaded = (node == nil)
|
||||
local node = minetest.get_node(p)
|
||||
local in_unloaded = node.name == "ignore"
|
||||
|
||||
if in_unloaded then
|
||||
-- Don't infinetly fall into unloaded map
|
||||
|
@ -783,6 +871,9 @@ minetest.register_entity(":__builtin:item", {
|
|||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
if self.is_clock then
|
||||
self.object:set_properties({
|
||||
textures = { "mcl_clock:clock_" .. (mcl_worlds.clock_works(p) and mcl_clock.old_time or mcl_clock.random_frame) }
|
||||
|
@ -791,7 +882,7 @@ minetest.register_entity(":__builtin:item", {
|
|||
|
||||
local nn = node.name
|
||||
local is_in_water = (minetest.get_item_group(nn, "liquid") ~= 0)
|
||||
local nn_above = minetest.get_node({x=p.x, y=p.y+0.1, z=p.z}).name
|
||||
local nn_above = minetest.get_node(vector.offset(p, 0, 0.1, 0)).name
|
||||
-- make sure it's more or less stationary and is at water level
|
||||
local sleep_threshold = 0.3
|
||||
local is_floating = false
|
||||
|
@ -804,8 +895,8 @@ minetest.register_entity(":__builtin:item", {
|
|||
end
|
||||
|
||||
if is_floating and self.physical_state == true then
|
||||
self.object:set_velocity({x = 0, y = 0, z = 0})
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
self.object:set_velocity(vector.zero())
|
||||
self.object:set_acceleration(vector.zero())
|
||||
disable_physics(self.object, self)
|
||||
end
|
||||
-- If no collector was found for a long enough time, declare the magnet as disabled
|
||||
|
@ -818,182 +909,27 @@ minetest.register_entity(":__builtin:item", {
|
|||
-- Destroy item in lava, fire or special nodes
|
||||
|
||||
local def = minetest.registered_nodes[nn]
|
||||
local lg = minetest.get_item_group(nn, "lava")
|
||||
local fg = minetest.get_item_group(nn, "fire")
|
||||
local dg = minetest.get_item_group(nn, "destroys_items")
|
||||
if (def and (lg ~= 0 or fg ~= 0 or dg == 1)) then
|
||||
--Wait 2 seconds to allow mob drops to be cooked, & picked up instead of instantly destroyed.
|
||||
if self.age > 2 and minetest.get_item_group(self.itemstring, "fire_immune") == 0 then
|
||||
if dg ~= 2 then
|
||||
minetest.sound_play("builtin_item_lava", {pos = self.object:get_pos(), gain = 0.5})
|
||||
end
|
||||
self._removed = true
|
||||
self.object:remove()
|
||||
return
|
||||
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
|
||||
if nodes_destroy_items(self, moveresult, def, nn) then return end
|
||||
|
||||
-- Push item out when stuck inside solid opaque node
|
||||
if not is_in_water and def and def.walkable and def.groups and def.groups.opaque == 1 then
|
||||
local shootdir
|
||||
local cx = (p.x % 1) - 0.5
|
||||
local cz = (p.z % 1) - 0.5
|
||||
local order = {}
|
||||
if push_out_item_stuck_in_solid(self, dtime, p, def, is_in_water) then return end
|
||||
|
||||
-- First prepare the order in which the 4 sides are to be checked.
|
||||
-- 1st: closest
|
||||
-- 2nd: other direction
|
||||
-- 3rd and 4th: other axis
|
||||
if math.abs(cx) < math.abs(cz) then
|
||||
order = cxcz(order, cx, "x", "z")
|
||||
order = cxcz(order, cz, "z", "x")
|
||||
else
|
||||
order = cxcz(order, cz, "z", "x")
|
||||
order = cxcz(order, cx, "x", "z")
|
||||
end
|
||||
|
||||
-- Check which one of the 4 sides is free
|
||||
for o=1, #order do
|
||||
local nn = minetest.get_node(vector.add(p, order[o])).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
if def and def.walkable == false and nn ~= "ignore" then
|
||||
shootdir = order[o]
|
||||
break
|
||||
end
|
||||
end
|
||||
-- If none of the 4 sides is free, shoot upwards
|
||||
if shootdir == nil then
|
||||
shootdir = { x=0, y=1, z=0 }
|
||||
local nn = minetest.get_node(vector.add(p, shootdir)).name
|
||||
if nn == "ignore" then
|
||||
-- Do not push into ignore
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Set new item moving speed accordingly
|
||||
local newv = vector.multiply(shootdir, 3)
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
self.object:set_velocity(newv)
|
||||
disable_physics(self.object, self, false, false)
|
||||
|
||||
|
||||
if shootdir.y == 0 then
|
||||
self._force = newv
|
||||
p.x = math.floor(p.x)
|
||||
p.y = math.floor(p.y)
|
||||
p.z = math.floor(p.z)
|
||||
self._forcestart = p
|
||||
self._forcetimer = 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- This code is run after the entity got a push from above “push away” code.
|
||||
-- It is responsible for making sure the entity is entirely outside the solid node
|
||||
-- (with its full collision box), not just its center.
|
||||
if self._forcetimer > 0 then
|
||||
local cbox = self.object:get_properties().collisionbox
|
||||
local ok = false
|
||||
if self._force.x > 0 and (p.x > (self._forcestart.x + 0.5 + (cbox[4] - cbox[1])/2)) then ok = true
|
||||
elseif self._force.x < 0 and (p.x < (self._forcestart.x + 0.5 - (cbox[4] - cbox[1])/2)) then ok = true
|
||||
elseif self._force.z > 0 and (p.z > (self._forcestart.z + 0.5 + (cbox[6] - cbox[3])/2)) then ok = true
|
||||
elseif self._force.z < 0 and (p.z < (self._forcestart.z + 0.5 - (cbox[6] - cbox[3])/2)) then ok = true end
|
||||
-- Item was successfully forced out. No more pushing
|
||||
if ok then
|
||||
self._forcetimer = -1
|
||||
self._force = nil
|
||||
enable_physics(self.object, self)
|
||||
else
|
||||
self._forcetimer = self._forcetimer - dtime
|
||||
end
|
||||
return
|
||||
elseif self._force then
|
||||
self._force = nil
|
||||
enable_physics(self.object, self)
|
||||
return
|
||||
end
|
||||
|
||||
-- Move item around on flowing liquids; add 'source' check to allow items to continue flowing a bit in the source block of flowing water.
|
||||
if def and not is_floating and (def.liquidtype == "flowing" or def.liquidtype == "source") then
|
||||
self._flowing = true
|
||||
|
||||
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
|
||||
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
|
||||
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
|
||||
local vec = flowlib.quick_flow(p, node)
|
||||
-- Just to make sure we don't manipulate the speed for no reason
|
||||
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
|
||||
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
|
||||
local f = 1.2
|
||||
-- Set new item moving speed into the direciton of the liquid
|
||||
local newv = vector.multiply(vec, f)
|
||||
-- Swap to acceleration instead of a static speed to better mimic MC mechanics.
|
||||
self.object:set_acceleration({x = newv.x, y = -0.22, z = newv.z})
|
||||
|
||||
self.physical_state = true
|
||||
self._flowing = true
|
||||
self.object:set_properties({
|
||||
physical = true
|
||||
})
|
||||
return
|
||||
end
|
||||
if is_in_water and def.liquidtype == "source" then
|
||||
local cur_vec = self.object:get_velocity()
|
||||
-- apply some acceleration in the opposite direction so it doesn't slide forever
|
||||
local vec = {
|
||||
x = 0 -cur_vec.x*0.9,
|
||||
y = 3 -cur_vec.y*0.9,
|
||||
z = 0 -cur_vec.z*0.9}
|
||||
self.object:set_acceleration(vec)
|
||||
-- slow down the item in water
|
||||
local vel = self.object:get_velocity()
|
||||
if vel.y < 0 then
|
||||
vel.y = vel.y * 0.9
|
||||
end
|
||||
self.object:set_velocity(vel)
|
||||
if self.physical_state ~= false or self._flowing ~= true then
|
||||
self.physical_state = true
|
||||
self._flowing = true
|
||||
self.object:set_properties({
|
||||
physical = true
|
||||
})
|
||||
end
|
||||
end
|
||||
elseif self._flowing == true and not is_in_water and not is_floating then
|
||||
-- Disable flowing physics if not on/in flowing liquid
|
||||
self._flowing = false
|
||||
enable_physics(self.object, self, true)
|
||||
return
|
||||
end
|
||||
if move_items_in_water (self, p, def, node, is_floating, is_in_water) then return end
|
||||
|
||||
-- If node is not registered or node is walkably solid and resting on nodebox
|
||||
local nn = minetest.get_node({x=p.x, y=p.y-0.5, z=p.z}).name
|
||||
local nn = minetest.get_node(vector.offset(p, 0, -0.5, 0)).name
|
||||
local def = minetest.registered_nodes[nn]
|
||||
local v = self.object:get_velocity()
|
||||
local is_on_floor = def and (def.walkable
|
||||
and not def.groups.slippery and v.y == 0)
|
||||
|
||||
if not minetest.registered_nodes[nn]
|
||||
or is_floating or is_on_floor then
|
||||
if not minetest.registered_nodes[nn] or is_floating or is_on_floor then
|
||||
|
||||
local own_stack = ItemStack(self.object:get_luaentity().itemstring)
|
||||
-- Merge with close entities of the same item
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(p, 0.8)) do
|
||||
local obj = object:get_luaentity()
|
||||
if obj and obj.name == "__builtin:item"
|
||||
and obj.physical_state == false then
|
||||
if obj and obj.name == "__builtin:item" and obj.physical_state == false then
|
||||
if self:try_merge_with(own_stack, object, obj) then
|
||||
return
|
||||
end
|
||||
|
|
|
@ -11,6 +11,14 @@ mcl_minecarts.check_float_time = 15
|
|||
dofile(mcl_minecarts.modpath.."/functions.lua")
|
||||
dofile(mcl_minecarts.modpath.."/rails.lua")
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
|
||||
local function mcl_log(message)
|
||||
if LOGGING_ON then
|
||||
mcl_util.mcl_log(message, "[Minecarts]", true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function detach_driver(self)
|
||||
if not self._driver then
|
||||
return
|
||||
|
@ -51,6 +59,134 @@ end
|
|||
|
||||
local activate_normal_minecart = detach_driver
|
||||
|
||||
local function hopper_take_item(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if not self or self.name ~= "mcl_minecarts:hopper_minecart" then return end
|
||||
|
||||
if mcl_util.check_dtime_timer(self, dtime, "hoppermc_take", 0.15) then
|
||||
--minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name)
|
||||
else
|
||||
--minetest.log("The check timer was not triggered")
|
||||
return
|
||||
end
|
||||
|
||||
--mcl_log("self.itemstring: ".. self.itemstring)
|
||||
|
||||
local above_pos = vector.offset(pos, 0, 0.9, 0)
|
||||
--mcl_log("self.itemstring: ".. minetest.pos_to_string(above_pos))
|
||||
local objs = minetest.get_objects_inside_radius(above_pos, 1.25)
|
||||
|
||||
if objs then
|
||||
|
||||
mcl_log("there is an itemstring. Number of objs: ".. #objs)
|
||||
|
||||
for k, v in pairs(objs) do
|
||||
local ent = v:get_luaentity()
|
||||
|
||||
if ent._removed or not ent.itemstring or ent.itemstring == "" then
|
||||
--minetest.log("Ignore this item")
|
||||
break
|
||||
end
|
||||
|
||||
-- Don't forget actual hoppers
|
||||
|
||||
local taken_items = false
|
||||
|
||||
mcl_log("ent.name: " .. tostring(ent.name))
|
||||
mcl_log("ent pos: " .. tostring(ent.object:get_pos()))
|
||||
|
||||
local inv = mcl_entity_invs.load_inv(self, 5)
|
||||
|
||||
if not inv then
|
||||
mcl_log("No inv")
|
||||
return false
|
||||
end
|
||||
|
||||
local current_itemstack = ItemStack(ent.itemstring)
|
||||
|
||||
mcl_log("inv. size: " .. self._inv_size)
|
||||
if inv:room_for_item("main", current_itemstack) then
|
||||
mcl_log("Room")
|
||||
inv:add_item("main", current_itemstack)
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
taken_items = true
|
||||
else
|
||||
mcl_log("no Room")
|
||||
end
|
||||
|
||||
if not taken_items then
|
||||
local items_remaining = current_itemstack:get_count()
|
||||
|
||||
-- This will take part of a floating item stack if no slot can hold the full amount
|
||||
for i = 1, self._inv_size, 1 do
|
||||
local stack = inv:get_stack("main", i)
|
||||
|
||||
mcl_log("i: " .. tostring(i))
|
||||
mcl_log("Items remaining: " .. items_remaining)
|
||||
mcl_log("Name: " .. tostring(stack:get_name()))
|
||||
|
||||
if current_itemstack:get_name() == stack:get_name() then
|
||||
mcl_log("We have a match. Name: " .. tostring(stack:get_name()))
|
||||
|
||||
local room_for = stack:get_stack_max() - stack:get_count()
|
||||
mcl_log("Room for: " .. tostring(room_for))
|
||||
|
||||
if room_for == 0 then
|
||||
-- Do nothing
|
||||
mcl_log("No room")
|
||||
elseif room_for < items_remaining then
|
||||
mcl_log("We have more items remaining than space")
|
||||
|
||||
items_remaining = items_remaining - room_for
|
||||
stack:set_count(stack:get_stack_max())
|
||||
inv:set_stack("main", i, stack)
|
||||
taken_items = true
|
||||
else
|
||||
local new_stack_size = stack:get_count() + items_remaining
|
||||
stack:set_count(new_stack_size)
|
||||
mcl_log("We have more than enough space. Now holds: " .. new_stack_size)
|
||||
|
||||
inv:set_stack("main", i, stack)
|
||||
items_remaining = 0
|
||||
|
||||
ent.object:get_luaentity().itemstring = ""
|
||||
ent.object:remove()
|
||||
|
||||
taken_items = true
|
||||
break
|
||||
end
|
||||
|
||||
mcl_log("Count: " .. tostring(stack:get_count()))
|
||||
mcl_log("stack max: " .. tostring(stack:get_stack_max()))
|
||||
--mcl_log("Is it empty: " .. stack:to_string())
|
||||
end
|
||||
|
||||
if i == self._inv_size and taken_items then
|
||||
mcl_log("We are on last item and still have items left. Set final stack size: " .. items_remaining)
|
||||
current_itemstack:set_count(items_remaining)
|
||||
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
|
||||
ent.itemstring = current_itemstack:to_string()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--Add in, and delete
|
||||
if taken_items then
|
||||
mcl_log("Saving")
|
||||
mcl_entity_invs.save_inv(ent)
|
||||
return taken_items
|
||||
else
|
||||
mcl_log("No need to save")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
|
||||
local entity_mapping = {}
|
||||
|
||||
|
@ -66,6 +202,7 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
on_rightclick = on_rightclick,
|
||||
|
||||
_driver = nil, -- player who sits in and controls the minecart (only for minecart!)
|
||||
_passenger = nil, -- for mobs
|
||||
_punched = false, -- used to re-send _velocity and position
|
||||
_velocity = {x=0, y=0, z=0}, -- only used on punch
|
||||
_start_pos = nil, -- Used to calculate distance for “On A Rail” achievement
|
||||
|
@ -86,6 +223,7 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
local data = minetest.deserialize(staticdata)
|
||||
if type(data) == "table" then
|
||||
self._railtype = data._railtype
|
||||
self._passenger = data._passenger
|
||||
end
|
||||
self.object:set_armor_groups({immortal=1})
|
||||
|
||||
|
@ -177,7 +315,11 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
|
||||
cart.on_activate_by_rail = on_activate_by_rail
|
||||
|
||||
local passenger_attach_position = vector.new(0, -1.75, 0)
|
||||
|
||||
function cart:on_step(dtime)
|
||||
hopper_take_item(self, dtime)
|
||||
|
||||
local ctrl, player = nil, nil
|
||||
if self._driver then
|
||||
player = minetest.get_player_by_name(self._driver)
|
||||
|
@ -212,6 +354,29 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
end
|
||||
end
|
||||
|
||||
-- Grab mob
|
||||
if math.random(1,20) > 15 and not self._passenger then
|
||||
if self.name == "mcl_minecarts:minecart" then
|
||||
local mobsnear = minetest.get_objects_inside_radius(self.object:get_pos(), 1.3)
|
||||
for n=1, #mobsnear do
|
||||
local mob = mobsnear[n]
|
||||
if mob then
|
||||
local entity = mob:get_luaentity()
|
||||
if entity and entity.is_mob then
|
||||
self._passenger = entity
|
||||
mob:set_attach(self.object, "", passenger_attach_position, vector.zero())
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif self._passenger then
|
||||
local passenger_pos = self._passenger.object:get_pos()
|
||||
if not passenger_pos then
|
||||
self._passenger = nil
|
||||
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()
|
||||
|
@ -236,19 +401,8 @@ local function register_entity(entity_id, mesh, textures, drop, on_rightclick, o
|
|||
return
|
||||
end
|
||||
|
||||
-- Drop items and remove cart entity
|
||||
local pname = ""
|
||||
if player then
|
||||
pname = player:get_player_name()
|
||||
end
|
||||
if not minetest.is_creative_enabled(pname) then
|
||||
for d=1, #drop do
|
||||
minetest.add_item(self.object:get_pos(), drop[d])
|
||||
end
|
||||
end
|
||||
|
||||
self.object:remove()
|
||||
return
|
||||
-- Do not drop minecart. It goes off the rails too frequently, and anyone using them for farms won't
|
||||
-- notice and lose their iron and not bother. Not cool until fixed.
|
||||
end
|
||||
self._last_float_check = 0
|
||||
end
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# textdomain: mcl_minecarts
|
||||
Minecart=Minevogn
|
||||
Minecarts can be used for a quick transportion on rails.=Minevogne kan bruges til hurtig transport på spor.
|
||||
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.=Minevogne kan kun køre på spor, og følger dem altid. Ved et T-kryds uden en vej ligeud drejer de altid til venstre. Farten påvirkes af sportypen.
|
||||
You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving.=Du kan placere minevogne på spor. Højre-klik for at stige ombord.
|
||||
To obtain the minecart, punch it while holding down the sneak key.=For at at få minevognen i din oppakning.
|
||||
A minecart with TNT is an explosive vehicle that travels on rail.=En minevogn med TNT as et eksplosivt fartøj som kører på spor.
|
||||
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.=Placér den på spor. Slå den for at flytte den. TNTet bliver antændt med flint og stål eller når minevognen er på et aktiveringsspor.
|
||||
To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited.=For at få minevognen med TNT i din oppakning skal du slå den mens du holder snigeknappen nede. Du kan ikke gøre dette hvis TNTen er antændt.
|
||||
A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel.=En minevogn med ovn er et fartøj som kører på spor. Den kan køre af sig selv med brændstof.
|
||||
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.=Placér den på spor. Hvis du putter kul i den vil ovnen brænde i lang tid, og minevognen vil køre af sig selv. Slå den for at sætte den i bevægelse.
|
||||
To obtain the minecart and furnace, punch them while holding down the sneak key.=For at få minevognen med ovn i din oppakning skal du slå den mens du holder snigeknappen nede.
|
||||
Minecart with Chest=Minevogn med kiste
|
||||
Minecart with Furnace=Minevogn med ovn
|
||||
Minecart with Command Block=Minevogn med kommandoblok
|
||||
Minecart with Hopper=Minevogn med tragt
|
||||
Minecart with TNT=Minevogn med 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.=Placér dem på jorden for at bygge din jerbane. Sporene kobler sig automatisk sammen med hinanden og laver sving, T-kryds, kryds og skråninger efter behov.
|
||||
Rail=Spor
|
||||
Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction.=Spor kan bruges til at bygge jernbaner til minevogne. Normale spor sænker minevognene en smule på grund af friktionsmodstand.
|
||||
Powered Rail=Strømspor
|
||||
Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts.=Spor kan bruges til at bygge jernbaner til minevogne. Strømspor kan accelerere eller bremse minevogne.
|
||||
Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power.=Uden redstonekraft vil sporet bremse minevognen. For at accelerere minevognen skal den bruge redstoneskraft.
|
||||
Activator Rail=Aktiveringsspor
|
||||
Rails can be used to build transport tracks for minecarts. Activator rails are used to activate special minecarts.=Spor kan bruges til at bygge jernbaner til minevogne. Aktiveringsspor bruges til at aktivere specielle minevogne.
|
||||
To make this rail activate minecarts, power it with redstone power and send a minecart over this piece of rail.=For at få dette spor til at aktiere minevogne skal du give det redstonekraft og sende en minevogn over dette sporstykke.
|
||||
Detector Rail=Detektorspor
|
||||
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.=Spor kan bruges til at bygge jernbaner til minevogne. Et detektorspor kan opdage en minevogn som kører over det og give kraft til redstonemekanismer.
|
||||
To detect a minecart and provide redstone power, connect it to redstone trails or redstone mechanisms and send any minecart over the rail.=For at opdage en minevogn og give redstonekraft skal den forbindes til redstonestøv eller redstonemekanismer og send en hvilkensomhelst minevogn hen over sporet.
|
||||
Track for minecarts=Spor til minevogne.
|
||||
Speed up when powered, slow down when not powered=Accelerérer når der er strøm, sænk hastigheden når der ikke er strøm.
|
||||
Activates minecarts when powered=Aktieverer minevogne når der er strøm.
|
||||
Emits redstone power when a minecart is detected=Udsender redstonekraft når en minevogn bliver opdaget.
|
||||
Vehicle for fast travel on rails=Fartøj til hurtig kørsel på spor.
|
||||
Can be ignited by tools or powered activator rail=Kan antændes med værktøj eller et aktivatorspor med strøm.
|
||||
Sneak to dismount=Snig for at stige af.
|
|
@ -10,15 +10,15 @@ To obtain the minecart and TNT, punch them while holding down the sneak key. You
|
|||
A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel.=Une wagonnet avec un four est un véhicule qui se déplace sur rails. Il peut se propulser avec du carburant.
|
||||
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.=Placez-le sur des rails. Si vous lui donnez du charbon, le four commencera à brûler pendant longtemps et le wagonnet pourra se déplacer. Frappez-le pour le faire bouger.
|
||||
To obtain the minecart and furnace, punch them while holding down the sneak key.=Pour obtenir le wagonnet et le four, frappez-les tout en maintenant la touche furtive enfoncée.
|
||||
Minecart with Chest=Wagonnet avec Coffre
|
||||
Minecart with Furnace=Wagonnet avec Four
|
||||
Minecart with Command Block=Wagonnet avec Bloc de Commande
|
||||
Minecart with Hopper=Wagonnet avec Entonoir
|
||||
Minecart with Chest=Wagonnet avec coffre
|
||||
Minecart with Furnace=Wagonnet avec four
|
||||
Minecart with Command Block=Wagonnet avec bloc de commande
|
||||
Minecart with Hopper=Wagonnet avec entonnoir
|
||||
Minecart with TNT=Wagonnet avec 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.=Placez-les sur le sol pour construire votre chemin de fer, les rails se connecteront automatiquement les uns aux autres et se transformeront en courbes, en jonctions en T, en traversées et en pentes au besoin.
|
||||
Rail=Rail
|
||||
Rails can be used to build transport tracks for minecarts. Normal rails slightly slow down minecarts due to friction.=Les rails peuvent être utilisés pour construire des voies de transport pour les wagonnets. Les rails ralentissent légèrement les wagonnets en raison de la friction.
|
||||
Powered Rail=Rail allimenté
|
||||
Powered Rail=Rail alimenté
|
||||
Rails can be used to build transport tracks for minecarts. Powered rails are able to accelerate and brake minecarts.=Les rails peuvent être utilisés pour construire des voies de transport pour les wagonnets. Les rails motorisés sont capables d'accélérer et de freiner les wagonnets.
|
||||
Without redstone power, the rail will brake minecarts. To make this rail accelerate minecarts, power it with redstone power.=Sans énergie de redstone, le rail freinera les wagonnets. Pour que ce rail accélère les minecarts, alimentez-le avec une source d'énergie redstone.
|
||||
Activator Rail=Rail d'activation
|
||||
|
|
|
@ -2,7 +2,7 @@ local S = minetest.get_translator(minetest.get_current_modname())
|
|||
|
||||
-- Template rail function
|
||||
local function register_rail(itemstring, tiles, def_extras, creative)
|
||||
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=1,destroy_by_lava_flow=1, transport=1}
|
||||
local groups = {handy=1,pickaxey=1, attached_node=1,rail=1,connect_to_raillike=minetest.raillike_group("rail"),dig_by_water=0,destroy_by_lava_flow=0, transport=1}
|
||||
if creative == false then
|
||||
groups.not_in_creative_inventory = 1
|
||||
end
|
||||
|
|
Before Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 274 B |
Before Width: | Height: | Size: 259 B |
Before Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 257 B |
|
@ -2,42 +2,29 @@ local mob_class = mcl_mobs.mob_class
|
|||
local mob_class_meta = {__index = mcl_mobs.mob_class}
|
||||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
-- API for Mobs Redo: MineClone 2 Edition (MRM)
|
||||
local MAX_MOB_NAME_LENGTH = 30
|
||||
local DEFAULT_FALL_SPEED = -9.81*1.5
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
local CRASH_WARN_FREQUENCY = 60
|
||||
local LIFETIMER_DISTANCE = 47
|
||||
|
||||
-- Localize
|
||||
local S = minetest.get_translator("mcl_mobs")
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
|
||||
local function mcl_log (message)
|
||||
if LOGGING_ON then
|
||||
mcl_util.mcl_log (message, "[Mobs]", true)
|
||||
end
|
||||
end
|
||||
|
||||
local DEVELOPMENT = minetest.settings:get_bool("mcl_development",false)
|
||||
|
||||
-- Invisibility mod check
|
||||
mcl_mobs.invis = {}
|
||||
|
||||
-- localize math functions
|
||||
local atann = math.atan
|
||||
|
||||
local function atan(x)
|
||||
if not x or x ~= x then
|
||||
return 0
|
||||
else
|
||||
return atann(x)
|
||||
end
|
||||
end
|
||||
|
||||
local remove_far = true
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
|
||||
|
||||
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob
|
||||
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn",true)
|
||||
|
||||
local MAPGEN_LIMIT = mcl_vars.mapgen_limit
|
||||
local MAPGEN_MOB_LIMIT = MAPGEN_LIMIT - 90
|
||||
-- 30927 seems to be the edge of the world, so could be closer, but this is safer
|
||||
|
||||
|
||||
-- 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)
|
||||
|
@ -46,15 +33,6 @@ if minetest.settings:get_bool("only_peaceful_mobs", false) then
|
|||
end)
|
||||
end
|
||||
|
||||
local node_ok = function(pos, fallback)
|
||||
fallback = fallback or mcl_mobs.fallback_node
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if node and minetest.registered_nodes[node.name] then
|
||||
return node
|
||||
end
|
||||
return minetest.registered_nodes[fallback]
|
||||
end
|
||||
|
||||
function mob_class:update_tag() --update nametag and/or the debug box
|
||||
local tag
|
||||
if mobs_debug then
|
||||
|
@ -83,16 +61,27 @@ function mob_class:update_tag() --update nametag and/or the debug box
|
|||
})
|
||||
end
|
||||
|
||||
function mob_class:jock_to(mob, reletive_pos, rot)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
self.jockey = mob
|
||||
local jock = minetest.add_entity(pos, mob)
|
||||
if not jock then return end
|
||||
jock:get_luaentity().docile_by_day = false
|
||||
jock:get_luaentity().riden_by_jock = true
|
||||
self.object:set_attach(jock, "", reletive_pos, rot)
|
||||
end
|
||||
|
||||
function mob_class:get_staticdata()
|
||||
|
||||
for _,p in pairs(minetest.get_connected_players()) do
|
||||
self:remove_particlespawners(p:get_player_name())
|
||||
end
|
||||
|
||||
-- remove mob when out of range unless tamed
|
||||
if remove_far
|
||||
and self.can_despawn
|
||||
and self.remove_ok
|
||||
and ((not self.nametag) or (self.nametag == ""))
|
||||
and self:despawn_allowed()
|
||||
and self.lifetimer <= 20 then
|
||||
if spawn_logging then
|
||||
minetest.log("action", "[mcl_mobs] Mob "..tostring(self.name).." despawns at "..minetest.pos_to_string(vector.round(self.object:get_pos())) .. " - out of range")
|
||||
|
@ -101,7 +90,6 @@ function mob_class:get_staticdata()
|
|||
return "remove"-- nil
|
||||
end
|
||||
|
||||
self.remove_ok = true
|
||||
self.attack = nil
|
||||
self.following = nil
|
||||
self.state = "stand"
|
||||
|
@ -123,6 +111,21 @@ function mob_class:get_staticdata()
|
|||
return minetest.serialize(tmp)
|
||||
end
|
||||
|
||||
local function valid_texture(self, def_textures)
|
||||
if not self.base_texture then
|
||||
return false
|
||||
end
|
||||
|
||||
if self.texture_selected then
|
||||
if #def_textures < self.texture_selected then
|
||||
self.texture_selected = nil
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function mob_class:mob_activate(staticdata, def, dtime)
|
||||
if not self.object:get_pos() or staticdata == "remove" then
|
||||
mcl_burning.extinguish(self.object)
|
||||
|
@ -144,17 +147,21 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
end
|
||||
end
|
||||
|
||||
if not self.base_texture then
|
||||
--If textures in definition change, reload textures
|
||||
if not valid_texture(self, def.textures) then
|
||||
|
||||
-- compatiblity with old simple mobs textures
|
||||
if type(def.textures[1]) == "string" then
|
||||
def.textures = {def.textures}
|
||||
end
|
||||
|
||||
if not self.texture_selected then
|
||||
local c = 1
|
||||
if #def.textures > c then c = #def.textures end
|
||||
self.texture_selected = math.random(c)
|
||||
end
|
||||
|
||||
self.base_texture = def.textures[math.random(c)]
|
||||
self.base_texture = def.textures[self.texture_selected]
|
||||
self.base_mesh = def.mesh
|
||||
self.base_size = self.visual_size
|
||||
self.base_colbox = self.collisionbox
|
||||
|
@ -276,6 +283,13 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
self._current_animation = nil
|
||||
self:set_animation( "stand")
|
||||
|
||||
|
||||
if self.riden_by_jock then --- Keep this function before self.on_spawn() is run.
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if self.on_spawn and not self.on_spawn_run then
|
||||
if self.on_spawn(self) then
|
||||
self.on_spawn_run = true
|
||||
|
@ -292,162 +306,217 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
self._run_armor_init = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
if def.after_activate then
|
||||
def.after_activate(self, staticdata, def, dtime)
|
||||
end
|
||||
end
|
||||
|
||||
-- execute current state (stand, walk, run, attacks)
|
||||
-- returns true if mob has died
|
||||
function mob_class:do_states(dtime, player_in_active_range)
|
||||
--if self.can_open_doors then check_doors(self) end
|
||||
|
||||
-- knockback timer. set in on_punch
|
||||
if self.pause_timer > 0 then
|
||||
self.pause_timer = self.pause_timer - dtime
|
||||
return
|
||||
end
|
||||
|
||||
self:env_danger_movement_checks(player_in_active_range)
|
||||
|
||||
if self.state == PATHFINDING then
|
||||
self:check_gowp(dtime)
|
||||
elseif self.state == "attack" then
|
||||
if self:do_states_attack(dtime) then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if mcl_util.check_dtime_timer(self, dtime, "onstep_dostates", 1) then
|
||||
if self.state == "stand" then
|
||||
self:do_states_stand(player_in_active_range)
|
||||
elseif self.state == "walk" then
|
||||
self:do_states_walk()
|
||||
elseif self.state == "runaway" then
|
||||
self:do_states_runaway()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:outside_limits()
|
||||
local pos = self.object:get_pos()
|
||||
if pos then
|
||||
local posx = math.abs(pos.x)
|
||||
local posy = math.abs(pos.y)
|
||||
local posz = math.abs(pos.z)
|
||||
if posx > MAPGEN_MOB_LIMIT or posy > MAPGEN_MOB_LIMIT or posz > MAPGEN_MOB_LIMIT then
|
||||
--minetest.log("action", "Getting close to limits of worldgen: " .. minetest.pos_to_string(pos))
|
||||
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
|
||||
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
|
||||
else
|
||||
if self.state ~= "stand" then
|
||||
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos))
|
||||
self.state = "stand"
|
||||
self:set_animation("stand")
|
||||
self.object:set_acceleration(vector.zero())
|
||||
self.object:set_velocity(vector.zero())
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- main mob function
|
||||
function mob_class:on_step(dtime)
|
||||
self.lifetimer = self.lifetimer - dtime
|
||||
|
||||
local function on_step_work (self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
if self:check_despawn(pos) then return true end
|
||||
|
||||
local d = 0.85
|
||||
if self:check_dying() then d = 0.92 end
|
||||
if self:check_despawn(pos, dtime) then return true end
|
||||
if self:outside_limits() then return end
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
--diffuse object velocity
|
||||
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d})
|
||||
-- Start: Death/damage processing
|
||||
-- All damage needs to be undertaken at the start. We need to exit processing if the mob dies.
|
||||
if self:check_death_and_slow_mob() then
|
||||
--minetest.log("action", "Mob is dying: ".. tostring(self.name))
|
||||
-- Do we abandon out of here now?
|
||||
end
|
||||
|
||||
if self:falling(pos) then return end
|
||||
|
||||
self:check_suspend()
|
||||
self:check_water_flow()
|
||||
|
||||
local yaw = 0
|
||||
if self:is_at_water_danger() and self.state ~= "attack" then
|
||||
if math.random(1, 10) <= 6 then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
else
|
||||
if self.move_in_group ~= false then
|
||||
self:check_herd(dtime)
|
||||
end
|
||||
end
|
||||
|
||||
if self:is_at_cliff_or_danger() then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
yaw = self:set_yaw( yaw + 0.78, 8)
|
||||
end
|
||||
|
||||
if not self.fire_resistant then
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
-- mcl_burning.tick may remove object immediately
|
||||
if not self.object:get_pos() then return end
|
||||
end
|
||||
|
||||
if mobs_debug then self:update_tag() end
|
||||
if self:step_damage (dtime, pos) then return end
|
||||
|
||||
if self.state == "die" then return end
|
||||
-- End: Death/damage processing
|
||||
|
||||
if self.jump_sound_cooloff > 0 then
|
||||
self.jump_sound_cooloff = self.jump_sound_cooloff - dtime
|
||||
end
|
||||
if self.opinion_sound_cooloff > 0 then
|
||||
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
|
||||
local player_in_active_range = self:player_in_active_range()
|
||||
self:check_suspend(player_in_active_range)
|
||||
|
||||
self:check_water_flow()
|
||||
|
||||
if not self._jumping_cliff then
|
||||
self._can_jump_cliff = self:can_jump_cliff()
|
||||
else
|
||||
self._can_jump_cliff = false
|
||||
end
|
||||
|
||||
--Mob following code.
|
||||
self:follow_flop()
|
||||
--set animation speed relitive to velocity
|
||||
self:set_animation_speed()
|
||||
self:flop()
|
||||
|
||||
self:check_smooth_rotation(dtime)
|
||||
|
||||
if player_in_active_range then
|
||||
self:set_animation_speed() -- set animation speed relative to velocity
|
||||
|
||||
self:check_head_swivel(dtime)
|
||||
|
||||
self:do_jump()
|
||||
self:set_armor_texture()
|
||||
if mcl_util.check_dtime_timer(self, dtime, "onstep_engage", 0.2) then
|
||||
self:check_follow()
|
||||
self:check_runaway_from()
|
||||
|
||||
self:monster_attack()
|
||||
self:npc_attack()
|
||||
end
|
||||
|
||||
self:check_herd(dtime)
|
||||
|
||||
if self.jump_sound_cooloff > 0 then self.jump_sound_cooloff = self.jump_sound_cooloff - dtime end
|
||||
self:do_jump()
|
||||
end
|
||||
|
||||
if mcl_util.check_dtime_timer(self, dtime, "onstep_occassional", 1) then
|
||||
|
||||
if player_in_active_range then
|
||||
self:check_item_pickup()
|
||||
self:set_armor_texture()
|
||||
self:step_opinion_sound(dtime)
|
||||
end
|
||||
|
||||
self:check_breeding()
|
||||
end
|
||||
|
||||
self:check_aggro(dtime)
|
||||
|
||||
-- run custom function (defined in mob lua file)
|
||||
if self.do_custom then
|
||||
|
||||
-- when false skip going any further
|
||||
if self.do_custom(self, dtime) == false then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- knockback timer
|
||||
if self.pause_timer > 0 then
|
||||
|
||||
self.pause_timer = self.pause_timer - dtime
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- attack timer
|
||||
self.timer = self.timer + dtime
|
||||
|
||||
if self.state ~= "attack" and self.state ~= PATHFINDING then
|
||||
if self.timer < 1 then
|
||||
return
|
||||
end
|
||||
self.timer = 0
|
||||
end
|
||||
self:check_particlespawners(dtime)
|
||||
self:check_item_pickup()
|
||||
|
||||
-- never go over 100
|
||||
if self.timer > 100 then
|
||||
self.timer = 1
|
||||
end
|
||||
if self.do_custom and self.do_custom(self, dtime) == false then return end
|
||||
|
||||
-- mob plays random sound at times
|
||||
if math.random(1, 70) == 1 then
|
||||
self:mob_sound("random", true)
|
||||
end
|
||||
if self:do_states(dtime, player_in_active_range) then return end
|
||||
|
||||
-- environmental damage timer (every 1 second)
|
||||
self.env_damage_timer = self.env_damage_timer + dtime
|
||||
|
||||
if (self.state == "attack" and self.env_damage_timer > 1)
|
||||
or self.state ~= "attack" then
|
||||
self:check_entity_cramming()
|
||||
self.env_damage_timer = 0
|
||||
|
||||
-- check for environmental damage (water, fire, lava etc.)
|
||||
if self:do_env_damage() then
|
||||
return
|
||||
end
|
||||
|
||||
-- node replace check (cow eats grass etc.)
|
||||
self:replace(pos)
|
||||
end
|
||||
|
||||
if self:do_states(dtime) then
|
||||
return
|
||||
end
|
||||
if mobs_debug then self:update_tag() end
|
||||
|
||||
if not self.object:get_luaentity() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local last_crash_warn_time = 0
|
||||
|
||||
local function log_error (stack_trace, info, info2)
|
||||
minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---")
|
||||
minetest.log("action", "Error: " .. stack_trace)
|
||||
minetest.log("action", "Bug info: " .. info)
|
||||
if info2 then
|
||||
minetest.log("action", "Bug info additional: " .. info2)
|
||||
end
|
||||
minetest.log("action", "--- Bug report end ---")
|
||||
end
|
||||
|
||||
local function warn_user_error ()
|
||||
local current_time = os.time()
|
||||
local time_since_warning = current_time - last_crash_warn_time
|
||||
|
||||
--minetest.log("previous_crash_time: " .. current_time)
|
||||
--minetest.log("last_crash_time: " .. last_crash_warn_time)
|
||||
--minetest.log("time_since_warning: " .. time_since_warning)
|
||||
|
||||
if time_since_warning > CRASH_WARN_FREQUENCY then
|
||||
last_crash_warn_time = current_time
|
||||
minetest.log("A game crashing bug was prevented. Please provide debug.log information to MineClone2 dev team for investigation. (Search for: --- Bug report start)")
|
||||
end
|
||||
end
|
||||
|
||||
local on_step_error_handler = function ()
|
||||
warn_user_error ()
|
||||
local info = debug.getinfo(1, "SnlufL")
|
||||
log_error(tostring(debug.traceback()), dump(info))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- main mob function
|
||||
function mob_class:on_step(dtime)
|
||||
if not DEVELOPMENT then
|
||||
-- Removed as bundled Lua (5.1 doesn't support xpcall)
|
||||
--local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
|
||||
local status, retVal = pcall(on_step_work, self, dtime)
|
||||
if status then
|
||||
return retVal
|
||||
else
|
||||
warn_user_error ()
|
||||
local pos = self.object:get_pos()
|
||||
if pos then
|
||||
local node = minetest.get_node(pos)
|
||||
if node and node.name == "ignore" then
|
||||
minetest.log("warning", "Pos is ignored: " .. dump(pos))
|
||||
end
|
||||
end
|
||||
log_error (dump(retVal), dump(pos), dump(self))
|
||||
end
|
||||
else
|
||||
return on_step_work (self, dtime)
|
||||
end
|
||||
end
|
||||
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
local function update_lifetimer(dtime)
|
||||
timer = timer + dtime
|
||||
if timer < 1 then return end
|
||||
for _, player in pairs(minetest.get_connected_players()) do
|
||||
local pos = player:get_pos()
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 47)) do
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, LIFETIMER_DISTANCE)) do
|
||||
local lua = obj:get_luaentity()
|
||||
if lua and lua.is_mob then
|
||||
lua.lifetimer = math.max(20, lua.lifetimer)
|
||||
|
@ -456,23 +525,126 @@ minetest.register_globalstep(function(dtime)
|
|||
end
|
||||
end
|
||||
timer = 0
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
update_lifetimer(dtime)
|
||||
end)
|
||||
|
||||
|
||||
minetest.register_chatcommand("clearmobs", {
|
||||
privs = { maphack = true },
|
||||
params = "<all>|<nametagged>|<range>",
|
||||
description=S("Removes all spawned mobs except nametagged and tamed ones. all removes all mobs, nametagged only nametagged ones and with the range paramter all mobs in a distance of the current player are removed."),
|
||||
func=function(n,param)
|
||||
local p = minetest.get_player_by_name(n)
|
||||
local num=tonumber(param)
|
||||
params = "[all|monster|passive|<mob name> [<range>|nametagged|tamed]]",
|
||||
description = S("Removes specified mobs except nametagged and tamed ones. For the second parameter, use nametagged/tamed to select only nametagged/tamed mobs, or a range to specify a maximum distance from the player."),
|
||||
func = function(player, param)
|
||||
local default = false
|
||||
if not param or param == "" then
|
||||
default = true
|
||||
minetest.chat_send_player(player,
|
||||
S("Default usage. Clearing hostile mobs. For more options please type: /help clearmobs"))
|
||||
end
|
||||
local mob, unsafe = param:match("^([%w]+)[ ]?([%w%d]*)$")
|
||||
|
||||
local all = false
|
||||
local nametagged = false
|
||||
local tamed = false
|
||||
|
||||
local mob_name, mob_type, range
|
||||
|
||||
-- Param 1 resolve
|
||||
if mob and mob ~= "" then
|
||||
if mob == "all" then
|
||||
all = true
|
||||
elseif mob == "passive" or mob == "monster" then
|
||||
mob_type = mob
|
||||
elseif mob then
|
||||
mob_name = mob
|
||||
end
|
||||
--minetest.log ("mob: [" .. mob .. "]")
|
||||
else
|
||||
--minetest.log("No valid first param")
|
||||
if default then
|
||||
--minetest.log("Use default")
|
||||
mob_type = "monster"
|
||||
end
|
||||
--return
|
||||
end
|
||||
|
||||
-- Param 2 resolve
|
||||
if unsafe and unsafe ~= "" then
|
||||
--minetest.log ("unsafe: [" .. unsafe .. "]")
|
||||
if unsafe == "nametagged" then
|
||||
nametagged = true
|
||||
elseif unsafe == "tamed" then
|
||||
tamed = true
|
||||
end
|
||||
|
||||
local num = tonumber(unsafe)
|
||||
if num then range = num end
|
||||
end
|
||||
|
||||
local p = minetest.get_player_by_name(player)
|
||||
|
||||
for _,o in pairs(minetest.luaentities) do
|
||||
if o.is_mob then
|
||||
if param == "all" or
|
||||
( param == "nametagged" and o.nametag ) or
|
||||
( param == "" and ( not o.nametag or o.nametag == "" ) and not o.tamed ) or
|
||||
( num and num > 0 and vector.distance(p:get_pos(),o.object:get_pos()) <= num ) then
|
||||
if o and o.is_mob then
|
||||
local mob_match = false
|
||||
|
||||
if all then
|
||||
--minetest.log("Match - All mobs specified")
|
||||
mob_match = true
|
||||
elseif mob_type then
|
||||
|
||||
--minetest.log("Match - o.type: ".. tostring(o.type))
|
||||
--minetest.log("mob_type: ".. tostring(mob_type))
|
||||
if mob_type == "monster" and o.type == mob_type then
|
||||
--minetest.log("Match - monster")
|
||||
mob_match = true
|
||||
elseif mob_type == "passive" and o.type ~= "monster" and o.type ~= "npc" then
|
||||
--minetest.log("Match - passive")
|
||||
mob_match = true
|
||||
else
|
||||
--minetest.log("No match for type.")
|
||||
end
|
||||
|
||||
elseif mob_name and (o.name == mob_name or string.find(o.name, mob_name)) then
|
||||
--minetest.log("Match - mob_name = ".. tostring(o.name))
|
||||
mob_match = true
|
||||
else
|
||||
--minetest.log("No match - o.type = ".. tostring(o.type))
|
||||
--minetest.log("No match - mob_name = ".. tostring(o.name))
|
||||
--minetest.log("No match - mob_type = ".. tostring(mob_name))
|
||||
end
|
||||
|
||||
if mob_match then
|
||||
local in_range = true
|
||||
if (not range or range <= 0 ) then
|
||||
in_range = true
|
||||
else
|
||||
if ( vector.distance(p:get_pos(),o.object:get_pos()) <= range ) then
|
||||
in_range = true
|
||||
else
|
||||
--minetest.log("Out of range")
|
||||
in_range = false
|
||||
end
|
||||
end
|
||||
|
||||
--minetest.log("o.nametag: ".. tostring(o.nametag))
|
||||
|
||||
if nametagged then
|
||||
if o.nametag then
|
||||
--minetest.log("Namedtagged and it has a name tag. Kill it")
|
||||
o.object:remove()
|
||||
end
|
||||
elseif tamed then
|
||||
if o.tamed then
|
||||
--minetest.log("Tamed. Kill it")
|
||||
o.object:remove()
|
||||
end
|
||||
elseif in_range and (not o.nametag or o.nametag == "") and not o.tamed then
|
||||
--minetest.log("No nametag or tamed. Kill it")
|
||||
o.object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end})
|
||||
|
|
|
@ -262,6 +262,7 @@ functions needed for the mob to work properly which contains the following:
|
|||
'custom_visual_size' will not reset visual_size from the base class on reload
|
||||
'noyaw' If true this mob will not automatically change yaw
|
||||
'particlespawners' Table of particlespawners attached to the mob. This is implemented in a coord safe manner i.e. spawners are only sent to players within the player_transfer_distance (and automatically removed). This enables infinitely lived particlespawners.
|
||||
'attack_frequency' Attack frequency in seconds. If unset, this defaults to 1. Implemented for melee only atm.
|
||||
|
||||
mobs:gopath(self,target,callback_arrived) pathfind a way to target and run callback on arrival
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
local mob_class = mcl_mobs.mob_class
|
||||
|
||||
local MAX_MOB_NAME_LENGTH = 30
|
||||
local HORNY_TIME = 30
|
||||
local HORNY_AGAIN_TIME = 300
|
||||
local CHILD_GROW_TIME = 60*20
|
||||
local HORNY_AGAIN_TIME = 30 -- was 300 or 15*20
|
||||
local CHILD_GROW_TIME = 60
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
|
||||
|
||||
|
@ -75,6 +74,7 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
|
|||
if self.food >= feed_count then
|
||||
self.food = 0
|
||||
self.horny = true
|
||||
self.persistent = true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -179,7 +179,7 @@ function mob_class:check_breeding()
|
|||
-- jump when fully grown so as not to fall into ground
|
||||
self.object:set_velocity({
|
||||
x = 0,
|
||||
y = self.jump_height*3,
|
||||
y = self.jump_height,
|
||||
z = 0
|
||||
})
|
||||
end
|
||||
|
@ -191,13 +191,10 @@ function mob_class:check_breeding()
|
|||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
else
|
||||
-- horny animal can mate for HORNY_TIME seconds,
|
||||
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
|
||||
if self.horny == true
|
||||
and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
|
||||
if self.horny == true then
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
|
||||
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
|
@ -205,6 +202,7 @@ function mob_class:check_breeding()
|
|||
self.horny = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- find another same animal who is also horny and mate if nearby
|
||||
if self.horny == true
|
||||
|
|
|
@ -8,6 +8,20 @@ local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
|||
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
|
||||
|
||||
local enable_pathfinding = true
|
||||
|
||||
local TIME_TO_FORGET_TARGET = 15
|
||||
|
||||
local atann = math.atan
|
||||
local function atan(x)
|
||||
if not x or x ~= x then
|
||||
return 0
|
||||
else
|
||||
return atann(x)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- check if daytime and also if mob is docile during daylight hours
|
||||
function mob_class:day_docile()
|
||||
if self.docile_by_day == false then
|
||||
|
@ -317,10 +331,7 @@ end
|
|||
|
||||
-- find someone to attack
|
||||
function mob_class:monster_attack()
|
||||
if not damage_enabled
|
||||
or self.passive ~= false
|
||||
or self.state == "attack"
|
||||
or self:day_docile() then
|
||||
if not damage_enabled or self.passive ~= false or self.state == "attack" or self:day_docile() then
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -329,9 +340,11 @@ function mob_class:monster_attack()
|
|||
local player, obj, min_player
|
||||
local type, name = "", ""
|
||||
local min_dist = self.view_range + 1
|
||||
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
||||
|
||||
local blacklist_attack = {}
|
||||
|
||||
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
||||
|
||||
for n = 1, #objs do
|
||||
if not objs[n]:is_player() then
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
@ -347,32 +360,29 @@ function mob_class:monster_attack()
|
|||
end
|
||||
|
||||
for n = 1, #objs do
|
||||
|
||||
|
||||
if objs[n]:is_player() then
|
||||
if mcl_mobs.invis[ objs[n]:get_player_name() ] or (not self:object_in_range(objs[n])) then
|
||||
type = ""
|
||||
elseif (self.type == "monster" or self._aggro) then
|
||||
-- self.aggro made player be attacked by npc again if out of range then back in again
|
||||
-- Does it serve a purpose other than that?
|
||||
player = objs[n]
|
||||
type = "player"
|
||||
name = "player"
|
||||
end
|
||||
else
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
||||
if obj then
|
||||
player = obj.object
|
||||
type = obj.type
|
||||
name = obj.name or ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- find specific mob to attack, failing that attack player/npc/animal
|
||||
if specific_attack(self.specific_attack, name)
|
||||
and (type == "player" or ( type == "npc" and self.attack_npcs )
|
||||
or (type == "animal" and self.attack_animals == true)) then
|
||||
|
||||
p = player:get_pos()
|
||||
sp = s
|
||||
|
||||
|
@ -388,10 +398,10 @@ function mob_class:monster_attack()
|
|||
attacked_p = true
|
||||
end
|
||||
end
|
||||
|
||||
-- choose closest player to attack
|
||||
if dist < min_dist
|
||||
and not attacked_p
|
||||
and self:line_of_sight( sp, p, 2) == true then
|
||||
local line_of_sight = self:line_of_sight( sp, p, 2) == true
|
||||
if dist < min_dist and not attacked_p and line_of_sight then
|
||||
min_dist = dist
|
||||
min_player = player
|
||||
end
|
||||
|
@ -422,11 +432,9 @@ function mob_class:npc_attack()
|
|||
local objs = minetest.get_objects_inside_radius(s, self.view_range)
|
||||
|
||||
for n = 1, #objs do
|
||||
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
||||
if obj and obj.type == "monster" then
|
||||
|
||||
p = obj.object:get_pos()
|
||||
sp = s
|
||||
|
||||
|
@ -436,8 +444,7 @@ function mob_class:npc_attack()
|
|||
p.y = p.y + 1
|
||||
sp.y = sp.y + 1
|
||||
|
||||
if dist < min_dist
|
||||
and self:line_of_sight( sp, p, 2) == true then
|
||||
if dist < min_dist and self:line_of_sight( sp, p, 2) == true then
|
||||
min_dist = dist
|
||||
min_player = obj.object
|
||||
end
|
||||
|
@ -734,7 +741,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
|
||||
local name = hitter:get_player_name() or ""
|
||||
|
||||
-- attack puncher and call other mobs for help
|
||||
-- attack puncher
|
||||
if self.passive == false
|
||||
and self.state ~= "flop"
|
||||
and (self.child == false or self.type == "monster")
|
||||
|
@ -746,6 +753,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
self:do_attack(hitter)
|
||||
self._aggro= true
|
||||
end
|
||||
end
|
||||
|
||||
-- alert others to the attack
|
||||
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
|
||||
|
@ -764,7 +772,7 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
obj:do_attack(hitter)
|
||||
elseif type(obj.group_attack) == "table" then
|
||||
for i=1, #obj.group_attack do
|
||||
if obj.name == obj.group_attack[i] then
|
||||
if obj.group_attack[i] == self.name then
|
||||
obj._aggro = true
|
||||
obj:do_attack(hitter)
|
||||
break
|
||||
|
@ -780,17 +788,427 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_aggro(dtime)
|
||||
if not self._aggro or not self.attack then return end
|
||||
if not self._check_aggro_timer or self._check_aggro_timer > 5 then
|
||||
|
||||
if not self._check_aggro_timer then
|
||||
self._check_aggro_timer = 0
|
||||
end
|
||||
|
||||
if self._check_aggro_timer > 5 then
|
||||
self._check_aggro_timer = 0
|
||||
|
||||
if self.attack then
|
||||
-- TODO consider removing this in favour of what is done in do_states_attack
|
||||
-- Attack is dropped in do_states_attack if out of range, so won't even trigger here
|
||||
-- I do not think this code does anything. Are mobs still loaded in at 128?
|
||||
if not self.attack:get_pos() or vector.distance(self.attack:get_pos(),self.object:get_pos()) > 128 then
|
||||
self._aggro = nil
|
||||
self.attack = nil
|
||||
self.state = "stand"
|
||||
end
|
||||
end
|
||||
end
|
||||
self._check_aggro_timer = self._check_aggro_timer + dtime
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function clear_aggro(self)
|
||||
self.state = "stand"
|
||||
self:set_velocity( 0)
|
||||
self:set_animation( "stand")
|
||||
|
||||
self.attack = nil
|
||||
self._aggro = nil
|
||||
|
||||
self.v_start = false
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self.path.way = nil
|
||||
end
|
||||
|
||||
function mob_class:do_states_attack (dtime)
|
||||
self.timer = self.timer + dtime
|
||||
if self.timer > 100 then
|
||||
self.timer = 1
|
||||
end
|
||||
|
||||
local s = self.object:get_pos()
|
||||
if not s then return end
|
||||
|
||||
local p = self.attack:get_pos() or s
|
||||
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
-- stop attacking if player invisible or out of range
|
||||
if not self.attack
|
||||
or not self.attack:get_pos()
|
||||
or not self:object_in_range(self.attack)
|
||||
or self.attack:get_hp() <= 0
|
||||
or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then
|
||||
|
||||
clear_aggro(self)
|
||||
return
|
||||
end
|
||||
|
||||
local target_line_of_sight = self:line_of_sight(s, p, 2)
|
||||
if not target_line_of_sight then
|
||||
if self.target_time_lost then
|
||||
local time_since_seen = os.time() - self.target_time_lost
|
||||
if time_since_seen > TIME_TO_FORGET_TARGET then
|
||||
self.target_time_lost = nil
|
||||
clear_aggro(self)
|
||||
return
|
||||
end
|
||||
else
|
||||
self.target_time_lost = os.time()
|
||||
end
|
||||
else
|
||||
self.target_time_lost = nil
|
||||
end
|
||||
|
||||
-- calculate distance from mob and enemy
|
||||
local dist = vector.distance(p, s)
|
||||
|
||||
if self.attack_type == "explode" then
|
||||
|
||||
if target_line_of_sight then
|
||||
local vec = { x = p.x - s.x, z = p.z - s.z }
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
end
|
||||
|
||||
local node_break_radius = self.explosion_radius or 1
|
||||
local entity_damage_radius = self.explosion_damage_radius
|
||||
or (node_break_radius * 2)
|
||||
|
||||
-- start timer when in reach and line of sight
|
||||
if not self.v_start and dist <= self.reach and target_line_of_sight then
|
||||
self.v_start = true
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self:mob_sound("fuse", nil, false)
|
||||
|
||||
-- stop timer if out of reach or direct line of sight
|
||||
elseif self.allow_fuse_reset and self.v_start
|
||||
and (dist >= self.explosiontimer_reset_radius or not target_line_of_sight) then
|
||||
self.v_start = false
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self.blinkstatus = false
|
||||
self:remove_texture_mod("^[brighten")
|
||||
end
|
||||
|
||||
-- walk right up to player unless the timer is active
|
||||
if self.v_start and (self.stop_to_explode or dist < self.reach) or not target_line_of_sight then
|
||||
self:set_velocity(0)
|
||||
else
|
||||
self:set_velocity(self.run_velocity)
|
||||
end
|
||||
|
||||
if self.animation and self.animation.run_start then
|
||||
self:set_animation( "run")
|
||||
else
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
|
||||
if self.v_start then
|
||||
self.timer = self.timer + dtime
|
||||
self.blinktimer = (self.blinktimer or 0) + dtime
|
||||
|
||||
if self.blinktimer > 0.2 then
|
||||
self.blinktimer = 0
|
||||
if self.blinkstatus then
|
||||
self:remove_texture_mod("^[brighten")
|
||||
else
|
||||
self:add_texture_mod("^[brighten")
|
||||
end
|
||||
self.blinkstatus = not self.blinkstatus
|
||||
end
|
||||
|
||||
if self.timer > self.explosion_timer then
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if mobs_griefing and not minetest.is_protected(pos, "") then
|
||||
mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { drop_chance = 1.0 }, self.object)
|
||||
else
|
||||
minetest.sound_play(self.sounds.explode, {
|
||||
pos = pos,
|
||||
gain = 1.0,
|
||||
max_hear_distance = self.sounds.distance or 32
|
||||
}, true)
|
||||
self:entity_physics(pos,entity_damage_radius)
|
||||
mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", nil, nil, node_break_radius, 1, 0)
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.attack_type == "dogfight"
|
||||
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy)
|
||||
or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then
|
||||
|
||||
if self.fly
|
||||
and dist > self.reach then
|
||||
|
||||
local p1 = s
|
||||
local me_y = math.floor(p1.y)
|
||||
local p2 = p
|
||||
local p_y = math.floor(p2.y + 1)
|
||||
local v = self.object:get_velocity()
|
||||
|
||||
if self:flight_check( s) then
|
||||
|
||||
if me_y < p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = 1 * self.walk_velocity,
|
||||
z = v.z
|
||||
})
|
||||
|
||||
elseif me_y > p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = -1 * self.walk_velocity,
|
||||
z = v.z
|
||||
})
|
||||
end
|
||||
else
|
||||
if me_y < p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = 0.01,
|
||||
z = v.z
|
||||
})
|
||||
|
||||
elseif me_y > p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = -0.01,
|
||||
z = v.z
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- rnd: new movement direction
|
||||
if self.path.following
|
||||
and self.path.way
|
||||
and self.attack_type ~= "dogshoot" then
|
||||
|
||||
-- no paths longer than 50
|
||||
if #self.path.way > 50
|
||||
or dist < self.reach then
|
||||
self.path.following = false
|
||||
return
|
||||
end
|
||||
|
||||
local p1 = self.path.way[1]
|
||||
|
||||
if not p1 then
|
||||
self.path.following = false
|
||||
return
|
||||
end
|
||||
|
||||
if math.abs(p1.x-s.x) + math.abs(p1.z - s.z) < 0.6 then
|
||||
-- reached waypoint, remove it from queue
|
||||
table.remove(self.path.way, 1)
|
||||
end
|
||||
|
||||
-- set new temporary target
|
||||
p = {x = p1.x, y = p1.y, z = p1.z}
|
||||
end
|
||||
|
||||
local vec = {
|
||||
x = p.x - s.x,
|
||||
z = p.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw + math.pi end
|
||||
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
-- move towards enemy if beyond mob reach
|
||||
if dist > self.reach then
|
||||
-- path finding by rnd
|
||||
if enable_pathfinding and self.pathfinding then
|
||||
self:smart_mobs(s, p, dist, dtime)
|
||||
end
|
||||
|
||||
if self:is_at_cliff_or_danger() then
|
||||
self:set_velocity( 0)
|
||||
self:set_animation( "stand")
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
yaw = self:set_yaw( yaw + 0.78, 8)
|
||||
else
|
||||
if self.path.stuck then
|
||||
self:set_velocity(self.walk_velocity)
|
||||
else
|
||||
self:set_velocity(self.run_velocity)
|
||||
end
|
||||
if self.animation and self.animation.run_start then
|
||||
self:set_animation("run")
|
||||
else
|
||||
self:set_animation("walk")
|
||||
end
|
||||
end
|
||||
else -- rnd: if inside reach range
|
||||
self.path.stuck = false
|
||||
self.path.stuck_timer = 0
|
||||
self.path.following = false -- not stuck anymore
|
||||
|
||||
self:set_velocity( 0)
|
||||
|
||||
local attack_frequency = self.attack_frequency or 1
|
||||
|
||||
if self.timer > attack_frequency then
|
||||
self.timer = 0
|
||||
|
||||
if not self.custom_attack then
|
||||
if self.double_melee_attack and math.random(1, 2) == 1 then
|
||||
self:set_animation("punch2")
|
||||
else
|
||||
self:set_animation("punch")
|
||||
end
|
||||
|
||||
local p2 = p
|
||||
local s2 = s
|
||||
|
||||
p2.y = p2.y + .5
|
||||
s2.y = s2.y + .5
|
||||
|
||||
if self:line_of_sight( p2, s2) == true then
|
||||
self:mob_sound("attack")
|
||||
|
||||
-- punch player (or what player is attached to)
|
||||
local attached = self.attack:get_attach()
|
||||
if attached then
|
||||
self.attack = attached
|
||||
end
|
||||
self.attack:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self.damage}
|
||||
}, nil)
|
||||
end
|
||||
else
|
||||
self.custom_attack(self, p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.attack_type == "shoot"
|
||||
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
|
||||
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then
|
||||
|
||||
p.y = p.y - .5
|
||||
s.y = s.y + .5
|
||||
|
||||
local dist = vector.distance(p, s)
|
||||
local vec = {
|
||||
x = p.x - s.x,
|
||||
y = p.y - s.y,
|
||||
z = p.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
local stay_away_from_player = vector.zero()
|
||||
|
||||
--strafe back and fourth
|
||||
|
||||
--stay away from player so as to shoot them
|
||||
if dist < self.avoid_distance and self.shooter_avoid_enemy then
|
||||
self:set_animation( "shoot")
|
||||
stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33)
|
||||
end
|
||||
|
||||
if self.strafes then
|
||||
if not self.strafe_direction then
|
||||
self.strafe_direction = 1.57
|
||||
end
|
||||
if math.random(40) == 1 then
|
||||
self.strafe_direction = self.strafe_direction*-1
|
||||
end
|
||||
|
||||
local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction)
|
||||
local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity)
|
||||
|
||||
if dir2 and stay_away_from_player then
|
||||
self.acc = vector.add(dir2, stay_away_from_player)
|
||||
end
|
||||
else
|
||||
self:set_velocity( 0)
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
|
||||
|
||||
if self.shoot_interval
|
||||
and self.timer > self.shoot_interval
|
||||
and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
|
||||
and math.random(1, 100) <= 60 then
|
||||
|
||||
self.timer = 0
|
||||
self:set_animation( "shoot")
|
||||
|
||||
-- play shoot attack sound
|
||||
self:mob_sound("shoot_attack")
|
||||
|
||||
-- Shoot arrow
|
||||
if minetest.registered_entities[self.arrow] then
|
||||
|
||||
local arrow, ent
|
||||
local v = 1
|
||||
if not self.shoot_arrow then
|
||||
self.firing = true
|
||||
minetest.after(1, function()
|
||||
self.firing = false
|
||||
end)
|
||||
arrow = minetest.add_entity(p, self.arrow)
|
||||
ent = arrow:get_luaentity()
|
||||
if ent.velocity then
|
||||
v = ent.velocity
|
||||
end
|
||||
ent.switch = 1
|
||||
ent.owner_id = tostring(self.object) -- add unique owner id to arrow
|
||||
|
||||
-- important for mcl_shields
|
||||
ent._shooter = self.object
|
||||
ent._saved_shooter_pos = self.object:get_pos()
|
||||
end
|
||||
|
||||
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
|
||||
-- offset makes shoot aim accurate
|
||||
vec.y = vec.y + self.shoot_offset
|
||||
vec.x = vec.x * (v / amount)
|
||||
vec.y = vec.y * (v / amount)
|
||||
vec.z = vec.z * (v / amount)
|
||||
if self.shoot_arrow then
|
||||
vec = vector.normalize(vec)
|
||||
self:shoot_arrow(p, vec)
|
||||
else
|
||||
arrow:set_velocity(vec)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
local math, tonumber, vector, minetest, mcl_mobs = math, tonumber, vector, minetest, mcl_mobs
|
||||
local mob_class = mcl_mobs.mob_class
|
||||
local active_particlespawners = {}
|
||||
local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
|
||||
local DEFAULT_FALL_SPEED = -9.81*1.5
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
|
||||
local player_transfer_distance = tonumber(minetest.settings:get("player_transfer_distance")) or 128
|
||||
if player_transfer_distance == 0 then player_transfer_distance = math.huge end
|
||||
|
||||
|
||||
local function validate_vector (vect)
|
||||
if vect then
|
||||
if tonumber(vect.x) and tonumber(vect.y) and tonumber(vect.z) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- custom particle effects
|
||||
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
|
||||
|
||||
|
@ -116,6 +127,7 @@ function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
|
|||
-- randomize the pitch a bit
|
||||
pitch = pitch + math.random(-10, 10) * 0.005
|
||||
end
|
||||
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
|
||||
minetest.sound_play(sound, {
|
||||
object = self.object,
|
||||
gain = 1.0,
|
||||
|
@ -126,6 +138,19 @@ function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
|
|||
end
|
||||
end
|
||||
|
||||
function mob_class:step_opinion_sound(dtime)
|
||||
if self.state ~= "attack" and self.state ~= PATHFINDING then
|
||||
|
||||
if self.opinion_sound_cooloff > 0 then
|
||||
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
|
||||
end
|
||||
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
|
||||
if math.random(1, 70) == 1 then
|
||||
self:mob_sound("random", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:add_texture_mod(mod)
|
||||
local full_mod = ""
|
||||
local already_added = false
|
||||
|
@ -231,16 +256,20 @@ function mob_class:set_animation(anim, fixed_frame)
|
|||
if not self.animation or not anim then
|
||||
return
|
||||
end
|
||||
|
||||
if self.jockey and self.object:get_attach() then
|
||||
anim = "jockey"
|
||||
elseif not self.object:get_attach() then
|
||||
self.jockey = nil
|
||||
end
|
||||
|
||||
if self.state == "die" and anim ~= "die" and anim ~= "stand" then
|
||||
return
|
||||
end
|
||||
|
||||
if self.jockey then
|
||||
anim = "jockey"
|
||||
end
|
||||
|
||||
|
||||
if self:flight_check() and self.fly and anim == "walk" then anim = "fly" end
|
||||
if self.fly and self:flight_check() and anim == "walk" then anim = "fly" end
|
||||
|
||||
self._current_animation = self._current_animation or ""
|
||||
|
||||
|
@ -279,30 +308,69 @@ local function dir_to_pitch(dir)
|
|||
return -math.atan2(-dir.y, xz)
|
||||
end
|
||||
|
||||
function mob_class:check_head_swivel(dtime)
|
||||
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
|
||||
local final_rotation = vector.new(0,0,0)
|
||||
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
|
||||
|
||||
local function who_are_you_looking_at (self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do
|
||||
if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then
|
||||
if not self._locked_object then
|
||||
if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then
|
||||
self._locked_object = obj
|
||||
end
|
||||
local stop_look_at_player_chance = math.random(833/self.curiosity)
|
||||
-- was 10000 - div by 12 for avg entities as outside loop
|
||||
|
||||
local stop_look_at_player = stop_look_at_player_chance == 1
|
||||
|
||||
if self.attack then
|
||||
if not self.target_time_lost then
|
||||
self._locked_object = self.attack
|
||||
else
|
||||
if math.random(10000/self.curiosity) == 1 then
|
||||
self._locked_object = nil
|
||||
end
|
||||
elseif self.following then
|
||||
self._locked_object = self.following
|
||||
elseif self._locked_object then
|
||||
if stop_look_at_player then
|
||||
--minetest.log("Stop look: ".. self.name)
|
||||
self._locked_object = nil
|
||||
end
|
||||
elseif not self._locked_object then
|
||||
if mcl_util.check_dtime_timer(self, dtime, "step_look_for_someone", 0.2) then
|
||||
--minetest.log("Change look check: ".. self.name)
|
||||
|
||||
-- For the wither this was 20/60=0.33, so probably need to rebalance and divide rates.
|
||||
-- but frequency of check isn't good as it is costly. Making others too infrequent requires testing
|
||||
local chance = 150/self.curiosity
|
||||
|
||||
if chance < 1 then chance = 1 end
|
||||
local look_at_player_chance = math.random(chance)
|
||||
|
||||
-- was 5000 but called in loop based on entities. so div by 12 as estimate avg of entities found,
|
||||
-- then div by 20 as less freq lookup
|
||||
|
||||
local look_at_player = look_at_player_chance == 1
|
||||
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 8)) do
|
||||
if obj:is_player() and vector.distance(pos,obj:get_pos()) < 4 then
|
||||
--minetest.log("Change look to player: ".. self.name)
|
||||
self._locked_object = obj
|
||||
break
|
||||
elseif obj:is_player() or (obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity()) then
|
||||
if look_at_player then
|
||||
--minetest.log("Change look to mob: ".. self.name)
|
||||
self._locked_object = obj
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.attack or self.following then
|
||||
self._locked_object = self.attack or self.following
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_head_swivel(dtime)
|
||||
if not self.head_swivel or type(self.head_swivel) ~= "string" then return end
|
||||
|
||||
|
||||
who_are_you_looking_at (self, dtime)
|
||||
|
||||
local final_rotation = vector.zero()
|
||||
local oldp,oldr = self.object:get_bone_position(self.head_swivel)
|
||||
|
||||
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
|
||||
local _locked_object_eye_height = 1.5
|
||||
|
@ -313,11 +381,14 @@ function mob_class:check_head_swivel(dtime)
|
|||
_locked_object_eye_height = self._locked_object:get_properties().eye_height
|
||||
end
|
||||
if _locked_object_eye_height then
|
||||
|
||||
local self_rot = self.object:get_rotation()
|
||||
if self.object:get_attach() then
|
||||
-- If a mob is attached, should we really be messing with what they are looking at?
|
||||
-- Should this be excluded?
|
||||
if self.object:get_attach() and self.object:get_attach():get_rotation() then
|
||||
self_rot = self.object:get_attach():get_rotation()
|
||||
end
|
||||
if self.rot then
|
||||
|
||||
local player_pos = self._locked_object:get_pos()
|
||||
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
|
||||
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
|
||||
|
@ -341,16 +412,17 @@ function mob_class:check_head_swivel(dtime)
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
|
||||
final_rotation = vector.multiply(oldr, 0.9)
|
||||
else
|
||||
final_rotation = vector.new(0,0,0)
|
||||
--final_rotation = vector.new(0,0,0)
|
||||
end
|
||||
|
||||
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function mob_class:set_animation_speed()
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
|
@ -366,7 +438,7 @@ function mob_class:set_animation_speed()
|
|||
end
|
||||
end
|
||||
--set_speed
|
||||
if self.acc then
|
||||
if validate_vector(self.acc) then
|
||||
self.object:add_velocity(self.acc)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -154,6 +154,7 @@ function mcl_mobs.register_mob(name, def)
|
|||
description = def.description,
|
||||
type = def.type,
|
||||
attack_type = def.attack_type,
|
||||
attack_frequency = def.attack_frequency,
|
||||
fly = def.fly or false,
|
||||
fly_in = def.fly_in or {"air", "__airlike"},
|
||||
owner = def.owner or "",
|
||||
|
@ -216,6 +217,7 @@ function mcl_mobs.register_mob(name, def)
|
|||
replace_with = def.replace_with,
|
||||
replace_offset = def.replace_offset or 0,
|
||||
on_replace = def.on_replace,
|
||||
replace_delay = def.replace_delay or 0,
|
||||
timer = 0,
|
||||
env_damage_timer = 0,
|
||||
tamed = false,
|
||||
|
@ -497,12 +499,11 @@ function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addeg
|
|||
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
|
||||
|
||||
if pos and within_limits(pos, 0) and not minetest.is_protected(pos, placer:get_player_name()) then
|
||||
local name = placer:get_player_name()
|
||||
local privs = minetest.get_player_privs(name)
|
||||
|
||||
|
||||
if under.name == "mcl_mobspawners:spawner" then
|
||||
if minetest.is_protected(pointed_thing.under, name) then
|
||||
minetest.record_protection_violation(pointed_thing.under, name)
|
||||
|
@ -512,7 +513,14 @@ function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addeg
|
|||
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())
|
||||
|
||||
local dim = mcl_worlds.pos_to_dimension(placer:get_pos())
|
||||
local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)}
|
||||
|
||||
--minetest.log("min light: " .. mob_light_lvl[1])
|
||||
--minetest.log("max light: " .. mob_light_lvl[2])
|
||||
|
||||
mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name(), mob_light_lvl[1], mob_light_lvl[2])
|
||||
if not minetest.is_creative_enabled(name) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
|
@ -550,7 +558,7 @@ function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addeg
|
|||
nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
|
||||
end
|
||||
ent.nametag = nametag
|
||||
update_tag(ent)
|
||||
ent:update_tag()
|
||||
end
|
||||
|
||||
-- if not in creative then take item
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# textdomain: mcl_mobs
|
||||
Peaceful mode active! No monsters will spawn.=Fredelig tilstand aktiveret! Ingen monstre vil spawne.
|
||||
This allows you to place a single mob.=Dette gør dig i stand til at placere et enkelt monster.
|
||||
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.=Placér det blot der hvor du ønsker, at monsteret skal komme. Dyr vil spawne tamme, medmindre du holder snige-knappen nede mens du placerer dem. Hvis du placerer denne på et monsterspawn, ændrer du hvilket monster det spawner.
|
||||
You need the “maphack” privilege to change the mob spawner.=Du skal have "maphack" privilegier for at ændre monsterspawneren.
|
||||
Name Tag=Navneskilt
|
||||
A name tag is an item to name a mob.=Et navneskilt bruges til at navngive et monster.
|
||||
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Før du bruger navneskiltet, skal du vælge navnet ved en ambolt. Derefter kan du bruge navneskiltet til at navngive et monster. Dette opbruger navneskiltet.
|
||||
Only peaceful mobs allowed!=Kun fredelige monstre er tilladt!
|
||||
Give names to mobs=Giv navne til monstre
|
||||
Set name at anvil=Vælg navn ved en ambolt.
|
|
@ -9,3 +9,5 @@ Before you use the name tag, you need to set a name at an anvil. Then you can us
|
|||
Only peaceful mobs allowed!=Seuls les mobs pacifiques sont autorisées!
|
||||
Give names to mobs=Donne des noms aux mobs
|
||||
Set name at anvil=Définir le nom sur l'enclume
|
||||
Removes specified mobs except nametagged and tamed ones. For the second parameter, use nametagged/tamed to select only nametagged/tamed mobs, or a range to specify a maximum distance from the player.=Enlève les mobs spécifiés sauf ceux qui sont nommés et apprivoisés. Pour le deuxième paramètre, utiliser nametagged/tamed pour ne sélectionner que les mobs nommés/apprivoisés, ou une distance pour spécifier la distance maximale par rapport au joueur.
|
||||
Default usage. Clearing hostile mobs. For more options please type: /help clearmobs=Usage par défaut. Enlève les mobs hostiles. Pour plus d'options saisir : /help clearmobs
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# textdomain: mcl_mobs
|
||||
Peaceful mode active! No monsters will spawn.=Мирный режим включён! Монстры не будут появляться.
|
||||
This allows you to place a single mob.=Позволяет вам разместить одного моба.
|
||||
Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns.=Просто поместите это туда, где хотите, чтобы появился моб. Животные будут появляться уже прирученные, если это не нужно, удерживайте клавишу [Красться] при размещении. Если поместить это на спаунер, появляющийся из него моб будет изменён.
|
||||
You need the “maphack” privilege to change the mob spawner.=Вам нужно обладать привилегией “maphack”, чтобы изменить спаунер моба.
|
||||
Peaceful mode active! No monsters will spawn.=Мирный режим включён! Чудовища не будут появляться.
|
||||
This allows you to place a single mob.=Позволяет вам породить одно существо.
|
||||
Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns.=Просто нажмите на блок, где хотите, чтобы появилось существо. Животные будут появляться уже прирученные, если это не нужно, удерживайте клавишу [Красться] при размещении. Если использовать на порождателе, тогда существо будет изменено.
|
||||
You need the “maphack” privilege to change the mob spawner.=Вам нужно обладать привилегией «maphack», чтобы изменить порождатель существ.
|
||||
Name Tag=Именная бирка
|
||||
A name tag is an item to name a mob.=Именная бирка это предмет, чтобы дать мобу имя.
|
||||
A name tag is an item to name a mob.=Именная бирка — это предмет, чтобы дать существу имя.
|
||||
Before you use the name tag, you need to set a name at an anvil. Then you can use the name tag to name a mob. This uses up the name tag.=Прежде чем использовать именную бирку, нужно задать имя на наковальне. Тогда вы сможете использовать бирку, чтобы дать имя мобу.
|
||||
Only peaceful mobs allowed!=Разрешены только мирные мобы!
|
||||
Give names to mobs=Даёт имена мобам
|
||||
Only peaceful mobs allowed!=Разрешены только мирные существа!
|
||||
Give names to mobs=Даёт имена существам
|
||||
Set name at anvil=Задайте имя при помощи наковальни
|
||||
|
|
|
@ -9,3 +9,5 @@ Before you use the name tag, you need to set a name at an anvil. Then you can us
|
|||
Only peaceful mobs allowed!=
|
||||
Give names to mobs=
|
||||
Set name at anvil=
|
||||
Removes specified mobs except nametagged and tamed ones. For the second parameter, use nametagged/tamed to select only nametagged/tamed mobs, or a range to specify a maximum distance from the player.=
|
||||
Default usage. Clearing hostile mobs. For more options please type: /help clearmobs=
|
||||
|
|
|
@ -2,4 +2,4 @@ name = mcl_mobs
|
|||
author = PilzAdam
|
||||
description = Adds a mob API for mods to add animals or monsters, etc.
|
||||
depends = mcl_particles
|
||||
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience
|
||||
optional_depends = mcl_weather, mcl_explosions, mcl_hunger, mcl_worlds, invisibility, lucky_block, cmi, doc_identifier, mcl_armor, mcl_portals, mcl_experience, mcl_sculk
|
||||
|
|
|
@ -3,14 +3,13 @@ local mob_class = mcl_mobs.mob_class
|
|||
local DEFAULT_FALL_SPEED = -9.81*1.5
|
||||
local FLOP_HEIGHT = 6
|
||||
local FLOP_HOR_SPEED = 1.5
|
||||
|
||||
local CHECK_HERD_FREQUENCY = 4
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
local enable_pathfinding = true
|
||||
|
||||
local node_ice = "mcl_core:ice"
|
||||
local node_snowblock = "mcl_core:snowblock"
|
||||
local node_snow = "mcl_core:snow"
|
||||
|
||||
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
|
||||
local atann = math.atan
|
||||
|
@ -22,14 +21,19 @@ local function atan(x)
|
|||
end
|
||||
end
|
||||
|
||||
local registered_fallback_node = minetest.registered_nodes[mcl_mobs.fallback_node]
|
||||
|
||||
-- get node but use fallback for nil or unknown
|
||||
local node_ok = function(pos, fallback)
|
||||
fallback = fallback or mcl_mobs.fallback_node
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if node and minetest.registered_nodes[node.name] then
|
||||
return node
|
||||
end
|
||||
if fallback then
|
||||
return minetest.registered_nodes[fallback]
|
||||
else
|
||||
return registered_fallback_node
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true is node can deal damage to self
|
||||
|
@ -202,19 +206,21 @@ end
|
|||
|
||||
-- is mob facing a cliff or danger
|
||||
function mob_class:is_at_cliff_or_danger()
|
||||
if self.fear_height == 0 or self:can_jump_cliff() or self._jumping_cliff or not self.object:get_luaentity() then -- 0 for no falling protection!
|
||||
if self.fear_height == 0 or self._jumping_cliff or self._can_jump_cliff or not self.object:get_luaentity() then -- 0 for no falling protection!
|
||||
return false
|
||||
end
|
||||
|
||||
local yaw = self.object:get_yaw()
|
||||
local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)
|
||||
local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
local ypos = pos.y + self.collisionbox[2] -- just above floor
|
||||
|
||||
local free_fall, blocker = minetest.line_of_sight(
|
||||
{x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
|
||||
{x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
|
||||
vector.new(pos.x + dir_x, ypos, pos.z + dir_z),
|
||||
vector.new(pos.x + dir_x, ypos - self.fear_height, pos.z + dir_z))
|
||||
|
||||
if free_fall then
|
||||
return true
|
||||
else
|
||||
|
@ -236,39 +242,82 @@ end
|
|||
|
||||
-- copy the 'mob facing cliff_or_danger check' from above, and rework to avoid water
|
||||
function mob_class:is_at_water_danger()
|
||||
if not self.object:get_luaentity() or self:can_jump_cliff() or self._jumping_cliff then
|
||||
if self.water_damage == 0 and self.breath_max == -1 then
|
||||
--minetest.log("Do not need a water check for: " .. self.name)
|
||||
return
|
||||
end
|
||||
|
||||
local in_water_danger = self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)
|
||||
if in_water_danger then return false end -- If you're in trouble, do not stop
|
||||
|
||||
if not self.object:get_luaentity() or self._jumping_cliff or self._can_jump_cliff then
|
||||
return false
|
||||
end
|
||||
local yaw = self.object:get_yaw()
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if not yaw or not pos then
|
||||
return
|
||||
end
|
||||
|
||||
local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)
|
||||
local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
local ypos = pos.y + self.collisionbox[2] -- just above floor
|
||||
|
||||
local free_fall, blocker = minetest.line_of_sight(
|
||||
{x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
|
||||
{x = pos.x + dir_x, y = ypos - 3, z = pos.z + dir_z})
|
||||
if free_fall then
|
||||
return true
|
||||
else
|
||||
local los, blocker = minetest.line_of_sight(
|
||||
vector.new(pos.x + dir_x, ypos, pos.z + dir_z),
|
||||
vector.new(pos.x + dir_x, ypos - 3, pos.z + dir_z))
|
||||
|
||||
if not los then
|
||||
local bnode = minetest.get_node(blocker)
|
||||
local waterdanger = self:is_node_waterhazard(bnode.name)
|
||||
if
|
||||
waterdanger and (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard( self.standing_on)) then
|
||||
return false
|
||||
elseif waterdanger and (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)) == false then
|
||||
|
||||
if waterdanger and not in_water_danger then
|
||||
return true
|
||||
else
|
||||
local def = minetest.registered_nodes[bnode.name]
|
||||
if def and def.walkable then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function mob_class:env_danger_movement_checks(player_in_active_range)
|
||||
local yaw = 0
|
||||
|
||||
if not player_in_active_range then return end
|
||||
|
||||
if self.state == PATHFINDING
|
||||
or self.state == "attack"
|
||||
or self.state == "stand"
|
||||
or self.state == "runaway" then
|
||||
return
|
||||
end
|
||||
|
||||
if self:is_at_water_danger() then
|
||||
--minetest.log("At water danger for mob, stop?: " .. self.name)
|
||||
if math.random(1, 10) <= 7 then
|
||||
if self.state ~= "stand" then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
end
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--[[if self:is_at_cliff_or_danger(can_jump_cliff) then
|
||||
if self.state ~= "stand" then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
end
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
yaw = self:set_yaw( yaw + 0.78, 8)
|
||||
end--]]
|
||||
end
|
||||
|
||||
-- jump if facing a solid node (not fences or gates)
|
||||
function mob_class:do_jump()
|
||||
if not self.jump
|
||||
|
@ -306,9 +355,11 @@ function mob_class:do_jump()
|
|||
jump_c_multiplier = v2/self.walk_velocity/2
|
||||
end
|
||||
|
||||
local yaw_dir = minetest.yaw_to_dir(self.object:get_yaw())
|
||||
|
||||
-- where is front
|
||||
local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
|
||||
local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
|
||||
local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+yaw_dir.x
|
||||
local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+yaw_dir.z
|
||||
|
||||
-- what is in front of mob?
|
||||
nod = node_ok({
|
||||
|
@ -337,7 +388,7 @@ function mob_class:do_jump()
|
|||
end
|
||||
|
||||
local ndef = minetest.registered_nodes[nod.name]
|
||||
if self.walk_chance == 0 or ndef and ndef.walkable or self:can_jump_cliff() then
|
||||
if self.walk_chance == 0 or ndef and ndef.walkable or self._can_jump_cliff then
|
||||
|
||||
if minetest.get_item_group(nod.name, "fence") == 0
|
||||
and minetest.get_item_group(nod.name, "fence_gate") == 0
|
||||
|
@ -347,7 +398,7 @@ function mob_class:do_jump()
|
|||
|
||||
v.y = self.jump_height + 0.1 * 3
|
||||
|
||||
if self:can_jump_cliff() then
|
||||
if self._can_jump_cliff then
|
||||
v=vector.multiply(v, vector.new(2.8,1,2.8))
|
||||
end
|
||||
|
||||
|
@ -429,7 +480,8 @@ end
|
|||
|
||||
|
||||
-- find and replace what mob is looking for (grass, wheat etc.)
|
||||
function mob_class:replace(pos)
|
||||
function mob_class:replace_node(pos)
|
||||
|
||||
|
||||
if not self.replace_rate
|
||||
or not self.replace_what
|
||||
|
@ -461,18 +513,21 @@ function mob_class:replace(pos)
|
|||
|
||||
local oldnode = {name = what, param2 = node.param2}
|
||||
local newnode = {name = with, param2 = node.param2}
|
||||
local on_replace_return
|
||||
|
||||
local on_replace_return = false
|
||||
if self.on_replace then
|
||||
on_replace_return = self.on_replace(self, pos, oldnode, newnode)
|
||||
end
|
||||
|
||||
|
||||
if on_replace_return ~= false then
|
||||
|
||||
if mobs_griefing then
|
||||
minetest.after(self.replace_delay, function()
|
||||
if self and self.object and self.object:get_velocity() and self.health > 0 then
|
||||
minetest.set_node(pos, newnode)
|
||||
end
|
||||
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -582,76 +637,52 @@ function mob_class:check_runaway_from()
|
|||
end
|
||||
|
||||
|
||||
-- follow player if owner or holding item, if fish outta water then flop
|
||||
function mob_class:follow_flop()
|
||||
|
||||
-- follow player if owner or holding item
|
||||
function mob_class:check_follow()
|
||||
-- find player to follow
|
||||
if (self.follow ~= ""
|
||||
or self.order == "follow")
|
||||
and not self.following
|
||||
if (self.follow ~= "" or self.order == "follow") and not self.following
|
||||
and self.state ~= "attack"
|
||||
and self.order ~= "sit"
|
||||
and self.state ~= "runaway" then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local players = minetest.get_connected_players()
|
||||
|
||||
for n = 1, #players do
|
||||
|
||||
if (self:object_in_range(players[n]))
|
||||
and not mcl_mobs.invis[ players[n]:get_player_name() ] then
|
||||
|
||||
if (self:object_in_range(players[n])) and not mcl_mobs.invis[ players[n]:get_player_name() ] then
|
||||
self.following = players[n]
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.type == "npc"
|
||||
and self.order == "follow"
|
||||
and self.state ~= "attack"
|
||||
and self.order ~= "sit"
|
||||
and self.owner ~= "" then
|
||||
if self.type == "npc" and self.order == "follow"
|
||||
and self.state ~= "attack" and self.order ~= "sit" and self.owner ~= "" then
|
||||
|
||||
-- npc stop following player if not owner
|
||||
if self.following
|
||||
and self.owner
|
||||
and self.owner ~= self.following:get_player_name() then
|
||||
if self.following and self.owner and self.owner ~= self.following:get_player_name() then
|
||||
self.following = nil
|
||||
end
|
||||
else
|
||||
-- stop following player if not holding specific item,
|
||||
-- mob is horny, fleeing or attacking
|
||||
if self.following
|
||||
and self.following:is_player()
|
||||
and (self:follow_holding(self.following) == false or
|
||||
self.horny or self.state == "runaway") then
|
||||
if self.following and self.following:is_player()
|
||||
and (self:follow_holding(self.following) == false or self.horny or self.state == "runaway") then
|
||||
self.following = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- follow that thing
|
||||
if self.following then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
|
||||
local p
|
||||
|
||||
if self.following:is_player() then
|
||||
|
||||
p = self.following:get_pos()
|
||||
|
||||
elseif self.following.object then
|
||||
|
||||
p = self.following.object:get_pos()
|
||||
end
|
||||
|
||||
if p then
|
||||
|
||||
local dist = vector.distance(p, s)
|
||||
|
||||
-- dont follow if out of range
|
||||
if (not self:object_in_range(self.following)) then
|
||||
self.following = nil
|
||||
else
|
||||
|
@ -661,17 +692,12 @@ function mob_class:follow_flop()
|
|||
}
|
||||
|
||||
local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
|
||||
self:set_yaw( yaw, 2.35)
|
||||
|
||||
-- anyone but standing npc's can move along
|
||||
if dist > 3
|
||||
and self.order ~= "stand" then
|
||||
|
||||
if dist > 3 and self.order ~= "stand" then
|
||||
self:set_velocity(self.follow_velocity)
|
||||
|
||||
if self.walk_chance ~= 0 then
|
||||
self:set_animation( "run")
|
||||
end
|
||||
|
@ -679,17 +705,18 @@ function mob_class:follow_flop()
|
|||
self:set_velocity(0)
|
||||
self:set_animation( "stand")
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:flop()
|
||||
-- swimmers flop when out of their element, and swim again when back in
|
||||
if self.fly then
|
||||
local s = self.object:get_pos()
|
||||
if self:flight_check( s) == false then
|
||||
|
||||
if self:flight_check(s) == false then
|
||||
self.state = "flop"
|
||||
self.object:set_acceleration({x = 0, y = DEFAULT_FALL_SPEED, z = 0})
|
||||
|
||||
|
@ -708,11 +735,10 @@ function mob_class:follow_flop()
|
|||
end
|
||||
|
||||
self:set_animation( "stand", true)
|
||||
|
||||
return
|
||||
elseif self.state == "flop" then
|
||||
self.state = "stand"
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
self.object:set_acceleration(vector.zero())
|
||||
self:set_velocity(0)
|
||||
end
|
||||
end
|
||||
|
@ -739,9 +765,12 @@ end
|
|||
local check_herd_timer = 0
|
||||
function mob_class:check_herd(dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
if not pos or self.state == "attack" then return end
|
||||
-- Does any mob not move in group. Weird check for something not set?
|
||||
if self.move_in_group == false then return end
|
||||
|
||||
check_herd_timer = check_herd_timer + dtime
|
||||
if check_herd_timer < 4 then return end
|
||||
if check_herd_timer < CHECK_HERD_FREQUENCY then return end
|
||||
check_herd_timer = 0
|
||||
for _,o in pairs(minetest.get_objects_inside_radius(pos,self.view_range)) do
|
||||
local l = o:get_luaentity()
|
||||
|
@ -769,70 +798,9 @@ function mob_class:teleport(target)
|
|||
end
|
||||
end
|
||||
|
||||
-- execute current state (stand, walk, run, attacks)
|
||||
-- returns true if mob has died
|
||||
function mob_class:do_states(dtime)
|
||||
--if self.can_open_doors then check_doors(self) end
|
||||
|
||||
function mob_class:do_states_walk()
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
if self.state == "stand" then
|
||||
if math.random(1, 4) == 1 then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local objs = minetest.get_objects_inside_radius(s, 3)
|
||||
local lp
|
||||
for n = 1, #objs do
|
||||
if objs[n]:is_player() then
|
||||
lp = objs[n]:get_pos()
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- look at any players nearby, otherwise turn randomly
|
||||
if lp and self.look_at_players then
|
||||
|
||||
local vec = {
|
||||
x = lp.x - s.x,
|
||||
z = lp.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if lp.x > s.x then yaw = yaw +math.pi end
|
||||
else
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
end
|
||||
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
if self.order == "sit" then
|
||||
self:set_animation( "sit")
|
||||
self:set_velocity(0)
|
||||
else
|
||||
self:set_animation( "stand")
|
||||
self:set_velocity(0)
|
||||
end
|
||||
|
||||
-- npc's ordered to stand stay standing
|
||||
if self.order == "stand" or self.order == "sleep" or self.order == "work" then
|
||||
|
||||
else
|
||||
if self.walk_chance ~= 0
|
||||
and self.facing_fence ~= true
|
||||
and math.random(1, 100) <= self.walk_chance
|
||||
and self:is_at_cliff_or_danger() == false then
|
||||
|
||||
self:set_velocity(self.walk_velocity)
|
||||
self.state = "walk"
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.state == PATHFINDING then
|
||||
self:check_gowp(dtime)
|
||||
|
||||
elseif self.state == "walk" then
|
||||
local s = self.object:get_pos()
|
||||
local lp = nil
|
||||
|
||||
|
@ -840,21 +808,13 @@ function mob_class:do_states(dtime)
|
|||
if (self.water_damage > 0
|
||||
and self.lava_damage > 0)
|
||||
or self.breath_max ~= -1 then
|
||||
|
||||
lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
|
||||
|
||||
elseif self.water_damage > 0 then
|
||||
|
||||
lp = minetest.find_node_near(s, 1, {"group:water"})
|
||||
|
||||
elseif self.lava_damage > 0 then
|
||||
|
||||
lp = minetest.find_node_near(s, 1, {"group:lava"})
|
||||
|
||||
elseif self.fire_damage > 0 then
|
||||
|
||||
lp = minetest.find_node_near(s, 1, {"group:fire"})
|
||||
|
||||
end
|
||||
|
||||
local is_in_danger = false
|
||||
|
@ -939,9 +899,68 @@ function mob_class:do_states(dtime)
|
|||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- runaway when punched
|
||||
elseif self.state == "runaway" then
|
||||
function mob_class:do_states_stand(player_in_active_range)
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
if math.random(1, 4) == 1 then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local objs = minetest.get_objects_inside_radius(s, 3)
|
||||
local lp
|
||||
for n = 1, #objs do
|
||||
if objs[n]:is_player() then
|
||||
lp = objs[n]:get_pos()
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- look at any players nearby, otherwise turn randomly
|
||||
if lp and self.look_at_players then
|
||||
|
||||
local vec = {
|
||||
x = lp.x - s.x,
|
||||
z = lp.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if lp.x > s.x then yaw = yaw +math.pi end
|
||||
else
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
end
|
||||
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
if self.order == "sit" then
|
||||
self:set_animation( "sit")
|
||||
self:set_velocity(0)
|
||||
else
|
||||
self:set_animation( "stand")
|
||||
self:set_velocity(0)
|
||||
end
|
||||
|
||||
-- npc's ordered to stand stay standing
|
||||
if self.order == "stand" or self.order == "sleep" or self.order == "work" then
|
||||
|
||||
else
|
||||
if player_in_active_range then
|
||||
if self.walk_chance ~= 0
|
||||
and self.facing_fence ~= true
|
||||
and math.random(1, 100) <= self.walk_chance
|
||||
and self:is_at_cliff_or_danger() == false then
|
||||
|
||||
self:set_velocity(self.walk_velocity)
|
||||
self.state = "walk"
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:do_states_runaway()
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
self.runaway_timer = self.runaway_timer + 1
|
||||
|
||||
|
@ -958,401 +977,12 @@ function mob_class:do_states(dtime)
|
|||
self:set_velocity( self.run_velocity)
|
||||
self:set_animation( "run")
|
||||
end
|
||||
|
||||
-- attack routines (explode, dogfight, shoot, dogshoot)
|
||||
elseif self.state == "attack" then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local p = self.attack:get_pos() or s
|
||||
|
||||
-- stop attacking if player invisible or out of range
|
||||
if not self.attack
|
||||
or not self.attack:get_pos()
|
||||
or not self:object_in_range(self.attack)
|
||||
or self.attack:get_hp() <= 0
|
||||
or (self.attack:is_player() and mcl_mobs.invis[ self.attack:get_player_name() ]) then
|
||||
|
||||
self.state = "stand"
|
||||
self:set_velocity( 0)
|
||||
self:set_animation( "stand")
|
||||
self.attack = nil
|
||||
self.v_start = false
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self.path.way = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- calculate distance from mob and enemy
|
||||
local dist = vector.distance(p, s)
|
||||
|
||||
if self.attack_type == "explode" then
|
||||
|
||||
local vec = {
|
||||
x = p.x - s.x,
|
||||
z = p.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
local node_break_radius = self.explosion_radius or 1
|
||||
local entity_damage_radius = self.explosion_damage_radius
|
||||
or (node_break_radius * 2)
|
||||
|
||||
-- start timer when in reach and line of sight
|
||||
if not self.v_start
|
||||
and dist <= self.reach
|
||||
and self:line_of_sight( s, p, 2) then
|
||||
|
||||
self.v_start = true
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self:mob_sound("fuse", nil, false)
|
||||
|
||||
-- stop timer if out of reach or direct line of sight
|
||||
elseif self.allow_fuse_reset
|
||||
and self.v_start
|
||||
and (dist >= self.explosiontimer_reset_radius
|
||||
or not self:line_of_sight( s, p, 2)) then
|
||||
self.v_start = false
|
||||
self.timer = 0
|
||||
self.blinktimer = 0
|
||||
self.blinkstatus = false
|
||||
self:remove_texture_mod("^[brighten")
|
||||
end
|
||||
|
||||
-- walk right up to player unless the timer is active
|
||||
if self.v_start and (self.stop_to_explode or dist < self.reach) then
|
||||
self:set_velocity( 0)
|
||||
else
|
||||
self:set_velocity( self.run_velocity)
|
||||
end
|
||||
|
||||
if self.animation and self.animation.run_start then
|
||||
self:set_animation( "run")
|
||||
else
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
|
||||
if self.v_start then
|
||||
|
||||
self.timer = self.timer + dtime
|
||||
self.blinktimer = (self.blinktimer or 0) + dtime
|
||||
|
||||
if self.blinktimer > 0.2 then
|
||||
|
||||
self.blinktimer = 0
|
||||
|
||||
if self.blinkstatus then
|
||||
self:remove_texture_mod("^[brighten")
|
||||
else
|
||||
self:add_texture_mod("^[brighten")
|
||||
end
|
||||
|
||||
self.blinkstatus = not self.blinkstatus
|
||||
end
|
||||
|
||||
if self.timer > self.explosion_timer then
|
||||
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if mobs_griefing and not minetest.is_protected(pos, "") then
|
||||
mcl_explosions.explode(mcl_util.get_object_center(self.object), self.explosion_strength, { drop_chance = 1.0 }, self.object)
|
||||
else
|
||||
minetest.sound_play(self.sounds.explode, {
|
||||
pos = pos,
|
||||
gain = 1.0,
|
||||
max_hear_distance = self.sounds.distance or 32
|
||||
}, true)
|
||||
self:entity_physics(pos,entity_damage_radius)
|
||||
mcl_mobs.effect(pos, 32, "mcl_particles_smoke.png", nil, nil, node_break_radius, 1, 0)
|
||||
end
|
||||
mcl_burning.extinguish(self.object)
|
||||
self.object:remove()
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.attack_type == "dogfight"
|
||||
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) and (dist >= self.avoid_distance or not self.shooter_avoid_enemy)
|
||||
or (self.attack_type == "dogshoot" and dist <= self.reach and self:dogswitch() == 0) then
|
||||
|
||||
if self.fly
|
||||
and dist > self.reach then
|
||||
|
||||
local p1 = s
|
||||
local me_y = math.floor(p1.y)
|
||||
local p2 = p
|
||||
local p_y = math.floor(p2.y + 1)
|
||||
local v = self.object:get_velocity()
|
||||
|
||||
if self:flight_check( s) then
|
||||
|
||||
if me_y < p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = 1 * self.walk_velocity,
|
||||
z = v.z
|
||||
})
|
||||
|
||||
elseif me_y > p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = -1 * self.walk_velocity,
|
||||
z = v.z
|
||||
})
|
||||
end
|
||||
else
|
||||
if me_y < p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = 0.01,
|
||||
z = v.z
|
||||
})
|
||||
|
||||
elseif me_y > p_y then
|
||||
|
||||
self.object:set_velocity({
|
||||
x = v.x,
|
||||
y = -0.01,
|
||||
z = v.z
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- rnd: new movement direction
|
||||
if self.path.following
|
||||
and self.path.way
|
||||
and self.attack_type ~= "dogshoot" then
|
||||
|
||||
-- no paths longer than 50
|
||||
if #self.path.way > 50
|
||||
or dist < self.reach then
|
||||
self.path.following = false
|
||||
return
|
||||
end
|
||||
|
||||
local p1 = self.path.way[1]
|
||||
|
||||
if not p1 then
|
||||
self.path.following = false
|
||||
return
|
||||
end
|
||||
|
||||
if math.abs(p1.x-s.x) + math.abs(p1.z - s.z) < 0.6 then
|
||||
-- reached waypoint, remove it from queue
|
||||
table.remove(self.path.way, 1)
|
||||
end
|
||||
|
||||
-- set new temporary target
|
||||
p = {x = p1.x, y = p1.y, z = p1.z}
|
||||
end
|
||||
|
||||
local vec = {
|
||||
x = p.x - s.x,
|
||||
z = p.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) + math.pi / 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw + math.pi end
|
||||
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
-- move towards enemy if beyond mob reach
|
||||
if dist > self.reach then
|
||||
|
||||
-- path finding by rnd
|
||||
if self.pathfinding -- only if mob has pathfinding enabled
|
||||
and enable_pathfinding then
|
||||
|
||||
self:smart_mobs(s, p, dist, dtime)
|
||||
end
|
||||
|
||||
if self:is_at_cliff_or_danger() then
|
||||
|
||||
self:set_velocity( 0)
|
||||
self:set_animation( "stand")
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
yaw = self:set_yaw( yaw + 0.78, 8)
|
||||
else
|
||||
|
||||
if self.path.stuck then
|
||||
self:set_velocity( self.walk_velocity)
|
||||
else
|
||||
self:set_velocity( self.run_velocity)
|
||||
end
|
||||
|
||||
if self.animation and self.animation.run_start then
|
||||
self:set_animation( "run")
|
||||
else
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
|
||||
else -- rnd: if inside reach range
|
||||
|
||||
self.path.stuck = false
|
||||
self.path.stuck_timer = 0
|
||||
self.path.following = false -- not stuck anymore
|
||||
|
||||
self:set_velocity( 0)
|
||||
|
||||
if not self.custom_attack then
|
||||
|
||||
if self.timer > 1 then
|
||||
|
||||
self.timer = 0
|
||||
|
||||
if self.double_melee_attack
|
||||
and math.random(1, 2) == 1 then
|
||||
self:set_animation( "punch2")
|
||||
else
|
||||
self:set_animation( "punch")
|
||||
end
|
||||
|
||||
local p2 = p
|
||||
local s2 = s
|
||||
|
||||
p2.y = p2.y + .5
|
||||
s2.y = s2.y + .5
|
||||
|
||||
if self:line_of_sight( p2, s2) == true then
|
||||
|
||||
-- play attack sound
|
||||
self:mob_sound("attack")
|
||||
|
||||
-- punch player (or what player is attached to)
|
||||
local attached = self.attack:get_attach()
|
||||
if attached then
|
||||
self.attack = attached
|
||||
end
|
||||
self.attack:punch(self.object, 1.0, {
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy = self.damage}
|
||||
}, nil)
|
||||
end
|
||||
end
|
||||
else -- call custom attack every second
|
||||
if self.custom_attack
|
||||
and self.timer > 1 then
|
||||
|
||||
self.timer = 0
|
||||
|
||||
self.custom_attack(self, p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.attack_type == "shoot"
|
||||
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
|
||||
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then
|
||||
|
||||
p.y = p.y - .5
|
||||
s.y = s.y + .5
|
||||
|
||||
local dist = vector.distance(p, s)
|
||||
local vec = {
|
||||
x = p.x - s.x,
|
||||
y = p.y - s.y,
|
||||
z = p.z - s.z
|
||||
}
|
||||
|
||||
yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate
|
||||
|
||||
if p.x > s.x then yaw = yaw +math.pi end
|
||||
|
||||
yaw = self:set_yaw( yaw, 0, dtime)
|
||||
|
||||
local stay_away_from_player = vector.new(0,0,0)
|
||||
|
||||
--strafe back and fourth
|
||||
|
||||
--stay away from player so as to shoot them
|
||||
if dist < self.avoid_distance and self.shooter_avoid_enemy then
|
||||
self:set_animation( "shoot")
|
||||
stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33)
|
||||
end
|
||||
|
||||
if self.strafes then
|
||||
if not self.strafe_direction then
|
||||
self.strafe_direction = 1.57
|
||||
end
|
||||
if math.random(40) == 1 then
|
||||
self.strafe_direction = self.strafe_direction*-1
|
||||
end
|
||||
self.acc = vector.add(vector.multiply(vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction), 0.3*self.walk_velocity), stay_away_from_player)
|
||||
else
|
||||
self:set_velocity( 0)
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
|
||||
|
||||
if self.shoot_interval
|
||||
and self.timer > self.shoot_interval
|
||||
and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
|
||||
and math.random(1, 100) <= 60 then
|
||||
|
||||
self.timer = 0
|
||||
self:set_animation( "shoot")
|
||||
|
||||
-- play shoot attack sound
|
||||
self:mob_sound("shoot_attack")
|
||||
|
||||
-- Shoot arrow
|
||||
if minetest.registered_entities[self.arrow] then
|
||||
|
||||
local arrow, ent
|
||||
local v = 1
|
||||
if not self.shoot_arrow then
|
||||
self.firing = true
|
||||
minetest.after(1, function()
|
||||
self.firing = false
|
||||
end)
|
||||
arrow = minetest.add_entity(p, self.arrow)
|
||||
ent = arrow:get_luaentity()
|
||||
if ent.velocity then
|
||||
v = ent.velocity
|
||||
end
|
||||
ent.switch = 1
|
||||
ent.owner_id = tostring(self.object) -- add unique owner id to arrow
|
||||
|
||||
-- important for mcl_shields
|
||||
ent._shooter = self.object
|
||||
ent._saved_shooter_pos = self.object:get_pos()
|
||||
end
|
||||
|
||||
local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
|
||||
-- offset makes shoot aim accurate
|
||||
vec.y = vec.y + self.shoot_offset
|
||||
vec.x = vec.x * (v / amount)
|
||||
vec.y = vec.y * (v / amount)
|
||||
vec.z = vec.z * (v / amount)
|
||||
if self.shoot_arrow then
|
||||
vec = vector.normalize(vec)
|
||||
self:shoot_arrow(p, vec)
|
||||
else
|
||||
arrow:set_velocity(vec)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_smooth_rotation(dtime)
|
||||
-- smooth rotation by ThomasMonroe314
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
|
||||
local mob_class = mcl_mobs.mob_class
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_villager",false)
|
||||
local PATHFINDING = "gowp"
|
||||
local enable_pathfinding = true
|
||||
local PATHFINDING_FAIL_THRESHOLD = 100 -- no. of ticks to fail before giving up. 20p/s. 5s helps them get through door
|
||||
local PATHFINDING_FAIL_WAIT = 30 -- how long to wait before trying to path again
|
||||
local PATHING_START_DELAY = 4 -- When doing non-prioritised pathing, how long to wait until last mob pathed
|
||||
|
||||
local LOG_MODULE = "[Mobs]"
|
||||
local PATHFINDING_SEARCH_DISTANCE = 50 -- How big the square is that pathfinding will look
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
|
||||
local one_down = vector.new(0,-1,0)
|
||||
local one_up = vector.new(0,1,0)
|
||||
|
||||
local plane_adjacents = {
|
||||
vector.new(1,0,0),
|
||||
vector.new(-1,0,0),
|
||||
vector.new(0,0,1),
|
||||
vector.new(0,0,-1),
|
||||
}
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_pathfinding",false)
|
||||
|
||||
local LOG_MODULE = "[Mobs Pathfinding]"
|
||||
local function mcl_log (message)
|
||||
if LOGGING_ON and message then
|
||||
minetest.log(LOG_MODULE .. " " .. message)
|
||||
|
@ -21,7 +37,7 @@ function output_table (wp)
|
|||
end
|
||||
|
||||
function append_paths (wp1, wp2)
|
||||
mcl_log("Start append")
|
||||
--mcl_log("Start append")
|
||||
if not wp1 or not wp2 then
|
||||
mcl_log("Cannot append wp's")
|
||||
return
|
||||
|
@ -31,7 +47,7 @@ function append_paths (wp1, wp2)
|
|||
for _,a in pairs (wp2) do
|
||||
table.insert(wp1, a)
|
||||
end
|
||||
mcl_log("End append")
|
||||
--mcl_log("End append")
|
||||
end
|
||||
|
||||
local function output_enriched (wp_out)
|
||||
|
@ -43,11 +59,12 @@ local function output_enriched (wp_out)
|
|||
|
||||
local action = outy["action"]
|
||||
if action then
|
||||
--mcl_log("Pos ".. i ..":" .. minetest.pos_to_string(outy["pos"]))
|
||||
mcl_log("type: " .. action["type"])
|
||||
mcl_log("action: " .. action["action"])
|
||||
mcl_log("target: " .. minetest.pos_to_string(action["target"]))
|
||||
end
|
||||
mcl_log("failed attempts: " .. outy["failed_attempts"])
|
||||
--mcl_log("failed attempts: " .. outy["failed_attempts"])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -56,34 +73,26 @@ end
|
|||
-- an action, such as to open or close a door where we know that pos requires that action
|
||||
local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_door_pos)
|
||||
local wp_out = {}
|
||||
|
||||
-- TODO Just pass in door position and the index before is open, the index after is close
|
||||
local current_door_index = -1
|
||||
|
||||
for i, cur_pos in pairs(wp_in) do
|
||||
local action = nil
|
||||
|
||||
local one_down = vector.new(0,-1,0)
|
||||
local cur_pos_to_add = vector.add(cur_pos, one_down)
|
||||
if door_open_pos and vector.equals (cur_pos, door_open_pos) then
|
||||
mcl_log ("Door open match")
|
||||
--action = {type = "door", action = "open"}
|
||||
action = {}
|
||||
action["type"] = "door"
|
||||
action["action"] = "open"
|
||||
action["target"] = cur_door_pos
|
||||
action = {type = "door", action = "open", target = cur_door_pos}
|
||||
cur_pos_to_add = vector.add(cur_pos, one_down)
|
||||
elseif door_close_pos and vector.equals(cur_pos, door_close_pos) then
|
||||
mcl_log ("Door close match")
|
||||
--action = {type = "door", action = "closed"}
|
||||
action = {}
|
||||
action["type"] = "door"
|
||||
action["action"] = "close"
|
||||
action["target"] = cur_door_pos
|
||||
action = {type = "door", action = "close", target = cur_door_pos}
|
||||
cur_pos_to_add = vector.add(cur_pos, one_down)
|
||||
elseif cur_door_pos and vector.equals(cur_pos, cur_door_pos) then
|
||||
mcl_log("Current door pos")
|
||||
action = {type = "door", action = "open", target = cur_door_pos}
|
||||
cur_pos_to_add = vector.add(cur_pos, one_down)
|
||||
action = {}
|
||||
action["type"] = "door"
|
||||
action["action"] = "open"
|
||||
action["target"] = cur_door_pos
|
||||
else
|
||||
cur_pos_to_add = cur_pos
|
||||
--mcl_log ("Pos doesn't match")
|
||||
|
@ -101,106 +110,166 @@ local function generate_enriched_path(wp_in, door_open_pos, door_close_pos, cur_
|
|||
return wp_out
|
||||
end
|
||||
|
||||
local plane_adjacents = {
|
||||
vector.new(1,0,0),
|
||||
vector.new(-1,0,0),
|
||||
vector.new(0,0,1),
|
||||
vector.new(0,0,-1),
|
||||
}
|
||||
local last_pathing_time = os.time()
|
||||
|
||||
function mob_class:ready_to_path(prioritised)
|
||||
mcl_log("Check ready to path")
|
||||
if self._pf_last_failed and (os.time() - self._pf_last_failed) < PATHFINDING_FAIL_WAIT then
|
||||
mcl_log("Not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
|
||||
return false
|
||||
else
|
||||
local time_since_path_start = os.time() - last_pathing_time
|
||||
mcl_log("time_since_path_start: " .. tostring(time_since_path_start))
|
||||
if prioritised or (time_since_path_start) > PATHING_START_DELAY then
|
||||
mcl_log("We are ready to pathfind, no previous fail or we are past threshold")
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- This function is used to see if we can path. We could use to check a route, rather than making people move.
|
||||
local function calculate_path_through_door (p, t, target)
|
||||
-- target is the same as t, just 1 square difference. Maybe we don't need target
|
||||
mcl_log("Plot route from mob: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t))
|
||||
local function calculate_path_through_door (p, cur_door_pos, t)
|
||||
if t then
|
||||
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p) .. ", to target: " .. minetest.pos_to_string(t))
|
||||
else
|
||||
mcl_log("Plot route through door from pos: " .. minetest.pos_to_string(p))
|
||||
end
|
||||
|
||||
local enriched_path = nil
|
||||
local wp, prospective_wp
|
||||
|
||||
local cur_door_pos = nil
|
||||
local pos_closest_to_door = nil
|
||||
local other_side_of_door = nil
|
||||
|
||||
--Path to door first
|
||||
local wp = minetest.find_path(p,t,150,1,4)
|
||||
if not wp then
|
||||
mcl_log("No direct path. Path through door")
|
||||
|
||||
-- This could improve. There could be multiple doors. Check you can path from door to target first.
|
||||
local cur_door_pos = minetest.find_node_near(target,16,{"group:door"})
|
||||
if cur_door_pos then
|
||||
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
|
||||
|
||||
for _,v in pairs(plane_adjacents) do
|
||||
pos_closest_to_door = vector.add(cur_door_pos,v)
|
||||
other_side_of_door = vector.add(cur_door_pos,-v)
|
||||
|
||||
local n = minetest.get_node(pos_closest_to_door)
|
||||
|
||||
if n.name == "air" then
|
||||
wp = minetest.find_path(p,pos_closest_to_door,150,1,4)
|
||||
if wp then
|
||||
mcl_log("We have air space next to door at: " .. minetest.pos_to_string(pos_closest_to_door))
|
||||
|
||||
prospective_wp = minetest.find_path(p, pos_closest_to_door, PATHFINDING_SEARCH_DISTANCE, 1, 4)
|
||||
|
||||
if prospective_wp then
|
||||
mcl_log("Found a path to next to door".. minetest.pos_to_string(pos_closest_to_door))
|
||||
other_side_of_door = vector.add(cur_door_pos,-v)
|
||||
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
|
||||
|
||||
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4)
|
||||
table.insert(prospective_wp, cur_door_pos)
|
||||
|
||||
if t then
|
||||
mcl_log("We have t, lets go from door to target")
|
||||
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
|
||||
|
||||
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
|
||||
table.insert(wp, cur_door_pos)
|
||||
append_paths (wp, wp_otherside_door_to_target)
|
||||
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
|
||||
append_paths (prospective_wp, wp_otherside_door_to_target)
|
||||
|
||||
wp = prospective_wp
|
||||
mcl_log("We have a path from outside door to target")
|
||||
else
|
||||
mcl_log("We cannot path from outside door to target")
|
||||
end
|
||||
break
|
||||
else
|
||||
mcl_log("This block next to door doesn't work.")
|
||||
end
|
||||
else
|
||||
mcl_log("Block is not air, it is: ".. n.name)
|
||||
mcl_log("No t, just add other side of door")
|
||||
table.insert(prospective_wp, other_side_of_door)
|
||||
wp = prospective_wp
|
||||
end
|
||||
|
||||
if wp then
|
||||
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
|
||||
break
|
||||
end
|
||||
else
|
||||
mcl_log("Cannot path to this air block next to door.")
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
mcl_log("No door found")
|
||||
end
|
||||
else
|
||||
mcl_log("We have a direct route")
|
||||
end
|
||||
|
||||
if wp and not enriched_path then
|
||||
mcl_log("Wp but not enriched")
|
||||
enriched_path = generate_enriched_path(wp)
|
||||
end
|
||||
return enriched_path
|
||||
end
|
||||
|
||||
local gopath_last = os.time()
|
||||
function mob_class:gopath(target,callback_arrived)
|
||||
|
||||
|
||||
function mob_class:gopath(target, callback_arrived, prioritised)
|
||||
if self.state == PATHFINDING then mcl_log("Already pathfinding, don't set another until done.") return end
|
||||
if not self:ready_to_path(prioritised) then return end
|
||||
|
||||
if self._pf_last_failed and (os.time() - self._pf_last_failed) < 30 then
|
||||
mcl_log("We are not ready to path as last fail is less than threshold: " .. (os.time() - self._pf_last_failed))
|
||||
return
|
||||
else
|
||||
mcl_log("We are ready to pathfind, no previous fail or we are past threshold")
|
||||
end
|
||||
|
||||
--if os.time() - gopath_last < 5 then
|
||||
-- mcl_log("Not ready to path yet")
|
||||
-- return
|
||||
--end
|
||||
--gopath_last = os.time()
|
||||
last_pathing_time = os.time()
|
||||
|
||||
self.order = nil
|
||||
|
||||
local p = self.object:get_pos()
|
||||
local t = vector.offset(target,0,1,0)
|
||||
|
||||
local wp = calculate_path_through_door(p, t, target)
|
||||
--Check direct route
|
||||
local wp = minetest.find_path(p, t, PATHFINDING_SEARCH_DISTANCE, 1, 4)
|
||||
|
||||
if not wp then
|
||||
mcl_log("### No direct path. Path through door closest to target.")
|
||||
local door_near_target = minetest.find_node_near(target, 16, {"group:door"})
|
||||
wp = calculate_path_through_door(p, door_near_target, t)
|
||||
|
||||
if not wp then
|
||||
mcl_log("### No path though door closest to target. Try door closest to origin.")
|
||||
local door_closest = minetest.find_node_near(p, 16, {"group:door"})
|
||||
wp = calculate_path_through_door(p, door_closest, t)
|
||||
|
||||
-- Path through 2 doors
|
||||
if not wp then
|
||||
mcl_log("### Still not wp. Need to path through 2 doors.")
|
||||
local path_through_closest_door = calculate_path_through_door(p, door_closest)
|
||||
|
||||
if path_through_closest_door and #path_through_closest_door > 0 then
|
||||
mcl_log("We have path through first door")
|
||||
mcl_log("Number of pos in path through door: " .. tostring(#path_through_closest_door))
|
||||
|
||||
local pos_after_door_entry = path_through_closest_door[#path_through_closest_door]
|
||||
if pos_after_door_entry then
|
||||
local pos_after_door = vector.add(pos_after_door_entry["pos"], one_up)
|
||||
mcl_log("pos_after_door: " .. minetest.pos_to_string(pos_after_door))
|
||||
local path_after_door = calculate_path_through_door(pos_after_door, door_near_target, t)
|
||||
if path_after_door and #path_after_door > 1 then
|
||||
mcl_log("We have path after first door")
|
||||
table.remove(path_after_door, 1) -- Remove duplicate
|
||||
wp = path_through_closest_door
|
||||
append_paths (wp, path_after_door)
|
||||
else
|
||||
mcl_log("Path after door is not good")
|
||||
end
|
||||
else
|
||||
mcl_log("No pos after door")
|
||||
end
|
||||
else
|
||||
mcl_log("Path through closest door empty or null")
|
||||
end
|
||||
else
|
||||
mcl_log("ok, we have a path through 1 door")
|
||||
end
|
||||
end
|
||||
else
|
||||
wp = generate_enriched_path(wp)
|
||||
mcl_log("We have a direct route")
|
||||
end
|
||||
|
||||
if not wp then
|
||||
mcl_log("Could not calculate path")
|
||||
self._pf_last_failed = os.time()
|
||||
-- Cover for a flaw in pathfind where it chooses the wrong door and gets stuck. Take a break, allow others.
|
||||
-- If cannot path, don't immediately try again
|
||||
end
|
||||
--output_table(wp)
|
||||
|
||||
if wp and #wp > 0 then
|
||||
--output_table(wp)
|
||||
self._target = t
|
||||
self.callback_arrived = callback_arrived
|
||||
local current_location = table.remove(wp,1)
|
||||
|
@ -269,17 +338,7 @@ function mob_class:do_pathfind_action(action)
|
|||
end
|
||||
end
|
||||
|
||||
local gowp_etime = 0
|
||||
|
||||
function mob_class:check_gowp(dtime)
|
||||
gowp_etime = gowp_etime + dtime
|
||||
|
||||
-- 0.1 is optimal.
|
||||
--less frequently = villager will get sent back after passing a point.
|
||||
--more frequently = villager will fail points they shouldn't they just didn't get there yet
|
||||
|
||||
--if gowp_etime < 0.05 then return end
|
||||
--gowp_etime = 0
|
||||
local p = self.object:get_pos()
|
||||
|
||||
-- no destination
|
||||
|
@ -292,7 +351,7 @@ function mob_class:check_gowp(dtime)
|
|||
-- arrived at location, finish gowp
|
||||
local distance_to_targ = vector.distance(p,self._target)
|
||||
--mcl_log("Distance to targ: ".. tostring(distance_to_targ))
|
||||
if distance_to_targ < 2 then
|
||||
if distance_to_targ < 1.8 then
|
||||
mcl_log("Arrived at _target")
|
||||
self.waypoints = nil
|
||||
self._target = nil
|
||||
|
@ -303,6 +362,8 @@ function mob_class:check_gowp(dtime)
|
|||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
if self.callback_arrived then return self.callback_arrived(self) end
|
||||
return true
|
||||
elseif not self.current_target then
|
||||
mcl_log("Not close enough to targ: ".. tostring(distance_to_targ))
|
||||
end
|
||||
|
||||
-- More pathing to be done
|
||||
|
@ -315,7 +376,7 @@ function mob_class:check_gowp(dtime)
|
|||
-- 0.8 is optimal for 0.025 frequency checks and also 1... Actually. 0.8 is winning
|
||||
-- 0.9 and 1.0 is also good. Stick with unless door open or closing issues
|
||||
if self.waypoints and #self.waypoints > 0 and ( not self.current_target or not self.current_target["pos"] or distance_to_current_target < 0.9 ) then
|
||||
-- We have waypoints, and no current target, or we're at it. We need a new current_target.
|
||||
-- We have waypoints, and are at current_target or have no current target. We need a new current_target.
|
||||
self:do_pathfind_action (self.current_target["action"])
|
||||
|
||||
local failed_attempts = self.current_target["failed_attempts"]
|
||||
|
@ -325,10 +386,11 @@ function mob_class:check_gowp(dtime)
|
|||
self:go_to_pos(self.current_target["pos"])
|
||||
return
|
||||
elseif self.current_target and self.current_target["pos"] then
|
||||
-- No waypoints left, but have current target. Potentially last waypoint to go to.
|
||||
-- No waypoints left, but have current target and not close enough. Potentially last waypoint to go to.
|
||||
|
||||
self.current_target["failed_attempts"] = self.current_target["failed_attempts"] + 1
|
||||
local failed_attempts = self.current_target["failed_attempts"]
|
||||
if failed_attempts >= 50 then
|
||||
if failed_attempts >= PATHFINDING_FAIL_THRESHOLD then
|
||||
mcl_log("Failed to reach position (" .. minetest.pos_to_string(self.current_target["pos"]) .. ") too many times. Abandon route. Times tried: " .. failed_attempts)
|
||||
self.state = "stand"
|
||||
self.current_target = nil
|
||||
|
@ -348,9 +410,22 @@ function mob_class:check_gowp(dtime)
|
|||
-- Is a little sensitive and could take 1 - 7 times. A 10 fail count might be a good exit condition.
|
||||
|
||||
mcl_log("We don't have waypoints or a current target. Let's try to path to target")
|
||||
local final_wp = minetest.find_path(p,self._target,150,1,4)
|
||||
|
||||
if self.waypoints then
|
||||
mcl_log("WP: " .. tostring(self.waypoints))
|
||||
mcl_log("WP num: " .. tostring(#self.waypoints))
|
||||
else
|
||||
mcl_log("No wp set")
|
||||
end
|
||||
if self.current_target then
|
||||
mcl_log("Current target: " .. tostring(self.current_target))
|
||||
else
|
||||
mcl_log("No current target")
|
||||
end
|
||||
|
||||
local final_wp = minetest.find_path(p, self._target, PATHFINDING_SEARCH_DISTANCE, 1, 4)
|
||||
if final_wp then
|
||||
mcl_log("We might be able to get to target here.")
|
||||
mcl_log("We can get to target here.")
|
||||
-- self.waypoints = final_wp
|
||||
self:go_to_pos(self._target)
|
||||
else
|
||||
|
@ -374,9 +449,9 @@ function mob_class:check_gowp(dtime)
|
|||
self:go_to_pos(self._current_target)
|
||||
else
|
||||
mcl_log("close to current target: ".. minetest.pos_to_string(self.current_target["pos"]))
|
||||
mcl_log("target is: ".. minetest.pos_to_string(self._target))
|
||||
self.current_target = nil
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,8 +5,7 @@ local ENTITY_CRAMMING_MAX = 24
|
|||
local CRAMMING_DAMAGE = 3
|
||||
local DEATH_DELAY = 0.5
|
||||
local DEFAULT_FALL_SPEED = -9.81*1.5
|
||||
local FLOP_HEIGHT = 6
|
||||
local FLOP_HOR_SPEED = 1.5
|
||||
|
||||
local PATHFINDING = "gowp"
|
||||
local mobs_debug = minetest.settings:get_bool("mobs_debug", false)
|
||||
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
|
||||
|
@ -48,7 +47,8 @@ end
|
|||
|
||||
function mob_class:player_in_active_range()
|
||||
for _,p in pairs(minetest.get_connected_players()) do
|
||||
if vector.distance(self.object:get_pos(),p:get_pos()) <= mob_active_range then return true end
|
||||
local pos = self.object:get_pos()
|
||||
if pos and vector.distance(pos, p:get_pos()) <= mob_active_range then return true end
|
||||
-- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning.
|
||||
end
|
||||
end
|
||||
|
@ -183,8 +183,23 @@ function mob_class:collision()
|
|||
return({x,z})
|
||||
end
|
||||
|
||||
function mob_class:check_death_and_slow_mob()
|
||||
local d = 0.7
|
||||
local dying = self:check_dying()
|
||||
if dying then d = 0.92 end
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
--diffuse object velocity
|
||||
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d})
|
||||
end
|
||||
return dying
|
||||
end
|
||||
|
||||
-- move mob in facing direction
|
||||
function mob_class:set_velocity(v)
|
||||
if not v then return end
|
||||
|
||||
local c_x, c_y = 0, 0
|
||||
|
||||
-- can mob be pushed, if so calculate direction
|
||||
|
@ -194,18 +209,15 @@ function mob_class:set_velocity(v)
|
|||
|
||||
-- halt mob if it has been ordered to stay
|
||||
if self.order == "stand" or self.order == "sit" then
|
||||
self.acc=vector.new(0,0,0)
|
||||
self.acc = vector.zero()
|
||||
return
|
||||
end
|
||||
|
||||
local yaw = (self.object:get_yaw() or 0) + self.rotate
|
||||
local vv = self.object:get_velocity()
|
||||
if vv then
|
||||
self.acc={
|
||||
x = ((math.sin(yaw) * -v) + c_x)*.27,
|
||||
y = 0,
|
||||
z = ((math.cos(yaw) * v) + c_y)*.27,
|
||||
}
|
||||
|
||||
if vv and yaw then
|
||||
self.acc = vector.new(((math.sin(yaw) * -v) + c_x) * .4, 0, ((math.cos(yaw) * v) + c_y) * .4)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -298,6 +310,8 @@ end
|
|||
function mob_class:set_yaw(yaw, delay, dtime)
|
||||
if self.noyaw then return end
|
||||
|
||||
if not self.object:get_yaw() or not self.object:get_pos() then return end
|
||||
|
||||
if self.state ~= PATHFINDING then
|
||||
self._turn_to = yaw
|
||||
end
|
||||
|
@ -313,9 +327,9 @@ function mob_class:set_yaw(yaw, delay, dtime)
|
|||
end
|
||||
|
||||
if math.deg(yaw) > 360 then
|
||||
yaw=yaw%360
|
||||
yaw=math.rad(math.deg(yaw)%360)
|
||||
elseif math.deg(yaw) < 0 then
|
||||
yaw=((360*5)-yaw)%360
|
||||
yaw=math.rad(((360*5)-math.deg(yaw))%360)
|
||||
end
|
||||
|
||||
--calculate the shortest way to turn to find our target
|
||||
|
@ -339,7 +353,7 @@ function mob_class:set_yaw(yaw, delay, dtime)
|
|||
ddtime = dtime
|
||||
end
|
||||
|
||||
if math.abs(target_shortest_path_nums) > 5 then
|
||||
if math.abs(target_shortest_path_nums) > 10 then
|
||||
self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime)))
|
||||
if self.acc then
|
||||
self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime))
|
||||
|
@ -449,6 +463,14 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
self:mob_sound("death")
|
||||
|
||||
local function death_handle(self)
|
||||
if cmi_cause and cmi_cause["type"] then
|
||||
--minetest.log("cmi_cause: " .. tostring(cmi_cause["type"]))
|
||||
end
|
||||
--minetest.log("cause: " .. tostring(cause))
|
||||
|
||||
-- TODO other env damage shouldn't drop xp
|
||||
-- "rain", "water", "drowning", "suffocation"
|
||||
|
||||
-- dropped cooked item if mob died in fire or lava
|
||||
if cause == "lava" or cause == "fire" then
|
||||
self:item_drop(true, 0)
|
||||
|
@ -465,11 +487,23 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
self:item_drop(cooked, looting)
|
||||
|
||||
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
|
||||
mcl_experience.throw_xp(self.object:get_pos(), math.random(self.xp_min, self.xp_max))
|
||||
local pos = self.object:get_pos()
|
||||
local xp_amount = math.random(self.xp_min, self.xp_max)
|
||||
|
||||
if not mcl_sculk.handle_death(pos, xp_amount) then
|
||||
--minetest.log("Xp not thrown")
|
||||
if minetest.is_creative_enabled("") ~= true then
|
||||
mcl_experience.throw_xp(pos, xp_amount)
|
||||
end
|
||||
else
|
||||
--minetest.log("xp thrown")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-- execute custom death function
|
||||
if self.on_die then
|
||||
|
||||
|
@ -487,6 +521,12 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
end
|
||||
end
|
||||
|
||||
if self.jockey or self.riden_by_jock then
|
||||
self.riden_by_jock = nil
|
||||
self.jockey = nil
|
||||
end
|
||||
|
||||
|
||||
local collisionbox
|
||||
if self.collisionbox then
|
||||
collisionbox = table.copy(self.collisionbox)
|
||||
|
@ -509,17 +549,16 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
|
||||
self:set_velocity(0)
|
||||
local acc = self.object:get_acceleration()
|
||||
if acc then
|
||||
acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0
|
||||
self.object:set_acceleration(acc)
|
||||
end
|
||||
|
||||
local length
|
||||
-- default death function and die animation (if defined)
|
||||
if self.instant_death then
|
||||
length = 0
|
||||
elseif self.animation
|
||||
and self.animation.die_start
|
||||
and self.animation.die_end then
|
||||
|
||||
elseif self.animation and self.animation.die_start and self.animation.die_end then
|
||||
local frames = self.animation.die_end - self.animation.die_start
|
||||
local speed = self.animation.die_speed or 15
|
||||
length = math.max(frames / speed, 0) + DEATH_DELAY
|
||||
|
@ -535,7 +574,6 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
if not self.object:get_luaentity() then
|
||||
return
|
||||
end
|
||||
|
||||
death_handle(self)
|
||||
local dpos = self.object:get_pos()
|
||||
local cbox = self.collisionbox
|
||||
|
@ -544,6 +582,7 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
self.object:remove()
|
||||
mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death)
|
||||
end
|
||||
|
||||
if length <= 0 then
|
||||
kill(self)
|
||||
else
|
||||
|
@ -594,9 +633,17 @@ function mob_class:do_env_damage()
|
|||
return true
|
||||
end
|
||||
|
||||
local sunlight = minetest.get_natural_light(pos, self.time_of_day)
|
||||
local node = minetest.get_node(pos)
|
||||
if node then
|
||||
if node.name ~= "ignore" then
|
||||
-- put below code in this block if we can prove that unloaded maps are causing crash.
|
||||
-- it should warn then error
|
||||
else
|
||||
--minetest.log("warning", "Pos is ignored: " .. dump(pos))
|
||||
end
|
||||
|
||||
local sunlight = mcl_util.get_natural_light(pos, self.time_of_day)
|
||||
|
||||
-- bright light harms mob
|
||||
if self.light_damage ~= 0 and (sunlight or 0) > 12 then
|
||||
if self:deal_light_damage(pos, self.light_damage) then
|
||||
return true
|
||||
|
@ -614,6 +661,8 @@ function mob_class:do_env_damage()
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local y_level = self.collisionbox[2]
|
||||
|
||||
if self.child then
|
||||
|
@ -632,11 +681,11 @@ function mob_class:do_env_damage()
|
|||
end
|
||||
|
||||
local nodef = minetest.registered_nodes[self.standing_in]
|
||||
local nodef2 = minetest.registered_nodes[self.standing_on]
|
||||
|
||||
-- rain
|
||||
if self.rain_damage > 0 then
|
||||
if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
|
||||
|
||||
self.health = self.health - self.rain_damage
|
||||
|
||||
if self:check_for_death("rain", {type = "environment",
|
||||
|
@ -649,13 +698,9 @@ function mob_class:do_env_damage()
|
|||
pos.y = pos.y + 1 -- for particle effect position
|
||||
|
||||
-- water damage
|
||||
if self.water_damage > 0
|
||||
and nodef.groups.water then
|
||||
|
||||
if self.water_damage > 0 and nodef.groups.water then
|
||||
if self.water_damage ~= 0 then
|
||||
|
||||
self.health = self.health - self.water_damage
|
||||
|
||||
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil)
|
||||
|
||||
if self:check_for_death("water", {type = "environment",
|
||||
|
@ -663,15 +708,10 @@ function mob_class:do_env_damage()
|
|||
return true
|
||||
end
|
||||
end
|
||||
|
||||
elseif self.lava_damage > 0 and (nodef.groups.lava) then
|
||||
-- lava damage
|
||||
elseif self.lava_damage > 0
|
||||
and (nodef.groups.lava) then
|
||||
|
||||
if self.lava_damage ~= 0 then
|
||||
|
||||
self.health = self.health - self.lava_damage
|
||||
|
||||
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
|
||||
mcl_burning.set_on_fire(self.object, 10)
|
||||
|
||||
|
@ -680,15 +720,20 @@ function mob_class:do_env_damage()
|
|||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- fire damage
|
||||
elseif self.fire_damage > 0
|
||||
and (nodef.groups.fire) then
|
||||
|
||||
elseif self.fire_damage > 0 and (nodef2.groups.fire) then
|
||||
-- magma damage
|
||||
if self.fire_damage ~= 0 then
|
||||
|
||||
self.health = self.health - self.fire_damage
|
||||
|
||||
if self:check_for_death("fire", {type = "environment",
|
||||
pos = pos, node = self.standing_in}) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
elseif self.fire_damage > 0 and (nodef.groups.fire) then
|
||||
-- fire damage
|
||||
if self.fire_damage ~= 0 then
|
||||
self.health = self.health - self.fire_damage
|
||||
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
|
||||
mcl_burning.set_on_fire(self.object, 5)
|
||||
|
||||
|
@ -697,12 +742,9 @@ function mob_class:do_env_damage()
|
|||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- damage_per_second node check
|
||||
elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then
|
||||
|
||||
-- damage_per_second node check
|
||||
self.health = self.health - nodef.damage_per_second
|
||||
|
||||
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
|
||||
|
||||
if self:check_for_death("dps", {type = "environment",
|
||||
|
@ -714,6 +756,7 @@ function mob_class:do_env_damage()
|
|||
-- Drowning damage
|
||||
if self.breath_max ~= -1 then
|
||||
local drowning = false
|
||||
|
||||
if self.breathes_in_water then
|
||||
if minetest.get_item_group(self.standing_in, "water") == 0 then
|
||||
drowning = true
|
||||
|
@ -721,10 +764,9 @@ function mob_class:do_env_damage()
|
|||
elseif nodef.drowning > 0 then
|
||||
drowning = true
|
||||
end
|
||||
|
||||
if drowning then
|
||||
|
||||
self.breath = math.max(0, self.breath - 1)
|
||||
|
||||
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
|
||||
if self.breath <= 0 then
|
||||
local dmg
|
||||
|
@ -775,7 +817,34 @@ function mob_class:do_env_damage()
|
|||
self.suffocation_timer = 0
|
||||
end
|
||||
|
||||
return self:check_for_death("", {type = "unknown"})
|
||||
return self:check_for_death("unknown", {type = "unknown"})
|
||||
end
|
||||
|
||||
function mob_class:step_damage (dtime, pos)
|
||||
if not self.fire_resistant then
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
if not self.object:get_pos() then return true end -- mcl_burning.tick may remove object immediately
|
||||
|
||||
if self:check_for_death("fire", {type = "fire"}) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- environmental damage timer (every 1 second)
|
||||
self.env_damage_timer = self.env_damage_timer + dtime
|
||||
|
||||
if self.env_damage_timer > 1 then
|
||||
self.env_damage_timer = 0
|
||||
|
||||
self:check_entity_cramming()
|
||||
|
||||
-- check for environmental damage (water, fire, lava etc.)
|
||||
if self:do_env_damage() then
|
||||
return true
|
||||
end
|
||||
|
||||
self:replace_node(pos) -- (sheep eats grass etc.)
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:damage_mob(reason,damage)
|
||||
|
@ -841,57 +910,41 @@ function mob_class:falling(pos)
|
|||
|
||||
-- floating in water (or falling)
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
local new_acceleration
|
||||
|
||||
if v.y > 0 then
|
||||
|
||||
-- apply gravity when moving up
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = DEFAULT_FALL_SPEED,
|
||||
z = 0
|
||||
})
|
||||
|
||||
new_acceleration = vector.new(0, DEFAULT_FALL_SPEED, 0)
|
||||
elseif v.y <= 0 and v.y > self.fall_speed then
|
||||
|
||||
-- fall downwards at set speed
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = self.fall_speed,
|
||||
z = 0
|
||||
})
|
||||
new_acceleration = vector.new(0, self.fall_speed, 0)
|
||||
else
|
||||
-- stop accelerating once max fall speed hit
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
new_acceleration =vector.zero()
|
||||
end
|
||||
|
||||
if minetest.registered_nodes[node_ok(pos).name].groups.lava then
|
||||
self.object:set_acceleration(new_acceleration)
|
||||
end
|
||||
|
||||
if self.floats_on_lava == 1 then
|
||||
local acc = self.object:get_acceleration()
|
||||
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
|
||||
z = 0
|
||||
})
|
||||
local registered_node = minetest.registered_nodes[node_ok(pos).name]
|
||||
|
||||
if registered_node.groups.lava then
|
||||
if acc and self.floats_on_lava == 1 then
|
||||
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0))
|
||||
end
|
||||
end
|
||||
|
||||
-- in water then float up
|
||||
if minetest.registered_nodes[node_ok(pos).name].groups.water then
|
||||
|
||||
if self.floats == 1 then
|
||||
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
|
||||
z = 0
|
||||
})
|
||||
if registered_node.groups.water then
|
||||
if acc and self.floats == 1 then
|
||||
self.object:set_acceleration(vector.new(0, -self.fall_speed / (math.max(1, v.y) ^ 2), 0))
|
||||
end
|
||||
else
|
||||
|
||||
-- fall damage onto solid ground
|
||||
if self.fall_damage == 1
|
||||
and self.object:get_velocity().y == 0 then
|
||||
if self.fall_damage == 1 and self.object:get_velocity().y == 0 then
|
||||
local n = node_ok(vector.offset(pos,0,-1,0)).name
|
||||
local d = (self.old_y or 0) - self.object:get_pos().y
|
||||
|
||||
|
@ -952,24 +1005,28 @@ end
|
|||
function mob_class:check_dying()
|
||||
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
|
||||
local rot = self.object:get_rotation()
|
||||
if rot then
|
||||
rot.z = ((math.pi/2-rot.z)*.2)+rot.z
|
||||
self.object:set_rotation(rot)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:check_suspend()
|
||||
if not self:player_in_active_range() then
|
||||
function mob_class:check_suspend(player_in_active_range)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if pos and not player_in_active_range then
|
||||
local node_under = node_ok(vector.offset(pos,0,-1,0)).name
|
||||
local acc = self.object:get_acceleration()
|
||||
|
||||
self:set_animation( "stand", true)
|
||||
|
||||
local acc = self.object:get_acceleration()
|
||||
if acc then
|
||||
if acc.y > 0 or node_under ~= "air" then
|
||||
self.object:set_acceleration(vector.new(0,0,0))
|
||||
self.object:set_velocity(vector.new(0,0,0))
|
||||
self.object:set_acceleration(vector.zero())
|
||||
self.object:set_velocity(vector.zero())
|
||||
end
|
||||
if acc.y == 0 and node_under == "air" then
|
||||
self:falling(pos)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
|
|
@ -24,25 +24,67 @@ local vector_floor = vector.floor
|
|||
|
||||
local table_copy = table.copy
|
||||
local table_remove = table.remove
|
||||
|
||||
local pairs = pairs
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_mobs_spawning", false)
|
||||
local function mcl_log (message, property)
|
||||
if LOGGING_ON then
|
||||
if property then
|
||||
message = message .. ": " .. dump(property)
|
||||
end
|
||||
mcl_util.mcl_log (message, "[Mobs spawn]", true)
|
||||
end
|
||||
end
|
||||
|
||||
local dbg_spawn_attempts = 0
|
||||
local dbg_spawn_succ = 0
|
||||
local dbg_spawn_counts = {}
|
||||
-- range for mob count
|
||||
local aoc_range = 136
|
||||
|
||||
local remove_far = true
|
||||
|
||||
local WAIT_FOR_SPAWN_ATTEMPT = 10
|
||||
local FIND_SPAWN_POS_RETRIES = 16
|
||||
local FIND_SPAWN_POS_RETRIES_SUCCESS_RESPIN = 8
|
||||
|
||||
local MOB_SPAWN_ZONE_INNER = 24
|
||||
local MOB_SPAWN_ZONE_MIDDLE = 32
|
||||
local MOB_SPAWN_ZONE_OUTER = 128
|
||||
|
||||
-- range for mob count
|
||||
local MOB_CAP_INNER_RADIUS = 32
|
||||
local aoc_range = 136
|
||||
|
||||
local MISSING_CAP_DEFAULT = 15
|
||||
local MOBS_CAP_CLOSE = 10
|
||||
|
||||
local SPAWN_MAPGEN_LIMIT = mcl_vars.mapgen_limit - 150
|
||||
|
||||
local mob_cap = {
|
||||
monster = tonumber(minetest.settings:get("mcl_mob_cap_monster")) or 70,
|
||||
animal = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10,
|
||||
hostile = tonumber(minetest.settings:get("mcl_mob_cap_monster")) or 70,
|
||||
passive = tonumber(minetest.settings:get("mcl_mob_cap_animal")) or 10,
|
||||
ambient = tonumber(minetest.settings:get("mcl_mob_cap_ambient")) or 15,
|
||||
water = tonumber(minetest.settings:get("mcl_mob_cap_water")) or 5, --currently unused
|
||||
water_ambient = tonumber(minetest.settings:get("mcl_mob_cap_water_ambient")) or 20, --currently unused
|
||||
water = tonumber(minetest.settings:get("mcl_mob_cap_water")) or 8,
|
||||
water_ambient = tonumber(minetest.settings:get("mcl_mob_cap_water_ambient")) or 20,
|
||||
water_underground = tonumber(minetest.settings:get("mcl_mob_cap_water_underground")) or 5,
|
||||
axolotl = tonumber(minetest.settings:get("mcl_mob_cap_axolotl")) or 2, -- TODO should be 5 when lush caves added
|
||||
player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75,
|
||||
global_hostile = tonumber(minetest.settings:get("mcl_mob_cap_hostile")) or 300,
|
||||
global_non_hostile = tonumber(minetest.settings:get("mcl_mob_cap_non_hostile")) or 300,
|
||||
total = tonumber(minetest.settings:get("mcl_mob_cap_total")) or 500,
|
||||
}
|
||||
|
||||
local peaceful_percentage_spawned = tonumber(minetest.settings:get("mcl_mob_peaceful_percentage_spawned")) or 30
|
||||
local peaceful_group_percentage_spawned = tonumber(minetest.settings:get("mcl_mob_peaceful_group_percentage_spawned")) or 15
|
||||
local hostile_group_percentage_spawned = tonumber(minetest.settings:get("mcl_mob_hostile_group_percentage_spawned")) or 20
|
||||
|
||||
mcl_log("Mob cap hostile: " .. mob_cap.hostile)
|
||||
mcl_log("Mob cap water: " .. mob_cap.water)
|
||||
mcl_log("Mob cap passive: " .. mob_cap.passive)
|
||||
|
||||
mcl_log("Percentage of peacefuls spawned: " .. peaceful_percentage_spawned)
|
||||
mcl_log("Percentage of peaceful spawns are group: " .. peaceful_group_percentage_spawned)
|
||||
mcl_log("Percentage of hostile spawns are group: " .. hostile_group_percentage_spawned)
|
||||
|
||||
--do mobs spawn?
|
||||
local mobs_spawn = minetest.settings:get_bool("mobs_spawn", true) ~= false
|
||||
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
|
||||
|
@ -169,6 +211,8 @@ local list_of_all_biomes = {
|
|||
"MushroomIslandShore",
|
||||
"JungleM_shore",
|
||||
"Jungle_shore",
|
||||
"BambooJungleM_shore",
|
||||
"BambooJungle_shore",
|
||||
"MangroveSwamp_shore",
|
||||
|
||||
-- dimension biome:
|
||||
|
@ -216,6 +260,10 @@ local list_of_all_biomes = {
|
|||
"JungleEdge",
|
||||
"SavannaM",
|
||||
"MangroveSwamp",
|
||||
"BambooJungle",
|
||||
"BambooJungleEdge",
|
||||
"BambooJungleEdgeM",
|
||||
"BambooJungleM",
|
||||
}
|
||||
|
||||
-- count how many mobs are in an area
|
||||
|
@ -244,18 +292,115 @@ local function count_mobs_total(mob_type)
|
|||
return num
|
||||
end
|
||||
|
||||
local function count_mobs_total_cap(mob_type)
|
||||
local function count_mobs_add_entry (mobs_list, mob_cat)
|
||||
if mobs_list[mob_cat] then
|
||||
mobs_list[mob_cat] = mobs_list[mob_cat] + 1
|
||||
else
|
||||
mobs_list[mob_cat] = 1
|
||||
end
|
||||
end
|
||||
|
||||
--categorise_by can be name or type or spawn_class
|
||||
local function count_mobs_all(categorise_by, pos)
|
||||
local mobs_found_wide = {}
|
||||
local mobs_found_close = {}
|
||||
|
||||
local num = 0
|
||||
for _,l in pairs(minetest.luaentities) do
|
||||
if l.is_mob then
|
||||
if ( mob_type == nil or l.type == mob_type ) and l.can_despawn and not l.nametag then
|
||||
for _,entity in pairs(minetest.luaentities) do
|
||||
if entity and entity.is_mob then
|
||||
|
||||
local add_entry = false
|
||||
--local mob_type = entity.type -- animal / monster / npc
|
||||
local mob_cat = entity[categorise_by]
|
||||
|
||||
if pos then
|
||||
local mob_pos = entity.object:get_pos()
|
||||
if mob_pos then
|
||||
local distance = vector.distance(pos, mob_pos)
|
||||
--mcl_log("distance: ".. distance)
|
||||
if distance <= MOB_SPAWN_ZONE_MIDDLE then
|
||||
--mcl_log("distance is close")
|
||||
count_mobs_add_entry (mobs_found_close, mob_cat)
|
||||
count_mobs_add_entry (mobs_found_wide, mob_cat)
|
||||
add_entry = true
|
||||
elseif distance <= MOB_SPAWN_ZONE_OUTER then
|
||||
--mcl_log("distance is wide")
|
||||
count_mobs_add_entry (mobs_found_wide, mob_cat)
|
||||
add_entry = true
|
||||
else
|
||||
--mcl_log("mob_pos: " .. minetest.pos_to_string(mob_pos))
|
||||
end
|
||||
end
|
||||
else
|
||||
count_mobs_add_entry (mobs_found_wide, mob_cat)
|
||||
add_entry = true
|
||||
end
|
||||
|
||||
|
||||
if add_entry then
|
||||
num = num + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return num
|
||||
--mcl_log("num: ".. num)
|
||||
return mobs_found_close, mobs_found_wide, num
|
||||
end
|
||||
|
||||
local function count_mobs_total_cap(mob_type)
|
||||
local total = 0
|
||||
local num = 0
|
||||
local hostile = 0
|
||||
local non_hostile = 0
|
||||
for _,l in pairs(minetest.luaentities) do
|
||||
if l.is_mob then
|
||||
total = total + 1
|
||||
local nametagged = l.nametag and l.nametag ~= ""
|
||||
if ( mob_type == nil or l.type == mob_type ) and not nametagged then
|
||||
if l.spawn_class == "hostile" then
|
||||
hostile = hostile + 1
|
||||
else
|
||||
non_hostile = non_hostile + 1
|
||||
end
|
||||
num = num + 1
|
||||
else
|
||||
mcl_log("l.name", l.name)
|
||||
mcl_log("l.nametag", l.nametag)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
mcl_log("Total mobs", total)
|
||||
mcl_log("hostile", hostile)
|
||||
mcl_log("non_hostile", non_hostile)
|
||||
return num, non_hostile, hostile
|
||||
end
|
||||
|
||||
local function output_mob_stats(mob_counts, total_mobs, chat_display)
|
||||
if (total_mobs) then
|
||||
local total_output = "Total mobs found: " .. total_mobs
|
||||
if chat_display then
|
||||
minetest.log(total_output)
|
||||
else
|
||||
minetest.log("action", total_output)
|
||||
end
|
||||
|
||||
end
|
||||
local detailed = ""
|
||||
if mob_counts then
|
||||
for k, v1 in pairs(mob_counts) do
|
||||
detailed = detailed .. tostring(k) .. ": " .. tostring(v1) .. "; "
|
||||
end
|
||||
end
|
||||
if detailed and detailed ~= "" then
|
||||
if chat_display then
|
||||
minetest.log(detailed)
|
||||
else
|
||||
minetest.log("action", detailed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- global functions
|
||||
|
||||
function mcl_mobs:spawn_abm_check(pos, node, name)
|
||||
|
@ -292,6 +437,8 @@ WARNING: BIOME INTEGRATION NEEDED -> How to get biome through lua??
|
|||
|
||||
--this is where all of the spawning information is kept
|
||||
local spawn_dictionary = {}
|
||||
--this is where all of the spawning information is kept for mobs that don't naturally spawn
|
||||
local non_spawn_dictionary = {}
|
||||
local summary_chance = 0
|
||||
|
||||
function mcl_mobs:spawn_setup(def)
|
||||
|
@ -357,7 +504,69 @@ function mcl_mobs:spawn_setup(def)
|
|||
summary_chance = summary_chance + chance
|
||||
end
|
||||
|
||||
function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
|
||||
function mcl_mobs:mob_light_lvl(mob_name, dimension)
|
||||
local spawn_dictionary_consolidated = {}
|
||||
|
||||
if non_spawn_dictionary[mob_name] then
|
||||
local mob_dimension = non_spawn_dictionary[mob_name][dimension]
|
||||
if mob_dimension then
|
||||
--minetest.log("Found in non spawn dictionary for dimension")
|
||||
return mob_dimension.min_light, mob_dimension.max_light
|
||||
else
|
||||
--minetest.log("Found in non spawn dictionary but not for dimension")
|
||||
local overworld_non_spawn_def = non_spawn_dictionary[mob_name]["overworld"]
|
||||
if overworld_non_spawn_def then
|
||||
return overworld_non_spawn_def.min_light, overworld_non_spawn_def.max_light
|
||||
end
|
||||
end
|
||||
else
|
||||
--minetest.log("must be in spawning dictionary")
|
||||
for i,v in pairs(spawn_dictionary) do
|
||||
local current_mob_name = spawn_dictionary[i].name
|
||||
local current_mob_dim = spawn_dictionary[i].dimension
|
||||
if mob_name == current_mob_name then
|
||||
if not spawn_dictionary_consolidated[current_mob_name] then
|
||||
spawn_dictionary_consolidated[current_mob_name] = {}
|
||||
end
|
||||
spawn_dictionary_consolidated[current_mob_name][current_mob_dim] = {
|
||||
["min_light"] = spawn_dictionary[i].min_light,
|
||||
["max_light"] = spawn_dictionary[i].max_light
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if spawn_dictionary_consolidated[mob_name] then
|
||||
--minetest.log("is in consolidated")
|
||||
local mob_dimension = spawn_dictionary_consolidated[mob_name][dimension]
|
||||
if mob_dimension then
|
||||
--minetest.log("found for dimension")
|
||||
return mob_dimension.min_light, mob_dimension.max_light
|
||||
else
|
||||
--minetest.log("not found for dimension, use overworld def")
|
||||
local mob_dimension_default = spawn_dictionary_consolidated[mob_name]["overworld"]
|
||||
if mob_dimension_default then
|
||||
return mob_dimension_default.min_light, mob_dimension_default.max_light
|
||||
end
|
||||
end
|
||||
else
|
||||
--minetest.log("not in consolidated")
|
||||
end
|
||||
end
|
||||
|
||||
minetest.log("action", "There are no light levels for mob (" .. tostring(mob_name) .. ") in dimension (" .. tostring(dimension) .. "). Return defaults")
|
||||
return 0, minetest.LIGHT_MAX+1
|
||||
end
|
||||
|
||||
function mcl_mobs:non_spawn_specific(mob_name,dimension,min_light,max_light)
|
||||
table.insert(non_spawn_dictionary, mob_name)
|
||||
non_spawn_dictionary[mob_name] = {
|
||||
[dimension] = {
|
||||
min_light = min_light , max_light = max_light
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_light, max_light, interval, chance, aoc, min_height, max_height, day_toggle, on_spawn, check_position)
|
||||
|
||||
-- Do mobs spawn at all?
|
||||
if not mobs_spawn then
|
||||
|
@ -394,22 +603,28 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_
|
|||
spawn_dictionary[key]["min_height"] = min_height
|
||||
spawn_dictionary[key]["max_height"] = max_height
|
||||
spawn_dictionary[key]["day_toggle"] = day_toggle
|
||||
spawn_dictionary[key]["check_position"] = check_position
|
||||
|
||||
summary_chance = summary_chance + chance
|
||||
end
|
||||
|
||||
|
||||
local two_pi = 2 * math.pi
|
||||
local function get_next_mob_spawn_pos(pos)
|
||||
local distance = math_random(25, 32)
|
||||
-- TODO We should consider spawning something a little further away sporadically.
|
||||
-- It would be good for sky farms and variance, rather than all being on the 24 - 32 block away radius
|
||||
local distance = math_random(MOB_SPAWN_ZONE_INNER, MOB_SPAWN_ZONE_MIDDLE)
|
||||
local angle = math_random() * two_pi
|
||||
|
||||
-- TODO Floor xoff and zoff and add 0.5 so it tries to spawn in the middle of the square. Less failed attempts.
|
||||
local xoff = math_round(distance * math_cos(angle))
|
||||
local yoff = math_round(distance * math_sin(angle))
|
||||
return vector.offset(pos, xoff, 0, yoff)
|
||||
local zoff = math_round(distance * math_sin(angle))
|
||||
return vector.offset(pos, xoff, 0, zoff)
|
||||
end
|
||||
|
||||
local function decypher_limits(posy)
|
||||
posy = math_floor(posy)
|
||||
return posy - 32, posy + 32
|
||||
return posy - MOB_SPAWN_ZONE_MIDDLE, posy + MOB_SPAWN_ZONE_MIDDLE
|
||||
end
|
||||
|
||||
--a simple helper function for mob_spawn
|
||||
|
@ -460,15 +675,20 @@ local function has_room(self,pos)
|
|||
return true
|
||||
end
|
||||
|
||||
local function spawn_check(pos,spawn_def,ignore_caps)
|
||||
if not spawn_def then return end
|
||||
|
||||
|
||||
local function spawn_check(pos, spawn_def)
|
||||
if not spawn_def or not pos then return end
|
||||
|
||||
dbg_spawn_attempts = dbg_spawn_attempts + 1
|
||||
local dimension = mcl_worlds.pos_to_dimension(pos)
|
||||
local mob_def = minetest.registered_entities[spawn_def.name]
|
||||
local mob_type = mob_def.type
|
||||
local gotten_node = get_node(pos).name
|
||||
local gotten_biome = minetest.get_biome_data(pos)
|
||||
|
||||
if not gotten_node or not gotten_biome then return end
|
||||
|
||||
gotten_biome = get_biome_name(gotten_biome.biome) --makes it easier to work with
|
||||
|
||||
local is_ground = minetest.get_item_group(gotten_node,"solid") ~= 0
|
||||
|
@ -483,34 +703,38 @@ local function spawn_check(pos,spawn_def,ignore_caps)
|
|||
local is_leaf = get_item_group(gotten_node, "leaves") ~= 0
|
||||
local is_bedrock = gotten_node == "mcl_core:bedrock"
|
||||
local is_grass = minetest.get_item_group(gotten_node,"grass_block") ~= 0
|
||||
local mob_count_wide = 0
|
||||
|
||||
local mob_count = 0
|
||||
if not ignore_caps then
|
||||
mob_count = count_mobs(pos,32,mob_type)
|
||||
mob_count_wide = count_mobs(pos,aoc_range,mob_type)
|
||||
end
|
||||
|
||||
if pos and spawn_def
|
||||
and ( mob_count_wide < (mob_cap[mob_type] or 15) )
|
||||
and ( mob_count < 5 )
|
||||
and pos.y >= spawn_def.min_height
|
||||
if pos.y >= spawn_def.min_height
|
||||
and pos.y <= spawn_def.max_height
|
||||
and spawn_def.dimension == dimension
|
||||
and biome_check(spawn_def.biomes, gotten_biome)
|
||||
and (is_ground or spawn_def.type_of_spawning ~= "ground")
|
||||
and biome_check(spawn_def.biomes, gotten_biome) then
|
||||
|
||||
--mcl_log("Level 1 spawn check passed")
|
||||
--minetest.log("Mob: " .. mob_def.name)
|
||||
|
||||
if (is_ground or spawn_def.type_of_spawning ~= "ground")
|
||||
and (spawn_def.type_of_spawning ~= "ground" or not is_leaf)
|
||||
and has_room(mob_def,pos)
|
||||
and (spawn_def.check_position and spawn_def.check_position(pos) or true)
|
||||
and (not is_farm_animal(spawn_def.name) or is_grass)
|
||||
and (spawn_def.type_of_spawning ~= "water" or is_water)
|
||||
and ( not spawn_protected or not minetest.is_protected(pos, "") )
|
||||
and not is_bedrock then
|
||||
--only need to poll for node light if everything else worked
|
||||
and not is_bedrock
|
||||
and has_room(mob_def,pos)
|
||||
and (spawn_def.check_position and spawn_def.check_position(pos) or spawn_def.check_position == nil)
|
||||
and ( not spawn_protected or not minetest.is_protected(pos, "") ) then
|
||||
|
||||
--mcl_log("Level 2 spawn check passed")
|
||||
|
||||
local gotten_light = get_node_light(pos)
|
||||
if gotten_light >= spawn_def.min_light and gotten_light <= spawn_def.max_light then
|
||||
--mcl_log("Level 3 spawn check passed")
|
||||
return true
|
||||
else
|
||||
--mcl_log("Spawn check level 3 failed")
|
||||
end
|
||||
else
|
||||
--mcl_log("Spawn check level 2 failed")
|
||||
end
|
||||
else
|
||||
--mcl_log("Spawn check level 1 failed")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
@ -529,8 +753,7 @@ function mcl_mobs.spawn(pos,id)
|
|||
end
|
||||
|
||||
|
||||
local function spawn_group(p,mob,spawn_on,group_max,group_min)
|
||||
if not group_min then group_min = 1 end
|
||||
local function spawn_group(p,mob,spawn_on,amount_to_spawn)
|
||||
local nn= minetest.find_nodes_in_area_under_air(vector.offset(p,-5,-3,-5),vector.offset(p,5,3,5),spawn_on)
|
||||
local o
|
||||
table.shuffle(nn)
|
||||
|
@ -538,9 +761,10 @@ local function spawn_group(p,mob,spawn_on,group_max,group_min)
|
|||
nn = {}
|
||||
table.insert(nn,p)
|
||||
end
|
||||
for i = 1, math.random(group_min,group_max) do
|
||||
|
||||
for i = 1, amount_to_spawn do
|
||||
local sp = vector.offset(nn[math.random(#nn)],0,1,0)
|
||||
if spawn_check(nn[math.random(#nn)],mob,true) then
|
||||
if spawn_check(nn[math.random(#nn)],mob) then
|
||||
if mob.type_of_spawning == "water" then
|
||||
sp = get_water_spawn(sp)
|
||||
end
|
||||
|
@ -628,25 +852,124 @@ if mobs_spawn then
|
|||
|
||||
local perlin_noise
|
||||
|
||||
local function spawn_a_mob(pos, dimension, y_min, y_max)
|
||||
--create a disconnected clone of the spawn dictionary
|
||||
--prevents memory leak
|
||||
local mob_library_worker_table = table_copy(spawn_dictionary)
|
||||
-- Get pos to spawn, x and z are randomised, y is range
|
||||
|
||||
|
||||
local function mob_cap_space (pos, mob_type, mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile)
|
||||
|
||||
-- Some mob examples
|
||||
--type = "monster", spawn_class = "hostile",
|
||||
--type = "animal", spawn_class = "passive",
|
||||
--local cod = { type = "animal", spawn_class = "water",
|
||||
|
||||
local type_cap = mob_cap[mob_type] or MISSING_CAP_DEFAULT
|
||||
local close_zone_cap = MOBS_CAP_CLOSE
|
||||
|
||||
local mob_total_wide = mob_counts_wide[mob_type]
|
||||
if not mob_total_wide then
|
||||
--mcl_log("none of type found. set as 0")
|
||||
mob_total_wide = 0
|
||||
end
|
||||
|
||||
local cap_space_wide = math.max(type_cap - mob_total_wide, 0)
|
||||
|
||||
mcl_log("mob_type", mob_type)
|
||||
mcl_log("cap_space_wide", cap_space_wide)
|
||||
|
||||
local cap_space_available = 0
|
||||
if mob_type == "hostile" then
|
||||
mcl_log("cap_space_global", cap_space_hostile)
|
||||
cap_space_available = math.min(cap_space_hostile, cap_space_wide)
|
||||
else
|
||||
mcl_log("cap_space_global", cap_space_non_hostile)
|
||||
cap_space_available = math.min(cap_space_non_hostile, cap_space_wide)
|
||||
end
|
||||
|
||||
local mob_total_close = mob_counts_close[mob_type]
|
||||
if not mob_total_close then
|
||||
--mcl_log("none of type found. set as 0")
|
||||
mob_total_close = 0
|
||||
end
|
||||
|
||||
local cap_space_close = math.max(close_zone_cap - mob_total_close, 0)
|
||||
cap_space_available = math.min(cap_space_available, cap_space_close)
|
||||
|
||||
mcl_log("cap_space_close", cap_space_close)
|
||||
mcl_log("cap_space_available", cap_space_available)
|
||||
|
||||
if false and mob_type == "water" then
|
||||
mcl_log("mob_type: " .. mob_type .. " and pos: " .. minetest.pos_to_string(pos))
|
||||
mcl_log("wide: " .. mob_total_wide .. "/" .. type_cap)
|
||||
mcl_log("cap_space_wide: " .. cap_space_wide)
|
||||
mcl_log("close: " .. mob_total_close .. "/" .. close_zone_cap)
|
||||
mcl_log("cap_space_close: " .. cap_space_close)
|
||||
end
|
||||
|
||||
return cap_space_available
|
||||
end
|
||||
|
||||
local function find_spawning_position(pos, max_times)
|
||||
local spawning_position
|
||||
|
||||
local max_loops = 1
|
||||
if max_times then max_loops = max_times end
|
||||
|
||||
local y_min, y_max = decypher_limits(pos.y)
|
||||
|
||||
--mcl_log("mapgen_limit: " .. SPAWN_MAPGEN_LIMIT)
|
||||
local i = 0
|
||||
repeat
|
||||
local goal_pos = get_next_mob_spawn_pos(pos)
|
||||
--grab mob that fits into the spawning location
|
||||
--randomly grab a mob, don't exclude any possibilities
|
||||
|
||||
if math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT then
|
||||
local spawning_position_list = find_nodes_in_area_under_air(
|
||||
{x = goal_pos.x, y = y_min, z = goal_pos.z},
|
||||
{x = goal_pos.x, y = y_max, z = goal_pos.z},
|
||||
{"group:solid", "group:water", "group:lava"}
|
||||
)
|
||||
if #spawning_position_list <= 0 then return end
|
||||
local spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
if #spawning_position_list > 0 then
|
||||
mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos))
|
||||
spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
else
|
||||
mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
|
||||
i = i + 1
|
||||
if i >= max_loops then
|
||||
mcl_log("Cancel finding spawn positions at: " .. max_loops)
|
||||
break
|
||||
end
|
||||
until spawning_position
|
||||
return spawning_position
|
||||
end
|
||||
|
||||
local function spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
|
||||
--create a disconnected clone of the spawn dictionary, prevents memory leak
|
||||
local mob_library_worker_table = table_copy(spawn_dictionary)
|
||||
|
||||
local spawning_position = find_spawning_position(pos, FIND_SPAWN_POS_RETRIES)
|
||||
if not spawning_position then
|
||||
minetest.log("action", "[Mobs spawn] Cannot find a valid spawn position after retries: " .. FIND_SPAWN_POS_RETRIES)
|
||||
return
|
||||
end
|
||||
|
||||
local mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("spawn_class", spawning_position)
|
||||
--output_mob_stats(mob_counts_close, total_mobs)
|
||||
--output_mob_stats(mob_counts_wide)
|
||||
|
||||
--grab mob that fits into the spawning location
|
||||
--randomly grab a mob, don't exclude any possibilities
|
||||
perlin_noise = perlin_noise or minetest_get_perlin(noise_params)
|
||||
local noise = perlin_noise:get_3d(spawning_position)
|
||||
local current_summary_chance = summary_chance
|
||||
|
||||
table.shuffle(mob_library_worker_table)
|
||||
|
||||
while #mob_library_worker_table > 0 do
|
||||
local mob_chance_offset = (math_round(noise * current_summary_chance + 12345) % current_summary_chance) + 1
|
||||
local mob_index = 1
|
||||
|
@ -657,13 +980,28 @@ if mobs_spawn then
|
|||
mob_chance = mob_library_worker_table[mob_index].chance
|
||||
step_chance = step_chance + mob_chance
|
||||
end
|
||||
local mob_def = mob_library_worker_table[mob_index]
|
||||
--minetest.log(mob_def.name.." "..step_chance.. " "..mob_chance)
|
||||
|
||||
local mob_def = mob_library_worker_table[mob_index]
|
||||
if mob_def and mob_def.name and minetest.registered_entities[mob_def.name] then
|
||||
local spawn_in_group = minetest.registered_entities[mob_def.name].spawn_in_group or 4
|
||||
local spawn_in_group_min = minetest.registered_entities[mob_def.name].spawn_in_group_min or 1
|
||||
local mob_type = minetest.registered_entities[mob_def.name].type
|
||||
if spawn_check(spawning_position,mob_def) then
|
||||
|
||||
local mob_def_ent = minetest.registered_entities[mob_def.name]
|
||||
local mob_spawn_class = mob_def_ent.spawn_class
|
||||
|
||||
local cap_space_available = mob_cap_space (spawning_position, mob_spawn_class, mob_counts_close, mob_counts_wide, cap_space_hostile, cap_space_non_hostile)
|
||||
|
||||
if cap_space_available > 0 then
|
||||
--mcl_log("Cap space available")
|
||||
|
||||
-- Spawn caps for animals and water creatures fill up rapidly. Need to throttle this somewhat
|
||||
-- for performance and for early game challenge. We don't want to reduce hostiles though.
|
||||
local spawn_hostile = (mob_spawn_class == "hostile")
|
||||
local spawn_passive = (mob_spawn_class ~= "hostile") and math.random(100) < peaceful_percentage_spawned
|
||||
|
||||
--mcl_log("Spawn_passive: " .. tostring(spawn_passive))
|
||||
--mcl_log("Spawn_hostile: " .. tostring(spawn_hostile))
|
||||
|
||||
if (spawn_hostile or spawn_passive) and spawn_check(spawning_position,mob_def) then
|
||||
if mob_def.type_of_spawning == "water" then
|
||||
spawning_position = get_water_spawn(spawning_position)
|
||||
if not spawning_position then
|
||||
|
@ -671,25 +1009,59 @@ if mobs_spawn then
|
|||
return
|
||||
end
|
||||
end
|
||||
if minetest.registered_entities[mob_def.name].can_spawn and not minetest.registered_entities[mob_def.name].can_spawn(spawning_position) then
|
||||
if mob_def_ent.can_spawn and not mob_def_ent.can_spawn(spawning_position) then
|
||||
minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position)))
|
||||
return
|
||||
end
|
||||
--everything is correct, spawn mob
|
||||
local object
|
||||
if spawn_in_group and ( mob_type ~= "monster" or math.random(5) == 1 ) then
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] A group of mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1))
|
||||
end
|
||||
object = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name},spawn_in_group,spawn_in_group_min)
|
||||
|
||||
--everything is correct, spawn mob
|
||||
local spawn_in_group = mob_def_ent.spawn_in_group or 4
|
||||
|
||||
local spawn_group_hostile = (mob_spawn_class == "hostile") and (math.random(100) < hostile_group_percentage_spawned)
|
||||
local spawn_group_passive = (mob_spawn_class ~= "hostile") and (math.random(100) < peaceful_group_percentage_spawned)
|
||||
|
||||
mcl_log("spawn_group_hostile: " .. tostring(spawn_group_hostile))
|
||||
mcl_log("spawn_group_passive: " .. tostring(spawn_group_passive))
|
||||
|
||||
local spawned
|
||||
if spawn_in_group and (spawn_group_hostile or spawn_group_passive) then
|
||||
local group_min = mob_def_ent.spawn_in_group_min or 1
|
||||
if not group_min then group_min = 1 end
|
||||
|
||||
local amount_to_spawn = math.random(group_min, spawn_in_group)
|
||||
mcl_log("Spawning quantity: " .. amount_to_spawn)
|
||||
amount_to_spawn = math.min(amount_to_spawn, cap_space_available)
|
||||
mcl_log("throttled spawning quantity: " .. amount_to_spawn)
|
||||
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] A group of " ..amount_to_spawn .. " " .. mob_def.name .. " mob spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1))
|
||||
end
|
||||
spawned = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name}, amount_to_spawn)
|
||||
else
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at ".. minetest.pos_to_string(spawning_position, 1))
|
||||
end
|
||||
object = mcl_mobs.spawn(spawning_position, mob_def.name)
|
||||
spawned = mcl_mobs.spawn(spawning_position, mob_def.name)
|
||||
end
|
||||
|
||||
if spawned then
|
||||
--mcl_log("We have spawned")
|
||||
mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("spawn_class", pos)
|
||||
local new_spawning_position = find_spawning_position(pos, FIND_SPAWN_POS_RETRIES_SUCCESS_RESPIN)
|
||||
if new_spawning_position then
|
||||
mcl_log("Setting new spawning position")
|
||||
spawning_position = new_spawning_position
|
||||
else
|
||||
mcl_log("Cannot set new spawning position")
|
||||
end
|
||||
end
|
||||
else
|
||||
--mcl_log("Spawn check failed")
|
||||
end
|
||||
else
|
||||
--mcl_log("Cap space full")
|
||||
end
|
||||
|
||||
end
|
||||
current_summary_chance = current_summary_chance - mob_chance
|
||||
table_remove(mob_library_worker_table, mob_index)
|
||||
|
@ -701,34 +1073,72 @@ if mobs_spawn then
|
|||
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
|
||||
timer = timer + dtime
|
||||
if timer < 10 then return end
|
||||
if timer < WAIT_FOR_SPAWN_ATTEMPT then return end
|
||||
timer = 0
|
||||
|
||||
local players = get_connected_players()
|
||||
local total_mobs = count_mobs_total_cap()
|
||||
local total_mobs, total_non_hostile, total_hostile = count_mobs_total_cap()
|
||||
|
||||
local cap_space_hostile = math.max(mob_cap.global_hostile - total_hostile, 0)
|
||||
local cap_space_non_hostile = math.max(mob_cap.global_non_hostile - total_non_hostile, 0)
|
||||
mcl_log("global cap_space_hostile", cap_space_hostile)
|
||||
mcl_log("global cap_space_non_hostile", cap_space_non_hostile)
|
||||
|
||||
if total_mobs > mob_cap.total or total_mobs > #players * mob_cap.player then
|
||||
minetest.log("action","[mcl_mobs] global mob cap reached. no cycle spawning.")
|
||||
return
|
||||
end --mob cap per player
|
||||
|
||||
for _, player in pairs(players) do
|
||||
local pos = player:get_pos()
|
||||
local dimension = mcl_worlds.pos_to_dimension(pos)
|
||||
-- ignore void and unloaded area
|
||||
if dimension ~= "void" and dimension ~= "default" then
|
||||
local y_min, y_max = decypher_limits(pos.y)
|
||||
spawn_a_mob(pos, dimension, y_min, y_max)
|
||||
spawn_a_mob(pos, cap_space_hostile, cap_space_non_hostile)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function mob_class:check_despawn(pos)
|
||||
local function despawn_allowed(self)
|
||||
local nametag = self.nametag and self.nametag ~= ""
|
||||
local not_busy = self.state ~= "attack" and self.following == nil
|
||||
if self.can_despawn == true then
|
||||
if not nametag and not_busy and not self.tamed == true and not self.persistent == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function mob_class:despawn_allowed()
|
||||
despawn_allowed(self)
|
||||
end
|
||||
|
||||
|
||||
assert(despawn_allowed({can_despawn=false}) == false, "despawn_allowed - can_despawn false failed")
|
||||
assert(despawn_allowed({can_despawn=true}) == true, "despawn_allowed - can_despawn true failed")
|
||||
|
||||
assert(despawn_allowed({can_despawn=true, nametag=""}) == true, "despawn_allowed - blank nametag failed")
|
||||
assert(despawn_allowed({can_despawn=true, nametag=nil}) == true, "despawn_allowed - nil nametag failed")
|
||||
assert(despawn_allowed({can_despawn=true, nametag="bob"}) == false, "despawn_allowed - nametag failed")
|
||||
|
||||
assert(despawn_allowed({can_despawn=true, state="attack"}) == false, "despawn_allowed - attack state failed")
|
||||
assert(despawn_allowed({can_despawn=true, following="blah"}) == false, "despawn_allowed - following state failed")
|
||||
|
||||
assert(despawn_allowed({can_despawn=true, tamed=false}) == true, "despawn_allowed - not tamed")
|
||||
assert(despawn_allowed({can_despawn=true, tamed=true}) == false, "despawn_allowed - tamed")
|
||||
|
||||
assert(despawn_allowed({can_despawn=true, persistent=true}) == false, "despawn_allowed - persistent")
|
||||
assert(despawn_allowed({can_despawn=true, persistent=false}) == true, "despawn_allowed - not persistent")
|
||||
|
||||
function mob_class:check_despawn(pos, dtime)
|
||||
self.lifetimer = self.lifetimer - dtime
|
||||
|
||||
-- Despawning: when lifetimer expires, remove mob
|
||||
if remove_far
|
||||
and self.can_despawn == true
|
||||
and ((not self.nametag) or (self.nametag == ""))
|
||||
and self.state ~= "attack"
|
||||
and self.following == nil then
|
||||
if remove_far and despawn_allowed(self) then
|
||||
if self.despawn_immediately or self.lifetimer <= 0 then
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] Mob "..self.name.." despawns at "..minetest.pos_to_string(pos, 1) .. " lifetimer ran out")
|
||||
|
@ -746,14 +1156,16 @@ function mob_class:check_despawn(pos)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
minetest.register_chatcommand("mobstats",{
|
||||
privs = { debug = true },
|
||||
func = function(n,param)
|
||||
minetest.chat_send_player(n,dump(dbg_spawn_counts))
|
||||
--minetest.chat_send_player(n,dump(dbg_spawn_counts))
|
||||
local pos = minetest.get_player_by_name(n):get_pos()
|
||||
minetest.chat_send_player(n,"mobs within 32 radius of player:"..count_mobs(pos,32))
|
||||
minetest.chat_send_player(n,"total mobs:"..count_mobs_total())
|
||||
minetest.chat_send_player(n,"spawning attempts since server start:"..dbg_spawn_attempts)
|
||||
minetest.chat_send_player(n,"successful spawns since server start:"..dbg_spawn_succ)
|
||||
minetest.chat_send_player(n,"mobs: within 32 radius of player/total loaded :"..count_mobs(pos,MOB_CAP_INNER_RADIUS) .. "/" .. count_mobs_total())
|
||||
minetest.chat_send_player(n,"spawning attempts since server start:" .. dbg_spawn_succ .. "/" .. dbg_spawn_attempts)
|
||||
|
||||
local mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("name") -- Can use "type"
|
||||
output_mob_stats(mob_counts_wide, total_mobs, true)
|
||||
end
|
||||
})
|
||||
|
|
Before Width: | Height: | Size: 895 B |
|
@ -0,0 +1,2 @@
|
|||
# textdomain:mcl_paintings
|
||||
Painting=Maleri
|
|
@ -0,0 +1,2 @@
|
|||
# textdomain:mcl_paintings
|
||||
Painting=Cuadro
|
Before Width: | Height: | Size: 229 B |
|
@ -39,7 +39,7 @@ This mod adds mobs which closely resemble the mobs from the game Minecraft, vers
|
|||
* Cave Spider
|
||||
* Enderman
|
||||
* Zombie Villager
|
||||
* Zombie Pigman
|
||||
* Zombie Piglin
|
||||
* Wither Skeleton
|
||||
* Magma Cube
|
||||
* Blaze
|
||||
|
|
|
@ -2,7 +2,7 @@ local S = minetest.get_translator(minetest.get_current_modname())
|
|||
|
||||
local axolotl = {
|
||||
type = "animal",
|
||||
spawn_class = "water",
|
||||
spawn_class = "axolotl",
|
||||
can_despawn = true,
|
||||
passive = false,
|
||||
hp_min = 14,
|
||||
|
|
|
@ -207,5 +207,6 @@ mcl_mobs.register_arrow("mobs_mc:blaze_fireball", {
|
|||
end
|
||||
})
|
||||
|
||||
-- spawn eggs
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:blaze", "overworld", 0, 11)
|
||||
-- spawn eggs.
|
||||
mcl_mobs.register_egg("mobs_mc:blaze", S("Blaze"), "#f6b201", "#fff87e", 0)
|
||||
|
|