Example #1
0
void LootManager::loadLootTables() {
	std::vector<std::string> filenames = mods->list("loot", !ModManager::LIST_FULL_PATHS);

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

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

		while (infile.next()) {
			if (infile.section == "") {
				if (infile.key == "loot") {
					ec_list->push_back(EventComponent());
					ec = &ec_list->back();
					parseLoot(infile.val, ec, ec_list);
				}
			}
			else if (infile.section == "loot") {
				if (infile.new_section) {
					ec_list->push_back(EventComponent());
					ec = &ec_list->back();
					ec->type = EventComponent::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 = eset->misc.currency_id;
					else if (Parse::toInt(ec->s, -1) != -1)
						ec->c = Parse::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->f = 0;
					else
						ec->f = Parse::toFloat(infile.val);
				}
				else if (infile.key == "quantity") {
					ec->a = std::max(Parse::popFirstInt(infile.val), 1);
					ec->b = std::max(Parse::popFirstInt(infile.val), ec->a);
				}
			}
		}

		infile.close();
	}
}
/**
 * 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, FileParser::MOD_FILE, FileParser::ERROR_NORMAL))
		return;

	bool clear_loot = true;
	bool flee_range_defined = false;

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

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

		// @ATTR name|string|Name
		if (infile.key == "name") name = msg->get(infile.val);
		// @ATTR humanoid|bool|This creature gives human traits when transformed into, such as the ability to talk with NPCs.
		else if (infile.key == "humanoid") humanoid = Parse::toBool(infile.val);
		// @ATTR lifeform|bool|Determines whether or not this entity is referred to as a living thing, such as displaying "Dead" vs "Destroyed" when their HP is 0.
		else if (infile.key == "lifeform") lifeform = Parse::toBool(infile.val);

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

		// enemy death rewards and events
		// @ATTR xp|int|XP awarded upon death.
		else if (infile.key == "xp") xp = num;
		else if (infile.key == "loot") {
			// @ATTR loot|repeatable(loot)|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(EventComponent());
			loot->parseLoot(infile.val, &loot_table.back(), &loot_table);
		}
		else if (infile.key == "loot_count") {
			// @ATTR loot_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of loot this creature can drop. Overrides the global drop_max setting.
			loot_count.x = Parse::popFirstInt(infile.val);
			loot_count.y = Parse::popFirstInt(infile.val);
			if (loot_count.x != 0 || loot_count.y != 0) {
				loot_count.x = std::max(loot_count.x, 1);
				loot_count.y = std::max(loot_count.y, loot_count.x);
			}
		}
		// @ATTR defeat_status|string|Campaign status to set upon death.
		else if (infile.key == "defeat_status") defeat_status = camp->registerStatus(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 = camp->registerStatus(infile.val);
		// @ATTR first_defeat_loot|item_id|Drops this item upon first death.
		else if (infile.key == "first_defeat_loot") first_defeat_loot = num;
		// @ATTR quest_loot|string, string, item_id : Required status, Required not status, Item|Drops this item when campaign status is met.
		else if (infile.key == "quest_loot") {
			quest_loot_requires_status = camp->registerStatus(Parse::popFirstString(infile.val));
			quest_loot_requires_not_status = camp->registerStatus(Parse::popFirstString(infile.val));
			quest_loot_id = Parse::popFirstInt(infile.val);
		}

		// behavior stats
		// @ATTR flying|bool|Creature can move over gaps/water.
		else if (infile.key == "flying") flying = Parse::toBool(infile.val);
		// @ATTR intangible|bool|Creature can move through walls.
		else if (infile.key == "intangible") intangible = Parse::toBool(infile.val);
		// @ATTR facing|bool|Creature can turn to face their target.
		else if (infile.key == "facing") facing = Parse::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::toDuration(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::toDuration(infile.val);
		// @ATTR chance_pursue|int|Percentage change that the creature will chase their target.
		else if (infile.key == "chance_pursue") chance_pursue = num;
		// @ATTR chance_flee|int|Percentage chance that the creature will run away from their target.
		else if (infile.key == "chance_flee") chance_flee = num;

		else if (infile.key == "power") {
			// @ATTR power|["melee", "ranged", "beacon", "on_hit", "on_death", "on_half_dead", "on_join_combat", "on_debuff"], power_id, int : State, Power, Chance|A power that has a chance of being triggered in a certain state.
			AIPower ai_power;

			std::string ai_type = Parse::popFirstString(infile.val);

			ai_power.id = powers->verifyID(Parse::popFirstInt(infile.val), &infile, !PowerManager::ALLOW_ZERO_ID);
			if (ai_power.id == 0)
				continue; // verifyID() will print our error message

			ai_power.chance = Parse::popFirstInt(infile.val);

			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|list(power_id)|A list of passive powers this creature has.
			powers_passive.clear();
			std::string p = Parse::popFirstString(infile.val);
			while (p != "") {
				powers_passive.push_back(Parse::toInt(p));
				p = Parse::popFirstString(infile.val);

				// if a passive power has a post power, add it to the AI power list so we can track its cooldown
				int post_power = powers->powers[powers_passive.back()].post_power;
				if (post_power > 0) {
					AIPower passive_post_power;
					passive_post_power.type = AI_POWER_PASSIVE_POST;
					passive_post_power.id = post_power;
					passive_post_power.chance = 0; // post_power chance is used instead
					powers_ai.push_back(passive_post_power);
				}
			}
		}

		// @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, float: Engage distance, Stop distance|The first value is the radius of the area this creature will be able to start chasing the hero. The second, optional, value is the radius at which this creature will stop pursuing their target and defaults to double the first value.
		else if (infile.key == "threat_range") {
			threat_range = Parse::toFloat(Parse::popFirstString(infile.val));

			std::string tr_far = Parse::popFirstString(infile.val);
			if (!tr_far.empty())
				threat_range_far = Parse::toFloat(tr_far);
			else
				threat_range_far = threat_range * 2;
		}
		// @ATTR flee_range|float|The radius at which this creature will start moving to a safe distance. Defaults to half of the threat_range.
		else if (infile.key == "flee_range") {
			flee_range = fnum;
			flee_range_defined = true;
		}
		// @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|filename|Filename of an animation definition.
		else if (infile.key == "animations") animations = infile.val;

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

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

		// @ATTR flee_duration|duration|The minimum amount of time that this creature will flee. They may flee longer than the specified time.
		else if (infile.key == "flee_duration") flee_duration = Parse::toDuration(infile.val);
		// @ATTR flee_cooldown|duration|The amount of time this creature must wait before they can start fleeing again.
		else if (infile.key == "flee_cooldown") flee_cooldown = Parse::toDuration(infile.val);

		// 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[Stats::HP_MAX];
	mp = starting[Stats::MP_MAX];

	if (!flee_range_defined)
		flee_range = threat_range / 2;

	applyEffects();
}
Example #3
0
void LootManager::parseLoot(std::string &val, EventComponent *e, std::vector<EventComponent> *ec_list) {
	if (e == NULL) return;

	std::string chance;
	bool first_is_filename = false;
	e->s = Parse::popFirstString(val);

	if (e->s == "currency")
		e->c = eset->misc.currency_id;
	else if (Parse::toInt(e->s, -1) != -1)
		e->c = Parse::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 = EventComponent::LOOT;

		// drop chance
		chance = Parse::popFirstString(val);
		if (chance == "fixed") e->f = 0;
		else e->f = Parse::toFloat(chance);

		// quantity min/max
		e->a = std::max(Parse::popFirstInt(val), 1);
		e->b = std::max(Parse::popFirstInt(val), e->a);
	}

	// add repeating loot
	if (ec_list) {
		std::string repeat_val = Parse::popFirstString(val);
		while (repeat_val != "") {
			ec_list->push_back(EventComponent());
			EventComponent *ec = &ec_list->back();
			ec->type = EventComponent::LOOT;

			ec->s = repeat_val;
			if (ec->s == "currency")
				ec->c = eset->misc.currency_id;
			else if (Parse::toInt(ec->s, -1) != -1)
				ec->c = Parse::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 = Parse::popFirstString(val);
				continue;
			}

			chance = Parse::popFirstString(val);
			if (chance == "fixed") ec->f = 0;
			else ec->f = Parse::toFloat(chance);

			ec->a = std::max(Parse::popFirstInt(val), 1);
			ec->b = std::max(Parse::popFirstInt(val), ec->a);

			repeat_val = Parse::popFirstString(val);
		}
	}
}
Example #4
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;

	portrait_filenames.resize(1);

	// @CLASS NPC|Description of NPCs in npcs/
	if (infile.open(npc_id, FileParser::MOD_FILE, FileParser::ERROR_NORMAL)) {
		bool clear_random_table = true;

		while (infile.next()) {
			if (infile.section == "dialog") {
				if (infile.new_section) {
					dialog.push_back(std::vector<EventComponent>());
				}
				EventComponent e;
				e.type = EventComponent::NONE;
				if (infile.key == "him" || infile.key == "her") {
					// @ATTR dialog.him|repeatable(string)|A line of dialog from the NPC.
					// @ATTR dialog.her|repeatable(string)|A line of dialog from the NPC.
					e.type = EventComponent::NPC_DIALOG_THEM;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "you") {
					// @ATTR dialog.you|repeatable(string)|A line of dialog from the player.
					e.type = EventComponent::NPC_DIALOG_YOU;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "voice") {
					// @ATTR dialog.voice|repeatable(string)|Filename of a voice sound file to play.
					e.type = EventComponent::NPC_VOICE;
					e.x = loadSound(infile.val, 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 = EventComponent::NPC_DIALOG_TOPIC;
					e.s = msg->get(infile.val);
				}
				else if (infile.key == "group") {
					// @ATTR dialog.group|string|Dialog group.
					e.type = EventComponent::NPC_DIALOG_GROUP;
					e.s = infile.val;
				}
				else if (infile.key == "allow_movement") {
					// @ATTR dialog.allow_movement|bool|Restrict the player's mvoement during dialog.
					e.type = EventComponent::NPC_ALLOW_MOVEMENT;
					e.s = infile.val;
				}
				else if (infile.key == "portrait_him" || infile.key == "portrait_her") {
					// @ATTR dialog.portrait_him|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
					// @ATTR dialog.portrait_her|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog.
					e.type = EventComponent::NPC_PORTRAIT_THEM;
					e.s = infile.val;
					portrait_filenames.push_back(e.s);
				}
				else if (infile.key == "portrait_you") {
					// @ATTR dialog.portrait_you|repeatable(filename)|Filename of a portrait to display for the player during this dialog.
					e.type = EventComponent::NPC_PORTRAIT_YOU;
					e.s = infile.val;
					portrait_filenames.push_back(e.s);
				}
				else {
					Event ev;
					EventManager::loadEventComponent(infile, &ev, NULL);

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

				if (e.type != EventComponent::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|filename|Filename of an animation definition.
					gfx = infile.val;
				}
				else if (infile.key == "direction") {
					// @ATTR direction|direction|The direction to use for this NPC's stance animation.
					direction = Parse::toDirection(infile.val);
				}

				// handle talkers
				else if (infile.key == "talker") {
					// @ATTR talker|bool|Allows this NPC to be talked to.
					talker = Parse::toBool(infile.val);
				}
				else if (infile.key == "portrait") {
					// @ATTR portrait|filename|Filename of the default portrait image.
					portrait_filenames[0] = infile.val;
				}

				// handle vendors
				else if (infile.key == "vendor") {
					// @ATTR vendor|bool|Allows this NPC to buy/sell items.
					vendor = Parse::toBool(infile.val);
				}
				else if (infile.key == "vendor_requires_status") {
					// @ATTR vendor_requires_status|list(string)|The player must have these statuses in order to use this NPC as a vendor.
					while (infile.val != "") {
						vendor_requires_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
					}
				}
				else if (infile.key == "vendor_requires_not_status") {
					// @ATTR vendor_requires_not_status|list(string)|The player must not have these statuses in order to use this NPC as a vendor.
					while (infile.val != "") {
						vendor_requires_not_status.push_back(camp->registerStatus(Parse::popFirstString(infile.val)));
					}
				}
				else if (infile.key == "constant_stock") {
					// @ATTR constant_stock|repeatable(list(item_id))|A list of items this vendor has for sale. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.
					while (infile.val != "") {
						std::string temp = Parse::popFirstString(infile.val);
						temp += ':';
						stack.item = Parse::popFirstInt(temp, ':');
						stack.quantity = Parse::popFirstInt(temp, ':');
						if (stack.quantity == 0)
							stack.quantity = 1;
						stock.add(stack, ItemStorage::NO_SLOT);
					}
				}
				else if (infile.key == "status_stock") {
					// @ATTR status_stock|repeatable(string, list(item_id)) : Required status, Item(s)|A list of items this vendor will have for sale if the required status is met. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.
					if (camp->checkStatus(camp->registerStatus(Parse::popFirstString(infile.val)))) {
						while (infile.val != "") {
							std::string temp = Parse::popFirstString(infile.val);
							temp += ':';
							stack.item = Parse::popFirstInt(temp, ':');
							stack.quantity = Parse::popFirstInt(temp, ':');
							if (stack.quantity == 0)
								stack.quantity = 1;
							stock.add(stack, ItemStorage::NO_SLOT);
						}
					}
				}
				else if (infile.key == "random_stock") {
					// @ATTR random_stock|list(loot)|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(EventComponent());
					loot->parseLoot(infile.val, &random_table.back(), &random_table);
				}
				else if (infile.key == "random_stock_count") {
					// @ATTR random_stock_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of random items this npc can have.
					random_table_count.x = Parse::popFirstInt(infile.val);
					random_table_count.y = Parse::popFirstInt(infile.val);
					if (random_table_count.x != 0 || random_table_count.y != 0) {
						random_table_count.x = std::max(random_table_count.x, 1);
						random_table_count.y = std::max(random_table_count.y, random_table_count.x);
					}
				}

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

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

	// fill inventory with items from random stock table
	unsigned rand_count = Math::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], ItemStorage::NO_SLOT);
	}
}