void MenuBook::loadText(FileParser &infile) { // @ATTR text.text_pos|x (integer), y (integer), w (integer), [left:center:right]|Position of the text. if (infile.key == "text_pos") { size.back().x = popFirstInt(infile.val); size.back().y = popFirstInt(infile.val); size.back().w = popFirstInt(infile.val); std::string _justify = popFirstString(infile.val); if (_justify == "left") justify.back() = JUSTIFY_LEFT; else if (_justify == "center") justify.back() = JUSTIFY_CENTER; else if (_justify == "right") justify.back() = JUSTIFY_RIGHT; } // @ATTR text.text_font|r (integer), g (integer), b (integer), style (string)|Font color and style. else if (infile.key == "text_font") { Color color; color.r = popFirstInt(infile.val); color.g = popFirstInt(infile.val); color.b = popFirstInt(infile.val); textColor.back() = color; textFont.back() = popFirstString(infile.val); } // @ATTR text.text|string|The text to be displayed. else if (infile.key == "text") { textData.back() = infile.val; // remove comma from the end textData.back() = textData.back().substr(0, textData.back().length() - 1); } else { infile.error("MenuBook: '%s' is not a valid key.", infile.key.c_str()); } }
/** * This is used in menus (e.g. MenuInventory) when parsing their config files */ LabelInfo eatLabelInfo(std::string val) { LabelInfo info; std::string justify,valign,style; std::string tmp = popFirstString(val); if (tmp == "hidden") { info.hidden = true; } else { info.hidden = false; info.x = toInt(tmp); info.y = popFirstInt(val); justify = popFirstString(val); valign = popFirstString(val); style = popFirstString(val); if (justify == "left") info.justify = JUSTIFY_LEFT; else if (justify == "center") info.justify = JUSTIFY_CENTER; else if (justify == "right") info.justify = JUSTIFY_RIGHT; if (valign == "top") info.valign = VALIGN_TOP; else if (valign == "center") info.valign = VALIGN_CENTER; else if (valign == "bottom") info.valign = VALIGN_BOTTOM; if (style != "") info.font_style = style; } return info; }
SDLFontEngine::SDLFontEngine() : FontEngine(), active_font(NULL) { // Initiate SDL_ttf if(!TTF_WasInit() && TTF_Init()==-1) { logError("SDLFontEngine: TTF_Init: %s", TTF_GetError()); Exit(2); } // load the fonts // @CLASS SDLFontEngine: Font settings|Description of engine/font_settings.txt FileParser infile; if (infile.open("engine/font_settings.txt")) { while (infile.next()) { if (infile.new_section) { SDLFontStyle f; f.name = infile.section; font_styles.push_back(f); } if (font_styles.empty()) continue; SDLFontStyle *style = &(font_styles.back()); if ((infile.key == "default" && style->path == "") || infile.key == LANGUAGE) { // @ATTR $STYLE.default, $STYLE.$LANGUAGE|filename (string), point size (integer), blending (boolean)|Filename, point size, and blend mode of the font to use for this language. $STYLE can be something like "font_normal" or "font_bold". $LANGUAGE can be a 2-letter region code. style->path = popFirstString(infile.val); style->ptsize = popFirstInt(infile.val); style->blend = toBool(popFirstString(infile.val)); style->ttfont = TTF_OpenFont(mods->locate("fonts/" + style->path).c_str(), style->ptsize); if(style->ttfont == NULL) { logError("FontEngine: TTF_OpenFont: %s", TTF_GetError()); } else { int lineskip = TTF_FontLineSkip(style->ttfont); style->line_height = lineskip; style->font_height = lineskip; } } } infile.close(); } // set the font colors Color color; if (infile.open("engine/font_colors.txt")) { while (infile.next()) { // @ATTR menu_normal, menu_bonus, menu_penalty, widget_normal, widget_disabled|r (integer), g (integer), b (integer)|Colors for menus and widgets // @ATTR combat_givedmg, combat_takedmg, combat_crit, combat_buff, combat_miss|r (integer), g (integer), b (integer)|Colors for combat text // @ATTR requirements_not_met, item_bonus, item_penalty, item_flavor|r (integer), g (integer), b (integer)|Colors for tooltips // @ATTR item_$QUALITY|r (integer), g (integer), b (integer)|Colors for item quality. $QUALITY should match qualities used in items/items.txt color_map[infile.key] = toRGB(infile.val); } infile.close(); } // Attempt to set the default active font setFont("font_regular"); if (!active_font) { logError("FontEngine: Unable to determine default font!"); Exit(1); } }
void GameStatePlay::loadTitles() { FileParser infile; // @CLASS GameStatePlay: Titles|Description of engine/titles.txt 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; // @ATTR title.title|string|The displayed title. if (infile.key == "title") titles.back().title = infile.val; // @ATTR title.level|int|Requires level. else if (infile.key == "level") titles.back().level = toInt(infile.val); // @ATTR title.power|power_id|Requires power. else if (infile.key == "power") titles.back().power = toInt(infile.val); // @ATTR title.requires_status|string|Requires status. else if (infile.key == "requires_status") titles.back().requires_status = infile.val; // @ATTR title.requires_not_status|string|Requires not status. else if (infile.key == "requires_not_status") titles.back().requires_not = infile.val; // @ATTR title.primary_stat|predefined_string, predefined_string : Primary stat, Lesser primary stat|Required primary stat(s). The lesser stat is optional. else if (infile.key == "primary_stat") { titles.back().primary_stat_1 = popFirstString(infile.val); titles.back().primary_stat_2 = popFirstString(infile.val); } else infile.error("GameStatePlay: '%s' is not a valid key.", infile.key.c_str()); } infile.close(); } }
void MenuBook::loadText(FileParser &infile) { // @ATTR text.text_pos|int, int, int, ["left", "center", "right"] : X, Y, Width, Text justify|Position of the text. if (infile.key == "text_pos") { size.back().x = popFirstInt(infile.val); size.back().y = popFirstInt(infile.val); size.back().w = popFirstInt(infile.val); std::string _justify = popFirstString(infile.val); if (_justify == "left") justify.back() = JUSTIFY_LEFT; else if (_justify == "center") justify.back() = JUSTIFY_CENTER; else if (_justify == "right") justify.back() = JUSTIFY_RIGHT; } // @ATTR text.text_font|color, string : Font color, Font style|Font color and style. else if (infile.key == "text_font") { Color color; color.r = static_cast<Uint8>(popFirstInt(infile.val)); color.g = static_cast<Uint8>(popFirstInt(infile.val)); color.b = static_cast<Uint8>(popFirstInt(infile.val)); textColor.back() = color; textFont.back() = popFirstString(infile.val); } // @ATTR text.text|string|The text to be displayed. else if (infile.key == "text") { // we use substr here to remove the comma from the end textData.back() = msg->get(infile.val.substr(0, infile.val.length() - 1)); } else { infile.error("MenuBook: '%s' is not a valid key.", infile.key.c_str()); } }
void Map::loadNPC(FileParser &infile) { std::string s; if (infile.key == "type") { // @ATTR npc.type|string|(IGNORED BY ENGINE) The "type" field, as used by Tiled and other mapping tools. npcs.back().type = infile.val; } else if (infile.key == "filename") { // @ATTR npc.filename|string|Filename of an NPC definition. npcs.back().id = infile.val; } else if (infile.key == "requires_status") { // @ATTR npc.requires_status|list(string)|Status required for NPC load. There can be multiple states, separated by comma while ( (s = popFirstString(infile.val)) != "") npcs.back().requires_status.push_back(s); } else if (infile.key == "requires_not_status") { // @ATTR npc.requires_not_status|list(string)|Status required to be missing for NPC load. There can be multiple states, separated by comma while ( (s = popFirstString(infile.val)) != "") npcs.back().requires_not_status.push_back(s); } else if (infile.key == "location") { // @ATTR npc.location|point|Location of NPC npcs.back().pos.x = static_cast<float>(popFirstInt(infile.val)) + 0.5f; npcs.back().pos.y = static_cast<float>(popFirstInt(infile.val)) + 0.5f; } else { infile.error("Map: '%s' is not a valid key.", infile.key.c_str()); } }
bool GameStateConfigBase::parseKeyButtons(FileParser &infile) { // @CLASS GameStateConfigBase|Description of menus/config.txt if (infile.key == "button_ok") { // @ATTR button_ok|int, int, alignment : X, Y, Alignment|Position of the "OK" button. int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); ok_button->setBasePos(x, y, a); } else if (infile.key == "button_defaults") { // @ATTR button_defaults|int, int, alignment : X, Y, Alignment|Position of the "Defaults" button. int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); defaults_button->setBasePos(x, y, a); } else if (infile.key == "button_cancel") { // @ATTR button_cancel|int, int, alignment : X, Y, Alignment|Position of the "Cancel" button. int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); cancel_button->setBasePos(x, y, a); } else { return false; } return true; }
Event_Component EventManager::getRandomMapFromFile(const std::string& fname) { // map pool is the same, so pick the next one in the "playlist" if (fname == mapr->intermap_random_filename && !mapr->intermap_random_queue.empty()) { Event_Component ec = mapr->intermap_random_queue.front(); mapr->intermap_random_queue.pop(); return ec; } // starting a new map pool, so clear the queue while (!mapr->intermap_random_queue.empty()) { mapr->intermap_random_queue.pop(); } FileParser infile; std::vector<Event_Component> ec_list; // @CLASS EventManager: Random Map List|Description of maps/random/lists/ if (infile.open(fname)) { while (infile.next()) { // @ATTR map|filename, int, int : Map file, X, Y|Adds a map and optional spawn position to the random list of maps to teleport to. if (infile.key == "map") { Event_Component ec; ec.s = popFirstString(infile.val); if (ec_list.empty() || ec.s != mapr->getFilename()) { ec.x = -1; ec.y = -1; std::string test_x = popFirstString(infile.val); if (!test_x.empty()) { ec.x = toInt(test_x); ec.y = popFirstInt(infile.val); } ec_list.push_back(ec); } } } infile.close(); } if (ec_list.empty()) { mapr->intermap_random_filename = ""; return Event_Component(); } else { mapr->intermap_random_filename = fname; while (!ec_list.empty()) { size_t index = rand() % ec_list.size(); mapr->intermap_random_queue.push(ec_list[index]); ec_list.erase(ec_list.begin() + index); } Event_Component ec = mapr->intermap_random_queue.front(); mapr->intermap_random_queue.pop(); return ec; } }
CursorManager::CursorManager() : cursor_normal(NULL) , cursor_interact(NULL) , cursor_talk(NULL) , cursor_attack(NULL) , cursor_current(NULL) , offset_current(NULL) { Image *graphics; FileParser infile; // @CLASS CursorManager|Description of engine/mouse_cursor.txt if (infile.open("engine/mouse_cursor.txt", true, "")) { while (infile.next()) { if (infile.key == "normal") { // @ATTR normal|string|Filename of an image for the normal cursor. graphics = render_device->loadImage(popFirstString(infile.val)); if (graphics) { cursor_normal = graphics->createSprite(); graphics->unref(); } offset_normal = toPoint(infile.val); } else if (infile.key == "interact") { // @ATTR interact|string|Filename of an image for the object interaction cursor. graphics = render_device->loadImage(popFirstString(infile.val)); if (graphics) { cursor_interact = graphics->createSprite(); graphics->unref(); } offset_interact = toPoint(infile.val); } else if (infile.key == "talk") { // @ATTR talk|string|Filename of an image for the NPC interaction cursor. graphics = render_device->loadImage(popFirstString(infile.val)); if (graphics) { cursor_talk = graphics->createSprite(); graphics->unref(); } offset_talk = toPoint(infile.val); } else if (infile.key == "attack") { // @ATTR attack|string|Filename of an image for the cursor when attacking enemies. graphics = render_device->loadImage(popFirstString(infile.val)); if (graphics) { cursor_attack = graphics->createSprite(); graphics->unref(); } offset_attack = toPoint(infile.val); } else { infile.error("CursorManager: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); } }
void GameSwitcher::loadFPS() { // Load FPS rendering settings FileParser infile; // @CLASS GameSwitcher: FPS counter|Description of menus/fps.txt if (infile.open("menus/fps.txt")) { while(infile.next()) { // @ATTR position|x (integer), y (integer), align (alignment)|Position of the fps counter. if(infile.key == "position") { fps_position.x = popFirstInt(infile.val); fps_position.y = popFirstInt(infile.val); fps_corner = parse_alignment(popFirstString(infile.val)); } // @ATTR color|r (integer), g (integer), b (integer)|Color of the fps counter text. else if(infile.key == "color") { fps_color = toRGB(infile.val); } else { infile.error("GameSwitcher: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); } // this is a dummy string used to approximate the fps position when aligned to the right font->setFont("font_regular"); fps_position.w = font->calc_width("00 fps"); fps_position.h = font->getLineHeight(); // Delete the label object if it exists (we'll recreate this with showFPS()) if (label_fps) { delete label_fps; label_fps = NULL; } }
/** * Take the savefile campaign= and convert to status array */ void CampaignManager::setAll(const std::string& s) { std::string str = s + ','; std::string token; while (str != "") { token = popFirstString(str, ','); if (token != "") this->setStatus(token); } }
/** * Load avatar sprite layer definitions into vector. */ void Avatar::loadLayerDefinitions() { layer_def = std::vector<std::vector<unsigned> >(8, std::vector<unsigned>()); layer_reference_order = std::vector<std::string>(); FileParser infile; // @CLASS Avatar: Hero layers|Description of engine/hero_layers.txt if (infile.open("engine/hero_layers.txt")) { while(infile.next()) { if (infile.key == "layer") { // @ATTR layer|direction (integer), string, ...]|Defines the hero avatar sprite layer unsigned dir = popFirstInt(infile.val); if (dir>7) { infile.error("Avatar: Hero layer direction must be in range [0,7]"); Exit(1); } std::string layer = popFirstString(infile.val); while (layer != "") { // check if already in layer_reference: unsigned ref_pos; for (ref_pos = 0; ref_pos < layer_reference_order.size(); ++ref_pos) if (layer == layer_reference_order[ref_pos]) break; if (ref_pos == layer_reference_order.size()) layer_reference_order.push_back(layer); layer_def[dir].push_back(ref_pos); layer = popFirstString(infile.val); } } else { infile.error("Avatar: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); } // There are the positions of the items relative to layer_reference_order // so if layer_reference_order=main,body,head,off // and we got a layer=3,off,body,head,main // then the layer_def[3] looks like (3,1,2,0) }
void MenuPowers::loadUpgrade(FileParser &infile) { // @ATTR upgrade.id|int|A power id from powers/powers.txt for this upgrade. if (infile.key == "id") { int id = popFirstInt(infile.val); if (id > 0) { skip_section = false; power_cell_upgrade.back().id = (id); } else { skip_section = true; power_cell_upgrade.pop_back(); infile.error("MenuPowers: Power index out of bounds 1-%d, skipping power.", INT_MAX); } return; } if (skip_section) return; // @ATTR upgrade.requires_primary|predefined_string, int : Primary stat name, Required value|Upgrade requires this primary stat to be at least the specificed value. if (infile.key == "requires_primary") { std::string prim_stat = popFirstString(infile.val); size_t prim_stat_index = getPrimaryStatIndex(prim_stat); if (prim_stat_index != PRIMARY_STATS.size()) { power_cell_upgrade.back().requires_primary[prim_stat_index] = toInt(infile.val); } else { infile.error("MenuPowers: '%s' is not a valid primary stat.", prim_stat.c_str()); } } // @ATTR upgrade.requires_point|bool|Upgrade requires a power point to unlock. else if (infile.key == "requires_point") power_cell_upgrade.back().requires_point = toBool(infile.val); // @ATTR upgrade.requires_level|int|Upgrade requires at least this level for the hero. else if (infile.key == "requires_level") power_cell_upgrade.back().requires_level = toInt(infile.val); // @ATTR upgrade.requires_power|int|Upgrade requires another power id. else if (infile.key == "requires_power") power_cell_upgrade.back().requires_power.push_back(toInt(infile.val)); // @ATTR upgrade.visible_requires_status|repeatable(string)|Hide the upgrade if we don't have this campaign status. else if (infile.key == "visible_requires_status") power_cell_upgrade.back().visible_requires_status.push_back(infile.val); // @ATTR upgrade.visible_requires_not_status|repeatable(string)|Hide the upgrade if we have this campaign status. else if (infile.key == "visible_requires_not_status") power_cell_upgrade.back().visible_requires_not.push_back(infile.val); else infile.error("MenuPowers: '%s' is not a valid key.", infile.key.c_str()); }
void MenuBook::loadImage(FileParser &infile) { // @ATTR image.image_pos|x (integer), y (integer)|Position of the image. if (infile.key == "image_pos") { image_dest.back() = toPoint(infile.val); } // @ATTR image.image|string|Filename of the image. else if (infile.key == "image") { Image *graphics; graphics = render_device->loadImage(popFirstString(infile.val)); if (graphics) { image.back() = graphics->createSprite(); graphics->unref(); } } else { infile.error("MenuBook: '%s' is not a valid key.", infile.key.c_str()); } }
EnemyGroupManager::EnemyGroupManager() { std::vector<std::string> enemy_paths = mods->list("enemies", false); for (unsigned i=0; i<enemy_paths.size(); ++i) { FileParser infile; // @CLASS EnemyGroupManager|Description of enemies in enemies/ if (!infile.open(enemy_paths[i])) return; Enemy_Level new_enemy; std::string category_str; infile.new_section = true; bool first = true; while (infile.next()) { if (infile.new_section || first) { new_enemy.type = enemy_paths[i]; first = false; } if (infile.key == "level") { // @ATTR level|int|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|list(predefined_string)|Comma separated list of enemy categories category_str = infile.val; } } infile.close(); std::string cat; while ( (cat = popFirstString(category_str)) != "") { _categories[cat].push_back(new_enemy); } } }
MenuInventory::MenuInventory(StatBlock *_stats) { stats = _stats; MAX_EQUIPPED = 4; MAX_CARRIED = 64; visible = false; setBackground("images/menus/inventory.png"); currency = 0; carried_cols = 4; // default to 4 if menus/inventory.txt::carried_cols not set carried_rows = 4; // default to 4 if menus/inventory.txt::carried_rows not set drag_prev_src = -1; changed_equipment = true; log_msg = ""; show_book = ""; closeButton = new WidgetButton("images/menus/buttons/button_x.png"); // Load config settings FileParser infile; // @CLASS MenuInventory|Description of menus/inventory.txt if (infile.open("menus/inventory.txt")) { while(infile.next()) { if (parseMenuKey(infile.key, infile.val)) continue; // @ATTR close|x (integer), y (integer)|Position of the close button. if(infile.key == "close") { Point pos = toPoint(infile.val); closeButton->setBasePos(pos.x, pos.y); } // @ATTR equipment_slot|x (integer), y (integer), size (integer), slot_type (string)|Position and item type of an equipment slot. else if(infile.key == "equipment_slot") { Rect area; Point pos; pos.x = area.x = popFirstInt(infile.val); pos.y = area.y = popFirstInt(infile.val); area.w = area.h = popFirstInt(infile.val); equipped_area.push_back(area); equipped_pos.push_back(pos); slot_type.push_back(popFirstString(infile.val)); } // @ATTR slot_name|string|The displayed name of the last defined equipment slot. else if(infile.key == "slot_name") slot_desc.push_back(infile.val); // @ATTR carried_area|x (integer), y (integer)|Position of the first normal inventory slot. else if(infile.key == "carried_area") { Point pos; carried_pos.x = carried_area.x = popFirstInt(infile.val); carried_pos.y = carried_area.y = popFirstInt(infile.val); } // @ATTR carried_cols|integer|The number of columns for the normal inventory. else if (infile.key == "carried_cols") carried_cols = std::max(1, toInt(infile.val)); // @ATTR carried_rows|integer|The number of rows for the normal inventory. else if (infile.key == "carried_rows") carried_rows = std::max(1, toInt(infile.val)); // @ATTR label_title|label|Position of the "Inventory" label. else if (infile.key == "label_title") title = eatLabelInfo(infile.val); // @ATTR currency|label|Position of the label that displays the total currency being carried. else if (infile.key == "currency") currency_lbl = eatLabelInfo(infile.val); // @ATTR help|x (integer), y (integer), w (integer), h (integer)|A mouse-over area that displays some help text for inventory shortcuts. else if (infile.key == "help") help_pos = toRect(infile.val); else infile.error("MenuInventory: '%s' is not a valid key.", infile.key.c_str()); } infile.close(); } MAX_EQUIPPED = equipped_area.size(); MAX_CARRIED = carried_cols * carried_rows; carried_area.w = carried_cols*ICON_SIZE; carried_area.h = carried_rows*ICON_SIZE; color_normal = font->getColor("menu_normal"); color_high = font->getColor("menu_bonus"); inventory[EQUIPMENT].init(MAX_EQUIPPED, equipped_area, slot_type); inventory[CARRIED].init(MAX_CARRIED, carried_area, ICON_SIZE, carried_cols); for (int i = 0; i < MAX_EQUIPPED; i++) { tablist.add(inventory[EQUIPMENT].slots[i]); } for (int i = 0; i < MAX_CARRIED; i++) { tablist.add(inventory[CARRIED].slots[i]); } align(); }
void MenuPowers::loadPower(FileParser &infile) { // @ATTR power.id|int|A power id from powers/powers.txt for this slot. if (infile.key == "id") { int id = popFirstInt(infile.val); if (id > 0) { skip_section = false; power_cell.back().id = id; } else { infile.error("MenuPowers: Power index out of bounds 1-%d, skipping power.", INT_MAX); } return; } if (power_cell.back().id <= 0) { skip_section = true; power_cell.pop_back(); slots.pop_back(); upgradeButtons.pop_back(); logError("MenuPowers: There is a power without a valid id as the first attribute. IDs must be the first attribute in the power menu definition."); } if (skip_section) return; // @ATTR power.tab|int|Tab index to place this power on, starting from 0. if (infile.key == "tab") power_cell.back().tab = toInt(infile.val); // @ATTR power.position|point|Position of this power icon; relative to MenuPowers "pos". else if (infile.key == "position") power_cell.back().pos = toPoint(infile.val); // @ATTR power.requires_primary|predefined_string, int : Primary stat name, Required value|Power requires this primary stat to be at least the specificed value. else if (infile.key == "requires_primary") { std::string prim_stat = popFirstString(infile.val); size_t prim_stat_index = getPrimaryStatIndex(prim_stat); if (prim_stat_index != PRIMARY_STATS.size()) { power_cell.back().requires_primary[prim_stat_index] = toInt(infile.val); } else { infile.error("MenuPowers: '%s' is not a valid primary stat.", prim_stat.c_str()); } } // @ATTR power.requires_point|bool|Power requires a power point to unlock. else if (infile.key == "requires_point") power_cell.back().requires_point = toBool(infile.val); // @ATTR power.requires_level|int|Power requires at least this level for the hero. else if (infile.key == "requires_level") power_cell.back().requires_level = toInt(infile.val); // @ATTR power.requires_power|power_id|Power requires another power id. else if (infile.key == "requires_power") power_cell.back().requires_power.push_back(toInt(infile.val)); // @ATTR power.visible_requires_status|repeatable(string)|Hide the power if we don't have this campaign status. else if (infile.key == "visible_requires_status") power_cell.back().visible_requires_status.push_back(infile.val); // @ATTR power.visible_requires_not_status|repeatable(string)|Hide the power if we have this campaign status. else if (infile.key == "visible_requires_not_status") power_cell.back().visible_requires_not.push_back(infile.val); // @ATTR power.upgrades|list(power_id)|A list of upgrade power ids that this power slot can upgrade to. Each of these powers should have a matching upgrade section. else if (infile.key == "upgrades") { if (power_cell.back().upgrades.empty()) { upgradeButtons.back() = new WidgetButton("images/menus/buttons/button_plus.png"); } std::string repeat_val = popFirstString(infile.val); while (repeat_val != "") { power_cell.back().upgrades.push_back(toInt(repeat_val)); repeat_val = popFirstString(infile.val); } if (!power_cell.back().upgrades.empty()) power_cell.back().upgrade_level = 1; } else infile.error("MenuPowers: '%s' is not a valid key.", infile.key.c_str()); }
SDLFontEngine::SDLFontEngine() : FontEngine(), active_font(NULL) { // Initiate SDL_ttf if(!TTF_WasInit() && TTF_Init()==-1) { logError("SDLFontEngine: TTF_Init: %s", TTF_GetError()); Exit(2); } // load the fonts // @CLASS SDLFontEngine: Font settings|Description of engine/font_settings.txt FileParser infile; if (infile.open("engine/font_settings.txt")) { while (infile.next()) { if (infile.new_section && infile.section == "font") { SDLFontStyle f; f.name = infile.section; font_styles.push_back(SDLFontStyle()); } if (font_styles.empty()) continue; SDLFontStyle *style = &(font_styles.back()); if (infile.key == "id") { // @ATTR font.id|string|An identifier used to reference this font. style->name = infile.val; } else if (infile.key == "style") { // @ATTR font.style|repeatable(["default", predefined_string], filename, int, bool) : Language, Font file, Point size, Blending|Filename, point size, and blend mode of the font to use for this language. Language can be "default" or a 2-letter region code. std::string lang = popFirstString(infile.val); if ((lang == "default" && style->path == "") || lang == LANGUAGE) { style->path = popFirstString(infile.val); style->ptsize = popFirstInt(infile.val); style->blend = toBool(popFirstString(infile.val)); style->ttfont = TTF_OpenFont(mods->locate("fonts/" + style->path).c_str(), style->ptsize); if(style->ttfont == NULL) { logError("FontEngine: TTF_OpenFont: %s", TTF_GetError()); } else { int lineskip = TTF_FontLineSkip(style->ttfont); style->line_height = lineskip; style->font_height = lineskip; } } } } infile.close(); } // set the font colors Color color; if (infile.open("engine/font_colors.txt")) { while (infile.next()) { // @ATTR menu_normal|color|Basic menu text color. Recommended: white. // @ATTR menu_bonus|color|Positive menu text color. Recommended: green. // @ATTR menu_penalty|color|Negative menu text color. Recommended: red. // @ATTR widget_normal|color|Basic widget text color. Recommended: white. // @ATTR widget_disabled|color|Disabled widget text color. Recommended: grey. // @ATTR combat_givedmg|color|Enemy damage text color. Recommended: white. // @ATTR combat_takedmg|color|Player damage text color. Recommended: red. // @ATTR combat_crit|color|Enemy critical damage text color. Recommended: yellow. // @ATTR requirements_no_met|color|Unmet requirements text color. Recommended: red. // @ATTR item_bonus|color|Item bonus text color. Recommended: green. // @ATTR item_penalty|color|Item penalty text color. Recommended: red. // @ATTR item_flavor|color|Item flavor text color. Recommended: grey. color_map[infile.key] = toRGB(infile.val); } infile.close(); } // Attempt to set the default active font setFont("font_regular"); if (!active_font) { logError("FontEngine: Unable to determine default font!"); Exit(1); } }
/** * Key bindings are found in config/keybindings.txt */ void InputState::loadKeyBindings() { FileParser infile; if (!infile.open(PATH_CONF + FILE_KEYBINDINGS, false, "")) { if (!infile.open("engine/default_keybindings.txt", true, "")) { saveKeyBindings(); return; } else saveKeyBindings(); } while (infile.next()) { int key1 = popFirstInt(infile.val); int key2 = popFirstInt(infile.val); // if we're loading an older keybindings file, convert greater than 0 mouse binds to negative if (key1 > 0 && key1 < 8) { key1 = (key1 + MOUSE_BIND_OFFSET) * (-1); } if (key2 > 0 && key2 < 8) { key2 = (key2 + MOUSE_BIND_OFFSET) * (-1); } // if we're loading an older keybindings file, we need to unbind all joystick bindings int key3 = -1; std::string temp = infile.val; if (popFirstString(temp) != "") { key3 = popFirstInt(infile.val); } int cursor = -1; if (infile.key == "cancel") cursor = CANCEL; else if (infile.key == "accept") cursor = ACCEPT; else if (infile.key == "up") cursor = UP; else if (infile.key == "down") cursor = DOWN; else if (infile.key == "left") cursor = LEFT; else if (infile.key == "right") cursor = RIGHT; else if (infile.key == "bar1") cursor = BAR_1; else if (infile.key == "bar2") cursor = BAR_2; else if (infile.key == "bar3") cursor = BAR_3; else if (infile.key == "bar4") cursor = BAR_4; else if (infile.key == "bar5") cursor = BAR_5; else if (infile.key == "bar6") cursor = BAR_6; else if (infile.key == "bar7") cursor = BAR_7; else if (infile.key == "bar8") cursor = BAR_8; else if (infile.key == "bar9") cursor = BAR_9; else if (infile.key == "bar0") cursor = BAR_0; else if (infile.key == "main1") cursor = MAIN1; else if (infile.key == "main2") cursor = MAIN2; else if (infile.key == "character") cursor = CHARACTER; else if (infile.key == "inventory") cursor = INVENTORY; else if (infile.key == "powers") cursor = POWERS; else if (infile.key == "log") cursor = LOG; else if (infile.key == "ctrl") cursor = CTRL; else if (infile.key == "shift") cursor = SHIFT; else if (infile.key == "alt") cursor = ALT; else if (infile.key == "delete") cursor = DEL; else if (infile.key == "actionbar") cursor = ACTIONBAR; else if (infile.key == "actionbar_back") cursor = ACTIONBAR_BACK; else if (infile.key == "actionbar_forward") cursor = ACTIONBAR_FORWARD; else if (infile.key == "actionbar_use") cursor = ACTIONBAR_USE; else if (infile.key == "developer_menu") cursor = DEVELOPER_MENU; if (cursor != -1) { binding[cursor] = key1; binding_alt[cursor] = key2; binding_joy[cursor] = key3; } } infile.close(); }
void setPaths() { // attempting to follow this spec: // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html // set config path (settings, keybindings) // $XDG_CONFIG_HOME/flare/ if (getenv("XDG_CONFIG_HOME") != NULL) { PATH_CONF = (std::string)getenv("XDG_CONFIG_HOME") + "/flare/"; } // $HOME/.config/flare/ else if (getenv("HOME") != NULL) { PATH_CONF = (std::string)getenv("HOME") + "/.config/"; createDir(PATH_CONF); PATH_CONF += "flare/"; } // ./config/ else { PATH_CONF = "./config/"; } createDir(PATH_CONF); // set user path (save games) // $XDG_DATA_HOME/flare/ if (getenv("XDG_DATA_HOME") != NULL) { PATH_USER = (std::string)getenv("XDG_DATA_HOME") + "/flare/"; } // $HOME/.local/share/flare/ else if (getenv("HOME") != NULL) { PATH_USER = (std::string)getenv("HOME") + "/.local/"; createDir(PATH_USER); PATH_USER += "share/"; createDir(PATH_USER); PATH_USER += "flare/"; } // ./saves/ else { PATH_USER = "******"; } createDir(PATH_USER); createDir(PATH_USER + "mods/"); createDir(PATH_USER + "saves/"); // data folder // while PATH_CONF and PATH_USER are created if not found, // PATH_DATA must already have the game data for the game to work. // in most releases the data will be in the same folder as the executable // - Windows apps are released as a simple folder // - OSX apps are released in a .app folder // Official linux distros might put the executable and data files // in a more standard location. // these flags are set to true when a valid directory is found bool path_data = false; // if the user specified a data path, try to use it if (dirExists(CUSTOM_PATH_DATA)) { if (!path_data) PATH_DATA = CUSTOM_PATH_DATA; path_data = true; } else if (!CUSTOM_PATH_DATA.empty()) logError("Settings: Could not find specified game data directory."); // Check for the local data before trying installed ones. if (dirExists("./mods")) { if (!path_data) PATH_DATA = "./"; path_data = true; } // check $XDG_DATA_DIRS options // a list of directories in preferred order separated by : if (getenv("XDG_DATA_DIRS") != NULL) { std::string pathlist = (std::string)getenv("XDG_DATA_DIRS"); std::string pathtest; pathtest = popFirstString(pathlist,':'); while (pathtest != "") { if (!path_data) { PATH_DATA = pathtest + "/flare/"; if (dirExists(PATH_DATA)) path_data = true; } if (path_data) break; pathtest = popFirstString(pathlist,':'); } } #if defined DATA_INSTALL_DIR if (!path_data) PATH_DATA = DATA_INSTALL_DIR "/"; if (!path_data && dirExists(PATH_DATA)) path_data = true; #endif // check /usr/local/share/flare/ and /usr/share/flare/ next if (!path_data) PATH_DATA = "/usr/local/share/flare/"; if (!path_data && dirExists(PATH_DATA)) path_data = true; if (!path_data) PATH_DATA = "/usr/share/flare/"; if (!path_data && dirExists(PATH_DATA)) path_data = true; // check "games" variants of these if (!path_data) PATH_DATA = "/usr/local/share/games/flare/"; if (!path_data && dirExists(PATH_DATA)) path_data = true; if (!path_data) PATH_DATA = "/usr/share/games/flare/"; if (!path_data && dirExists(PATH_DATA)) path_data = true; // finally assume the local folder if (!path_data) PATH_DATA = "./"; }
GameStateLoad::GameStateLoad() : GameState() , background(NULL) , selection(NULL) , portrait_border(NULL) , portrait(NULL) , loading_requested(false) , loading(false) , loaded(false) , delete_items(true) , selected_slot(-1) , visible_slots(0) , scroll_offset(0) , has_scroll_bar(false) , game_slot_max(4) , text_trim_boundary(0) { if (items == NULL) items = new ItemManager(); label_loading = new WidgetLabel(); // Confirmation box to confirm deleting confirm = new MenuConfirm(msg->get("Delete Save"), msg->get("Delete this save?")); button_exit = new WidgetButton(); button_exit->label = msg->get("Exit to Title"); button_new = new WidgetButton(); button_new->label = msg->get("New Game"); button_new->enabled = true; button_load = new WidgetButton(); button_load->label = msg->get("Choose a Slot"); button_load->enabled = false; button_delete = new WidgetButton(); button_delete->label = msg->get("Delete Save"); button_delete->enabled = false; scrollbar = new WidgetScrollBar(); // Set up tab list tablist = TabList(HORIZONTAL); tablist.add(button_exit); tablist.add(button_new); // Read positions from config file FileParser infile; // @CLASS GameStateLoad|Description of menus/gameload.txt if (infile.open("menus/gameload.txt")) { while (infile.next()) { // @ATTR button_new|int, int, alignment : X, Y, Alignment|Position of the "New Game" button. if (infile.key == "button_new") { int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); button_new->setBasePos(x, y, a); } // @ATTR button_load|int, int, alignment : X, Y, Alignment|Position of the "Load Game" button. else if (infile.key == "button_load") { int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); button_load->setBasePos(x, y, a); } // @ATTR button_delete|int, int, alignment : X, Y, Alignment|Position of the "Delete Save" button. else if (infile.key == "button_delete") { int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); button_delete->setBasePos(x, y, a); } // @ATTR button_exit|int, int, alignment : X, Y, Alignment|Position of the "Exit to Title" button. else if (infile.key == "button_exit") { int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); ALIGNMENT a = parse_alignment(popFirstString(infile.val)); button_exit->setBasePos(x, y, a); } // @ATTR portrait|rectangle|Position and dimensions of the portrait image. else if (infile.key == "portrait") { portrait_dest = toRect(infile.val); } // @ATTR gameslot|rectangle|Position and dimensions of the first game slot. else if (infile.key == "gameslot") { gameslot_pos = toRect(infile.val); } // @ATTR name|label|The label for the hero's name. Position is relative to game slot position. else if (infile.key == "name") { name_pos = eatLabelInfo(infile.val); } // @ATTR level|label|The label for the hero's level. Position is relative to game slot position. else if (infile.key == "level") { level_pos = eatLabelInfo(infile.val); } // @ATTR class|label|The label for the hero's class. Position is relative to game slot position. else if (infile.key == "class") { class_pos = eatLabelInfo(infile.val); } // @ATTR map|label|The label for the hero's current location. Position is relative to game slot position. else if (infile.key == "map") { map_pos = eatLabelInfo(infile.val); } // @ATTR slot_number|label|The label for the save slot index. Position is relative to game slot position. else if (infile.key == "slot_number") { slot_number_pos = eatLabelInfo(infile.val); } // @ATTR loading_label|label|The label for the "Entering game world..."/"Loading saved game..." text. else if (infile.key == "loading_label") { loading_pos = eatLabelInfo(infile.val); } // @ATTR sprite|point|Position for the avatar preview image in each slot else if (infile.key == "sprite") { sprites_pos = toPoint(infile.val); } // @ATTR visible_slots|int|The maximum numbers of visible save slots. else if (infile.key == "visible_slots") { game_slot_max = toInt(infile.val); // can't have less than 1 game slot visible game_slot_max = std::max(game_slot_max, 1); } // @ATTR text_trim_boundary|int|The position of the right-side boundary where text will be shortened with an ellipsis. Position is relative to game slot position. else if (infile.key == "text_trim_boundary") { text_trim_boundary = toInt(infile.val); } else { infile.error("GameStateLoad: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); } // prevent text from overflowing on the right edge of game slots if (text_trim_boundary == 0 || text_trim_boundary > gameslot_pos.w) text_trim_boundary = gameslot_pos.w; button_new->refresh(); button_load->refresh(); button_delete->refresh(); loadGraphics(); readGameSlots(); color_normal = font->getColor("menu_normal"); refreshWidgets(); updateButtons(); // if we specified a slot to load at launch, load it now if (!LOAD_SLOT.empty()) { size_t load_slot_id = toInt(LOAD_SLOT) - 1; LOAD_SLOT.clear(); if (load_slot_id < game_slots.size()) { setSelectedSlot(static_cast<int>(load_slot_id)); loading_requested = true; } } render_device->setBackgroundColor(Color(0,0,0,0)); }
/** * Set paths for sound effects */ bool StatBlock::loadSfxStat(FileParser *infile) { // @CLASS StatBlock: Sound effects|Description of heroes in engine/avatar/ and enemies in enemies/ if (infile->new_section) { sfx_attack.clear(); sfx_hit.clear(); sfx_die.clear(); sfx_critdie.clear(); sfx_block.clear(); } if (infile->key == "sfx_attack") { // @ATTR sfx_attack|repeatable(predefined_string, filename) : Animation name, Sound file|Filename of sound effect for the specified attack animation. std::string anim_name = popFirstString(infile->val); std::string filename = popFirstString(infile->val); size_t found_index = sfx_attack.size(); for (size_t i = 0; i < sfx_attack.size(); ++i) { if (anim_name == sfx_attack[i].first) { found_index = i; break; } } if (found_index == sfx_attack.size()) { sfx_attack.push_back(std::pair<std::string, std::vector<std::string> >()); sfx_attack.back().first = anim_name; sfx_attack.back().second.push_back(filename); } else { if (std::find(sfx_attack[found_index].second.begin(), sfx_attack[found_index].second.end(), filename) == sfx_attack[found_index].second.end()) { sfx_attack[found_index].second.push_back(filename); } } } else if (infile->key == "sfx_hit") { // @ATTR sfx_hit|repeatable(filename)|Filename of sound effect for being hit. if (std::find(sfx_hit.begin(), sfx_hit.end(), infile->val) == sfx_hit.end()) { sfx_hit.push_back(infile->val); } } else if (infile->key == "sfx_die") { // @ATTR sfx_die|repeatable(filename)|Filename of sound effect for dying. if (std::find(sfx_die.begin(), sfx_die.end(), infile->val) == sfx_die.end()) { sfx_die.push_back(infile->val); } } else if (infile->key == "sfx_critdie") { // @ATTR sfx_critdie|repeatable(filename)|Filename of sound effect for dying to a critical hit. if (std::find(sfx_critdie.begin(), sfx_critdie.end(), infile->val) == sfx_critdie.end()) { sfx_critdie.push_back(infile->val); } } else if (infile->key == "sfx_block") { // @ATTR sfx_block|repeatable(filename)|Filename of sound effect for blocking an incoming hit. if (std::find(sfx_block.begin(), sfx_block.end(), infile->val) == sfx_block.end()) { sfx_block.push_back(infile->val); } } else if (infile->key == "sfx_levelup") { // @ATTR sfx_levelup|filename|Filename of sound effect for leveling up. sfx_levelup = infile->val; } else { return false; } return true; }
/** * NPCs are stored in simple config files * * @param npc_id Config file for npc */ void NPC::load(const std::string& npc_id) { FileParser infile; ItemStack stack; portrait_filenames.resize(1); // @CLASS NPC|Description of NPCs in npcs/ if (infile.open(npc_id)) { bool clear_random_table = true; while (infile.next()) { if (infile.section == "dialog") { if (infile.new_section) { dialog.push_back(std::vector<Event_Component>()); } Event_Component e; e.type = EC_NONE; if (infile.key == "him" || infile.key == "her") { // @ATTR dialog.him|repeatable(string)|A line of dialog from the NPC. // @ATTR dialog.her|repeatable(string)|A line of dialog from the NPC. e.type = EC_NPC_DIALOG_THEM; e.s = msg->get(infile.val); } else if (infile.key == "you") { // @ATTR dialog.you|repeatable(string)|A line of dialog from the player. e.type = EC_NPC_DIALOG_YOU; e.s = msg->get(infile.val); } else if (infile.key == "voice") { // @ATTR dialog.voice|repeatable(string)|Filename of a voice sound file to play. e.type = EC_NPC_VOICE; e.x = loadSound(infile.val, NPC_VOX_QUEST); } else if (infile.key == "topic") { // @ATTR dialog.topic|string|The name of this dialog topic. Displayed when picking a dialog tree. e.type = EC_NPC_DIALOG_TOPIC; e.s = msg->get(infile.val); } else if (infile.key == "group") { // @ATTR dialog.group|string|Dialog group. e.type = EC_NPC_DIALOG_GROUP; e.s = infile.val; } else if (infile.key == "allow_movement") { // @ATTR dialog.allow_movement|bool|Restrict the player's mvoement during dialog. e.type = EC_NPC_ALLOW_MOVEMENT; e.s = infile.val; } else if (infile.key == "portrait_him" || infile.key == "portrait_her") { // @ATTR dialog.portrait_him|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog. // @ATTR dialog.portrait_her|repeatable(filename)|Filename of a portrait to display for the NPC during this dialog. e.type = EC_NPC_PORTRAIT_THEM; e.s = infile.val; portrait_filenames.push_back(e.s); } else if (infile.key == "portrait_you") { // @ATTR dialog.portrait_you|repeatable(filename)|Filename of a portrait to display for the player during this dialog. e.type = EC_NPC_PORTRAIT_YOU; e.s = infile.val; portrait_filenames.push_back(e.s); } else { Event ev; EventManager::loadEventComponent(infile, &ev, NULL); for (size_t i=0; i<ev.components.size(); ++i) { if (ev.components[i].type != EC_NONE) { dialog.back().push_back(ev.components[i]); } } } if (e.type != EC_NONE) { dialog.back().push_back(e); } } else { filename = npc_id; if (infile.new_section) { // APPENDed file clear_random_table = true; } if (infile.key == "name") { // @ATTR name|string|NPC's name. name = msg->get(infile.val); } else if (infile.key == "gfx") { // @ATTR gfx|filename|Filename of an animation definition. gfx = infile.val; } else if (infile.key == "direction") { // @ATTR direction|direction|The direction to use for this NPC's stance animation. direction = parse_direction(infile.val); } // handle talkers else if (infile.key == "talker") { // @ATTR talker|bool|Allows this NPC to be talked to. talker = toBool(infile.val); } else if (infile.key == "portrait") { // @ATTR portrait|filename|Filename of the default portrait image. portrait_filenames[0] = infile.val; } // handle vendors else if (infile.key == "vendor") { // @ATTR vendor|bool|Allows this NPC to buy/sell items. vendor = toBool(infile.val); } else if (infile.key == "vendor_requires_status") { // @ATTR vendor_requires_status|list(string)|The player must have these statuses in order to use this NPC as a vendor. while (infile.val != "") { vendor_requires_status.push_back(popFirstString(infile.val)); } } else if (infile.key == "vendor_requires_not_status") { // @ATTR vendor_requires_not_status|list(string)|The player must not have these statuses in order to use this NPC as a vendor. while (infile.val != "") { vendor_requires_not_status.push_back(popFirstString(infile.val)); } } else if (infile.key == "constant_stock") { // @ATTR constant_stock|repeatable(list(item_id))|A list of items this vendor has for sale. stack.quantity = 1; while (infile.val != "") { stack.item = popFirstInt(infile.val); stock.add(stack); } } else if (infile.key == "status_stock") { // @ATTR status_stock|repeatable(string, list(item_id)) : Required status, Item(s)|A list of items this vendor will have for sale if the required status is met. if (camp->checkStatus(popFirstString(infile.val))) { stack.quantity = 1; while (infile.val != "") { stack.item = popFirstInt(infile.val); stock.add(stack); } } } else if (infile.key == "random_stock") { // @ATTR random_stock|list(loot)|Use a loot table to add random items to the stock; either a filename or an inline definition. if (clear_random_table) { random_table.clear(); clear_random_table = false; } random_table.push_back(Event_Component()); loot->parseLoot(infile.val, &random_table.back(), &random_table); } else if (infile.key == "random_stock_count") { // @ATTR random_stock_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of random items this npc can have. random_table_count.x = popFirstInt(infile.val); random_table_count.y = popFirstInt(infile.val); if (random_table_count.x != 0 || random_table_count.y != 0) { random_table_count.x = std::max(random_table_count.x, 1); random_table_count.y = std::max(random_table_count.y, random_table_count.x); } } // handle vocals else if (infile.key == "vox_intro") { // @ATTR vox_intro|repeatable(filename)|Filename of a sound file to play when initially interacting with the NPC. loadSound(infile.val, NPC_VOX_INTRO); } else { infile.error("NPC: '%s' is not a valid key.", infile.key.c_str()); } } } infile.close(); } loadGraphics(); // fill inventory with items from random stock table unsigned rand_count = randBetween(random_table_count.x, random_table_count.y); std::vector<ItemStack> rand_itemstacks; for (unsigned i=0; i<rand_count; ++i) { loot->checkLoot(random_table, NULL, &rand_itemstacks); } std::sort(rand_itemstacks.begin(), rand_itemstacks.end(), compareItemStack); for (size_t i=0; i<rand_itemstacks.size(); ++i) { stock.add(rand_itemstacks[i]); } }
void MenuBook::loadBook() { if (book_loaded) return; // Read data from config file FileParser infile; // @CLASS MenuBook|Description of books in books/ if (infile.open(book_name)) { while (infile.next()) { if (parseMenuKey(infile.key, infile.val)) continue; infile.val = infile.val + ','; // @ATTR close|x (integer), y (integer)|Position of the close button. if(infile.key == "close") { int x = popFirstInt(infile.val); int y = popFirstInt(infile.val); closeButton->setBasePos(x, y); } // @ATTR background|string|Filename for the background image. else if (infile.key == "background") { setBackground(popFirstString(infile.val)); } else if (infile.section == "") { infile.error("MenuBook: '%s' is not a valid key.", infile.key.c_str()); } if (infile.new_section) { // for sections that are stored in collections, add a new object here if (infile.section == "text") { text.push_back(NULL); textData.push_back(""); textColor.push_back(Color()); justify.push_back(0); textFont.push_back(""); size.push_back(Rect()); } else if (infile.section == "image") { image.push_back(NULL); image_dest.push_back(Point()); } } if (infile.section == "text") loadText(infile); else if (infile.section == "image") loadImage(infile); } infile.close(); } // setup image dest for (unsigned i=0; i < image.size(); i++) { image[i]->setDest(image_dest[i]); } // render text to surface for (unsigned i=0; i<text.size(); i++) { font->setFont(textFont[i]); Point pSize = font->calc_size(textData[i], size[i].w); Image *graphics = render_device->createImage(size[i].w, pSize.y); if (justify[i] == JUSTIFY_CENTER) font->render(textData[i], size[i].w/2, 0, justify[i], graphics, size[i].w, textColor[i]); else if (justify[i] == JUSTIFY_RIGHT) font->render(textData[i], size[i].w, 0, justify[i], graphics, size[i].w, textColor[i]); else font->render(textData[i], 0, 0, justify[i], graphics, size[i].w, textColor[i]); text[i] = graphics->createSprite(); graphics->unref(); } align(); book_loaded = true; }
void GameStateLoad::readGameSlots() { FileParser infile; std::stringstream filename; std::vector<std::string> save_dirs; getDirList(PATH_USER + "saves/" + SAVE_PREFIX, save_dirs); std::sort(save_dirs.begin(), save_dirs.end(), compareSaveDirs); game_slots.resize(save_dirs.size(), NULL); visible_slots = (game_slot_max > static_cast<int>(game_slots.size()) ? static_cast<int>(game_slots.size()) : game_slot_max); for (size_t i=0; i<save_dirs.size(); ++i){ // save data is stored in slot#/avatar.txt filename.str(""); filename << PATH_USER << "saves/" << SAVE_PREFIX << "/" << save_dirs[i] << "/avatar.txt"; if (!infile.open(filename.str(),false)) continue; game_slots[i] = new GameSlot(); game_slots[i]->id = toInt(save_dirs[i]); while (infile.next()) { // load (key=value) pairs if (infile.key == "name") game_slots[i]->stats.name = infile.val; else if (infile.key == "class") { game_slots[i]->stats.character_class = popFirstString(infile.val); game_slots[i]->stats.character_subclass = popFirstString(infile.val); } else if (infile.key == "xp") game_slots[i]->stats.xp = toInt(infile.val); else if (infile.key == "build") { for (size_t j = 0; j < PRIMARY_STATS.size(); ++j) { game_slots[i]->stats.primary[j] = popFirstInt(infile.val); } } else if (infile.key == "equipped") { std::string repeat_val = popFirstString(infile.val); while (repeat_val != "") { game_slots[i]->equipped.push_back(toInt(repeat_val)); repeat_val = popFirstString(infile.val); } } else if (infile.key == "option") { game_slots[i]->stats.gfx_base = popFirstString(infile.val); game_slots[i]->stats.gfx_head = popFirstString(infile.val); game_slots[i]->stats.gfx_portrait = popFirstString(infile.val); } else if (infile.key == "spawn") { game_slots[i]->current_map = getMapName(popFirstString(infile.val)); } else if (infile.key == "permadeath") { game_slots[i]->stats.permadeath = toBool(infile.val); } } infile.close(); game_slots[i]->stats.recalc(); game_slots[i]->stats.direction = 6; game_slots[i]->preview.setStatBlock(&(game_slots[i]->stats)); loadPreview(game_slots[i]); } }
/** * load a statblock, typically for an enemy definition */ void StatBlock::load(const std::string& filename) { // @CLASS StatBlock: Enemies|Description of enemies in enemies/ FileParser infile; if (!infile.open(filename)) return; bool clear_loot = true; bool flee_range_defined = false; while (infile.next()) { if (infile.new_section) { // APPENDed file clear_loot = true; } int num = toInt(infile.val); float fnum = toFloat(infile.val); bool valid = loadCoreStat(&infile) || loadSfxStat(&infile); // @ATTR name|string|Name if (infile.key == "name") name = msg->get(infile.val); // @ATTR humanoid|bool|This creature gives human traits when transformed into, such as the ability to talk with NPCs. else if (infile.key == "humanoid") humanoid = toBool(infile.val); // @ATTR level|int|Level else if (infile.key == "level") level = num; // enemy death rewards and events // @ATTR xp|int|XP awarded upon death. else if (infile.key == "xp") xp = num; else if (infile.key == "loot") { // @ATTR loot|repeatable(loot)|Possible loot that can be dropped on death. // loot entries format: // loot=[id],[percent_chance] // optionally allow range: // loot=[id],[percent_chance],[count_min],[count_max] if (clear_loot) { loot_table.clear(); clear_loot = false; } loot_table.push_back(Event_Component()); loot->parseLoot(infile.val, &loot_table.back(), &loot_table); } else if (infile.key == "loot_count") { // @ATTR loot_count|int, int : Min, Max|Sets the minimum (and optionally, the maximum) amount of loot this creature can drop. Overrides the global drop_max setting. loot_count.x = popFirstInt(infile.val); loot_count.y = popFirstInt(infile.val); if (loot_count.x != 0 || loot_count.y != 0) { loot_count.x = std::max(loot_count.x, 1); loot_count.y = std::max(loot_count.y, loot_count.x); } } // @ATTR defeat_status|string|Campaign status to set upon death. else if (infile.key == "defeat_status") defeat_status = infile.val; // @ATTR convert_status|string|Campaign status to set upon being converted to a player ally. else if (infile.key == "convert_status") convert_status = infile.val; // @ATTR first_defeat_loot|item_id|Drops this item upon first death. else if (infile.key == "first_defeat_loot") first_defeat_loot = num; // @ATTR quest_loot|string, string, item_id : Required status, Required not status, Item|Drops this item when campaign status is met. else if (infile.key == "quest_loot") { quest_loot_requires_status = popFirstString(infile.val); quest_loot_requires_not_status = popFirstString(infile.val); quest_loot_id = popFirstInt(infile.val); } // behavior stats // @ATTR flying|bool|Creature can move over gaps/water. else if (infile.key == "flying") flying = toBool(infile.val); // @ATTR intangible|bool|Creature can move through walls. else if (infile.key == "intangible") intangible = toBool(infile.val); // @ATTR facing|bool|Creature can turn to face their target. else if (infile.key == "facing") facing = toBool(infile.val); // @ATTR waypoint_pause|duration|Duration to wait at each waypoint in 'ms' or 's'. else if (infile.key == "waypoint_pause") waypoint_pause = parse_duration(infile.val); // @ATTR turn_delay|duration|Duration it takes for this creature to turn and face their target in 'ms' or 's'. else if (infile.key == "turn_delay") turn_delay = parse_duration(infile.val); // @ATTR chance_pursue|int|Percentage change that the creature will chase their target. else if (infile.key == "chance_pursue") chance_pursue = num; // @ATTR chance_flee|int|Percentage chance that the creature will run away from their target. else if (infile.key == "chance_flee") chance_flee = num; else if (infile.key == "power") { // @ATTR power|["melee", "ranged", "beacon", "on_hit", "on_death", "on_half_dead", "on_join_combat", "on_debuff"], power_id, int : State, Power, Chance|A power that has a chance of being triggered in a certain state. AIPower ai_power; std::string ai_type = popFirstString(infile.val); ai_power.id = powers->verifyID(popFirstInt(infile.val), &infile, false); if (ai_power.id == 0) continue; // verifyID() will print our error message ai_power.chance = popFirstInt(infile.val); if (ai_type == "melee") ai_power.type = AI_POWER_MELEE; else if (ai_type == "ranged") ai_power.type = AI_POWER_RANGED; else if (ai_type == "beacon") ai_power.type = AI_POWER_BEACON; else if (ai_type == "on_hit") ai_power.type = AI_POWER_HIT; else if (ai_type == "on_death") ai_power.type = AI_POWER_DEATH; else if (ai_type == "on_half_dead") ai_power.type = AI_POWER_HALF_DEAD; else if (ai_type == "on_join_combat") ai_power.type = AI_POWER_JOIN_COMBAT; else if (ai_type == "on_debuff") ai_power.type = AI_POWER_DEBUFF; else { infile.error("StatBlock: '%s' is not a valid enemy power type.", ai_type.c_str()); continue; } if (ai_power.type == AI_POWER_HALF_DEAD) half_dead_power = true; powers_ai.push_back(ai_power); } else if (infile.key == "passive_powers") { // @ATTR passive_powers|list(power_id)|A list of passive powers this creature has. powers_passive.clear(); std::string p = popFirstString(infile.val); while (p != "") { powers_passive.push_back(toInt(p)); p = popFirstString(infile.val); } } // @ATTR melee_range|float|Minimum distance from target required to use melee powers. else if (infile.key == "melee_range") melee_range = fnum; // @ATTR threat_range|float, float: Engage distance, Stop distance|The first value is the radius of the area this creature will be able to start chasing the hero. The second, optional, value is the radius at which this creature will stop pursuing their target and defaults to double the first value. else if (infile.key == "threat_range") { threat_range = toFloat(popFirstString(infile.val)); std::string tr_far = popFirstString(infile.val); if (!tr_far.empty()) threat_range_far = toFloat(tr_far); else threat_range_far = threat_range * 2; } // @ATTR flee_range|float|The radius at which this creature will start moving to a safe distance. Defaults to half of the threat_range. else if (infile.key == "flee_range") { flee_range = fnum; flee_range_defined = true; } // @ATTR combat_style|["default", "aggressive", "passive"]|How the creature will enter combat. Default is within range of the hero; Aggressive is always in combat; Passive must be attacked to enter combat. else if (infile.key == "combat_style") { if (infile.val == "default") combat_style = COMBAT_DEFAULT; else if (infile.val == "aggressive") combat_style = COMBAT_AGGRESSIVE; else if (infile.val == "passive") combat_style = COMBAT_PASSIVE; else infile.error("StatBlock: Unknown combat style '%s'", infile.val.c_str()); } // @ATTR animations|filename|Filename of an animation definition. else if (infile.key == "animations") animations = infile.val; // @ATTR suppress_hp|bool|Hides the enemy HP bar for this creature. else if (infile.key == "suppress_hp") suppress_hp = toBool(infile.val); else if (infile.key == "categories") { // @ATTR categories|list(string)|Categories that this enemy belongs to. categories.clear(); std::string cat; while ((cat = popFirstString(infile.val)) != "") { categories.push_back(cat); } } // @ATTR flee_duration|duration|The minimum amount of time that this creature will flee. They may flee longer than the specified time. else if (infile.key == "flee_duration") flee_duration = parse_duration(infile.val); // @ATTR flee_cooldown|duration|The amount of time this creature must wait before they can start fleeing again. else if (infile.key == "flee_cooldown") flee_cooldown = parse_duration(infile.val); // this is only used for EnemyGroupManager // we check for them here so that we don't get an error saying they are invalid else if (infile.key == "rarity") ; // but do nothing else if (!valid) { infile.error("StatBlock: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); hp = starting[STAT_HP_MAX]; mp = starting[STAT_MP_MAX]; if (!flee_range_defined) flee_range = threat_range / 2; applyEffects(); }
void StatBlock::loadHeroStats() { // set the default global cooldown cooldown = parse_duration("66ms"); // Redefine numbers from config file if present FileParser infile; // @CLASS StatBlock: Hero stats|Description of engine/stats.txt if (infile.open("engine/stats.txt")) { while (infile.next()) { int value = toInt(infile.val); bool valid = loadCoreStat(&infile); if (infile.key == "max_points_per_stat") { // @ATTR max_points_per_stat|int|Maximum points for each primary stat. max_points_per_stat = value; } else if (infile.key == "sfx_step") { // @ATTR sfx_step|string|An id for a set of step sound effects. See items/step_sounds.txt. sfx_step = infile.val; } else if (infile.key == "stat_points_per_level") { // @ATTR stat_points_per_level|int|The amount of stat points awarded each level. stat_points_per_level = value; } else if (infile.key == "power_points_per_level") { // @ATTR power_points_per_level|int|The amount of power points awarded each level. power_points_per_level = value; } else if (!valid) { infile.error("StatBlock: '%s' is not a valid key.", infile.key.c_str()); } } infile.close(); } if (max_points_per_stat == 0) max_points_per_stat = max_spendable_stat_points / 4 + 1; statsLoaded = true; // load the XP table // @CLASS StatBlock: XP table|Description of engine/xp_table.txt if (infile.open("engine/xp_table.txt")) { while(infile.next()) { if (infile.key == "level") { // @ATTR level|int, int : Level, XP|The amount of XP required for this level. unsigned lvl_id = popFirstInt(infile.val); unsigned long lvl_xp = toUnsignedLong(popFirstString(infile.val)); if (lvl_id > xp_table.size()) xp_table.resize(lvl_id); xp_table[lvl_id - 1] = lvl_xp; } } infile.close(); } if (xp_table.empty()) { logError("StatBlock: No XP table defined."); xp_table.push_back(0); } max_spendable_stat_points = static_cast<int>(xp_table.size()) * stat_points_per_level; }
void MenuDevConsole::execute() { std::string command = input_box->getText(); if (command == "") return; input_scrollback.push_back(command); input_scrollback_pos = input_scrollback.size(); input_box->setText(""); log_history->add(command, false, &color_echo); std::vector<std::string> args; command += ' '; std::string arg = popFirstString(command, ' '); while (arg != "") { args.push_back(arg); arg = popFirstString(command, ' '); } if (args.empty()) { return; } if (args[0] == "help") { log_history->add("teleport - " + msg->get("teleports the player to a specific tile, and optionally, a specific map"), false); log_history->add("unset_status - " + msg->get("unsets the given campaign statuses if they are set"), false); log_history->add("set_status - " + msg->get("sets the given campaign statuses"), false); log_history->add("give_xp - " + msg->get("rewards the player with the specified amount of experience points"), false); log_history->add("give_currency - " + msg->get("adds the specified amount of currency to the player's inventory"), false); log_history->add("give_item - " + msg->get("adds an item to the player's inventory"), false); log_history->add("spawn_enemy - " + msg->get("spawns an enemy matching the given category next to the player"), false); log_history->add("toggle_devhud - " + msg->get("turns on/off the developer hud"), false); log_history->add("clear - " + msg->get("clears the command history"), false); log_history->add("help - " + msg->get("displays this text"), false); } else if (args[0] == "clear") { log_history->clear(); } else if (args[0] == "toggle_devhud") { DEV_HUD = !DEV_HUD; log_history->add(msg->get("Toggled the developer hud"), false); } else if (args[0] == "spawn_enemy") { if (args.size() > 1) { Enemy_Level el = enemyg->getRandomEnemy(args[1], 0, 0); if (el.type != "") { Point spawn_pos = floor(mapr->collider.get_random_neighbor(floor(pc->stats.pos), 1)); powers->spawn(args[1], spawn_pos); log_history->add(msg->get("Spawning enemy from category: ") + args[1]); } else { log_history->add(msg->get("ERROR: Invalid enemy category"), false, &color_error); } } else { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "give_item") { if (args.size() > 1) { int id = toInt(args[1]); if (id <= 0 || (unsigned)id >= items->items.size() || items->items[id].name == "") { log_history->add(msg->get("ERROR: Invalid item ID"), false, &color_error); return; } int quantity = (args.size() > 2) ? toInt(args[2]) : 1; if (quantity > 0) { if (id == CURRENCY_ID) { camp->rewardCurrency(quantity); } else { ItemStack stack; stack.item = id; stack.quantity = quantity; camp->rewardItem(stack); } log_history->add(msg->get("Added item: ") + items->items[id].name + " (" + toString(typeid(int), &quantity) + ")", false); } } else { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "give_currency") { int quantity = (args.size() > 1) ? toInt(args[1]) : 0; if (quantity > 0) { camp->rewardCurrency(quantity); log_history->add(msg->get("Added currency: ") + toString(typeid(int), &quantity), false); } if (args.size() < 2) { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "give_xp") { int quantity = (args.size() > 1) ? toInt(args[1]) : 0; if (quantity > 0) { camp->rewardXP(quantity, true); log_history->add(msg->get("Added XP: ") + toString(typeid(int), &quantity), false); } if (args.size() < 2) { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "set_status") { for (unsigned i = 1; i < args.size(); ++i) { camp->setStatus(args[i]); log_history->add(msg->get("Set campaign status: ") + args[i], false); } if (args.size() < 2) { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "unset_status") { for (unsigned i = 1; i < args.size(); ++i) { if (camp->checkStatus(args[i])) { camp->unsetStatus(args[i]); log_history->add(msg->get("Unset campaign status: ") + args[i], false); } else { log_history->add(msg->get("ERROR: Unknown campaign status: ") + args[i], false, &color_error); } } if (args.size() < 2) { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else if (args[0] == "teleport") { if (args.size() > 2) { FPoint dest; dest.x = toInt(args[1]) + 0.5f; dest.y = toInt(args[2]) + 0.5f; if (args.size() > 3) { if (fileExists(mods->locate(args[3]))) { mapr->teleportation = true; mapr->teleport_destination.x = dest.x; mapr->teleport_destination.y = dest.y; mapr->teleport_mapname = args[3]; log_history->add(msg->get("Teleporting to: " + args[1] + ", " + args[2] + ", " + args[3]), false); } else { log_history->add(msg->get("ERROR: Unknown map: ") + args[3], false, &color_error); } } else { mapr->teleportation = true; mapr->teleport_destination.x = dest.x; mapr->teleport_destination.y = dest.y; log_history->add(msg->get("Teleporting to: " + args[1] + ", " + args[2]), false); } } else { log_history->add(msg->get("ERROR: Too few arguments"), false, &color_error); } } else { log_history->add(msg->get("ERROR: Unknown command"), false, &color_error); } }
bool StatBlock::loadCoreStat(FileParser *infile) { // @CLASS StatBlock: Core stats|Description of engine/stats.txt and enemies in enemies/ if (infile->key == "speed") { // @ATTR speed|float|Movement speed float fvalue = toFloat(infile->val, 0); speed = speed_default = fvalue / MAX_FRAMES_PER_SEC; return true; } else if (infile->key == "cooldown") { // @ATTR cooldown|int|Cooldown between attacks in 'ms' or 's'. cooldown = parse_duration(infile->val); return true; } else if (infile->key == "cooldown_hit") { // @ATTR cooldown_hit|duration|Duration of cooldown after being hit in 'ms' or 's'. cooldown_hit = parse_duration(infile->val); return true; } else if (infile->key == "stat") { // @ATTR stat|string, int : Stat name, Value|The starting value for this stat. std::string stat = popFirstString(infile->val); int value = popFirstInt(infile->val); for (size_t i=0; i<STAT_COUNT; ++i) { if (STAT_KEY[i] == stat) { starting[i] = value; return true; } } for (size_t i = 0; i < DAMAGE_TYPES.size(); ++i) { if (DAMAGE_TYPES[i].min == stat) { starting[STAT_COUNT + (i*2)] = value; return true; } else if (DAMAGE_TYPES[i].max == stat) { starting[STAT_COUNT + (i*2) + 1] = value; return true; } } } else if (infile->key == "stat_per_level") { // @ATTR stat_per_level|predefined_string, int : Stat name, Value|The value for this stat added per level. std::string stat = popFirstString(infile->val); int value = popFirstInt(infile->val); for (unsigned i=0; i<STAT_COUNT; i++) { if (STAT_KEY[i] == stat) { per_level[i] = value; return true; } } for (size_t i = 0; i < DAMAGE_TYPES.size(); ++i) { if (DAMAGE_TYPES[i].min == stat) { per_level[STAT_COUNT + (i*2)] = value; return true; } else if (DAMAGE_TYPES[i].max == stat) { per_level[STAT_COUNT + (i*2) + 1] = value; return true; } } } else if (infile->key == "stat_per_primary") { // @ATTR stat_per_primary|predefined_string, predefined_string, int : Primary Stat, Stat name, Value|The value for this stat added for every point allocated to this primary stat. std::string prim_stat = popFirstString(infile->val); size_t prim_stat_index = getPrimaryStatIndex(prim_stat); std::string stat = popFirstString(infile->val); int value = popFirstInt(infile->val); for (unsigned i=0; i<STAT_COUNT; i++) { if (STAT_KEY[i] == stat) { per_primary[prim_stat_index][i] = value; return true; } } for (size_t i = 0; i < DAMAGE_TYPES.size(); ++i) { if (DAMAGE_TYPES[i].min == stat) { per_primary[prim_stat_index][STAT_COUNT + (i*2)] = value; return true; } else if (DAMAGE_TYPES[i].max == stat) { per_primary[prim_stat_index][STAT_COUNT + (i*2) + 1] = value; return true; } } } else if (infile->key == "vulnerable") { // @ATTR vulnerable|predefined_string, int : Element, Value|Percentage weakness to this element. std::string element = popFirstString(infile->val); int value = popFirstInt(infile->val); for (unsigned int i=0; i<ELEMENTS.size(); i++) { if (element == ELEMENTS[i].id) { vulnerable[i] = vulnerable_base[i] = value; return true; } } } else if (infile->key == "power_filter") { // @ATTR power_filter|list(power_id)|Only these powers are allowed to hit this entity. std::string power_id = popFirstString(infile->val); while (!power_id.empty()) { power_filter.push_back(toInt(power_id)); power_id = popFirstString(infile->val); } return true; } return false; }
void Map::loadEnemyGroup(FileParser &infile, Map_Group *group) { if (infile.key == "type") { // @ATTR enemygroup.type|string|(IGNORED BY ENGINE) The "type" field, as used by Tiled and other mapping tools. group->type = infile.val; } else if (infile.key == "category") { // @ATTR enemygroup.category|predefined_string|The category of enemies that will spawn in this group. group->category = infile.val; } else if (infile.key == "level") { // @ATTR enemygroup.level|int, int : Min, Max|Defines the level range of enemies in group. If only one number is given, it's the exact level. group->levelmin = std::max(0, popFirstInt(infile.val)); group->levelmax = std::max(std::max(0, toInt(popFirstString(infile.val))), group->levelmin); } else if (infile.key == "location") { // @ATTR enemygroup.location|rectangle|Location area for enemygroup group->pos.x = popFirstInt(infile.val); group->pos.y = popFirstInt(infile.val); group->area.x = popFirstInt(infile.val); group->area.y = popFirstInt(infile.val); } else if (infile.key == "number") { // @ATTR enemygroup.number|int, int : Min, Max|Defines the range of enemies in group. If only one number is given, it's the exact amount. group->numbermin = std::max(0, popFirstInt(infile.val)); group->numbermax = std::max(std::max(0, toInt(popFirstString(infile.val))), group->numbermin); } else if (infile.key == "chance") { // @ATTR enemygroup.chance|int|Percentage of chance float n = static_cast<float>(std::max(0, popFirstInt(infile.val))) / 100.0f; group->chance = std::min(1.0f, std::max(0.0f, n)); } else if (infile.key == "direction") { // @ATTR enemygroup.direction|direction|Direction that enemies will initially face. group->direction = parse_direction(infile.val); } else if (infile.key == "waypoints") { // @ATTR enemygroup.waypoints|list(point)|Enemy waypoints; single enemy only; negates wander_radius std::string none = ""; std::string a = popFirstString(infile.val); std::string b = popFirstString(infile.val); while (a != none) { FPoint p; p.x = static_cast<float>(toInt(a)) + 0.5f; p.y = static_cast<float>(toInt(b)) + 0.5f; group->waypoints.push(p); a = popFirstString(infile.val); b = popFirstString(infile.val); } // disable wander radius, since we can't have waypoints and wandering at the same time group->wander_radius = 0; } else if (infile.key == "wander_radius") { // @ATTR enemygroup.wander_radius|int|The radius (in tiles) that an enemy will wander around randomly; negates waypoints group->wander_radius = std::max(0, popFirstInt(infile.val)); // clear waypoints, since wandering will use the waypoint queue while (!group->waypoints.empty()) { group->waypoints.pop(); } } else if (infile.key == "requires_status") { // @ATTR enemygroup.requires_status|list(string)|Status required for loading enemies std::string s; while ((s = popFirstString(infile.val)) != "") { group->requires_status.push_back(s); } } else if (infile.key == "requires_not_status") { // @ATTR enemygroup.requires_not_status|list(string)|Status required to be missing for loading enemies std::string s; while ((s = popFirstString(infile.val)) != "") { group->requires_not_status.push_back(s); } } else { infile.error("Map: '%s' is not a valid key.", infile.key.c_str()); } }