api.lua 101 KB

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