api.lua 101 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420
  1. -- File rewritten to be live-reloadable August 14, 2018 by MustTest.
  2. -- `mobs.registered' is checked throughout file, but only set `true' @ END!
  3. -- localize functions
  4. local pi = math.pi
  5. local square = math.sqrt
  6. local sin = math.sin
  7. local cos = math.cos
  8. local abs = math.abs
  9. local min = math.min
  10. local max = math.max
  11. local atann = math.atan
  12. local random = math.random
  13. local math_random = math.random
  14. local floor = math.floor
  15. local v_round = vector.round
  16. local v_equals = vector.equals
  17. local vector_distance = vector.distance
  18. local atan = function(x)
  19. if not x or x ~= x then
  20. return 0
  21. else
  22. return atann(x)
  23. end
  24. end
  25. -- function to tell mob which direction to turn to face target
  26. -- add pi to the returned yaw to face in the opposite direction
  27. local function compute_yaw_to_target(self, p, s)
  28. local vec = {
  29. x = p.x - s.x,
  30. z = p.z - s.z
  31. }
  32. local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
  33. if p.x >= s.x then yaw = yaw + pi end
  34. return yaw
  35. end
  36. -- Load settings.
  37. local damage_enabled = true --minetest.setting_getbool("enable_damage")
  38. local peaceful_only = false --minetest.setting_getbool("only_peaceful_mobs")
  39. local disable_blood = false --minetest.setting_getbool("mobs_disable_blood")
  40. local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
  41. local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
  42. local creative = minetest.setting_getbool("creative_mode")
  43. local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
  44. local remove_far = minetest.setting_getbool("remove_far_mobs")
  45. local difficulty = tonumber(minetest.setting_get("mob_difficulty")) or 1.0
  46. local show_health = minetest.settings:get_bool("mob_show_health") ~= false
  47. local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
  48. local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
  49. local default_knockback = 1
  50. -- Used by spawner function.
  51. mobs.spawn_protected = spawn_protected
  52. -- Legacy.
  53. if not mobs.invis then
  54. mobs.invis = {}
  55. end
  56. function mobs.is_invisible(pname)
  57. return cloaking.is_cloaked(pname)
  58. end
  59. -- creative check
  60. local creative_mode_cache = minetest.settings:get_bool("creative_mode")
  61. function mobs.is_creative(name)
  62. return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
  63. end
  64. -- Peaceful mode message so players will know there are no monsters.
  65. -- Perform registration only ONCE.
  66. if not mobs.registered and peaceful_only then
  67. minetest.register_on_joinplayer(function(player)
  68. minetest.chat_send_player(player:get_player_name(),
  69. "# Server: Peaceful mode active - no new monsters will spawn.")
  70. end)
  71. end
  72. -- calculate aoc range for mob count
  73. local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
  74. -- pathfinding settings
  75. local enable_pathfinding = false
  76. local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
  77. local stuck_path_timeout = 10 -- how long will mob follow path before giving up
  78. -- default nodes
  79. local node_fire = "fire:basic_flame"
  80. local node_permanent_flame = "fire:permanent_flame"
  81. local node_ice = "default:ice"
  82. local node_snowblock = "default:snowblock"
  83. local node_snow = "default:snow"
  84. local node_pathfiner_place = "default:cobble"
  85. mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
  86. -- play sound
  87. local function mob_sound(self, sound)
  88. if sound then
  89. local dist = self.sounds.distance or 20
  90. ambiance.sound_play(sound, self.object:get_pos(), 1.0, dist)
  91. -- This isn't working!
  92. --minetest.sound_play(sound, {
  93. -- object = self.object,
  94. -- gain = 1.0,
  95. -- max_hear_distance = self.sounds.distance
  96. --})
  97. end
  98. end
  99. local kill_adj = {
  100. "killed",
  101. "slain",
  102. "slaughtered",
  103. "mauled",
  104. "murdered",
  105. "pwned",
  106. "owned",
  107. "dispatched",
  108. "neutralized",
  109. "wasted",
  110. "polished off",
  111. "rubbed out",
  112. "snuffed out",
  113. "assassinated",
  114. "annulled",
  115. "destroyed",
  116. "finished off",
  117. "terminated",
  118. "wiped out",
  119. "scrubbed",
  120. "abolished",
  121. "obliterated",
  122. "voided",
  123. "ended",
  124. "annihilated",
  125. "undone",
  126. "nullified",
  127. "exterminated",
  128. }
  129. local kill_adj2 = {
  130. "killed",
  131. "slew",
  132. "slaughtered",
  133. "mauled",
  134. "murdered",
  135. "pwned",
  136. "owned",
  137. "dispatched",
  138. "neutralized",
  139. "wasted",
  140. "polished off",
  141. "rubbed out",
  142. "snuffed out",
  143. "assassinated",
  144. "annulled",
  145. "destroyed",
  146. "finished off",
  147. "terminated",
  148. "wiped out",
  149. "scrubbed",
  150. "abolished",
  151. "obliterated",
  152. "voided",
  153. "ended",
  154. "annihilated",
  155. "undid",
  156. "nullified",
  157. "exterminated",
  158. }
  159. local kill_adj3 = {
  160. "kill",
  161. "slay",
  162. "slaughter",
  163. "maul",
  164. "murder",
  165. "pwn",
  166. "own",
  167. "dispatch",
  168. "neutralize",
  169. "waste",
  170. "polish off",
  171. "rub out",
  172. "snuff out",
  173. "assassinate",
  174. "annul",
  175. "destroy",
  176. "finish off",
  177. "terminate",
  178. "wipe out",
  179. "scrub",
  180. "abolish",
  181. "obliterate",
  182. "void",
  183. "end",
  184. "annihilate",
  185. "undo",
  186. "nullify",
  187. "exterminate",
  188. }
  189. local kill_adv = {
  190. "brutally",
  191. "",
  192. "swiftly",
  193. "",
  194. "savagely",
  195. "",
  196. "viciously",
  197. "",
  198. "uncivilly",
  199. "",
  200. "barbarously",
  201. "",
  202. "ruthlessly",
  203. "",
  204. "ferociously",
  205. "",
  206. "rudely",
  207. "",
  208. "cruelly",
  209. "",
  210. }
  211. local kill_ang = {
  212. "angry",
  213. "",
  214. "PO'ed",
  215. "",
  216. "furious",
  217. "",
  218. "disgusted",
  219. "",
  220. "infuriated",
  221. "",
  222. "annoyed",
  223. "",
  224. "irritated",
  225. "",
  226. "bitter",
  227. "",
  228. "offended",
  229. "",
  230. "outraged",
  231. "",
  232. "irate",
  233. "",
  234. "enraged",
  235. "",
  236. "indignant",
  237. "",
  238. "irritable",
  239. "",
  240. "cross",
  241. "",
  242. "riled",
  243. "",
  244. "vexed",
  245. "",
  246. "wrathful",
  247. "",
  248. "fierce",
  249. "",
  250. "displeased",
  251. "",
  252. "irascible",
  253. "",
  254. "ireful",
  255. "",
  256. "sulky",
  257. "",
  258. "ill-tempered",
  259. "",
  260. "vehement",
  261. "",
  262. "raging",
  263. "",
  264. "incensed",
  265. "",
  266. "frenzied",
  267. "",
  268. "enthusiastic",
  269. "",
  270. "fuming",
  271. "",
  272. "cranky",
  273. "",
  274. "peevish",
  275. "",
  276. "belligerent",
  277. "",
  278. }
  279. local function mob_killed_player(self, player)
  280. local pname = player:get_player_name()
  281. local mname = utility.get_short_desc(self.description or "mob")
  282. local adv = kill_adv[math_random(1, #kill_adv)]
  283. if adv ~= "" then
  284. adv = adv .. " "
  285. end
  286. local adj = kill_adj[math_random(1, #kill_adj)]
  287. local ang = kill_ang[math_random(1, #kill_ang)]
  288. if ang ~= "" then
  289. ang = ang .. " "
  290. end
  291. local an = "a"
  292. if ang ~= "" then
  293. if ang:find("^[aeiouAEIOU]") then
  294. an = "an"
  295. end
  296. else
  297. if mname:find("^[aeiouAEIOU]") then
  298. an = "an"
  299. end
  300. end
  301. local victim = "<" .. rename.gpn(pname) .. ">"
  302. if cloaking.is_cloaked(pname) or player_labels.query_nametag_onoff(pname) == false then
  303. victim = "An explorer"
  304. end
  305. minetest.chat_send_all("# Server: " .. victim .. " was " .. adv .. adj .. " by " .. an .. " " .. ang .. mname .. ".")
  306. end
  307. local pain_words = {
  308. "harm",
  309. "pain",
  310. "grief",
  311. "trouble",
  312. "evil",
  313. "ill will",
  314. }
  315. local murder_messages = {
  316. "<n> <v> collapsed from <an_angry_k>'s <angry>attack.",
  317. "<an_angry_k>'s <w> apparently wasn't such an unusual weapon after all, as <n> <v> found out.",
  318. "<an_angry_k> <brutally><slew> <n> <v> with great prejudice.",
  319. "<n> <v> died from <an_angry_k>'s horrid slaying.",
  320. "<n> <v> fell prey to <an_angry_k>'s deadly <w>.",
  321. "<an_angry_k> went out of <k_his> way to <slay> <n> <v> with <k_his> <w>.",
  322. "<n> <v> danced <v_himself> to death under <an_angry_k>'s craftily wielded <w>.",
  323. "<an_angry_k> used <k_his> <w> to <slay> <n> <v> with prejudice.",
  324. "<an_angry_k> made a splortching sound with <n> <v>'s head.",
  325. "<n> <v> was <slain> by <an_angry_k>'s skillfully handled <w>.",
  326. "<n> <v> became prey for <an_angry_k>.",
  327. "<n> <v> didn't get out of <an_angry_k>'s way in time.",
  328. "<n> <v> SAW <an_angry_k> coming with <k_his> <w>. Didn't get away in time.",
  329. "<n> <v> made no real attempt to get out of <an_angry_k>'s way.",
  330. "<an_angry_k> barreled through <n> <v> as if <v_he> wasn't there.",
  331. "<an_angry_k> sent <n> <v> to that place where kindling wood isn't needed.",
  332. "<n> <v> didn't suspect that <an_angry_k> meant <v_him> any <pain>.",
  333. "<n> <v> fought <an_angry_k> to the death and lost painfully.",
  334. "<n> <v> knew <an_angry_k> was wielding <k_his> <w> but didn't guess what <k> meant to do with it.",
  335. "<an_angry_k> <brutally>clonked <n> <v> over the head using <k_his> <w> with silent skill.",
  336. "<an_angry_k> made sure <n> <v> didn't see that coming!",
  337. "<an_angry_k> has decided <k_his> favorite weapon is <k_his> <w>.",
  338. "<n> <v> did the mad hatter dance just before being <slain> with <an_angry_k>'s <w>.",
  339. "<n> <v> played the victim to <an_angry_k>'s bully behavior!",
  340. "<an_angry_k> used <n> <v> for weapons practice with <k_his> <w>.",
  341. "<n> <v> failed to avoid <an_angry_k>'s oncoming weapon.",
  342. "<an_angry_k> successfully got <n> <v> to complain of a headache.",
  343. "<n> <v> got <v_himself> some serious hurt from <an_angry_k>'s <w>.",
  344. "Trying to talk peace to <an_angry_k> didn't win any for <n> <v>.",
  345. "<n> <v> was <brutally><slain> by <an_angry_k>'s <w>.",
  346. "<n> <v> jumped the mad-hatter dance under <an_angry_k>'s <w>.",
  347. "<n> <v> got <v_himself> a fatal mauling by <an_angry_k>'s <w>.",
  348. "<an_angry_k> <brutally><slew> <n> <v> with <k_his> <w>.",
  349. "<an_angry_k> split <n> <v>'s wig.",
  350. "<an_angry_k> took revenge on <n> <v>.",
  351. "<an_angry_k> <brutally><slew> <n> <v>.",
  352. "<n> <v> played dead. Permanently.",
  353. "<n> <v> never saw what hit <v_him>.",
  354. "<an_angry_k> took <n> <v> by surprise.",
  355. "<n> <v> was <brutally><slain>.",
  356. "<an_angry_k> didn't take any prisoners from <n> <v>.",
  357. "<an_angry_k> <brutally>pinned <n> <v> to the wall with <k_his> <w>.",
  358. "<n> <v> failed <v_his> weapon checks.",
  359. "<k> eliminated <n> <v>.",
  360. }
  361. local message_spam_avoidance = {}
  362. local function player_killed_mob(self, player)
  363. local pname = player:get_player_name()
  364. if message_spam_avoidance[pname] then
  365. return
  366. end
  367. local mname = utility.get_short_desc(self.description or "mob")
  368. local msg = murder_messages[math_random(1, #murder_messages)]
  369. msg = string.gsub(msg, "<v>", mname)
  370. local ksex = skins.get_gender_strings(pname)
  371. local vsex = skins.get_random_standard_gender(5) -- 5% female.
  372. msg = string.gsub(msg, "<k_himself>", ksex.himself)
  373. msg = string.gsub(msg, "<k_his>", ksex.his)
  374. msg = string.gsub(msg, "<v_himself>", vsex.himself)
  375. msg = string.gsub(msg, "<v_his>", vsex.his)
  376. msg = string.gsub(msg, "<v_him>", vsex.him)
  377. msg = string.gsub(msg, "<v_he>", vsex.he)
  378. if string.find(msg, "<brutally>") then
  379. local adv = kill_adv[math_random(1, #kill_adv)]
  380. if adv ~= "" then
  381. adv = adv .. " "
  382. end
  383. msg = string.gsub(msg, "<brutally>", adv)
  384. end
  385. if string.find(msg, "<slain>") then
  386. local adj = kill_adj[math_random(1, #kill_adj)]
  387. msg = string.gsub(msg, "<slain>", adj)
  388. end
  389. if string.find(msg, "<slew>") then
  390. local adj = kill_adj2[math_random(1, #kill_adj2)]
  391. msg = string.gsub(msg, "<slew>", adj)
  392. end
  393. if string.find(msg, "<slay>") then
  394. local adj = kill_adj3[math_random(1, #kill_adj3)]
  395. msg = string.gsub(msg, "<slay>", adj)
  396. end
  397. if string.find(msg, "<pain>") then
  398. local adj = pain_words[math_random(1, #pain_words)]
  399. msg = string.gsub(msg, "<pain>", adj)
  400. end
  401. if string.find(msg, "<angry>") then
  402. local ang = kill_ang[math_random(1, #kill_ang)]
  403. if ang ~= "" then
  404. ang = ang .. " "
  405. end
  406. msg = string.gsub(msg, "<angry>", ang)
  407. end
  408. if string.find(msg, "<an_angry_k>") then
  409. local replace = ""
  410. local angry = kill_ang[math_random(1, #kill_ang)]
  411. if angry ~= "" then
  412. local an = "a"
  413. if angry:find("^[aeiouAEIOU]") then
  414. an = "an"
  415. end
  416. replace = an .. " " .. angry .. " "
  417. end
  418. local name = ""
  419. if cloaking.is_cloaked(pname) or player_labels.query_nametag_onoff(pname) == false then
  420. if replace == "" then
  421. name = "an explorer"
  422. else
  423. name = "explorer"
  424. end
  425. replace = replace .. name
  426. else
  427. replace = replace .. "<" .. rename.gpn(pname) .. ">"
  428. end
  429. msg = string.gsub(msg, "<an_angry_k>", replace)
  430. end
  431. if msg:find("<k>") then
  432. local replace = "<" .. rename.gpn(pname) .. ">"
  433. if cloaking.is_cloaked(pname) or player_labels.query_nametag_onoff(pname) == false then
  434. replace = "an explorer"
  435. end
  436. msg = msg:gsub("<k>", replace)
  437. end
  438. if string.find(msg, "<n>") then
  439. local an = "a"
  440. if mname:find("^[aeiouAEIOU]") then
  441. an = "an"
  442. end
  443. msg = string.gsub(msg, "<n>", an)
  444. end
  445. -- Get weapon description.
  446. if string.find(msg, "<w>") then
  447. local wield = player:get_wielded_item()
  448. local def = minetest.registered_items[wield:get_name()]
  449. local meta = wield:get_meta()
  450. local description = meta:get_string("description")
  451. if description ~= "" then
  452. msg = string.gsub(msg, "<w>", "'" .. utility.get_short_desc(description):trim() .. "'")
  453. elseif def and def.description then
  454. local str = utility.get_short_desc(def.description)
  455. if str == "" then
  456. str = "Potato Fist"
  457. end
  458. msg = string.gsub(msg, "<w>", str)
  459. end
  460. end
  461. -- Make first character uppercase.
  462. msg = string.upper(msg:sub(1, 1)) .. msg:sub(2)
  463. msg = string.gsub(msg, "%s+", " ") -- Remove duplicate spaces.
  464. msg = string.gsub(msg, " %.$", ".") -- Remove space before period.
  465. minetest.chat_send_all("# Server: " .. msg)
  466. message_spam_avoidance[pname] = {}
  467. minetest.after(math_random(10, 60*2), function()
  468. message_spam_avoidance[pname] = nil
  469. end)
  470. end
  471. local function do_attack(self, player)
  472. if self.state == "attack" then
  473. return
  474. end
  475. self.state = "attack"
  476. self.attack = player
  477. if random(0, 100) < 90 and self.sounds.war_cry then
  478. mob_sound(self, self.sounds.war_cry)
  479. end
  480. end
  481. local function set_velocity(self, v)
  482. -- Do not move if mob has been ordered to stay.
  483. if self.order == "stand" then
  484. self.object:set_velocity({x = 0, y = 0, z = 0})
  485. return
  486. end
  487. local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
  488. local vel = self.object:get_velocity()
  489. local y = 0
  490. if vel then
  491. y = vel.y or 0
  492. end
  493. -- Fix crash: 2020-09-12 14:26:15: ERROR[Main]: ServerError: AsyncErr:
  494. -- ServerThread::run Lua: Runtime error from mod 'griefer' in callback
  495. -- luaentity_Step(): Invalid float vector dimension range 'y' (expected
  496. -- -2.14748e+06 < y < 2.14748e+06 got -1.96364e+12).
  497. y = max(min(y, 200), -200)
  498. self.object:set_velocity({
  499. x = sin(yaw) * -v,
  500. y = y,
  501. z = cos(yaw) * v
  502. })
  503. end
  504. local function get_velocity(self)
  505. local v = self.object:getvelocity()
  506. return (v.x * v.x + v.z * v.z) ^ 0.5
  507. end
  508. -- set and return valid yaw
  509. local function set_yaw(self, yaw, delay)
  510. if not yaw or yaw ~= yaw then
  511. yaw = 0
  512. end
  513. delay = delay or 0
  514. if delay == 0 then
  515. self.object:set_yaw(yaw)
  516. return yaw
  517. end
  518. self.target_yaw = yaw
  519. self.delay = delay
  520. return self.target_yaw
  521. end
  522. -- global function to set mob yaw
  523. function mobs.yaw(self, yaw, delay)
  524. set_yaw(self, yaw, delay)
  525. end
  526. local function set_animation(self, anim)
  527. if not self.animation or not anim then
  528. return
  529. end
  530. self.animation.current = self.animation.current or ""
  531. -- only set different animation for attacks when setting to same set
  532. if anim ~= "punch" and anim ~= "shoot"
  533. and string.find(self.animation.current, anim) then
  534. return
  535. end
  536. -- check for more than one animation
  537. local num = 0
  538. for n = 1, 4 do
  539. if self.animation[anim .. n .. "_start"]
  540. and self.animation[anim .. n .. "_end"] then
  541. num = n
  542. end
  543. end
  544. -- choose random animation from set
  545. if num > 0 then
  546. num = random(0, num)
  547. anim = anim .. (num ~= 0 and num or "")
  548. end
  549. if anim == self.animation.current
  550. or not self.animation[anim .. "_start"]
  551. or not self.animation[anim .. "_end"] then
  552. return
  553. end
  554. self.animation.current = anim
  555. self.object:set_animation({
  556. x = self.animation[anim .. "_start"],
  557. y = self.animation[anim .. "_end"]},
  558. self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
  559. 0, self.animation[anim .. "_loop"] ~= false)
  560. end
  561. -- above function exported for mount.lua
  562. function mobs.set_animation(self, anim)
  563. set_animation(self, anim)
  564. end
  565. -- calculate distance
  566. local function get_distance(a, b)
  567. local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
  568. return square(x * x + y * y + z * z)
  569. end
  570. -- check line of sight (by BrunoMine, tweaked by Astrobe)
  571. local function line_of_sight(self, pos1, pos2, stepsize)
  572. if not pos1 or not pos2 then return end
  573. stepsize = stepsize or 1
  574. local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
  575. local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
  576. -- normal walking and flying mobs can see you through air
  577. if s == true then return true end
  578. -- New pos1 to be analyzed
  579. local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
  580. local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
  581. -- Checks the return
  582. if r == true then return true end
  583. -- Nodename found
  584. local nn = minetest.get_node(pos).name
  585. -- It continues to advance in the line of sight in search of a real
  586. -- obstruction which counts as 'normal' nodebox.
  587. local registered = minetest.reg_ns_nodes
  588. local ndef = registered[nn] or minetest.registered_nodes[nn]
  589. while ndef
  590. and (ndef.walkable == false
  591. or ndef.drawtype == "nodebox"
  592. or ndef.drawtype:find("glasslike")) do
  593. npos1 = vector.add(npos1, stepv)
  594. if get_distance(npos1, pos2) < stepsize then return true end
  595. -- scan again
  596. r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
  597. if r == true then return true end
  598. -- New Nodename found
  599. nn = minetest.get_node(pos).name
  600. ndef = registered[nn] or minetest.registered_nodes[nn]
  601. end
  602. return false
  603. end
  604. -- global function
  605. function mobs.line_of_sight(self, pos1, pos2, stepsize)
  606. return line_of_sight(self, pos1, pos2, stepsize)
  607. end
  608. -- are we flying in what we are suppose to? (taikedz)
  609. local function flight_check(self, pos_w)
  610. local def = minetest.reg_ns_nodes[self.standing_in]
  611. if not def then return false end -- nil check
  612. if type(self.fly_in) == "string"
  613. and self.standing_in == self.fly_in then
  614. return true
  615. elseif type(self.fly_in) == "table" then
  616. for _,fly_in in pairs(self.fly_in) do
  617. if self.standing_in == fly_in then
  618. return true
  619. end
  620. end
  621. end
  622. --print ("standing in " .. self.standing_in)
  623. -- stops mobs getting stuck inside stairs and plantlike nodes
  624. if def.drawtype ~= "airlike"
  625. and def.drawtype ~= "liquid"
  626. and def.drawtype ~= "flowingliquid" then
  627. return true
  628. end
  629. -- enables mobs to fly in non-walkable stuff like thin default:snow
  630. if not def.walkable then
  631. return true
  632. end
  633. return false
  634. end
  635. -- custom particle effects
  636. local function effect(pos, amount, texture, min_size, max_size, radius, gravity, glow)
  637. radius = radius or 2
  638. min_size = min_size or 0.5
  639. max_size = max_size or 1
  640. gravity = gravity or -10
  641. glow = glow or 0
  642. minetest.add_particlespawner({
  643. amount = amount,
  644. time = 0.25,
  645. minpos = pos,
  646. maxpos = pos,
  647. minvel = {x = -radius, y = -radius, z = -radius},
  648. maxvel = {x = radius, y = radius, z = radius},
  649. minacc = {x = 0, y = gravity, z = 0},
  650. maxacc = {x = 0, y = gravity, z = 0},
  651. minexptime = 0.1,
  652. maxexptime = 1,
  653. minsize = min_size,
  654. maxsize = max_size,
  655. texture = texture,
  656. glow = glow,
  657. })
  658. end
  659. -- Update nametag colour.
  660. local function update_tag(self)
  661. local col = "#00FF00"
  662. local qua = self.hp_max / 4
  663. if self.health <= floor(qua * 3) then
  664. col = "#FFFF00"
  665. end
  666. if self.health <= floor(qua * 2) then
  667. col = "#FF6600"
  668. end
  669. if self.health <= floor(qua) then
  670. col = "#FF0000"
  671. end
  672. self.object:set_properties({
  673. nametag = self.nametag,
  674. nametag_color = col,
  675. })
  676. end
  677. -- drop items
  678. local function item_drop(self, cooked)
  679. -- check for nil or no drops
  680. if not self.drops or #self.drops == 0 then
  681. return
  682. end
  683. -- no drops if disabled by setting
  684. if not mobs_drop_items then return end
  685. -- no drops for child mobs
  686. if self.child then return end
  687. local obj, item, num
  688. local pos = self.object:get_pos()
  689. for n = 1, #self.drops do
  690. if random(1, self.drops[n].chance) == 1 then
  691. num = random(self.drops[n].min or 0, self.drops[n].max or 1)
  692. item = self.drops[n].name
  693. -- cook items when true
  694. if cooked then
  695. local output = minetest.get_craft_result({
  696. method = "cooking", width = 1, items = {item}})
  697. if output and output.item and not output.item:is_empty() then
  698. item = output.item:get_name()
  699. end
  700. end
  701. -- add item if it exists
  702. obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
  703. if obj and obj:get_luaentity() then
  704. obj:set_velocity({
  705. x = random(-10, 10) / 9,
  706. y = 6,
  707. z = random(-10, 10) / 9,
  708. })
  709. elseif obj then
  710. obj:remove() -- item does not exist
  711. end
  712. end
  713. end
  714. self.drops = {}
  715. end
  716. -- check if mob is dead or only hurt
  717. local function check_for_death(self, cause, cmi_cause)
  718. -- has health actually changed?
  719. if self.health == self.old_health and self.health > 0 then
  720. return
  721. end
  722. self.old_health = self.health
  723. -- still got some health? play hurt sound
  724. if self.health > 0 then
  725. mob_sound(self, self.sounds.damage)
  726. -- make sure health isn't higher than max
  727. if self.health > self.hp_max then
  728. self.health = self.hp_max
  729. end
  730. -- backup nametag so we can show health stats
  731. if not self.nametag2 then
  732. self.nametag2 = self.nametag or ""
  733. end
  734. if show_health and (cmi_cause and cmi_cause.type == "punch") then
  735. self.htimer = 2
  736. self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
  737. update_tag(self)
  738. end
  739. return false
  740. end
  741. -- Mob will die, check if we were attacked.
  742. if cause == "hit" then
  743. if self.last_attacked_by and self.last_attacked_by ~= "" then
  744. local attacked_by = minetest.get_player_by_name(self.last_attacked_by)
  745. if attacked_by then
  746. player_killed_mob(self, attacked_by)
  747. end
  748. end
  749. end
  750. -- only drop items if weapon is of sufficient level to overcome mob's armor level
  751. local can_drop = true
  752. if cmi_cause and cmi_cause.tool_capabilities then
  753. local max_drop_level = (cmi_cause.tool_capabilities.max_drop_level or 0)
  754. -- Increase weapon's max drop level if rank is level 7.
  755. local tool_level = 1
  756. if cmi_cause.wielded then
  757. local tool_meta = cmi_cause.wielded:get_meta()
  758. tool_level = tonumber(tool_meta:get_string("tr_lastlevel")) or 1
  759. end
  760. if tool_level >= 7 then
  761. max_drop_level = max_drop_level + 1
  762. end
  763. if (max_drop_level) < (self.armor_level or 0) then
  764. can_drop = false
  765. end
  766. end
  767. -- mob doesn't drop anything if killed by sunlight
  768. -- this fixes the problem of drops being scattered around after sunrise
  769. -- caused by icemen and sandmen
  770. if cause == "light" then
  771. can_drop = false
  772. end
  773. if can_drop then
  774. -- dropped cooked item if mob died in lava
  775. if cause == "lava" then
  776. item_drop(self, true)
  777. else
  778. item_drop(self, nil)
  779. end
  780. end
  781. mob_sound(self, self.sounds.death)
  782. local pos = self.object:get_pos()
  783. -- execute custom death function
  784. if self.on_die then
  785. self.on_die(self, pos)
  786. -- Mark for removal as last action on mob_step().
  787. self.mkrm = true
  788. return true
  789. end
  790. -- default death function and die animation (if defined)
  791. if self.animation
  792. and self.animation.die_start
  793. and self.animation.die_end then
  794. local frames = self.animation.die_end - self.animation.die_start
  795. local speed = self.animation.die_speed or 15
  796. local length = max(frames / speed, 0)
  797. self.attack = nil
  798. self.v_start = false
  799. self.timer = 0
  800. self.blinktimer = 0
  801. self.passive = true
  802. self.state = "die"
  803. set_velocity(self, 0)
  804. set_animation(self, "die")
  805. minetest.after(length, function(self)
  806. -- Mark for removal as last action on mob_step().
  807. -- Note: this is deferred for a bit!
  808. self.mkrm = true
  809. end, self)
  810. else
  811. -- Mark for removal as last action on mob_step().
  812. self.mkrm = true
  813. end
  814. effect(pos, 20, "tnt_smoke.png")
  815. return true
  816. end
  817. -- Check if within physical map limits (-30911 to 30927).
  818. local function within_limits(pos, radius)
  819. if (pos.x - radius) > -30913
  820. and (pos.x + radius) < 30928
  821. and (pos.y - radius) > -30913
  822. and (pos.y + radius) < 30928
  823. and (pos.z - radius) > -30913
  824. and (pos.z + radius) < 30928 then
  825. return true -- Within limits.
  826. end
  827. return false -- Beyond limits.
  828. end
  829. -- Is mob facing a cliff.
  830. local function is_at_cliff(self)
  831. if self.fear_height == 0 then -- 0 for no falling protection!
  832. return false
  833. end
  834. local yaw = self.object:getyaw()
  835. local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
  836. local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
  837. local pos = self.object:getpos()
  838. local ypos = pos.y + self.collisionbox[2] -- just above floor
  839. if minetest.line_of_sight(
  840. {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
  841. {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
  842. , 1) then
  843. return true
  844. end
  845. return false
  846. end
  847. -- Get node but use fallback for nil or unknown.
  848. local function node_ok(pos, fallback)
  849. fallback = fallback or mobs.fallback_node
  850. local node = minetest.get_node_or_nil(pos)
  851. if node and minetest.reg_ns_nodes[node.name] then
  852. return node
  853. end
  854. return minetest.reg_ns_nodes[fallback]
  855. end
  856. -- Global function.
  857. function mobs.node_ok(pos, fallback)
  858. return node_ok(pos, fallback)
  859. end
  860. -- Environmental damage (water, lava, fire, light).
  861. local function do_env_damage(self)
  862. -- feed/tame text timer (so mob 'full' messages dont spam chat)
  863. if self.htimer > 0 then
  864. self.htimer = self.htimer - 1
  865. end
  866. -- reset nametag after showing health stats
  867. if self.htimer < 1 and self.nametag2 then
  868. self.nametag = self.nametag2
  869. self.nametag2 = nil
  870. update_tag(self)
  871. end
  872. local pos = self.object:getpos()
  873. self.time_of_day = minetest.get_timeofday()
  874. -- remove mob if beyond map limits
  875. if not within_limits(pos, 0) then
  876. -- Mark for removal as last action on mob_step().
  877. self.mkrm = true
  878. return
  879. end
  880. -- mob may simply despawn at daytime, without dropping anything
  881. if random(1, 10) == 1 then -- add some random delay chance
  882. if self.daytime_despawn and pos.y > -10
  883. and self.time_of_day > 0.2
  884. and self.time_of_day < 0.8
  885. and (minetest.get_node_light(pos) or 0) > 12 then
  886. if self.on_despawn then
  887. self.on_despawn(self)
  888. return
  889. else
  890. -- Mark for removal as last action on mob_step().
  891. self.mkrm = true
  892. return
  893. end
  894. end
  895. end
  896. -- bright light harms mob
  897. -- daylight above ground
  898. if self.light_damage ~= 0
  899. and pos.y > -10
  900. and self.time_of_day > 0.2
  901. and self.time_of_day < 0.8
  902. and (minetest.get_node_light(pos) or 0) > 12 then
  903. self.health = self.health - self.light_damage
  904. effect(pos, 5, "tnt_smoke.png")
  905. if check_for_death(self, "light", {type = "light"}) then return end
  906. end
  907. if self.despawns_in_dark_caves and pos.y < -12 then
  908. if (minetest.get_node_light(pos) or 0) == 0 then
  909. -- Mark for removal as last action on mob_step().
  910. self.mkrm = true
  911. return
  912. end
  913. end
  914. -- don't fall when on ignore, just stand still
  915. if self.standing_in == "ignore" then
  916. self.object:set_velocity({x = 0, y = 0, z = 0})
  917. end
  918. local nodef = minetest.reg_ns_nodes[self.standing_in]
  919. local nodef2 = minetest.reg_ns_nodes[self.standing_on]
  920. -- Stairs nodes don't do env damage.
  921. if not nodef or not nodef2 then
  922. return
  923. end
  924. pos.y = pos.y + 1 -- for particle effect position
  925. -- water
  926. if self.water_damage
  927. and nodef.groups.water then
  928. if self.water_damage ~= 0 then
  929. self.health = self.health - self.water_damage
  930. effect(pos, 5, "bubble.png", nil, nil, 1, nil)
  931. if check_for_death(self, "water", {type = "environment",
  932. pos = pos, node = self.standing_in}) then return end
  933. end
  934. -- lava or fire
  935. elseif self.lava_damage
  936. and (nodef.groups.lava or nodef.groups.fire) then
  937. if self.lava_damage ~= 0 then
  938. self.health = self.health - self.lava_damage
  939. effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
  940. if check_for_death(self, "lava", {type = "environment",
  941. pos = pos, node = self.standing_in}) then return end
  942. end
  943. -- damage_per_second node check
  944. elseif nodef.damage_per_second ~= 0 then
  945. self.health = self.health - nodef.damage_per_second
  946. effect(pos, 5, "tnt_smoke.png")
  947. if check_for_death(self, "dps", {type = "environment",
  948. pos = pos, node = self.standing_in}) then return end
  949. end
  950. if nodef2.groups.lava and self.lava_annihilates and self.lava_annihilates == true then
  951. self.health = 0
  952. effect(pos, 5, "tnt_smoke.png")
  953. pos.y = pos.y - 1 -- erase effect of adjusting for particle position.
  954. local pb = v_round(pos) -- use rounded position
  955. local pa = {x=pb.x, y=pb.y+1, z=pb.z}
  956. if minetest.get_node(pb).name == "air" and minetest.get_node(pa).name == "air" then
  957. if self.makes_bones_in_lava and self.makes_bones_in_lava == true then
  958. minetest.add_node(pb, {name="bones:bones_type2"})
  959. local meta = minetest.get_meta(pb)
  960. meta:set_int("protection_cancel", 1)
  961. minetest.add_node(pa, {name="fire:basic_flame"})
  962. minetest.check_for_falling(pb)
  963. else
  964. minetest.add_node(pb, {name="fire:basic_flame"})
  965. end
  966. end
  967. if check_for_death(self, "lava", {type = "environment",
  968. pos = pos, node = self.standing_in}) then return end
  969. end
  970. check_for_death(self, "", {type = "unknown"})
  971. end
  972. -- jump if facing a solid node (not fences or gates)
  973. local function do_jump(self)
  974. if not self.jump
  975. or self.jump_height == 0
  976. or self.fly
  977. or self.child
  978. or self.order == "stand" then
  979. return false
  980. end
  981. self.facing_fence = false
  982. -- something stopping us while moving?
  983. if self.state ~= "stand"
  984. and get_velocity(self) > 0.5
  985. and self.object:get_velocity().y ~= 0 then
  986. return false
  987. end
  988. local pos = self.object:get_pos()
  989. local yaw = self.object:get_yaw()
  990. -- what is mob standing on?
  991. pos.y = pos.y + self.collisionbox[2] - 0.2
  992. local nod = node_ok(pos)
  993. --print ("standing on:", nod.name, pos.y)
  994. if minetest.registered_nodes[nod.name].walkable == false then
  995. return false
  996. end
  997. -- where is front
  998. local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
  999. local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
  1000. -- what is in front of mob?
  1001. local nod = node_ok({
  1002. x = pos.x + dir_x,
  1003. y = pos.y + 0.5,
  1004. z = pos.z + dir_z
  1005. })
  1006. -- thin blocks that do not need to be jumped
  1007. if nod.name == node_snow then
  1008. return false
  1009. end
  1010. --print ("in front:", nod.name, pos.y + 0.5)
  1011. if self.walk_chance == 0
  1012. or minetest.registered_items[nod.name].walkable then
  1013. if self.type == "monster" or (not nod.name:find("fence")
  1014. and not nod.name:find("gate")) then
  1015. local v = self.object:get_velocity()
  1016. v.y = self.jump_height
  1017. set_animation(self, "jump") -- only when defined
  1018. self.object:set_velocity(v)
  1019. -- when in air move forward
  1020. minetest.after(0.3, function(self, v)
  1021. if self.object:get_luaentity() then
  1022. self.object:set_acceleration({
  1023. x = v.x * 2,--1.5,
  1024. y = 0,
  1025. z = v.z * 2,--1.5
  1026. })
  1027. end
  1028. end, self, v)
  1029. if get_velocity(self) > 0 then
  1030. mob_sound(self, self.sounds.jump)
  1031. end
  1032. else
  1033. self.facing_fence = true
  1034. end
  1035. return true
  1036. end
  1037. return false
  1038. end
  1039. -- Blast damage to entities nearby (modified from TNT mod).
  1040. local function entity_physics(pos, radius)
  1041. radius = radius * 2
  1042. local objs = minetest.get_objects_inside_radius(pos, radius)
  1043. local obj_pos, dist
  1044. for n = 1, #objs do
  1045. obj_pos = objs[n]:get_pos()
  1046. dist = get_distance(pos, obj_pos)
  1047. if dist < 1 then dist = 1 end
  1048. local damage = floor((4 / dist) * radius)
  1049. local ent = objs[n]:get_luaentity()
  1050. -- punches work on entities AND players
  1051. objs[n]:punch(objs[n], 1.0, {
  1052. full_punch_interval = 1.0,
  1053. damage_groups = {fleshy = damage},
  1054. }, pos)
  1055. end
  1056. end
  1057. -- Should mob follow what I'm holding?
  1058. local function follow_holding(self, clicker)
  1059. local item = clicker:get_wielded_item()
  1060. local t = type(self.follow)
  1061. -- single item
  1062. if t == "string"
  1063. and item:get_name() == self.follow then
  1064. return true
  1065. -- multiple items
  1066. elseif t == "table" then
  1067. for no = 1, #self.follow do
  1068. if self.follow[no] == item:get_name() then
  1069. return true
  1070. end
  1071. end
  1072. end
  1073. return false
  1074. end
  1075. -- find two animals of same type and breed if nearby and horny
  1076. local function breed(self)
  1077. -- child takes 240 seconds before growing into adult
  1078. if self.child == true then
  1079. self.hornytimer = self.hornytimer + 1
  1080. if self.hornytimer > 240 then
  1081. self.child = false
  1082. self.hornytimer = 0
  1083. self.object:set_properties({
  1084. textures = self.base_texture,
  1085. mesh = self.base_mesh,
  1086. visual_size = self.base_size,
  1087. collisionbox = self.base_colbox,
  1088. selectionbox = self.base_selbox,
  1089. })
  1090. -- custom function when child grows up
  1091. if self.on_grown then
  1092. self.on_grown(self)
  1093. else
  1094. -- jump when fully grown so as not to fall into ground
  1095. self.object:set_velocity({
  1096. x = 0,
  1097. y = self.jump_height,
  1098. z = 0
  1099. })
  1100. end
  1101. end
  1102. return
  1103. end
  1104. -- horny animal can mate for 40 seconds,
  1105. -- afterwards horny animal cannot mate again for 200 seconds
  1106. if self.horny == true
  1107. and self.hornytimer < 240 then
  1108. self.hornytimer = self.hornytimer + 1
  1109. if self.hornytimer >= 240 then
  1110. self.hornytimer = 0
  1111. self.horny = false
  1112. end
  1113. end
  1114. -- find another same animal who is also horny and mate if nearby
  1115. if self.horny == true
  1116. and self.hornytimer <= 40 then
  1117. local pos = self.object:get_pos()
  1118. effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
  1119. local objs = minetest.get_objects_inside_radius(pos, 3)
  1120. local num = 0
  1121. local ent = nil
  1122. for n = 1, #objs do
  1123. ent = objs[n]:get_luaentity()
  1124. -- check for same animal with different colour
  1125. local canmate = false
  1126. if ent then
  1127. if ent.name == self.name then
  1128. canmate = true
  1129. else
  1130. local entname = string.split(ent.name,":")
  1131. local selfname = string.split(self.name,":")
  1132. if entname[1] == selfname[1] then
  1133. entname = string.split(entname[2],"_")
  1134. selfname = string.split(selfname[2],"_")
  1135. if entname[1] == selfname[1] then
  1136. canmate = true
  1137. end
  1138. end
  1139. end
  1140. end
  1141. if ent
  1142. and canmate == true
  1143. and ent.horny == true
  1144. and ent.hornytimer <= 40 then
  1145. num = num + 1
  1146. end
  1147. -- found your mate? then have a baby
  1148. if num > 1 then
  1149. self.hornytimer = 41
  1150. ent.hornytimer = 41
  1151. -- spawn baby
  1152. minetest.after(5, function(self, ent)
  1153. if not self.object:get_luaentity() then
  1154. return
  1155. end
  1156. -- custom breed function
  1157. if self.on_breed then
  1158. -- when false skip going any further
  1159. if self.on_breed(self, ent) == false then
  1160. return
  1161. end
  1162. else
  1163. effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
  1164. end
  1165. local mob = minetest.add_entity(pos, self.name)
  1166. local ent2 = mob:get_luaentity()
  1167. local textures = self.base_texture
  1168. -- using specific child texture (if found)
  1169. if self.child_texture then
  1170. textures = self.child_texture[1]
  1171. end
  1172. -- and resize to half height
  1173. mob:set_properties({
  1174. textures = textures,
  1175. visual_size = {
  1176. x = self.base_size.x * .5,
  1177. y = self.base_size.y * .5,
  1178. },
  1179. collisionbox = {
  1180. self.base_colbox[1] * .5,
  1181. self.base_colbox[2] * .5,
  1182. self.base_colbox[3] * .5,
  1183. self.base_colbox[4] * .5,
  1184. self.base_colbox[5] * .5,
  1185. self.base_colbox[6] * .5,
  1186. },
  1187. selectionbox = {
  1188. self.base_selbox[1] * .5,
  1189. self.base_selbox[2] * .5,
  1190. self.base_selbox[3] * .5,
  1191. self.base_selbox[4] * .5,
  1192. self.base_selbox[5] * .5,
  1193. self.base_selbox[6] * .5,
  1194. },
  1195. })
  1196. -- tamed and owned by parents' owner
  1197. ent2.child = true
  1198. ent2.tamed = true
  1199. ent2.owner = self.owner
  1200. end, self, ent)
  1201. num = 0
  1202. break
  1203. end
  1204. end
  1205. end
  1206. end
  1207. -- find and replace what mob is looking for (grass, wheat etc.)
  1208. local function replace(self, pos)
  1209. if not mobs_griefing
  1210. or not self.replace_rate
  1211. or not self.replace_what
  1212. or self.child == true
  1213. or self.object:get_velocity().y ~= 0
  1214. or random(1, self.replace_rate) > 1 then
  1215. return
  1216. end
  1217. local what, with, y_offset
  1218. if type(self.replace_what[1]) == "table" then
  1219. local num = random(#self.replace_what)
  1220. what = self.replace_what[num][1] or ""
  1221. with = self.replace_what[num][2] or ""
  1222. y_offset = self.replace_what[num][3] or 0
  1223. else
  1224. what = self.replace_what
  1225. with = self.replace_with or ""
  1226. y_offset = self.replace_offset or 0
  1227. end
  1228. pos.y = pos.y + y_offset
  1229. local range = self.replace_range or 1
  1230. local target = minetest.find_node_near(pos, range, what)
  1231. if target then
  1232. -- Do not disturb protected stuff.
  1233. if minetest.test_protection(target, "") then return end
  1234. -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
  1235. local oldnode = {name = what}
  1236. local newnode = {name = with}
  1237. local on_replace_return
  1238. if self.on_replace then
  1239. on_replace_return = self.on_replace(self, target, oldnode, newnode)
  1240. end
  1241. if on_replace_return ~= false then
  1242. minetest.add_node(target, {name = with})
  1243. -- when cow/sheep eats grass, replace wool and milk
  1244. if self.gotten == true then
  1245. self.gotten = false
  1246. self.object:set_properties(self)
  1247. end
  1248. end
  1249. end
  1250. end
  1251. -- Check if daytime and also if mob is docile during daylight hours.
  1252. local function day_docile(self)
  1253. if self.docile_by_day == false then
  1254. return false
  1255. elseif self.docile_by_day == true
  1256. and self.time_of_day > 0.2
  1257. and self.time_of_day < 0.8 then
  1258. return true
  1259. end
  1260. end
  1261. local function try_break_block(self, s)
  1262. s = v_round(s)
  1263. -- remove one block above to make room to jump
  1264. if not minetest.test_protection(s, "") then
  1265. local node1 = node_ok(s, "air").name
  1266. local ndef1 = minetest.registered_nodes[node1]
  1267. if node1 ~= "air"
  1268. and node1 ~= "ignore"
  1269. and node1 ~= "bones:bones" -- don't destroy player's bones!
  1270. and ndef1
  1271. and (not ndef1.groups.level or ndef1.groups.level <= 1)
  1272. and not ndef1.groups.unbreakable
  1273. and not ndef1.groups.liquid
  1274. and not ndef1.on_construct
  1275. and not ndef1.on_blast then
  1276. local oldnode = minetest.get_node(s)
  1277. minetest.add_node(s, {name = "air"})
  1278. -- Run script hook
  1279. for _, callback in ipairs(minetest.registered_on_dignodes) do
  1280. -- Deepcopy pos, oldnode, because callback can modify them
  1281. callback(table.copy(s), table.copy(oldnode), self.object)
  1282. end
  1283. --minetest.add_item(s, ItemStack(node1))
  1284. minetest.check_for_falling(s)
  1285. -- This function takes both nodetables and nodenames.
  1286. -- Pass node names, because passing a node table gives wrong results.
  1287. local drops = minetest.get_node_drops(oldnode.name, "")
  1288. --minetest.chat_send_player("MustTest", dump(drops))
  1289. for _, item in pairs(drops) do
  1290. local p = {
  1291. x = s.x + math_random()/2 - 0.25,
  1292. y = s.y + math_random()/2 - 0.25,
  1293. z = s.z + math_random()/2 - 0.25,
  1294. }
  1295. minetest.add_item(p, item)
  1296. end
  1297. return true -- success!
  1298. end
  1299. end
  1300. end
  1301. local function highlight_path(self)
  1302. --[[
  1303. -- show path using particles
  1304. if self.path.way and #self.path.way > 0 then
  1305. --print ("-- path length:" .. tonumber(#self.path.way))
  1306. for _,pos in pairs(self.path.way) do
  1307. minetest.add_particle({
  1308. pos = pos,
  1309. velocity = {x=0, y=0, z=0},
  1310. acceleration = {x=0, y=0, z=0},
  1311. expirationtime = 3,
  1312. size = 4,
  1313. collisiondetection = false,
  1314. vertical = false,
  1315. texture = "heart.png",
  1316. })
  1317. end
  1318. end
  1319. --]]
  1320. end
  1321. local los_switcher = false
  1322. local height_switcher = false
  1323. -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
  1324. local function smart_mobs(self, s, p, dist, dtime)
  1325. --print('begin')
  1326. self.path.putnode_timer = self.path.putnode_timer + dtime
  1327. do -- compartmentalize
  1328. local s1 = self.path.lastpos -- should be rounded to nearest node
  1329. local s2 = v_round(s)
  1330. -- is it becoming stuck?
  1331. if v_equals(s1, s2) then -- if rounded positions match, mob has not moved!
  1332. --print('i\'m stuck!')
  1333. --minetest.chat_send_player("MustTest", "Stuck1!")
  1334. self.path.stuck_timer = self.path.stuck_timer + dtime
  1335. else
  1336. --print('not stuck')
  1337. --minetest.chat_send_player("MustTest", "not stuck")
  1338. self.path.stuck_timer = 0
  1339. end
  1340. self.path.lastpos = s2
  1341. end
  1342. local target_pos = self.attack:get_pos()
  1343. local use_pathfind = false
  1344. local has_lineofsight = false
  1345. has_lineofsight = minetest.line_of_sight(
  1346. {x = s.x, y = (s.y) + .5, z = s.z},
  1347. {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
  1348. --if has_lineofsight then
  1349. -- minetest.chat_send_player("MustTest", "LOS")
  1350. --end
  1351. -- im stuck, search for path
  1352. if not has_lineofsight then
  1353. --print('nosight')
  1354. if los_switcher == true then
  1355. use_pathfind = true
  1356. los_switcher = false
  1357. end -- cannot see target!
  1358. else
  1359. --print('sight')
  1360. if los_switcher == false then
  1361. los_switcher = true
  1362. use_pathfind = false
  1363. minetest.after(random(10, 30)/10, function(self)
  1364. if self.object:get_luaentity() then
  1365. if has_lineofsight then
  1366. self.path.following = false
  1367. end
  1368. end
  1369. end, self)
  1370. end -- can see target!
  1371. end
  1372. if self.attack and self.path.stuck_timer > stuck_timeout and not has_lineofsight then
  1373. --minetest.chat_send_player("MustTest", "giving up!")
  1374. self.attack = nil
  1375. self.state = "stand"
  1376. self.path.stuck_timer = 0
  1377. set_velocity(self, 0)
  1378. return
  1379. end
  1380. if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
  1381. --print('stuck - not following')
  1382. --minetest.chat_send_player("MustTest", "stuck - not following")
  1383. use_pathfind = true
  1384. self.path.stuck_timer = 0
  1385. minetest.after(random(10, 30)/10, function(self)
  1386. if self.object:get_luaentity() then
  1387. if has_lineofsight then
  1388. self.path.following = false
  1389. end
  1390. end
  1391. end, self)
  1392. end
  1393. if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
  1394. --print('stuck - following')
  1395. --minetest.chat_send_player("MustTest", "REALLY Stuck!")
  1396. use_pathfind = true
  1397. self.path.stuck_timer = 0
  1398. minetest.after(random(10, 30)/10, function(self)
  1399. if self.object:get_luaentity() then
  1400. -- mob got stuck while following path - could be badly formed path?
  1401. -- dunno why original code had a LOS check here
  1402. -- ok, need LOS check because otherwise mob follows path even when player is clear shot away. looks stupid
  1403. if has_lineofsight then
  1404. self.path.following = false
  1405. end
  1406. end
  1407. end, self)
  1408. end
  1409. if abs(vector.subtract(s,target_pos).y) > self.stepheight then
  1410. --print('height difference')
  1411. if height_switcher then
  1412. if not has_lineofsight then
  1413. use_pathfind = true
  1414. end
  1415. height_switcher = false
  1416. else
  1417. use_pathfind = false
  1418. height_switcher = true
  1419. end
  1420. else
  1421. --print('no height difference')
  1422. if not height_switcher then
  1423. use_pathfind = false
  1424. height_switcher = true
  1425. else
  1426. if not has_lineofsight then
  1427. use_pathfind = true
  1428. end
  1429. height_switcher = false
  1430. end
  1431. end
  1432. if use_pathfind and (not self.path.following or not self.path.way) then
  1433. --print('will pathfind!')
  1434. --minetest.chat_send_player("MustTest", "will pathfind!")
  1435. -- lets try find a path, first take care of positions
  1436. -- since pathfinder is very sensitive
  1437. local sheight = self.collisionbox[5] - self.collisionbox[2]
  1438. -- round position to center of node to avoid stuck in walls
  1439. -- also adjust height for player models!
  1440. s.x = floor(s.x + 0.5)
  1441. -- s.y = floor(s.y + 0.5) - sheight
  1442. s.z = floor(s.z + 0.5)
  1443. local ssight, sground = minetest.line_of_sight(s, {
  1444. x = s.x, y = s.y - 4, z = s.z}, 1)
  1445. -- determine node above ground
  1446. if not ssight then
  1447. s.y = sground.y + 1
  1448. end
  1449. local p1 = self.attack:get_pos()
  1450. p1 = v_round(p1)
  1451. local dropheight = 6
  1452. if self.fear_height > 0 then dropheight = (self.fear_height - 1) end
  1453. --print("trying to find path!")
  1454. --local air1 = minetest.find_node_near(s, 3, "air")
  1455. --local air2 = minetest.find_node_near(p1, 3, "air")
  1456. --if air1 and air2 then
  1457. --self.path.way = minetest.find_path(air1, air2, 16, self.stepheight, dropheight, "Dijkstra")
  1458. self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
  1459. --end
  1460. -- Show path using particles.
  1461. highlight_path(self)
  1462. self.state = ""
  1463. do_attack(self, self.attack)
  1464. -- no path found, try something else
  1465. if not self.path.way then
  1466. self.path.following = false
  1467. -- lets make way by digging/building if not accessible
  1468. if (self.pathfinding or 0) == 2 and mobs_griefing then
  1469. -- is player higher than mob?
  1470. if s.y < p1.y and not self.fly then
  1471. -- build upwards
  1472. -- not necessary to check protection if only placing nodes
  1473. -- timer is used to prevent mob from spamming lots of blocks
  1474. s = v_round(s)
  1475. if self.path.putnode_timer > 1 then
  1476. local node = minetest.get_node(s)
  1477. local ndef = minetest.registered_nodes[node.name]
  1478. local canput = false
  1479. local prot = minetest.test_protection(s, "")
  1480. if ndef and (ndef.buildable_to or ndef.groups.liquid) then
  1481. canput = true
  1482. end
  1483. if prot and node.name ~= "air" then
  1484. canput = false
  1485. end
  1486. if canput then
  1487. -- Place node the mob likes, or use fallback.
  1488. minetest.add_node(s, {name = self.place_node or node_pathfiner_place})
  1489. local meta = minetest.get_meta(s)
  1490. meta:set_int("protection_cancel", 1)
  1491. sfn.drop_node(s)
  1492. end
  1493. self.path.putnode_timer = 0
  1494. end
  1495. local sheight = math.ceil(self.collisionbox[5]) + 1
  1496. -- assume mob is 2 blocks high so it digs above its head
  1497. s.y = s.y + sheight
  1498. try_break_block(self, s)
  1499. s.y = s.y - sheight
  1500. --self.object:set_pos({x = s.x, y = s.y + 1, z = s.z}) -- this causes mob to glitch
  1501. self.object:set_velocity({x = 0, y = 5, z = 0})
  1502. -- target is directly under the mob -- dig through floor!
  1503. elseif abs(p1.x - s.x) < 0.2 and abs(p1.z - s.z) < 0.2 and p1.y < (s.y - 2) then
  1504. s.y = s.y - 1
  1505. local res = try_break_block(self, s)
  1506. s.y = s.y + 1
  1507. if not res then
  1508. --minetest.chat_send_player("MustTest", "Cannot get target!")
  1509. -- cannot dig, stop trying to get target.
  1510. self.state = "stand"
  1511. set_velocity(self, 0)
  1512. set_animation(self, "stand")
  1513. self.attack = nil
  1514. self.v_start = false
  1515. self.timer = 0
  1516. self.blinktimer = 0
  1517. self.path.way = nil
  1518. end
  1519. -- move to center of hole to try and fall down it
  1520. --local v = v_round(s)
  1521. --self.object:set_pos(v)
  1522. --self.path.way = {[1]={x=v.x, y=v.y, z=v.z}}
  1523. --self.path.following = true
  1524. --self.path.stuck_timer = 0
  1525. else -- dig 2 blocks to make door toward player direction
  1526. local yaw1 = self.object:get_yaw() + pi / 2
  1527. local p1 = {
  1528. x = s.x + cos(yaw1),
  1529. y = s.y,
  1530. z = s.z + sin(yaw1)
  1531. }
  1532. try_break_block(self, p1)
  1533. p1.y = p1.y + 1
  1534. try_break_block(self, p1)
  1535. end
  1536. end
  1537. -- will try again in 2 second
  1538. self.path.stuck_timer = stuck_timeout - 2
  1539. -- frustration! cant find the damn path :(
  1540. if random(1, 20) == 1 then
  1541. mob_sound(self, self.sounds.random)
  1542. end
  1543. else
  1544. -- yay i found path
  1545. mob_sound(self, self.sounds.war_cry)
  1546. set_velocity(self, self.walk_velocity)
  1547. -- follow path now that it has it
  1548. self.path.following = true
  1549. end
  1550. else
  1551. if use_pathfind and self.path.way then
  1552. --print('already have path')
  1553. highlight_path(self)
  1554. if not self.path.following then
  1555. --print('following existing path')
  1556. self.path.following = true
  1557. end
  1558. end
  1559. end
  1560. end
  1561. -- specific attacks
  1562. local function specific_attack(list, what)
  1563. -- no list so attack default (player, animals etc.)
  1564. if list == nil then
  1565. return true
  1566. end
  1567. -- is found entity on list to attack?
  1568. for no = 1, #list do
  1569. if list[no] == what then
  1570. return true
  1571. end
  1572. end
  1573. return false
  1574. end
  1575. -- general attack function for all mobs ==========
  1576. local function general_attack(self)
  1577. -- return if already attacking, passive or docile during day
  1578. if self.passive
  1579. or self.state == "attack"
  1580. or day_docile(self) then
  1581. return
  1582. end
  1583. local s = self.object:get_pos()
  1584. -- Stupid spurious errors.
  1585. if not s then
  1586. return
  1587. end
  1588. local objs = minetest.get_objects_inside_radius(s, self.view_range)
  1589. -- remove entities we aren't interested in
  1590. for n = 1, #objs do
  1591. local ent = objs[n]:get_luaentity()
  1592. -- are we a player?
  1593. if objs[n]:is_player() then
  1594. local pname = objs[n]:get_player_name()
  1595. -- if player invisible or mob not setup to attack then remove from list
  1596. if self.attack_players == false
  1597. or (self.owner and self.type ~= "monster")
  1598. or mobs.is_invisible(pname)
  1599. or not specific_attack(self.specific_attack, "player")
  1600. or minetest.check_player_privs(pname, {mob_respect=true}) then
  1601. objs[n] = nil
  1602. --print("- pla", n)
  1603. end
  1604. -- ignore dead players
  1605. if objs[n] and objs[n]:get_hp() <= 0 then
  1606. objs[n] = nil
  1607. end
  1608. -- If player nametag is off, reduce range at which mob can see them.
  1609. if objs[n] and player_labels.query_nametag_onoff(pname) == false then
  1610. local r = self.view_range * 0.8
  1611. local p = objs[n]:get_pos()
  1612. if vector_distance(p, s) > r then
  1613. objs[n] = nil
  1614. end
  1615. end
  1616. -- or are we a mob?
  1617. elseif ent and ent._cmi_is_mob then
  1618. -- remove mobs not to attack
  1619. if self.name == ent.name
  1620. or (not self.attack_animals and ent.type == "animal")
  1621. or (not self.attack_monsters and ent.type == "monster")
  1622. or (not self.attack_npcs and ent.type == "npc")
  1623. or not specific_attack(self.specific_attack, ent.name) then
  1624. objs[n] = nil
  1625. --print("- mob", n, self.name, ent.name)
  1626. end
  1627. -- remove all other entities
  1628. else
  1629. --print(" -obj", n)
  1630. objs[n] = nil
  1631. end
  1632. end
  1633. local p, sp, dist, min_player
  1634. local min_dist = self.view_range + 1
  1635. -- go through remaining entities and select closest
  1636. for _,player in pairs(objs) do
  1637. p = player:get_pos()
  1638. sp = s
  1639. dist = get_distance(p, s)
  1640. -- aim higher to make looking up hills more realistic
  1641. p.y = p.y + 1
  1642. sp.y = sp.y + 1
  1643. -- choose closest player to attack that isnt self
  1644. if dist ~= 0
  1645. and dist < min_dist
  1646. and line_of_sight(self, sp, p, 0.5) == true then
  1647. min_dist = dist
  1648. min_player = player
  1649. end
  1650. end
  1651. -- attack closest player or mob
  1652. if min_player then
  1653. do_attack(self, min_player)
  1654. end
  1655. end
  1656. -- specific runaway
  1657. local function specific_runaway(list, what)
  1658. -- no list so do not run
  1659. if list == nil then
  1660. return false
  1661. end
  1662. -- found entity on list to attack?
  1663. for no = 1, #list do
  1664. if list[no] == what then
  1665. return true
  1666. end
  1667. end
  1668. return false
  1669. end
  1670. -- find someone to runaway from
  1671. local function runaway_from(self)
  1672. if not self.runaway_from then
  1673. return
  1674. end
  1675. local s = self.object:get_pos()
  1676. local p, sp, dist, pname
  1677. local player, obj, min_player, name
  1678. local min_dist = self.view_range + 1
  1679. local objs = minetest.get_objects_inside_radius(s, self.view_range)
  1680. for n = 1, #objs do
  1681. if objs[n]:is_player() then
  1682. pname = objs[n]:get_player_name()
  1683. if mobs.is_invisible(pname)
  1684. or self.owner == pname then
  1685. name = ""
  1686. else
  1687. player = objs[n]
  1688. name = "player"
  1689. end
  1690. else
  1691. obj = objs[n]:get_luaentity()
  1692. if obj then
  1693. player = obj.object
  1694. name = obj.name or ""
  1695. end
  1696. end
  1697. -- find specific mob to runaway from
  1698. if name ~= "" and name ~= self.name
  1699. and specific_runaway(self.runaway_from, name) then
  1700. p = player:get_pos()
  1701. sp = s
  1702. -- aim higher to make looking up hills more realistic
  1703. p.y = p.y + 1
  1704. sp.y = sp.y + 1
  1705. dist = get_distance(p, s)
  1706. -- choose closest player/mob to runaway from
  1707. if dist < min_dist
  1708. and line_of_sight(self, sp, p, 2) == true then
  1709. min_dist = dist
  1710. min_player = player
  1711. end
  1712. end
  1713. end
  1714. if min_player then
  1715. local lp = player:get_pos()
  1716. local yaw = compute_yaw_to_target(self, lp, s)
  1717. yaw = set_yaw(self, yaw, 4)
  1718. self.state = "runaway"
  1719. self.runaway_timer = 3
  1720. self.following = nil
  1721. end
  1722. end
  1723. -- follow player if owner or holding item, if fish outta water then flop
  1724. local function follow_flop(self)
  1725. -- find player to follow
  1726. if (self.follow ~= ""
  1727. or self.order == "follow")
  1728. and not self.following
  1729. and self.state ~= "attack"
  1730. and self.state ~= "runaway" then
  1731. local s = self.object:get_pos()
  1732. local players = minetest.get_connected_players()
  1733. for n = 1, #players do
  1734. if get_distance(players[n]:get_pos(), s) < self.view_range
  1735. and not mobs.is_invisible( players[n]:get_player_name() ) then
  1736. self.following = players[n]
  1737. break
  1738. end
  1739. end
  1740. end
  1741. if self.type == "npc"
  1742. and self.order == "follow"
  1743. and self.state ~= "attack"
  1744. and self.owner ~= "" then
  1745. -- npc stop following player if not owner
  1746. if self.following
  1747. and self.owner
  1748. and self.owner ~= self.following:get_player_name() then
  1749. self.following = nil
  1750. end
  1751. else
  1752. -- stop following player if not holding specific item
  1753. if self.following
  1754. and self.following:is_player()
  1755. and follow_holding(self, self.following) == false then
  1756. self.following = nil
  1757. end
  1758. end
  1759. -- follow that thing
  1760. if self.following then
  1761. local s = self.object:get_pos()
  1762. local p
  1763. if self.following:is_player() then
  1764. p = self.following:get_pos()
  1765. elseif self.following.object then
  1766. p = self.following.object:get_pos()
  1767. end
  1768. if p then
  1769. local dist = get_distance(p, s)
  1770. -- dont follow if out of range
  1771. if dist > self.view_range then
  1772. self.following = nil
  1773. else
  1774. local yaw = compute_yaw_to_target(self, p, s)
  1775. yaw = set_yaw(self, yaw, 6)
  1776. -- anyone but standing npc's can move along
  1777. if dist > self.reach
  1778. and self.order ~= "stand" then
  1779. set_velocity(self, self.walk_velocity or 0)
  1780. if self.walk_chance ~= 0 then
  1781. set_animation(self, "walk")
  1782. end
  1783. else
  1784. set_velocity(self, 0)
  1785. set_animation(self, "stand")
  1786. end
  1787. return
  1788. end
  1789. end
  1790. end
  1791. -- swimmers flop when out of their element, and swim again when back in
  1792. if self.fly then
  1793. local s = self.object:get_pos()
  1794. if not flight_check(self, s) then
  1795. self.state = "flop"
  1796. self.object:set_velocity({x = 0, y = -5, z = 0})
  1797. set_animation(self, "stand")
  1798. return
  1799. elseif self.state == "flop" then
  1800. self.state = "stand"
  1801. end
  1802. end
  1803. end
  1804. -- dogshoot attack switch and counter function
  1805. local function dogswitch(self, dtime)
  1806. -- switch mode not activated
  1807. if not self.dogshoot_switch
  1808. or not dtime then
  1809. return 0
  1810. end
  1811. self.dogshoot_count = self.dogshoot_count + dtime
  1812. if (self.dogshoot_switch == 1
  1813. and self.dogshoot_count > self.dogshoot_count_max)
  1814. or (self.dogshoot_switch == 2
  1815. and self.dogshoot_count > self.dogshoot_count2_max) then
  1816. self.dogshoot_count = 0
  1817. if self.dogshoot_switch == 1 then
  1818. self.dogshoot_switch = 2
  1819. else
  1820. self.dogshoot_switch = 1
  1821. end
  1822. end
  1823. return self.dogshoot_switch
  1824. end
  1825. -- execute current state (stand, walk, run, attacks)
  1826. local function do_states(self, dtime)
  1827. local yaw = self.object:get_yaw()
  1828. -- Stupid spurious bugs.
  1829. if not yaw then
  1830. return
  1831. end
  1832. if self.state == "stand" then
  1833. if random(1, 4) == 1 then
  1834. local lp = nil
  1835. local s = self.object:get_pos()
  1836. local objs = minetest.get_objects_inside_radius(s, 3)
  1837. for n = 1, #objs do
  1838. if objs[n]:is_player() then
  1839. lp = objs[n]:get_pos()
  1840. break
  1841. end
  1842. end
  1843. -- look at any players nearby, otherwise turn randomly
  1844. if lp then
  1845. local yaw = compute_yaw_to_target(self, lp, s)
  1846. else
  1847. yaw = yaw + random(-0.5, 0.5)
  1848. end
  1849. yaw = set_yaw(self, yaw, 8)
  1850. end
  1851. set_velocity(self, 0)
  1852. set_animation(self, "stand")
  1853. -- npc's ordered to stand stay standing
  1854. --if self.type ~= "npc"
  1855. if self.order ~= "stand" then
  1856. if self.walk_chance ~= 0
  1857. and self.facing_fence ~= true
  1858. and random(1, 100) <= self.walk_chance
  1859. and is_at_cliff(self) == false then
  1860. set_velocity(self, self.walk_velocity or 0)
  1861. self.state = "walk"
  1862. set_animation(self, "walk")
  1863. --[[ fly up/down randomly for flying mobs
  1864. if self.fly and random(1, 100) <= self.walk_chance then
  1865. local v = self.object:get_velocity()
  1866. local ud = random(-1, 2) / 9
  1867. self.object:set_velocity({x = v.x, y = ud, z = v.z})
  1868. end--]]
  1869. end
  1870. end
  1871. elseif self.state == "walk" then
  1872. local s = self.object:get_pos()
  1873. local lp = nil
  1874. -- is there something I need to avoid?
  1875. if self.water_damage > 0
  1876. and self.lava_damage > 0 then
  1877. lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
  1878. elseif self.water_damage > 0 then
  1879. lp = minetest.find_node_near(s, 1, {"group:water"})
  1880. elseif self.lava_damage > 0 then
  1881. lp = minetest.find_node_near(s, 1, {"group:lava"})
  1882. end
  1883. if lp then
  1884. -- if mob in water or lava then look for land
  1885. local ndef = minetest.reg_ns_nodes[self.standing_in]
  1886. if (self.lava_damage and ndef and ndef.groups.lava)
  1887. or (self.water_damage and ndef and ndef.groups.water) then
  1888. lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
  1889. "group:sand", node_ice, node_snowblock})
  1890. -- did we find land?
  1891. if lp then
  1892. local yaw = compute_yaw_to_target(self, lp, s)
  1893. -- look towards land and jump/move in that direction
  1894. yaw = set_yaw(self, yaw, 6)
  1895. do_jump(self)
  1896. set_velocity(self, self.walk_velocity or 0)
  1897. else
  1898. yaw = yaw + random(-0.5, 0.5)
  1899. end
  1900. else
  1901. local yaw = compute_yaw_to_target(self, lp, s)
  1902. end
  1903. yaw = set_yaw(self, yaw, 8)
  1904. -- otherwise randomly turn
  1905. elseif random(1, 100) <= 30 then
  1906. yaw = yaw + random(-0.5, 0.5)
  1907. yaw = set_yaw(self, yaw, 8)
  1908. end
  1909. -- stand for great fall in front
  1910. local temp_is_cliff = is_at_cliff(self)
  1911. if self.facing_fence == true
  1912. or temp_is_cliff
  1913. or random(1, 100) <= 30 then
  1914. set_velocity(self, 0)
  1915. self.state = "stand"
  1916. set_animation(self, "stand")
  1917. else
  1918. set_velocity(self, self.walk_velocity or 0)
  1919. if flight_check(self)
  1920. and self.animation
  1921. and self.animation.fly_start
  1922. and self.animation.fly_end then
  1923. set_animation(self, "fly")
  1924. else
  1925. set_animation(self, "walk")
  1926. end
  1927. end
  1928. -- runaway when punched
  1929. elseif self.state == "runaway" then
  1930. self.runaway_timer = self.runaway_timer + 1
  1931. -- stop after 5 seconds or when at cliff
  1932. if self.runaway_timer > 5
  1933. or is_at_cliff(self) then
  1934. self.runaway_timer = 0
  1935. set_velocity(self, 0)
  1936. self.state = "stand"
  1937. set_animation(self, "stand")
  1938. else
  1939. set_velocity(self, self.run_velocity or 0)
  1940. set_animation(self, "walk")
  1941. end
  1942. -- attack routines (explode, dogfight, shoot, dogshoot)
  1943. elseif self.state == "attack" then
  1944. -- calculate distance from mob and enemy
  1945. local s = self.object:get_pos()
  1946. local p = self.attack and self.attack:get_pos() or s
  1947. local dist = get_distance(p, s)
  1948. -- stop attacking if player invisible or out of range
  1949. if dist > self.view_range
  1950. or not self.attack
  1951. or not self.attack:get_pos()
  1952. or self.attack:get_hp() <= 0
  1953. or (self.attack:is_player() and mobs.is_invisible( self.attack:get_player_name() )) then
  1954. -- print(" ** stop attacking **", dist, self.view_range)
  1955. self.state = "stand"
  1956. set_velocity(self, 0)
  1957. set_animation(self, "stand")
  1958. self.attack = nil
  1959. self.v_start = false
  1960. self.timer = 0
  1961. self.blinktimer = 0
  1962. self.path.way = nil
  1963. return
  1964. end
  1965. if self.attack_type == "explode" then
  1966. local yaw = compute_yaw_to_target(self, p, s)
  1967. yaw = set_yaw(self, yaw)
  1968. local node_break_radius = self.explosion_radius or 1
  1969. local entity_damage_radius = self.explosion_damage_radius
  1970. or (node_break_radius * 2)
  1971. -- start timer when in reach and line of sight
  1972. if not self.v_start
  1973. and dist <= self.reach
  1974. and line_of_sight(self, s, p, 2) then
  1975. self.v_start = true
  1976. self.timer = 0
  1977. self.blinktimer = 0
  1978. mob_sound(self, self.sounds.fuse)
  1979. -- print ("=== explosion timer started", self.explosion_timer)
  1980. -- stop timer if out of reach or direct line of sight
  1981. elseif self.allow_fuse_reset
  1982. and self.v_start
  1983. and (dist > self.reach
  1984. or not line_of_sight(self, s, p, 2)) then
  1985. self.v_start = false
  1986. self.timer = 0
  1987. self.blinktimer = 0
  1988. self.blinkstatus = false
  1989. self.object:settexturemod("")
  1990. end
  1991. -- walk right up to player unless the timer is active
  1992. if self.v_start and (self.stop_to_explode or dist < 1.5) then
  1993. set_velocity(self, 0)
  1994. else
  1995. set_velocity(self, self.run_velocity or 0)
  1996. end
  1997. if self.animation and self.animation.run_start then
  1998. set_animation(self, "run")
  1999. else
  2000. set_animation(self, "walk")
  2001. end
  2002. if self.v_start then
  2003. self.timer = self.timer + dtime
  2004. self.blinktimer = (self.blinktimer or 0) + dtime
  2005. if self.blinktimer > 0.2 then
  2006. self.blinktimer = 0
  2007. if self.blinkstatus then
  2008. self.object:settexturemod("")
  2009. else
  2010. self.object:settexturemod("^[brighten")
  2011. end
  2012. self.blinkstatus = not self.blinkstatus
  2013. end
  2014. -- print ("=== explosion timer", self.timer)
  2015. if self.timer > self.explosion_timer then
  2016. local pos = self.object:get_pos()
  2017. -- dont damage anything if area protected or next to water
  2018. if minetest.find_node_near(pos, 1, {"group:water"})
  2019. or minetest.test_protection(pos, "") then
  2020. node_break_radius = 1
  2021. end
  2022. -- Mark for removal as last action on mob_step().
  2023. self.mkrm = true
  2024. if minetest.get_modpath("tnt") and tnt and tnt.boom then
  2025. tnt.boom(pos, {
  2026. radius = node_break_radius,
  2027. damage_radius = entity_damage_radius,
  2028. sound = self.sounds.explode,
  2029. })
  2030. else
  2031. minetest.sound_play(self.sounds.explode, {
  2032. pos = pos,
  2033. gain = 1.0,
  2034. max_hear_distance = self.sounds.distance or 32
  2035. })
  2036. entity_physics(pos, entity_damage_radius)
  2037. effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
  2038. end
  2039. return
  2040. end
  2041. end
  2042. elseif self.attack_type == "dogfight"
  2043. or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
  2044. or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
  2045. if self.fly
  2046. and dist > self.reach then
  2047. local p1 = s
  2048. local me_y = floor(p1.y)
  2049. local p2 = p
  2050. local p_y = floor(p2.y + 1)
  2051. local v = self.object:get_velocity()
  2052. if flight_check(self, s) then
  2053. if me_y < p_y then
  2054. self.object:set_velocity({
  2055. x = v.x,
  2056. y = 1 * self.walk_velocity,
  2057. z = v.z
  2058. })
  2059. elseif me_y > p_y then
  2060. self.object:set_velocity({
  2061. x = v.x,
  2062. y = -1 * self.walk_velocity,
  2063. z = v.z
  2064. })
  2065. end
  2066. else
  2067. if me_y < p_y then
  2068. self.object:set_velocity({
  2069. x = v.x,
  2070. y = 0.01,
  2071. z = v.z
  2072. })
  2073. elseif me_y > p_y then
  2074. self.object:set_velocity({
  2075. x = v.x,
  2076. y = -0.01,
  2077. z = v.z
  2078. })
  2079. end
  2080. end
  2081. end
  2082. -- rnd: new movement direction
  2083. if self.path.following
  2084. and self.path.way
  2085. and self.attack_type ~= "dogshoot" then
  2086. -- no paths longer than 50
  2087. if #self.path.way > 50
  2088. or dist < self.reach then
  2089. self.path.following = false
  2090. return
  2091. end
  2092. local p1 = self.path.way[1]
  2093. if not p1 then
  2094. self.path.following = false
  2095. return
  2096. end
  2097. --if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
  2098. -- must use `get_distance' and not `abs' because waypoint may be vertical from mob
  2099. if get_distance(p1, s) < 0.6 then
  2100. -- reached waypoint, remove it from queue
  2101. table.remove(self.path.way, 1)
  2102. end
  2103. -- set new temporary target
  2104. p = {x = p1.x, y = p1.y, z = p1.z}
  2105. end
  2106. -- flag should be set if mob is directly over its target and therefore should move more slowly
  2107. local overunder_waypoint = false
  2108. -- is mob directly over or under the target?
  2109. if abs(p.x - s.x) < 0.2 and abs(p.z - s.z) < 0.2 and abs(p.y - s.y) > 0.5 then
  2110. -- mob is directly over or under its waypoint/target
  2111. overunder_waypoint = true
  2112. end
  2113. local yaw = compute_yaw_to_target(self, p, s)
  2114. yaw = set_yaw(self, yaw)
  2115. -- move towards enemy if beyond mob reach
  2116. if dist > self.reach then
  2117. -- path finding by rnd
  2118. if self.pathfinding and self.pathfinding ~= 0 -- only if mob has pathfinding enabled
  2119. and enable_pathfinding then
  2120. smart_mobs(self, s, p, dist, dtime)
  2121. end
  2122. ---[[
  2123. if is_at_cliff(self) then
  2124. set_velocity(self, 0)
  2125. set_animation(self, "stand")
  2126. else
  2127. if self.path.stuck then
  2128. set_velocity(self, self.walk_velocity or 0)
  2129. else --]]
  2130. if overunder_waypoint then
  2131. set_velocity(self, 0.1) ---[[
  2132. else
  2133. set_velocity(self, self.run_velocity or 0) ---[[
  2134. end
  2135. end
  2136. if not overunder_waypoint then
  2137. if self.animation and self.animation.run_start then
  2138. set_animation(self, "run")
  2139. else
  2140. set_animation(self, "walk")
  2141. end
  2142. else
  2143. set_animation(self, "stand")
  2144. end
  2145. end
  2146. --]]
  2147. else -- rnd: if inside reach range
  2148. self.path.stuck = false
  2149. self.path.stuck_timer = 0
  2150. self.path.following = false -- not stuck anymore
  2151. set_velocity(self, 0)
  2152. if not self.custom_attack then
  2153. if self.timer > 1 then
  2154. self.timer = 0
  2155. -- if self.double_melee_attack
  2156. -- and random(1, 2) == 1 then
  2157. -- set_animation(self, "punch2")
  2158. -- else
  2159. set_animation(self, "punch")
  2160. -- end
  2161. local p2 = p
  2162. local s2 = s
  2163. p2.y = p2.y + .5
  2164. s2.y = s2.y + .5
  2165. if line_of_sight(self, p2, s2) == true then
  2166. -- play attack sound
  2167. mob_sound(self, self.sounds.attack)
  2168. local targetname = (self.attack:is_player() and self.attack:get_player_name() or "")
  2169. -- punch player (or what player is attached to)
  2170. local attached = self.attack:get_attach()
  2171. if attached or default.player_attached[targetname] then
  2172. -- Mob has a chance of removing the player from whatever they're attached to.
  2173. if self.attack:is_player() and random(1, 5) == 1 then
  2174. utility.detach_player_with_message(self.attack)
  2175. elseif attached then
  2176. self.attack = attached
  2177. end
  2178. end
  2179. -- Don't bother the admin.
  2180. if not gdac.player_is_admin(targetname) then
  2181. local dmg1 = self.damage or 0
  2182. local dmg2 = math_random(self.damage_min or 0, self.damage_max or 0)
  2183. local dmg = dmg1
  2184. if dmg2 > dmg1 then
  2185. dmg = dmg2
  2186. end
  2187. self.attack:punch(self.object, 1.0, {
  2188. full_punch_interval = 1.0,
  2189. damage_groups = {fleshy = dmg}
  2190. }, nil)
  2191. ambiance.sound_play("default_punch", self.attack:get_pos(), 2.0, 30)
  2192. --mob_sound(self, "default_punch")
  2193. end
  2194. -- report death!
  2195. if self.attack:is_player() and self.attack:get_hp() <= 0 then
  2196. mob_killed_player(self, self.attack)
  2197. self.attack = nil -- stop attacking
  2198. end
  2199. end
  2200. end
  2201. else -- call custom attack every second
  2202. if self.custom_attack
  2203. and self.timer > 1 then
  2204. self.timer = 0
  2205. self.custom_attack(self, p)
  2206. end
  2207. end
  2208. end
  2209. elseif self.attack_type == "shoot"
  2210. or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
  2211. or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
  2212. p.y = p.y - .5
  2213. s.y = s.y + .5
  2214. local dist = get_distance(p, s)
  2215. local yaw = compute_yaw_to_target(self, p, s)
  2216. local vec = { -- vec is needed elsewhere
  2217. x = p.x - s.x,
  2218. y = p.y - s.y,
  2219. z = p.z - s.z
  2220. }
  2221. yaw = set_yaw(self, yaw)
  2222. set_velocity(self, 0)
  2223. if self.shoot_interval
  2224. and self.timer > self.shoot_interval
  2225. and random(1, 100) <= 60 then
  2226. self.timer = 0
  2227. set_animation(self, "shoot")
  2228. -- play shoot attack sound
  2229. mob_sound(self, self.sounds.shoot_attack)
  2230. local p = self.object:get_pos()
  2231. p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
  2232. if minetest.registered_entities[self.arrow] then
  2233. local obj = minetest.add_entity(p, self.arrow)
  2234. local ent = obj:get_luaentity()
  2235. local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
  2236. local v = ent.velocity or 1 -- or set to default
  2237. ent.switch = 1
  2238. ent.owner_id = tostring(self.object) -- add unique owner id to arrow
  2239. -- offset makes shoot aim accurate
  2240. vec.y = vec.y + self.shoot_offset
  2241. vec.x = vec.x * (v / amount)
  2242. vec.y = vec.y * (v / amount)
  2243. vec.z = vec.z * (v / amount)
  2244. obj:set_velocity(vec)
  2245. end
  2246. end
  2247. end
  2248. end
  2249. end
  2250. -- falling and fall damage
  2251. local function falling(self, pos)
  2252. if self.fly then
  2253. return
  2254. end
  2255. -- floating in water (or falling)
  2256. local v = self.object:get_velocity()
  2257. if v.y > 0 then
  2258. -- apply gravity when moving up
  2259. self.object:set_acceleration({
  2260. x = 0,
  2261. y = -10,
  2262. z = 0
  2263. })
  2264. elseif v.y <= 0 and v.y > self.fall_speed then
  2265. -- fall downwards at set speed
  2266. self.object:set_acceleration({
  2267. x = 0,
  2268. y = self.fall_speed,
  2269. z = 0
  2270. })
  2271. else
  2272. -- stop accelerating once max fall speed hit
  2273. self.object:set_acceleration({x = 0, y = 0, z = 0})
  2274. end
  2275. -- If in water then float up. Nil check.
  2276. local ndef = self.standing_in and minetest.reg_ns_nodes[self.standing_in]
  2277. if ndef and ndef.groups.water then
  2278. if self.floats == 1 then
  2279. self.object:set_acceleration({
  2280. x = 0,
  2281. y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
  2282. z = 0
  2283. })
  2284. end
  2285. else
  2286. -- fall damage onto solid ground
  2287. if self.fall_damage == 1
  2288. and self.object:get_velocity().y == 0 then
  2289. local d = (self.old_y or 0) - self.object:get_pos().y
  2290. if d > 5 then
  2291. self.health = self.health - floor(d - 5)
  2292. effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
  2293. if check_for_death(self, "fall", {type = "fall"}) then
  2294. return
  2295. end
  2296. end
  2297. self.old_y = self.object:get_pos().y
  2298. end
  2299. end
  2300. end
  2301. -- is Took Ranks mod active?
  2302. local tr = minetest.get_modpath("toolranks")
  2303. -- deal damage and effects when mob punched
  2304. local function mob_punch(self, hitter, tflp, tool_capabilities, dir)
  2305. -- Record name of last attacker.
  2306. self.last_attacked_by = (hitter and hitter:is_player() and hitter:get_player_name()) or ""
  2307. -- custom punch function
  2308. if self.do_punch then
  2309. -- when false skip going any further
  2310. if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
  2311. return
  2312. end
  2313. end
  2314. -- mob health check
  2315. -- if self.health <= 0 then
  2316. -- return
  2317. -- end
  2318. -- error checking when mod profiling is enabled
  2319. if not tool_capabilities then
  2320. minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
  2321. return
  2322. end
  2323. -- is mob protected?
  2324. if self.protected and hitter:is_player()
  2325. and minetest.test_protection(v_round(self.object:get_pos()), hitter:get_player_name()) then
  2326. minetest.chat_send_player(hitter:get_player_name(), "# Server: Mob has been protected!")
  2327. return
  2328. end
  2329. -- weapon wear
  2330. local weapon = hitter:get_wielded_item()
  2331. --minetest.log("weapon: <" .. weapon:get_name() .. ">")
  2332. local punch_interval = 1.4
  2333. -- calculate mob damage
  2334. local damage = 0
  2335. local armor = self.object:get_armor_groups() or {}
  2336. local tmp
  2337. -- quick error check incase it ends up 0 (serialize.h check test)
  2338. if tflp == 0 then
  2339. tflp = 0.2
  2340. end
  2341. do
  2342. for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
  2343. tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
  2344. if tmp < 0 then
  2345. tmp = 0.0
  2346. elseif tmp > 1 then
  2347. tmp = 1.0
  2348. end
  2349. damage = damage + (tool_capabilities.damage_groups[group] or 0)
  2350. * tmp * ((armor[group] or 0) / 100.0)
  2351. end
  2352. end
  2353. -- check for tool immunity or special damage
  2354. for n = 1, #self.immune_to do
  2355. if self.immune_to[n][1] == weapon:get_name() then
  2356. damage = self.immune_to[n][2] or 0
  2357. break
  2358. -- if "all" then no tool does damage unless it's specified in list
  2359. elseif self.immune_to[n][1] == "all" then
  2360. damage = self.immune_to[n][2] or 0
  2361. end
  2362. end
  2363. -- healing
  2364. if damage <= -1 then
  2365. self.health = self.health - floor(damage)
  2366. return
  2367. end
  2368. -- print ("Mob Damage is", damage)
  2369. -- add weapon wear
  2370. if tool_capabilities then
  2371. punch_interval = tool_capabilities.full_punch_interval or 1.4
  2372. end
  2373. if weapon:get_definition()
  2374. and weapon:get_definition().tool_capabilities then
  2375. -- toolrank support
  2376. local wear = floor((punch_interval / 75) * 9000)
  2377. if mobs.is_creative(hitter:get_player_name()) then
  2378. if tr then
  2379. wear = 1
  2380. else
  2381. wear = 0
  2382. end
  2383. end
  2384. if tr then
  2385. if weapon:get_definition()
  2386. and weapon:get_definition().original_description then
  2387. weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
  2388. end
  2389. else
  2390. weapon:add_wear(wear)
  2391. end
  2392. hitter:set_wielded_item(weapon)
  2393. end
  2394. -- only play hit sound and show blood effects if damage is 1 or over
  2395. if damage >= 1 then
  2396. -- weapon sounds
  2397. if weapon:get_definition().sounds ~= nil then
  2398. local s = random(0, #weapon:get_definition().sounds)
  2399. minetest.sound_play(weapon:get_definition().sounds[s], {
  2400. object = self.object, --hitter,
  2401. max_hear_distance = 20
  2402. })
  2403. else
  2404. minetest.sound_play("default_punch", {
  2405. object = self.object, --hitter,
  2406. max_hear_distance = 20
  2407. })
  2408. end
  2409. -- blood_particles
  2410. if self.blood_amount > 0
  2411. and not disable_blood then
  2412. local pos = self.object:get_pos()
  2413. pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
  2414. -- do we have a single blood texture or multiple?
  2415. if type(self.blood_texture) == "table" then
  2416. local blood = self.blood_texture[random(1, #self.blood_texture)]
  2417. effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
  2418. else
  2419. effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
  2420. end
  2421. end
  2422. -- do damage
  2423. self.health = self.health - floor(damage)
  2424. -- exit here if dead, special item check
  2425. if weapon:get_name() == "mobs:pick_lava" then
  2426. if check_for_death(self, "lava", {
  2427. type = "punch",
  2428. puncher = hitter,
  2429. tool_capabilities = tool_capabilities,
  2430. wielded = weapon,
  2431. }) then
  2432. return
  2433. end
  2434. else
  2435. if check_for_death(self, "hit", {
  2436. type = "punch",
  2437. puncher = hitter,
  2438. tool_capabilities = tool_capabilities,
  2439. wielded = weapon,
  2440. }) then
  2441. return
  2442. end
  2443. end
  2444. --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
  2445. minetest.after(0.1, function()
  2446. if not self.object:get_luaentity() then return end
  2447. self.object:settexturemod("^[colorize:#c9900070")
  2448. core.after(0.3, function()
  2449. self.object:settexturemod("")
  2450. end)
  2451. end) ]]
  2452. -- knock back effect (only on full punch)
  2453. if self.knock_back
  2454. and tflp >= punch_interval then
  2455. local v = self.object:get_velocity()
  2456. local r = 1.4 - min(punch_interval, 1.4)
  2457. local kb = r * 5
  2458. local up = 2
  2459. -- if already in air then dont go up anymore when hit
  2460. if v.y > 0
  2461. or self.fly then
  2462. up = 0
  2463. end
  2464. -- direction error check
  2465. dir = dir or {x = 0, y = 0, z = 0}
  2466. -- check if tool already has specific knockback value
  2467. if tool_capabilities.damage_groups["knockback"] then
  2468. kb = tool_capabilities.damage_groups["knockback"]
  2469. else
  2470. kb = kb * default_knockback
  2471. end
  2472. self.object:set_velocity({
  2473. x = dir.x * kb,
  2474. y = up,
  2475. z = dir.z * kb
  2476. })
  2477. self.pause_timer = 0.25
  2478. end
  2479. end -- END if damage
  2480. -- if skittish then run away
  2481. if self.runaway == true then
  2482. local lp = hitter:get_pos()
  2483. local s = self.object:get_pos()
  2484. local yaw = compute_yaw_to_target(self, lp, s)
  2485. yaw = yaw + pi -- go in reverse
  2486. yaw = set_yaw(self, yaw, 6)
  2487. self.state = "runaway"
  2488. self.runaway_timer = 0
  2489. self.following = nil
  2490. end
  2491. local name = (hitter:is_player() and hitter:get_player_name()) or ""
  2492. --minetest.chat_send_player("MustTest", "Attack!")
  2493. -- attack puncher and call other mobs for help
  2494. if (self.passive == false or self.attack_players == true)
  2495. and self.state ~= "flop"
  2496. and self.child == false
  2497. and name ~= "" and name ~= self.owner
  2498. and not mobs.is_invisible( name ) then
  2499. --minetest.chat_send_player("MustTest", "Will really attack!")
  2500. -- attack whoever punched mob
  2501. self.state = ""
  2502. do_attack(self, hitter)
  2503. -- alert others to the attack
  2504. local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
  2505. local obj = nil
  2506. for n = 1, #objs do
  2507. obj = objs[n]:get_luaentity()
  2508. if obj and obj._cmi_is_mob then
  2509. -- only alert members of same mob
  2510. if obj.group_attack == true
  2511. and obj.state ~= "attack"
  2512. and obj.owner ~= name
  2513. and obj.name == self.name then
  2514. do_attack(obj, hitter)
  2515. end
  2516. -- have owned mobs attack player threat
  2517. if obj.owner == name and obj.owner_loyal then
  2518. do_attack(obj, self.object)
  2519. end
  2520. end
  2521. end
  2522. end
  2523. end
  2524. -- export!
  2525. function mobs.mob_punch(self, hitter, tflp, tool_capabilities, dir)
  2526. return mob_punch(self, hitter, tflp, tool_capabilities, dir)
  2527. end
  2528. -- get entity staticdata
  2529. local function mob_staticdata(self)
  2530. -- remove mob when out of range unless tamed
  2531. if remove_far
  2532. and self.remove_ok
  2533. and self.type ~= "npc"
  2534. and self.state ~= "attack"
  2535. and not self.tamed
  2536. and self.lifetimer < 20000 then
  2537. --print ("REMOVED " .. self.name)
  2538. -- Mark for removal as last action on mob_step().
  2539. self.mkrm = true
  2540. return ""-- nil
  2541. end
  2542. self.remove_ok = true
  2543. self.attack = nil
  2544. self.following = nil
  2545. self.state = "stand"
  2546. -- used to rotate older mobs
  2547. if self.drawtype
  2548. and self.drawtype == "side" then
  2549. self.rotate = math.rad(90)
  2550. end
  2551. local tmp = {}
  2552. for _,stat in pairs(self) do
  2553. local t = type(stat)
  2554. if t ~= "function"
  2555. and t ~= "nil"
  2556. and t ~= "userdata"
  2557. and _ ~= "_cmi_components" then
  2558. tmp[_] = self[_]
  2559. end
  2560. end
  2561. --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
  2562. return minetest.serialize(tmp)
  2563. end
  2564. -- export!
  2565. function mobs.mob_staticdata(self)
  2566. return mob_staticdata(self)
  2567. end
  2568. -- activate mob and reload settings
  2569. local function mob_activate(self, staticdata, def, dtime)
  2570. -- Remove mob if activated during daytime and has 'daytime_despawn'.
  2571. if self.daytime_despawn then
  2572. local tod = (minetest.get_timeofday() or 0) * 24000
  2573. if tod > 4500 and tod < 19500 then
  2574. -- Daylight, but mob despawns at daytime.
  2575. -- Mark for removal as last action on mob_step().
  2576. self.mkrm = true
  2577. return
  2578. end
  2579. end
  2580. -- load entity variables
  2581. local tmp = minetest.deserialize(staticdata)
  2582. if tmp then
  2583. for _, stat in pairs(tmp) do
  2584. self[_] = stat
  2585. end
  2586. end
  2587. -- Do select random texture, set model and size.
  2588. if not self.base_texture then
  2589. -- Do compatiblity with old simple mobs textures.
  2590. if def.textures and type(def.textures[1]) == "string" then
  2591. def.textures = {def.textures}
  2592. end
  2593. self.base_texture = def.textures and def.textures[random(1, #def.textures)]
  2594. self.base_mesh = def.mesh
  2595. self.base_size = self.visual_size
  2596. self.base_colbox = self.collisionbox
  2597. self.base_selbox = self.selectionbox
  2598. end
  2599. -- for current mobs that dont have this set
  2600. if not self.base_selbox then
  2601. self.base_selbox = self.selectionbox or self.base_colbox
  2602. end
  2603. -- set texture, model and size
  2604. local textures = self.base_texture
  2605. local mesh = self.base_mesh
  2606. local vis_size = self.base_size
  2607. local colbox = self.base_colbox
  2608. local selbox = self.base_selbox
  2609. -- specific texture if gotten
  2610. if self.gotten == true
  2611. and def.gotten_texture then
  2612. textures = def.gotten_texture
  2613. end
  2614. -- specific mesh if gotten
  2615. if self.gotten == true
  2616. and def.gotten_mesh then
  2617. mesh = def.gotten_mesh
  2618. end
  2619. -- set child objects to half size
  2620. if self.child == true then
  2621. vis_size = {
  2622. x = self.base_size.x * .5,
  2623. y = self.base_size.y * .5,
  2624. }
  2625. if def.child_texture then
  2626. textures = def.child_texture[1]
  2627. end
  2628. colbox = {
  2629. self.base_colbox[1] * .5,
  2630. self.base_colbox[2] * .5,
  2631. self.base_colbox[3] * .5,
  2632. self.base_colbox[4] * .5,
  2633. self.base_colbox[5] * .5,
  2634. self.base_colbox[6] * .5
  2635. }
  2636. selbox = {
  2637. self.base_selbox[1] * .5,
  2638. self.base_selbox[2] * .5,
  2639. self.base_selbox[3] * .5,
  2640. self.base_selbox[4] * .5,
  2641. self.base_selbox[5] * .5,
  2642. self.base_selbox[6] * .5
  2643. }
  2644. end
  2645. if self.health == 0 then
  2646. self.health = random (self.hp_min, self.hp_max)
  2647. end
  2648. -- pathfinding init
  2649. self.path = {}
  2650. self.path.way = {} -- path to follow, table of positions
  2651. self.path.lastpos = {x = 0, y = 0, z = 0}
  2652. self.path.stuck = false
  2653. self.path.following = false -- currently following path?
  2654. self.path.stuck_timer = 0 -- if stuck for too long search for path
  2655. self.path.putnode_timer = 0
  2656. -- mob defaults
  2657. self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
  2658. self.old_y = self.object:get_pos().y
  2659. self.old_health = self.health
  2660. self.sounds.distance = self.sounds.distance or 10
  2661. self.textures = textures
  2662. self.mesh = mesh
  2663. self.collisionbox = colbox
  2664. self.selectionbox = selbox
  2665. self.visual_size = vis_size
  2666. self.standing_in = "air"
  2667. self.standing_on = "air"
  2668. -- check existing nametag
  2669. if not self.nametag then
  2670. self.nametag = def.nametag
  2671. end
  2672. -- set anything changed above
  2673. self.object:set_properties(self)
  2674. set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
  2675. update_tag(self)
  2676. set_animation(self, "stand")
  2677. -- run on_spawn function if found
  2678. if self.on_spawn and not self.on_spawn_run then
  2679. if self.on_spawn(self) then
  2680. self.on_spawn_run = true -- if true, set flag to run once only
  2681. end
  2682. end
  2683. -- run after_activate
  2684. if def.after_activate then
  2685. def.after_activate(self, staticdata, def, dtime)
  2686. end
  2687. end
  2688. -- export!
  2689. function mobs.mob_activate(self, staticdata, def, dtime)
  2690. return mob_activate(self, staticdata, def, dtime)
  2691. end
  2692. local function smooth_rotate(self)
  2693. -- smooth rotation by ThomasMonroe314
  2694. if self.delay and self.delay > 0 then
  2695. local yaw = self.object:get_yaw()
  2696. if self.delay == 1 then
  2697. yaw = self.target_yaw
  2698. else
  2699. local dif = abs(yaw - self.target_yaw)
  2700. if yaw > self.target_yaw then
  2701. if dif > pi then
  2702. dif = 2 * pi - dif -- need to add
  2703. yaw = yaw + dif / self.delay
  2704. else
  2705. yaw = yaw - dif / self.delay -- need to subtract
  2706. end
  2707. elseif yaw < self.target_yaw then
  2708. if dif > pi then
  2709. dif = 2 * pi - dif
  2710. yaw = yaw - dif / self.delay -- need to subtract
  2711. else
  2712. yaw = yaw + dif / self.delay -- need to add
  2713. end
  2714. end
  2715. if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
  2716. if yaw < 0 then yaw = yaw + (pi * 2) end
  2717. end
  2718. self.delay = self.delay - 1
  2719. self.object:set_yaw(yaw)
  2720. end
  2721. -- end rotation
  2722. end
  2723. -- main mob function
  2724. local function mob_step(self, dtime)
  2725. -- The final (actually first) action of mob_step():
  2726. -- if the mob was marked for removal, we call :remove() here.
  2727. -- :remove() should not be called anywhere else!
  2728. if self.mkrm then
  2729. self.object:remove()
  2730. return
  2731. end
  2732. local pos = self.object:get_pos()
  2733. if not pos then return end -- Stupid spurious errors.
  2734. local yaw = 0
  2735. -- when lifetimer expires remove mob (except npc and tamed)
  2736. if self.type ~= "npc"
  2737. and not self.tamed
  2738. and self.state ~= "attack"
  2739. and remove_far ~= true
  2740. and self.lifetimer < 20000 then
  2741. self.lifetimer = self.lifetimer - dtime
  2742. if self.lifetimer <= 0 then
  2743. -- only despawn away from player
  2744. local objs = minetest.get_objects_inside_radius(pos, 15)
  2745. for n = 1, #objs do
  2746. if objs[n]:is_player() then
  2747. self.lifetimer = 20
  2748. return
  2749. end
  2750. end
  2751. effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
  2752. -- Mark for removal as last action on mob_step().
  2753. self.mkrm = true
  2754. return
  2755. end
  2756. end
  2757. -- get node at foot level every quarter second
  2758. self.node_timer = (self.node_timer or 0) + dtime
  2759. if self.node_timer > 0.25 then
  2760. self.node_timer = 0
  2761. local y_level = self.collisionbox[2]
  2762. if self.child then
  2763. y_level = self.collisionbox[2] * 0.5
  2764. end
  2765. -- what is mob standing in?
  2766. self.standing_in = node_ok({
  2767. x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
  2768. -- print ("standing in " .. self.standing_in)
  2769. self.standing_on = node_ok({
  2770. x = pos.x, y = ((pos.y + y_level) - 0.5), z = pos.z}, "air").name
  2771. -- print ("standing on " .. self.standing_on)
  2772. end
  2773. -- check if falling, flying, floating
  2774. falling(self, pos)
  2775. -- Do smooth rotation.
  2776. smooth_rotate(self)
  2777. -- knockback timer
  2778. if self.pause_timer > 0 then
  2779. self.pause_timer = self.pause_timer - dtime
  2780. return
  2781. end
  2782. -- run custom function (defined in mob lua file)
  2783. if self.do_custom then
  2784. -- when false skip going any further
  2785. if self.do_custom(self, dtime) == false then
  2786. return
  2787. end
  2788. end
  2789. -- attack timer
  2790. self.timer = self.timer + dtime
  2791. if self.state ~= "attack" then
  2792. if self.timer < 1 then
  2793. return
  2794. end
  2795. self.timer = 0
  2796. end
  2797. -- never go over 100
  2798. if self.timer > 100 then
  2799. self.timer = 1
  2800. end
  2801. -- mob plays random sound at times
  2802. if random(1, 100) == 1 then
  2803. mob_sound(self, self.sounds.random)
  2804. end
  2805. -- environmental damage timer (every 1 second)
  2806. self.env_damage_timer = self.env_damage_timer + dtime
  2807. if (self.state == "attack" and self.env_damage_timer > 1)
  2808. or self.state ~= "attack" then
  2809. self.env_damage_timer = 0
  2810. -- check for environmental damage (water, fire, lava etc.)
  2811. do_env_damage(self)
  2812. -- node replace check (cow eats grass etc.)
  2813. replace(self, pos)
  2814. end
  2815. general_attack(self)
  2816. breed(self)
  2817. follow_flop(self)
  2818. do_states(self, dtime)
  2819. do_jump(self)
  2820. runaway_from(self)
  2821. end
  2822. -- export!
  2823. function mobs.mob_step(self, dtime)
  2824. return mob_step(self, dtime)
  2825. end
  2826. -- Default function when mobs are blown up with TNT.
  2827. local function do_tnt(obj, damage)
  2828. obj.object:punch(obj.object, 1.0, {
  2829. full_punch_interval = 1.0,
  2830. damage_groups = {fleshy = damage},
  2831. }, nil)
  2832. return false, true, {}
  2833. end
  2834. -- export!
  2835. function mobs.do_tnt(obj, damage)
  2836. return do_tnt(obj, damage)
  2837. end
  2838. local function first_or_second(arg1, arg2)
  2839. if type(arg1) ~= "nil" then
  2840. return arg1
  2841. else
  2842. return arg2
  2843. end
  2844. end
  2845. -- register mob entity function
  2846. if not mobs.registered then
  2847. mobs.spawning_mobs = {}
  2848. -- Register mob function.
  2849. mobs.register_mob = function(name, def)
  2850. mobs.spawning_mobs[name] = true
  2851. minetest.register_entity(name, {
  2852. -- Warning: this parameter is set by the engine anway!
  2853. name = name,
  2854. _name = name,
  2855. mob = true, -- Object is a mob.
  2856. type = def.type,
  2857. armor_level = def.armor_level or 0,
  2858. description = def.description,
  2859. stepheight = def.stepheight or 1.1, -- was 0.6
  2860. attack_type = def.attack_type,
  2861. fly = def.fly,
  2862. fly_in = def.fly_in or "air",
  2863. owner = def.owner or "",
  2864. order = def.order or "",
  2865. on_die = def.on_die,
  2866. do_custom = def.do_custom,
  2867. jump_height = def.jump_height or 4, -- was 6
  2868. drawtype = def.drawtype, -- DEPRECATED, use rotate instead
  2869. rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
  2870. lifetimer = def.lifetimer or 180, -- 3 minutes
  2871. hp_min = (def.hp_min or 5) * difficulty,
  2872. hp_max = (def.hp_max or 10) * difficulty,
  2873. physical = true,
  2874. collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
  2875. selectionbox = def.selectionbox or def.collisionbox,
  2876. visual = def.visual,
  2877. visual_size = def.visual_size or {x = 1, y = 1},
  2878. mesh = def.mesh,
  2879. makes_footstep_sound = def.makes_footstep_sound or false,
  2880. view_range = def.view_range or 5,
  2881. walk_velocity = def.walk_velocity or 1,
  2882. run_velocity = def.run_velocity or 2,
  2883. -- Mob always does at least this amount of damage.
  2884. -- But if random damage between min and max would be greater,
  2885. -- then that damage is done instead.
  2886. damage = (def.damage or 0) * difficulty,
  2887. damage_min = (def.damage_min or 0) * difficulty,
  2888. damage_max = (def.damage_max or 0) * difficulty,
  2889. daytime_despawn = def.daytime_despawn,
  2890. on_despawn = def.on_despawn,
  2891. light_damage = def.light_damage or 0,
  2892. water_damage = def.water_damage or 0,
  2893. lava_damage = def.lava_damage or 0,
  2894. suffocation = def.suffocation or 2,
  2895. lava_annihilates = first_or_second(def.lava_annihilates, true),
  2896. makes_bones_in_lava = first_or_second(def.makes_bones_in_lava, true),
  2897. fall_damage = def.fall_damage or 1,
  2898. fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
  2899. drops = def.drops or {},
  2900. armor = def.armor or 100,
  2901. on_rightclick = def.on_rightclick,
  2902. arrow = def.arrow,
  2903. shoot_interval = def.shoot_interval,
  2904. sounds = def.sounds or {},
  2905. animation = def.animation,
  2906. follow = def.follow,
  2907. jump = def.jump ~= false,
  2908. walk_chance = def.walk_chance or 50,
  2909. attacks_monsters = def.attacks_monsters or false,
  2910. --fov = def.fov or 120,
  2911. passive = def.passive or false,
  2912. knock_back = def.knock_back ~= false,
  2913. blood_amount = def.blood_amount or 5,
  2914. blood_texture = def.blood_texture or "mobs_blood.png",
  2915. shoot_offset = def.shoot_offset or 0,
  2916. floats = def.floats or 1, -- floats in water by default
  2917. replace_rate = def.replace_rate,
  2918. replace_what = def.replace_what,
  2919. replace_with = def.replace_with,
  2920. replace_offset = def.replace_offset or 0,
  2921. on_replace = def.on_replace,
  2922. -- Feature added by MustTest.
  2923. replace_range = def.replace_range or 1,
  2924. despawns_in_dark_caves = def.despawns_in_dark_caves or false,
  2925. timer = 0,
  2926. env_damage_timer = 0, -- only used when state = "attack"
  2927. tamed = false,
  2928. pause_timer = 0,
  2929. horny = false,
  2930. hornytimer = 0,
  2931. child = false,
  2932. gotten = false,
  2933. health = 0,
  2934. reach = def.reach or 3,
  2935. htimer = 0,
  2936. texture_list = def.textures,
  2937. child_texture = def.child_texture,
  2938. docile_by_day = def.docile_by_day or false,
  2939. time_of_day = 0.5,
  2940. fear_height = def.fear_height or 0,
  2941. runaway = def.runaway,
  2942. runaway_timer = 0,
  2943. pathfinding = def.pathfinding or 0,
  2944. instance_pathfinding_chance = def.instance_pathfinding_chance,
  2945. place_node = def.place_node,
  2946. immune_to = def.immune_to or {},
  2947. explosion_radius = def.explosion_radius,
  2948. explosion_damage_radius = def.explosion_damage_radius,
  2949. explosion_timer = def.explosion_timer or 3,
  2950. allow_fuse_reset = def.allow_fuse_reset ~= false,
  2951. stop_to_explode = def.stop_to_explode ~= false,
  2952. custom_attack = def.custom_attack,
  2953. double_melee_attack = def.double_melee_attack,
  2954. dogshoot_switch = def.dogshoot_switch,
  2955. dogshoot_count = 0,
  2956. dogshoot_count_max = def.dogshoot_count_max or 5,
  2957. dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
  2958. group_attack = def.group_attack or false,
  2959. attack_monsters = def.attacks_monsters or def.attack_monsters or false,
  2960. attack_animals = def.attack_animals or false,
  2961. attack_players = def.attack_players ~= false,
  2962. attack_npcs = def.attack_npcs ~= false,
  2963. specific_attack = def.specific_attack,
  2964. runaway_from = def.runaway_from,
  2965. owner_loyal = def.owner_loyal,
  2966. facing_fence = false,
  2967. _cmi_is_mob = true,
  2968. on_spawn = def.on_spawn,
  2969. on_blast = def.on_blast or function(...) return mobs.do_tnt(...) end,
  2970. on_step = function(...) return mobs.mob_step(...) end,
  2971. do_punch = def.do_punch,
  2972. on_punch = function(...) return mobs.mob_punch(...) end,
  2973. on_breed = def.on_breed,
  2974. on_grown = def.on_grown,
  2975. on_activate = function(self, staticdata, dtime)
  2976. return mobs.mob_activate(self, staticdata, def, dtime)
  2977. end,
  2978. get_staticdata = function(self)
  2979. return mobs.mob_staticdata(self)
  2980. end,
  2981. })
  2982. end -- END mobs:register_mob function
  2983. end
  2984. local function arrow_step(self, dtime, def)
  2985. self.timer = self.timer + 1
  2986. local pos = self.object:get_pos()
  2987. if self.switch == 0
  2988. or self.timer > 150
  2989. or not within_limits(pos, 0) then
  2990. self.object:remove() ; -- print ("removed arrow")
  2991. return
  2992. end
  2993. -- does arrow have a tail (fireball)
  2994. if def.tail
  2995. and def.tail == 1
  2996. and def.tail_texture then
  2997. minetest.add_particle({
  2998. pos = pos,
  2999. velocity = {x = 0, y = 0, z = 0},
  3000. acceleration = {x = 0, y = 0, z = 0},
  3001. expirationtime = def.expire or 0.25,
  3002. collisiondetection = false,
  3003. texture = def.tail_texture,
  3004. size = def.tail_size or 5,
  3005. glow = def.glow or 0,
  3006. })
  3007. end
  3008. if self.hit_node then
  3009. local node = node_ok(pos).name
  3010. local ndef = minetest.reg_ns_nodes[node]
  3011. if not ndef or ndef.walkable then
  3012. self.hit_node(self, pos, node)
  3013. if self.drop == true then
  3014. pos.y = pos.y + 1
  3015. self.lastpos = (self.lastpos or pos)
  3016. minetest.add_item(self.lastpos, self.object:get_luaentity().name)
  3017. end
  3018. self.object:remove() ; -- print ("hit node")
  3019. return
  3020. end
  3021. end
  3022. if self.hit_player or self.hit_mob then
  3023. for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.5)) do
  3024. if self.hit_player
  3025. and player:is_player() then
  3026. self.hit_player(self, player)
  3027. self.object:remove() ; -- print ("hit player")
  3028. return
  3029. end
  3030. local entity = player:get_luaentity()
  3031. if entity
  3032. and self.hit_mob
  3033. and entity._cmi_is_mob == true
  3034. and tostring(player) ~= self.owner_id
  3035. and entity.name ~= self.object:get_luaentity().name then
  3036. self.hit_mob(self, player)
  3037. self.object:remove() ; --print ("hit mob")
  3038. return
  3039. end
  3040. end
  3041. end
  3042. self.lastpos = pos
  3043. end
  3044. -- export!
  3045. function mobs.arrow_step(self, dtime, def)
  3046. return arrow_step(self, dtime, def)
  3047. end
  3048. -- register mob arrow entity function
  3049. if not mobs.registered then
  3050. -- register arrow for shoot attack
  3051. function mobs.register_arrow(name, def)
  3052. if not name or not def then return end -- errorcheck
  3053. minetest.register_entity(name, {
  3054. physical = false,
  3055. visual = def.visual,
  3056. visual_size = def.visual_size,
  3057. textures = def.textures,
  3058. velocity = def.velocity,
  3059. hit_player = def.hit_player,
  3060. hit_node = def.hit_node,
  3061. hit_mob = def.hit_mob,
  3062. drop = def.drop or false, -- drops arrow as registered item when true
  3063. collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
  3064. timer = 0,
  3065. switch = 0,
  3066. owner_id = def.owner_id,
  3067. rotate = def.rotate,
  3068. automatic_face_movement_dir = def.rotate
  3069. and (def.rotate - (pi / 180)) or false,
  3070. on_activate = def.on_activate,
  3071. on_step = def.on_step or function(self, dtime) return mobs.arrow_step(self, dtime, def) end,
  3072. })
  3073. end
  3074. end
  3075. -- Spawner item.
  3076. -- Note: This also introduces the “spawn_egg” group:
  3077. -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
  3078. -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
  3079. if not mobs.registered then
  3080. mobs.register_egg = function(mob, desc, background, addegg, no_creative)
  3081. local invimg = background
  3082. if addegg == 1 then
  3083. invimg = "mobs_egg.png^(" .. background .. "^[mask:mobs_egg_overlay.png)"
  3084. elseif addegg == 0 then
  3085. invimg = background
  3086. end
  3087. -- register new spawn egg containing mob information
  3088. minetest.register_craftitem(mob .. "_set", {
  3089. description = desc .. " Spawn Egg (Tamed)",
  3090. inventory_image = invimg,
  3091. groups = {not_in_creative_inventory=1, not_in_craft_guide=1, spawn_egg=2},
  3092. stack_max = 1,
  3093. on_place = function(itemstack, placer, pointed_thing)
  3094. local pos = pointed_thing.above
  3095. -- am I clicking on something with existing on_rightclick function?
  3096. local under = minetest.get_node(pointed_thing.under)
  3097. local def = minetest.reg_ns_nodes[under.name]
  3098. if def and def.on_rightclick then
  3099. return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
  3100. end
  3101. if pos and within_limits(pos, 0) then
  3102. if not minetest.registered_entities[mob] then
  3103. return
  3104. end
  3105. pos.y = pos.y + 1
  3106. local data = itemstack:get_metadata()
  3107. local mob = minetest.add_entity(pos, mob, data)
  3108. local ent = mob:get_luaentity()
  3109. if not ent then mob:remove()
  3110. minetest.chat_send_player(name, "# Server: Failed to retrieve creature!")
  3111. return
  3112. end
  3113. -- set owner if not a monster
  3114. if ent.type ~= "monster" then
  3115. ent.owner = placer:get_player_name()
  3116. ent.tamed = true
  3117. end
  3118. -- since mob is unique we remove egg once spawned
  3119. itemstack:take_item()
  3120. end
  3121. return itemstack
  3122. end,
  3123. })
  3124. -- register old stackable mob egg
  3125. minetest.register_craftitem(mob, {
  3126. description = desc .. " Spawn Egg",
  3127. inventory_image = invimg,
  3128. groups = {not_in_creative_inventory=1, not_in_craft_guide=1, spawn_egg=1},
  3129. on_place = function(itemstack, placer, pointed_thing)
  3130. local pos = pointed_thing.above
  3131. local name = placer:get_player_name()
  3132. if pos and within_limits(pos, 0) then
  3133. if not minetest.registered_entities[mob] then
  3134. return
  3135. end
  3136. pos.y = pos.y + 1
  3137. local mob = minetest.add_entity(pos, mob)
  3138. local ent = mob:get_luaentity()
  3139. if not ent then mob:remove()
  3140. minetest.chat_send_player(name, "# Server: Failed to create mob!")
  3141. return
  3142. end
  3143. -- don't set owner if monster or sneak pressed
  3144. if ent.type ~= "monster"
  3145. and not placer:get_player_control().sneak then
  3146. ent.owner = placer:get_player_name()
  3147. ent.tamed = true
  3148. end
  3149. end
  3150. itemstack:take_item()
  3151. return itemstack
  3152. end,
  3153. })
  3154. end
  3155. end
  3156. -- Capture critter (thanks to blert2112 for idea).
  3157. function mobs.capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
  3158. if self.child
  3159. or not clicker:is_player()
  3160. or not clicker:get_inventory() then
  3161. return false
  3162. end
  3163. -- get name of clicked mob
  3164. local mobname = self.name
  3165. -- if not nil change what will be added to inventory
  3166. if replacewith then
  3167. mobname = replacewith
  3168. end
  3169. local name = clicker:get_player_name()
  3170. local tool = clicker:get_wielded_item()
  3171. -- are we using hand, net or lasso to pick up mob?
  3172. if tool:get_name() ~= ""
  3173. and tool:get_name() ~= "mobs:net"
  3174. and tool:get_name() ~= "mobs:lasso" then
  3175. return false
  3176. end
  3177. -- Is mob tamed?
  3178. if self.tamed == false and force_take == false then
  3179. minetest.chat_send_player(name, "# Server: Animal not tamed!")
  3180. return true -- false
  3181. end
  3182. -- Cannot pick up if not owner.
  3183. if self.owner ~= name and force_take == false then
  3184. minetest.chat_send_player(name, "# Server: Player <" .. rename.gpn(self.owner) .. "> is owner!")
  3185. return true -- false
  3186. end
  3187. if clicker:get_inventory():room_for_item("main", mobname) then
  3188. -- Was mob clicked with hand, net, or lasso?
  3189. local chance = 0
  3190. local tool = clicker:get_wielded_item()
  3191. if tool:get_name() == "" then
  3192. chance = chance_hand
  3193. elseif tool:get_name() == "mobs:net" then
  3194. chance = chance_net
  3195. tool:add_wear(4000) -- 17 uses
  3196. clicker:set_wielded_item(tool)
  3197. elseif tool:get_name() == "mobs:lasso" then
  3198. chance = chance_lasso
  3199. tool:add_wear(650) -- 100 uses
  3200. clicker:set_wielded_item(tool)
  3201. end
  3202. -- calculate chance.. add to inventory if successful?
  3203. if chance > 0 and random(1, 100) <= chance then
  3204. -- default mob egg
  3205. local new_stack = ItemStack(mobname)
  3206. -- add special mob egg with all mob information
  3207. -- unless 'replacewith' contains new item to use
  3208. if not replacewith then
  3209. new_stack = ItemStack(mobname .. "_set")
  3210. local tmp = {}
  3211. for _,stat in pairs(self) do
  3212. local t = type(stat)
  3213. if t ~= "function"
  3214. and t ~= "nil"
  3215. and t ~= "userdata" then
  3216. tmp[_] = self[_]
  3217. end
  3218. end
  3219. local data_str = minetest.serialize(tmp)
  3220. new_stack:set_metadata(data_str)
  3221. end
  3222. local inv = clicker:get_inventory()
  3223. if inv:room_for_item("main", new_stack) then
  3224. inv:add_item("main", new_stack)
  3225. else
  3226. minetest.add_item(clicker:get_pos(), new_stack)
  3227. end
  3228. -- Mark for removal as last action on mob_step().
  3229. self.mkrm = true
  3230. mob_sound(self, "default_place_node_hard")
  3231. elseif chance ~= 0 then
  3232. minetest.chat_send_player(name, "# Server: Missed!")
  3233. mob_sound(self, "mobs_swing")
  3234. end
  3235. end
  3236. end
  3237. -- Make tables persistent even when file reloaded.
  3238. if not mobs.registered then
  3239. mobs.nametagdata = {}
  3240. mobs.nametagdata.mob_obj = {}
  3241. mobs.nametagdata.mob_sta = {}
  3242. end
  3243. local mob_obj = mobs.nametagdata.mob_obj
  3244. local mob_sta = mobs.nametagdata.mob_sta
  3245. -- Feeding, taming and breeding (thanks blert2112).
  3246. function mobs.feed_tame(self, clicker, feed_count, breed, tame)
  3247. if not self.follow then
  3248. return false
  3249. end
  3250. -- can eat/tame with item in hand
  3251. if follow_holding(self, clicker) then
  3252. -- if not in creative then take item
  3253. if not creative then
  3254. local item = clicker:get_wielded_item()
  3255. item:take_item()
  3256. clicker:set_wielded_item(item)
  3257. end
  3258. -- increase health
  3259. self.health = self.health + 4
  3260. if self.health >= self.hp_max then
  3261. self.health = self.hp_max
  3262. if self.htimer < 1 then
  3263. minetest.chat_send_player(clicker:get_player_name(), "# Server: Mob has full health!")
  3264. self.htimer = 5
  3265. end
  3266. end
  3267. self.object:set_hp(self.health)
  3268. update_tag(self)
  3269. -- make children grow quicker
  3270. if self.child == true then
  3271. self.hornytimer = self.hornytimer + 20
  3272. return true
  3273. end
  3274. -- feed and tame
  3275. self.food = (self.food or 0) + 1
  3276. if self.food >= feed_count then
  3277. self.food = 0
  3278. if breed and self.hornytimer == 0 then
  3279. self.horny = true
  3280. end
  3281. self.gotten = false
  3282. if tame then
  3283. if self.tamed == false then
  3284. minetest.chat_send_player(clicker:get_player_name(), "# Server: Mob has been tamed!")
  3285. end
  3286. self.tamed = true
  3287. if not self.owner or self.owner == "" then
  3288. self.owner = clicker:get_player_name()
  3289. end
  3290. end
  3291. -- make sound when fed so many times
  3292. mob_sound(self, self.sounds.random)
  3293. end
  3294. return true
  3295. end
  3296. local item = clicker:get_wielded_item()
  3297. -- if mob has been tamed you can name it with a nametag
  3298. if item:get_name() == "mobs:nametag"
  3299. and clicker:get_player_name() == self.owner then
  3300. local name = clicker:get_player_name()
  3301. -- store mob and nametag stack in external variables
  3302. mob_obj[name] = self
  3303. mob_sta[name] = item
  3304. local tag = self.nametag or ""
  3305. minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
  3306. .. default.gui_bg
  3307. .. default.gui_bg_img
  3308. .. "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape("Enter name:") .. ";" .. tag .. "]"
  3309. .. "button_exit[2.5,3.5;3,1;mob_rename;" .. minetest.formspec_escape("Rename") .. "]")
  3310. end
  3311. return false
  3312. end
  3313. function mobs.nametag_receive_fields(player, formname, fields)
  3314. -- right-clicked with nametag and name entered?
  3315. if formname == "mobs_nametag"
  3316. and fields.name
  3317. and fields.name ~= "" then
  3318. local name = player:get_player_name()
  3319. if not mob_obj[name]
  3320. or not mob_obj[name].object then
  3321. return
  3322. end
  3323. -- make sure nametag is being used to name mob
  3324. local item = player:get_wielded_item()
  3325. if item:get_name() ~= "mobs:nametag" then
  3326. return
  3327. end
  3328. -- limit name entered to 64 characters long
  3329. if string.len(fields.name) > 64 then
  3330. fields.name = string.sub(fields.name, 1, 64)
  3331. end
  3332. -- update nametag
  3333. mob_obj[name].nametag = fields.name
  3334. update_tag(mob_obj[name])
  3335. -- if not in creative then take item
  3336. if not mobs.is_creative(name) then
  3337. mob_sta[name]:take_item()
  3338. player:set_wielded_item(mob_sta[name])
  3339. end
  3340. -- reset external variables
  3341. mob_obj[name] = nil
  3342. mob_sta[name] = nil
  3343. end
  3344. end
  3345. -- inspired by blockmen's nametag mod
  3346. if not mobs.registered then
  3347. minetest.register_on_player_receive_fields(function(...)
  3348. return mobs.nametag_receive_fields(...)
  3349. end)
  3350. end
  3351. -- compatibility function for old entities to new modpack entities
  3352. if not mobs.registered then
  3353. function mobs.alias_mob(old_name, new_name)
  3354. -- spawn egg
  3355. minetest.register_alias(old_name, new_name)
  3356. -- entity
  3357. minetest.register_entity(":" .. old_name, {
  3358. physical = false,
  3359. on_activate = function(self)
  3360. if minetest.registered_entities[new_name] then
  3361. minetest.add_entity(self.object:get_pos(), new_name)
  3362. end
  3363. -- Remove mob immediately, as last step of this function.
  3364. -- Controls returns to engine.
  3365. self.object:remove()
  3366. self.mkrm = true
  3367. end
  3368. })
  3369. end
  3370. end
  3371. -- Register as a reloadable file.
  3372. if not mobs.registered then
  3373. local c = "mobs:api"
  3374. local f = mobs.modpath .. "/api.lua"
  3375. reload.register_file(c, f, false)
  3376. mobs.registered = true
  3377. end