void EnemyGroupManager::parseEnemyFilesAndStore() { FileParser infile; if (!infile.open("enemies", true, false)) return; Enemy_Level new_enemy; infile.new_section = true; while (infile.next()) { if (infile.new_section) { const string fname = infile.getFileName(); const int firstpos = fname.rfind("/") + 1; const int len = fname.length() - firstpos - 4; //removes the ".txt" from the filename new_enemy.type = fname.substr(firstpos, len); } if (infile.key == "level") { new_enemy.level = atoi(infile.val.c_str()); } else if (infile.key == "rarity") { new_enemy.rarity = infile.val.c_str(); } else if (infile.key == "categories") { string cat; while ( (cat = infile.nextValue()) != "") { _categories[cat].push_back(new_enemy); } } } infile.close(); }
void EnemyGroupManager::parseEnemyFilesAndStore() { FileParser infile; // @CLASS enemies|Describing enemies files in enemies/ if (!infile.open("enemies", true, false)) return; Enemy_Level new_enemy; infile.new_section = true; bool first = true; while (infile.next()) { if (infile.new_section || first) { const string fname = infile.getFileName(); const int firstpos = fname.rfind("/") + 1; const int len = fname.length() - firstpos - 4; //removes the ".txt" from the filename new_enemy.type = fname.substr(firstpos, len); first = false; } if (infile.key == "level") { // @ATTR level|integer|Level of the enemy new_enemy.level = toInt(infile.val); } else if (infile.key == "rarity") { // @ATTR rarity|[common,uncommon,rare]|Enemy rarity new_enemy.rarity = infile.val; } else if (infile.key == "categories") { // @ATTR categories|string,...|Comma separated list of enemy categories string cat; while ( (cat = infile.nextValue()) != "") { _categories[cat].push_back(new_enemy); } } } infile.close(); }
bool GameStateCutscene::load(std::string filename) { FileParser infile; // @CLASS Cutscene|Description of cutscenes in cutscenes/ if (!infile.open(filename)) return false; // parse the cutscene file while (infile.next()) { if (infile.new_section) { if (infile.section == "scene") scenes.push(Scene()); } if (infile.section.empty()) { // allow having an empty section (globals such as scale_gfx might be set here } else if (infile.section == "scene") { SceneComponent sc = SceneComponent(); if (infile.key == "caption") { // @ATTR scene.caption|string|A caption that will be shown. sc.type = infile.key; sc.s = msg->get(infile.val); } else if (infile.key == "image") { // @ATTR scene.image|string|An image that will be shown. sc.type = infile.key; sc.s = infile.val; } else if (infile.key == "pause") { // @ATTR scene.pause|duration|Pause before next component sc.type = infile.key; sc.x = parse_duration(infile.val); } else if (infile.key == "soundfx") { // @ATTR scene.soundfx|string|A sound that will be played sc.type = infile.key; sc.s = infile.val; } if (sc.type != "") scenes.back().components.push(sc); } else { fprintf(stderr, "unknown section %s in file %s\n", infile.section.c_str(), infile.getFileName().c_str()); } if (infile.key == "scale_gfx") { // @ATTR scale_gfx|bool|The graphics will be scaled to fit screen width scale_graphics = toBool(infile.val); } else if (infile.key == "caption_margins") { // @ATTR caption_margins|[x,y]|Percentage-based margins for the caption text based on screen size caption_margins.x = toFloat(infile.nextValue())/100.0f; caption_margins.y = toFloat(infile.val)/100.0f; } } if (scenes.empty()) { fprintf(stderr, "No scenes defined in cutscene file %s\n", filename.c_str()); return false; } return true; }
void AnimationSet::load() { assert(!loaded); loaded = true; FileParser parser; if (!parser.open(name, true, true, "Error loading animation definition: " + name)) return; string _name = ""; int position = 0; int frames = 0; int duration = 0; Point render_size; Point render_offset; string type = ""; string starting_animation = ""; bool first_section=true; bool compressed_loading=false; // is reset every section to false, set by frame keyword Animation *newanim = NULL; vector<short> active_frames; // Parse the file and on each new section create an animation object from the data parsed previously parser.next(); do { // create the animation if finished parsing a section if (parser.new_section) { if (!first_section && !compressed_loading) { Animation *a = new Animation(_name, type, sprite); a->setupUncompressed(render_size, render_offset, position, frames, duration); if (!active_frames.empty()) a->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(a); } first_section = false; compressed_loading = false; } if (parser.key == "image") { if (sprite) { printf("multiple images specified in %s, dragons be here!\n", name.c_str()); SDL_Quit(); exit(128); } imagefile = parser.val; imag->increaseCount(imagefile); sprite = imag->getSurface(imagefile); } else if (parser.key == "position") { position = toInt(parser.val); } else if (parser.key == "frames") { frames = toInt(parser.val); } else if (parser.key == "duration") { duration = toInt(parser.val); // TEMP: if an animation is too fast, display one frame per fps anyway if (duration < 1) duration=1; } else if (parser.key == "type") type = parser.val; else if (parser.key == "render_size") { render_size.x = toInt(parser.nextValue()); render_size.y = toInt(parser.nextValue()); } else if (parser.key == "render_offset") { render_offset.x = toInt(parser.nextValue()); render_offset.y = toInt(parser.nextValue()); } else if (parser.key == "active_frame") { active_frames.clear(); string nv = parser.nextValue(); if (nv == "all") { active_frames.push_back(-1); } else { while (nv != "") { active_frames.push_back(toInt(nv)); nv = parser.nextValue(); } sort(active_frames.begin(), active_frames.end()); active_frames.erase(unique(active_frames.begin(), active_frames.end()), active_frames.end()); } } else if (parser.key == "frame") { if (compressed_loading == false) { // first frame statement in section newanim = new Animation(_name, type, sprite); newanim->setup(frames, duration); if (!active_frames.empty()) newanim->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(newanim); compressed_loading = true; } // frame = index, direction, x, y, w, h, offsetx, offsety SDL_Rect r; Point offset; const int index = toInt(parser.nextValue()); const int direction = toInt(parser.nextValue()); r.x = toInt(parser.nextValue()); r.y = toInt(parser.nextValue()); r.w = toInt(parser.nextValue()); r.h = toInt(parser.nextValue()); offset.x = toInt(parser.nextValue()); offset.y = toInt(parser.nextValue()); newanim->addFrame(index, direction, r, offset); } else { fprintf(stderr, "animations definitions (%s): Key %s not supported!\n", parser.getFileName().c_str(), parser.key.c_str()); } if (_name == "") { // This is the first animation starting_animation = parser.section; } _name = parser.section; } while (parser.next()); if (!compressed_loading) { // add final animation Animation *a = new Animation(_name, type, sprite); a->setupUncompressed(render_size, render_offset, position, frames, duration); if (!active_frames.empty()) a->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(a); } if (starting_animation != "") { Animation *a = getAnimation(starting_animation); delete defaultAnimation; defaultAnimation = a; } }
void GameStatePlay::loadTitles() { FileParser infile; if (infile.open("engine/titles.txt")) { while (infile.next()) { if (infile.new_section && infile.section == "title") { Title t; titles.push_back(t); } if (titles.empty()) continue; if (infile.key == "title") titles.back().title = infile.val; else if (infile.key == "level") titles.back().level = toInt(infile.val); else if (infile.key == "power") titles.back().power = toInt(infile.val); else if (infile.key == "requires_status") titles.back().requires_status = infile.val; else if (infile.key == "requires_not_status") titles.back().requires_not = infile.val; else if (infile.key == "primary_stat") titles.back().primary_stat = infile.val; else fprintf(stderr, "GameStatePlay: Unknown key value in title definitons: %s in file %s in section %s\n", infile.key.c_str(), infile.getFileName().c_str(), infile.section.c_str()); } infile.close(); } }
void PowerManager::loadPowers() { FileParser infile; // @CLASS Power|Description about powers... if (!infile.open("powers/powers.txt", true, false)) return; int input_id = 0; bool skippingEntry = false; while (infile.next()) { // id needs to be the first component of each power. That is how we write // data to the correct power. if (infile.key == "id") { // @ATTR id|integer|Uniq identifier for the power definition. input_id = toInt(infile.val); skippingEntry = input_id < 1; if (skippingEntry) fprintf(stderr, "Power index out of bounds 1-%d, skipping\n", INT_MAX); if (static_cast<int>(powers.size()) < input_id + 1) powers.resize(input_id + 1); continue; } if (skippingEntry) continue; if (infile.key == "type") { // @ATTR type|[fixed:missile:repeater:spawn:transform]|Defines the type of power definiton if (infile.val == "fixed") powers[input_id].type = POWTYPE_FIXED; else if (infile.val == "missile") powers[input_id].type = POWTYPE_MISSILE; else if (infile.val == "repeater") powers[input_id].type = POWTYPE_REPEATER; else if (infile.val == "spawn") powers[input_id].type = POWTYPE_SPAWN; else if (infile.val == "transform") powers[input_id].type = POWTYPE_TRANSFORM; else fprintf(stderr, "unknown type %s\n", infile.val.c_str()); } else if (infile.key == "name") // @ATTR name|string|The name of the power powers[input_id].name = msg->get(infile.val); else if (infile.key == "description") // @ATTR description|string|Description of the power powers[input_id].description = msg->get(infile.val); else if (infile.key == "icon") // @ATTR icon|string|The icon to visually represent the power eg. in skill tree or action bar. powers[input_id].icon = toInt(infile.val); else if (infile.key == "new_state") { // @ATTR new_state|string|When power is used, hero or enemy will change to this state. Must be one of the states [block, instant, user defined] if (infile.val == "block") powers[input_id].new_state = POWSTATE_BLOCK; else if (infile.val == "instant") powers[input_id].new_state = POWSTATE_INSTANT; else { powers[input_id].new_state = POWSTATE_ATTACK; powers[input_id].attack_anim = infile.val; } } else if (infile.key == "face") // @ATTR face|bool|Power will make hero or enemy to face the target location. powers[input_id].face = toBool(infile.val); else if (infile.key == "source_type") { // @ATTR source_type|[hero:neutral:enemy]| if (infile.val == "hero") powers[input_id].source_type = SOURCE_TYPE_HERO; else if (infile.val == "neutral") powers[input_id].source_type = SOURCE_TYPE_NEUTRAL; else if (infile.val == "enemy") powers[input_id].source_type = SOURCE_TYPE_ENEMY; else fprintf(stderr, "unknown source_type %s\n", infile.val.c_str()); } else if (infile.key == "beacon") // @ATTR beacon|bool|True if enemy is calling its allies. powers[input_id].beacon = toBool(infile.val); else if (infile.key == "count") // @ATTR count|integer|The count of hazards/effect or spawns to be created by this power. powers[input_id].count = toInt(infile.val); else if (infile.key == "passive") // @ATTR passive|bool|If power is unlocked when the hero or enemy spawns it will be automatically activated. powers[input_id].passive = toBool(infile.val); else if (infile.key == "passive_trigger") { // @ATTR passive_trigger|[on_block:on_hit:on_halfdeath:on_joincombat:on_death]|This will only activate a passive power under a certain condition. if (infile.val == "on_block") powers[input_id].passive_trigger = TRIGGER_BLOCK; else if (infile.val == "on_hit") powers[input_id].passive_trigger = TRIGGER_HIT; else if (infile.val == "on_halfdeath") powers[input_id].passive_trigger = TRIGGER_HALFDEATH; else if (infile.val == "on_joincombat") powers[input_id].passive_trigger = TRIGGER_JOINCOMBAT; else if (infile.val == "on_death") powers[input_id].passive_trigger = TRIGGER_DEATH; else fprintf(stderr, "unknown passive trigger %s\n", infile.val.c_str()); } // power requirements else if (infile.key == "requires_flags") { std::string flag = popFirstString(infile.val); while (flag != "") { powers[input_id].requires_flags.insert(flag); flag = popFirstString(infile.val); } } else if (infile.key == "requires_mp") // @ATTR requires_mp|integer|Restrict power usage to a specified MP level. powers[input_id].requires_mp = toInt(infile.val); else if (infile.key == "requires_hp") // @ATTR requires_hp|integer|Restrict power usage to a specified HP level. powers[input_id].requires_hp = toInt(infile.val); else if (infile.key == "sacrifice") // @ATTR sacrifice|bool| powers[input_id].sacrifice = toBool(infile.val); else if (infile.key == "requires_los") // @ATTR requires_los|bool|Requires a line-of-sight to target. powers[input_id].requires_los = toBool(infile.val); else if (infile.key == "requires_empty_target") // @ATTR requires_empty_target|bool|The power can only be cast when target tile is empty. powers[input_id].requires_empty_target = toBool(infile.val); else if (infile.key == "requires_item") // @ATTR requires_item|item_id|Requires a specific item in inventory. powers[input_id].requires_item = toInt(infile.val); else if (infile.key == "requires_equipped_item") // @ATTR requires_equipped_item|item_id|Requires a specific item to be equipped on hero. powers[input_id].requires_equipped_item = toInt(infile.val); else if (infile.key == "requires_targeting") // @ATTR requires_targeting|bool|Power is only used when targeting using click-to-target. powers[input_id].requires_targeting = toBool(infile.val); else if (infile.key == "cooldown") // @ATTR cooldown|duration|Specify the duration for cooldown of the power. powers[input_id].cooldown = parse_duration(infile.val); // animation info else if (infile.key == "animation") // @ATTR animation|string|The name of power animation. powers[input_id].animation_name = infile.val; else if (infile.key == "soundfx") // @ATTR soundfx|string|Sound effect to play when use of power. powers[input_id].sfx_index = loadSFX(infile.val); else if (infile.key == "directional") // @ATTR directional|bool|The animation sprite sheet contains 8 directions, one per row. powers[input_id].directional = toBool(infile.val); else if (infile.key == "visual_random") // @ATTR visual_random|integer|The animation sprite sheet contains rows of random options powers[input_id].visual_random = toInt(infile.val); else if (infile.key == "visual_option") // @ATTR visual_option|integer|The animation sprite sheet containers rows of similar effects, use a specific option. powers[input_id].visual_option = toInt(infile.val); else if (infile.key == "aim_assist") // @ATTR aim_assist|bool|Power is aim assisted. powers[input_id].aim_assist = toBool(infile.val); else if (infile.key == "speed") // @ATTR speed|integer|The speed of missile hazard, the unit is defined as map units per frame. powers[input_id].speed = toFloat(infile.val) / MAX_FRAMES_PER_SEC; else if (infile.key == "lifespan") // @ATTR lifespan|duration|How long the hazard/animation lasts. powers[input_id].lifespan = parse_duration(infile.val); else if (infile.key == "floor") // @ATTR floor|bool|The hazard is drawn between the background and the object layer. powers[input_id].floor = toBool(infile.val); else if (infile.key == "complete_animation") // @ATTR complete_animation|bool| powers[input_id].complete_animation = toBool(infile.val); // hazard traits else if (infile.key == "use_hazard") // @ATTR use_hazard|bool|Power uses hazard. powers[input_id].use_hazard = toBool(infile.val); else if (infile.key == "no_attack") // @ATTR no_attack|bool| powers[input_id].no_attack = toBool(infile.val); else if (infile.key == "radius") // @ATTR radius|integer|Radius in pixels powers[input_id].radius = toFloat(infile.val); else if (infile.key == "base_damage") { // @ATTR base_damage|[melee:ranged:ment]| if (infile.val == "none") powers[input_id].base_damage = BASE_DAMAGE_NONE; else if (infile.val == "melee") powers[input_id].base_damage = BASE_DAMAGE_MELEE; else if (infile.val == "ranged") powers[input_id].base_damage = BASE_DAMAGE_RANGED; else if (infile.val == "ment") powers[input_id].base_damage = BASE_DAMAGE_MENT; else fprintf(stderr, "unknown base_damage %s\n", infile.val.c_str()); } else if (infile.key == "starting_pos") { // @ATTR starting_pos|[source, target, melee]|Start position for hazard if (infile.val == "source") powers[input_id].starting_pos = STARTING_POS_SOURCE; else if (infile.val == "target") powers[input_id].starting_pos = STARTING_POS_TARGET; else if (infile.val == "melee") powers[input_id].starting_pos = STARTING_POS_MELEE; else fprintf(stderr, "unknown starting_pos %s\n", infile.val.c_str()); } else if (infile.key == "multitarget") // @ATTR multitarget|bool| powers[input_id].multitarget = toBool(infile.val); else if (infile.key == "trait_armor_penetration") // @ATTR trait_armor_penetration|bool| powers[input_id].trait_armor_penetration = toBool(infile.val); else if (infile.key == "trait_avoidance_ignore") // @ATTR trait_avoidance_ignore|bool| powers[input_id].trait_avoidance_ignore = toBool(infile.val); else if (infile.key == "trait_crits_impaired") // @ATTR trait_crits_impaired|bool| powers[input_id].trait_crits_impaired = toInt(infile.val); else if (infile.key == "trait_elemental") { // @ATTR, trait_elemental|string| for (unsigned int i=0; i<ELEMENTS.size(); i++) { if (infile.val == ELEMENTS[i].name) powers[input_id].trait_elemental = i; } } else if (infile.key == "target_range") // @ATTR, target_range|float||The distance from the caster that the power can be activated powers[input_id].target_range = toFloat(infile.nextValue()); //steal effects else if (infile.key == "hp_steal") // @ATTR, hp_steal|integer|Percentage of damage to steal into HP powers[input_id].hp_steal = toInt(infile.val); else if (infile.key == "mp_steal") // @ATTR, mp_steal|integer|Percentage of damage to steal into MP powers[input_id].mp_steal = toInt(infile.val); //missile modifiers else if (infile.key == "missile_angle") // @ATTR missile_angle|integer|Angle of missile powers[input_id].missile_angle = toInt(infile.val); else if (infile.key == "angle_variance") // @ATTR angle_variance|integer|Percentage of variance added to missile angle powers[input_id].angle_variance = toInt(infile.val); else if (infile.key == "speed_variance") // @ATTR speed_variance|integer|Percentage of variance added to missile speed powers[input_id].speed_variance = toFloat(infile.val); //repeater modifiers else if (infile.key == "delay") // @ATTR delay|duration|Delay between repeats powers[input_id].delay = parse_duration(infile.val); // buff/debuff durations else if (infile.key == "transform_duration") // @ATTR transform_duration|duration|Duration for transform powers[input_id].transform_duration = toInt(infile.val); else if (infile.key == "manual_untransform") // @ATTR transform_duration|bool|Force manual untranform powers[input_id].manual_untransform = toBool(infile.val); else if (infile.key == "keep_equipment") // @ATTR keep_equipment|bool|Keep equipment while transformed powers[input_id].keep_equipment = toBool(infile.val); // buffs else if (infile.key == "buff") // @ATTR buff|bool| powers[input_id].buff= toBool(infile.val); else if (infile.key == "buff_teleport") // @ATTR buff_teleport|bool| powers[input_id].buff_teleport = toBool(infile.val); else if (infile.key == "buff_party") // @ATTR buff_part|bool| powers[input_id].buff_party = toBool(infile.val); else if (infile.key == "buff_party_power_id") // @ATTR buff_part_power_id|bool| powers[input_id].buff_party_power_id = toInt(infile.val); else if (infile.key == "post_effect") { // @ATTR post_effect|[effect_id, magnitude (integer), duration (integer)]|Post effect. PostEffect pe; pe.id = popFirstString(infile.val); pe.magnitude = popFirstInt(infile.val); pe.duration = popFirstInt(infile.val); powers[input_id].post_effects.push_back(pe); } // pre and post power effects else if (infile.key == "post_power") // @ATTR post_power|power_id|Post power powers[input_id].post_power = toInt(infile.val); else if (infile.key == "wall_power") // @ATTR wall_power|power_id|Wall power powers[input_id].wall_power = toInt(infile.val); else if (infile.key == "allow_power_mod") // @ATTR allow_power_mod|bool|Allow power modifiers powers[input_id].allow_power_mod = toBool(infile.val); // spawn info else if (infile.key == "spawn_type") // @ATTR spawn_type|string|Type of spawn. powers[input_id].spawn_type = infile.val; else if (infile.key == "target_neighbor") // @ATTR target_neighbor|int|Neigbor target. powers[input_id].target_neighbor = toInt(infile.val); else if (infile.key == "spawn_limit") { // @ATTR spawn_limit|[fixed:stat:unlimited],stat[physical:mental:offense:defens]| std::string mode = popFirstString(infile.val); if (mode == "fixed") powers[input_id].spawn_limit_mode = SPAWN_LIMIT_MODE_FIXED; else if (mode == "stat") powers[input_id].spawn_limit_mode = SPAWN_LIMIT_MODE_STAT; else if (mode == "unlimited") powers[input_id].spawn_limit_mode = SPAWN_LIMIT_MODE_UNLIMITED; else fprintf(stderr, "unknown spawn_limit_mode %s\n", mode.c_str()); if(powers[input_id].spawn_limit_mode != SPAWN_LIMIT_MODE_UNLIMITED) { powers[input_id].spawn_limit_qty = popFirstInt(infile.val); if(powers[input_id].spawn_limit_mode == SPAWN_LIMIT_MODE_STAT) { powers[input_id].spawn_limit_every = popFirstInt(infile.val); std::string stat = popFirstString(infile.val); if (stat == "physical") powers[input_id].spawn_limit_stat = SPAWN_LIMIT_STAT_PHYSICAL; else if (stat == "mental") powers[input_id].spawn_limit_stat = SPAWN_LIMIT_STAT_MENTAL; else if (stat == "offense") powers[input_id].spawn_limit_stat = SPAWN_LIMIT_STAT_OFFENSE; else if (stat == "defense") powers[input_id].spawn_limit_stat = SPAWN_LIMIT_STAT_DEFENSE; else fprintf(stderr, "unknown spawn_limit_stat %s\n", stat.c_str()); } } } else if (infile.key == "spawn_level") { std::string mode = popFirstString(infile.val); if (mode == "default") powers[input_id].spawn_level_mode = SPAWN_LEVEL_MODE_DEFAULT; else if (mode == "fixed") powers[input_id].spawn_level_mode = SPAWN_LEVEL_MODE_FIXED; else if (mode == "stat") powers[input_id].spawn_level_mode = SPAWN_LEVEL_MODE_STAT; else if (mode == "level") powers[input_id].spawn_level_mode = SPAWN_LEVEL_MODE_LEVEL; else fprintf(stderr, "unknown spawn_level_mode %s\n", mode.c_str()); if(powers[input_id].spawn_level_mode != SPAWN_LEVEL_MODE_DEFAULT) { powers[input_id].spawn_level_qty = popFirstInt(infile.val); if(powers[input_id].spawn_level_mode != SPAWN_LEVEL_MODE_FIXED) { powers[input_id].spawn_level_every = popFirstInt(infile.val); if(powers[input_id].spawn_level_mode == SPAWN_LEVEL_MODE_STAT) { std::string stat = popFirstString(infile.val); if (stat == "physical") powers[input_id].spawn_level_stat = SPAWN_LEVEL_STAT_PHYSICAL; else if (stat == "mental") powers[input_id].spawn_level_stat = SPAWN_LEVEL_STAT_MENTAL; else if (stat == "offense") powers[input_id].spawn_level_stat = SPAWN_LEVEL_STAT_OFFENSE; else if (stat == "defense") powers[input_id].spawn_level_stat = SPAWN_LEVEL_STAT_DEFENSE; else fprintf(stderr, "unknown spawn_level_stat %s\n", stat.c_str()); } } } } else if (infile.key == "target_party") // @ATTR target_party|bool| powers[input_id].target_party = toBool(infile.val); else if (infile.key == "target_categories") { // @ATTR target_categories|string,...| string cat; while ((cat = infile.nextValue()) != "") { powers[input_id].target_categories.push_back(cat); } } else if (infile.key == "modifier_accuracy") { // @ATTR modifier_accuracy|[multiply:add:absolute], integer| std::string mode = popFirstString(infile.val); if(mode == "multiply") powers[input_id].mod_accuracy_mode = STAT_MODIFIER_MODE_MULTIPLY; else if(mode == "add") powers[input_id].mod_accuracy_mode = STAT_MODIFIER_MODE_ADD; else if(mode == "absolute") powers[input_id].mod_accuracy_mode = STAT_MODIFIER_MODE_ABSOLUTE; else fprintf(stderr, "unknown stat_modifier_mode %s\n", mode.c_str()); powers[input_id].mod_accuracy_value = popFirstInt(infile.val); } else if (infile.key == "modifier_damage") { // @ATTR modifier_damage|[multiply:add:absolute], integer| std::string mode = popFirstString(infile.val); if(mode == "multiply") powers[input_id].mod_damage_mode = STAT_MODIFIER_MODE_MULTIPLY; else if(mode == "add") powers[input_id].mod_damage_mode = STAT_MODIFIER_MODE_ADD; else if(mode == "absolute") powers[input_id].mod_damage_mode = STAT_MODIFIER_MODE_ABSOLUTE; else fprintf(stderr, "unknown stat_modifier_mode %s\n", mode.c_str()); powers[input_id].mod_damage_value_min = popFirstInt(infile.val); powers[input_id].mod_damage_value_max = popFirstInt(infile.val); } else if (infile.key == "modifier_critical") { // @ATTR modifier_critical|[multiply:add:absolute], integer| std::string mode = popFirstString(infile.val); if(mode == "multiply") powers[input_id].mod_crit_mode = STAT_MODIFIER_MODE_MULTIPLY; else if(mode == "add") powers[input_id].mod_crit_mode = STAT_MODIFIER_MODE_ADD; else if(mode == "absolute") powers[input_id].mod_crit_mode = STAT_MODIFIER_MODE_ABSOLUTE; else fprintf(stderr, "unknown stat_modifier_mode %s\n", mode.c_str()); powers[input_id].mod_crit_value = popFirstInt(infile.val); } else fprintf(stderr, "ignoring unknown key %s set to %s in file %s\n", infile.key.c_str(), infile.val.c_str(), infile.getFileName().c_str()); } infile.close(); }
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)]|Play a sound at 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,x(integer),y(integer),drop_chance([fixed:chance(integer)]),quantity_min(integer),quantity_max(integer)],...|Add loot to the event e->s = infile.nextValue(); e->x = toInt(infile.nextValue()); e->y = toInt(infile.nextValue()); // 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 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()); 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") { // @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|integer| e->x = toInt(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|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 == "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 specified enemies 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|string| e->s = infile.val; } else if (infile.key == "npc") { // @ATTR event.npc|string| 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. e->s = infile.val; } else if (infile.key == "repeat") { // @ATTR event.repeat|string| e->s = infile.val; } else { fprintf(stderr, "EventManager: 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::loadEvent(FileParser &infile, Event* evnt) { if (!evnt) return; if (infile.key == "type") { // @ATTR event.type|[on_trigger:on_mapexit:on_leave:on_load:on_clear]|Type of map event. std::string type = infile.val; evnt->type = type; if (type == "on_trigger"); else if (type == "on_mapexit"); // no need to set keep_after_trigger to false correctly, it's ignored anyway else if (type == "on_leave"); else if (type == "on_load") { evnt->keep_after_trigger = false; } else if (type == "on_clear") { evnt->keep_after_trigger = false; } else { fprintf(stderr, "EventManager: Loading event in file %s\nEvent type %s unknown, change to \"on_trigger\" to suppress this warning.\n", infile.getFileName().c_str(), type.c_str()); } } else if (infile.key == "location") { // @ATTR event.location|[x,y,w,h]|Defines the location area for the event. evnt->location.x = toInt(infile.nextValue()); evnt->location.y = toInt(infile.nextValue()); evnt->location.w = toInt(infile.nextValue()); evnt->location.h = toInt(infile.nextValue()); evnt->center.x = evnt->location.x + (float)evnt->location.w/2; evnt->center.y = evnt->location.y + (float)evnt->location.h/2; } else if (infile.key == "hotspot") { // @ATTR event.hotspot|[ [x, y, w, h] : location ]|Event uses location as hotspot or defined by rect. if (infile.val == "location") { evnt->hotspot.x = evnt->location.x; evnt->hotspot.y = evnt->location.y; evnt->hotspot.w = evnt->location.w; evnt->hotspot.h = evnt->location.h; } else { evnt->hotspot.x = toInt(infile.nextValue()); evnt->hotspot.y = toInt(infile.nextValue()); evnt->hotspot.w = toInt(infile.nextValue()); evnt->hotspot.h = toInt(infile.nextValue()); } } else if (infile.key == "cooldown") { // @ATTR event.cooldown|duration|Duration for event cooldown. evnt->cooldown = parse_duration(infile.val); } else if (infile.key == "reachable_from") { // @ATTR event.reachable_from|[x,y,w,h]|If the hero is inside this rectangle, they can activate the event. evnt->reachable_from.x = toInt(infile.nextValue()); evnt->reachable_from.y = toInt(infile.nextValue()); evnt->reachable_from.w = toInt(infile.nextValue()); evnt->reachable_from.h = toInt(infile.nextValue()); } else { loadEventComponent(infile, evnt, NULL); } }
void AnimationSet::load() { assert(!loaded); loaded = true; FileParser parser; // @CLASS Animation|Description of animations in animations/ if (!parser.open(name, true, "Error loading animation definition: " + name)) return; string _name = ""; int position = 0; int frames = 0; int duration = 0; Point render_size; Point render_offset; string type = ""; string starting_animation = ""; bool first_section=true; bool compressed_loading=false; // is reset every section to false, set by frame keyword Animation *newanim = NULL; vector<short> active_frames; // Parse the file and on each new section create an animation object from the data parsed previously while (parser.next()) { // create the animation if finished parsing a section if (parser.new_section) { if (!first_section && !compressed_loading) { Animation *a = new Animation(_name, type, sprite); a->setupUncompressed(render_size, render_offset, position, frames, duration); if (!active_frames.empty()) a->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(a); } first_section = false; compressed_loading = false; } if (parser.key == "image") { // @ATTR image|string|Filename of sprite-sheet image. if (sprite != NULL) { printf("multiple images specified in %s, dragons be here!\n", name.c_str()); SDL_Quit(); exit(128); } sprite = render_device->loadImage(parser.val); } else if (parser.key == "position") { // @ATTR position|integer|Number of frames to the right to use as the first frame. Unpacked animations only. position = toInt(parser.val); } else if (parser.key == "frames") { // @ATTR frames|integer|The total number of frames frames = toInt(parser.val); } else if (parser.key == "duration") { // @ATTR duration|integer|The duration of each frame. duration = parse_duration(parser.val); } else if (parser.key == "type") // @ATTR type|[play_once, back_forth, looped]|How to loop (or not loop) this animation. type = parser.val; else if (parser.key == "render_size") { // @ATTR render_size|w (integer), h (integer)|Width and height of animation. render_size.x = toInt(parser.nextValue()); render_size.y = toInt(parser.nextValue()); } else if (parser.key == "render_offset") { // @ATTR render_offset|x (integer), y (integer)|Render x/y offset. render_offset.x = toInt(parser.nextValue()); render_offset.y = toInt(parser.nextValue()); } else if (parser.key == "active_frame") { // @ATTR active_frame|[all:frame (integer), ...]|A list of frames marked as "active". Also, "all" can be used to mark all frames as active. active_frames.clear(); string nv = parser.nextValue(); if (nv == "all") { active_frames.push_back(-1); } else { while (nv != "") { active_frames.push_back(toInt(nv)); nv = parser.nextValue(); } sort(active_frames.begin(), active_frames.end()); active_frames.erase(unique(active_frames.begin(), active_frames.end()), active_frames.end()); } } else if (parser.key == "frame") { // @ATTR frame|index (integer), direction (integer), x (integer), y (integer), w (integer), h (integer), x offset (integer), y offset (integer)|A single frame of a compressed animation. if (compressed_loading == false) { // first frame statement in section newanim = new Animation(_name, type, sprite); newanim->setup(frames, duration); if (!active_frames.empty()) newanim->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(newanim); compressed_loading = true; } // frame = index, direction, x, y, w, h, offsetx, offsety Rect r; Point offset; const int index = toInt(parser.nextValue()); const int direction = toInt(parser.nextValue()); r.x = toInt(parser.nextValue()); r.y = toInt(parser.nextValue()); r.w = toInt(parser.nextValue()); r.h = toInt(parser.nextValue()); offset.x = toInt(parser.nextValue()); offset.y = toInt(parser.nextValue()); newanim->addFrame(index, direction, r, offset); } else { fprintf(stderr, "animations definitions (%s): Key %s not supported!\n", parser.getFileName().c_str(), parser.key.c_str()); } if (_name == "") { // This is the first animation starting_animation = parser.section; } _name = parser.section; } if (!compressed_loading) { // add final animation Animation *a = new Animation(_name, type, sprite); a->setupUncompressed(render_size, render_offset, position, frames, duration); if (!active_frames.empty()) a->setActiveFrames(active_frames); active_frames.clear(); animations.push_back(a); } if (starting_animation != "") { Animation *a = getAnimation(starting_animation); delete defaultAnimation; defaultAnimation = a; } }
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 Map::loadEvent(FileParser &infile) { if (infile.key == "type") { std::string type = infile.val; events.back().type = type; if (type == "on_trigger"); else if (type == "on_mapexit"); // no need to set keep_after_trigger to false correctly, it's ignored anyway else if (type == "on_leave"); else if (type == "on_load") { events.back().keep_after_trigger = false; } else if (type == "on_clear") { events.back().keep_after_trigger = false; } else { fprintf(stderr, "Map: Loading event in file %s\nEvent type %s unknown, change to \"on_trigger\" to suppress this warning.\n", infile.getFileName().c_str(), type.c_str()); } } else if (infile.key == "location") { events.back().location.x = toInt(infile.nextValue()); events.back().location.y = toInt(infile.nextValue()); events.back().location.w = toInt(infile.nextValue()); events.back().location.h = toInt(infile.nextValue()); } else if (infile.key == "hotspot") { if (infile.val == "location") { events.back().hotspot.x = events.back().location.x; events.back().hotspot.y = events.back().location.y; events.back().hotspot.w = events.back().location.w; events.back().hotspot.h = events.back().location.h; } else { events.back().hotspot.x = toInt(infile.nextValue()); events.back().hotspot.y = toInt(infile.nextValue()); events.back().hotspot.w = toInt(infile.nextValue()); events.back().hotspot.h = toInt(infile.nextValue()); } } else if (infile.key == "cooldown") { events.back().cooldown = parse_duration(infile.val); } else { loadEventComponent(infile); } }