api.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. -- support for i18n
  2. local S = armor_i18n.gettext
  3. local skin_previews = {}
  4. local use_player_monoids = minetest.global_exists("player_monoids")
  5. local use_armor_monoid = minetest.global_exists("armor_monoid")
  6. local use_pova_mod = minetest.get_modpath("pova")
  7. local armor_def = setmetatable({}, {
  8. __index = function()
  9. return setmetatable({
  10. groups = setmetatable({}, {
  11. __index = function()
  12. return 0
  13. end})
  14. }, {
  15. __index = function()
  16. return 0
  17. end
  18. })
  19. end,
  20. })
  21. local armor_textures = setmetatable({}, {
  22. __index = function()
  23. return setmetatable({}, {
  24. __index = function()
  25. return "blank.png"
  26. end
  27. })
  28. end
  29. })
  30. armor = {
  31. timer = 0,
  32. elements = {"head", "torso", "legs", "feet", "ring", "gloves", "amulet"},
  33. physics = {"jump", "speed", "gravity"},
  34. attributes = {"heal", "fire", "water", "block", "dmg_resist"},
  35. formspec = "image[2.5,0;2,4;armor_preview]"..
  36. default.gui_bg..
  37. default.gui_bg_img..
  38. default.gui_slots..
  39. default.get_hotbar_bg(0, 4.7)..
  40. "list[current_player;main;0,4.7;8,1;]"..
  41. "list[current_player;main;0,5.85;8,3;8]",
  42. def = armor_def,
  43. textures = armor_textures,
  44. default_skin = "character",
  45. materials = {
  46. wood = "group:wood",
  47. },
  48. fire_nodes = {
  49. {"default:lava_source", 5, 12},
  50. {"default:lava_flowing", 5, 8},
  51. {"nether:lava_source", 5, 8},
  52. {"fire:basic_flame", 3, 4},
  53. {"fire:permanent_flame", 3, 4},
  54. {"epic:permanent_flame", 3, 4},
  55. {"nether:lava_crust", 3, 4},
  56. },
  57. registered_groups = {["fleshy"]=100},
  58. registered_callbacks = {
  59. on_update = {},
  60. on_equip = {},
  61. on_unequip = {},
  62. on_damage = {},
  63. on_destroy = {},
  64. },
  65. migrate_old_inventory = true,
  66. version = "0.4.13",
  67. }
  68. armor.config = {
  69. init_delay = 2,
  70. init_times = 10,
  71. bones_delay = 1,
  72. update_time = 1,
  73. drop = minetest.get_modpath("bones") ~= nil,
  74. destroy = false,
  75. level_multiplier = 0,
  76. heal_multiplier = 1,
  77. material_wood = true,
  78. material_cactus = true,
  79. material_steel = true,
  80. material_bronze = true,
  81. material_diamond = true,
  82. material_gold = true,
  83. material_mithril = true,
  84. material_crystal = true,
  85. water_protect = true,
  86. fire_protect = minetest.get_modpath("ethereal") ~= nil,
  87. punch_damage = true,
  88. }
  89. -- Armor Registration
  90. armor.register_armor = function(self, name, def)
  91. minetest.register_tool(name, def)
  92. end
  93. armor.register_armor_group = function(self, group, base)
  94. base = base or 100
  95. self.registered_groups[group] = base
  96. if use_armor_monoid then
  97. armor_monoid.register_armor_group(group, base)
  98. end
  99. end
  100. -- Armor callbacks
  101. armor.register_on_update = function(self, func)
  102. if type(func) == "function" then
  103. table.insert(self.registered_callbacks.on_update, func)
  104. end
  105. end
  106. armor.register_on_equip = function(self, func)
  107. if type(func) == "function" then
  108. table.insert(self.registered_callbacks.on_equip, func)
  109. end
  110. end
  111. armor.register_on_unequip = function(self, func)
  112. if type(func) == "function" then
  113. table.insert(self.registered_callbacks.on_unequip, func)
  114. end
  115. end
  116. armor.register_on_damage = function(self, func)
  117. if type(func) == "function" then
  118. table.insert(self.registered_callbacks.on_damage, func)
  119. end
  120. end
  121. armor.register_on_destroy = function(self, func)
  122. if type(func) == "function" then
  123. table.insert(self.registered_callbacks.on_destroy, func)
  124. end
  125. end
  126. armor.run_callbacks = function(self, callback, player, index, stack)
  127. if stack then
  128. local def = stack:get_definition() or {}
  129. if type(def[callback]) == "function" then
  130. def[callback](player, index, stack)
  131. end
  132. end
  133. local callbacks = self.registered_callbacks[callback]
  134. if callbacks then
  135. for _, func in pairs(callbacks) do
  136. func(player, index, stack)
  137. end
  138. end
  139. end
  140. armor.update_player_visuals = function(self, player)
  141. if not player then
  142. return
  143. end
  144. local name = player:get_player_name()
  145. if self.textures[name] then
  146. default.player_set_textures(player, {
  147. self.textures[name].skin,
  148. self.textures[name].armor,
  149. self.textures[name].wielditem,
  150. })
  151. end
  152. self:run_callbacks("on_update", player)
  153. end
  154. armor.set_player_armor = function(self, player)
  155. local name, armor_inv = self:get_valid_player(player, "[set_player_armor]")
  156. if not name then
  157. return
  158. end
  159. local state = 0
  160. local count = 0
  161. local material = {count=1}
  162. local preview = armor:get_preview(name)
  163. local texture = "3d_armor_trans.png"
  164. local textures = {}
  165. local physics = {}
  166. local attributes = {}
  167. local levels = {}
  168. local groups = {}
  169. local change = {}
  170. for _, phys in pairs(self.physics) do
  171. physics[phys] = 1
  172. end
  173. for _, attr in pairs(self.attributes) do
  174. attributes[attr] = 0
  175. end
  176. for group, _ in pairs(self.registered_groups) do
  177. change[group] = 1
  178. levels[group] = 0
  179. end
  180. local list = armor_inv:get_list("armor")
  181. if type(list) ~= "table" then
  182. return
  183. end
  184. for i, stack in pairs(list) do
  185. if stack:get_count() == 1 then
  186. local def = stack:get_definition()
  187. for _, element in pairs(self.elements) do
  188. if def.groups["armor_"..element] then
  189. if def.armor_groups then
  190. for group, level in pairs(def.armor_groups) do
  191. if levels[group] then
  192. levels[group] = levels[group] + level
  193. end
  194. end
  195. else
  196. local level = def.groups["armor_"..element]
  197. levels["fleshy"] = levels["fleshy"] + level --Level is 90% of armor_group added value.
  198. end
  199. break
  200. end
  201. -- DEPRECATED, use armor_groups instead
  202. if def.groups["armor_radiation"] and levels["radiation"] then
  203. levels["radiation"] = def.groups["armor_radiation"]
  204. end
  205. end
  206. local item = stack:get_name()
  207. local tex = def.texture or item:gsub("%:", "_")
  208. tex = tex:gsub(".png$", "")
  209. local prev = def.preview or tex.."_preview"
  210. prev = prev:gsub(".png$", "")
  211. texture = texture.."^"..tex..".png"
  212. preview = preview.."^"..prev..".png"
  213. state = state + stack:get_wear()
  214. count = count + 1
  215. for _, phys in pairs(self.physics) do
  216. local value = def.groups["physics_"..phys] or 0
  217. physics[phys] = physics[phys] + value
  218. end
  219. for _, attr in pairs(self.attributes) do
  220. local value = def.groups["armor_"..attr] or 0
  221. attributes[attr] = attributes[attr] + value
  222. end
  223. local mat = string.match(item, "%:.+_(.+)$")
  224. if material.name then
  225. if material.name == mat then
  226. material.count = material.count + 1
  227. end
  228. else
  229. material.name = mat
  230. end
  231. end
  232. end
  233. for group, level in pairs(levels) do
  234. if level > 0 then
  235. level = level * armor.config.level_multiplier
  236. if material.name and material.count == #self.elements then
  237. level = level * 1.1
  238. end
  239. end
  240. local base = self.registered_groups[group]
  241. self.def[name].groups[group] = level
  242. if level > base then
  243. level = base
  244. end
  245. groups[group] = base - level
  246. change[group] = groups[group] / base
  247. end
  248. for _, attr in pairs(self.attributes) do
  249. local mult = attr == "heal" and self.config.heal_multiplier or 1
  250. self.def[name][attr] = attributes[attr] * mult
  251. end
  252. local pos = player:get_pos()
  253. local altitude = pos.y
  254. for _, phys in pairs(self.physics) do
  255. if altitude > 5000 and altitude < 6000 then
  256. physics['gravity'] = physics['gravity'] - .13
  257. elseif altitude > 6000 and altitude < 7000 then
  258. physics['gravity'] = physics['gravity'] - .2
  259. end
  260. self.def[name][phys] = physics[phys]
  261. end
  262. if use_armor_monoid then
  263. armor_monoid.monoid:add_change(player, change, "3d_armor:armor")
  264. else
  265. player:set_armor_groups(groups)
  266. end
  267. if use_player_monoids then
  268. player_monoids.speed:add_change(player, physics.speed,
  269. "3d_armor:physics")
  270. player_monoids.jump:add_change(player, physics.jump,
  271. "3d_armor:physics")
  272. player_monoids.gravity:add_change(player, physics.gravity,
  273. "3d_armor:physics")
  274. elseif use_pova_mod then
  275. -- only add the changes, not the default 1.0 for each physics setting
  276. pova.add_override(name, "3d_armor", {
  277. speed = physics.speed - 1,
  278. jump = physics.jump - 1,
  279. gravity = physics.gravity - 1,
  280. })
  281. pova.do_override(player)
  282. else
  283. player:set_physics_override(physics)
  284. end
  285. self.textures[name].armor = texture
  286. self.textures[name].preview = preview
  287. self.def[name].level = self.def[name].groups.fleshy or 0
  288. self.def[name].state = state
  289. self.def[name].count = count
  290. self:update_player_visuals(player)
  291. end
  292. armor.punch = function(self, player, hitter, time_from_last_punch, tool_capabilities)
  293. local name, armor_inv = self:get_valid_player(player, "[punch]")
  294. if not name then
  295. return
  296. end
  297. local state = 0
  298. local count = 0
  299. local recip = true
  300. local default_groups = {cracky=3, snappy=3, choppy=3, crumbly=3, level=1}
  301. local list = armor_inv:get_list("armor")
  302. for i, stack in pairs(list) do
  303. if stack:get_count() == 1 then
  304. local name = stack:get_name()
  305. local use = minetest.get_item_group(name, "armor_use") or 0
  306. local damage = use > 0
  307. local def = stack:get_definition() or {}
  308. if type(def.on_punched) == "function" then
  309. damage = def.on_punched(player, hitter, time_from_last_punch,
  310. tool_capabilities) ~= false and damage == true
  311. end
  312. if damage == true and tool_capabilities then
  313. local damage_groups = def.damage_groups or default_groups
  314. local level = damage_groups.level or 0
  315. local groupcaps = tool_capabilities.groupcaps or {}
  316. local uses = 0
  317. damage = false
  318. for group, caps in pairs(groupcaps) do
  319. local maxlevel = caps.maxlevel or 0
  320. local diff = maxlevel - level
  321. if diff == 0 then
  322. diff = 1
  323. end
  324. if diff > 0 and caps.times then
  325. local group_level = damage_groups[group]
  326. if group_level then
  327. local time = caps.times[group_level]
  328. if time then
  329. local dt = time_from_last_punch or 0
  330. if dt > time / diff then
  331. if caps.uses then
  332. uses = caps.uses * math.pow(3, diff)
  333. end
  334. damage = true
  335. break
  336. end
  337. end
  338. end
  339. end
  340. end
  341. if damage == true and recip == true and hitter and
  342. def.reciprocate_damage == true and uses > 0 then
  343. local item = hitter:get_wielded_item()
  344. if item and item:get_name() ~= "" then
  345. item:add_wear(65535 / uses)
  346. hitter:set_wielded_item(item)
  347. end
  348. -- reciprocate tool damage only once
  349. recip = false
  350. end
  351. end
  352. if damage == true and hitter == "fire" then
  353. damage = minetest.get_item_group(name, "flammable") > 0
  354. end
  355. if damage == true and hitter == "water" then
  356. damage = minetest.get_item_group(name, "armor_water") > 0
  357. end
  358. if damage == true then
  359. self:damage(player, i, stack, use)
  360. end
  361. state = state + stack:get_wear()
  362. count = count + 1
  363. end
  364. end
  365. self.def[name].state = state
  366. self.def[name].count = count
  367. end
  368. armor.damage = function(self, player, index, stack, use)
  369. local old_stack = ItemStack(stack)
  370. stack:add_wear(use)
  371. self:run_callbacks("on_damage", player, index, stack)
  372. self:set_inventory_stack(player, index, stack)
  373. if stack:get_count() == 0 then
  374. self:run_callbacks("on_unequip", player, index, old_stack)
  375. self:run_callbacks("on_destroy", player, index, old_stack)
  376. self:set_player_armor(player)
  377. end
  378. end
  379. armor.get_player_skin = function(self, name)
  380. if (self.skin_mod == "skins" or self.skin_mod == "simple_skins") and skins.skins[name] then
  381. return skins.skins[name]..".png"
  382. elseif self.skin_mod == "u_skins" and u_skins.u_skins[name] then
  383. return u_skins.u_skins[name]..".png"
  384. elseif self.skin_mod == "wardrobe" and wardrobe.playerSkins and wardrobe.playerSkins[name] then
  385. return wardrobe.playerSkins[name]
  386. elseif self.skin_mod == "custom_skin" then
  387. return custom_skin.texture[name]
  388. end
  389. return armor.default_skin..".png"
  390. end
  391. armor.add_preview = function(self, preview)
  392. skin_previews[preview] = true
  393. end
  394. armor.get_preview = function(self, name)
  395. local preview = string.gsub(armor:get_player_skin(name), ".png", "_preview.png")
  396. if skin_previews[preview] then
  397. return preview
  398. end
  399. return "character_preview.png"
  400. end
  401. armor.get_armor_formspec = function(self, name, listring)
  402. if armor.def[name].init_time == 0 then
  403. return "label[0,0;Armor not initialized!]"
  404. end
  405. local formspec = armor.formspec..
  406. "list[detached:"..name.."_armor;armor;0,0.5;2,3;]"
  407. if listring == true then
  408. formspec = formspec.."listring[current_player;main]"..
  409. "listring[detached:"..name.."_armor;armor]"
  410. end
  411. formspec = formspec:gsub("armor_preview", armor.textures[name].preview)
  412. formspec = formspec:gsub("armor_level", armor.def[name].level)
  413. for _, attr in pairs(self.attributes) do
  414. formspec = formspec:gsub("armor_attr_"..attr, armor.def[name][attr])
  415. end
  416. for group, _ in pairs(self.registered_groups) do
  417. formspec = formspec:gsub("armor_group_"..group,
  418. armor.def[name].groups[group])
  419. end
  420. return formspec
  421. end
  422. armor.get_element = function(self, item_name)
  423. for _, element in pairs(armor.elements) do
  424. if minetest.get_item_group(item_name, "armor_"..element) > 0 then
  425. return element
  426. end
  427. end
  428. end
  429. armor.serialize_inventory_list = function(self, list)
  430. local list_table = {}
  431. for _, stack in ipairs(list) do
  432. table.insert(list_table, stack:to_string())
  433. end
  434. return minetest.serialize(list_table)
  435. end
  436. armor.deserialize_inventory_list = function(self, list_string)
  437. local list_table = minetest.deserialize(list_string)
  438. local list = {}
  439. for _, stack in ipairs(list_table or {}) do
  440. table.insert(list, ItemStack(stack))
  441. end
  442. return list
  443. end
  444. armor.load_armor_inventory = function(self, player)
  445. local _, inv = self:get_valid_player(player, "[load_armor_inventory]")
  446. if inv then
  447. local player_attributes = player:get_meta()
  448. local armor_list_string = player_attributes:get_string("3d_armor_inventory")
  449. if armor_list_string then
  450. inv:set_list("armor",
  451. self:deserialize_inventory_list(armor_list_string))
  452. return true
  453. end
  454. end
  455. end
  456. armor.save_armor_inventory = function(self, player)
  457. local _, inv = self:get_valid_player(player, "[save_armor_inventory]")
  458. if inv then
  459. local player_attributes = player:get_meta()
  460. player_attributes:set_string("3d_armor_inventory",
  461. self:serialize_inventory_list(inv:get_list("armor")))
  462. end
  463. end
  464. armor.update_inventory = function(self, player)
  465. -- DEPRECATED: Legacy inventory support
  466. end
  467. armor.set_inventory_stack = function(self, player, i, stack)
  468. local _, inv = self:get_valid_player(player, "[set_inventory_stack]")
  469. if inv then
  470. inv:set_stack("armor", i, stack)
  471. self:save_armor_inventory(player)
  472. end
  473. end
  474. armor.get_valid_player = function(self, player, msg)
  475. msg = msg or ""
  476. if not player then
  477. minetest.log("warning", S("3d_armor: Player reference is nil @1", msg))
  478. return
  479. end
  480. local name = player:get_player_name()
  481. if not name then
  482. minetest.log("warning", S("3d_armor: Player name is nil @1", msg))
  483. return
  484. end
  485. local inv = minetest.get_inventory({type="detached", name=name.."_armor"})
  486. if not inv then
  487. minetest.log("warning", S("3d_armor: Detached armor inventory is nil @1", msg))
  488. return
  489. end
  490. return name, inv
  491. end
  492. armor.drop_armor = function(pos, stack)
  493. local node = minetest.get_node_or_nil(pos)
  494. if node then
  495. local obj = minetest.add_item(pos, stack)
  496. if obj then
  497. obj:setvelocity({x=math.random(-1, 1), y=5, z=math.random(-1, 1)})
  498. end
  499. end
  500. end