/** * Add a new message to the log */ void MenuHUDLog::add(const std::string& s, bool prevent_spam) { hide_overlay = false; // Make sure we don't spam the same message repeatedly if (log_msg.empty() || log_msg.back() != s || !prevent_spam) { // add new message log_msg.push_back(substituteVarsInString(s, pc)); msg_age.push_back(calcDuration(log_msg.back())); // render the log entry and store it in a buffer font->setFont("font_regular"); Point size = font->calc_size(log_msg.back(), window_area.w - (paragraph_spacing*2)); Image *graphics = render_device->createImage(size.x, size.y); font->renderShadowed(log_msg.back(), 0, 0, JUSTIFY_LEFT, graphics, window_area.w - (paragraph_spacing*2), color_normal); msg_buffer.push_back(graphics->createSprite()); graphics->unref(); } else if (!msg_age.empty()) { msg_age.back() = calcDuration(log_msg.back()); } // force HUD messages to vanish in order if (msg_age.size() > 1) { const size_t last = msg_age.size()-1; if (msg_age[last] < msg_age[last-1]) msg_age[last] = msg_age[last-1]; } }
void MenuTalker::createBuffer() { if (static_cast<unsigned>(dialog_node) >= npc->dialog.size() || event_cursor >= npc->dialog[dialog_node].size()) return; std::string line; // speaker name EVENT_COMPONENT_TYPE etype = npc->dialog[dialog_node][event_cursor].type; std::string who; if (etype == EC_NPC_DIALOG_THEM) { who = npc->name; } else if (etype == EC_NPC_DIALOG_YOU) { who = hero_name; } label_name->set(window_area.x+text_pos.x+text_offset.x, window_area.y+text_pos.y+text_offset.y, JUSTIFY_LEFT, VALIGN_TOP, who, color_normal, font_who); line = substituteVarsInString(npc->dialog[dialog_node][event_cursor].s, pc); // render dialog text to the scrollbox buffer Point line_size = font->calc_size(line,textbox->pos.w-(text_offset.x*2)); textbox->resize(textbox->pos.w, line_size.y); textbox->line_height = font->getLineHeight(); font->setFont(font_dialog); font->render( line, text_offset.x, 0, JUSTIFY_LEFT, textbox->contents->getGraphics(), text_pos.w - text_offset.x*2, color_normal ); align(); }
/** * Add a new message to the log. */ void MenuLog::add(const std::string& s, int log_type, bool prevent_spam, Color* color, int style) { log[log_type]->add(substituteVarsInString(s, pc), prevent_spam, color, style); }
/** * logic() * Handle a single frame. This includes: * - move the avatar based on buttons pressed * - calculate the next frame of animation * - calculate camera position based on avatar position * * @param action The actionbar power activated and the target. action.power == 0 means no power. * @param restrict_power_use Whether or not to allow power usage on mouse1 * @param npc True if the player is talking to an NPC. Can limit ability to move/attack in certain conditions */ void Avatar::logic(std::vector<ActionData> &action_queue, bool restrict_power_use, bool npc) { // hazards are processed after Avatar and Enemy[] // so process and clear sound effects from previous frames // check sound effects if (AUDIO) { if (play_sfx_phys) snd->play(sound_melee, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_ment) snd->play(sound_mental, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_hit) snd->play(sound_hit, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_die) snd->play(sound_die, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_critdie) snd->play(sound_die, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if(play_sfx_block) snd->play(sound_block, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); // clear sound flags play_sfx_hit = false; play_sfx_phys = false; play_sfx_ment = false; play_sfx_die = false; play_sfx_critdie = false; play_sfx_block = false; } // clear current space to allow correct movement mapr->collider.unblock(stats.pos.x, stats.pos.y); // turn on all passive powers if ((stats.hp > 0 || stats.effects.triggered_death) && !respawn && !transform_triggered) powers->activatePassives(&stats); if (transform_triggered) transform_triggered = false; // handle when the player stops blocking if (stats.effects.triggered_block && !stats.blocking) { stats.cur_state = AVATAR_STANCE; stats.effects.triggered_block = false; stats.effects.clearTriggerEffects(TRIGGER_BLOCK); stats.refresh_stats = true; } stats.logic(); bool allowed_to_move; bool allowed_to_use_power = true; // check for revive if (stats.hp <= 0 && stats.effects.revive) { stats.hp = stats.get(STAT_HP_MAX); stats.alive = true; stats.corpse = false; stats.cur_state = AVATAR_STANCE; } // check level up if (stats.level < static_cast<int>(stats.xp_table.size()) && stats.xp >= stats.xp_table[stats.level]) { stats.level_up = true; stats.level++; std::stringstream ss; ss << msg->get("Congratulations, you have reached level %d!", stats.level); if (stats.level < stats.max_spendable_stat_points) { ss << " " << msg->get("You may increase one attribute through the Character Menu."); newLevelNotification = true; } log_msg = ss.str(); stats.recalc(); snd->play(sound_levelup); // if the player managed to level up while dead (e.g. via a bleeding creature), restore to life if (stats.cur_state == AVATAR_DEAD) { stats.cur_state = AVATAR_STANCE; } } // check for bleeding to death if (stats.hp == 0 && !(stats.cur_state == AVATAR_DEAD)) { stats.effects.triggered_death = true; stats.cur_state = AVATAR_DEAD; } // assist mouse movement if (!inpt->pressing[MAIN1]) { drag_walking = false; } // block some interactions when attacking if (!inpt->pressing[MAIN1] && !inpt->pressing[MAIN2]) { stats.attacking = false; } else if((inpt->pressing[MAIN1] && !inpt->lock[MAIN1]) || (inpt->pressing[MAIN2] && !inpt->lock[MAIN2])) { stats.attacking = true; } // handle animation if (!stats.effects.stun) { activeAnimation->advanceFrame(); for (unsigned i=0; i < anims.size(); i++) { if (anims[i] != NULL) anims[i]->advanceFrame(); } } if (target_anim && target_anim->getTimesPlayed() >= 1) { target_visible = false; target_anim->reset(); } if (target_anim && target_visible) target_anim->advanceFrame(); // change the cursor if we're attacking if (action_queue.empty()) { lock_cursor = false; } else if (lock_cursor) { curs->setCursor(CURSOR_ATTACK); } // save a valid tile position in the event that we untransform on an invalid tile if (stats.transformed && mapr->collider.is_valid_position(stats.pos.x,stats.pos.y,MOVEMENT_NORMAL, true)) { transform_pos = stats.pos; transform_map = mapr->getFilename(); } if (!stats.effects.stun) { switch(stats.cur_state) { case AVATAR_STANCE: setAnimation("stance"); // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_move = restrict_power_use && (!inpt->lock[MAIN1] || drag_walking) && !lockAttack && !npc; allowed_to_use_power = !allowed_to_move; } else { allowed_to_move = true; allowed_to_use_power = true; } // handle transitions to RUN if (allowed_to_move) set_direction(); if (pressing_move() && allowed_to_move) { if (MOUSE_MOVE && inpt->pressing[MAIN1]) { inpt->lock[MAIN1] = true; drag_walking = true; } if (move()) { // no collision stats.cur_state = AVATAR_RUN; } } if (MOUSE_MOVE && !inpt->pressing[MAIN1]) { inpt->lock[MAIN1] = false; lockAttack = false; } break; case AVATAR_RUN: setAnimation("run"); if (!sound_steps.empty()) { int stepfx = rand() % static_cast<int>(sound_steps.size()); if (activeAnimation->isFirstFrame() || activeAnimation->isActiveFrame()) snd->play(sound_steps[stepfx]); } // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_use_power = !(restrict_power_use && !inpt->lock[MAIN1]); } else { allowed_to_use_power = true; } // handle direction changes set_direction(); // handle transition to STANCE if (!pressing_move()) { stats.cur_state = AVATAR_STANCE; break; } else if (!move()) { // collide with wall stats.cur_state = AVATAR_STANCE; break; } if (activeAnimation->getName() != "run") stats.cur_state = AVATAR_STANCE; break; case AVATAR_ATTACK: setAnimation(attack_anim); if (MOUSE_MOVE) lockAttack = true; if (activeAnimation->isFirstFrame()) { if (attack_anim == "swing") snd->play(sound_melee); else if (attack_anim == "cast") snd->play(sound_mental); power_cast_duration[current_power] = activeAnimation->getDuration(); power_cast_ticks[current_power] = power_cast_duration[current_power]; } // do power if (activeAnimation->isActiveFrame() && !stats.hold_state) { // some powers check if the caster is blocking a tile // so we block the player tile prematurely here mapr->collider.block(stats.pos.x, stats.pos.y, false); powers->activate(current_power, &stats, act_target); hero_cooldown[current_power] = powers->getPower(current_power).cooldown; if (stats.state_ticks > 0) stats.hold_state = true; } if ((activeAnimation->isLastFrame() && stats.state_ticks == 0) || activeAnimation->getName() != attack_anim) { stats.cur_state = AVATAR_STANCE; stats.cooldown_ticks = stats.cooldown; allowed_to_use_power = false; } break; case AVATAR_BLOCK: setAnimation("block"); stats.blocking = false; break; case AVATAR_HIT: setAnimation("hit"); if (activeAnimation->isFirstFrame()) { stats.effects.triggered_hit = true; } if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "hit") { stats.cur_state = AVATAR_STANCE; } break; case AVATAR_DEAD: allowed_to_use_power = false; if (stats.effects.triggered_death) break; if (stats.transformed) { stats.transform_duration = 0; untransform(); } setAnimation("die"); if (!stats.corpse && activeAnimation->isFirstFrame() && activeAnimation->getTimesPlayed() < 1) { stats.effects.clearEffects(); // reset power cooldowns for (size_t i = 0; i < hero_cooldown.size(); i++) { hero_cooldown[i] = 0; power_cast_ticks[i] = 0; } // raise the death penalty flag. Another module will read this and reset. stats.death_penalty = true; // close menus in GameStatePlay close_menus = true; snd->play(sound_die); if (stats.permadeath) { log_msg = msg->get("You are defeated. Game over! ${INPUT_CONTINUE} to exit to Title."); log_msg = substituteVarsInString(log_msg, this); } else { log_msg = msg->get("You are defeated. ${INPUT_CONTINUE} to continue."); log_msg = substituteVarsInString(log_msg, this); } // if the player is attacking, we need to block further input if (inpt->pressing[MAIN1]) inpt->lock[MAIN1] = true; } if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "die") { stats.corpse = true; } // allow respawn with Accept if not permadeath if (inpt->pressing[ACCEPT] || (TOUCHSCREEN && inpt->pressing[MAIN1] && !inpt->lock[MAIN1])) { if (inpt->pressing[ACCEPT]) inpt->lock[ACCEPT] = true; if (TOUCHSCREEN && inpt->pressing[MAIN1]) inpt->lock[MAIN1] = true; mapr->teleportation = true; mapr->teleport_mapname = mapr->respawn_map; if (stats.permadeath) { // set these positions so it doesn't flash before jumping to Title mapr->teleport_destination.x = stats.pos.x; mapr->teleport_destination.y = stats.pos.y; } else { respawn = true; // set teleportation variables. GameEngine acts on these. mapr->teleport_destination.x = mapr->respawn_point.x; mapr->teleport_destination.y = mapr->respawn_point.y; } } break; default: break; } // handle power usage if (allowed_to_use_power) handlePower(action_queue); } // calc new cam position from player position // cam is focused at player position mapr->cam.x = stats.pos.x; mapr->cam.y = stats.pos.y; // check for map events mapr->checkEvents(stats.pos); // decrement all cooldowns for (unsigned i = 0; i < hero_cooldown.size(); i++) { hero_cooldown[i]--; if (hero_cooldown[i] < 0) hero_cooldown[i] = 0; power_cast_ticks[i]--; if (power_cast_ticks[i] < 0) power_cast_ticks[i] = 0; } // make the current square solid mapr->collider.block(stats.pos.x, stats.pos.y, false); if (stats.state_ticks == 0 && stats.hold_state) stats.hold_state = false; if (stats.cur_state != AVATAR_ATTACK && stats.charge_speed != 0.0f) stats.charge_speed = 0.0f; }
void MenuPowers::createTooltip(TooltipData* tip, int slot_num, const std::vector<Power_Menu_Cell>& power_cells, bool show_unlock_prompt) { if (power_cells[slot_num].upgrade_level > 0) tip->addText(powers->powers[power_cells[slot_num].id].name + " (" + msg->get("Level %d", power_cells[slot_num].upgrade_level) + ")"); else tip->addText(powers->powers[power_cells[slot_num].id].name); if (powers->powers[power_cells[slot_num].id].passive) tip->addText(msg->get("Passive")); tip->addColoredText(substituteVarsInString(powers->powers[power_cells[slot_num].id].description, pc), color_flavor); // add mana cost if (powers->powers[power_cells[slot_num].id].requires_mp > 0) { tip->addText(msg->get("Costs %d MP", powers->powers[power_cells[slot_num].id].requires_mp)); } // add health cost if (powers->powers[power_cells[slot_num].id].requires_hp > 0) { tip->addText(msg->get("Costs %d HP", powers->powers[power_cells[slot_num].id].requires_hp)); } // add cooldown time if (powers->powers[power_cells[slot_num].id].cooldown > 0) { std::stringstream ss; ss << msg->get("Cooldown:") << " " << getDurationString(powers->powers[power_cells[slot_num].id].cooldown); tip->addText(ss.str()); } const Power &pwr = powers->powers[power_cells[slot_num].id]; for (size_t i=0; i<pwr.post_effects.size(); ++i) { std::stringstream ss; EffectDef* effect_ptr = powers->getEffectDef(pwr.post_effects[i].id); // base stats if (effect_ptr == NULL) { if (pwr.post_effects[i].magnitude > 0) { ss << "+"; } ss << pwr.post_effects[i].magnitude; bool found_key = false; for (size_t j=0; j<STAT_COUNT; ++j) { if (pwr.post_effects[i].id == STAT_KEY[j]) { if (STAT_PERCENT[j]) ss << "%"; ss << " " << STAT_NAME[j]; found_key = true; break; } } if (!found_key) { for (size_t j=0; j<ELEMENTS.size(); ++j) { if (pwr.post_effects[i].id == ELEMENTS[j].id + "_resist") { ss << "% " << msg->get("%s Resistance", ELEMENTS[j].name.c_str()); found_key = true; break; } } } if (!found_key) { for (size_t j=0; j<PRIMARY_STATS.size(); ++j) { if (pwr.post_effects[i].id == PRIMARY_STATS[j].id) { ss << " " << PRIMARY_STATS[j].name; found_key = true; break; } } } if (!found_key) { for (size_t j=0; j<DAMAGE_TYPES.size(); ++j) { if (pwr.post_effects[i].id == DAMAGE_TYPES[j].min) { ss << " " << DAMAGE_TYPES[j].name_min; found_key = true; break; } else if (pwr.post_effects[i].id == DAMAGE_TYPES[j].max) { ss << " " << DAMAGE_TYPES[j].name_max; found_key = true; break; } } } } else { if (effect_ptr->type == "damage") { ss << pwr.post_effects[i].magnitude << " " << msg->get("Damage per second"); } else if (effect_ptr->type == "damage_percent") { ss << pwr.post_effects[i].magnitude << "% " << msg->get("Damage per second"); } else if (effect_ptr->type == "hpot") { ss << pwr.post_effects[i].magnitude << " " << msg->get("HP per second"); } else if (effect_ptr->type == "hpot_percent") { ss << pwr.post_effects[i].magnitude << "% " << msg->get("HP per second"); } else if (effect_ptr->type == "mpot") { ss << pwr.post_effects[i].magnitude << " " << msg->get("MP per second"); } else if (effect_ptr->type == "mpot_percent") { ss << pwr.post_effects[i].magnitude << "% " << msg->get("MP per second"); } else if (effect_ptr->type == "speed") { if (pwr.post_effects[i].magnitude == 0) ss << msg->get("Immobilize"); else ss << msg->get("%d%% Speed", pwr.post_effects[i].magnitude); } else if (effect_ptr->type == "attack_speed") { ss << msg->get("%d%% Attack Speed", pwr.post_effects[i].magnitude); } else if (effect_ptr->type == "immunity") { ss << msg->get("Immunity"); } else if (effect_ptr->type == "immunity_damage") { ss << msg->get("Immunity to damage over time"); } else if (effect_ptr->type == "immunity_slow") { ss << msg->get("Immunity to slow"); } else if (effect_ptr->type == "immunity_stun") { ss << msg->get("Immunity to stun"); } else if (effect_ptr->type == "immunity_hp_steal") { ss << msg->get("Immunity to HP steal"); } else if (effect_ptr->type == "immunity_mp_steal") { ss << msg->get("Immunity to MP steal"); } else if (effect_ptr->type == "immunity_knockback") { ss << msg->get("Immunity to knockback"); } else if (effect_ptr->type == "immunity_damage_reflect") { ss << msg->get("Immunity to damage reflection"); } else if (effect_ptr->type == "stun") { ss << msg->get("Stun"); } else if (effect_ptr->type == "revive") { ss << msg->get("Automatic revive on death"); } else if (effect_ptr->type == "convert") { ss << msg->get("Convert"); } else if (effect_ptr->type == "fear") { ss << msg->get("Fear"); } else if (effect_ptr->type == "death_sentence") { ss << msg->get("Lifespan"); } else if (effect_ptr->type == "shield") { if (pwr.base_damage == DAMAGE_TYPES.size()) continue; if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY) { int magnitude = stats->getDamageMax(pwr.base_damage) * pwr.mod_damage_value_min / 100; ss << magnitude; } else if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_ADD) { int magnitude = stats->getDamageMax(pwr.base_damage) + pwr.mod_damage_value_min; ss << magnitude; } else if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_ABSOLUTE) { if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max) ss << pwr.mod_damage_value_min; else ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max; } else { ss << stats->getDamageMax(pwr.base_damage); } ss << " " << msg->get("Magical Shield"); } else if (effect_ptr->type == "heal") { if (pwr.base_damage == DAMAGE_TYPES.size()) continue; int mag_min = stats->getDamageMin(pwr.base_damage); int mag_max = stats->getDamageMax(pwr.base_damage); if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY) { mag_min = mag_min * pwr.mod_damage_value_min / 100; mag_max = mag_max * pwr.mod_damage_value_min / 100; ss << mag_min << "-" << mag_max; } else if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_ADD) { mag_min = mag_min + pwr.mod_damage_value_min; mag_max = mag_max + pwr.mod_damage_value_min; ss << mag_min << "-" << mag_max; } else if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_ABSOLUTE) { if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max) ss << pwr.mod_damage_value_min; else ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max; } else { ss << mag_min << "-" << mag_max; } ss << " " << msg->get("Healing"); } else if (effect_ptr->type == "knockback") { ss << pwr.post_effects[i].magnitude << " " << msg->get("Knockback"); } else if (pwr.post_effects[i].magnitude == 0) { // nothing } } if (!ss.str().empty()) { if (pwr.post_effects[i].duration > 0) { if (effect_ptr && effect_ptr->type == "death_sentence") { ss << ": " << getDurationString(pwr.post_effects[i].duration); } else { ss << " (" << getDurationString(pwr.post_effects[i].duration) << ")"; } if (pwr.post_effects[i].chance != 100) ss << " "; } if (pwr.post_effects[i].chance != 100) { ss << "(" << msg->get("%d%% chance", pwr.post_effects[i].chance) << ")"; } tip->addColoredText(ss.str(), color_bonus); } } if (pwr.use_hazard || pwr.type == POWTYPE_REPEATER) { std::stringstream ss; // modifier_damage if (pwr.mod_damage_mode > -1) { if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_ADD && pwr.mod_damage_value_min > 0) ss << "+"; if (pwr.mod_damage_value_max == 0 || pwr.mod_damage_value_min == pwr.mod_damage_value_max) { ss << pwr.mod_damage_value_min; } else { ss << pwr.mod_damage_value_min << "-" << pwr.mod_damage_value_max; } if (pwr.mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY) { ss << "%"; } ss << " "; if (pwr.base_damage != DAMAGE_TYPES.size()) { ss << DAMAGE_TYPES[pwr.base_damage].name; } if (pwr.count > 1 && pwr.type != POWTYPE_REPEATER) ss << " (x" << pwr.count << ")"; if (!ss.str().empty()) tip->addColoredText(ss.str(), color_bonus); } // modifier_accuracy if (pwr.mod_accuracy_mode > -1) { ss.str(""); if (pwr.mod_accuracy_mode == STAT_MODIFIER_MODE_ADD && pwr.mod_accuracy_value > 0) ss << "+"; ss << pwr.mod_accuracy_value; if (pwr.mod_accuracy_mode == STAT_MODIFIER_MODE_MULTIPLY) { ss << "%"; } ss << " "; ss << msg->get("Base Accuracy"); if (!ss.str().empty()) tip->addColoredText(ss.str(), color_bonus); } // modifier_critical if (pwr.mod_crit_mode > -1) { ss.str(""); if (pwr.mod_crit_mode == STAT_MODIFIER_MODE_ADD && pwr.mod_crit_value > 0) ss << "+"; ss << pwr.mod_crit_value; if (pwr.mod_crit_mode == STAT_MODIFIER_MODE_MULTIPLY) { ss << "%"; } ss << " "; ss << msg->get("Base Critical Chance"); if (!ss.str().empty()) tip->addColoredText(ss.str(), color_bonus); } if (pwr.trait_armor_penetration) { ss.str(""); ss << msg->get("Ignores Absorbtion"); tip->addColoredText(ss.str(), color_bonus); } if (pwr.trait_avoidance_ignore) { ss.str(""); ss << msg->get("Ignores Avoidance"); tip->addColoredText(ss.str(), color_bonus); } if (pwr.trait_crits_impaired > 0) { ss.str(""); ss << msg->get("%d%% Chance to crit slowed targets", pwr.trait_crits_impaired); tip->addColoredText(ss.str(), color_bonus); } if (pwr.trait_elemental > -1) { ss.str(""); ss << msg->get("%s Elemental Damage", ELEMENTS[pwr.trait_elemental].name.c_str()); tip->addColoredText(ss.str(), color_bonus); } } std::set<std::string>::iterator it; for (it = powers->powers[power_cells[slot_num].id].requires_flags.begin(); it != powers->powers[power_cells[slot_num].id].requires_flags.end(); ++it) { for (size_t i=0; i<EQUIP_FLAGS.size(); ++i) { if ((*it) == EQUIP_FLAGS[i].id) { tip->addText(msg->get("Requires a %s", msg->get(EQUIP_FLAGS[i].name))); } } } // add requirement for (size_t i = 0; i < PRIMARY_STATS.size(); ++i) { if (power_cells[slot_num].requires_primary[i] > 0) { if (stats->get_primary(i) < power_cells[slot_num].requires_primary[i]) tip->addColoredText(msg->get("Requires %s %d", power_cells[slot_num].requires_primary[i], PRIMARY_STATS[i].name.c_str()), color_penalty); else tip->addText(msg->get("Requires %s %d", power_cells[slot_num].requires_primary[i], PRIMARY_STATS[i].name.c_str())); } } // Draw required Level Tooltip if ((power_cells[slot_num].requires_level > 0) && stats->level < power_cells[slot_num].requires_level) { tip->addColoredText(msg->get("Requires Level %d", power_cells[slot_num].requires_level), color_penalty); } else if ((power_cells[slot_num].requires_level > 0) && stats->level >= power_cells[slot_num].requires_level) { tip->addText(msg->get("Requires Level %d", power_cells[slot_num].requires_level)); } for (size_t j=0; j < power_cells[slot_num].requires_power.size(); ++j) { if (power_cells[slot_num].requires_power[j] == 0) continue; int req_index = getCellByPowerIndex(power_cells[slot_num].requires_power[j], power_cell_all); if (req_index == -1) continue; std::string req_power_name; if (power_cell_all[req_index].upgrade_level > 0) req_power_name = powers->powers[power_cell_all[req_index].id].name + " (" + msg->get("Level %d", power_cell_all[req_index].upgrade_level) + ")"; else req_power_name = powers->powers[power_cell_all[req_index].id].name; // Required Power Tooltip int req_cell_index = getCellByPowerIndex(power_cells[slot_num].requires_power[j], power_cell_all); if (!checkUnlocked(req_cell_index)) { tip->addColoredText(msg->get("Requires Power: %s", req_power_name), color_penalty); } else { tip->addText(msg->get("Requires Power: %s", req_power_name)); } } // Draw unlock power Tooltip if (power_cells[slot_num].requires_point && !(std::find(stats->powers_list.begin(), stats->powers_list.end(), power_cells[slot_num].id) != stats->powers_list.end())) { int unlock_id = getCellByPowerIndex(power_cells[slot_num].id, power_cell_all); if (show_unlock_prompt && points_left > 0 && checkUnlock(unlock_id)) { tip->addColoredText(msg->get("Click to Unlock (uses 1 Skill Point)"), color_bonus); } else { if (power_cells[slot_num].requires_point && points_left < 1) tip->addColoredText(msg->get("Requires 1 Skill Point"), color_penalty); else tip->addText(msg->get("Requires 1 Skill Point")); } } }
/** * Create detailed tooltip showing all relevant item info */ TooltipData ItemManager::getTooltip(ItemStack stack, StatBlock *stats, int context) { TooltipData tip; if (stack.empty()) return tip; Color color = getItemColor(stack.item); // name std::stringstream ss; if (stack.quantity == 1) ss << getItemName(stack.item); else ss << getItemName(stack.item) << " (" << stack.quantity << ")"; tip.addText(ss.str(), color); // quest item if (items[stack.item].quest_item) { tip.addText(msg->get("Quest Item"), color_bonus); } // only show the name of the currency item if (stack.item == CURRENCY_ID) return tip; // flavor text if (items[stack.item].flavor != "") { tip.addText(substituteVarsInString(items[stack.item].flavor, pc), color_flavor); } // level if (items[stack.item].level != 0) { tip.addText(msg->get("Level %d", items[stack.item].level)); } // type if (items[stack.item].type != "") { tip.addText(msg->get(getItemType(items[stack.item].type))); } // item quality text for colorblind users if (COLORBLIND && items[stack.item].quality != "") { color = color_normal; for (size_t i=0; i<item_qualities.size(); ++i) { if (item_qualities[i].id == items[stack.item].quality) { tip.addText(msg->get("Quality: %s", msg->get(item_qualities[i].name)), color); break; } } } // damage if (items[stack.item].dmg_melee_max > 0) { if (items[stack.item].dmg_melee_min < items[stack.item].dmg_melee_max) tip.addText(msg->get("Melee damage: %d-%d", items[stack.item].dmg_melee_min, items[stack.item].dmg_melee_max)); else tip.addText(msg->get("Melee damage: %d", items[stack.item].dmg_melee_max)); } if (items[stack.item].dmg_ranged_max > 0) { if (items[stack.item].dmg_ranged_min < items[stack.item].dmg_ranged_max) tip.addText(msg->get("Ranged damage: %d-%d", items[stack.item].dmg_ranged_min, items[stack.item].dmg_ranged_max)); else tip.addText(msg->get("Ranged damage: %d", items[stack.item].dmg_ranged_max)); } if (items[stack.item].dmg_ment_max > 0) { if (items[stack.item].dmg_ment_min < items[stack.item].dmg_ment_max) tip.addText(msg->get("Mental damage: %d-%d", items[stack.item].dmg_ment_min, items[stack.item].dmg_ment_max)); else tip.addText(msg->get("Mental damage: %d", items[stack.item].dmg_ment_max)); } // absorb if (items[stack.item].abs_max > 0) { if (items[stack.item].abs_min < items[stack.item].abs_max) tip.addText(msg->get("Absorb: %d-%d", items[stack.item].abs_min, items[stack.item].abs_max)); else tip.addText(msg->get("Absorb: %d", items[stack.item].abs_max)); } // bonuses unsigned bonus_counter = 0; while (bonus_counter < items[stack.item].bonus.size()) { ss.str(""); BonusData* bdata = &items[stack.item].bonus[bonus_counter]; if (bdata->is_speed) { ss << msg->get("%d%% Speed", bdata->value); if (bdata->value >= 100) color = color_bonus; else color = color_penalty; } else { if (bdata->value > 0) { color = color_bonus; } else { color = color_penalty; } getBonusString(ss, bdata); } tip.addText(ss.str(), color); bonus_counter++; } // power if (items[stack.item].power_desc != "") { tip.addText(items[stack.item].power_desc, color_bonus); } // level requirement if (items[stack.item].requires_level > 0) { if (stats->level < items[stack.item].requires_level) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Level %d", items[stack.item].requires_level), color); } // base stat requirement for (unsigned i=0; i<items[stack.item].req_stat.size(); ++i) { if (items[stack.item].req_val[i] > 0) { if (items[stack.item].req_stat[i] == REQUIRES_PHYS) { if (stats->get_physical() < items[stack.item].req_val[i]) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Physical %d", items[stack.item].req_val[i]), color); } else if (items[stack.item].req_stat[i] == REQUIRES_MENT) { if (stats->get_mental() < items[stack.item].req_val[i]) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Mental %d", items[stack.item].req_val[i]), color); } else if (items[stack.item].req_stat[i] == REQUIRES_OFF) { if (stats->get_offense() < items[stack.item].req_val[i]) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Offense %d", items[stack.item].req_val[i]), color); } else if (items[stack.item].req_stat[i] == REQUIRES_DEF) { if (stats->get_defense() < items[stack.item].req_val[i]) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Defense %d", items[stack.item].req_val[i]), color); } } } // requires class if (items[stack.item].requires_class != "") { if (items[stack.item].requires_class != stats->character_class) color = color_requirements_not_met; else color = color_normal; tip.addText(msg->get("Requires Class: %s", msg->get(items[stack.item].requires_class)), color); } // buy or sell price if (items[stack.item].getPrice() > 0 && stack.item != CURRENCY_ID) { int price_per_unit; if (context == VENDOR_BUY) { price_per_unit = items[stack.item].getPrice(); if (stats->currency < price_per_unit) color = color_requirements_not_met; else color = color_normal; if (items[stack.item].max_quantity <= 1) tip.addText(msg->get("Buy Price: %d %s", price_per_unit, CURRENCY), color); else tip.addText(msg->get("Buy Price: %d %s each", price_per_unit, CURRENCY), color); } else if (context == VENDOR_SELL) { price_per_unit = items[stack.item].getSellPrice(); if (stats->currency < price_per_unit) color = color_requirements_not_met; else color = color_normal; if (items[stack.item].max_quantity <= 1) tip.addText(msg->get("Buy Price: %d %s", price_per_unit, CURRENCY), color); else tip.addText(msg->get("Buy Price: %d %s each", price_per_unit, CURRENCY), color); } else if (context == PLAYER_INV) { price_per_unit = items[stack.item].getSellPrice(); if (price_per_unit == 0) price_per_unit = 1; if (items[stack.item].max_quantity <= 1) tip.addText(msg->get("Sell Price: %d %s", price_per_unit, CURRENCY)); else tip.addText(msg->get("Sell Price: %d %s each", price_per_unit, CURRENCY)); } } if (items[stack.item].set > 0) { // item set bonuses ItemSet set = item_sets[items[stack.item].set]; bonus_counter = 0; tip.addText("\n" + msg->get("Set: ") + msg->get(item_sets[items[stack.item].set].name), set.color); while (bonus_counter < set.bonus.size()) { ss.str(""); Set_bonus* bdata = &set.bonus[bonus_counter]; ss << msg->get("%d items: ", bdata->requirement); if (bdata->is_speed) { ss << msg->get("%d%% Speed", bdata->value); } else { getBonusString(ss, bdata); } tip.addText(ss.str(), set.color); bonus_counter++; } } return tip; }