/src/StatBlock.cpp

http://github.com/clintbellanger/flare · C++ · 450 lines · 318 code · 61 blank · 71 comment · 245 complexity · 28df6dedb5537b1cd3f49ce6d610ffaa MD5 · raw file

  1. /*
  2. Copyright 2011 Clint Bellanger
  3. This file is part of FLARE.
  4. FLARE is free software: you can redistribute it and/or modify it under the terms
  5. of the GNU General Public License as published by the Free Software Foundation,
  6. either version 3 of the License, or (at your option) any later version.
  7. FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
  8. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  9. PARTICULAR PURPOSE. See the GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License along with
  11. FLARE. If not, see http://www.gnu.org/licenses/
  12. */
  13. /**
  14. * class StatBlock
  15. *
  16. * Character stats and calculations
  17. */
  18. #include "StatBlock.h"
  19. #include "FileParser.h"
  20. #include "SharedResources.h"
  21. using namespace std;
  22. StatBlock::StatBlock() {
  23. name = "";
  24. alive = true;
  25. corpse = false;
  26. hero = false;
  27. hero_pos.x = hero_pos.y = -1;
  28. hero_alive = true;
  29. // core stats
  30. offense_character = defense_character = physical_character = mental_character = 0;
  31. offense_additional = defense_additional = physical_additional = mental_additional = 0;
  32. physoff = physdef = mentoff = mentdef = 0;
  33. physment = offdef = 0;
  34. character_class="";
  35. level = 0;
  36. hp = maxhp = hp_per_minute = hp_ticker = 0;
  37. mp = maxmp = mp_per_minute = mp_ticker = 0;
  38. accuracy = 75;
  39. avoidance = 25;
  40. crit = 0;
  41. // equipment stats
  42. dmg_melee_min = 1;
  43. dmg_melee_max = 4;
  44. dmg_ment_min = 0;
  45. dmg_ment_max = 0;
  46. dmg_ranged_min = 0;
  47. dmg_ranged_max = 0;
  48. absorb_min = 0;
  49. absorb_max = 0;
  50. wielding_physical = false;
  51. wielding_mental = false;
  52. wielding_offense = false;
  53. // buff and debuff stats
  54. slow_duration = 0;
  55. bleed_duration = 0;
  56. stun_duration = 0;
  57. immobilize_duration = 0;
  58. immunity_duration = 0;
  59. haste_duration = 0;
  60. hot_duration = 0;
  61. hot_value = 0;
  62. forced_move_duration = 0;
  63. shield_hp = 0;
  64. shield_frame = 0;
  65. vengeance_stacks = 0;
  66. vengeance_frame = 0;
  67. cooldown_ticks = 0;
  68. blocking = false;
  69. // xp table
  70. // (level * level * 100) plus previous total
  71. xp_table[0] = 0;
  72. for (int i=1; i<MAX_CHARACTER_LEVEL; i++) {
  73. xp_table[i] = i * i * 100 + xp_table[i-1];
  74. }
  75. teleportation=false;
  76. for (int i=0; i<POWERSLOT_COUNT; i++) {
  77. power_chance[i] = 0;
  78. power_index[i] = -1;
  79. power_cooldown[i] = 0;
  80. power_ticks[i] = 0;
  81. }
  82. melee_range = 64;
  83. melee_weapon_power = -1;
  84. ranged_weapon_power = -1;
  85. mental_weapon_power = -1;
  86. attunement_fire = 100;
  87. attunement_ice = 100;
  88. gold = 0;
  89. death_penalty = false;
  90. // campaign status interaction
  91. defeat_status = "";
  92. quest_loot_requires = "";
  93. quest_loot_not = "";
  94. quest_loot_id = 0;
  95. first_defeat_loot = 0;
  96. // default hero base/option
  97. base="male";
  98. head="head_short";
  99. portrait="male01";
  100. // default animations
  101. animations = "";
  102. // default animation speed
  103. animationSpeed = 100;
  104. }
  105. /**
  106. * load a statblock, typically for an enemy definition
  107. */
  108. void StatBlock::load(const string& filename) {
  109. FileParser infile;
  110. int num = 0;
  111. if (infile.open(mods->locate(filename))) {
  112. while (infile.next()) {
  113. if (isInt(infile.val)) num = atoi(infile.val.c_str());
  114. if (infile.key == "name") name = msg->get(infile.val);
  115. else if (infile.key == "sfx_prefix") sfx_prefix = infile.val;
  116. else if (infile.key == "gfx_prefix") gfx_prefix = infile.val;
  117. else if (infile.key == "level") level = num;
  118. // enemy death rewards and events
  119. else if (infile.key == "xp") xp = num;
  120. else if (infile.key == "loot_chance") loot_chance = num;
  121. else if (infile.key == "defeat_status") defeat_status = infile.val;
  122. else if (infile.key == "first_defeat_loot") first_defeat_loot = num;
  123. else if (infile.key == "quest_loot") {
  124. quest_loot_requires = infile.nextValue();
  125. quest_loot_not = infile.nextValue();
  126. quest_loot_id = atoi(infile.nextValue().c_str());
  127. }
  128. // combat stats
  129. else if (infile.key == "hp") {
  130. hp = num;
  131. maxhp = num;
  132. }
  133. else if (infile.key == "mp") {
  134. mp = num;
  135. maxmp = num;
  136. }
  137. else if (infile.key == "cooldown") cooldown = num;
  138. else if (infile.key == "accuracy") accuracy = num;
  139. else if (infile.key == "avoidance") avoidance = num;
  140. else if (infile.key == "dmg_melee_min") dmg_melee_min = num;
  141. else if (infile.key == "dmg_melee_max") dmg_melee_max = num;
  142. else if (infile.key == "dmg_ment_min") dmg_ment_min = num;
  143. else if (infile.key == "dmg_ment_max") dmg_ment_max = num;
  144. else if (infile.key == "dmg_ranged_min") dmg_ranged_min = num;
  145. else if (infile.key == "dmg_ranged_max") dmg_ranged_max = num;
  146. else if (infile.key == "absorb_min") absorb_min = num;
  147. else if (infile.key == "absorb_max") absorb_max = num;
  148. // behavior stats
  149. else if (infile.key == "speed") speed = num;
  150. else if (infile.key == "dspeed") dspeed = num;
  151. else if (infile.key == "dir_favor") dir_favor = num;
  152. else if (infile.key == "chance_pursue") chance_pursue = num;
  153. else if (infile.key == "chance_flee") chance_flee = num;
  154. else if (infile.key == "chance_melee_phys") power_chance[MELEE_PHYS] = num;
  155. else if (infile.key == "chance_melee_ment") power_chance[MELEE_MENT] = num;
  156. else if (infile.key == "chance_ranged_phys") power_chance[RANGED_PHYS] = num;
  157. else if (infile.key == "chance_ranged_ment") power_chance[RANGED_MENT] = num;
  158. else if (infile.key == "power_melee_phys") power_index[MELEE_PHYS] = num;
  159. else if (infile.key == "power_melee_ment") power_index[MELEE_MENT] = num;
  160. else if (infile.key == "power_ranged_phys") power_index[RANGED_PHYS] = num;
  161. else if (infile.key == "power_ranged_ment") power_index[RANGED_MENT] = num;
  162. else if (infile.key == "power_beacon") power_index[BEACON] = num;
  163. else if (infile.key == "cooldown_melee_phys") power_cooldown[MELEE_PHYS] = num;
  164. else if (infile.key == "cooldown_melee_ment") power_cooldown[MELEE_MENT] = num;
  165. else if (infile.key == "cooldown_ranged_phys") power_cooldown[RANGED_PHYS] = num;
  166. else if (infile.key == "cooldown_ranged_ment") power_cooldown[RANGED_MENT] = num;
  167. else if (infile.key == "melee_range") melee_range = num;
  168. else if (infile.key == "threat_range") threat_range = num;
  169. else if (infile.key == "attunement_fire") attunement_fire=num;
  170. else if (infile.key == "attunement_ice") attunement_ice=num;
  171. // animation stats
  172. else if (infile.key == "melee_weapon_power") melee_weapon_power = num;
  173. else if (infile.key == "mental_weapon_power") mental_weapon_power = num;
  174. else if (infile.key == "ranged_weapon_power") ranged_weapon_power = num;
  175. else if (infile.key == "animations") animations = infile.val;
  176. else if (infile.key == "animation_speed") animationSpeed = num;
  177. }
  178. infile.close();
  179. }
  180. }
  181. /**
  182. * Reduce temphp first, then hp
  183. */
  184. void StatBlock::takeDamage(int dmg) {
  185. if (shield_hp > 0) {
  186. shield_hp -= dmg;
  187. if (shield_hp < 0) {
  188. hp += shield_hp;
  189. shield_hp = 0;
  190. }
  191. }
  192. else {
  193. hp -= dmg;
  194. }
  195. if (hp <= 0) {
  196. hp = 0;
  197. alive = false;
  198. }
  199. }
  200. /**
  201. * Recalc derived stats from base stats
  202. * Creatures might skip these formulas.
  203. */
  204. void StatBlock::recalc() {
  205. level = 0;
  206. for (int i=0; i<MAX_CHARACTER_LEVEL; i++) {
  207. if (xp >= xp_table[i])
  208. level=i+1;
  209. }
  210. // TODO: move these formula numbers to an engine config file
  211. int hp_base = 10;
  212. int hp_per_level = 2;
  213. int hp_per_physical = 8;
  214. int hp_regen_base = 10;
  215. int hp_regen_per_level = 1;
  216. int hp_regen_per_physical = 4;
  217. int mp_base = 10;
  218. int mp_per_level = 2;
  219. int mp_per_mental = 8;
  220. int mp_regen_base = 10;
  221. int mp_regen_per_level = 1;
  222. int mp_regen_per_mental = 4;
  223. int accuracy_base = 75;
  224. int accuracy_per_level = 1;
  225. int accuracy_per_offense = 5;
  226. int avoidance_base = 25;
  227. int avoidance_per_level = 1;
  228. int avoidance_per_defense = 5;
  229. int crit_base = 5;
  230. int crit_per_level = 1;
  231. int lev0 = level -1;
  232. int phys0 = get_physical() -1;
  233. int ment0 = get_mental() -1;
  234. int off0 = get_offense() -1;
  235. int def0 = get_defense() -1;
  236. hp = maxhp = hp_base + (hp_per_level * lev0) + (hp_per_physical * phys0);
  237. mp = maxmp = mp_base + (mp_per_level * lev0) + (mp_per_mental * ment0);
  238. hp_per_minute = hp_regen_base + (hp_regen_per_level * lev0) + (hp_regen_per_physical * phys0);
  239. mp_per_minute = mp_regen_base + (mp_regen_per_level * lev0) + (mp_regen_per_mental * ment0);
  240. accuracy = accuracy_base + (accuracy_per_level * lev0) + (accuracy_per_offense * off0);
  241. avoidance = avoidance_base + (avoidance_per_level * lev0) + (avoidance_per_defense * def0);
  242. crit = crit_base + (crit_per_level * lev0);
  243. physoff = get_physical() + get_offense();
  244. physdef = get_physical() + get_defense();
  245. mentoff = get_mental() + get_offense();
  246. mentdef = get_mental() + get_defense();
  247. physment = get_physical() + get_mental();
  248. offdef = get_offense() + get_defense();
  249. int stat_sum = get_physical() + get_mental() + get_offense() + get_defense();
  250. // TODO: These class names do. not get caught by xgettext, so figure out
  251. // a way to translate them.
  252. // determine class
  253. // if all four stats are max, Grand Master
  254. if (stat_sum >= 20)
  255. character_class = msg->get("Grand Master");
  256. // if three stats are max, Master
  257. else if (stat_sum >= 16)
  258. character_class = msg->get("Master");
  259. // if one attribute is much higher than the others, use the attribute class name
  260. else if (get_physical() > get_mental()+1 && get_physical() > get_offense()+1 && get_physical() > get_defense()+1)
  261. character_class = msg->get("Warrior");
  262. else if (get_mental() > get_physical()+1 && get_mental() > get_offense()+1 && get_mental() > get_defense()+1)
  263. character_class = msg->get("Wizard");
  264. else if (get_offense() > get_physical()+1 && get_offense() > get_mental()+1 && get_offense() > get_defense()+1)
  265. character_class = msg->get("Ranger");
  266. else if (get_defense() > get_physical()+1 && get_defense() > get_mental()+1 && get_defense() > get_offense()+1)
  267. character_class = msg->get("Paladin");
  268. // if there is no dominant attribute, use the dicipline class name
  269. else if (physoff > physdef && physoff > mentoff && physoff > mentdef && physoff > physment && physoff > offdef)
  270. character_class = msg->get("Rogue");
  271. else if (physdef > physoff && physdef > mentoff && physdef > mentdef && physdef > physment && physdef > offdef)
  272. character_class = msg->get("Knight");
  273. else if (mentoff > physoff && mentoff > physdef && mentoff > mentdef && mentoff > physment && mentoff > offdef)
  274. character_class = msg->get("Shaman");
  275. else if (mentdef > physoff && mentdef > physdef && mentdef > mentoff && mentdef > physment && mentdef > offdef)
  276. character_class = msg->get("Cleric");
  277. else if (physment > physoff && physment > physdef && physment > mentoff && physment > mentdef && physment > offdef)
  278. character_class = msg->get("Battle Mage");
  279. else if (offdef > physoff && offdef > physdef && offdef > mentoff && offdef > mentdef && offdef > physment)
  280. character_class = msg->get("Heavy Archer");
  281. // otherwise, use the generic name
  282. else character_class = msg->get("Adventurer");
  283. }
  284. /**
  285. * Process per-frame actions
  286. */
  287. void StatBlock::logic() {
  288. // handle cooldowns
  289. if (cooldown_ticks > 0) cooldown_ticks--; // global cooldown
  290. for (int i=0; i<POWERSLOT_COUNT; i++) { // NPC/enemy powerslot cooldown
  291. if (power_ticks[i] > 0) power_ticks[i]--;
  292. }
  293. // HP regen
  294. if (hp_per_minute > 0 && hp < maxhp && hp > 0) {
  295. hp_ticker++;
  296. if (hp_ticker >= (60 * FRAMES_PER_SEC)/hp_per_minute) {
  297. hp++;
  298. hp_ticker = 0;
  299. }
  300. }
  301. // MP regen
  302. if (mp_per_minute > 0 && mp < maxmp && hp > 0) {
  303. mp_ticker++;
  304. if (mp_ticker >= (60 * FRAMES_PER_SEC)/mp_per_minute) {
  305. mp++;
  306. mp_ticker = 0;
  307. }
  308. }
  309. // handle buff/debuff durations
  310. if (slow_duration > 0)
  311. slow_duration--;
  312. if (bleed_duration > 0)
  313. bleed_duration--;
  314. if (stun_duration > 0)
  315. stun_duration--;
  316. if (immobilize_duration > 0)
  317. immobilize_duration--;
  318. if (immunity_duration > 0)
  319. immunity_duration--;
  320. if (haste_duration > 0)
  321. haste_duration--;
  322. if (hot_duration > 0)
  323. hot_duration--;
  324. if (forced_move_duration > 0)
  325. forced_move_duration--;
  326. // apply bleed
  327. if (bleed_duration % FRAMES_PER_SEC == 1) {
  328. takeDamage(1);
  329. }
  330. // apply healing over time
  331. if (hot_duration % FRAMES_PER_SEC == 1) {
  332. hp += hot_value;
  333. if (hp > maxhp) hp = maxhp;
  334. }
  335. // handle buff/debuff animations
  336. shield_frame++;
  337. if (shield_frame == 12) shield_frame = 0;
  338. vengeance_frame+= vengeance_stacks;
  339. if (vengeance_frame >= 24) vengeance_frame -= 24;
  340. }
  341. /**
  342. * Remove temporary buffs/debuffs
  343. */
  344. void StatBlock::clearEffects() {
  345. immunity_duration = 0;
  346. immobilize_duration = 0;
  347. bleed_duration = 0;
  348. stun_duration = 0;
  349. shield_hp = 0;
  350. slow_duration = 0;
  351. haste_duration = 0;
  352. forced_move_duration = 0;
  353. vengeance_stacks = 0;
  354. }
  355. /**
  356. * Get the renderable for various effects on the player (buffs/debuffs)
  357. *
  358. * @param effect_type STAT_EFFECT_* consts defined in StatBlock.h
  359. */
  360. Renderable StatBlock::getEffectRender(int effect_type) {
  361. Renderable r;
  362. r.map_pos.x = pos.x;
  363. r.map_pos.y = pos.y;
  364. if (effect_type == STAT_EFFECT_SHIELD) {
  365. r.src.x = (shield_frame/3) * 128;
  366. r.src.y = 0;
  367. r.src.w = 128;
  368. r.src.h = 128;
  369. r.offset.x = 64;
  370. r.offset.y = 96;
  371. r.object_layer = true;
  372. }
  373. else if (effect_type == STAT_EFFECT_VENGEANCE) {
  374. r.src.x = (vengeance_frame/6) * 64;
  375. r.src.y = 128;
  376. r.src.w = 64;
  377. r.src.h = 64;
  378. r.offset.x = 32;
  379. r.offset.y = 32;
  380. r.object_layer = false;
  381. }
  382. return r;
  383. }
  384. StatBlock::~StatBlock() {
  385. }