Compare commits
596 Commits
csm-enable
...
master
Author | SHA1 | Date |
---|---|---|
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 | |
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 |
|
@ -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,4 @@
|
|||
*.blend2
|
||||
*.blend3
|
||||
/.idea/
|
||||
*.xcf
|
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.
|
||||
|
@ -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
|
||||
|
||||
|
|
15
CREDITS.md
|
@ -35,7 +35,8 @@
|
|||
* SumianVoice
|
||||
* MrRar
|
||||
* talamh
|
||||
* Faerraven
|
||||
* Faerraven / Michieal
|
||||
* FossFanatic
|
||||
|
||||
## Contributors
|
||||
* Laurent Rocher
|
||||
|
@ -96,6 +97,12 @@
|
|||
* TheOnlyJoeEnderman
|
||||
* Ranko Saotome
|
||||
* Gregor Parzefall
|
||||
* Wbjitscool
|
||||
* b3nderman
|
||||
* CyberMango
|
||||
* gldrk
|
||||
* SmokeyDope
|
||||
* atomdmac
|
||||
|
||||
## MineClone5
|
||||
* kay27
|
||||
|
@ -147,11 +154,13 @@
|
|||
* jordan4ibanez
|
||||
* paramat
|
||||
* cora
|
||||
* Faerraven / Michieal
|
||||
|
||||
## 3D Models
|
||||
* 22i
|
||||
* tobyplowy
|
||||
* epCode
|
||||
* Faerraven / Michieal
|
||||
|
||||
## Textures
|
||||
* XSSheep
|
||||
|
@ -166,6 +175,7 @@
|
|||
* RandomLegoBrick
|
||||
* cora
|
||||
* Faerraven / Michieal
|
||||
* Nicu
|
||||
|
||||
## Translations
|
||||
* Wuzzy
|
||||
|
@ -180,9 +190,12 @@
|
|||
* snowyu
|
||||
* 3raven
|
||||
* SakuraRiu
|
||||
* anarquimico
|
||||
|
||||
## Funders
|
||||
* 40W
|
||||
* bauknecht
|
||||
* Cora
|
||||
|
||||
## Special thanks
|
||||
* celeron55 for creating Minetest
|
||||
|
|
|
@ -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!
|
|
@ -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
|
||||
|
@ -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):
|
||||
|
|
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.82.0"
|
||||
|
||||
git tag 0.81.1
|
||||
git tag 0.82.0
|
||||
|
||||
git push origin 0.81.1
|
||||
git push origin 0.82.0
|
||||
|
||||
#Update version in game.conf to -SNAPSHOT
|
||||
|
||||
git commit -m "Post-release set version 0.82.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
|
|
@ -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.82.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
|
||||
|
@ -228,7 +228,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 +262,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 +282,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
|
||||
|
@ -350,7 +352,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
|
||||
|
@ -360,6 +362,12 @@ local function overwrite()
|
|||
minetest.override_item(tname, {
|
||||
tool_capabilities = toolcaps
|
||||
})
|
||||
else
|
||||
-- This is needed to deal damage when punching mobs
|
||||
-- with random items in hand in survival mode
|
||||
minetest.override_item(tname, {
|
||||
tool_capabilities = mcl_meshhand.survival_hand_tool_caps
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
mcl_damage.run_death_callbacks(player, mcl_damage.from_mt(mt_reason))
|
||||
minetest.log("action","Player "..player:get_player_name().." died at "..minetest.pos_to_string(vector.round(player:get_pos())))
|
||||
end)
|
||||
|
||||
|
|
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 216 B |
|
@ -2,8 +2,8 @@ mcl_util = {}
|
|||
|
||||
-- Updates all values in t using values from to*.
|
||||
function table.update(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
for _, to in ipairs {...} do
|
||||
for k, v in pairs(to) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
|
@ -12,8 +12,8 @@ end
|
|||
|
||||
-- Updates nil values in t using values from to*.
|
||||
function table.update_nil(t, ...)
|
||||
for _, to in ipairs{...} do
|
||||
for k,v in pairs(to) do
|
||||
for _, to in ipairs {...} do
|
||||
for k, v in pairs(to) do
|
||||
if t[k] == nil then
|
||||
t[k] = v
|
||||
end
|
||||
|
@ -22,9 +22,9 @@ function table.update_nil(t, ...)
|
|||
return t
|
||||
end
|
||||
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_default",false)
|
||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_default", false)
|
||||
local LOG_MODULE = "[MCL2]"
|
||||
function mcl_util.mcl_log (message, module, bypass_default_logger)
|
||||
function mcl_util.mcl_log(message, module, bypass_default_logger)
|
||||
local selected_module = LOG_MODULE
|
||||
if module then
|
||||
selected_module = module
|
||||
|
@ -34,7 +34,6 @@ function mcl_util.mcl_log (message, module, bypass_default_logger)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
function mcl_util.file_exists(name)
|
||||
if type(name) ~= "string" then return end
|
||||
local f = io.open(name)
|
||||
|
@ -69,7 +68,7 @@ function mcl_util.rotate_axis_and_place(itemstack, placer, pointed_thing, infini
|
|||
local undef = minetest.registered_nodes[unode.name]
|
||||
if undef and undef.on_rightclick then
|
||||
undef.on_rightclick(pointed_thing.under, unode, placer,
|
||||
itemstack, pointed_thing)
|
||||
itemstack, pointed_thing)
|
||||
return
|
||||
end
|
||||
local fdir = minetest.dir_to_facedir(placer:get_look_dir())
|
||||
|
@ -151,23 +150,23 @@ end
|
|||
function mcl_util.get_double_container_neighbor_pos(pos, param2, side)
|
||||
if side == "right" then
|
||||
if param2 == 0 then
|
||||
return {x=pos.x-1, y=pos.y, z=pos.z}
|
||||
return {x = pos.x - 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 1 then
|
||||
return {x=pos.x, y=pos.y, z=pos.z+1}
|
||||
return {x = pos.x, y = pos.y, z = pos.z + 1}
|
||||
elseif param2 == 2 then
|
||||
return {x=pos.x+1, y=pos.y, z=pos.z}
|
||||
return {x = pos.x + 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 3 then
|
||||
return {x=pos.x, y=pos.y, z=pos.z-1}
|
||||
return {x = pos.x, y = pos.y, z = pos.z - 1}
|
||||
end
|
||||
else
|
||||
if param2 == 0 then
|
||||
return {x=pos.x+1, y=pos.y, z=pos.z}
|
||||
return {x = pos.x + 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 1 then
|
||||
return {x=pos.x, y=pos.y, z=pos.z-1}
|
||||
return {x = pos.x, y = pos.y, z = pos.z - 1}
|
||||
elseif param2 == 2 then
|
||||
return {x=pos.x-1, y=pos.y, z=pos.z}
|
||||
return {x = pos.x - 1, y = pos.y, z = pos.z}
|
||||
elseif param2 == 3 then
|
||||
return {x=pos.x, y=pos.y, z=pos.z+1}
|
||||
return {x = pos.x, y = pos.y, z = pos.z + 1}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -185,7 +184,7 @@ end
|
|||
function mcl_util.get_eligible_transfer_item_slot(src_inventory, src_list, dst_inventory, dst_list, condition)
|
||||
local size = src_inventory:get_size(src_list)
|
||||
local stack
|
||||
for i=1, size do
|
||||
for i = 1, size do
|
||||
stack = src_inventory:get_stack(src_list, i)
|
||||
if not stack:is_empty() and (condition == nil or condition(stack, src_inventory, src_list, dst_inventory, dst_list)) then
|
||||
return i
|
||||
|
@ -288,10 +287,10 @@ function mcl_util.move_item_container(source_pos, destination_pos, source_list,
|
|||
-- Main inventory for most container types
|
||||
if sctype == 2 or sctype == 3 or sctype == 5 or sctype == 6 or sctype == 7 then
|
||||
source_list = "main"
|
||||
-- Furnace: output
|
||||
-- Furnace: output
|
||||
elseif sctype == 4 then
|
||||
source_list = "dst"
|
||||
-- Unknown source container type. Bail out
|
||||
-- Unknown source container type. Bail out
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
@ -344,7 +343,7 @@ function mcl_util.move_item_container(source_pos, destination_pos, source_list,
|
|||
-- Main inventory for most container types
|
||||
if dctype == 2 or dctype == 3 or dctype == 5 or dctype == 6 or dctype == 7 then
|
||||
destination_list = "main"
|
||||
-- Furnace source slot
|
||||
-- Furnace source slot
|
||||
elseif dctype == 4 then
|
||||
destination_list = "src"
|
||||
end
|
||||
|
@ -409,7 +408,7 @@ end
|
|||
-- Returns true if item (itemstring or ItemStack) can be used as a furnace fuel.
|
||||
-- Returns false otherwise
|
||||
function mcl_util.is_fuel(item)
|
||||
return minetest.get_craft_result({method="fuel", width=1, items={item}}).time ~= 0
|
||||
return minetest.get_craft_result({method = "fuel", width = 1, items = {item}}).time ~= 0
|
||||
end
|
||||
|
||||
-- Returns a on_place function for plants
|
||||
|
@ -456,7 +455,7 @@ function mcl_util.generate_on_place_plant_function(condition)
|
|||
|
||||
if success then
|
||||
if idef.sounds and idef.sounds.place then
|
||||
minetest.sound_play(idef.sounds.place, {pos=pointed_thing.above, gain=1}, true)
|
||||
minetest.sound_play(idef.sounds.place, {pos = pointed_thing.above, gain = 1}, true)
|
||||
end
|
||||
end
|
||||
itemstack = new_itemstack
|
||||
|
@ -557,6 +556,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
|
||||
|
@ -643,78 +647,80 @@ end
|
|||
|
||||
local function roundN(n, d)
|
||||
if type(n) ~= "number" then return n end
|
||||
local m = 10^d
|
||||
return math.floor(n * m + 0.5) / m
|
||||
local m = 10 ^ d
|
||||
return math.floor(n * m + 0.5) / m
|
||||
end
|
||||
|
||||
local function close_enough(a,b)
|
||||
local rt=true
|
||||
local function close_enough(a, b)
|
||||
local rt = true
|
||||
if type(a) == "table" and type(b) == "table" then
|
||||
for k,v in pairs(a) do
|
||||
if roundN(v,2) ~= roundN(b[k],2) then
|
||||
rt=false
|
||||
for k, v in pairs(a) do
|
||||
if roundN(v, 2) ~= roundN(b[k], 2) then
|
||||
rt = false
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
rt = roundN(a,2) == roundN(b,2)
|
||||
rt = roundN(a, 2) == roundN(b, 2)
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
local function props_changed(props,oldprops)
|
||||
local changed=false
|
||||
local p={}
|
||||
for k,v in pairs(props) do
|
||||
if not close_enough(v,oldprops[k]) then
|
||||
p[k]=v
|
||||
changed=true
|
||||
local function props_changed(props, oldprops)
|
||||
local changed = false
|
||||
local p = {}
|
||||
for k, v in pairs(props) do
|
||||
if not close_enough(v, oldprops[k]) then
|
||||
p[k] = v
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
return changed,p
|
||||
return changed, p
|
||||
end
|
||||
|
||||
--tests for roundN
|
||||
local test_round1=15
|
||||
local test_round2=15.00199999999
|
||||
local test_round3=15.00111111
|
||||
local test_round4=15.00999999
|
||||
local test_round1 = 15
|
||||
local test_round2 = 15.00199999999
|
||||
local test_round3 = 15.00111111
|
||||
local test_round4 = 15.00999999
|
||||
|
||||
assert(roundN(test_round1,2)==roundN(test_round1,2))
|
||||
assert(roundN(test_round1,2)==roundN(test_round2,2))
|
||||
assert(roundN(test_round1,2)==roundN(test_round3,2))
|
||||
assert(roundN(test_round1,2)~=roundN(test_round4,2))
|
||||
assert(roundN(test_round1, 2) == roundN(test_round1, 2))
|
||||
assert(roundN(test_round1, 2) == roundN(test_round2, 2))
|
||||
assert(roundN(test_round1, 2) == roundN(test_round3, 2))
|
||||
assert(roundN(test_round1, 2) ~= roundN(test_round4, 2))
|
||||
|
||||
-- tests for close_enough
|
||||
local test_cb = {-0.35,0,-0.35,0.35,0.8,0.35} --collisionboxes
|
||||
local test_cb_close = {-0.351213,0,-0.35,0.35,0.8,0.351212}
|
||||
local test_cb_diff = {-0.35,0,-1.35,0.35,0.8,0.35}
|
||||
local test_cb = {-0.35, 0, -0.35, 0.35, 0.8, 0.35} --collisionboxes
|
||||
local test_cb_close = {-0.351213, 0, -0.35, 0.35, 0.8, 0.351212}
|
||||
local test_cb_diff = {-0.35, 0, -1.35, 0.35, 0.8, 0.35}
|
||||
|
||||
local test_eh = 1.65 --eye height
|
||||
local test_eh_close = 1.65123123
|
||||
local test_eh_diff = 1.35
|
||||
|
||||
local test_nt = { r = 225, b = 225, a = 225, g = 225 } --nametag
|
||||
local test_nt_diff = { r = 225, b = 225, a = 0, g = 225 }
|
||||
local test_nt = {r = 225, b = 225, a = 225, g = 225} --nametag
|
||||
local test_nt_diff = {r = 225, b = 225, a = 0, g = 225}
|
||||
|
||||
assert(close_enough(test_cb,test_cb_close))
|
||||
assert(not close_enough(test_cb,test_cb_diff))
|
||||
assert(close_enough(test_eh,test_eh_close))
|
||||
assert(not close_enough(test_eh,test_eh_diff))
|
||||
assert(not close_enough(test_nt,test_nt_diff)) --no floats involved here
|
||||
assert(close_enough(test_cb, test_cb_close))
|
||||
assert(not close_enough(test_cb, test_cb_diff))
|
||||
assert(close_enough(test_eh, test_eh_close))
|
||||
assert(not close_enough(test_eh, test_eh_diff))
|
||||
assert(not close_enough(test_nt, test_nt_diff)) --no floats involved here
|
||||
|
||||
--tests for properties_changed
|
||||
local test_properties_set1={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
|
||||
local test_properties_set2={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
|
||||
local test_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)
|
||||
local test_p1, _ = props_changed(test_properties_set1, test_properties_set1)
|
||||
local test_p2, _ = props_changed(test_properties_set1, test_properties_set2)
|
||||
|
||||
assert(not test_p1)
|
||||
assert(test_p2)
|
||||
|
||||
function mcl_util.set_properties(obj,props)
|
||||
local changed,p=props_changed(props,obj:get_properties())
|
||||
function mcl_util.set_properties(obj, props)
|
||||
local changed, p = props_changed(props, obj:get_properties())
|
||||
if changed then
|
||||
obj:set_properties(p)
|
||||
end
|
||||
|
@ -728,3 +734,302 @@ 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
|
||||
|
|
Before Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 267 B |
|
@ -252,10 +252,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
|
||||
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
|
||||
end
|
||||
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)
|
||||
|
@ -263,9 +270,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
|
||||
|
|
|
@ -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,10 +2,9 @@ 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
|
||||
|
||||
-- Localize
|
||||
local S = minetest.get_translator("mcl_mobs")
|
||||
|
@ -17,27 +16,20 @@ local function mcl_log (message)
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
-- Invisibility mod check
|
||||
mcl_mobs.invis = {}
|
||||
|
||||
-- localize math functions
|
||||
local atann = math.atan
|
||||
|
||||
local function atan(x)
|
||||
if not x or x ~= x then
|
||||
return 0
|
||||
else
|
||||
return atann(x)
|
||||
end
|
||||
end
|
||||
|
||||
local remove_far = true
|
||||
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
||||
local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
|
||||
local mobs_debug = minetest.settings:get_bool("mobs_debug", false) -- Shows helpful debug info above each mob
|
||||
local spawn_logging = minetest.settings:get_bool("mcl_logging_mobs_spawn",true)
|
||||
|
||||
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)
|
||||
|
@ -144,8 +136,8 @@ 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 self.base_texture or (def.textures and table.indexof(def.textures, self.base_texture) == -1) then
|
||||
-- compatiblity with old simple mobs textures
|
||||
if type(def.textures[1]) == "string" then
|
||||
def.textures = {def.textures}
|
||||
|
@ -297,150 +289,193 @@ function mob_class:mob_activate(staticdata, def, dtime)
|
|||
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
|
||||
|
||||
|
||||
-- main mob function
|
||||
function mob_class:on_step(dtime)
|
||||
self.lifetimer = self.lifetimer - dtime
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
if self:check_despawn(pos) then return true end
|
||||
|
||||
local d = 0.85
|
||||
if self:check_dying() then d = 0.92 end
|
||||
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
--diffuse object velocity
|
||||
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d})
|
||||
end
|
||||
|
||||
if self:falling(pos) then return end
|
||||
|
||||
self:check_suspend()
|
||||
self:check_water_flow()
|
||||
|
||||
local yaw = 0
|
||||
if self:is_at_water_danger() and self.state ~= "attack" then
|
||||
if math.random(1, 10) <= 6 then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
else
|
||||
if self.move_in_group ~= false then
|
||||
self:check_herd(dtime)
|
||||
if self.state == "stand" then
|
||||
self:do_states_stand()
|
||||
elseif self.state == PATHFINDING then
|
||||
self:check_gowp(dtime)
|
||||
elseif self.state == "walk" then
|
||||
self:do_states_walk()
|
||||
elseif self.state == "runaway" then
|
||||
-- runaway when punched
|
||||
self:do_states_runaway()
|
||||
elseif self.state == "attack" then
|
||||
-- attack routines (explode, dogfight, shoot, dogshoot)
|
||||
if self:do_states_attack(dtime) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self:is_at_cliff_or_danger() then
|
||||
self:set_velocity(0)
|
||||
self.state = "stand"
|
||||
self:set_animation( "stand")
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
yaw = self:set_yaw( yaw + 0.78, 8)
|
||||
end
|
||||
|
||||
if not self.fire_resistant then
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
-- mcl_burning.tick may remove object immediately
|
||||
if not self.object:get_pos() then return end
|
||||
end
|
||||
|
||||
if mobs_debug then self:update_tag() end
|
||||
|
||||
if self.state == "die" then return end
|
||||
|
||||
if self.jump_sound_cooloff > 0 then
|
||||
self.jump_sound_cooloff = self.jump_sound_cooloff - dtime
|
||||
end
|
||||
if self.opinion_sound_cooloff > 0 then
|
||||
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
|
||||
end
|
||||
|
||||
--Mob following code.
|
||||
self:follow_flop()
|
||||
--set animation speed relitive to velocity
|
||||
self:set_animation_speed()
|
||||
self:check_smooth_rotation(dtime)
|
||||
self:check_head_swivel(dtime)
|
||||
|
||||
self:do_jump()
|
||||
self:set_armor_texture()
|
||||
self:check_runaway_from()
|
||||
|
||||
self:monster_attack()
|
||||
self:npc_attack()
|
||||
self:check_breeding()
|
||||
self:check_aggro(dtime)
|
||||
|
||||
-- run custom function (defined in mob lua file)
|
||||
if self.do_custom then
|
||||
|
||||
-- when false skip going any further
|
||||
if self.do_custom(self, dtime) == false then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- knockback timer
|
||||
local function update_timers (self, dtime)
|
||||
-- knockback timer. set in on_punch
|
||||
if self.pause_timer > 0 then
|
||||
|
||||
self.pause_timer = self.pause_timer - dtime
|
||||
|
||||
return
|
||||
return true
|
||||
end
|
||||
|
||||
-- attack timer
|
||||
-- attack timer. Not anymore, it seems. Used for also occassionally processing mob step too!
|
||||
self.timer = self.timer + dtime
|
||||
|
||||
if self.state ~= "attack" and self.state ~= PATHFINDING then
|
||||
if self.timer < 1 then
|
||||
return
|
||||
return true
|
||||
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
|
||||
end
|
||||
|
||||
-- mob plays random sound at times
|
||||
if math.random(1, 70) == 1 then
|
||||
self:mob_sound("random", true)
|
||||
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
|
||||
|
||||
local function on_step_work (self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if not pos then return end
|
||||
|
||||
if self:check_despawn(pos, dtime) then return true end
|
||||
if self:outside_limits() then return end
|
||||
|
||||
-- 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
|
||||
|
||||
-- environmental damage timer (every 1 second)
|
||||
self.env_damage_timer = self.env_damage_timer + dtime
|
||||
if self:falling(pos) then return end
|
||||
self:check_suspend()
|
||||
|
||||
if (self.state == "attack" and self.env_damage_timer > 1)
|
||||
or self.state ~= "attack" then
|
||||
self:check_entity_cramming()
|
||||
self.env_damage_timer = 0
|
||||
if not self.fire_resistant then
|
||||
mcl_burning.tick(self.object, dtime, self)
|
||||
if not self.object:get_pos() then return end -- mcl_burning.tick may remove object immediately
|
||||
|
||||
-- check for environmental damage (water, fire, lava etc.)
|
||||
if self:do_env_damage() then
|
||||
return
|
||||
if self:check_for_death("fire", {type = "fire"}) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if self:env_damage (dtime, pos) then return end
|
||||
|
||||
if self.state == "die" then return end
|
||||
-- End: Death/damage processing
|
||||
|
||||
self:check_water_flow()
|
||||
self:env_danger_movement_checks (dtime)
|
||||
|
||||
self:follow_flop() -- Mob following code.
|
||||
|
||||
self:set_animation_speed() -- set animation speed relative to velocity
|
||||
|
||||
self:check_smooth_rotation(dtime)
|
||||
self:check_head_swivel(dtime)
|
||||
|
||||
if self.jump_sound_cooloff > 0 then self.jump_sound_cooloff = self.jump_sound_cooloff - dtime end
|
||||
self:do_jump()
|
||||
|
||||
self:check_runaway_from()
|
||||
self:monster_attack()
|
||||
self:npc_attack()
|
||||
self:check_aggro(dtime)
|
||||
|
||||
if self.do_custom and self.do_custom(self, dtime) == false then return end
|
||||
|
||||
|
||||
-- In certain circumstances, we abandon processing of certain functionality
|
||||
local skip_processing = false
|
||||
if update_timers(self, dtime) then
|
||||
skip_processing = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
if not skip_processing then
|
||||
self:check_breeding()
|
||||
|
||||
self:check_item_pickup()
|
||||
self:set_armor_texture()
|
||||
|
||||
self:check_particlespawners(dtime)
|
||||
|
||||
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
|
||||
|
||||
-- node replace check (cow eats grass etc.)
|
||||
self:replace(pos)
|
||||
if self:do_states(dtime) then return end
|
||||
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 on_step_error_handler = function ()
|
||||
local info = debug.getinfo(1, "SnlufL")
|
||||
|
||||
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
|
||||
minetest.log("action", "--- Bug report start (please provide a few lines before this also for context) ---")
|
||||
minetest.log("action", "Stack trace: ".. tostring(debug.traceback()))
|
||||
minetest.log("action", "Bug info: ".. dump(info))
|
||||
minetest.log("action", "--- Bug report end ---")
|
||||
end
|
||||
|
||||
-- main mob function
|
||||
function mob_class:on_step(dtime)
|
||||
local status, retVal = xpcall(on_step_work, on_step_error_handler, self, dtime)
|
||||
if status then
|
||||
return retVal
|
||||
end
|
||||
end
|
||||
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
timer = timer + dtime
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
@ -179,7 +178,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,18 +190,16 @@ 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 then
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
|
||||
-- horny animal can mate for HORNY_TIME seconds,
|
||||
-- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
|
||||
if self.horny == true
|
||||
and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
|
||||
self.hornytimer = self.hornytimer + 1
|
||||
|
||||
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
self.hornytimer = 0
|
||||
self.horny = false
|
||||
if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
|
||||
self.hornytimer = 0
|
||||
self.horny = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -8,6 +8,18 @@ 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 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
|
||||
|
@ -734,7 +746,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,37 +758,37 @@ 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)
|
||||
local obj = nil
|
||||
-- alert others to the attack
|
||||
local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
|
||||
local obj = nil
|
||||
|
||||
for n = 1, #objs do
|
||||
for n = 1, #objs do
|
||||
|
||||
obj = objs[n]:get_luaentity()
|
||||
obj = objs[n]:get_luaentity()
|
||||
|
||||
if obj then
|
||||
-- only alert members of same mob or friends
|
||||
if obj.group_attack
|
||||
and obj.state ~= "attack"
|
||||
and obj.owner ~= name then
|
||||
if obj.name == self.name then
|
||||
obj:do_attack(hitter)
|
||||
elseif type(obj.group_attack) == "table" then
|
||||
for i=1, #obj.group_attack do
|
||||
if obj.name == obj.group_attack[i] then
|
||||
obj._aggro = true
|
||||
obj:do_attack(hitter)
|
||||
break
|
||||
end
|
||||
if obj then
|
||||
-- only alert members of same mob or friends
|
||||
if obj.group_attack
|
||||
and obj.state ~= "attack"
|
||||
and obj.owner ~= name then
|
||||
if obj.name == self.name then
|
||||
obj:do_attack(hitter)
|
||||
elseif type(obj.group_attack) == "table" then
|
||||
for i=1, #obj.group_attack do
|
||||
if obj.group_attack[i] == self.name then
|
||||
obj._aggro = true
|
||||
obj:do_attack(hitter)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- have owned mobs attack player threat
|
||||
if obj.owner == name and obj.owner_loyal then
|
||||
obj:do_attack(self.object)
|
||||
end
|
||||
-- have owned mobs attack player threat
|
||||
if obj.owner == name and obj.owner_loyal then
|
||||
obj:do_attack(self.object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -794,3 +806,397 @@ function mob_class:check_aggro(dtime)
|
|||
end
|
||||
self._check_aggro_timer = self._check_aggro_timer + dtime
|
||||
end
|
||||
|
||||
function mob_class:do_states_attack (dtime)
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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")
|
||||
|
@ -8,6 +8,15 @@ local player_transfer_distance = tonumber(minetest.settings:get("player_transfer
|
|||
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 +125,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,
|
||||
|
@ -279,30 +289,62 @@ 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)
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do
|
||||
if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then
|
||||
if not self._locked_object then
|
||||
if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then
|
||||
self._locked_object = obj
|
||||
end
|
||||
else
|
||||
if math.random(10000/self.curiosity) == 1 then
|
||||
self._locked_object = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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 or self.following then
|
||||
self._locked_object = self.attack or 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 math.random(1, 30) 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 = 20/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
|
||||
|
||||
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)
|
||||
|
||||
local final_rotation = vector.new(0,0,0)
|
||||
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
|
||||
|
@ -317,6 +359,7 @@ function mob_class:check_head_swivel(dtime)
|
|||
if self.object:get_attach() then
|
||||
self_rot = self.object:get_attach():get_rotation()
|
||||
end
|
||||
|
||||
if self.rot then
|
||||
local player_pos = self._locked_object:get_pos()
|
||||
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
|
||||
|
@ -345,12 +388,14 @@ function mob_class:check_head_swivel(dtime)
|
|||
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 +411,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
|
||||
|
|
|
@ -503,6 +503,8 @@ function mcl_mobs.register_egg(mob, desc, background_color, overlay_color, addeg
|
|||
|
||||
local name = placer:get_player_name()
|
||||
local privs = minetest.get_player_privs(name)
|
||||
local dim = mcl_worlds.pos_to_dimension(placer:get_pos())
|
||||
local mob_light_lvl = {mcl_mobs:mob_light_lvl(itemstack:get_name(),dim)}
|
||||
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 +514,7 @@ 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())
|
||||
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
|
||||
|
|
|
@ -4,7 +4,6 @@ local DEFAULT_FALL_SPEED = -9.81*1.5
|
|||
local FLOP_HEIGHT = 6
|
||||
local FLOP_HOR_SPEED = 1.5
|
||||
local PATHFINDING = "gowp"
|
||||
local enable_pathfinding = true
|
||||
|
||||
local node_ice = "mcl_core:ice"
|
||||
local node_snowblock = "mcl_core:snowblock"
|
||||
|
@ -240,9 +239,15 @@ function mob_class:is_at_water_danger()
|
|||
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(
|
||||
|
@ -269,6 +274,32 @@ function mob_class:is_at_water_danger()
|
|||
return false
|
||||
end
|
||||
|
||||
function mob_class:env_danger_movement_checks(dtime)
|
||||
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
|
||||
-- This code should probably be moved to movement code
|
||||
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
|
||||
end
|
||||
|
||||
-- jump if facing a solid node (not fences or gates)
|
||||
function mob_class:do_jump()
|
||||
if not self.jump
|
||||
|
@ -429,7 +460,7 @@ 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
|
||||
|
@ -769,591 +800,190 @@ 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 lp = nil
|
||||
|
||||
-- is there something I need to avoid?
|
||||
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
|
||||
if lp then
|
||||
-- If mob in or on dangerous block, look for land
|
||||
if (self:is_node_dangerous(self.standing_in) or
|
||||
self:is_node_dangerous(self.standing_on)) or (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)) and (not self.fly) then
|
||||
is_in_danger = true
|
||||
|
||||
-- If mob in or on dangerous block, look for land
|
||||
if is_in_danger then
|
||||
-- Better way to find shore - copied from upstream
|
||||
lp = minetest.find_nodes_in_area_under_air(
|
||||
{x = s.x - 5, y = s.y - 0.5, z = s.z - 5},
|
||||
{x = s.x + 5, y = s.y + 1, z = s.z + 5},
|
||||
{"group:solid"})
|
||||
|
||||
lp = #lp > 0 and lp[math.random(#lp)]
|
||||
|
||||
-- did we find land?
|
||||
if lp 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
|
||||
|
||||
-- look towards land and move in that direction
|
||||
yaw = self:set_yaw( yaw, 6)
|
||||
self:set_velocity(self.walk_velocity)
|
||||
|
||||
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
|
||||
|
||||
-- is there something I need to avoid?
|
||||
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
|
||||
if lp then
|
||||
-- If mob in or on dangerous block, look for land
|
||||
if (self:is_node_dangerous(self.standing_in) or
|
||||
self:is_node_dangerous(self.standing_on)) or (self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)) and (not self.fly) then
|
||||
is_in_danger = true
|
||||
|
||||
-- If mob in or on dangerous block, look for land
|
||||
if is_in_danger then
|
||||
-- Better way to find shore - copied from upstream
|
||||
lp = minetest.find_nodes_in_area_under_air(
|
||||
{x = s.x - 5, y = s.y - 0.5, z = s.z - 5},
|
||||
{x = s.x + 5, y = s.y + 1, z = s.z + 5},
|
||||
{"group:solid"})
|
||||
|
||||
lp = #lp > 0 and lp[math.random(#lp)]
|
||||
|
||||
-- did we find land?
|
||||
if lp 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
|
||||
|
||||
-- look towards land and move in that direction
|
||||
yaw = self:set_yaw( yaw, 6)
|
||||
self:set_velocity(self.walk_velocity)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- A danger is near but mob is not inside
|
||||
else
|
||||
else
|
||||
|
||||
-- Randomly turn
|
||||
if math.random(1, 100) <= 30 then
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
-- Randomly turn
|
||||
if math.random(1, 100) <= 30 then
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
end
|
||||
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
|
||||
-- otherwise randomly turn
|
||||
elseif math.random(1, 100) <= 30 then
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
elseif math.random(1, 100) <= 30 then
|
||||
yaw = yaw + math.random(-0.5, 0.5)
|
||||
yaw = self:set_yaw( yaw, 8)
|
||||
end
|
||||
|
||||
-- stand for great fall or danger or fence in front
|
||||
local cliff_or_danger = false
|
||||
if is_in_danger then
|
||||
cliff_or_danger = self:is_at_cliff_or_danger()
|
||||
end
|
||||
if self.facing_fence == true
|
||||
or cliff_or_danger
|
||||
or math.random(1, 100) <= 30 then
|
||||
-- stand for great fall or danger or fence in front
|
||||
local cliff_or_danger = false
|
||||
if is_in_danger then
|
||||
cliff_or_danger = self:is_at_cliff_or_danger()
|
||||
end
|
||||
if self.facing_fence == true
|
||||
or cliff_or_danger
|
||||
or math.random(1, 100) <= 30 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)
|
||||
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)
|
||||
else
|
||||
|
||||
self:set_velocity(self.walk_velocity)
|
||||
|
||||
if self:flight_check()
|
||||
and self.animation
|
||||
and self.animation.fly_start
|
||||
and self.animation.fly_end then
|
||||
self:set_animation( "fly")
|
||||
else
|
||||
|
||||
self:set_velocity(self.walk_velocity)
|
||||
|
||||
if self:flight_check()
|
||||
and self.animation
|
||||
and self.animation.fly_start
|
||||
and self.animation.fly_end then
|
||||
self:set_animation( "fly")
|
||||
else
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
|
||||
-- runaway when punched
|
||||
elseif self.state == "runaway" then
|
||||
|
||||
self.runaway_timer = self.runaway_timer + 1
|
||||
|
||||
-- stop after 5 seconds or when at cliff
|
||||
if self.runaway_timer > 5
|
||||
or self:is_at_cliff_or_danger() then
|
||||
self.runaway_timer = 0
|
||||
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)
|
||||
else
|
||||
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
|
||||
|
||||
self:set_animation( "walk")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mob_class:do_states_stand()
|
||||
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 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
|
||||
|
||||
function mob_class:do_states_runaway()
|
||||
local yaw = self.object:get_yaw() or 0
|
||||
|
||||
self.runaway_timer = self.runaway_timer + 1
|
||||
|
||||
-- stop after 5 seconds or when at cliff
|
||||
if self.runaway_timer > 5
|
||||
or self:is_at_cliff_or_danger() then
|
||||
self.runaway_timer = 0
|
||||
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)
|
||||
else
|
||||
self:set_velocity( self.run_velocity)
|
||||
self:set_animation( "run")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function mob_class:check_smooth_rotation(dtime)
|
||||
-- smooth rotation by ThomasMonroe314
|
||||
if self._turn_to then
|
||||
|
|
|
@ -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")
|
||||
if cur_door_pos then
|
||||
mcl_log("Found a door near: " .. minetest.pos_to_string(cur_door_pos))
|
||||
|
||||
-- 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)
|
||||
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("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 n = minetest.get_node(pos_closest_to_door)
|
||||
|
||||
if n.name == "air" 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))
|
||||
mcl_log("Opposite is: ".. minetest.pos_to_string(other_side_of_door))
|
||||
|
||||
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)
|
||||
|
||||
local wp_otherside_door_to_target = minetest.find_path(other_side_of_door,t,150,1,4)
|
||||
if wp_otherside_door_to_target and #wp_otherside_door_to_target > 0 then
|
||||
table.insert(wp, cur_door_pos)
|
||||
append_paths (wp, wp_otherside_door_to_target)
|
||||
enriched_path = generate_enriched_path(wp, pos_closest_to_door, other_side_of_door, cur_door_pos)
|
||||
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.")
|
||||
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("Block is not air, it is: ".. n.name)
|
||||
mcl_log("Cannot path to this air block next to door.")
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
mcl_log("No door found")
|
||||
end
|
||||
else
|
||||
mcl_log("We have a direct route")
|
||||
mcl_log("No door found")
|
||||
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,6 +183,19 @@ function mob_class:collision()
|
|||
return({x,z})
|
||||
end
|
||||
|
||||
function mob_class:check_death_and_slow_mob()
|
||||
local d = 0.85
|
||||
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)
|
||||
local c_x, c_y = 0, 0
|
||||
|
@ -298,6 +311,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
|
||||
|
@ -449,6 +464,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)
|
||||
|
@ -509,17 +532,16 @@ function mob_class:check_for_death(cause, cmi_cause)
|
|||
|
||||
self:set_velocity(0)
|
||||
local acc = self.object:get_acceleration()
|
||||
acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0
|
||||
self.object:set_acceleration(acc)
|
||||
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 +557,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 +565,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
|
||||
|
@ -632,11 +654,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 +671,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 +681,10 @@ function mob_class:do_env_damage()
|
|||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- lava damage
|
||||
elseif self.lava_damage > 0
|
||||
and (nodef.groups.lava) then
|
||||
|
||||
elseif self.lava_damage > 0 and (nodef.groups.lava) then
|
||||
-- lava damage
|
||||
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 +693,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 +715,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 +729,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 +737,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 +790,26 @@ 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:env_damage (dtime, pos)
|
||||
-- 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,33 +875,32 @@ function mob_class:falling(pos)
|
|||
|
||||
-- floating in water (or falling)
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
if v.y > 0 then
|
||||
-- apply gravity when moving up
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = DEFAULT_FALL_SPEED,
|
||||
z = 0
|
||||
})
|
||||
|
||||
if v.y > 0 then
|
||||
|
||||
-- apply gravity when moving up
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = DEFAULT_FALL_SPEED,
|
||||
z = 0
|
||||
})
|
||||
|
||||
elseif v.y <= 0 and v.y > self.fall_speed then
|
||||
|
||||
-- fall downwards at set speed
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = self.fall_speed,
|
||||
z = 0
|
||||
})
|
||||
else
|
||||
-- stop accelerating once max fall speed hit
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
elseif v.y <= 0 and v.y > self.fall_speed then
|
||||
-- fall downwards at set speed
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = self.fall_speed,
|
||||
z = 0
|
||||
})
|
||||
else
|
||||
-- stop accelerating once max fall speed hit
|
||||
self.object:set_acceleration({x = 0, y = 0, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
local acc = self.object:get_acceleration()
|
||||
|
||||
if minetest.registered_nodes[node_ok(pos).name].groups.lava then
|
||||
|
||||
if self.floats_on_lava == 1 then
|
||||
|
||||
if acc and self.floats_on_lava == 1 then
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
|
||||
|
@ -878,9 +911,7 @@ function mob_class:falling(pos)
|
|||
|
||||
-- in water then float up
|
||||
if minetest.registered_nodes[node_ok(pos).name].groups.water then
|
||||
|
||||
if self.floats == 1 then
|
||||
|
||||
if acc and self.floats == 1 then
|
||||
self.object:set_acceleration({
|
||||
x = 0,
|
||||
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
|
||||
|
@ -888,10 +919,8 @@ function mob_class:falling(pos)
|
|||
})
|
||||
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 +981,31 @@ end
|
|||
function mob_class:check_dying()
|
||||
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
|
||||
local rot = self.object:get_rotation()
|
||||
rot.z = ((math.pi/2-rot.z)*.2)+rot.z
|
||||
self.object:set_rotation(rot)
|
||||
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
|
||||
local pos = self.object:get_pos()
|
||||
local pos = self.object:get_pos()
|
||||
|
||||
if pos and not self: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)
|
||||
if acc.y > 0 or node_under ~= "air" then
|
||||
self.object:set_acceleration(vector.new(0,0,0))
|
||||
self.object:set_velocity(vector.new(0,0,0))
|
||||
end
|
||||
if acc.y == 0 and node_under == "air" then
|
||||
self:falling(pos)
|
||||
|
||||
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))
|
||||
end
|
||||
if acc.y == 0 and node_under == "air" then
|
||||
self:falling(pos)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
|
|
@ -24,25 +24,60 @@ 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)
|
||||
if LOGGING_ON then
|
||||
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 = 5
|
||||
|
||||
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 13,
|
||||
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 = tonumber(minetest.settings:get("mcl_mob_cap_water")) or 8,
|
||||
water_ambient = tonumber(minetest.settings:get("mcl_mob_cap_water_ambient")) or 20, --currently unused
|
||||
player = tonumber(minetest.settings:get("mcl_mob_cap_player")) or 75,
|
||||
total = tonumber(minetest.settings:get("mcl_mob_cap_total")) or 500,
|
||||
}
|
||||
|
||||
local peaceful_percentage_spawned = tonumber(minetest.settings:get("mcl_mob_peaceful_percentage_spawned")) or 35
|
||||
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 +204,8 @@ local list_of_all_biomes = {
|
|||
"MushroomIslandShore",
|
||||
"JungleM_shore",
|
||||
"Jungle_shore",
|
||||
"BambooJungleM_shore",
|
||||
"BambooJungle_shore",
|
||||
"MangroveSwamp_shore",
|
||||
|
||||
-- dimension biome:
|
||||
|
@ -216,6 +253,10 @@ local list_of_all_biomes = {
|
|||
"JungleEdge",
|
||||
"SavannaM",
|
||||
"MangroveSwamp",
|
||||
"BambooJungle",
|
||||
"BambooJungleEdge",
|
||||
"BambooJungleEdgeM",
|
||||
"BambooJungleM",
|
||||
}
|
||||
|
||||
-- count how many mobs are in an area
|
||||
|
@ -244,6 +285,60 @@ local function count_mobs_total(mob_type)
|
|||
return num
|
||||
end
|
||||
|
||||
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 _,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
|
||||
--mcl_log("num: ".. num)
|
||||
return mobs_found_close, mobs_found_wide, num
|
||||
end
|
||||
|
||||
local function count_mobs_total_cap(mob_type)
|
||||
local num = 0
|
||||
for _,l in pairs(minetest.luaentities) do
|
||||
|
@ -256,6 +351,32 @@ local function count_mobs_total_cap(mob_type)
|
|||
return num
|
||||
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 +413,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,6 +480,47 @@ function mcl_mobs:spawn_setup(def)
|
|||
summary_chance = summary_chance + chance
|
||||
end
|
||||
|
||||
function mcl_mobs:mob_light_lvl(mob_name, dimension)
|
||||
local spawn_dictionary_consolidated = {}
|
||||
--see if the mob exists in the nonspawn dictionary, if so then return light values
|
||||
if non_spawn_dictionary[mob_name] ~= nil then
|
||||
local mob_dimension = non_spawn_dictionary[mob_name][dimension]
|
||||
if mob_name ~= nil then
|
||||
return mob_dimension.min_light,mob_dimension.max_light
|
||||
else
|
||||
return non_spawn_dictionary[mob_name]["overworld"].min_light, non_spawn_dictionary[mob_name]["overworld"].max_light
|
||||
end
|
||||
|
||||
--if the mob doesn't exist in non_spawn, check spawn_dictonary
|
||||
else
|
||||
for i,v in pairs(spawn_dictionary) do
|
||||
if spawn_dictionary[spawn_dictionary[i].name] == nil then
|
||||
spawn_dictionary_consolidated[spawn_dictionary[i].name] = {}
|
||||
end
|
||||
spawn_dictionary_consolidated[spawn_dictionary[i].name][dimension] = {
|
||||
["min_light"] = spawn_dictionary[i].min_light,
|
||||
["max_light"] = spawn_dictionary[i].max_light
|
||||
}
|
||||
end
|
||||
mob_dimension = spawn_dictionary_consolidated[mob_name][dimension]
|
||||
mob_dimension_default = spawn_dictionary_consolidated[mob_name]["overworld"]
|
||||
if spawn_dictionary_consolidated[mob_name] == mob_name and mob_dimension ~= nil then
|
||||
return mob_dimension.min_light, mob_dimension.max_light
|
||||
else
|
||||
return mob_dimension_default.min_light, mob_dimension_default.max_light
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
-- Do mobs spawn at all?
|
||||
|
@ -398,18 +562,23 @@ function mcl_mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_
|
|||
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,7 +629,9 @@ local function has_room(self,pos)
|
|||
return true
|
||||
end
|
||||
|
||||
local function spawn_check(pos,spawn_def,ignore_caps)
|
||||
|
||||
|
||||
local function spawn_check(pos, spawn_def)
|
||||
if not spawn_def then return end
|
||||
dbg_spawn_attempts = dbg_spawn_attempts + 1
|
||||
local dimension = mcl_worlds.pos_to_dimension(pos)
|
||||
|
@ -483,17 +654,8 @@ 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
|
||||
and pos.y <= spawn_def.max_height
|
||||
and spawn_def.dimension == dimension
|
||||
|
@ -529,8 +691,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 +699,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 +790,116 @@ 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
|
||||
-- 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)
|
||||
|
||||
-- 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 = type_cap - mob_total_wide
|
||||
if cap_space_wide < 1 then
|
||||
cap_space_wide = 0
|
||||
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 = close_zone_cap - mob_total_close
|
||||
if cap_space_close < 1 then
|
||||
cap_space_close = 0
|
||||
end
|
||||
|
||||
--mcl_log("spawn_class: " .. spawn_class)
|
||||
|
||||
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_wide, cap_space_close
|
||||
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)
|
||||
|
||||
if math.abs(goal_pos.x) <= SPAWN_MAPGEN_LIMIT and math.abs(pos.y) <= SPAWN_MAPGEN_LIMIT and math.abs(goal_pos.z) <= SPAWN_MAPGEN_LIMIT then
|
||||
local spawning_position_list = find_nodes_in_area_under_air(
|
||||
{x = goal_pos.x, y = y_min, z = goal_pos.z},
|
||||
{x = goal_pos.x, y = y_max, z = goal_pos.z},
|
||||
{"group:solid", "group:water", "group:lava"}
|
||||
)
|
||||
if #spawning_position_list > 0 then
|
||||
mcl_log("Spawning positions available: " .. minetest.pos_to_string(goal_pos))
|
||||
spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
else
|
||||
mcl_log("Spawning position isn't good. Do not spawn: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
else
|
||||
mcl_log("Pos outside mapgen limits: " .. minetest.pos_to_string(goal_pos))
|
||||
end
|
||||
|
||||
|
||||
i = i + 1
|
||||
if i >= max_loops then
|
||||
mcl_log("Cancel finding spawn positions at: " .. max_loops)
|
||||
break
|
||||
end
|
||||
until spawning_position
|
||||
return spawning_position
|
||||
end
|
||||
|
||||
local function spawn_a_mob(pos)
|
||||
--create a disconnected clone of the spawn dictionary, prevents memory leak
|
||||
local mob_library_worker_table = table_copy(spawn_dictionary)
|
||||
local goal_pos = get_next_mob_spawn_pos(pos)
|
||||
|
||||
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
|
||||
local spawning_position_list = find_nodes_in_area_under_air(
|
||||
{x = goal_pos.x, y = y_min, z = goal_pos.z},
|
||||
{x = goal_pos.x, y = y_max, z = goal_pos.z},
|
||||
{"group:solid", "group:water", "group:lava"}
|
||||
)
|
||||
if #spawning_position_list <= 0 then return end
|
||||
local spawning_position = spawning_position_list[math_random(1, #spawning_position_list)]
|
||||
|
||||
perlin_noise = perlin_noise or minetest_get_perlin(noise_params)
|
||||
local noise = perlin_noise:get_3d(spawning_position)
|
||||
local current_summary_chance = summary_chance
|
||||
|
||||
table.shuffle(mob_library_worker_table)
|
||||
|
||||
while #mob_library_worker_table > 0 do
|
||||
local mob_chance_offset = (math_round(noise * current_summary_chance + 12345) % current_summary_chance) + 1
|
||||
local mob_index = 1
|
||||
|
@ -657,39 +910,95 @@ 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
|
||||
if mob_def.type_of_spawning == "water" then
|
||||
spawning_position = get_water_spawn(spawning_position)
|
||||
if not spawning_position then
|
||||
minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos)))
|
||||
|
||||
local mob_def_ent = minetest.registered_entities[mob_def.name]
|
||||
--local mob_type = mob_def_ent.type
|
||||
local mob_spawn_class = mob_def_ent.spawn_class
|
||||
|
||||
--mcl_log("mob_spawn_class: " .. mob_spawn_class)
|
||||
|
||||
local cap_space_wide, cap_space_close = mob_cap_space (spawning_position, mob_spawn_class, mob_counts_close, mob_counts_wide)
|
||||
|
||||
|
||||
if cap_space_close > 0 and cap_space_wide > 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
|
||||
-- or not hostile
|
||||
--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
|
||||
minetest.log("warning","[mcl_mobs] no water spawn for mob "..mob_def.name.." found at "..minetest.pos_to_string(vector.round(pos)))
|
||||
return
|
||||
end
|
||||
end
|
||||
if 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
|
||||
end
|
||||
if minetest.registered_entities[mob_def.name].can_spawn and not minetest.registered_entities[mob_def.name].can_spawn(spawning_position) then
|
||||
minetest.log("warning","[mcl_mobs] mob "..mob_def.name.." refused to spawn at "..minetest.pos_to_string(vector.round(spawning_position)))
|
||||
return
|
||||
end
|
||||
--everything is correct, spawn mob
|
||||
local object
|
||||
if spawn_in_group and ( mob_type ~= "monster" or math.random(5) == 1 ) then
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] A group of mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at " .. minetest.pos_to_string(spawning_position, 1))
|
||||
end
|
||||
object = spawn_group(spawning_position,mob_def,{minetest.get_node(vector.offset(spawning_position,0,-1,0)).name},spawn_in_group,spawn_in_group_min)
|
||||
|
||||
else
|
||||
if logging then
|
||||
minetest.log("action", "[mcl_mobs] Mob " .. mob_def.name .. " spawns on " ..minetest.get_node(vector.offset(spawning_position,0,-1,0)).name .." at ".. minetest.pos_to_string(spawning_position, 1))
|
||||
--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)
|
||||
|
||||
if amount_to_spawn > cap_space_wide then
|
||||
mcl_log("Spawning quantity: " .. amount_to_spawn)
|
||||
mcl_log("Throttle amount to cap space: " .. cap_space_wide)
|
||||
amount_to_spawn = cap_space_wide
|
||||
end
|
||||
|
||||
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
|
||||
spawned = mcl_mobs.spawn(spawning_position, mob_def.name)
|
||||
end
|
||||
object = mcl_mobs.spawn(spawning_position, mob_def.name)
|
||||
|
||||
if spawned then
|
||||
--mcl_log("We have spawned")
|
||||
mob_counts_close, mob_counts_wide, total_mobs = count_mobs_all("type", 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,28 +1010,32 @@ 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()
|
||||
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)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function mob_class:check_despawn(pos)
|
||||
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
|
||||
|
@ -746,14 +1059,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 |
Before Width: | Height: | Size: 229 B |
|
@ -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)
|
||||
|
|
|
@ -12,13 +12,13 @@ mcl_mobs.register_mob("mobs_mc:chicken", {
|
|||
description = S("Chicken"),
|
||||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
|
||||
passive = true,
|
||||
runaway = true,
|
||||
hp_min = 4,
|
||||
hp_max = 4,
|
||||
xp_min = 1,
|
||||
xp_max = 3,
|
||||
collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.69, 0.2},
|
||||
runaway = true,
|
||||
floats = 1,
|
||||
head_swivel = "head.control",
|
||||
bone_eye_height = 4,
|
||||
|
|
|
@ -40,14 +40,14 @@ local cod = {
|
|||
xp_max = 3,
|
||||
armor = 100,
|
||||
rotate = 180,
|
||||
spawn_in_group_min = 3,
|
||||
spawn_in_group = 8,
|
||||
spawn_in_group_min = 2, -- was 3
|
||||
spawn_in_group = 4, -- was 8 nerfed until we can cap them properly locally. this is a group size, not a per spawn attempt
|
||||
tilt_swim = true,
|
||||
collisionbox = {-0.3, 0.0, -0.3, 0.3, 0.79, 0.3},
|
||||
visual = "mesh",
|
||||
mesh = "extra_mobs_cod.b3d",
|
||||
textures = {
|
||||
{"extra_mobs_cod.png"}
|
||||
{"mobs_mc_cod.png"}
|
||||
},
|
||||
sounds = {
|
||||
},
|
||||
|
|
|
@ -7,13 +7,14 @@ local cow_def = {
|
|||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
passive = true,
|
||||
runaway = true,
|
||||
hp_min = 10,
|
||||
hp_max = 10,
|
||||
xp_min = 1,
|
||||
xp_max = 3,
|
||||
collisionbox = {-0.45, -0.01, -0.45, 0.45, 1.39, 0.45},
|
||||
spawn_in_group = 4,
|
||||
spawn_in_group_min = 3,
|
||||
spawn_in_group_min = 2,
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_cow.b3d",
|
||||
textures = { {
|
||||
|
@ -40,7 +41,6 @@ local cow_def = {
|
|||
max = 2,
|
||||
looting = "common",},
|
||||
},
|
||||
runaway = true,
|
||||
sounds = {
|
||||
random = "mobs_mc_cow",
|
||||
damage = "mobs_mc_cow_hurt",
|
||||
|
@ -93,8 +93,8 @@ mcl_mobs.register_mob("mobs_mc:cow", cow_def)
|
|||
-- Mooshroom
|
||||
local mooshroom_def = table.copy(cow_def)
|
||||
mooshroom_def.description = S("Mooshroom")
|
||||
mooshroom_def.spawn_in_group_min = 4
|
||||
mooshroom_def.spawn_in_group = 8
|
||||
mooshroom_def.spawn_in_group_min = 2
|
||||
mooshroom_def.spawn_in_group = 4
|
||||
mooshroom_def.textures = { {"mobs_mc_mooshroom.png", "mobs_mc_mushroom_red.png"}, {"mobs_mc_mooshroom_brown.png", "mobs_mc_mushroom_brown.png" } }
|
||||
mooshroom_def.on_rightclick = function(self, clicker)
|
||||
if self:feed_tame(clicker, 1, true, false) then return end
|
||||
|
|
|
@ -42,8 +42,8 @@ local dolphin = {
|
|||
walk_chance = 100,
|
||||
breath_max = 120,
|
||||
rotate = 180,
|
||||
spawn_in_group_min = 3,
|
||||
spawn_in_group = 5,
|
||||
spawn_in_group_min = 2, -- was 3
|
||||
spawn_in_group = 4, -- was 4. nerfed until water has own cap, and it represents max pack size rather than per spawn attempt
|
||||
tilt_swim = true,
|
||||
collisionbox = {-0.3, 0.0, -0.3, 0.3, 0.79, 0.3},
|
||||
visual = "mesh",
|
||||
|
|
|
@ -174,3 +174,4 @@ mcl_mobs.register_egg("mobs_mc:enderdragon", S("Ender Dragon"), "#252525", "#b31
|
|||
|
||||
|
||||
mcl_wip.register_wip_item("mobs_mc:enderdragon")
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:enderdragon","overworld",0,minetest.LIGHT_MAX+1)
|
||||
|
|
|
@ -259,6 +259,7 @@ mcl_mobs.register_mob("mobs_mc:enderman", {
|
|||
description = S("Enderman"),
|
||||
type = "monster",
|
||||
spawn_class = "passive",
|
||||
can_despawn = true,
|
||||
passive = true,
|
||||
pathfinding = 1,
|
||||
hp_min = 40,
|
||||
|
@ -442,6 +443,7 @@ mcl_mobs.register_mob("mobs_mc:enderman", {
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- TAKE AND PLACE STUFF BEHAVIOUR BELOW.
|
||||
if not mobs_griefing then
|
||||
return
|
||||
|
@ -469,6 +471,7 @@ mcl_mobs.register_mob("mobs_mc:enderman", {
|
|||
local dug = minetest.get_node_or_nil(take_pos)
|
||||
if dug and dug.name == "air" then
|
||||
self._taken_node = node.name
|
||||
self.can_despawn = false
|
||||
local def = minetest.registered_nodes[self._taken_node]
|
||||
-- Update animation and texture accordingly (adds visibly carried block)
|
||||
local block_type
|
||||
|
@ -519,6 +522,7 @@ mcl_mobs.register_mob("mobs_mc:enderman", {
|
|||
if success then
|
||||
local def = minetest.registered_nodes[self._taken_node]
|
||||
-- Update animation accordingly (removes visible block)
|
||||
self.can_despawn = true
|
||||
self.animation = select_enderman_animation("normal")
|
||||
self:set_animation(self.animation.current)
|
||||
if def.sounds and def.sounds.place then
|
||||
|
|
|
@ -39,3 +39,4 @@ mcl_mobs.register_mob("mobs_mc:endermite", {
|
|||
})
|
||||
|
||||
mcl_mobs.register_egg("mobs_mc:endermite", S("Endermite"), "#161616", "#6d6d6d", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:endermite","overworld",0,7)
|
||||
|
|
|
@ -139,6 +139,6 @@ mcl_mobs.register_arrow("mobs_mc:fireball", {
|
|||
|
||||
|
||||
|
||||
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:ghast","overworld","0","7")
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:ghast", S("Ghast"), "#f9f9f9", "#bcbcbc", 0)
|
||||
|
|
|
@ -25,7 +25,7 @@ local base_psdef = {
|
|||
local psdefs = {}
|
||||
for i=1,4 do
|
||||
local p = table.copy(base_psdef)
|
||||
p.texture = "extra_mobs_glow_squid_glint"..i..".png"
|
||||
p.texture = "mobs_mc_glow_squid_glint"..i..".png"
|
||||
table.insert(psdefs,p)
|
||||
end
|
||||
|
||||
|
@ -47,7 +47,7 @@ mcl_mobs.register_mob("mobs_mc:glow_squid", {
|
|||
visual = "mesh",
|
||||
mesh = "extra_mobs_glow_squid.b3d",
|
||||
textures = {
|
||||
{ "extra_mobs_glow_squid.png" }
|
||||
{ "mobs_mc_glow_squid.png" }
|
||||
},
|
||||
sounds = {
|
||||
damage = { name = "mobs_mc_squid_hurt", gain = 0.3 },
|
||||
|
|
|
@ -131,5 +131,7 @@ minetest.LIGHT_MAX+1,
|
|||
mcl_vars.mg_nether_min,
|
||||
mcl_vars.mg_nether_max)
|
||||
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:hoglin","overworld",0,7)
|
||||
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:hoglin", S("Hoglin"), "#85682e", "#2b2140", 0)
|
||||
|
|
|
@ -125,7 +125,7 @@ local horse = {
|
|||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
spawn_in_group_min = 2,
|
||||
spawn_in_group = 6,
|
||||
spawn_in_group = 4, -- was 6. nerfed until group size is a cap rather than per spawn cycle
|
||||
visual = "mesh",
|
||||
mesh = "mobs_mc_horse.b3d",
|
||||
visual_size = {x=3.0, y=3.0},
|
||||
|
|
|
@ -15,7 +15,7 @@ mcl_mobs.register_mob("mobs_mc:iron_golem", {
|
|||
description = S("Iron Golem"),
|
||||
type = "npc",
|
||||
spawn_class = "passive",
|
||||
passive = true,
|
||||
passive = false,
|
||||
hp_min = 100,
|
||||
hp_max = 100,
|
||||
breath_max = -1,
|
||||
|
@ -42,7 +42,7 @@ mcl_mobs.register_mob("mobs_mc:iron_golem", {
|
|||
damage = 14,
|
||||
knock_back = false,
|
||||
reach = 3,
|
||||
group_attack = true,
|
||||
group_attack = { "mobs_mc:villager" },
|
||||
attacks_monsters = true,
|
||||
attack_type = "dogfight",
|
||||
_got_poppy = false,
|
||||
|
@ -206,3 +206,4 @@ function mobs_mc.check_iron_golem_summon(pos)
|
|||
end
|
||||
end
|
||||
end
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:iron_golem","overworld",0,minetest.LIGHT_MAX+1)
|
||||
|
|
|
@ -56,8 +56,8 @@ mcl_mobs.register_mob("mobs_mc:llama", {
|
|||
shoot_interval = 5.5,
|
||||
arrow = "mobs_mc:llamaspit",
|
||||
shoot_offset = 1, --3.5 *would* be a good value visually but it somehow messes with the projectiles trajectory
|
||||
spawn_in_group_min = 4,
|
||||
spawn_in_group = 6,
|
||||
spawn_in_group_min = 2, -- was 4
|
||||
spawn_in_group = 4, -- was 6 nerfed until we can cap them properly locally. this is a group size, not a per spawn attempt
|
||||
|
||||
head_swivel = "head.control",
|
||||
bone_eye_height = 11,
|
||||
|
|
|
@ -164,7 +164,7 @@ cat.on_spawn = function(self)
|
|||
if not self._texture then
|
||||
self._texture = cat.textures[math.random(#cat.textures)]
|
||||
end
|
||||
self.object:set_properties({textures = {self._texture}})
|
||||
self.object:set_properties({textures = self._texture})
|
||||
end
|
||||
|
||||
mcl_mobs.register_mob("mobs_mc:cat", cat)
|
||||
|
|
|
@ -6,6 +6,7 @@ mcl_mobs.register_mob("mobs_mc:pig", {
|
|||
description = S("Pig"),
|
||||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
passive = true,
|
||||
runaway = true,
|
||||
hp_min = 10,
|
||||
hp_max = 10,
|
||||
|
|
|
@ -283,6 +283,7 @@ piglin_brute.group_attack = { "mobs_mc:piglin", "mobs_mc:piglin_brute" }
|
|||
mcl_mobs.register_mob("mobs_mc:piglin_brute", piglin_brute)
|
||||
|
||||
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:piglin","overworld",0,7)
|
||||
-- Regular spawning in the Nether
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:piglin",
|
||||
|
@ -299,7 +300,7 @@ minetest.LIGHT_MAX+1,
|
|||
3,
|
||||
mcl_vars.mg_lava_nether_max,
|
||||
mcl_vars.mg_nether_max)
|
||||
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:sword_piglin","overworld",0,7)
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:sword_piglin",
|
||||
"nether",
|
||||
|
|
|
@ -122,3 +122,4 @@ pillager = {
|
|||
|
||||
mcl_mobs.register_mob("mobs_mc:pillager", pillager)
|
||||
mcl_mobs.register_egg("mobs_mc:pillager", S("Pillager"), "#532f36", "#959b9b", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:pillager","overworld",0,7)
|
||||
|
|
|
@ -25,7 +25,7 @@ local salmon = {
|
|||
visual = "mesh",
|
||||
mesh = "extra_mobs_salmon.b3d",
|
||||
textures = {
|
||||
{"extra_mobs_salmon.png"}
|
||||
{"mobs_mc_salmon.png"}
|
||||
},
|
||||
sounds = {
|
||||
},
|
||||
|
|
|
@ -56,6 +56,7 @@ mcl_mobs.register_mob("mobs_mc:sheep", {
|
|||
description = S("Sheep"),
|
||||
type = "animal",
|
||||
spawn_class = "passive",
|
||||
passive = true,
|
||||
hp_min = 8,
|
||||
hp_max = 8,
|
||||
xp_min = 1,
|
||||
|
|
|
@ -178,7 +178,7 @@ mcl_mobs.register_arrow("mobs_mc:shulkerbullet", {
|
|||
|
||||
|
||||
mcl_mobs.register_egg("mobs_mc:shulker", S("Shulker"), "#946694", "#4d3852", 0)
|
||||
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:shulker","overworld",0,minetest.LIGHT_MAX+1)
|
||||
--[[
|
||||
mcl_mobs:spawn_specific(
|
||||
"mobs_mc:shulker",
|
||||
|
|
|
@ -56,3 +56,4 @@ mcl_mobs.register_mob("mobs_mc:silverfish", {
|
|||
})
|
||||
|
||||
mcl_mobs.register_egg("mobs_mc:silverfish", S("Silverfish"), "#6d6d6d", "#313131", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:silverfish","overworld",0,11)
|
||||
|
|
|
@ -118,3 +118,4 @@ mcl_vars.mg_nether_max)
|
|||
--]]
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:witherskeleton", S("Wither Skeleton"), "#141414", "#474d4d", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:witherskeleton","overworld",0,7)
|
||||
|
|
|
@ -278,7 +278,6 @@ swamp_light_max,
|
|||
4,
|
||||
swamp_min,
|
||||
swamp_max)
|
||||
|
||||
-- Magma cube
|
||||
local magma_cube_big = {
|
||||
description = S("Magma Cube"),
|
||||
|
@ -431,6 +430,11 @@ nether_max)
|
|||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:magma_cube_big", S("Magma Cube"), "#350000", "#fcfc00")
|
||||
|
||||
-- non_spawn_specific is typically for mobs who don't spawn in the overworld, or mobs that don't spawn
|
||||
-- naturally. However, slimes are a particular case where they spawn under different conditions in the same
|
||||
-- dimension.
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:slime_big","overworld",0,minetest.LIGHT_MAX+1)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:magma_cube_big","overworld",0, minetest.LIGHT_MAX+1)
|
||||
mcl_mobs.register_egg("mobs_mc:slime_big", S("Slime"), "#52a03e", "#7ebf6d")
|
||||
|
||||
-- FIXME: add spawn eggs for small and tiny slimes and magma cubes
|
||||
|
|
|
@ -197,3 +197,4 @@ end
|
|||
|
||||
-- Spawn egg
|
||||
mcl_mobs.register_egg("mobs_mc:snowman", S("Snow Golem"), "#f2f2f2", "#fd8f47", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:snowman","overworld",0,minetest.LIGHT_MAX+1)
|
||||
|
|
|
@ -246,3 +246,4 @@ mcl_mobs:spawn_setup({
|
|||
|
||||
-- spawn eggs
|
||||
mcl_mobs.register_egg("mobs_mc:strider", S("Strider"), "#000000", "#FF0000", 0)
|
||||
mcl_mobs:non_spawn_specific("mobs_mc:strider","nether",0,minetest.LIGHT_MAX+1)
|
||||
|
|
Before Width: | Height: | Size: 443 B |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 192 B |
Before Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 166 B |
Before Width: | Height: | Size: 165 B |
Before Width: | Height: | Size: 148 B |
Before Width: | Height: | Size: 179 B |
Before Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 165 B |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 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: 1.0 KiB |
Before Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 85 B |
Before Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.6 KiB |