functions.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. local invisibility = (rawget(_G, "invisibility") and invisibility) or {}
  2. local damage_enabled = minetest.setting_getbool("enable_damage")
  3. local get_distance = function(a, b)
  4. local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
  5. return math.sqrt(x * x + y * y + z * z)
  6. end
  7. local split = function(str, sep)
  8. local fields = {}
  9. local str = str or ""
  10. local sep = sep or ";"
  11. for word in string.gmatch(str, '([^'..sep..']+)') do
  12. fields[#fields + 1] = word
  13. end
  14. return fields
  15. end
  16. local value_in_table = function(tab, val)
  17. for index, value in ipairs(tab) do
  18. if value == val then
  19. return true
  20. end
  21. end
  22. return false
  23. end
  24. local npc_guard_attack = function(self)
  25. if not damage_enabled or self.state == "attack" then
  26. return
  27. end
  28. local player, entity_type, obj, min_player, npc_race = nil, nil, nil, nil, nil
  29. local min_dist = self.view_range + 1
  30. if not (self.object and self.object:get_pos()) then
  31. return
  32. end
  33. local objs = minetest.get_objects_inside_radius(self.object:get_pos(), self.view_range)
  34. for n = 1, #objs do
  35. if invisibility[ objs[n]:get_player_name() ] then
  36. entity_type = ""
  37. elseif objs[n]:is_player() then
  38. player = objs[n]
  39. entity_type = "player"
  40. else
  41. obj = objs[n]:get_luaentity()
  42. if obj then
  43. player = obj.object
  44. entity_type = obj.type
  45. npc_race = obj.race
  46. end
  47. end
  48. if entity_type == "player" or entity_type == "npc" or entity_type == "monster" then
  49. local s = self.object:getpos()
  50. local p = player:getpos()
  51. local sp = s
  52. -- aim higher to make looking up hills more realistic
  53. p.y = p.y + 1
  54. sp.y = sp.y + 1
  55. local dist = get_distance(p, s)
  56. if dist < self.view_range then
  57. -- choose closest player to attack
  58. if self:line_of_sight_water(sp, p, 2) == true
  59. and dist < min_dist then
  60. if entity_type == "player"
  61. and player:get_player_name() ~= self.owner
  62. and self.whitelist
  63. and not value_in_table(self.whitelist, player:get_player_name()) then
  64. local player_privs = minetest.get_player_privs(player:get_player_name())
  65. if (self.attack_player_GAMEelf and player_privs.GAMEelf)
  66. or (self.attack_player_GAMEman and player_privs.GAMEman)
  67. or (self.attack_player_GAMEorc and player_privs.GAMEorc)
  68. or (self.attack_player_GAMEhobbit and player_privs.GAMEhobbit)
  69. or (self.attack_player_GAMEdwarf and player_privs.GAMEdwarf)
  70. or (self.blacklist
  71. and value_in_table(self.blacklist, player:get_player_name())) then
  72. min_dist = dist
  73. min_player = player
  74. end
  75. elseif entity_type == "npc" and npc_race ~= "ents" and self.race ~= npc_race then
  76. if (self["attack_npc_"..npc_race]) then
  77. min_dist = dist
  78. min_player = player
  79. end
  80. elseif entity_type == "monster" and self.attacks_monsters then
  81. min_dist = dist
  82. min_player = player
  83. end
  84. end
  85. end
  86. end
  87. end
  88. -- attack player
  89. if min_player then
  90. self:do_attack(min_player)
  91. end
  92. end
  93. local npc_attack = function(self)
  94. if self.type ~= "npc"
  95. or not damage_enabled
  96. or self.state == "attack" then
  97. return
  98. end
  99. local player, entity_type, obj, min_player, npc_race = nil, nil, nil, nil, nil
  100. local min_dist = self.view_range + 1
  101. if not (self.object and self.object:get_pos()) then
  102. return
  103. end
  104. local objs = minetest.get_objects_inside_radius(self.object:get_pos(), self.view_range)
  105. for n = 1, #objs do
  106. if invisibility[ objs[n]:get_player_name() ] then
  107. entity_type = ""
  108. elseif objs[n]:is_player() then
  109. player = objs[n]
  110. entity_type = "player"
  111. else
  112. obj = objs[n]:get_luaentity()
  113. if obj then
  114. player = obj.object
  115. entity_type = obj.type
  116. npc_race = obj.race
  117. end
  118. end
  119. if entity_type == "player" or entity_type == "npc" or entity_type == "monster" then
  120. if not (self.object and self.object:get_pos()) then
  121. return
  122. end
  123. local s = self.object:get_pos()
  124. local p = player:get_pos()
  125. local sp = s
  126. -- aim higher to make looking up hills more realistic
  127. p.y = p.y + 1
  128. sp.y = sp.y + 1
  129. local dist = get_distance(p, s)
  130. if dist < self.view_range then
  131. -- choose closest player to attack
  132. if self:line_of_sight_water(sp, p, 2) == true
  133. and dist < min_dist then
  134. if entity_type == "player" then
  135. if not lottclasses.player_same_race_or_ally(player, self.race) then
  136. min_dist = dist
  137. min_player = player
  138. end
  139. elseif entity_type == "npc" then
  140. if npc_race ~= "ents" and npc_race ~= self.race and not lottclasses.allies[self.race][npc_race] then
  141. min_dist = dist
  142. min_player = player
  143. end
  144. elseif entity_type == "monster" and self.attacks_monsters then
  145. min_dist = dist
  146. min_player = player
  147. end
  148. end
  149. end
  150. end
  151. end
  152. if min_player then
  153. self:do_attack(min_player)
  154. end
  155. end
  156. local guard_foods = {
  157. ["lottfarming:corn"] = 5,
  158. ["farming:bread"] = 10
  159. }
  160. lottmobs.save_guard_hunger = function()
  161. minetest.mkdir(minetest.get_worldpath().."/"..SAVEDIR)
  162. local file = io.open(minetest.get_worldpath().."/"..SAVEDIR.."/guard_hunger.txt", "w")
  163. if file then
  164. file:write(minetest.serialize(lottmobs.player_guards))
  165. file:close()
  166. end
  167. end
  168. lottmobs.do_guard_hunger = function(dtime)
  169. for player, bool in pairs(lottmobs.connected_player_names) do
  170. if lottmobs.player_guards[player] then
  171. for name, hunger_def in pairs(lottmobs.player_guards[player]) do
  172. lottmobs.guard_eat(hunger_def, player, name, dtime)
  173. end
  174. end
  175. end
  176. end
  177. lottmobs.guard_eat_active = function(self)
  178. if lottmobs.player_guards[self.owner] then
  179. local hunger_def = lottmobs.player_guards[self.owner][self.game_name]
  180. if hunger_def then
  181. self.health = self.health + (hunger_def.health - hunger_def.last_active_health)
  182. hunger_def.last_active_health = self.health
  183. hunger_def.health = self.health
  184. self.object:set_hp(self.health)
  185. return
  186. end
  187. end
  188. self.health = 0
  189. self.object:set_hp(self.health)
  190. end
  191. lottmobs.guard_eat = function(self, owner, name, dtime)
  192. if self.health <= 0 then
  193. lottmobs.player_guards[owner][name] = nil
  194. lottmobs.save_guard_hunger()
  195. end
  196. self.eat_timer = self.eat_timer + dtime
  197. self.timer = self.timer + dtime
  198. if self.eat_timer >= 60 then
  199. self.food_level = self.food_level - 1
  200. self.eat_timer = 0
  201. end
  202. if self.food_level <= 0 and self.timer >= 1 then
  203. self.timer = 0
  204. local food_inv = minetest.get_inventory({type="player", name=owner})
  205. if food_inv then
  206. for food, eat_value in pairs(guard_foods) do
  207. local taken = food_inv:remove_item("bag4contents", ItemStack(food.." 1"))
  208. self.food_level = self.food_level + taken:get_count() * eat_value
  209. if self.food_level > 1 then
  210. break
  211. end
  212. end
  213. end
  214. if self.food_level <= 0 then
  215. self.health = self.health - 1
  216. end
  217. end
  218. end
  219. lottmobs.guard_die = function(self, pos)
  220. if self.owner and self.owner ~= "" then
  221. lottmobs.player_guards[self.owner][self.game_name] = nil
  222. lottmobs.save_guard_hunger()
  223. end
  224. if self.attack and self.attack:is_player() then
  225. lottachievements.kill(self.name, self.attack)
  226. end
  227. end
  228. lottmobs.do_custom_guard = function(self, dtime)
  229. -- attack timer
  230. self.timer = self.timer + dtime
  231. if self.state ~= "attack" then
  232. if self.timer < 1 then
  233. return false
  234. end
  235. self.timer = 0
  236. end
  237. -- never go over 100
  238. if self.timer > 100 then
  239. self.timer = 1
  240. end
  241. -- node replace check (cow eats grass etc.)
  242. if not (self.object and self.object:get_pos()) then
  243. return
  244. end
  245. local pos = self.object:get_pos()
  246. self:replace(pos)
  247. -- mob plays random sound at times
  248. if self.sounds.random
  249. and random(1, 100) == 1 then
  250. minetest.sound_play(self.sounds.random, {
  251. object = self.object,
  252. max_hear_distance = self.sounds.distance
  253. })
  254. end
  255. -- environmental damage timer (every 1 second)
  256. self.env_damage_timer = self.env_damage_timer + dtime
  257. if (self.state == "attack" and self.env_damage_timer > 1)
  258. or self.state ~= "attack" then
  259. self.env_damage_timer = 0
  260. self:do_env_damage()
  261. end
  262. if self.owner and self.owner ~= "" then
  263. lottmobs.guard_eat_active(self)
  264. npc_guard_attack(self)
  265. else
  266. npc_attack(self)
  267. end
  268. self:follow_flop()
  269. self:do_states(dtime)
  270. return false
  271. end
  272. local checkbox_pos = {
  273. "1,4",
  274. "4,4",
  275. "7,4",
  276. "1,5",
  277. "4,5",
  278. "7,5",
  279. "1,6",
  280. "4,6",
  281. "7,6"
  282. }
  283. local get_guard_formspec = function(self)
  284. local selected_idx = 1
  285. if self.whitelist == nil then
  286. self.whitelist = {}
  287. end
  288. if self.blacklist == nil then
  289. self.blacklist = {}
  290. end
  291. if self.order == "stand" then selected_idx = 2 end
  292. local formspec = "size[10,11]"..
  293. "label[1,1;Name:\t"..self.game_name.."]"..
  294. "dropdown[1,2;2;order;follow,stand;"..selected_idx.."]"..
  295. "label[1,3;Attack:]"
  296. local j = 1
  297. local attack_race = nil
  298. for i = 1, 5, 1 do
  299. if lottclasses.races[i] ~= self.race then
  300. attack_race = self["attack_npc_"..lottclasses.races[i]]
  301. if attack_race == nil then
  302. attack_race = not lottclasses.allies[self.race][lottclasses.races[i]]
  303. self["attack_npc_"..lottclasses.races[i]] = attack_race
  304. end
  305. formspec = formspec..
  306. "checkbox["..checkbox_pos[j]..";attack_npc_"..lottclasses.races[i]..";"..
  307. lottclasses.races_prefix[i].." NPCs;"..tostring(attack_race).."]"
  308. j = j + 1
  309. end
  310. end
  311. for i = 1, 5, 1 do
  312. attack_race = self["attack_player_"..lottclasses.races[i]]
  313. if attack_race == nil then
  314. attack_race = not lottclasses.allies[self.race][lottclasses.races[i]]
  315. self["attack_player_"..lottclasses.races[i]] = attack_race
  316. end
  317. formspec = formspec..
  318. "checkbox["..checkbox_pos[j]..";attack_player_"..lottclasses.races[i]..";"..
  319. lottclasses.races_prefix[i].." Players;"..tostring(attack_race).."]"
  320. j = j + 1
  321. end
  322. formspec = formspec..
  323. "field[1,8;9,1;whitelist;Whitelist;"..
  324. minetest.formspec_escape(table.concat(self.whitelist, ";")).."]"..
  325. "field[1,9;9,1;blacklist;Blacklist;"..
  326. minetest.formspec_escape(table.concat(self.blacklist, ";")).."]"..
  327. "button_exit[1,10;2,1;exit_button; Proceed]"
  328. return formspec
  329. end
  330. lottmobs.get_hiring_formspec = function(price)
  331. local formspec = "size[6,3]" ..
  332. "label[0,0;Select the amount of gold you want to offer:]"..
  333. "dropdown[2,1;2;offer;1"
  334. for i = 5, price, 5 do
  335. formspec = formspec..","..i
  336. end
  337. formspec = formspec..";1]button_exit[2.25,2;1.5,1;done;Done]"
  338. return formspec
  339. end
  340. lottmobs.guard = function(self, clicker, payment, mob_name, race, price)
  341. lottmobs.change_settings = function(fields)
  342. if fields.order then
  343. self.order = fields.order
  344. end
  345. for i, v in pairs(lottclasses.races) do
  346. local attack_npc = "attack_npc_"..v
  347. local attack_player = "attack_player_"..v
  348. if fields[attack_npc] == "true" then
  349. self[attack_npc] = true
  350. elseif fields[attack_npc] == "false" then
  351. self[attack_npc] = false
  352. end
  353. if fields[attack_player] == "true" then
  354. self[attack_player] = true
  355. elseif fields[attack_player] == "false" then
  356. self[attack_player] = false
  357. end
  358. end
  359. if fields.whitelist then
  360. self.whitelist = split(fields.whitelist, ";")
  361. end
  362. if fields.blacklist then
  363. self.blacklist = split(fields.blacklist, ";")
  364. end
  365. end
  366. local item = clicker:get_wielded_item()
  367. local name = clicker:get_player_name()
  368. if item:get_name() == "lottfarming:corn"
  369. or item:get_name() == "farming:bread" then
  370. local hp = self.object:get_hp()
  371. if hp >= self.hp_max then
  372. minetest.chat_send_player(name, "NPC at full health.")
  373. return
  374. end
  375. hp = hp + 4
  376. if hp > self.hp_max then hp = self.hp_max end
  377. self.object:set_hp(hp)
  378. if not minetest.setting_getbool("creative_mode") then
  379. item:take_item()
  380. clicker:set_wielded_item(item)
  381. end
  382. elseif item:get_name() == payment and self.tamed == false and lottclasses.player_same_race_or_ally(clicker, self.race) then
  383. lottmobs.face_pos(self, clicker:getpos())
  384. self.state = "stand"
  385. if not price then price = 50 end
  386. minetest.show_formspec(name, "mob_hiring", lottmobs.get_hiring_formspec(price))
  387. lottmobs.hire = function(cost)
  388. if math.random(1, (price/cost)) == 1 then
  389. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> Okay, I'll work for you.")
  390. local count = item:get_count()
  391. if count > cost or minetest.setting_getbool("creative_mode") then
  392. if not minetest.setting_getbool("creative_mode") then
  393. item:take_item(cost)
  394. clicker:set_wielded_item(item)
  395. end
  396. clicker:get_inventory():add_item("main", self.name)
  397. self.object:remove()
  398. else
  399. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> What, you don't have that much money?! Stop wasting my time!")
  400. end
  401. else
  402. local rand = math.random(1, 5)
  403. if rand == 1 then
  404. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> Stop bothering me!")
  405. self.object:remove()
  406. elseif rand == 2 then
  407. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> Are you mocking me? I don't take kindly to mockers!")
  408. self:do_attack(clicker)
  409. elseif rand == 3 then
  410. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> You're joking, right? Oh, you're serious? Well, to let you know, I won't be working for you for that pitiful amount.")
  411. else
  412. minetest.chat_send_player(name, "[NPC] <" .. mob_name .. "> Do you really think I'll work for you for that much?!")
  413. end
  414. end
  415. end
  416. elseif self.owner and self.owner == name then
  417. minetest.show_formspec(name, "mob_settings", get_guard_formspec(self))
  418. else
  419. if lottclasses.player_same_race_or_ally(clicker, self.race) then
  420. if self.game_name == "mob" then
  421. self.game_name = lottmobs[race]["names"][math.random(1, #lottmobs[race]["names"])]
  422. end
  423. minetest.chat_send_player(name, "[NPC] <" .. self.game_name .. "> " ..
  424. lottmobs[race]["messages"][math.random(1, #lottmobs[race]["messages"])])
  425. end
  426. end
  427. end
  428. minetest.register_on_player_receive_fields(function(player, formname, fields)
  429. if formname == "mob_hiring" then
  430. if fields.done then
  431. if tonumber(fields.offer) then
  432. local n = tonumber(fields.offer)
  433. if n > 50 then
  434. n = 50
  435. end
  436. lottmobs.hire(n)
  437. else
  438. minetest.chat_send_player(player:get_player_name(), "Offer must be a number!")
  439. end
  440. end
  441. elseif formname == "mob_naming" then
  442. lottmobs.name(fields.naming)
  443. elseif formname == "mob_settings" then
  444. lottmobs.change_settings(fields)
  445. end
  446. end)
  447. lottmobs.register_guard_craftitem = function(name, description, inventory_image)
  448. minetest.register_craftitem(name, {
  449. description = description,
  450. inventory_image = inventory_image,
  451. on_place = function(itemstack, placer, pointed_thing)
  452. if pointed_thing.above then
  453. local owner = placer:get_player_name()
  454. if not lottmobs.player_guards[owner] then
  455. lottmobs.player_guards[owner] = {}
  456. end
  457. local add_guard = function(game_name)
  458. local pos = pointed_thing.above
  459. pos.y = pos.y + 1
  460. if not minetest.setting_getbool("creative_mode") then
  461. itemstack:take_item()
  462. end
  463. local obj = minetest.add_entity(pos, name):get_luaentity()
  464. obj.game_name = game_name
  465. obj.nametag = game_name
  466. obj:update_tag()
  467. obj.tamed = true
  468. obj.owner = owner
  469. obj.order = "follow"
  470. obj.eat_timer = 0
  471. obj.food_level = 20
  472. return obj
  473. end
  474. lottmobs.name = function(name)
  475. if name and name ~= "" and not lottmobs.player_guards[owner][name] then
  476. local obj = add_guard(name)
  477. lottmobs.player_guards[owner][name] = {food_level = 20, health = obj.health, eat_timer = 0, timer = 0, last_active_health = obj.health}
  478. lottmobs.save_guard_hunger()
  479. else
  480. minetest.show_formspec(owner, "mob_naming", "field[naming;Name your guard:;")
  481. end
  482. end
  483. minetest.show_formspec(owner, "mob_naming", "field[naming;Name your guard:;")
  484. end
  485. return itemstack
  486. end
  487. })
  488. end