Ejemplo n.º 1
0
void MenuDevHUD::alignElements() {
	stringstream ss;
	int line_width = 0;
	int line_height = font->getLineHeight();

	ss.str("");
	ss << msg->get("Player (x,y): ") << pc->stats.pos.x << ", " << pc->stats.pos.y;
	player_pos.set(window_area.x, window_area.y, JUSTIFY_LEFT, VALIGN_TOP, ss.str(), font->getColor("menu_normal"));
	clampFloor(line_width, player_pos.bounds.w);

	FPoint target = screen_to_map(inpt->mouse.x,  inpt->mouse.y, pc->stats.pos.x, pc->stats.pos.y);
	ss.str("");
	ss << msg->get("Target (x,y): ") << target.x << ", " << target.y;
	target_pos.set(window_area.x, window_area.y+line_height, JUSTIFY_LEFT, VALIGN_TOP, ss.str(), font->getColor("menu_normal"));
	clampFloor(line_width, target_pos.bounds.w);

	ss.str("");
	ss << msg->get("Mouse (x,y): ") << inpt->mouse.x << ", " << inpt->mouse.y;
	mouse_pos.set(window_area.x, window_area.y+line_height*2, JUSTIFY_LEFT, VALIGN_TOP, ss.str(), font->getColor("menu_normal"));
	clampFloor(line_width, mouse_pos.bounds.w);

	window_area = original_area;
	window_area.w = line_width;
	window_area.h = line_height*3;

	align();
}
Ejemplo n.º 2
0
LootManager::LootManager()
	: sfx_loot(0)
	, drop_max(1)
	, drop_radius(1)
	, hero(NULL)
	, tooltip_margin(0)
{
	tip = new WidgetTooltip();

	FileParser infile;
	// load loot animation settings from engine config file
	// @CLASS Loot|Description of engine/loot.txt
	if (infile.open("engine/loot.txt")) {
		while (infile.next()) {
			if (infile.key == "tooltip_margin") {
				// @ATTR tooltip_margin|integer|Vertical offset of the loot tooltip from the loot itself.
				tooltip_margin = toInt(infile.val);
			}
			else if (infile.key == "autopickup_currency") {
				// @ATTR autopickup_currency|boolean|Enable autopickup for currency
				AUTOPICKUP_CURRENCY = toBool(infile.val);
			}
			else if (infile.key == "currency_name") {
				// This key is parsed in loadMiscSettings() in Settings.cpp
			}
			else if (infile.key == "vendor_ratio") {
				// @ATTR vendor_ratio|integer|Prices ratio for vendors
				VENDOR_RATIO = static_cast<float>(toInt(infile.val)) / 100.0f;
			}
			else if (infile.key == "sfx_loot") {
				// @ATTR sfx_loot|string|Filename of a sound effect to play for dropping loot.
				sfx_loot =  snd->load(infile.val, "LootManager dropping loot");
			}
			else if (infile.key == "drop_max") {
				// @ATTR drop_max|integer|The maximum number of random item stacks that can drop at once
				drop_max = toInt(infile.val);
				clampFloor(drop_max, 1);
			}
			else if (infile.key == "drop_radius") {
				// @ATTR drop_radius|integer|The distance (in tiles) away from the origin that loot can drop
				drop_radius = toInt(infile.val);
				clampFloor(drop_radius, 1);
			}
			else {
				infile.error("LootManager: '%s' is not a valid key.", infile.key.c_str());
			}
		}
		infile.close();
	}

	// reset current map loot
	loot.clear();

	loadGraphics();

	full_msg = false;

	loadLootTables();
}
Ejemplo n.º 3
0
/**
 * NPCs are stored in simple config files
 *
 * @param npc_id Config file for npc
 */
void NPC::load(const std::string& npc_id) {

	FileParser infile;
	ItemStack stack;

	std::string filename_portrait = "";

	bool clear_random_table = true;

	// @CLASS NPC|Description of NPCs in npcs/
	if (infile.open(npc_id)) {
		while (infile.next()) {
			if (infile.section == "dialog") {
				if (infile.new_section) {
					dialog.push_back(std::vector<Event_Component>());
				}
				Event_Component e;
				e.type = EC_NONE;
				if (infile.key == "him" || infile.key == "her") {
					// @ATTR dialog.him, dialog.her|string|A line of dialog from the NPC.
					e.type = EC_NPC_DIALOG_THEM;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "you") {
					// @ATTR dialog.you|string|A line of dialog from the player.
					e.type = EC_NPC_DIALOG_YOU;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "voice") {
					// @ATTR dialog.voice|string|Filename of a voice sound file to play.
					e.type = EC_NPC_VOICE;
					e.x = loadSound(infile.val, NPC_VOX_QUEST);
				}
				else if (infile.key == "topic") {
					// @ATTR dialog.topic|string|The name of this dialog topic. Displayed when picking a dialog tree.
					e.type = EC_NPC_DIALOG_TOPIC;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "group") {
					// @ATTR dialog.group|string|Dialog group.
					e.type = EC_NPC_DIALOG_GROUP;
					e.s = infile.val;
				}
				else if (infile.key == "allow_movement") {
					// @ATTR dialog.allow_movement|boolean|Restrict the player's mvoement during dialog.
					e.type = EC_NPC_ALLOW_MOVEMENT;
					e.s = infile.val;
				}
				else {
					Event ev;
					EventManager::loadEventComponent(infile, &ev, NULL);

					for (size_t i=0; i<ev.components.size(); ++i) {
						if (ev.components[i].type != EC_NONE) {
							dialog.back().push_back(ev.components[i]);
						}
					}
				}

				if (e.type != EC_NONE) {
					dialog.back().push_back(e);
				}
			}
			else {
				filename = npc_id;

				if (infile.new_section) {
					// APPENDed file
					clear_random_table = true;
				}

				if (infile.key == "name") {
					// @ATTR name|string|NPC's name.
					name = msg->get(infile.val);
				}
				else if (infile.key == "gfx") {
					// @ATTR gfx|string|Filename of an animation definition.
					gfx = infile.val;
				}

				// handle talkers
				else if (infile.key == "talker") {
					// @ATTR talker|boolean|Allows this NPC to be talked to.
					talker = toBool(infile.val);
				}
				else if (infile.key == "portrait") {
					// @ATTR portrait|string|Filename of a portrait image.
					filename_portrait = infile.val;
				}

				// handle vendors
				else if (infile.key == "vendor") {
					// @ATTR vendor|string|Allows this NPC to buy/sell items.
					vendor = toBool(infile.val);
				}
				else if (infile.key == "constant_stock") {
					// @ATTR constant_stock|item (integer), ...|A list of items this vendor has for sale.
					stack.quantity = 1;
					while (infile.val != "") {
						stack.item = toInt(infile.nextValue());
						stock.add(stack);
					}
				}
				else if (infile.key == "status_stock") {
					// @ATTR status_stock|status (string), item (integer), ...|A list of items this vendor will have for sale if the required status is met.
					if (camp->checkStatus(infile.nextValue())) {
						stack.quantity = 1;
						while (infile.val != "") {
							stack.item = toInt(infile.nextValue());
							stock.add(stack);
						}
					}
				}
				else if (infile.key == "random_stock") {
					// @ATTR random_stock|[string,drop_chance([fixed:chance(integer)]),quantity_min(integer),quantity_max(integer)],...|Use a loot table to add random items to the stock; either a filename or an inline definition.
					if (clear_random_table) {
						random_table.clear();
						clear_random_table = false;
					}

					random_table.push_back(Event_Component());
					loot->parseLoot(infile, &random_table.back(), &random_table);
				}
				else if (infile.key == "random_stock_count") {
					// @ATTR random_stock_count|min (integer), max (integer)|Sets the minimum (and optionally, the maximum) amount of random items this npc can have.
					random_table_count.x = toInt(infile.nextValue());
					random_table_count.y = toInt(infile.nextValue());
					if (random_table_count.x != 0 || random_table_count.y != 0) {
						clampFloor(random_table_count.x, 1);
						clampFloor(random_table_count.y, random_table_count.x);
					}
				}

				// handle vocals
				else if (infile.key == "vox_intro") {
					// @ATTR vox_intro|string|Filename of a sound file to play when initially interacting with the NPC.
					loadSound(infile.val, NPC_VOX_INTRO);
				}

				else {
					infile.error("NPC: '%s' is not a valid key.", infile.key.c_str());
				}
			}
		}
		infile.close();
	}
	loadGraphics(filename_portrait);

	// fill inventory with items from random stock table
	unsigned rand_count = randBetween(random_table_count.x, random_table_count.y);

	std::vector<ItemStack> rand_itemstacks;
	for (unsigned i=0; i<rand_count; ++i) {
		loot->checkLoot(random_table, NULL, &rand_itemstacks);
	}
	std::sort(rand_itemstacks.begin(), rand_itemstacks.end(), compareItemStack);
	for (size_t i=0; i<rand_itemstacks.size(); ++i) {
		stock.add(rand_itemstacks[i]);
	}
}
Ejemplo n.º 4
0
void Avatar::transform() {
	// calling a transform power locks the actionbar, so we unlock it here
	inpt->unlockActionBar();

	delete charmed_stats;
	charmed_stats = NULL;

	Enemy_Level el = enemyg->getRandomEnemy(stats.transform_type, 0, 0);

	if (el.type != "") {
		charmed_stats = new StatBlock();
		charmed_stats->load(el.type);
	}
	else {
		logError("Avatar: Could not transform into creature type '%s'", stats.transform_type.c_str());
		stats.transform_type = "";
		return;
	}

	transform_triggered = true;
	stats.transformed = true;
	setPowers = true;

	// temporary save hero stats
	delete hero_stats;

	hero_stats = new StatBlock();
	*hero_stats = stats;

	// do not allow two copies of the summons list
	hero_stats->summons.clear();

	// replace some hero stats
	stats.speed = charmed_stats->speed;
	stats.flying = charmed_stats->flying;
	stats.intangible = charmed_stats->intangible;
	stats.humanoid = charmed_stats->humanoid;
	stats.animations = charmed_stats->animations;
	stats.powers_list = charmed_stats->powers_list;
	stats.powers_passive = charmed_stats->powers_passive;
	stats.effects.clearEffects();

	anim->decreaseCount("animations/hero.txt");
	anim->increaseCount(charmed_stats->animations);
	animationSet = anim->getAnimationSet(charmed_stats->animations);
	delete activeAnimation;
	activeAnimation = animationSet->getAnimation();
	stats.cur_state = AVATAR_STANCE;

	// base stats
	for (unsigned int i=0; i<STAT_COUNT; ++i) {
		clampFloor(stats.starting[i], charmed_stats->starting[i]);
	}

	// resistances
	for (unsigned int i=0; i<stats.vulnerable.size(); i++) {
		clampCeil(stats.vulnerable[i], charmed_stats->vulnerable[i]);
	}

	loadSounds(charmed_stats);
	loadStepFX("NULL");

	stats.applyEffects();

	transform_pos = stats.pos;
	transform_map = mapr->getFilename();
}
Ejemplo n.º 5
0
/**
 * Whenever a hazard collides with an entity, this function resolves the effect
 * Called by HazardManager
 *
 * Returns false on miss
 */
bool Entity::takeHit(Hazard &h) {

	//check if this enemy should be affected by this hazard based on the category
	if(!powers->powers[h.power_index].target_categories.empty() && !stats.hero) {
		//the power has a target category requirement, so if it doesnt match, dont continue
		bool match_found = false;
		for (unsigned int i=0; i<stats.categories.size(); i++) {
			if(std::find(powers->powers[h.power_index].target_categories.begin(), powers->powers[h.power_index].target_categories.end(), stats.categories[i]) != powers->powers[h.power_index].target_categories.end()) {
				match_found = true;
			}
		}
		if(!match_found)
			return false;
	}

	//if the target is already dead, they cannot be hit
	if ((stats.cur_state == ENEMY_DEAD || stats.cur_state == ENEMY_CRITDEAD) && !stats.hero)
		return false;

	if(stats.cur_state == AVATAR_DEAD && stats.hero)
		return false;

	//if the target is an enemy and they are not already in combat, activate a beacon to draw other enemies into battle
	if (!stats.in_combat && !stats.hero && !stats.hero_ally) {
		stats.join_combat = true;
		stats.in_combat = true;
		powers->activate(stats.power_index[BEACON], &stats, stats.pos); //emit beacon
	}

	// exit if it was a beacon (to prevent stats.targeted from being set)
	if (powers->powers[h.power_index].beacon) return false;

	// prepare the combat text
	CombatText *combat_text = comb;

	// if it's a miss, do nothing
	int accuracy = h.accuracy;
	if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_MULTIPLY)
		accuracy = accuracy * powers->powers[h.power_index].mod_accuracy_value / 100;
	else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ADD)
		accuracy += powers->powers[h.power_index].mod_accuracy_value;
	else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		accuracy = powers->powers[h.power_index].mod_accuracy_value;

	int avoidance = 0;
	if(!powers->powers[h.power_index].trait_avoidance_ignore) {
		avoidance = stats.get(STAT_AVOIDANCE);
	}

	int true_avoidance = 100 - (accuracy + 25 - avoidance);
	//if we are using an absolute accuracy, offset the constant 25 added to the accuracy
	if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		true_avoidance += 25;
	clampFloor(true_avoidance, MIN_AVOIDANCE);
	clampCeil(true_avoidance, MAX_AVOIDANCE);

	if (h.missile && percentChance(stats.get(STAT_REFLECT))) {
		// reflect the missile 180 degrees
		h.setAngle(h.angle+M_PI);

		// change hazard source to match the reflector's type
		// maybe we should change the source stats pointer to the reflector's StatBlock
		if (h.source_type == SOURCE_TYPE_HERO || h.source_type == SOURCE_TYPE_ALLY)
			h.source_type = SOURCE_TYPE_ENEMY;
		else if (h.source_type == SOURCE_TYPE_ENEMY)
			h.source_type = stats.hero ? SOURCE_TYPE_HERO : SOURCE_TYPE_ALLY;

		// reset the hazard ticks
		h.lifespan = h.base_lifespan;

		if (activeAnimation->getName() == "block") {
			play_sfx_block = true;
		}

		return false;
	}

	if (percentChance(true_avoidance)) {
		combat_text->addMessage(msg->get("miss"), stats.pos, COMBAT_MESSAGE_MISS);
		return false;
	}

	// calculate base damage
	int dmg = randBetween(h.dmg_min, h.dmg_max);

	if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY)
		dmg = dmg * powers->powers[h.power_index].mod_damage_value_min / 100;
	else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ADD)
		dmg += powers->powers[h.power_index].mod_damage_value_min;
	else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		dmg = randBetween(powers->powers[h.power_index].mod_damage_value_min, powers->powers[h.power_index].mod_damage_value_max);

	// apply elemental resistance
	if (h.trait_elemental >= 0 && unsigned(h.trait_elemental) < stats.vulnerable.size()) {
		unsigned i = h.trait_elemental;
		int vulnerable = stats.vulnerable[i];
		clampFloor(vulnerable,MIN_RESIST);
		if (stats.vulnerable[i] < 100)
			clampCeil(vulnerable,MAX_RESIST);
		dmg = (dmg * vulnerable) / 100;
	}

	if (!h.trait_armor_penetration) { // armor penetration ignores all absorption
		// substract absorption from armor
		int absorption = randBetween(stats.get(STAT_ABS_MIN), stats.get(STAT_ABS_MAX));

		if (absorption > 0 && dmg > 0) {
			int abs = absorption;
			if ((abs*100)/dmg < MIN_BLOCK)
				absorption = (dmg * MIN_BLOCK) /100;
			if ((abs*100)/dmg > MAX_BLOCK)
				absorption = (dmg * MAX_BLOCK) /100;
			if ((abs*100)/dmg < MIN_ABSORB && !stats.effects.triggered_block)
				absorption = (dmg * MIN_ABSORB) /100;
			if ((abs*100)/dmg > MAX_ABSORB && !stats.effects.triggered_block)
				absorption = (dmg * MAX_ABSORB) /100;

			// Sometimes, the absorb limits cause absorbtion to drop to 1
			// This could be confusing to a player that has something with an absorb of 1 equipped
			// So we round absorption up in this case
			if (absorption == 0) absorption = 1;
		}

		dmg = dmg - absorption;
		if (dmg <= 0) {
			dmg = 0;
			if (h.trait_elemental < 0) {
				if (stats.effects.triggered_block && MAX_BLOCK < 100) dmg = 1;
				else if (!stats.effects.triggered_block && MAX_ABSORB < 100) dmg = 1;
			}
			else {
				if (MAX_RESIST < 100) dmg = 1;
			}
			if (activeAnimation->getName() == "block") {
				play_sfx_block = true;
				resetActiveAnimation();
			}
		}
	}

	// check for crits
	int true_crit_chance = h.crit_chance;

	if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_MULTIPLY)
		true_crit_chance = true_crit_chance * powers->powers[h.power_index].mod_crit_value / 100;
	else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ADD)
		true_crit_chance += powers->powers[h.power_index].mod_crit_value;
	else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ABSOLUTE)
		true_crit_chance = powers->powers[h.power_index].mod_crit_value;

	if (stats.effects.stun || stats.effects.speed < 100)
		true_crit_chance += h.trait_crits_impaired;

	bool crit = percentChance(true_crit_chance);
	if (crit) {
		dmg = dmg + h.dmg_max;
		if(!stats.hero)
			mapr->shaky_cam_ticks = MAX_FRAMES_PER_SEC/2;
	}

	if(stats.hero)
		combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_TAKEDMG);
	else {
		if(crit)
			combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_CRIT);
		else
			combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_GIVEDMG);
	}

	// temporarily save the current HP for calculating HP/MP steal on final blow
	int prev_hp = stats.hp;

	// apply damage
	stats.takeDamage(dmg);

	// after effects
	if (dmg > 0) {

		// damage always breaks stun
		stats.effects.removeEffectType(EFFECT_STUN);

		if (stats.hp > 0) {
			powers->effect(&stats, h.src_stats, h.power_index,h.source_type);
		}

		if (!stats.effects.immunity) {
			if (h.hp_steal != 0) {
				int steal_amt = (std::min(dmg, prev_hp) * h.hp_steal) / 100;
				if (steal_amt == 0) steal_amt = 1;
				combat_text->addMessage(msg->get("+%d HP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF);
				h.src_stats->hp = std::min(h.src_stats->hp + steal_amt, h.src_stats->get(STAT_HP_MAX));
			}
			if (h.mp_steal != 0) {
				int steal_amt = (std::min(dmg, prev_hp) * h.mp_steal) / 100;
				if (steal_amt == 0) steal_amt = 1;
				combat_text->addMessage(msg->get("+%d MP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF);
				h.src_stats->mp = std::min(h.src_stats->mp + steal_amt, h.src_stats->get(STAT_MP_MAX));
			}
		}
	}

	// post effect power
	if (h.post_power > 0 && dmg > 0) {
		powers->activate(h.post_power, h.src_stats, stats.pos);
	}

	// loot
	if (dmg > 0 && !h.loot.empty()) {
		for (unsigned i=0; i<h.loot.size(); i++) {
			powers->loot.push_back(h.loot[i]);
			powers->loot.back().x = (int)stats.pos.x;
			powers->loot.back().y = (int)stats.pos.y;
		}
	}

	// interrupted to new state
	if (dmg > 0) {
		bool chance_poise = percentChance(stats.get(STAT_POISE));

		if(stats.hp <= 0) {
			stats.effects.triggered_death = true;
			if(stats.hero)
				stats.cur_state = AVATAR_DEAD;
			else {
				doRewards(h.source_type);
				if (crit)
					stats.cur_state = ENEMY_CRITDEAD;
				else
					stats.cur_state = ENEMY_DEAD;
				mapr->collider.unblock(stats.pos.x,stats.pos.y);
			}
		}
		// don't go through a hit animation if stunned
		else if (!stats.effects.stun && !chance_poise) {
			play_sfx_hit = true;

			if(!chance_poise && stats.cooldown_hit_ticks == 0) {
				if(stats.hero)
					stats.cur_state = AVATAR_HIT;
				else
					stats.cur_state = ENEMY_HIT;
				stats.cooldown_hit_ticks = stats.cooldown_hit;

				if (stats.untransform_on_hit)
					stats.transform_duration = 0;
			}
			// roll to see if the enemy's ON_HIT power is casted
			if (percentChance(stats.power_chance[ON_HIT])) {
				powers->activate(stats.power_index[ON_HIT], &stats, stats.pos);
			}
		}
		// just play the hit sound
		else
			play_sfx_hit = true;
	}

	return true;
}
Ejemplo n.º 6
0
/**
 * Base damage and absorb is 0
 * Plus an optional bonus_per_[base stat]
 */
void StatBlock::calcBase() {
	// bonuses are skipped for the default level 1 of a stat
	int lev0 = level -1;
	int phys0 = get_physical() -1;
	int ment0 = get_mental() -1;
	int off0 = get_offense() -1;
	int def0 = get_defense() -1;

	clampFloor(lev0,0);
	clampFloor(phys0,0);
	clampFloor(ment0,0);
	clampFloor(off0,0);
	clampFloor(def0,0);

	for (int i=0; i<STAT_COUNT; i++) {
		base[i] = starting[i];
		base[i] += lev0 * per_level[i];
		base[i] += phys0 * per_physical[i];
		base[i] += ment0 * per_mental[i];
		base[i] += off0 * per_offense[i];
		base[i] += def0 * per_defense[i];
	}

	// add damage/absorb from equipment
	base[STAT_DMG_MELEE_MIN] += dmg_melee_min_add;
	base[STAT_DMG_MELEE_MAX] += dmg_melee_max_add;
	base[STAT_DMG_MENT_MIN] += dmg_ment_min_add;
	base[STAT_DMG_MENT_MAX] += dmg_ment_max_add;
	base[STAT_DMG_RANGED_MIN] += dmg_ranged_min_add;
	base[STAT_DMG_RANGED_MAX] += dmg_ranged_max_add;
	base[STAT_ABS_MIN] += absorb_min_add;
	base[STAT_ABS_MAX] += absorb_max_add;

	// increase damage and absorb to minimum amounts
	clampFloor(base[STAT_DMG_MELEE_MIN], 0);
	clampFloor(base[STAT_DMG_MELEE_MAX], base[STAT_DMG_MELEE_MIN]);
	clampFloor(base[STAT_DMG_RANGED_MIN], 0);
	clampFloor(base[STAT_DMG_RANGED_MAX], base[STAT_DMG_RANGED_MIN]);
	clampFloor(base[STAT_DMG_MENT_MIN], 0);
	clampFloor(base[STAT_DMG_MENT_MAX], base[STAT_DMG_MENT_MIN]);
	clampFloor(base[STAT_ABS_MIN], 0);
	clampFloor(base[STAT_ABS_MAX], base[STAT_ABS_MIN]);
}
Ejemplo n.º 7
0
void LootManager::loadLootTables() {
	std::vector<std::string> filenames = mods->list("loot", false);

	for (unsigned i=0; i<filenames.size(); i++) {
		FileParser infile;
		if (!infile.open(filenames[i]))
			continue;

		std::vector<Event_Component> *ec_list = &loot_tables[filenames[i]];
		Event_Component *ec = NULL;
		bool skip_to_next = false;

		while (infile.next()) {
			if (infile.section == "") {
				if (infile.key == "loot") {
					ec_list->push_back(Event_Component());
					ec = &ec_list->back();
					parseLoot(infile, ec, ec_list);
				}
			}
			else if (infile.section == "loot") {
				if (infile.new_section) {
					ec_list->push_back(Event_Component());
					ec = &ec_list->back();
					ec->type = "loot";
					skip_to_next = false;
				}

				if (skip_to_next || ec == NULL)
					continue;

				if (infile.key == "id") {
					ec->s = infile.val;

					if (ec->s == "currency")
						ec->c = CURRENCY_ID;
					else if (toInt(ec->s, -1) != -1)
						ec->c = toInt(ec->s);
					else {
						skip_to_next = true;
						infile.error("LootManager: Invalid item id for loot.");
					}
				}
				else if (infile.key == "chance") {
					if (infile.val == "fixed")
						ec->z = 0;
					else
						ec->z = toInt(infile.val);
				}
				else if (infile.key == "quantity") {
					ec->a = toInt(infile.nextValue());
					clampFloor(ec->a, 1);
					ec->b = toInt(infile.nextValue());
					clampFloor(ec->b, ec->a);
				}
			}
		}

		infile.close();
	}
}
Ejemplo n.º 8
0
void LootManager::parseLoot(FileParser &infile, Event_Component *e, std::vector<Event_Component> *ec_list) {
	if (e == NULL) return;

	std::string chance;
	bool first_is_filename = false;
	e->s = infile.nextValue();

	if (e->s == "currency")
		e->c = CURRENCY_ID;
	else if (toInt(e->s, -1) != -1)
		e->c = toInt(e->s);
	else if (ec_list) {
		// load entire loot table
		std::string filename = e->s;

		// remove the last event component, since getLootTable() will create a new one
		if (e == &ec_list->back())
			ec_list->pop_back();

		getLootTable(filename, ec_list);
		first_is_filename = true;
	}

	if (!first_is_filename) {
		// make sure the type is "loot"
		e->type = "loot";

		// drop chance
		chance = infile.nextValue();
		if (chance == "fixed") e->z = 0;
		else e->z = toInt(chance);

		// quantity min/max
		e->a = toInt(infile.nextValue());
		clampFloor(e->a, 1);
		e->b = toInt(infile.nextValue());
		clampFloor(e->b, e->a);
	}

	// add repeating loot
	if (ec_list) {
		std::string repeat_val = infile.nextValue();
		while (repeat_val != "") {
			ec_list->push_back(Event_Component());
			Event_Component *ec = &ec_list->back();
			ec->type = infile.key;

			ec->s = repeat_val;
			if (ec->s == "currency")
				ec->c = CURRENCY_ID;
			else if (toInt(ec->s, -1) != -1)
				ec->c = toInt(ec->s);
			else {
				// remove the last event component, since getLootTable() will create a new one
				ec_list->pop_back();

				getLootTable(repeat_val, ec_list);

				repeat_val = infile.nextValue();
				continue;
			}

			chance = infile.nextValue();
			if (chance == "fixed") ec->z = 0;
			else ec->z = toInt(chance);

			ec->a = toInt(infile.nextValue());
			clampFloor(ec->a, 1);
			ec->b = toInt(infile.nextValue());
			clampFloor(ec->b, ec->a);

			repeat_val = infile.nextValue();
		}
	}
}
Ejemplo n.º 9
0
void EventManager::loadEventComponent(FileParser &infile, Event* evnt, Event_Component* ec) {
	Event_Component *e = NULL;
	if (evnt) {
		evnt->components.push_back(Event_Component());
		e = &evnt->components.back();
	}
	else if (ec) {
		e = ec;
	}

	if (!e) return;

	e->type = EC_NONE;

	if (infile.key == "tooltip") {
		// @ATTR event.tooltip|string|Tooltip for event
		e->type = EC_TOOLTIP;

		e->s = msg->get(infile.val);
	}
	else if (infile.key == "power_path") {
		// @ATTR event.power_path|[hero:[x,y]]|Event power path
		e->type = EC_POWER_PATH;

		// x,y are src, if s=="hero" we target the hero,
		// else we'll use values in a,b as coordinates
		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());

		std::string dest = infile.nextValue();
		if (dest == "hero") {
			e->s = "hero";
		}
		else {
			e->a = toInt(dest);
			e->b = toInt(infile.nextValue());
		}
	}
	else if (infile.key == "power_damage") {
		// @ATTR event.power_damage|min(integer), max(integer)|Range of power damage
		e->type = EC_POWER_DAMAGE;

		e->a = toInt(infile.nextValue());
		e->b = toInt(infile.nextValue());
	}
	else if (infile.key == "intermap") {
		// @ATTR event.intermap|[map(string),x(integer),y(integer)]|Jump to specific map at location specified.
		e->type = EC_INTERMAP;

		e->s = infile.nextValue();
		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());
	}
	else if (infile.key == "intramap") {
		// @ATTR event.intramap|[x(integer),y(integer)]|Jump to specific position within current map.
		e->type = EC_INTRAMAP;

		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());
	}
	else if (infile.key == "mapmod") {
		// @ATTR event.mapmod|[string,int,int,int],..|Modify map tiles
		e->type = EC_MAPMOD;

		e->s = infile.nextValue();
		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());
		e->z = toInt(infile.nextValue());

		// add repeating mapmods
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_MAPMOD;
				e->s = repeat_val;
				e->x = toInt(infile.nextValue());
				e->y = toInt(infile.nextValue());
				e->z = toInt(infile.nextValue());

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "soundfx") {
		// @ATTR event.soundfx|[soundfile(string),x(integer),y(integer),loop(boolean)]|Filename of a sound to play. Optionally, it can be played at a specific location and/or looped.
		e->type = EC_SOUNDFX;

		e->s = infile.nextValue();
		e->x = e->y = -1;
		e->z = static_cast<int>(false);

		std::string s = infile.nextValue();
		if (s != "") e->x = toInt(s);

		s = infile.nextValue();
		if (s != "") e->y = toInt(s);

		s = infile.nextValue();
		if (s != "") e->z = static_cast<int>(toBool(s));
	}
	else if (infile.key == "loot") {
		// @ATTR event.loot|[string,drop_chance([fixed:chance(integer)]),quantity_min(integer),quantity_max(integer)],...|Add loot to the event; either a filename or an inline definition.
		e->type = EC_LOOT;

		loot->parseLoot(infile, e, &evnt->components);
	}
	else if (infile.key == "loot_count") {
		// @ATTR event.loot_count|min (integer), max (integer)|Sets the minimum (and optionally, the maximum) amount of loot this event can drop. Overrides the global drop_max setting.
		e->type = EC_LOOT_COUNT;

		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());
		if (e->x != 0 || e->y != 0) {
			clampFloor(e->x, 1);
			clampFloor(e->y, e->x);
		}
	}
	else if (infile.key == "msg") {
		// @ATTR event.msg|string|Adds a message to be displayed for the event.
		e->type = EC_MSG;

		e->s = msg->get(infile.val);
	}
	else if (infile.key == "shakycam") {
		// @ATTR event.shakycam|duration|Makes the camera shake for this duration in 'ms' or 's'.
		e->type = EC_SHAKYCAM;

		e->x = parse_duration(infile.val);
	}
	else if (infile.key == "requires_status") {
		// @ATTR event.requires_status|string,...|Event requires list of statuses
		e->type = EC_REQUIRES_STATUS;

		e->s = infile.nextValue();

		// add repeating requires_status
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_REQUIRES_STATUS;
				e->s = repeat_val;

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "requires_not_status") {
		// @ATTR event.requires_not_status|string,...|Event requires not list of statuses
		e->type = EC_REQUIRES_NOT_STATUS;

		e->s = infile.nextValue();

		// add repeating requires_not
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_REQUIRES_NOT_STATUS;
				e->s = repeat_val;

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "requires_level") {
		// @ATTR event.requires_level|integer|Event requires hero level
		e->type = EC_REQUIRES_LEVEL;

		e->x = toInt(infile.nextValue());
	}
	else if (infile.key == "requires_not_level") {
		// @ATTR event.requires_not_level|integer|Event requires not hero level
		e->type = EC_REQUIRES_NOT_LEVEL;

		e->x = toInt(infile.nextValue());
	}
	else if (infile.key == "requires_currency") {
		// @ATTR event.requires_currency|integer|Event requires atleast this much currency
		e->type = EC_REQUIRES_CURRENCY;

		e->x = toInt(infile.nextValue());
	}
	else if (infile.key == "requires_not_currency") {
		// @ATTR event.requires_not_currency|integer|Event requires no more than this much currency
		e->type = EC_REQUIRES_NOT_CURRENCY;

		e->x = toInt(infile.nextValue());
	}
	else if (infile.key == "requires_item") {
		// @ATTR event.requires_item|integer,...|Event requires specific item
		e->type = EC_REQUIRES_ITEM;

		e->x = toInt(infile.nextValue());

		// add repeating requires_item
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_REQUIRES_ITEM;
				e->x = toInt(repeat_val);

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "requires_not_item") {
		// @ATTR event.requires_not_item|integer,...|Event requires not having a specific item
		e->type = EC_REQUIRES_NOT_ITEM;

		e->x = toInt(infile.nextValue());

		// add repeating requires_not_item
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_REQUIRES_NOT_ITEM;
				e->x = toInt(repeat_val);

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "requires_class") {
		// @ATTR event.requires_class|string|Event requires this base class
		e->type = EC_REQUIRES_CLASS;

		e->s = infile.nextValue();
	}
	else if (infile.key == "requires_not_class") {
		// @ATTR event.requires_not_class|string|Event requires not this base class
		e->type = EC_REQUIRES_NOT_CLASS;

		e->s = infile.nextValue();
	}
	else if (infile.key == "set_status") {
		// @ATTR event.set_status|string,...|Sets specified statuses
		e->type = EC_SET_STATUS;

		e->s = infile.nextValue();

		// add repeating set_status
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_SET_STATUS;
				e->s = repeat_val;

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "unset_status") {
		// @ATTR event.unset_status|string,...|Unsets specified statuses
		e->type = EC_UNSET_STATUS;

		e->s = infile.nextValue();

		// add repeating unset_status
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_UNSET_STATUS;
				e->s = repeat_val;

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "remove_currency") {
		// @ATTR event.remove_currency|integer|Removes specified amount of currency from hero inventory
		e->type = EC_REMOVE_CURRENCY;

		e->x = toInt(infile.val);
		clampFloor(e->x, 0);
	}
	else if (infile.key == "remove_item") {
		// @ATTR event.remove_item|integer,...|Removes specified item from hero inventory
		e->type = EC_REMOVE_ITEM;

		e->x = toInt(infile.nextValue());

		// add repeating remove_item
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_REMOVE_ITEM;
				e->x = toInt(repeat_val);

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "reward_xp") {
		// @ATTR event.reward_xp|integer|Reward hero with specified amount of experience points.
		e->type = EC_REWARD_XP;

		e->x = toInt(infile.val);
		clampFloor(e->x, 0);
	}
	else if (infile.key == "reward_currency") {
		// @ATTR event.reward_currency|integer|Reward hero with specified amount of currency.
		e->type = EC_REWARD_CURRENCY;

		e->x = toInt(infile.val);
		clampFloor(e->x, 0);
	}
	else if (infile.key == "reward_item") {
		// @ATTR event.reward_item|x(integer),y(integer)|Reward hero with y number of item x.
		e->type = EC_REWARD_ITEM;

		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.val);
		clampFloor(e->y, 0);
	}
	else if (infile.key == "restore") {
		// @ATTR event.restore|string|Restore the hero's HP, MP, and/or status.
		e->type = EC_RESTORE;

		e->s = infile.val;
	}
	else if (infile.key == "power") {
		// @ATTR event.power|power_id|Specify power coupled with event.
		e->type = EC_POWER;

		e->x = toInt(infile.val);
	}
	else if (infile.key == "spawn") {
		// @ATTR event.spawn|[string,x(integer),y(integer)], ...|Spawn an enemy from this category at location
		e->type = EC_SPAWN;

		e->s = infile.nextValue();
		e->x = toInt(infile.nextValue());
		e->y = toInt(infile.nextValue());

		// add repeating spawn
		if (evnt) {
			std::string repeat_val = infile.nextValue();
			while (repeat_val != "") {
				evnt->components.push_back(Event_Component());
				e = &evnt->components.back();
				e->type = EC_SPAWN;

				e->s = repeat_val;
				e->x = toInt(infile.nextValue());
				e->y = toInt(infile.nextValue());

				repeat_val = infile.nextValue();
			}
		}
	}
	else if (infile.key == "stash") {
		// @ATTR event.stash|boolean|Opens the Stash menu.
		e->type = EC_STASH;

		e->s = infile.val;
	}
	else if (infile.key == "npc") {
		// @ATTR event.npc|string|Filename of an NPC to start dialog with.
		e->type = EC_NPC;

		e->s = infile.val;
	}
	else if (infile.key == "music") {
		// @ATTR event.music|string|Change background music to specified file.
		e->type = EC_MUSIC;

		e->s = infile.val;
	}
	else if (infile.key == "cutscene") {
		// @ATTR event.cutscene|string|Show specified cutscene by filename.
		e->type = EC_CUTSCENE;

		e->s = infile.val;
	}
	else if (infile.key == "repeat") {
		// @ATTR event.repeat|boolean|Allows the event to be triggered again.
		e->type = EC_REPEAT;

		e->s = infile.val;
	}
	else if (infile.key == "save_game") {
		// @ATTR event.save_game|boolean|Saves the game when the event is triggered. The respawn position is set to where the player is standing.
		e->type = EC_SAVE_GAME;

		e->s = infile.val;
	}
	else if (infile.key == "book") {
		// @ATTR event.book|string|Opens a book by filename.
		e->type = EC_BOOK;

		e->s = infile.val;
	}
	else {
		infile.error("EventManager: '%s' is not a valid key.", infile.key.c_str());
	}
}
Ejemplo n.º 10
0
/**
 * load a statblock, typically for an enemy definition
 */
void StatBlock::load(const std::string& filename) {
	// @CLASS StatBlock: Enemies|Description of enemies in enemies/
	FileParser infile;
	if (!infile.open(filename))
		return;

	bool clear_loot = true;

	while (infile.next()) {
		if (infile.new_section) {
			// APPENDed file
			clear_loot = true;
		}

		int num = toInt(infile.val);
		float fnum = toFloat(infile.val);
		bool valid = loadCoreStat(&infile) || loadSfxStat(&infile);

		// @ATTR name|string|Name
		if (infile.key == "name") name = msg->get(infile.val);
		// @ATTR humanoid|boolean|This creature gives human traits when transformed into, such as the ability to talk with NPCs.
		else if (infile.key == "humanoid") humanoid = toBool(infile.val);

		// @ATTR level|integer|Level
		else if (infile.key == "level") level = num;

		// enemy death rewards and events
		// @ATTR xp|integer|XP awarded upon death.
		else if (infile.key == "xp") xp = num;
		else if (infile.key == "loot") {
			// @ATTR loot|[currency:item (integer)], chance (integer), min (integer), max (integer)|Possible loot that can be dropped on death.

			// loot entries format:
			// loot=[id],[percent_chance]
			// optionally allow range:
			// loot=[id],[percent_chance],[count_min],[count_max]

			if (clear_loot) {
				loot_table.clear();
				clear_loot = false;
			}

			loot_table.push_back(Event_Component());
			loot->parseLoot(infile, &loot_table.back(), &loot_table);
		}
		else if (infile.key == "loot_count") {
			// @ATTR loot_count|min (integer), max (integer)|Sets the minimum (and optionally, the maximum) amount of loot this creature can drop. Overrides the global drop_max setting.
			loot_count.x = toInt(infile.nextValue());
			loot_count.y = toInt(infile.nextValue());
			if (loot_count.x != 0 || loot_count.y != 0) {
				clampFloor(loot_count.x, 1);
				clampFloor(loot_count.y, loot_count.x);
			}
		}
		// @ATTR defeat_status|string|Campaign status to set upon death.
		else if (infile.key == "defeat_status") defeat_status = infile.val;
		// @ATTR convert_status|string|Campaign status to set upon being converted to a player ally.
		else if (infile.key == "convert_status") convert_status = infile.val;
		// @ATTR first_defeat_loot|integer|Drops this item upon first death.
		else if (infile.key == "first_defeat_loot") first_defeat_loot = num;
		// @ATTR quest_loot|[requires status (string), requires not status (string), item (integer)|Drops this item when campaign status is met.
		else if (infile.key == "quest_loot") {
			quest_loot_requires_status = infile.nextValue();
			quest_loot_requires_not_status = infile.nextValue();
			quest_loot_id = toInt(infile.nextValue());
		}
		// combat stats
		// @ATTR cooldown|integer|Cooldown between attacks in 'ms' or 's'.
		else if (infile.key == "cooldown") cooldown = parse_duration(infile.val);

		// behavior stats
		// @ATTR flying|boolean|Creature can move over gaps/water.
		else if (infile.key == "flying") flying = toBool(infile.val);
		// @ATTR intangible|boolean|Creature can move through walls.
		else if (infile.key == "intangible") intangible = toBool(infile.val);
		// @ATTR facing|boolean|Creature can turn to face their target.
		else if (infile.key == "facing") facing = toBool(infile.val);

		// @ATTR waypoint_pause|duration|Duration to wait at each waypoint in 'ms' or 's'.
		else if (infile.key == "waypoint_pause") waypoint_pause = parse_duration(infile.val);

		// @ATTR turn_delay|duration|Duration it takes for this creature to turn and face their target in 'ms' or 's'.
		else if (infile.key == "turn_delay") turn_delay = parse_duration(infile.val);
		// @ATTR chance_pursue|integer|Percentage change that the creature will chase their target.
		else if (infile.key == "chance_pursue") chance_pursue = num;
		// @ATTR chance_flee|integer|Percentage chance that the creature will run away from their target.
		else if (infile.key == "chance_flee") chance_flee = num;
		// @ATTR cooldown_hit|duration|Duration of cooldown after being hit in 'ms' or 's'.
		else if (infile.key == "cooldown_hit") cooldown_hit = parse_duration(infile.val);

		else if (infile.key == "power") {
			// @ATTR power|type (string), power id (integer), chance (integer)|A power that has a chance of being triggered in a certain state. States may be any of: melee, ranged, beacon, on_hit, on_death, on_half_dead, on_join_combat, on_debuff
			AIPower ai_power;

			std::string ai_type = infile.nextValue();

			ai_power.id = powers->verifyID(toInt(infile.nextValue()), &infile, false);
			if (ai_power.id == 0)
				continue; // verifyID() will print our error message

			ai_power.chance = toInt(infile.nextValue());

			if (ai_type == "melee") ai_power.type = AI_POWER_MELEE;
			else if (ai_type == "ranged") ai_power.type = AI_POWER_RANGED;
			else if (ai_type == "beacon") ai_power.type = AI_POWER_BEACON;
			else if (ai_type == "on_hit") ai_power.type = AI_POWER_HIT;
			else if (ai_type == "on_death") ai_power.type = AI_POWER_DEATH;
			else if (ai_type == "on_half_dead") ai_power.type = AI_POWER_HALF_DEAD;
			else if (ai_type == "on_join_combat") ai_power.type = AI_POWER_JOIN_COMBAT;
			else if (ai_type == "on_debuff") ai_power.type = AI_POWER_DEBUFF;
			else {
				infile.error("StatBlock: '%s' is not a valid enemy power type.", ai_type.c_str());
				continue;
			}

			if (ai_power.type == AI_POWER_HALF_DEAD)
				half_dead_power = true;

			powers_ai.push_back(ai_power);
		}

		else if (infile.key == "passive_powers") {
			// @ATTR passive_powers|power (integer), ...|A list of passive powers this creature has.
			powers_passive.clear();
			std::string p = infile.nextValue();
			while (p != "") {
				powers_passive.push_back(toInt(p));
				p = infile.nextValue();
			}
		}

		// @ATTR melee_range|float|Minimum distance from target required to use melee powers.
		else if (infile.key == "melee_range") melee_range = fnum;
		// @ATTR threat_range|float|Radius of the area this creature will be able to start chasing the hero.
		else if (infile.key == "threat_range") threat_range = fnum;
		// @ATTR combat_style|[default:aggressive:passive]|How the creature will enter combat. Default is within range of the hero; Aggressive is always in combat; Passive must be attacked to enter combat.
		else if (infile.key == "combat_style") {
			if (infile.val == "default") combat_style = COMBAT_DEFAULT;
			else if (infile.val == "aggressive") combat_style = COMBAT_AGGRESSIVE;
			else if (infile.val == "passive") combat_style = COMBAT_PASSIVE;
			else infile.error("StatBlock: Unknown combat style '%s'", infile.val.c_str());
		}

		// @ATTR animations|string|Filename of an animation definition.
		else if (infile.key == "animations") animations = infile.val;

		// @ATTR suppress_hp|boolean|Hides the enemy HP bar for this creature.
		else if (infile.key == "suppress_hp") suppress_hp = toBool(infile.val);

		else if (infile.key == "categories") {
			// @ATTR categories|category (string), ...|Categories that this enemy belongs to.
			categories.clear();
			std::string cat;
			while ((cat = infile.nextValue()) != "") {
				categories.push_back(cat);
			}
		}

		// this is only used for EnemyGroupManager
		// we check for them here so that we don't get an error saying they are invalid
		else if (infile.key == "rarity") ; // but do nothing

		else if (!valid) {
			infile.error("StatBlock: '%s' is not a valid key.", infile.key.c_str());
		}
	}
	infile.close();

	hp = starting[STAT_HP_MAX];
	mp = starting[STAT_MP_MAX];

	applyEffects();
}
Ejemplo n.º 11
0
void EventManager::loadEventComponent(FileParser &infile, Event* evnt, Event_Component* ec) {
    Event_Component *e = NULL;
    if (evnt) {
        evnt->components.push_back(Event_Component());
        e = &evnt->components.back();
    }
    else if (ec) {
        e = ec;
    }

    if (!e) return;

    e->type = infile.key;

    if (infile.key == "tooltip") {
        // @ATTR event.tooltip|string|Tooltip for event
        e->s = msg->get(infile.val);
    }
    else if (infile.key == "power_path") {
        // @ATTR event.power_path|[hero:[x,y]]|Event power path
        // x,y are src, if s=="hero" we target the hero,
        // else we'll use values in a,b as coordinates
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.nextValue());

        std::string dest = infile.nextValue();
        if (dest == "hero") {
            e->s = "hero";
        }
        else {
            e->a = toInt(dest);
            e->b = toInt(infile.nextValue());
        }
    }
    else if (infile.key == "power_damage") {
        // @ATTR event.power_damage|min(integer), max(integer)|Range of power damage
        e->a = toInt(infile.nextValue());
        e->b = toInt(infile.nextValue());
    }
    else if (infile.key == "intermap") {
        // @ATTR event.intermap|[map(string),x(integer),y(integer)]|Jump to specific map at location specified.
        e->s = infile.nextValue();
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.nextValue());
    }
    else if (infile.key == "intramap") {
        // @ATTR event.intramap|[x(integer),y(integer)]|Jump to specific position within current map.
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.nextValue());
    }
    else if (infile.key == "mapmod") {
        // @ATTR event.mapmod|[string,int,int,int],..|Modify map tiles
        e->s = infile.nextValue();
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.nextValue());
        e->z = toInt(infile.nextValue());

        // add repeating mapmods
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->s = repeat_val;
                e->x = toInt(infile.nextValue());
                e->y = toInt(infile.nextValue());
                e->z = toInt(infile.nextValue());

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "soundfx") {
        // @ATTR event.soundfx|[soundfile(string),x(integer),y(integer)]|Filename of a sound to play at an optional location
        e->s = infile.nextValue();
        e->x = e->y = -1;

        std::string s = infile.nextValue();
        if (s != "") e->x = toInt(s);

        s = infile.nextValue();
        if (s != "") e->y = toInt(s);

    }
    else if (infile.key == "loot") {
        // @ATTR event.loot|[string,drop_chance([fixed:chance(integer)]),quantity_min(integer),quantity_max(integer)],...|Add loot to the event; either a filename or an inline definition.
        loot->parseLoot(infile, e, &evnt->components);
    }
    else if (infile.key == "msg") {
        // @ATTR event.msg|string|Adds a message to be displayed for the event.
        e->s = msg->get(infile.val);
    }
    else if (infile.key == "shakycam") {
        // @ATTR event.shakycam|duration|Makes the camera shake for this duration in 'ms' or 's'.
        e->x = parse_duration(infile.val);
    }
    else if (infile.key == "requires_status") {
        // @ATTR event.requires_status|string,...|Event requires list of statuses
        e->s = infile.nextValue();

        // add repeating requires_status
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->s = repeat_val;

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "requires_not_status") {
        // @ATTR event.requires_not_status|string,...|Event requires not list of statuses
        e->s = infile.nextValue();

        // add repeating requires_not
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->s = repeat_val;

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "requires_level") {
        // @ATTR event.requires_level|integer|Event requires hero level
        e->x = toInt(infile.nextValue());
    }
    else if (infile.key == "requires_not_level") {
        // @ATTR event.requires_not_level|integer|Event requires not hero level
        e->x = toInt(infile.nextValue());
    }
    else if (infile.key == "requires_currency") {
        // @ATTR event.requires_currency|integer|Event requires atleast this much currency
        e->x = toInt(infile.nextValue());
    }
    else if (infile.key == "requires_item") {
        // @ATTR event.requires_item|integer,...|Event requires specific item
        e->x = toInt(infile.nextValue());

        // add repeating requires_item
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->x = toInt(repeat_val);

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "requires_class") {
        // @ATTR event.requires_class|string|Event requires this base class
        e->s = infile.nextValue();
    }
    else if (infile.key == "set_status") {
        // @ATTR event.set_status|string,...|Sets specified statuses
        e->s = infile.nextValue();

        // add repeating set_status
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->s = repeat_val;

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "unset_status") {
        // @ATTR event.unset_status|string,...|Unsets specified statuses
        e->s = infile.nextValue();

        // add repeating unset_status
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->s = repeat_val;

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "remove_currency") {
        // @ATTR event.remove_currency|integer|Removes specified amount of currency from hero inventory
        e->x = toInt(infile.val);
        clampFloor(e->x, 0);
    }
    else if (infile.key == "remove_item") {
        // @ATTR event.remove_item|integer,...|Removes specified item from hero inventory
        e->x = toInt(infile.nextValue());

        // add repeating remove_item
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;
                e->x = toInt(repeat_val);

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "reward_xp") {
        // @ATTR event.reward_xp|integer|Reward hero with specified amount of experience points.
        e->x = toInt(infile.val);
        clampFloor(e->x, 0);
    }
    else if (infile.key == "reward_currency") {
        // @ATTR event.reward_currency|integer|Reward hero with specified amount of currency.
        e->x = toInt(infile.val);
        clampFloor(e->x, 0);
    }
    else if (infile.key == "reward_item") {
        // @ATTR event.reward_item|x(integer),y(integer)|Reward hero with y number of item x.
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.val);
        clampFloor(e->y, 0);
    }
    else if (infile.key == "restore") {
        // @ATTR event.restore|string|Restore the hero's HP, MP, and/or status.
        e->s = infile.val;
    }
    else if (infile.key == "power") {
        // @ATTR event.power|power_id|Specify power coupled with event.
        e->x = toInt(infile.val);
    }
    else if (infile.key == "spawn") {
        // @ATTR event.spawn|[string,x(integer),y(integer)], ...|Spawn an enemy from this category at location
        e->s = infile.nextValue();
        e->x = toInt(infile.nextValue());
        e->y = toInt(infile.nextValue());

        // add repeating spawn
        if (evnt) {
            std::string repeat_val = infile.nextValue();
            while (repeat_val != "") {
                evnt->components.push_back(Event_Component());
                e = &evnt->components.back();
                e->type = infile.key;

                e->s = repeat_val;
                e->x = toInt(infile.nextValue());
                e->y = toInt(infile.nextValue());

                repeat_val = infile.nextValue();
            }
        }
    }
    else if (infile.key == "stash") {
        // @ATTR event.stash|boolean|Opens the Stash menu.
        e->s = infile.val;
    }
    else if (infile.key == "npc") {
        // @ATTR event.npc|string|Filename of an NPC to start dialog with.
        e->s = infile.val;
    }
    else if (infile.key == "music") {
        // @ATTR event.music|string|Change background music to specified file.
        e->s = infile.val;
    }
    else if (infile.key == "cutscene") {
        // @ATTR event.cutscene|string|Show specified cutscene by filename.
        e->s = infile.val;
    }
    else if (infile.key == "repeat") {
        // @ATTR event.repeat|boolean|Allows the event to be triggered again.
        e->s = infile.val;
    }
    else if (infile.key == "save_game") {
        // @ATTR event.save_game|boolean|Saves the game when the event is triggered. The respawn position is set to where the player is standing.
        e->s = infile.val;
    }
    else {
        infile.error("EventManager: '%s' is not a valid key.", infile.key.c_str());
    }
}