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