init.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. local modname = minetest.get_current_modname()
  2. local modpath = minetest.get_modpath(modname)
  3. local worldpath = minetest.get_worldpath()
  4. local last_punch_time = {}
  5. local attack_chance = {}
  6. local pending_players = {}
  7. local knockback = {}
  8. local timer = 0
  9. -- support for i18n
  10. armor_i18n = { }
  11. armor_i18n.gettext, armor_i18n.ngettext = dofile(modpath.."/intllib.lua")
  12. -- local functions
  13. local S = armor_i18n.gettext
  14. local F = minetest.formspec_escape
  15. dofile(modpath.."/api.lua")
  16. -- Legacy Config Support
  17. local input = io.open(modpath.."/armor.conf", "r")
  18. if input then
  19. dofile(modpath.."/armor.conf")
  20. input:close()
  21. input = nil
  22. end
  23. input = io.open(worldpath.."/armor.conf", "r")
  24. if input then
  25. dofile(worldpath.."/armor.conf")
  26. input:close()
  27. input = nil
  28. end
  29. for name, _ in pairs(armor.config) do
  30. local global = "ARMOR_"..name:upper()
  31. if minetest.global_exists(global) then
  32. armor.config[name] = _G[global]
  33. end
  34. end
  35. if minetest.global_exists("ARMOR_MATERIALS") then
  36. armor.materials = table.copy(ARMOR_MATERIALS)
  37. end
  38. if minetest.global_exists("ARMOR_FIRE_NODES") then
  39. armor.fire_nodes = table.copy(ARMOR_FIRE_NODES)
  40. end
  41. -- Load Configuration
  42. for name, config in pairs(armor.config) do
  43. local setting = minetest.settings:get("armor_"..name)
  44. if type(config) == "number" then
  45. setting = tonumber(setting)
  46. elseif type(config) == "boolean" then
  47. setting = minetest.settings:get_bool("armor_"..name)
  48. end
  49. if setting ~= nil then
  50. armor.config[name] = setting
  51. end
  52. end
  53. for material, _ in pairs(armor.materials) do
  54. local key = "material_"..material
  55. if armor.config[key] == false then
  56. armor.materials[material] = nil
  57. end
  58. end
  59. -- Mod Compatibility
  60. if minetest.get_modpath("technic") then
  61. armor.formspec = armor.formspec..
  62. "label[5,2.5;"..F(S("Radiation"))..": armor_group_radiation]"
  63. armor:register_armor_group("radiation")
  64. end
  65. local skin_mods = {"skins", "u_skins", "simple_skins", "wardrobe", "custom_skin"}
  66. for _, mod in pairs(skin_mods) do
  67. local path = minetest.get_modpath(mod)
  68. if path then
  69. local dir_list = minetest.get_dir_list(path.."/textures")
  70. for _, fn in pairs(dir_list) do
  71. if fn:find("_preview.png$") then
  72. armor:add_preview(fn)
  73. end
  74. end
  75. armor.skin_mod = mod
  76. end
  77. end
  78. if not minetest.get_modpath("moreores") then
  79. armor.materials.mithril = nil
  80. end
  81. if not minetest.get_modpath("ethereal") then
  82. armor.materials.crystal = nil
  83. end
  84. dofile(modpath.."/armor.lua")
  85. -- Armor Initialization
  86. armor.formspec = armor.formspec..
  87. "label[5,1;"..F(S("Level"))..": armor_level]"..
  88. "label[5,1.5;"..F(S("Block"))..": armor_attr_block]"
  89. if armor.config.fire_protect then
  90. armor.formspec = armor.formspec.."label[5,2;"..F(S("Fire"))..": armor_attr_fire]"
  91. end
  92. armor:register_on_destroy(function(player, index, stack)
  93. local name = player:get_player_name()
  94. local def = stack:get_definition()
  95. if name and def and def.description then
  96. minetest.chat_send_player(name, S("Your @1 got destroyed!", def.description))
  97. end
  98. end)
  99. local function validate_armor_inventory(player)
  100. -- Workaround for detached inventory swap exploit
  101. local _, inv = armor:get_valid_player(player, "[validate_armor_inventory]")
  102. if not inv then
  103. return
  104. end
  105. local armor_prev = {}
  106. local player_attributes = player:get_meta()
  107. local armor_list_string = player_attributes:get_string("3d_armor_inventory")
  108. if armor_list_string then
  109. local armor_list = armor:deserialize_inventory_list(armor_list_string)
  110. for i, stack in ipairs(armor_list) do
  111. if stack:get_count() > 0 then
  112. armor_prev[stack:get_name()] = i
  113. end
  114. end
  115. end
  116. local elements = {}
  117. local player_inv = player:get_inventory()
  118. for i = 1, 8 do
  119. local stack = inv:get_stack("armor", i)
  120. if stack:get_count() > 0 then
  121. local item = stack:get_name()
  122. local element = armor:get_element(item)
  123. if element and not elements[element] then
  124. if armor_prev[item] then
  125. armor_prev[item] = nil
  126. else
  127. -- Item was not in previous inventory
  128. armor:run_callbacks("on_equip", player, i, stack)
  129. end
  130. elements[element] = true;
  131. else
  132. inv:remove_item("armor", stack)
  133. -- The following code returns invalid items to the player's main
  134. -- inventory but could open up the possibity for a hacked client
  135. -- to receive items back they never really had. I am not certain
  136. -- so remove the is_singleplayer check at your own risk :]
  137. if player_inv:room_for_item("main", stack) then
  138. player_inv:add_item("main", stack)
  139. end
  140. end
  141. end
  142. end
  143. for item, i in pairs(armor_prev) do
  144. local stack = ItemStack(item)
  145. -- Previous item is not in current inventory
  146. armor:run_callbacks("on_unequip", player, i, stack)
  147. end
  148. end
  149. local function init_player_armor(player)
  150. local name = player:get_player_name()
  151. local pos = player:get_pos()
  152. if not name or not pos then
  153. return false
  154. end
  155. local armor_inv = minetest.create_detached_inventory(name.."_armor", {
  156. on_put = function(inv, listname, index, stack, player)
  157. validate_armor_inventory(player)
  158. armor:save_armor_inventory(player)
  159. armor:set_player_armor(player)
  160. end,
  161. on_take = function(inv, listname, index, stack, player)
  162. validate_armor_inventory(player)
  163. armor:save_armor_inventory(player)
  164. armor:set_player_armor(player)
  165. end,
  166. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  167. validate_armor_inventory(player)
  168. armor:save_armor_inventory(player)
  169. armor:set_player_armor(player)
  170. end,
  171. allow_put = function(inv, listname, index, put_stack, player)
  172. local element = armor:get_element(put_stack:get_name())
  173. if not element then
  174. return 0
  175. end
  176. for i = 1, 8 do
  177. local stack = inv:get_stack("armor", i)
  178. local def = stack:get_definition() or {}
  179. if def.groups and def.groups["armor_"..element]
  180. and i ~= index then
  181. return 0
  182. end
  183. end
  184. return 1
  185. end,
  186. allow_take = function(inv, listname, index, stack, player)
  187. local player_inv = player:get_inventory()
  188. if player_inv:room_for_item('main', stack) then
  189. return stack:get_count()
  190. else
  191. return 0
  192. end
  193. end,
  194. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  195. return count
  196. end,
  197. }, name)
  198. armor_inv:set_size("armor", 8)
  199. if not armor:load_armor_inventory(player) and armor.migrate_old_inventory then
  200. local player_inv = player:get_inventory()
  201. player_inv:set_size("armor", 8)
  202. for i=1, 8 do
  203. local stack = player_inv:get_stack("armor", i)
  204. armor_inv:set_stack("armor", i, stack)
  205. end
  206. armor:save_armor_inventory(player)
  207. player_inv:set_size("armor", 0)
  208. end
  209. for i=1, 8 do
  210. local stack = armor_inv:get_stack("armor", i)
  211. if stack:get_count() > 0 then
  212. armor:run_callbacks("on_equip", player, i, stack)
  213. end
  214. end
  215. armor.def[name] = {
  216. init_time = minetest.get_gametime(),
  217. level = 0,
  218. state = 0,
  219. count = 0,
  220. groups = {},
  221. }
  222. for _, phys in pairs(armor.physics) do
  223. armor.def[name][phys] = 1
  224. end
  225. for _, attr in pairs(armor.attributes) do
  226. armor.def[name][attr] = 0
  227. end
  228. for group, _ in pairs(armor.registered_groups) do
  229. armor.def[name].groups[group] = 0
  230. end
  231. local skin = armor:get_player_skin(name)
  232. armor.textures[name] = {
  233. skin = skin,
  234. armor = "3d_armor_trans.png",
  235. wielditem = "3d_armor_trans.png",
  236. preview = armor.default_skin.."_preview.png",
  237. }
  238. local texture_path = minetest.get_modpath("player_textures")
  239. if texture_path then
  240. local dir_list = minetest.get_dir_list(texture_path.."/textures")
  241. for _, fn in pairs(dir_list) do
  242. if fn == "player_"..name..".png" then
  243. armor.textures[name].skin = fn
  244. break
  245. end
  246. end
  247. end
  248. armor:set_player_armor(player)
  249. return true
  250. end
  251. -- Armor Player Model
  252. default.player_register_model("3d_armor_character.b3d", {
  253. animation_speed = 30,
  254. textures = {
  255. armor.default_skin..".png",
  256. "3d_armor_trans.png",
  257. "3d_armor_trans.png",
  258. },
  259. animations = {
  260. stand = {x=0, y=79},
  261. lay = {x=162, y=166},
  262. walk = {x=168, y=187},
  263. mine = {x=189, y=198},
  264. walk_mine = {x=200, y=219},
  265. sit = {x=81, y=160},
  266. },
  267. })
  268. minetest.register_on_player_receive_fields(function(player, formname, fields)
  269. local name = armor:get_valid_player(player, "[on_player_receive_fields]")
  270. if not name then
  271. return
  272. end
  273. for field, _ in pairs(fields) do
  274. if string.find(field, "skins_set") then
  275. minetest.after(0, function(player)
  276. local skin = armor:get_player_skin(name)
  277. armor.textures[name].skin = skin
  278. armor:set_player_armor(player)
  279. end, player)
  280. end
  281. end
  282. end)
  283. minetest.register_on_joinplayer(function(player)
  284. default.player_set_model(player, "3d_armor_character.b3d")
  285. minetest.after(0, function(player)
  286. if init_player_armor(player) == false then
  287. pending_players[player] = 0
  288. end
  289. end, player)
  290. end)
  291. minetest.register_on_leaveplayer(function(player)
  292. local name = player:get_player_name()
  293. if name then
  294. armor.def[name] = nil
  295. armor.textures[name] = nil
  296. end
  297. pending_players[player] = nil
  298. end)
  299. if armor.config.drop == true or armor.config.destroy == true then
  300. minetest.register_on_dieplayer(function(player)
  301. local name, armor_inv = armor:get_valid_player(player, "[on_dieplayer]")
  302. if not name then
  303. return
  304. end
  305. local drop = {}
  306. for i=1, armor_inv:get_size("armor") do
  307. local stack = armor_inv:get_stack("armor", i)
  308. if stack:get_count() > 0 then
  309. table.insert(drop, stack)
  310. armor:run_callbacks("on_unequip", player, i, stack)
  311. armor_inv:set_stack("armor", i, nil)
  312. end
  313. end
  314. armor:save_armor_inventory(player)
  315. armor:set_player_armor(player)
  316. local pos = player:get_pos()
  317. if pos and armor.config.destroy == false then
  318. minetest.after(armor.config.bones_delay, function()
  319. local meta = nil
  320. local maxp = vector.add(pos, 8)
  321. local minp = vector.subtract(pos, 8)
  322. local bones = minetest.find_nodes_in_area(minp, maxp, {"bones:bones"})
  323. for _, p in pairs(bones) do
  324. local m = minetest.get_meta(p)
  325. if m:get_string("owner") == name then
  326. meta = m
  327. break
  328. end
  329. end
  330. if meta then
  331. local inv = meta:get_inventory()
  332. for _,stack in ipairs(drop) do
  333. if inv:room_for_item("main", stack) then
  334. inv:add_item("main", stack)
  335. else
  336. armor.drop_armor(pos, stack)
  337. end
  338. end
  339. else
  340. for _,stack in ipairs(drop) do
  341. armor.drop_armor(pos, stack)
  342. end
  343. end
  344. end)
  345. end
  346. end)
  347. end
  348. if armor.config.punch_damage == true then
  349. minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities)
  350. local name = player:get_player_name()
  351. if name then
  352. last_punch_time[name] = minetest.get_gametime()
  353. local attackchance = tool_capabilities.damage_groups['attack_chance']
  354. attack_chance[name] = attackchance
  355. end
  356. end)
  357. end
  358. -- Prevent knockback for blocked attacks
  359. local old_calculate_knockback = minetest.calculate_knockback
  360. function minetest.calculate_knockback(player, ...)
  361. local name = player:get_player_name()
  362. if knockback[name] then
  363. return 0
  364. end
  365. return old_calculate_knockback(player, ...)
  366. end
  367. minetest.register_on_player_hpchange(function(player, hp_change)
  368. if player and hp_change < 0 then
  369. local name = player:get_player_name()
  370. if name then
  371. local time = last_punch_time[name] or 0
  372. local attackchance = attack_chance[name] or 100
  373. if time ~= 'fire' then
  374. local hunger = hbhunger.hunger[name] or 30
  375. if player:get_breath() > 0 and hunger >= 1 then
  376. local dmg_resist = math.floor(armor.def[name].dmg_resist)
  377. local block = armor.def[name].block
  378. local should_damage = (hp_change*-1) > (dmg_resist/2) --Should armor be damaged by the attack
  379. if block >= math.random(attackchance) then --Armor blocked the attack
  380. hp_change = 0
  381. knockback[name] = true
  382. else
  383. hp_change = (hp_change + dmg_resist)
  384. if hp_change >= 0 then
  385. knockback[name] = true
  386. else
  387. knockback[name] = false
  388. end
  389. end
  390. if should_damage then
  391. armor:punch(player)
  392. end
  393. end
  394. end
  395. end
  396. end
  397. return hp_change
  398. end, true)
  399. minetest.register_globalstep(function(dtime)
  400. timer = timer + dtime
  401. if timer > armor.config.init_delay then
  402. for player, count in pairs(pending_players) do
  403. local remove = init_player_armor(player) == true
  404. pending_players[player] = count + 1
  405. if remove == false and count > armor.config.init_times then
  406. minetest.log("warning", S("3d_armor: Failed to initialize player"))
  407. remove = true
  408. end
  409. if remove == true then
  410. pending_players[player] = nil
  411. end
  412. end
  413. timer = 0
  414. end
  415. end)
  416. -- Fire Protection and water breating, added by TenPlus1
  417. if armor.config.fire_protect == true then
  418. -- override hot nodes so they do not hurt player anywhere but mod
  419. for _, row in pairs(armor.fire_nodes) do
  420. if minetest.registered_nodes[row[1]] then
  421. minetest.override_item(row[1], {damage_per_second = 0})
  422. end
  423. end
  424. else
  425. print (S("[3d_armor] Fire Nodes disabled"))
  426. end
  427. minetest.register_globalstep(function(dtime)
  428. armor.timer = armor.timer + dtime
  429. if armor.timer < armor.config.update_time then
  430. return
  431. end
  432. for _,player in pairs(minetest.get_connected_players()) do
  433. local name = player:get_player_name()
  434. local pos = player:get_pos()
  435. local hp = player:get_hp()
  436. if not name or not pos or not hp then
  437. return
  438. end
  439. -- water breathing
  440. if armor.config.water_protect == true then
  441. if armor.def[name].water > 0 and player:get_breath() < 40 then
  442. local breath_level = player:get_breath()
  443. local armor_level = armor.def[name].water
  444. local new_breath = (breath_level + armor_level)
  445. player:set_breath(new_breath)
  446. armor:punch(player, 'water')
  447. end
  448. end
  449. -- healing
  450. local max_hp = player:get_properties().hp_max
  451. if hp < max_hp and player:get_breath() >= 1 and hbhunger.hunger[name] >= 1 then
  452. local heal = armor.def[name].heal
  453. if heal >= 1 then
  454. local new_hp = math.floor(hp + heal)
  455. player:set_hp(new_hp)
  456. armor:punch(player)
  457. end
  458. end
  459. -- fire protection
  460. if armor.config.fire_protect == true then
  461. local fire_damage = true
  462. pos.y = pos.y + 1.4 -- head level
  463. local node_head = minetest.get_node(pos).name
  464. pos.y = pos.y - 1.2 -- feet level
  465. local node_feet = minetest.get_node(pos).name
  466. -- is player inside a hot node?
  467. for _, row in pairs(armor.fire_nodes) do
  468. -- check fire protection, if not enough then get hurt
  469. if row[1] == node_head or row[1] == node_feet then
  470. if fire_damage == true then --runs when player takes damage.
  471. armor:punch(player, 'fire')
  472. last_punch_time[name] = 'fire'
  473. fire_damage = false
  474. end
  475. if hp > 0 and armor.def[name].fire < row[2] then --Only runs if player is dead.
  476. hp = hp - row[3] * armor.config.update_time
  477. player:set_hp(hp)
  478. break
  479. end
  480. end
  481. end
  482. end
  483. end
  484. armor.timer = 0
  485. end)