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(); }
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(); }
/** * 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]); } }
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(); }
/** * 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; }
/** * 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]); }
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(); } }
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()); } }
/** * 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(); }
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()); } }