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.val, ec, ec_list); } } else if (infile.section == "loot") { if (infile.new_section) { ec_list->push_back(Event_Component()); ec = &ec_list->back(); ec->type = EC_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 = std::max(popFirstInt(infile.val), 1); ec->b = std::max(popFirstInt(infile.val), ec->a); } } } infile.close(); } }
Event_Component EventManager::getRandomMapFromFile(const std::string& fname) { // map pool is the same, so pick the next one in the "playlist" if (fname == mapr->intermap_random_filename && !mapr->intermap_random_queue.empty()) { Event_Component ec = mapr->intermap_random_queue.front(); mapr->intermap_random_queue.pop(); return ec; } // starting a new map pool, so clear the queue while (!mapr->intermap_random_queue.empty()) { mapr->intermap_random_queue.pop(); } FileParser infile; std::vector<Event_Component> ec_list; // @CLASS EventManager: Random Map List|Description of maps/random/lists/ if (infile.open(fname)) { while (infile.next()) { // @ATTR map|filename, int, int : Map file, X, Y|Adds a map and optional spawn position to the random list of maps to teleport to. if (infile.key == "map") { Event_Component ec; ec.s = popFirstString(infile.val); if (ec_list.empty() || ec.s != mapr->getFilename()) { ec.x = -1; ec.y = -1; std::string test_x = popFirstString(infile.val); if (!test_x.empty()) { ec.x = toInt(test_x); ec.y = popFirstInt(infile.val); } ec_list.push_back(ec); } } } infile.close(); } if (ec_list.empty()) { mapr->intermap_random_filename = ""; return Event_Component(); } else { mapr->intermap_random_filename = fname; while (!ec_list.empty()) { size_t index = rand() % ec_list.size(); mapr->intermap_random_queue.push(ec_list[index]); ec_list.erase(ec_list.begin() + index); } Event_Component ec = mapr->intermap_random_queue.front(); mapr->intermap_random_queue.pop(); return ec; } }
void MapRenderer::checkEvents(FPoint loc) { Point maploc; maploc.x = int(loc.x); maploc.y = int(loc.y); std::vector<Event>::iterator it; // loop in reverse because we may erase elements for (it = events.end(); it != events.begin(); ) { --it; // skip inactive events if (!EventManager::isActive(*it)) continue; if ((*it).type == "on_clear") { if (enemies_cleared && EventManager::executeEvent(*it)) it = events.erase(it); continue; } bool inside = maploc.x >= (*it).location.x && maploc.y >= (*it).location.y && maploc.x <= (*it).location.x + (*it).location.w-1 && maploc.y <= (*it).location.y + (*it).location.h-1; if ((*it).type == "on_leave") { if (inside) { if (!(*it).getComponent("wasInsideEventArea")) { (*it).components.push_back(Event_Component()); (*it).components.back().type = std::string("wasInsideEventArea"); } } else { if ((*it).getComponent("wasInsideEventArea")) { (*it).deleteAllComponents("wasInsideEventArea"); if (EventManager::executeEvent(*it)) it = events.erase(it); } } } else { if (inside) if (EventManager::executeEvent(*it)) it = events.erase(it); } } }
/** * 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]); } }
/** * 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; bool flee_range_defined = false; 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|bool|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|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(Event_Component()); 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 = popFirstInt(infile.val); loot_count.y = 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 = 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|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 = popFirstString(infile.val); quest_loot_requires_not_status = popFirstString(infile.val); quest_loot_id = popFirstInt(infile.val); } // behavior stats // @ATTR flying|bool|Creature can move over gaps/water. else if (infile.key == "flying") flying = toBool(infile.val); // @ATTR intangible|bool|Creature can move through walls. else if (infile.key == "intangible") intangible = toBool(infile.val); // @ATTR facing|bool|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|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 = popFirstString(infile.val); ai_power.id = powers->verifyID(popFirstInt(infile.val), &infile, false); if (ai_power.id == 0) continue; // verifyID() will print our error message ai_power.chance = 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 = popFirstString(infile.val); while (p != "") { powers_passive.push_back(toInt(p)); p = popFirstString(infile.val); } } // @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 = toFloat(popFirstString(infile.val)); std::string tr_far = popFirstString(infile.val); if (!tr_far.empty()) threat_range_far = 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 = 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 = 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_duration(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_duration(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[STAT_HP_MAX]; mp = starting[STAT_MP_MAX]; if (!flee_range_defined) flee_range = threat_range / 2; applyEffects(); }
/** * 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)) { bool clear_random_table = true; 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|repeatable(string)|A line of dialog from the NPC. // @ATTR dialog.her|repeatable(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|repeatable(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|repeatable(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|bool|Restrict the player's mvoement during dialog. e.type = EC_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 = EC_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 = EC_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 != 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|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_direction(infile.val); } // handle talkers else if (infile.key == "talker") { // @ATTR talker|bool|Allows this NPC to be talked to. talker = 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 = 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(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(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. stack.quantity = 1; while (infile.val != "") { stack.item = popFirstInt(infile.val); stock.add(stack); } } 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. if (camp->checkStatus(popFirstString(infile.val))) { stack.quantity = 1; while (infile.val != "") { stack.item = popFirstInt(infile.val); stock.add(stack); } } } 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(Event_Component()); 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 = popFirstInt(infile.val); random_table_count.y = 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, NPC_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 = 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]); } }
/** * 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); } // @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 chance_melee_phys|integer|Percentage chance that the creature will use their physical melee power. else if (infile.key == "chance_melee_phys") power_chance[MELEE_PHYS] = num; // @ATTR chance_melee_ment|integer|Percentage chance that the creature will use their mental melee power. else if (infile.key == "chance_melee_ment") power_chance[MELEE_MENT] = num; // @ATTR chance_ranged_phys|integer|Percentage chance that the creature will use their physical ranged power. else if (infile.key == "chance_ranged_phys") power_chance[RANGED_PHYS] = num; // @ATTR chance_ranged_ment|integer|Percentage chance that the creature will use their mental ranged power. else if (infile.key == "chance_ranged_ment") power_chance[RANGED_MENT] = num; // @ATTR power_melee_phys|integer|Power index for the physical melee power. else if (infile.key == "power_melee_phys") power_index[MELEE_PHYS] = num; // @ATTR power_melee_ment|integer|Power index for the mental melee power. else if (infile.key == "power_melee_ment") power_index[MELEE_MENT] = num; // @ATTR power_ranged_phys|integer|Power index for the physical ranged power. else if (infile.key == "power_ranged_phys") power_index[RANGED_PHYS] = num; // @ATTR power_ranged_ment|integer|Power index for the mental ranged power. else if (infile.key == "power_ranged_ment") power_index[RANGED_MENT] = num; // @ATTR power_beacon|integer|Power index of a "beacon" power used to aggro nearby creatures. else if (infile.key == "power_beacon") power_index[BEACON] = num; // @ATTR power_on_hit|integer|Power index that is triggered when hit. else if (infile.key == "power_on_hit") power_index[ON_HIT] = num; // @ATTR power_on_death|integer|Power index that is triggered when dead. else if (infile.key == "power_on_death") power_index[ON_DEATH] = num; // @ATTR power_on_half_dead|integer|Power index that is triggered when at half health. else if (infile.key == "power_on_half_dead") power_index[ON_HALF_DEAD] = num; // @ATTR power_on_debuff|integer|Power index that is triggered when under a negative status effect. else if (infile.key == "power_on_debuff") power_index[ON_DEBUFF] = num; // @ATTR power_on_join_combat|integer|Power index that is triggered when initiating combat. else if (infile.key == "power_on_join_combat") power_index[ON_JOIN_COMBAT] = num; // @ATTR chance_on_hit|integer|Percentage chance that power_on_hit will be triggered. else if (infile.key == "chance_on_hit") power_chance[ON_HIT] = num; // @ATTR chance_on_death|integer|Percentage chance that power_on_death will be triggered. else if (infile.key == "chance_on_death") power_chance[ON_DEATH] = num; // @ATTR chance_on_half_dead|integer|Percentage chance that power_on_half_dead will be triggered. else if (infile.key == "chance_on_half_dead") power_chance[ON_HALF_DEAD] = num; // @ATTR chance_on_debuff|integer|Percentage chance that power_on_debuff will be triggered. else if (infile.key == "chance_on_debuff") power_chance[ON_DEBUFF] = num; // @ATTR chance_on_join_combat|integer|Percentage chance that power_on_join_combat will be triggered. else if (infile.key == "chance_on_join_combat") power_chance[ON_JOIN_COMBAT] = 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 == "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(); }
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(); } } }
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()); } }
bool EventManager::loadEventComponentString(std::string &key, std::string &val, 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 true; e->type = EC_NONE; if (key == "tooltip") { // @ATTR event.tooltip|string|Tooltip for event e->type = EC_TOOLTIP; e->s = msg->get(val); } else if (key == "power_path") { // @ATTR event.power_path|["hero", point]|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 = popFirstInt(val); e->y = popFirstInt(val); std::string dest = popFirstString(val); if (dest == "hero") { e->s = "hero"; } else { e->a = toInt(dest); e->b = popFirstInt(val); } } else if (key == "power_damage") { // @ATTR event.power_damage|int, int : Min, Max|Range of power damage e->type = EC_POWER_DAMAGE; e->a = popFirstInt(val); e->b = popFirstInt(val); } else if (key == "intermap") { // @ATTR event.intermap|filename, int, int : Map file, X, Y|Jump to specific map at location specified. e->type = EC_INTERMAP; e->s = popFirstString(val); e->x = -1; e->y = -1; std::string test_x = popFirstString(val); if (!test_x.empty()) { e->x = toInt(test_x); e->y = popFirstInt(val); } } else if (key == "intermap_random") { // @ATTR event.intermap_random|filename|Pick a random map from a map list file and teleport to it. e->type = EC_INTERMAP; e->s = popFirstString(val); e->z = 1; // flag that tells an intermap event that it contains a map list } else if (key == "intramap") { // @ATTR event.intramap|int, int : X, Y|Jump to specific position within current map. e->type = EC_INTRAMAP; e->x = popFirstInt(val); e->y = popFirstInt(val); } else if (key == "mapmod") { // @ATTR event.mapmod|list(predefined_string, int, int, int) : Layer, X, Y, Tile ID|Modify map tiles e->type = EC_MAPMOD; e->s = popFirstString(val); e->x = popFirstInt(val); e->y = popFirstInt(val); e->z = popFirstInt(val); // add repeating mapmods if (evnt) { std::string repeat_val = popFirstString(val); while (repeat_val != "") { evnt->components.push_back(Event_Component()); e = &evnt->components.back(); e->type = EC_MAPMOD; e->s = repeat_val; e->x = popFirstInt(val); e->y = popFirstInt(val); e->z = popFirstInt(val); repeat_val = popFirstString(val); } } } else if (key == "soundfx") { // @ATTR event.soundfx|filename, int, int, bool : Sound file, X, Y, loop|Filename of a sound to play. Optionally, it can be played at a specific location and/or looped. e->type = EC_SOUNDFX; e->s = popFirstString(val); e->x = e->y = -1; e->z = static_cast<int>(false); std::string s = popFirstString(val); if (s != "") e->x = toInt(s); s = popFirstString(val); if (s != "") e->y = toInt(s); s = popFirstString(val); if (s != "") e->z = static_cast<int>(toBool(s)); } else if (key == "loot") { // @ATTR event.loot|list(loot)|Add loot to the event. e->type = EC_LOOT; loot->parseLoot(val, e, &evnt->components); } else if (key == "loot_count") { // @ATTR event.loot_count|int, int : Min, Max|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 = popFirstInt(val); e->y = popFirstInt(val); if (e->x != 0 || e->y != 0) { e->x = std::max(e->x, 1); e->y = std::max(e->y, e->x); } } else if (key == "msg") { // @ATTR event.msg|string|Adds a message to be displayed for the event. e->type = EC_MSG; e->s = msg->get(val); } else if (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(val); } else if (key == "requires_status") { // @ATTR event.requires_status|list(string)|Event requires list of statuses e->type = EC_REQUIRES_STATUS; e->s = popFirstString(val); // add repeating requires_status if (evnt) { std::string repeat_val = popFirstString(val); while (repeat_val != "") { evnt->components.push_back(Event_Component()); e = &evnt->components.back(); e->type = EC_REQUIRES_STATUS; e->s = repeat_val; repeat_val = popFirstString(val); } } } else if (key == "requires_not_status") { // @ATTR event.requires_not_status|list(string)|Event requires not list of statuses e->type = EC_REQUIRES_NOT_STATUS; e->s = popFirstString(val); // add repeating requires_not if (evnt) { std::string repeat_val = popFirstString(val); 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 = popFirstString(val); } } } else if (key == "requires_level") { // @ATTR event.requires_level|int|Event requires hero level e->type = EC_REQUIRES_LEVEL; e->x = popFirstInt(val); } else if (key == "requires_not_level") { // @ATTR event.requires_not_level|int|Event requires not hero level e->type = EC_REQUIRES_NOT_LEVEL; e->x = popFirstInt(val); } else if (key == "requires_currency") { // @ATTR event.requires_currency|int|Event requires atleast this much currency e->type = EC_REQUIRES_CURRENCY; e->x = popFirstInt(val); } else if (key == "requires_not_currency") { // @ATTR event.requires_not_currency|int|Event requires no more than this much currency e->type = EC_REQUIRES_NOT_CURRENCY; e->x = popFirstInt(val); } else if (key == "requires_item") { // @ATTR event.requires_item|list(item_id)|Event requires specific item (not equipped) e->type = EC_REQUIRES_ITEM; e->x = popFirstInt(val); // add repeating requires_item if (evnt) { std::string repeat_val = popFirstString(val); 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 = popFirstString(val); } } } else if (key == "requires_not_item") { // @ATTR event.requires_not_item|list(item_id)|Event requires not having a specific item (not equipped) e->type = EC_REQUIRES_NOT_ITEM; e->x = popFirstInt(val); // add repeating requires_not_item if (evnt) { std::string repeat_val = popFirstString(val); 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 = popFirstString(val); } } } else if (key == "requires_class") { // @ATTR event.requires_class|predefined_string|Event requires this base class e->type = EC_REQUIRES_CLASS; e->s = popFirstString(val); } else if (key == "requires_not_class") { // @ATTR event.requires_not_class|predefined_string|Event requires not this base class e->type = EC_REQUIRES_NOT_CLASS; e->s = popFirstString(val); } else if (key == "set_status") { // @ATTR event.set_status|list(string)|Sets specified statuses e->type = EC_SET_STATUS; e->s = popFirstString(val); // add repeating set_status if (evnt) { std::string repeat_val = popFirstString(val); while (repeat_val != "") { evnt->components.push_back(Event_Component()); e = &evnt->components.back(); e->type = EC_SET_STATUS; e->s = repeat_val; repeat_val = popFirstString(val); } } } else if (key == "unset_status") { // @ATTR event.unset_status|list(string)|Unsets specified statuses e->type = EC_UNSET_STATUS; e->s = popFirstString(val); // add repeating unset_status if (evnt) { std::string repeat_val = popFirstString(val); while (repeat_val != "") { evnt->components.push_back(Event_Component()); e = &evnt->components.back(); e->type = EC_UNSET_STATUS; e->s = repeat_val; repeat_val = popFirstString(val); } } } else if (key == "remove_currency") { // @ATTR event.remove_currency|int|Removes specified amount of currency from hero inventory e->type = EC_REMOVE_CURRENCY; e->x = std::max(toInt(val), 0); } else if (key == "remove_item") { // @ATTR event.remove_item|list(item_id)|Removes specified item from hero inventory e->type = EC_REMOVE_ITEM; e->x = popFirstInt(val); // add repeating remove_item if (evnt) { std::string repeat_val = popFirstString(val); 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 = popFirstString(val); } } } else if (key == "reward_xp") { // @ATTR event.reward_xp|int|Reward hero with specified amount of experience points. e->type = EC_REWARD_XP; e->x = std::max(toInt(val), 0); } else if (key == "reward_currency") { // @ATTR event.reward_currency|int|Reward hero with specified amount of currency. e->type = EC_REWARD_CURRENCY; e->x = std::max(toInt(val), 0); } else if (key == "reward_item") { // @ATTR event.reward_item|item_id, int : Item, Quantity|Reward hero with y number of item x. e->type = EC_REWARD_ITEM; e->x = popFirstInt(val); e->y = std::max(popFirstInt(val), 1); } else if (key == "restore") { // @ATTR event.restore|["hp", "mp", "hpmp", "status", "all"]|Restore the hero's HP, MP, and/or status. e->type = EC_RESTORE; e->s = val; } else if (key == "power") { // @ATTR event.power|power_id|Specify power coupled with event. e->type = EC_POWER; e->x = toInt(val); } else if (key == "spawn") { // @ATTR event.spawn|list(predefined_string, int, int) : Enemy category, X, Y|Spawn an enemy from this category at location e->type = EC_SPAWN; e->s = popFirstString(val); e->x = popFirstInt(val); e->y = popFirstInt(val); // add repeating spawn if (evnt) { std::string repeat_val = popFirstString(val); while (repeat_val != "") { evnt->components.push_back(Event_Component()); e = &evnt->components.back(); e->type = EC_SPAWN; e->s = repeat_val; e->x = popFirstInt(val); e->y = popFirstInt(val); repeat_val = popFirstString(val); } } } else if (key == "stash") { // @ATTR event.stash|bool|If true, the Stash menu if opened. e->type = EC_STASH; e->x = static_cast<int>(toBool(val)); } else if (key == "npc") { // @ATTR event.npc|filename|Filename of an NPC to start dialog with. e->type = EC_NPC; e->s = val; } else if (key == "music") { // @ATTR event.music|filename|Change background music to specified file. e->type = EC_MUSIC; e->s = val; } else if (key == "cutscene") { // @ATTR event.cutscene|filename|Show specified cutscene by filename. e->type = EC_CUTSCENE; e->s = val; } else if (key == "repeat") { // @ATTR event.repeat|bool|If true, the event to be triggered again. e->type = EC_REPEAT; e->x = static_cast<int>(toBool(val)); } else if (key == "save_game") { // @ATTR event.save_game|bool|If true, the game is saved when the event is triggered. The respawn position is set to where the player is standing. e->type = EC_SAVE_GAME; e->x = static_cast<int>(toBool(val)); } else if (key == "book") { // @ATTR event.book|filename|Opens a book by filename. e->type = EC_BOOK; e->s = val; } else if (key == "script") { // @ATTR event.script|filename|Loads and executes an Event from a file. e->type = EC_SCRIPT; e->s = val; } else if (key == "chance_exec") { // @ATTR event.chance_exec|int|Percentage chance that this event will execute when triggered. e->type = EC_CHANCE_EXEC; e->x = popFirstInt(val); } else { return false; } return true; }
void Map::loadEventComponent(FileParser &infile) { // new event component events.back().components.push_back(Event_Component()); Event_Component *e = &events.back().components.back(); e->type = infile.key; if (infile.key == "tooltip") { e->s = msg->get(infile.val); } else if (infile.key == "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") { e->a = toInt(infile.nextValue()); e->b = toInt(infile.nextValue()); } else if (infile.key == "intermap") { e->s = infile.nextValue(); e->x = toInt(infile.nextValue()); e->y = toInt(infile.nextValue()); } else if (infile.key == "intramap") { e->x = toInt(infile.nextValue()); e->y = toInt(infile.nextValue()); } else if (infile.key == "mapmod") { e->s = infile.nextValue(); e->x = toInt(infile.nextValue()); e->y = toInt(infile.nextValue()); e->z = toInt(infile.nextValue()); // add repeating mapmods std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().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") { 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") { e->s = infile.nextValue(); e->x = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; e->y = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; // drop chance std::string chance = infile.nextValue(); if (chance == "fixed") e->z = 0; else e->z = toInt(chance); // quantity min/max e->a = toInt(infile.nextValue()); if (e->a < 1) e->a = 1; e->b = toInt(infile.nextValue()); if (e->b < e->a) e->b = e->a; // add repeating loot std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; e->x = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; e->y = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; chance = infile.nextValue(); if (chance == "fixed") e->z = 0; else e->z = toInt(chance); e->a = toInt(infile.nextValue()); if (e->a < 1) e->a = 1; e->b = toInt(infile.nextValue()); if (e->b < e->a) e->b = e->a; repeat_val = infile.nextValue(); } } else if (infile.key == "msg") { e->s = msg->get(infile.val); } else if (infile.key == "shakycam") { e->x = toInt(infile.val); } else if (infile.key == "requires_status") { e->s = infile.nextValue(); // add repeating requires_status std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; repeat_val = infile.nextValue(); } } else if (infile.key == "requires_not") { e->s = infile.nextValue(); // add repeating requires_not std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; repeat_val = infile.nextValue(); } } else if (infile.key == "requires_level") { e->x = toInt(infile.nextValue()); } else if (infile.key == "requires_not_level") { e->x = toInt(infile.nextValue()); } else if (infile.key == "requires_item") { e->x = toInt(infile.nextValue()); // add repeating requires_item std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->x = toInt(repeat_val); repeat_val = infile.nextValue(); } } else if (infile.key == "set_status") { e->s = infile.nextValue(); // add repeating set_status std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; repeat_val = infile.nextValue(); } } else if (infile.key == "unset_status") { e->s = infile.nextValue(); // add repeating unset_status std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; repeat_val = infile.nextValue(); } } else if (infile.key == "remove_item") { e->x = toInt(infile.nextValue()); // add repeating remove_item std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->x = toInt(repeat_val); repeat_val = infile.nextValue(); } } else if (infile.key == "reward_xp") { e->x = toInt(infile.val); } else if (infile.key == "power") { e->x = toInt(infile.val); } else if (infile.key == "spawn") { e->s = infile.nextValue(); e->x = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; e->y = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; // add repeating spawn std::string repeat_val = infile.nextValue(); while (repeat_val != "") { events.back().components.push_back(Event_Component()); e = &events.back().components.back(); e->type = infile.key; e->s = repeat_val; e->x = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; e->y = toInt(infile.nextValue()) * UNITS_PER_TILE + UNITS_PER_TILE/2; repeat_val = infile.nextValue(); } } else if (infile.key == "stash") { e->s = infile.val; } else if (infile.key == "npc") { e->s = infile.val; } else if (infile.key == "music") { e->s = infile.val; } else if (infile.key == "cutscene") { e->s = infile.val; } else if (infile.key == "repeat") { e->s = infile.val; } else { fprintf(stderr, "Map: Unknown key value: %s in file %s in section %s\n", infile.key.c_str(), infile.getFileName().c_str(), infile.section.c_str()); } }
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()); } }