PageRenderTime 74ms CodeModel.GetById 2ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 0ms

/src/StatBlock.cpp

http://github.com/clintbellanger/flare
C++ | 450 lines | 318 code | 61 blank | 71 comment | 245 complexity | 28df6dedb5537b1cd3f49ce6d610ffaa MD5 | raw file
  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