armor.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. -- Localize for performance.
  2. local math_floor = math.floor
  3. local math_random = math.random
  4. ARMOR_INIT_DELAY = 1
  5. ARMOR_INIT_TIMES = 1
  6. ARMOR_BONES_DELAY = 1
  7. ARMOR_UPDATE_TIME = 1
  8. ARMOR_DROP = minetest.get_modpath("bones") ~= nil
  9. ARMOR_DESTROY = false
  10. ARMOR_LEVEL_MULTIPLIER = 1
  11. ARMOR_HEAL_MULTIPLIER = 1
  12. ARMOR_RADIATION_MULTIPLIER = 1
  13. ARMOR_MATERIALS = {
  14. wood = "group:wood",
  15. cactus = "default:cactus",
  16. steel = "default:steel_ingot",
  17. bronze = "default:bronze_ingot",
  18. diamond = "default:diamond",
  19. gold = "default:gold_ingot",
  20. mithril = "moreores:mithril_ingot",
  21. crystal = "ethereal:crystal_ingot",
  22. }
  23. ARMOR_FIRE_PROTECT = minetest.get_modpath("ethereal") ~= nil
  24. ARMOR_FIRE_NODES = {
  25. {"default:lava_source", 5, 8},
  26. {"default:lava_flowing", 5, 8},
  27. {"fire:basic_flame", 3, 4},
  28. {"fire:permanent_flame", 3, 4},
  29. {"ethereal:crystal_spike", 2, 1},
  30. {"ethereal:fire_flower", 2, 1},
  31. {"default:torch", 1, 1},
  32. }
  33. local skin_mod = nil
  34. local inv_mod = nil
  35. local modpath = minetest.get_modpath(ARMOR_MOD_NAME)
  36. local worldpath = minetest.get_worldpath()
  37. local input = io.open(modpath.."/armor.conf", "r")
  38. if input then
  39. dofile(modpath.."/armor.conf")
  40. input:close()
  41. input = nil
  42. end
  43. input = io.open(worldpath.."/armor.conf", "r")
  44. if input then
  45. dofile(worldpath.."/armor.conf")
  46. input:close()
  47. input = nil
  48. end
  49. if not minetest.get_modpath("moreores") then
  50. ARMOR_MATERIALS.mithril = nil
  51. end
  52. if not minetest.get_modpath("ethereal") then
  53. ARMOR_MATERIALS.crystal = nil
  54. end
  55. armor = {
  56. timer = 0,
  57. elements = {"head", "torso", "legs", "feet"},
  58. physics = {"jump","speed","gravity"},
  59. --formspec = "size[8,8.5]image[2,0.75;2,4;armor_preview]"
  60. -- .."list[current_player;main;0,4.5;8,4;]"
  61. -- .."list[current_player;craft;4,1;3,3;]"
  62. -- .."list[current_player;craftpreview;7,2;1,1;]"
  63. -- .."listring[current_player;main]"
  64. -- .."listring[current_player;craft]",
  65. textures = {},
  66. default_skin = "character",
  67. version = "0.4.6",
  68. }
  69. if minetest.get_modpath("inventory_plus") then
  70. inv_mod = "inventory_plus"
  71. armor.formspec =
  72. "size[8,8.5]" ..
  73. default.gui_bg ..
  74. default.gui_bg_img ..
  75. default.gui_slots ..
  76. "button[0,0.5;2,0.5;main;Back]" ..
  77. "image[4,0.25;2,4;armor_preview]" ..
  78. "label[6,0.5;HP Max: hp_max]" ..
  79. "label[6,1;Level: armor_level]" ..
  80. "label[6,1.4;Heal: armor_heal]" ..
  81. --"label[6,1.8;Fire: armor_fire]" ..
  82. --"label[6,2.2;Radiation: armor_radiation]" ..
  83. "list[current_player;main;0,4.25;8,1;]" ..
  84. "list[current_player;main;0,5.5;8,3;8]" ..
  85. default.get_hotbar_bg(0, 4.25)
  86. end
  87. if minetest.get_modpath("skins") then
  88. skin_mod = "skins"
  89. elseif minetest.get_modpath("simple_skins") then
  90. skin_mod = "simple_skins"
  91. elseif minetest.get_modpath("u_skins") then
  92. skin_mod = "u_skins"
  93. elseif minetest.get_modpath("wardrobe") then
  94. skin_mod = "wardrobe"
  95. end
  96. armor.def = {
  97. state = 0,
  98. count = 0,
  99. }
  100. armor.update_player_visuals = function(self, player)
  101. if not player then
  102. return
  103. end
  104. local name = player:get_player_name()
  105. if self.textures[name] then
  106. default.player_set_textures(player, {
  107. self.textures[name].skin,
  108. self.textures[name].armor,
  109. self.textures[name].wielditem,
  110. })
  111. end
  112. end
  113. armor.set_player_armor = function(self, player)
  114. local name, player_inv = armor:get_valid_player(player, "[set_player_armor]")
  115. if not name then
  116. return
  117. end
  118. local armor_texture = "3d_armor_trans.png"
  119. local armor_level = 0
  120. local armor_heal = 0
  121. local armor_fire = 0
  122. local armor_water = 0
  123. local armor_radiation = 0
  124. local state = 0
  125. local items = 0
  126. local elements = {}
  127. local textures = {}
  128. local physics_o = {speed=1,gravity=1,jump=1}
  129. local material = {type=nil, count=1}
  130. local preview = armor:get_preview(name) or "character_preview.png"
  131. for _,v in ipairs(self.elements) do
  132. elements[v] = false
  133. end
  134. for i=1, 6 do
  135. local stack = player_inv:get_stack("armor", i)
  136. local item = stack:get_name()
  137. if stack:get_count() == 1 then
  138. local def = stack:get_definition()
  139. for k, v in pairs(elements) do
  140. if v == false then
  141. local level = def.groups["armor_"..k]
  142. if level then
  143. local texture = def.texture or item:gsub("%:", "_")
  144. table.insert(textures, texture..".png")
  145. preview = preview.."^"..texture.."_preview.png"
  146. armor_level = armor_level + level
  147. state = state + stack:get_wear()
  148. items = items + 1
  149. armor_heal = armor_heal + (def.groups["armor_heal"] or 0)
  150. armor_fire = armor_fire + (def.groups["armor_fire"] or 0)
  151. armor_water = armor_water + (def.groups["armor_water"] or 0)
  152. armor_radiation = armor_radiation + (def.groups["armor_radiation"] or 0)
  153. for kk,vv in ipairs(self.physics) do
  154. local o_value = def.groups["physics_"..vv]
  155. if o_value then
  156. physics_o[vv] = physics_o[vv] + o_value
  157. end
  158. end
  159. local mat = string.match(item, "%:.+_(.+)$")
  160. if material.type then
  161. if material.type == mat then
  162. material.count = material.count + 1
  163. end
  164. else
  165. material.type = mat
  166. end
  167. elements[k] = true
  168. end
  169. end
  170. end
  171. end
  172. end
  173. if minetest.get_modpath("shields") then
  174. armor_level = armor_level * 0.9
  175. end
  176. -- I guess this gives an armor bonus if all armors are the same material?
  177. -- MustTest.
  178. if material.type and material.count == #self.elements then
  179. armor_level = armor_level * 1.1
  180. end
  181. armor_level = armor_level * ARMOR_LEVEL_MULTIPLIER
  182. armor_heal = armor_heal * ARMOR_HEAL_MULTIPLIER
  183. armor_radiation = armor_radiation * ARMOR_RADIATION_MULTIPLIER
  184. if #textures > 0 then
  185. armor_texture = table.concat(textures, "^")
  186. end
  187. local armor_groups = {fleshy=100}
  188. if armor_level > 0 then
  189. armor_groups.level = math_floor(armor_level / 20)
  190. armor_groups.fleshy = 100 - armor_level
  191. armor_groups.radiation = 100 - armor_radiation
  192. end
  193. player:set_armor_groups(armor_groups)
  194. player:set_physics_override(physics_o)
  195. self.textures[name].armor = armor_texture
  196. self.textures[name].preview = preview
  197. self.def[name].state = state
  198. self.def[name].count = items
  199. self.def[name].level = armor_level
  200. self.def[name].heal = armor_heal
  201. self.def[name].jump = physics_o.jump
  202. self.def[name].speed = physics_o.speed
  203. self.def[name].gravity = physics_o.gravity
  204. self.def[name].fire = armor_fire
  205. self.def[name].water = armor_water
  206. self.def[name].radiation = armor_radiation
  207. self:update_player_visuals(player)
  208. end
  209. armor.update_armor = function(self, player)
  210. -- Legacy support: Called when armor levels are changed
  211. -- Other mods can hook on to this function, see hud mod for example
  212. end
  213. armor.get_player_skin = function(self, name)
  214. local skin = nil
  215. if skin_mod == "skins" or skin_mod == "simple_skins" then
  216. skin = skins.skins[name]
  217. elseif skin_mod == "u_skins" then
  218. skin = u_skins.u_skins[name]
  219. elseif skin_mod == "wardrobe" then
  220. skin = string.gsub(wardrobe.playerSkins[name], "%.png$","")
  221. end
  222. return skin or armor.default_skin
  223. end
  224. armor.get_preview = function(self, name)
  225. if skin_mod == "skins" then
  226. return armor:get_player_skin(name).."_preview.png"
  227. end
  228. end
  229. local function get_player_max_hp(name)
  230. local pref = minetest.get_player_by_name(name)
  231. if pref then
  232. return pref:get_properties().hp_max
  233. end
  234. return 20
  235. end
  236. armor.get_armor_formspec = function(self, name)
  237. if not armor.textures[name] then
  238. minetest.log("error", "3d_armor: Player texture["..name.."] is nil [get_armor_formspec]")
  239. return ""
  240. end
  241. if not armor.def[name] then
  242. minetest.log("error", "3d_armor: Armor def["..name.."] is nil [get_armor_formspec]")
  243. return ""
  244. end
  245. local formspec = armor.formspec .. "list[detached:"..name.."_armor;armor;0,1.5;3,2;]"
  246. formspec = formspec:gsub("armor_preview", armor.textures[name].preview)
  247. formspec = formspec:gsub("armor_level", armor.def[name].level)
  248. formspec = formspec:gsub("armor_heal", armor.def[name].heal)
  249. formspec = formspec:gsub("armor_fire", armor.def[name].fire)
  250. formspec = formspec:gsub("armor_radiation", armor.def[name].radiation)
  251. formspec = formspec:gsub("hp_max", tostring(get_player_max_hp(name)))
  252. return formspec
  253. end
  254. armor.update_inventory = function(self, player)
  255. local name = armor:get_valid_player(player, "[set_player_armor]")
  256. if not name or inv_mod == "inventory_enhanced" then
  257. return
  258. end
  259. if inv_mod == "unified_inventory" then
  260. if unified_inventory.current_page[name] == "armor" then
  261. unified_inventory.set_inventory_formspec(player, "armor")
  262. end
  263. else
  264. local formspec = armor:get_armor_formspec(name)
  265. if inv_mod == "inventory_plus" then
  266. formspec = formspec.."listring[current_player;main]"
  267. .."listring[detached:"..name.."_armor;armor]"
  268. local page = player:get_inventory_formspec()
  269. if page:find("detached:"..name.."_armor") then
  270. inventory_plus.set_inventory_formspec(player, formspec)
  271. end
  272. elseif not core.setting_getbool("creative_mode") then
  273. player:set_inventory_formspec(formspec)
  274. end
  275. end
  276. end
  277. armor.get_valid_player = function(self, player, msg)
  278. msg = msg or ""
  279. if not player then
  280. minetest.log("error", "3d_armor: Player reference is nil "..msg)
  281. return
  282. end
  283. local name = player:get_player_name()
  284. if not name then
  285. minetest.log("error", "3d_armor: Player name is nil "..msg)
  286. return
  287. end
  288. local pos = player:getpos()
  289. local player_inv = player:get_inventory()
  290. local armor_inv = minetest.get_inventory({type="detached", name=name.."_armor"})
  291. if not pos then
  292. minetest.log("error", "3d_armor: Player position is nil "..msg)
  293. return
  294. elseif not player_inv then
  295. minetest.log("error", "3d_armor: Player inventory is nil "..msg)
  296. return
  297. elseif not armor_inv then
  298. minetest.log("error", "3d_armor: Detached armor inventory is nil "..msg)
  299. return
  300. end
  301. return name, player_inv, armor_inv, pos
  302. end
  303. -- Register Player Model
  304. ---[===[
  305. default.player_register_model("3d_armor_character.b3d", {
  306. animation_speed = 30,
  307. textures = {
  308. armor.default_skin..".png",
  309. "3d_armor_trans.png",
  310. "3d_armor_trans.png",
  311. },
  312. animations = {
  313. stand = {x=0, y=79},
  314. lay = {x=162, y=166},
  315. walk = {x=168, y=187},
  316. mine = {x=189, y=198},
  317. walk_mine = {x=200, y=219},
  318. sit = {x=81, y=160},
  319. },
  320. })
  321. --]===]
  322. -- Register Callbacks
  323. minetest.register_on_player_receive_fields(function(player, formname, fields)
  324. local name = armor:get_valid_player(player, "[on_player_receive_fields]")
  325. if not name or inv_mod == "inventory_enhanced" then
  326. return
  327. end
  328. if inv_mod == "inventory_plus" and fields.armor then
  329. local formspec = armor:get_armor_formspec(name)
  330. inventory_plus.set_inventory_formspec(player, formspec)
  331. return
  332. end
  333. for field, _ in pairs(fields) do
  334. if string.find(field, "skins_set") then
  335. minetest.after(0, function(player)
  336. local skin = armor:get_player_skin(name)
  337. armor.textures[name].skin = skin..".png"
  338. armor:set_player_armor(player)
  339. end, player)
  340. end
  341. end
  342. end)
  343. minetest.register_on_joinplayer(function(player)
  344. default.player_set_model(player, "3d_armor_character.b3d")
  345. local name = player:get_player_name()
  346. local player_inv = player:get_inventory()
  347. local armor_inv = minetest.create_detached_inventory(name.."_armor", {
  348. on_put = function(inv, listname, index, stack, player)
  349. player:get_inventory():set_stack(listname, index, stack)
  350. armor:set_player_armor(player)
  351. armor:update_inventory(player)
  352. end,
  353. on_take = function(inv, listname, index, stack, player)
  354. player:get_inventory():set_stack(listname, index, nil)
  355. armor:set_player_armor(player)
  356. armor:update_inventory(player)
  357. end,
  358. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  359. local plaver_inv = player:get_inventory()
  360. local stack = inv:get_stack(to_list, to_index)
  361. player_inv:set_stack(to_list, to_index, stack)
  362. player_inv:set_stack(from_list, from_index, nil)
  363. armor:set_player_armor(player)
  364. armor:update_inventory(player)
  365. end,
  366. allow_put = function(inv, listname, index, stack, player)
  367. return 1
  368. end,
  369. allow_take = function(inv, listname, index, stack, player)
  370. return stack:get_count()
  371. end,
  372. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  373. return count
  374. end,
  375. }, name)
  376. armor_inv:set_size("armor", 6)
  377. player_inv:set_size("armor", 6)
  378. for i=1, 6 do
  379. local stack = player_inv:get_stack("armor", i)
  380. armor_inv:set_stack("armor", i, stack)
  381. end
  382. armor.def[name] = {
  383. state = 0,
  384. count = 0,
  385. level = 0,
  386. heal = 0,
  387. jump = 1,
  388. speed = 1,
  389. gravity = 1,
  390. fire = 0,
  391. water = 0,
  392. radiation = 0,
  393. }
  394. armor.textures[name] = {
  395. skin = armor.default_skin..".png",
  396. armor = "3d_armor_trans.png",
  397. wielditem = "3d_armor_trans.png",
  398. preview = armor.default_skin.."_preview.png",
  399. }
  400. if skin_mod == "skins" then
  401. local skin = skins.skins[name]
  402. if skin and skins.get_type(skin) == skins.type.MODEL then
  403. armor.textures[name].skin = skin..".png"
  404. end
  405. elseif skin_mod == "simple_skins" then
  406. local skin = skins.skins[name]
  407. if skin then
  408. armor.textures[name].skin = skin..".png"
  409. end
  410. elseif skin_mod == "u_skins" then
  411. local skin = u_skins.u_skins[name]
  412. if skin and u_skins.get_type(skin) == u_skins.type.MODEL then
  413. armor.textures[name].skin = skin..".png"
  414. end
  415. elseif skin_mod == "wardrobe" then
  416. local skin = wardrobe.playerSkins[name]
  417. if skin then
  418. armor.textures[name].skin = skin
  419. end
  420. end
  421. if minetest.get_modpath("player_textures") then
  422. local filename = minetest.get_modpath("player_textures").."/textures/player_"..name
  423. local f = io.open(filename..".png")
  424. if f then
  425. f:close()
  426. armor.textures[name].skin = "player_"..name..".png"
  427. end
  428. end
  429. for i=1, ARMOR_INIT_TIMES do
  430. minetest.after(ARMOR_INIT_DELAY * i, function(player)
  431. armor:set_player_armor(player)
  432. if not inv_mod then
  433. armor:update_inventory(player)
  434. end
  435. end, player)
  436. end
  437. end)
  438. if ARMOR_DROP == true or ARMOR_DESTROY == true then
  439. armor.drop_armor = function(pos, stack)
  440. local obj = minetest.add_item(pos, stack)
  441. if obj then
  442. obj:setvelocity({x=math_random(-1, 1), y=5, z=math_random(-1, 1)})
  443. end
  444. end
  445. armor.on_dieplayer = function(player, bp)
  446. local name, player_inv, armor_inv = armor:get_valid_player(player, "[on_dieplayer]")
  447. local pos = vector.new(bp)
  448. if not name then
  449. return
  450. end
  451. local drop = {}
  452. for i=1, player_inv:get_size("armor") do
  453. local stack = armor_inv:get_stack("armor", i)
  454. if stack:get_count() > 0 then
  455. table.insert(drop, stack)
  456. armor_inv:set_stack("armor", i, nil)
  457. player_inv:set_stack("armor", i, nil)
  458. end
  459. end
  460. armor:set_player_armor(player)
  461. if inv_mod == "unified_inventory" then
  462. unified_inventory.set_inventory_formspec(player, "craft")
  463. elseif inv_mod == "inventory_plus" then
  464. local formspec = inventory_plus.get_formspec(player,"main")
  465. inventory_plus.set_inventory_formspec(player, formspec)
  466. else
  467. armor:update_inventory(player)
  468. end
  469. local location = minetest.pos_to_string(pos)
  470. if ARMOR_DESTROY == false then
  471. local node = minetest.get_node(pos)
  472. if node.name == "bones:bones" then
  473. local meta = minetest.get_meta(pos)
  474. local inv = meta:get_inventory()
  475. for _,stack in ipairs(drop) do
  476. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  477. inv:add_item("main", stack)
  478. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  479. else
  480. armor.drop_armor(pos, stack)
  481. end
  482. end
  483. else
  484. minetest.log("warning", "Failed to add armor to bones node at " ..
  485. minetest.pos_to_string(pos) .. "!")
  486. for _,stack in ipairs(drop) do
  487. armor.drop_armor(pos, stack)
  488. end
  489. end
  490. end
  491. end
  492. end
  493. minetest.register_on_player_hpchange(function(player, hp_change)
  494. local name, player_inv, armor_inv = armor:get_valid_player(player, "[on_hpchange]")
  495. if name and hp_change < 0 then
  496. -- used for insta kill tools/commands like /kill (doesnt damage armor)
  497. if hp_change < -100 then
  498. return hp_change
  499. end
  500. local heal_max = 0
  501. local state = 0
  502. local items = 0
  503. for i=1, 6 do
  504. local stack = player_inv:get_stack("armor", i)
  505. if stack:get_count() > 0 then
  506. local use = stack:get_definition().groups["armor_use"] or 0
  507. local heal = stack:get_definition().groups["armor_heal"] or 0
  508. local item = stack:get_name()
  509. stack:add_wear(use)
  510. armor_inv:set_stack("armor", i, stack)
  511. player_inv:set_stack("armor", i, stack)
  512. state = state + stack:get_wear()
  513. items = items + 1
  514. if stack:get_count() == 0 then
  515. local desc = minetest.registered_items[item].description
  516. if desc then
  517. minetest.chat_send_player(name, "# Server: Your " .. desc .. " got destroyed!")
  518. ambiance.sound_play("default_tool_breaks", player:get_pos(), 1.0, 20)
  519. end
  520. armor:set_player_armor(player)
  521. armor:update_inventory(player)
  522. end
  523. heal_max = heal_max + heal
  524. end
  525. end
  526. armor.def[name].state = state
  527. armor.def[name].count = items
  528. heal_max = heal_max * ARMOR_HEAL_MULTIPLIER
  529. if heal_max > math_random(100) then
  530. hp_change = 0
  531. end
  532. armor:update_armor(player)
  533. end
  534. return hp_change
  535. end, true)
  536. -- Register button once.
  537. if inv_mod and inv_mod == "inventory_plus" then
  538. inventory_plus.register_button("armor", "Armor")
  539. end