enchanting.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python3
  2. from enum import Enum
  3. from typing import Dict, Tuple, List
  4. import argparse
  5. import math
  6. import random
  7. class EnchantmentType(Enum):
  8. FINITE_CHARGES = "FINITE_CHARGES"
  9. DAILY_CHARGES = "DAILY_CHARGES"
  10. COOLDOWN = "COOLDOWN"
  11. CONSTANT = "CONSTANT"
  12. class TimeFactor(Enum):
  13. RUSHING = 0.5
  14. RUSHING_EVEN_MORE = 0.25
  15. NORMAL = 0.0
  16. PAITENCE = 2.0
  17. EVEN_MORE_PAITENCE = 4.0
  18. enchantment_types_to_skilldays: Dict[EnchantmentType, Tuple[int, bool]] = {
  19. EnchantmentType.FINITE_CHARGES: (50, False),
  20. EnchantmentType.DAILY_CHARGES: (125, True),
  21. EnchantmentType.COOLDOWN: (500, True),
  22. EnchantmentType.CONSTANT: (5000, True)
  23. }
  24. def ease_of_enchantment(skill: int, quality: int, spell_level: int, unfamiliarity: int) -> int:
  25. return skill + quality - spell_level - unfamiliarity
  26. def num_charges(ease_of_enchantment: int) -> int:
  27. return max(1, math.floor(float(ease_of_enchantment) / 2))
  28. def enchantment_time(ease: int, skill_days: int) -> int:
  29. return math.floor(skill_days / math.pow(ease, 2))
  30. def _volatility_from_time(time_factor: TimeFactor) -> int:
  31. if time_factor is TimeFactor.NORMAL:
  32. return 0
  33. return -1 * int(math.log2(time_factor.value))
  34. def _volatility_from_spell_level(spell_level: int) -> int:
  35. return min(int(float(spell_level) / 3), 3) # 3-5 -> 1, 6-8 -> 2, 9 -> 3
  36. def _volatility_from_ease(ease: int) -> int:
  37. if 1 <= ease < 5:
  38. return 0
  39. elif ease >= 5 and ease <= 9:
  40. return -1
  41. elif ease >= 10 and ease <= 14:
  42. return -2
  43. elif ease >= 15:
  44. return -3
  45. else:
  46. raise ValueError
  47. def calculate_volatility(enchantment_type: EnchantmentType,
  48. spell_level: int,
  49. time_factor: TimeFactor,
  50. enchantment_ease: int,
  51. existing_enchantments: int) -> int:
  52. ENCHANTMENT_TYPE_TO_VOLATILITY: Dict[EnchantmentType, int] = {
  53. EnchantmentType.DAILY_CHARGES: 2,
  54. EnchantmentType.COOLDOWN: 3,
  55. EnchantmentType.CONSTANT: 4
  56. }
  57. vol_from_spell_level = _volatility_from_spell_level(spell_level)
  58. vol_from_time = _volatility_from_time(time_factor)
  59. vol_from_ease = _volatility_from_ease(enchantment_ease)
  60. if existing_enchantments > 0:
  61. vol_from_existing: int = pow(2, existing_enchantments)
  62. else:
  63. vol_from_existing = 0
  64. volatility = sum([ENCHANTMENT_TYPE_TO_VOLATILITY[enchantment_type],
  65. vol_from_spell_level,
  66. vol_from_time,
  67. vol_from_existing,
  68. vol_from_ease])
  69. return volatility
  70. POSSIBLE_COSTS_OF_USE = {
  71. 1: ["2d4_DAMAGE", "WASTE", "FIZZLE", "UNTAMED_MAGIC"],
  72. 2: ["3d6_DAMAGE", "FERAL_MAGIC", "BACKFIRE", "DESTRUCTION"],
  73. 3: ["DESTRUCTION"]
  74. }
  75. def cost_of_use(volatility_level) -> List[str]:
  76. costs: List[str] = []
  77. while volatility_level > 0:
  78. if volatility_level >= 3:
  79. costs += POSSIBLE_COSTS_OF_USE[3]
  80. volatility_level -= 3
  81. continue
  82. else:
  83. possible_costs = POSSIBLE_COSTS_OF_USE[volatility_level]
  84. costs.append(possible_costs[random.randint(0, len(possible_costs) - 1)])
  85. volatility_level -= volatility_level
  86. return costs
  87. class Enchantment():
  88. def __init__(self):
  89. self.etype: EnchantmentType = EnchantmentType.FINITE_CHARGES
  90. self.ease: int = 0
  91. self.time_to_enchant: int = 0
  92. self.cost: List[str] = list()
  93. self.charges: int = -1
  94. self.volatility: int = 0
  95. def print_enchantment(self):
  96. output = ""
  97. output += f"Will take {self.time_to_enchant} days."
  98. if self.charges > 0:
  99. output += f" Has {self.charges} charges."
  100. if len(self.cost) > 0:
  101. output += f" Costs of use are: {self.cost}"
  102. print(output)
  103. def main():
  104. parser = argparse.ArgumentParser()
  105. parser.add_argument("--caster-level", required=True, type=int)
  106. parser.add_argument("--spell-level", required=True, type=int)
  107. parser.add_argument("--vessel-level", required=True, type=int)
  108. parser.add_argument("--unfamiliarity", required=True, type=int)
  109. parser.add_argument("--enchant-type", required=True, type=str)
  110. parser.add_argument("--time-factor", required=True, type=str)
  111. parser.add_argument("--existing-enchantments", required=True, type=int, default=0)
  112. args = parser.parse_args()
  113. enchantment = Enchantment()
  114. enchantment.ease = ease_of_enchantment(
  115. args.caster_level,
  116. args.vessel_level,
  117. args.spell_level,
  118. args.unfamiliarity
  119. )
  120. if EnchantmentType[args.enchant_type] == EnchantmentType.DAILY_CHARGES:
  121. enchantment.charges = num_charges(enchantment.ease)
  122. enchantment.etype = EnchantmentType[args.enchant_type]
  123. enchantment.volatility = calculate_volatility(
  124. enchantment.etype,
  125. args.spell_level,
  126. TimeFactor[args.time_factor],
  127. enchantment.ease,
  128. args.existing_enchantments
  129. )
  130. skill_days_required = enchantment_types_to_skilldays[enchantment.etype][0]
  131. enchantment.time_to_enchant = math.ceil(TimeFactor[args.time_factor].value * enchantment_time(enchantment.ease, skill_days_required))
  132. enchantment.cost = cost_of_use(enchantment.volatility)
  133. enchantment.print_enchantment()
  134. if __name__ == "__main__":
  135. main()