character_effects.lua 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. --- Elemental and status effects a character gets from the equipment and the equipped materia.
  2. --
  3. -- Used by the status menu, and usable by anything that needs to know what a character resists or
  4. -- inflicts.
  5. --- Element IDs, as stored in the item and materia data.
  6. Characters.ELEMENT = {
  7. FIRE = 0, ICE = 1, BOLT = 2, EARTH = 3, POISON = 4, GRAVITY = 5, WATER = 6, WIND = 7,
  8. HOLY = 8, RESTORATIVE = 9, CUT = 10, HIT = 11, PUNCH = 12, SHOOT = 13, SHOUT = 14, HIDDEN = 15,
  9. }
  10. --- How an element affects a character. Lower is stronger.
  11. Characters.ELEMENT_MODE = {
  12. ABSORB = 0,
  13. NULLIFY = 1,
  14. HALF = 2,
  15. }
  16. --- Status IDs, as stored in the item and materia data (bit position in the kernel).
  17. Characters.STATUS = {
  18. DEATH = 0, NEAR_DEATH = 1, SLEEP = 2, POISON = 3, SADNESS = 4, FURY = 5, CONFUSION = 6,
  19. SILENCE = 7, HASTE = 8, SLOW = 9, STOP = 10, FROG = 11, SMALL = 12, SLOW_NUMB = 13,
  20. PETRIFY = 14, REGEN = 15, BARRIER = 16, MBARRIER = 17, REFLECT = 18, DUAL = 19, SHIELD = 20,
  21. D_SENTENCE = 21, MANIPULATE = 22, BERSERK = 23, PEERLESS = 24, PARALYZED = 25, DARKNESS = 26,
  22. }
  23. --- Element used in the materia data for "no element".
  24. local NO_ELEMENT = Characters.ELEMENT.HIDDEN
  25. --- Materia level (1-based) for an amount of AP.
  26. local function materia_level(definition, ap)
  27. local level = 0
  28. for _, level_ap in ipairs(definition.levels_ap) do
  29. if level_ap > ap then break end
  30. level = level + 1
  31. end
  32. return math.max(level, 1)
  33. end
  34. --- Mode an Elemental materia gives to armor, by materia level.
  35. local function elemental_armor_mode(level)
  36. if level >= 3 then return Characters.ELEMENT_MODE.ABSORB end
  37. if level == 2 then return Characters.ELEMENT_MODE.NULLIFY end
  38. return Characters.ELEMENT_MODE.HALF
  39. end
  40. --- Adds an element resistance, keeping the strongest one.
  41. local function add_resistance(defense, element, mode)
  42. if mode == nil or mode > Characters.ELEMENT_MODE.HALF then return end
  43. if defense[element] == nil or mode < defense[element] then
  44. defense[element] = mode
  45. end
  46. end
  47. --- Gets the materia linked to a slot.
  48. --
  49. -- @param item The weapon or armor definition.
  50. -- @param slot Slot of the item, 1-8.
  51. -- @return List of slots (1-8) linked to it.
  52. local function linked_slots(item, slot)
  53. local linked = {}
  54. -- slots[n] == 1 means that slot n is linked to slot n + 1.
  55. if item.slots[slot] == 1 and #(item.slots) > slot then table.insert(linked, slot + 1) end
  56. if slot > 1 and item.slots[slot - 1] == 1 then table.insert(linked, slot - 1) end
  57. return linked
  58. end
  59. --- Applies the link materia (Elemental, Added Effect) of a weapon or armor.
  60. local function apply_link_materia(char_id, part, result)
  61. local equipment = Characters[char_id][part]
  62. local item = Game.Items[equipment.id]
  63. if item == nil or item.slots == nil then return end
  64. for slot = 1, #(item.slots) do
  65. local equipped = equipment.materia[slot - 1]
  66. local definition = equipped ~= nil and Game.Materia[equipped.id] or nil
  67. if definition ~= nil and definition.link ~= nil then
  68. local link = definition.link.id
  69. for _, partner_slot in ipairs(linked_slots(item, slot)) do
  70. local partner = equipment.materia[partner_slot - 1]
  71. local partner_def = partner ~= nil and Game.Materia[partner.id] or nil
  72. if partner_def ~= nil then
  73. if link == Materia.LINK.ELEMENTAL then
  74. for _, element in ipairs(partner_def.elements or {}) do
  75. if element ~= NO_ELEMENT then
  76. if part == "weapon" then
  77. result.elements.attack[element] = true
  78. else
  79. add_resistance(
  80. result.elements.defense, element,
  81. elemental_armor_mode(materia_level(definition, equipped.ap))
  82. )
  83. end
  84. end
  85. end
  86. elseif link == Materia.LINK.ADDED_EFFECT then
  87. local target = (part == "weapon") and result.status.attack or result.status.defense
  88. for _, status in ipairs(partner_def.status or {}) do
  89. target[status] = true
  90. end
  91. end
  92. end
  93. end
  94. end
  95. end
  96. end
  97. --- Calculates the elemental and status effects of a character.
  98. --
  99. -- Takes into account the weapon, the armor, the accessory and the linked materia:
  100. -- * Weapon elements, and Elemental materia linked in the weapon: elements the attacks have.
  101. -- * Armor and accessory elements: resistances. Elemental materia linked in the armor gives half
  102. -- damage (level 1), nullifies (level 2) or absorbs (level 3 or higher).
  103. -- * Added Effect materia: weapon statuses are inflicted, armor statuses are protected against.
  104. -- * Accessory statuses: protected against.
  105. --
  106. -- @param char_id ID of the character.
  107. -- @return A table:
  108. -- elements = {attack = {[element] = true}, defense = {[element] = mode}}
  109. -- status = {attack = {[status] = true}, defense = {[status] = true}}
  110. Characters.get_effects = function(char_id)
  111. local result = {
  112. elements = {attack = {}, defense = {}},
  113. status = {attack = {}, defense = {}},
  114. }
  115. local character = Characters[char_id]
  116. if character == nil then return result end
  117. -- Weapon
  118. local weapon = Game.Items[character.weapon.id]
  119. if weapon ~= nil then
  120. for _, element in ipairs(weapon.elements or {}) do
  121. result.elements.attack[element] = true
  122. end
  123. end
  124. -- Armor
  125. local armor = Game.Items[character.armor.id]
  126. if armor ~= nil then
  127. for _, element in ipairs(armor.elements or {}) do
  128. add_resistance(result.elements.defense, element.id, element.mode)
  129. end
  130. end
  131. -- Accessory
  132. local accessory = character.accessory ~= nil and Game.Items[character.accessory] or nil
  133. if accessory ~= nil then
  134. for _, element in ipairs(accessory.elements or {}) do
  135. if type(element) == "table" then
  136. add_resistance(result.elements.defense, element.id, element.mode)
  137. else
  138. -- Older data without a mode.
  139. add_resistance(result.elements.defense, element, Characters.ELEMENT_MODE.NULLIFY)
  140. end
  141. end
  142. for _, status in ipairs(accessory.status or {}) do
  143. result.status.defense[status] = true
  144. end
  145. end
  146. -- Materia
  147. apply_link_materia(char_id, "weapon", result)
  148. apply_link_materia(char_id, "armor", result)
  149. return result
  150. end