armor.lua 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. -- Localize for performance.
  2. local math_floor = math.floor
  3. local math_random = math.random
  4. armor.elements = armor.elements or {"head", "torso", "legs", "feet"}
  5. armor.physics = armor.physics or {"jump", "speed", "gravity"}
  6. armor.default_skin = "character"
  7. armor.version = "MustTest"
  8. armor.formspec =
  9. "size[8,8.5]" ..
  10. default.gui_bg ..
  11. default.gui_bg_img ..
  12. default.gui_slots ..
  13. "button[0,0.5;2,0.5;main;Back]" ..
  14. "image[4,0.25;2,4;armor_preview]" ..
  15. "label[6,0.1;Health: hp_max]" ..
  16. "label[6,0.4;Block: armor_heal]" ..
  17. "list[current_player;main;0,4.25;8,1;]" ..
  18. "list[current_player;main;0,5.5;8,3;8]" ..
  19. default.get_hotbar_bg(0, 4.25)
  20. -- Transient player data storage.
  21. armor.def = armor.def or {}
  22. armor.textures = armor.textures or {}
  23. --------------------------------------------------------------------------------
  24. -- Use this function (just before) you call player:punch(), to notify the armor
  25. -- mod what the reason info for the punch is. Because as of this writing, you
  26. -- can't pass the PlayerHPChangeReason table directly to the :punch() method.
  27. -- Silly Minetest!
  28. function armor.notify_punch_reason(reason)
  29. armor.hp_change_reason = reason
  30. armor.hp_change_reason.type = "punch"
  31. end
  32. -- Same as above, but use if you're calling :set_hp() on the player, and need to
  33. -- notify the armor code what the HP change is for. Note that this is especially
  34. -- important if you're changing the players 'hp_max' as well. In such cases, the
  35. -- reason object often DOES NOT propogate through the engine, and thus this has
  36. -- to be done manually and entirely in Lua. Silly Minetest!
  37. function armor.notify_set_hp_reason(reason)
  38. armor.hp_change_reason = reason
  39. armor.hp_change_reason.type = "set_hp"
  40. end
  41. --------------------------------------------------------------------------------
  42. -- To be called by armor code only.
  43. function armor.notify_death_reason(reason)
  44. armor.death_reason = reason
  45. end
  46. -- May return nil. Note: this function also clears the reason data, to ensure it
  47. -- is only used once!
  48. function armor.get_hp_change_reason(engine_reason)
  49. if armor.hp_change_reason then
  50. local reason = armor.hp_change_reason
  51. armor.hp_change_reason = nil
  52. -- Copy everything from the engine's reason, but don't clobber our special
  53. -- 'reason' key.
  54. for k, v in pairs(engine_reason) do
  55. if k ~= "reason" then
  56. reason[k] = v
  57. end
  58. end
  59. return reason
  60. end
  61. return nil
  62. end
  63. -- To be called in one place only (the bones code) as it clears the reason
  64. -- at the same time.
  65. function armor.get_death_reason(engine_reason)
  66. if armor.death_reason then
  67. local reason = armor.death_reason
  68. armor.death_reason = nil
  69. -- Copy everything from the engine's reason, but don't clobber our special
  70. -- 'reason' key.
  71. for k, v in pairs(engine_reason) do
  72. if k ~= "reason" then
  73. reason[k] = v
  74. end
  75. end
  76. return reason
  77. end
  78. return nil
  79. end
  80. function armor.update_player_visuals(self, player)
  81. if not player then
  82. return
  83. end
  84. local name = player:get_player_name()
  85. if self.textures[name] then
  86. default.player_set_textures(player, {
  87. self.textures[name].skin,
  88. self.textures[name].armor,
  89. self.textures[name].wielditem,
  90. })
  91. end
  92. end
  93. function armor.set_player_armor(self, player)
  94. local name, player_inv = armor:get_valid_player(player, "[set_player_armor]")
  95. if not name then
  96. return
  97. end
  98. local armor_texture = "3d_armor_trans.png"
  99. -- Armor groups.
  100. local loc_arm_grps = {}
  101. local armor_heal = 0
  102. local state = 0
  103. local items = 0
  104. local elements = {}
  105. local textures = {}
  106. local physics_o = {speed=1,gravity=1,jump=1}
  107. local material = {type=nil, count=1}
  108. local preview_string = armor:get_preview(name) or "character_preview.png"
  109. -- List of textures to be applied to the character preview.
  110. local preview_table = {}
  111. for _,v in ipairs(self.elements) do
  112. elements[v] = false
  113. end
  114. for i = 1, 6 do
  115. local stack = player_inv:get_stack("armor", i)
  116. local item = stack:get_name()
  117. if stack:get_count() == 1 then
  118. local def = stack:get_definition()
  119. for k, v in pairs(elements) do
  120. if v == false then
  121. local level = def.groups["armor_" .. k]
  122. if level and level > 0 then
  123. --minetest.log('armor piece: ' .. k)
  124. local tex = def.texture or item:gsub("%:", "_")
  125. textures[#textures+1] = (tex .. ".png")
  126. preview_table[#preview_table+1] = (tex .. "_preview.png")
  127. state = state + stack:get_wear()
  128. items = items + 1
  129. armor_heal = armor_heal + (def.groups["armor_heal"] or 0)
  130. -- Local armor groups.
  131. local lag = def._armor_resist_groups or {}
  132. for k, v in pairs(lag) do
  133. --minetest.log('group: ' .. k)
  134. loc_arm_grps[k] = (loc_arm_grps[k] or 0) + lag[k]
  135. end
  136. for kk,vv in ipairs(self.physics) do
  137. local o_value = def.groups["physics_"..vv]
  138. if o_value then
  139. physics_o[vv] = physics_o[vv] + o_value
  140. end
  141. end
  142. local mat = string.match(item, "%:.+_(.+)$")
  143. if material.type then
  144. if material.type == mat then
  145. material.count = material.count + 1
  146. end
  147. else
  148. material.type = mat
  149. end
  150. -- Mark this type/peice of armor as handled.
  151. -- This avoids letting players use duplicate armor peices.
  152. elements[k] = true
  153. end
  154. end
  155. end
  156. end
  157. end
  158. -- I guess this gives an armor bonus if all armors are the same material?
  159. -- MustTest.
  160. if material.type and material.count == #self.elements then
  161. loc_arm_grps.fleshy = (loc_arm_grps.fleshy or 0) * 1.1
  162. end
  163. armor_heal = armor_heal * ARMOR_HEAL_MULTIPLIER
  164. if #textures > 0 then
  165. armor_texture = table.concat(textures, "^")
  166. end
  167. local armor_groups = {}
  168. for k, v in pairs(loc_arm_grps) do
  169. armor_groups[k] = 100 - (loc_arm_grps[k] * ARMOR_LEVEL_MULTIPLIER)
  170. --minetest.log('armor: ' .. k .. '=' .. armor_groups[k])
  171. -- Damage mitigation cannot go above 90%.
  172. if armor_groups[k] < 10 then
  173. armor_groups[k] = 10
  174. end
  175. end
  176. -- Sort preview texture list so that the shield is always on top.
  177. -- This looks better.
  178. table.sort(preview_table, function(a, b)
  179. if not a:find("shield") and b:find("shield") then
  180. return true
  181. end
  182. return false
  183. end)
  184. -- Generate character preview (a list of textures applied on top of each other).
  185. for k, v in ipairs(preview_table) do
  186. preview_string = preview_string .. "^" .. v
  187. end
  188. player:set_armor_groups(utility.builtin_armor_groups(armor_groups))
  189. pova.set_modifier(player, "physics", physics_o, "3d_armor")
  190. self.textures[name].armor = armor_texture
  191. self.textures[name].preview = preview_string
  192. self.def[name].state = state
  193. self.def[name].count = items
  194. self.def[name].heal = armor_heal
  195. self.def[name].jump = physics_o.jump
  196. self.def[name].speed = physics_o.speed
  197. self.def[name].gravity = physics_o.gravity
  198. self.def[name].resistances = loc_arm_grps
  199. self:update_player_visuals(player)
  200. end
  201. function armor.update_armor(self, player)
  202. -- Legacy support: Called when armor levels are changed
  203. -- Other mods can hook on to this function, see hud mod for example
  204. end
  205. function armor.get_player_skin(self, name)
  206. local skin = skins.skins[name]
  207. return skin or armor.default_skin
  208. end
  209. function armor.get_preview(self, name)
  210. end
  211. local function get_player_max_hp(name)
  212. local pref = minetest.get_player_by_name(name)
  213. if pref then
  214. local scale = 500
  215. local hp_max = pova.get_active_modifier(pref, "properties").hp_max
  216. return math_floor(hp_max / scale)
  217. end
  218. return 20
  219. end
  220. -- Pair internal armor group keys to human-readable names.
  221. local formspec_keysubs = {
  222. fleshy = "slash",
  223. boom = "explosive",
  224. cracky = "bash",
  225. crumbly = "wither",
  226. snappy = "pierce",
  227. choppy = "cleave",
  228. arrow = "ranged",
  229. lava = "molten",
  230. heat = "fire",
  231. electrocute = "arcane",
  232. }
  233. function armor.get_armor_formspec(self, name)
  234. if not armor.textures[name] then
  235. minetest.log("error", "3d_armor: Player texture["..name.."] is nil [get_armor_formspec]")
  236. return ""
  237. end
  238. if not armor.def[name] then
  239. minetest.log("error", "3d_armor: Armor def["..name.."] is nil [get_armor_formspec]")
  240. return ""
  241. end
  242. local formspec = armor.formspec .. "list[detached:"..name.."_armor;armor;0,1.5;3,2;]"
  243. formspec = formspec:gsub("armor_preview", armor.textures[name].preview)
  244. formspec = formspec:gsub("armor_heal", math_floor(armor.def[name].heal))
  245. formspec = formspec:gsub("hp_max", tostring(get_player_max_hp(name)))
  246. --minetest.log('testing: ' .. type(armor.def[name].resistances))
  247. -- Print out armor stats, whatever they are.
  248. local y = 0.7
  249. for k, v in pairs(armor.def[name].resistances) do
  250. --minetest.log('k=' .. k .. ', v=' .. v)
  251. if formspec_keysubs[k] then
  252. k = formspec_keysubs[k]
  253. end
  254. local s = k:sub(1, 1):upper() .. k:sub(2)
  255. formspec = formspec .. "label[6," .. y .. ";" .. s .. ": " .. math_floor(v) .. "]"
  256. y = y + 0.3
  257. end
  258. return formspec
  259. end
  260. function armor.update_inventory(self, player)
  261. local name = armor:get_valid_player(player, "[set_player_armor]")
  262. if not name then
  263. return
  264. end
  265. local formspec = armor:get_armor_formspec(name)
  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. end
  273. function armor.get_valid_player(self, player, msg)
  274. msg = msg or ""
  275. if not player then
  276. minetest.log("error", "3d_armor: Player reference is nil "..msg)
  277. return
  278. end
  279. local pname = player:get_player_name()
  280. if not pname then
  281. minetest.log("error", "3d_armor: Player name is nil "..msg)
  282. return
  283. end
  284. local pos = player:get_pos()
  285. local player_inv = player:get_inventory()
  286. local armor_inv = minetest.get_inventory({type="detached", name=pname.."_armor"})
  287. if not pos then
  288. minetest.log("error", "3d_armor: Player position is nil "..msg)
  289. return
  290. elseif not player_inv then
  291. minetest.log("error", "3d_armor: Player inventory is nil "..msg)
  292. return
  293. elseif not armor_inv then
  294. -- This happens because player:set_hp() is used in 'on_joinplayer()' from
  295. -- another mod (XP) before the armor inv is set up. This is normal.
  296. -- Can't figure out why this happens even when the XP change happens *after*
  297. -- the joinplayer callbacks inside minetest.after()!?
  298. minetest.log("warning", "3d_armor: Detached armor inventory is nil "..msg)
  299. return
  300. end
  301. return pname, player_inv, armor_inv, pos
  302. end
  303. function armor.on_player_receive_fields(player, formname, fields)
  304. local name = armor:get_valid_player(player, "[on_player_receive_fields]")
  305. if not name then
  306. return
  307. end
  308. if fields.armor then
  309. local formspec = armor:get_armor_formspec(name)
  310. inventory_plus.set_inventory_formspec(player, formspec)
  311. return
  312. end
  313. for field, _ in pairs(fields) do
  314. if string.find(field, "skins_set") then
  315. minetest.after(0, function(player)
  316. local skin = armor:get_player_skin(name)
  317. armor.textures[name].skin = skin..".png"
  318. armor:set_player_armor(player)
  319. end, player)
  320. end
  321. end
  322. end
  323. function armor.on_joinplayer(player)
  324. default.player_set_model(player, "3d_armor_character.b3d")
  325. local name = player:get_player_name()
  326. local player_inv = player:get_inventory()
  327. local armor_inv = minetest.create_detached_inventory(name.."_armor", {
  328. on_put = function(inv, listname, index, stack, player)
  329. player:get_inventory():set_stack(listname, index, stack)
  330. armor:set_player_armor(player)
  331. armor:update_inventory(player)
  332. end,
  333. on_take = function(inv, listname, index, stack, player)
  334. player:get_inventory():set_stack(listname, index, nil)
  335. armor:set_player_armor(player)
  336. armor:update_inventory(player)
  337. end,
  338. on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  339. local plaver_inv = player:get_inventory()
  340. local stack = inv:get_stack(to_list, to_index)
  341. player_inv:set_stack(to_list, to_index, stack)
  342. player_inv:set_stack(from_list, from_index, nil)
  343. armor:set_player_armor(player)
  344. armor:update_inventory(player)
  345. end,
  346. allow_put = function(inv, listname, index, stack, player)
  347. return 1
  348. end,
  349. allow_take = function(inv, listname, index, stack, player)
  350. return stack:get_count()
  351. end,
  352. allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
  353. return count
  354. end,
  355. }, name)
  356. armor_inv:set_size("armor", 6)
  357. player_inv:set_size("armor", 6)
  358. for i=1, 6 do
  359. local stack = player_inv:get_stack("armor", i)
  360. armor_inv:set_stack("armor", i, stack)
  361. end
  362. armor.def[name] = {
  363. state = 0,
  364. count = 0,
  365. heal = 0,
  366. jump = 1,
  367. speed = 1,
  368. gravity = 1,
  369. }
  370. armor.textures[name] = {
  371. skin = armor.default_skin..".png",
  372. armor = "3d_armor_trans.png",
  373. wielditem = "3d_armor_trans.png",
  374. preview = armor.default_skin.."_preview.png",
  375. }
  376. local skin = skins.skins[name]
  377. if skin then
  378. armor.textures[name].skin = skin .. ".png"
  379. end
  380. if minetest.get_modpath("player_textures") then
  381. local filename = minetest.get_modpath("player_textures").."/textures/player_"..name
  382. local f = io.open(filename..".png")
  383. if f then
  384. f:close()
  385. armor.textures[name].skin = "player_"..name..".png"
  386. end
  387. end
  388. for i=1, ARMOR_INIT_TIMES do
  389. minetest.after(ARMOR_INIT_DELAY * i, function(player)
  390. armor:set_player_armor(player)
  391. end, player)
  392. end
  393. end
  394. function armor.drop_armor(pos, stack)
  395. local obj = minetest.add_item(pos, stack)
  396. if obj then
  397. obj:set_velocity({x=math_random(-1, 1), y=5, z=math_random(-1, 1)})
  398. end
  399. end
  400. function armor.on_dieplayer(player, bp)
  401. local name, player_inv, armor_inv = armor:get_valid_player(player, "[on_dieplayer]")
  402. local pos = vector.new(bp)
  403. if not name then
  404. return
  405. end
  406. local drop = {}
  407. for i=1, player_inv:get_size("armor") do
  408. local stack = armor_inv:get_stack("armor", i)
  409. if stack:get_count() > 0 then
  410. table.insert(drop, stack)
  411. armor_inv:set_stack("armor", i, nil)
  412. player_inv:set_stack("armor", i, nil)
  413. end
  414. end
  415. armor:set_player_armor(player)
  416. local formspec = inventory_plus.get_formspec(player,"main")
  417. inventory_plus.set_inventory_formspec(player, formspec)
  418. local location = minetest.pos_to_string(pos)
  419. local node = minetest.get_node(pos)
  420. if node.name == "bones:bones" then
  421. local meta = minetest.get_meta(pos)
  422. local inv = meta:get_inventory()
  423. for _,stack in ipairs(drop) do
  424. if stack:get_count() > 0 and inv:room_for_item("main", stack) then
  425. inv:add_item("main", stack)
  426. minetest.log("action", "Put " .. stack:to_string() .. " in bones @ " .. location .. ".")
  427. else
  428. armor.drop_armor(pos, stack)
  429. end
  430. end
  431. else
  432. minetest.log("warning", "Failed to add armor to bones node at " ..
  433. minetest.pos_to_string(pos) .. "!")
  434. for _,stack in ipairs(drop) do
  435. armor.drop_armor(pos, stack)
  436. end
  437. end
  438. end
  439. function armor.get_damage_type_from_reason(reason)
  440. local rs = reason.type or ""
  441. if rs == "set_hp" or rs == "punch" then
  442. -- Use custom reason only if available, otherwise use engine-defined reason,
  443. -- which will just be 'set_hp' or 'punch'.
  444. if reason.reason and reason.reason ~= "" then
  445. if reason.reason == "node_damage" then
  446. -- If the notification reason is 'node_damage', then 'damage_group'
  447. -- should store the actual damage type.
  448. rs = reason.damage_group
  449. else
  450. -- Otherwise the reason itself is usually the damage type.
  451. rs = reason.reason
  452. end
  453. end
  454. end
  455. return rs
  456. end
  457. function armor.damage_type_disables_cloak(rstr)
  458. if rstr == "fleshy" or rstr == "arrow" or rstr == "boom" or rstr == "fireball"
  459. or rstr == "cracky" or rstr == "crumbly" or rstr == "snappy" or rstr == "choppy"
  460. or rstr == "crush" then
  461. return true
  462. end
  463. return false
  464. end
  465. function armor.armor_wear_ignores_damage(rstr)
  466. if rstr == "" or rstr == "xp_update" or rstr == "hp_boost_end"
  467. or rstr == "hunger" or rstr == "drown" or rstr == "poison"
  468. or rstr == "pressure" then
  469. return true
  470. end
  471. return false
  472. end
  473. -- Calc wear multiplier based on reason and armor piece.
  474. -- Notes: 'type' will be "set_hp" if from player:set_hp().
  475. -- Must use 'reason' field in that case.
  476. --
  477. -- Reason types are:
  478. -- fall (fall damage, duh)
  479. -- punch (punched by something)
  480. -- drown (drowning, duh)
  481. -- heat (caused by quite a few sources of heat, including lava)
  482. -- pressure (water pressure, usually)
  483. -- ground (ground/floor hazard, spikes, etc)
  484. -- sharp (like cactus)
  485. -- crush (by falling node/object)
  486. -- portal (arcane damage by teleporting)
  487. -- poison (mushrooms, rotten meat)
  488. -- hunger (starvation)
  489. -- kill (kill command)
  490. -- radiation (reactors, etc)
  491. -- electrocute (solar panels)
  492. -- fireball (DM artillery, etc)
  493. -- arrow (player weapon or mob)
  494. -- boom (explosions)
  495. --
  496. -- Note: the above are also the names of damage groups and armor groups.
  497. function armor.wear_from_reason(item, def, reason)
  498. local rs = armor.get_damage_type_from_reason(reason)
  499. --minetest.log('Reason: ' .. rs)
  500. -- If reason not known, just return default wear multiplier.
  501. if rs == "" then
  502. return 1
  503. end
  504. if def._armor_wear_groups then
  505. local mult = def._armor_wear_groups[rs] or 1
  506. --minetest.log('wear multiplier: ' .. mult)
  507. return mult
  508. end
  509. -- Default, if wear modifier not found.
  510. return 1
  511. end
  512. function armor.on_player_hp_change(player, hp_change, reason)
  513. -- If a notified reason is available, use that instead.
  514. if reason.type == "punch" or reason.type == "set_hp" then
  515. local huh = armor.get_hp_change_reason(reason)
  516. if huh then
  517. reason = huh
  518. --minetest.chat_send_all('replace dump: ' .. dump(reason))
  519. end
  520. end
  521. local pname, player_inv, armor_inv = armor:get_valid_player(player, "[on_hpchange]")
  522. if not (pname and hp_change < 0) then
  523. return hp_change
  524. end
  525. -- Admin does not take damage.
  526. local singleplayer = minetest.is_singleplayer()
  527. if not singleplayer then
  528. if gdac.player_is_admin(player) then
  529. return 0
  530. end
  531. end
  532. --minetest.log('reason: ' .. (reason.type or reason.reason or "N/A"))
  533. --minetest.log('dump: ' .. dump(reason))
  534. --minetest.chat_send_all('dump: ' .. dump(reason))
  535. --minetest.log('on_player_hp_change: ' .. hp_change)
  536. -- used for insta kill tools/commands like /kill (doesnt damage armor)
  537. if hp_change <= -60000 then
  538. return hp_change
  539. end
  540. local heal_max = 0
  541. local state = 0
  542. local items = 0
  543. -- Need to scale fall damage since players' HP is very high.
  544. -- AFAIK, the only other way to adjust this would be to change every node's
  545. -- 'fall_damage_add_percent', but that would NOT be a good idea.
  546. if reason.type == "fall" then
  547. --minetest.log('fall: ' .. hp_change)
  548. -- Kids, don't get bit like I did. I just spend 1 hr trying to debug this
  549. -- code, and it turned out the problem was passing a negative value to this
  550. -- function for the amount of damage. Facepalm. Why do they let me code!?
  551. if armor.stomp_at(player, player:get_pos(), math.abs(hp_change * 1000)) then
  552. hp_change = hp_change * 100
  553. else
  554. hp_change = hp_change * 500
  555. end
  556. elseif reason.type == "drown" then
  557. -- In the case of drowning damage, we HAVE to do it this way, because
  558. -- Minetest does NOT, apparently, correctly apply drowning damage itself
  559. -- when the value is very high!
  560. hp_change = hp_change * 500
  561. end
  562. -- Why do I have to do this ugly hack? Because Minetest!!!!11111!!!11
  563. -- Note: purpose of this hack is to ensure that the node's 'damage_per_second'
  564. -- can be mitigated by player's current armor stats. As of this writing, by
  565. -- default in Minetest, 'damage_per_second' behaves like :set_hp(), bypassing
  566. -- armor groups.
  567. if reason.type == "node_damage" and reason.node and reason.node ~= "" then
  568. local ndef = minetest.registered_nodes[reason.node]
  569. if ndef and ndef._damage_per_second_type then
  570. local dtp = ndef._damage_per_second_type
  571. local dps = ndef.damage_per_second or 0
  572. -- Do NOT execute the following code within this function's call-stack.
  573. -- That's just asking for trouble because of possible recursion.
  574. minetest.after(0, function()
  575. local pref = minetest.get_player_by_name(pname)
  576. if pref then
  577. -- PlayerHPChangeReason.type will be 'punch'.
  578. utility.damage_player(pref, dtp, dps,
  579. {reason="node_damage", damage_group=dtp, source_node=reason.node, node_pos=reason.node_pos})
  580. end
  581. end)
  582. -- Do not apply damage from this point. Damage will be applied via 'punch'
  583. -- on the next server step.
  584. return 0
  585. end
  586. end
  587. local damage_type = armor.get_damage_type_from_reason(reason)
  588. local ignore_wear = armor.armor_wear_ignores_damage(damage_type)
  589. -- Test code to check that I know what I'm doing.
  590. --minetest.log('hpchange type: ' .. reason.type .. ', reason: ' .. damage_type)
  591. --minetest.log('scaled hp change: ' .. hp_change)
  592. -- Do not scale damage if the engine type is 'set_hp'.
  593. if reason.type == "punch" then
  594. -- If HP change would kill player, do NOT scale it!
  595. -- That results in misbehavior. This does have the result that damage
  596. -- scaling behaves weird on the final hit before a player dies, as in that
  597. -- case damage isn't scaled.
  598. if math.abs(hp_change) < player:get_hp() then
  599. hp_change = hp_change * hunger.get_damage_resistance(pname)
  600. end
  601. end
  602. for i = 1, 6 do
  603. local stack = player_inv:get_stack("armor", i)
  604. if stack:get_count() > 0 then
  605. local idef = stack:get_definition()
  606. local use = idef.groups["armor_use"] or 0
  607. local heal = idef.groups["armor_heal"] or 0
  608. local item = stack:get_name()
  609. if not ignore_wear then
  610. stack:add_wear(use * armor.wear_from_reason(item, idef, reason))
  611. end
  612. armor_inv:set_stack("armor", i, stack)
  613. player_inv:set_stack("armor", i, stack)
  614. state = state + stack:get_wear()
  615. items = items + 1
  616. if stack:get_count() == 0 then
  617. local desc = minetest.registered_items[item].description
  618. if desc then
  619. minetest.chat_send_player(pname, "# Server: Your " .. desc .. " got destroyed!")
  620. ambiance.sound_play("default_tool_breaks", player:get_pos(), 1.0, 20)
  621. end
  622. armor:set_player_armor(player)
  623. armor:update_inventory(player)
  624. end
  625. heal_max = heal_max + heal
  626. end
  627. end
  628. armor.def[pname].state = state
  629. armor.def[pname].count = items
  630. heal_max = heal_max * ARMOR_HEAL_MULTIPLIER
  631. heal_max = math.min(heal_max, 90)
  632. if math_random(100) < heal_max then
  633. hp_change = 0
  634. end
  635. armor:update_armor(player)
  636. -- Critical: the hp change must be an integer; floating-point comparisons are
  637. -- deadly.
  638. hp_change = math_floor(hp_change)
  639. -- Check for combat-related reasons.
  640. -- (Ignore this if damage was blocked by the heal chance.)
  641. if math.abs(hp_change) > 0 and armor.damage_type_disables_cloak(damage_type) then
  642. cloaking.disable_if_enabled(pname, true)
  643. end
  644. -- If this change would kill the player, set the death reason.
  645. -- Note: this RELIES on the bones code getting called after this returns, on
  646. -- the SAME server step, such that no other death reason could intervene for
  647. -- any other player that is currently logged in.
  648. if math.abs(hp_change) >= player:get_hp() then
  649. --minetest.chat_send_all('armor notify: ' .. dump(reason))
  650. armor.notify_death_reason(reason)
  651. end
  652. -- Simulate a 'stun' effect for heavy hits.
  653. if hp_change <= -(3*500) then
  654. pova.update_modifier(player, "physics", {sneak=false}, "damage.stun.1", {time=2})
  655. end
  656. if hp_change <= -(6*500) then
  657. pova.update_modifier(player, "physics", {speed=0.8}, "damage.stun.3", {time=3})
  658. elseif hp_change <= -(2*500) then
  659. pova.update_modifier(player, "physics", {speed=0.95}, "damage.stun.2", {time=3})
  660. end
  661. return hp_change
  662. end