/** * \copydoc MapEntity::notify_added_to_map */ void Pickable::notify_added_to_map(Map& map) { Detector::notify_added_to_map(map); if (map.is_started()) { // notify the Lua item get_equipment().get_item(treasure.get_item_name()).notify_pickable_appeared(*this); } // otherwise, notify_map_started() will do the job }
/** * @brief Makes sure the keys effects are coherent with the hero's equipment and abilities. */ void Game::update_keys_effect() { // when the game is paused or a dialog box is shown, the sword key is not the usual one if (is_paused() || is_showing_message()) { return; // if the game is interrupted for some other reason (e.g. a transition), let the normal sword icon } // make sure the sword key is coherent with having a sword if (get_equipment().has_ability("sword") && keys_effect->get_sword_key_effect() != KeysEffect::SWORD_KEY_SWORD) { keys_effect->set_sword_key_effect(KeysEffect::SWORD_KEY_SWORD); } else if (!get_equipment().has_ability("sword") && keys_effect->get_sword_key_effect() == KeysEffect::SWORD_KEY_SWORD) { keys_effect->set_sword_key_effect(KeysEffect::SWORD_KEY_NONE); } }
/** * \brief Notifies the appropriate script that the hero is interacting with this entity. */ void Npc::call_script_hero_interaction() { if (behavior == BEHAVIOR_MAP_SCRIPT) { get_lua_context()->entity_on_interaction(*this); } else { EquipmentItem& item = get_equipment().get_item(item_name); get_lua_context()->item_on_npc_interaction(item, *this); } }
/** * \brief This function is called when the hero stops an attack with his shield. * * By default, the shield sound is played and the enemy cannot attack again for a while. */ void Enemy::attack_stopped_by_hero_shield() { Sound::play("shield"); uint32_t now = System::now(); can_attack = false; can_attack_again_date = now + 1000; get_equipment().notify_ability_used("shield"); }
/** * \copydoc Entity::notify_created */ void Pickable::notify_created() { Detector::notify_created(); update_ground_below(); notify_ground_below_changed(); // Necessary if on empty ground. // This entity and the map are now both ready. Notify the Lua item. EquipmentItem& item = get_equipment().get_item(treasure.get_item_name()); item.notify_pickable_appeared(*this); }
/** * \copydoc Entity::notify_action_command_pressed */ bool Npc::notify_action_command_pressed() { Hero& hero = get_hero(); if (hero.is_free() && get_commands_effects().get_action_key_effect() != CommandsEffects::ACTION_KEY_NONE ) { CommandsEffects::ActionKeyEffect effect = get_commands_effects().get_action_key_effect(); get_commands_effects().set_action_key_effect(CommandsEffects::ACTION_KEY_NONE); SpritePtr sprite = get_sprite(); // if this is a usual NPC, look towards the hero if (subtype == USUAL_NPC) { int direction = (get_hero().get_animation_direction() + 2) % 4; if (sprite != nullptr) { sprite->set_current_direction(direction); } } if (effect != CommandsEffects::ACTION_KEY_LIFT) { // start the normal behavior if (behavior == BEHAVIOR_DIALOG) { get_game().start_dialog(dialog_to_show, ScopedLuaRef(), ScopedLuaRef()); } else { call_script_hero_interaction(); } return true; } else { // lift the entity if (get_equipment().has_ability(Ability::LIFT)) { std::string animation_set_id = "stopped"; if (sprite != nullptr) { animation_set_id = sprite->get_animation_set_id(); } hero.start_lifting(std::make_shared<CarriedObject>( hero, *this, animation_set_id, "stone", 2, 0) ); Sound::play("lift"); remove_from_map(); return true; } } } return false; }
/** * \brief Plays the sword loading sound. */ void Hero::SwordLoadingState::play_load_sound() { std::ostringstream oss; oss << "sword_spin_attack_load_" << get_equipment().get_ability(Ability::SWORD); std::string custom_sound_name = oss.str(); if (Sound::exists(custom_sound_name)) { Sound::play(custom_sound_name); // this particular sword has a custom loading sound effect } else { Sound::play("sword_spin_attack_load"); } }
/** * \brief Plays the sword loading sound. */ void Hero::SpinAttackState::play_spin_attack_sound() { std::ostringstream oss; oss << "sword_spin_attack_release_" << get_equipment().get_ability("sword"); std::string custom_sound_name = oss.str(); if (Sound::exists(custom_sound_name)) { Sound::play(custom_sound_name); // this particular sword has a spin attack sound effect } else { Sound::play("sword_spin_attack_release"); } }
/** * @brief Updates the entity. */ void ShopItem::update() { if (is_looking_item && !get_game().is_dialog_enabled()) { // the description message has just finished const std::string question_dialog_id = "_shop.question"; get_dialog_box().start_dialog(question_dialog_id); get_dialog_box().set_variable(question_dialog_id, price); is_asking_question = true; is_looking_item = false; } else if (is_asking_question && !get_game().is_dialog_enabled()) { // the question has just finished is_asking_question = false; int answer = get_dialog_box().get_last_answer(); if (answer == 0) { // the player wants to buy the item Equipment& equipment = get_equipment(); EquipmentItem& item = treasure.get_item(); if (equipment.get_money() < price) { // not enough rupees Sound::play("wrong"); get_dialog_box().start_dialog("_shop.not_enough_money"); } else if (item.has_amount() && item.get_amount() >= item.get_max_amount()) { // the player already has the maximum amount of this item Sound::play("wrong"); get_dialog_box().start_dialog("_shop.amount_full"); } else { bool can_buy = get_lua_context().shop_item_on_buying(*this); if (can_buy) { // give the treasure equipment.remove_money(price); get_hero().start_treasure(treasure, LUA_REFNIL); if (treasure.is_saved()) { remove_from_map(); get_savegame().set_boolean(treasure.get_savegame_variable(), true); } get_lua_context().shop_item_on_bought(*this); } } } } }
/** Drachen und Seeschlangen können entstehen */ void spawn_dragons(void) { region *r; faction *monsters = get_or_create_monsters(); for (r = regions; r; r = r->next) { unit *u; if (fval(r->terrain, SEA_REGION) && rng_int() % 10000 < 1) { u = create_unit(r, monsters, 1, get_race(RC_SEASERPENT), 0, NULL, NULL); fset(u, UFL_ISNEW | UFL_MOVED); equip_unit(u, get_equipment("monster_seaserpent")); } if ((r->terrain == newterrain(T_GLACIER) || r->terrain == newterrain(T_SWAMP) || r->terrain == newterrain(T_DESERT)) && rng_int() % 10000 < (5 + 100 * chaosfactor(r))) { if (chance(0.80)) { u = create_unit(r, monsters, nrand(60, 20) + 1, get_race(RC_FIREDRAGON), 0, NULL, NULL); } else { u = create_unit(r, monsters, nrand(30, 20) + 1, get_race(RC_DRAGON), 0, NULL, NULL); } fset(u, UFL_ISNEW | UFL_MOVED); equip_unit(u, get_equipment("monster_dragon")); log_debug("spawning %d %s in %s.\n", u->number, LOC(default_locale, rc_name_s(u_race(u), (u->number == 1) ? NAME_SINGULAR : NAME_PLURAL)), regionname(r, NULL)); name_unit(u); /* add message to the region */ ADDMSG(&r->msgs, msg_message("sighting", "region race number", r, u_race(u), u->number)); } } }
/** * \copydoc Detector::notify_action_command_pressed */ bool Destructible::notify_action_command_pressed() { KeysEffect::ActionKeyEffect effect = get_keys_effect().get_action_key_effect(); if ((effect == KeysEffect::ACTION_KEY_LIFT || effect == KeysEffect::ACTION_KEY_LOOK) && get_weight() != -1 && !is_being_cut && !is_waiting_for_regeneration() && !is_regenerating) { if (get_equipment().has_ability(Ability::LIFT, get_weight())) { uint32_t explosion_date = get_can_explode() ? System::now() + 6000 : 0; get_hero().start_lifting(std::make_shared<CarriedItem>( get_hero(), *this, get_animation_set_id(), get_destruction_sound(), get_damage_on_enemies(), explosion_date) ); // Play the sound. Sound::play("lift"); // Create the pickable treasure. create_treasure(); if (!get_can_regenerate()) { // Remove this destructible from the map. remove_from_map(); } else { // The item can actually regenerate. play_destroy_animation(); } // Notify Lua. get_lua_context().destructible_on_lifting(*this); } else { // Cannot lift the object. get_hero().start_grabbing(); get_lua_context().destructible_on_looked(*this); } return true; } return false; }
/** * \brief Starts this state. * \param previous_state the previous state */ void Hero::LiftingState::start(const State* previous_state) { State::start(previous_state); // initialize the entity that will be lifted lifted_item->set_map(get_map()); get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_THROW); get_sprites().set_animation_lifting(); get_sprites().set_lifted_item(lifted_item); get_hero().set_facing_entity(nullptr); get_equipment().notify_ability_used(ABILITY_LIFT); }
/** * @brief Returns whether the player is able to open this chest now. * @return true if this is a small chest or if the player has the big key. */ bool Chest::can_open() { switch (get_opening_method()) { case OPENING_BY_INTERACTION: // No condition: the hero can always open the chest. return true; case OPENING_BY_INTERACTION_IF_SAVEGAME_VARIABLE: { // The hero can open the chest if a savegame variable is set. const std::string& required_savegame_variable = get_opening_condition(); if (required_savegame_variable.empty()) { return false; } Savegame& savegame = get_savegame(); if (savegame.is_boolean(required_savegame_variable)) { return savegame.get_boolean(required_savegame_variable); } if (savegame.is_integer(required_savegame_variable)) { return savegame.get_integer(required_savegame_variable) > 0; } if (savegame.is_string(required_savegame_variable)) { return !savegame.get_string(required_savegame_variable).empty(); } return false; } case OPENING_BY_INTERACTION_IF_ITEM: { // The hero can open the chest if he has an item. const std::string& required_item_name = get_opening_condition(); if (required_item_name.empty()) { return false; } const EquipmentItem& item = get_equipment().get_item(required_item_name); return item.is_saved() && item.get_variant() > 0 && (!item.has_amount() || item.get_amount() > 0); } default: return false; } }
static void get_villagers(region * r, unit * u) { unit *newunit; message *msg = msg_message("encounter_villagers", "unit", u); const char *name = LOC(u->faction->locale, "villagers"); r_addmessage(r, u->faction, msg); msg_release(msg); newunit = create_unit(r, u->faction, rng_int() % 20 + 3, u->faction->race, 0, name, u); leave(newunit, true); fset(newunit, UFL_ISNEW | UFL_MOVED); equip_unit(newunit, get_equipment("random_villagers")); }
/** * \brief Notifies this detector that the player is interacting by using an * equipment item. * * This function is called when the player uses an equipment item * while the hero is facing this NPC. * * \param item_used The equipment item used. * \return true if an interaction occured. */ bool Npc::notify_interaction_with_item(EquipmentItem& item_used) { bool interaction_occured; if (behavior == BEHAVIOR_ITEM_SCRIPT) { EquipmentItem& item_to_notify = get_equipment().get_item(item_name); interaction_occured = get_lua_context()->item_on_npc_interaction_item( item_to_notify, *this, item_used ); } else { interaction_occured = get_lua_context()->entity_on_interaction_item( *this, item_used ); } return interaction_occured; }
/** * \brief This function is called when this entity detects a collision with the hero. * \param hero the hero * \param collision_mode the collision mode that detected the collision */ void Destructible::notify_collision_with_hero(Hero& hero, CollisionMode /* collision_mode */) { if (get_weight() != -1 && !is_being_cut && !is_waiting_for_regeneration() && !is_regenerating && get_keys_effect().get_action_key_effect() == KeysEffect::ACTION_KEY_NONE && hero.is_free()) { if (get_equipment().has_ability(Ability::LIFT, get_weight())) { get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_LIFT); } else { get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_LOOK); } } }
/** * @brief Consumes the savegame variable or the equipment item that was required * to open the door. */ void Door::consume_opening_condition() { switch (get_opening_method()) { case OPENING_BY_INTERACTION_IF_SAVEGAME_VARIABLE: { // Reset or decrement the savegame variable that was required. const std::string& required_savegame_variable = get_opening_condition(); Savegame& savegame = get_savegame(); if (!required_savegame_variable.empty()) { if (savegame.is_boolean(required_savegame_variable)) { savegame.set_boolean(required_savegame_variable, false); } else if (savegame.is_integer(required_savegame_variable)) { int current_value = savegame.get_integer(required_savegame_variable); savegame.set_integer(required_savegame_variable, current_value - 1); } else if (savegame.is_string(required_savegame_variable)) { savegame.set_string(required_savegame_variable, ""); } } break; } case OPENING_BY_INTERACTION_IF_ITEM: { // Remove the equipment item that was required. if (!opening_condition.empty()) { EquipmentItem& item = get_equipment().get_item(opening_condition); if (item.is_saved() && item.get_variant() > 0) { if (item.has_amount()) { item.set_amount(item.get_amount() - 1); } else { item.set_variant(0); } } } break; } default: // Ignored. break; } }
/** * \brief Notifies this state that the action command was just pressed. */ void Hero::FreeState::notify_action_command_pressed() { if (get_keys_effect().is_action_key_acting_on_facing_entity()) { // action on the facing entity hero.get_facing_entity()->notify_action_command_pressed(); } else if (hero.is_facing_point_on_obstacle()) { // grab an obstacle hero.set_state(new GrabbingState(hero)); } else if (get_equipment().has_ability("run")) { // run hero.start_running(); } }
/** * \brief This function is called when this entity detects a collision with the hero. * \param hero the hero * \param collision_mode the collision mode that detected the collision */ void Destructible::notify_collision_with_hero(Hero& hero, CollisionMode collision_mode) { if (features[subtype].can_be_lifted && !is_being_cut && !is_disabled() && !is_regenerating && get_keys_effect().get_action_key_effect() == KeysEffect::ACTION_KEY_NONE && hero.is_free()) { int weight = features[subtype].weight; if (get_equipment().has_ability("lift", weight)) { get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_LIFT); } else { get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_LOOK); } } }
/** * @brief This function is called when the player interacts with this chest. * * This function is called when the player presses the action key * when the hero is facing this detector, and the action icon lets him do this. * The hero opens the chest if possible. */ void Chest::action_key_pressed() { if (is_visible() && get_hero().is_free()) { if (!big_chest || get_equipment().has_ability("open_dungeon_big_locks")) { Sound::play("chest_open"); set_open(true); treasure_date = System::now() + 300; get_keys_effect().set_action_key_effect(KeysEffect::ACTION_KEY_NONE); get_hero().start_freezed(); } else { Sound::play("wrong"); get_dialog_box().start_dialog("_big_key_required"); } } }
/** * \brief Updates the game elements. * * Updates the map, the equipment, the HUD, etc. */ void Game::update() { // update the transitions between maps update_transitions(); if (restarting || !started) { return; } // update the map current_map->update(); // call game:on_update() in Lua get_lua_context().game_on_update(*this); // update the equipment and HUD get_equipment().update(); update_keys_effect(); }
/** * \brief Attacks the hero if possible. * * This function is called when there is a collision between the enemy and the hero. * * \param hero The hero to attack. * \param this_sprite The sprite of this enemy that detected the collision with the hero, * or NULL if it was not a pixel-precise collision. */ void Enemy::attack_hero(Hero& hero, Sprite* this_sprite) { if (!is_immobilized() && can_attack && hero.can_be_hurt(this)) { bool hero_protected = false; if (minimum_shield_needed != 0 && get_equipment().has_ability(ABILITY_SHIELD, minimum_shield_needed) && hero.can_use_shield()) { // Compute the direction corresponding to the angle between the enemy and the hero. double angle = hero.get_angle(*this, NULL, this_sprite); int protected_direction4 = (int) ((angle + Geometry::PI_OVER_2 / 2.0) * 4 / Geometry::TWO_PI); protected_direction4 = (protected_direction4 + 4) % 4; // Also get the direction of the enemy's sprite. int sprite_opposite_direction4 = -1; if (this_sprite != NULL) { sprite_opposite_direction4 = (this_sprite->get_current_direction() + 2) % 4; } // The hero is protected if he is facing the opposite of one of these directions. hero_protected = hero.is_facing_direction4(protected_direction4) || hero.is_facing_direction4(sprite_opposite_direction4); } if (hero_protected) { attack_stopped_by_hero_shield(); } else { // Let the enemy script handle this if it wants. const bool handled = get_lua_context().enemy_on_attacking_hero( *this, hero, this_sprite); if (!handled) { // Scripts did not customize the attack: // do the built-in hurt state of the hero. hero.hurt(*this, this_sprite, damage_on_hero); } } } }
/** * \brief Updates this state. */ void Hero::FallingState::update() { State::update(); Hero& hero = get_hero(); if (!is_suspended() && get_sprites().is_animation_finished()) { // the hero has just finished falling Teletransporter* teletransporter = hero.get_delayed_teletransporter(); if (teletransporter != NULL) { // special hole with a teletransporter teletransporter->transport_hero(hero); } else { // normal hole that hurts the hero get_equipment().remove_life(2); hero.set_state(new BackToSolidGroundState(hero, true)); } } }
/** * \brief Starts this state. * \param previous_state The previous state. */ void Hero::HurtState::start(const HeroState* previous_state) { HeroState::start(previous_state); Equipment& equipment = get_equipment(); Sound::play("hero_hurt"); Hero& hero = get_hero(); const uint32_t invincibility_duration = 2000; hero.set_invincible(true, invincibility_duration); get_sprites().set_animation_hurt(); get_sprites().blink(invincibility_duration); if (has_source) { double angle = Geometry::get_angle(source_xy, hero.get_xy()); std::shared_ptr<StraightMovement> movement = std::make_shared<StraightMovement>(false, true); movement->set_max_distance(24); movement->set_speed(120); movement->set_angle(angle); hero.set_movement(movement); } end_hurt_date = System::now() + 200; // See if the script customizes how the hero takes damages. bool handled = get_lua_context().hero_on_taking_damage(get_hero(), damage); if (!handled && damage != 0) { // No customized damage: perform the default calculation. // The level of the tunic reduces the damage, // but we remove at least 1 life point. int life_points = std::max(1, damage / (equipment.get_ability(Ability::TUNIC))); equipment.remove_life(life_points); if (equipment.has_ability(Ability::TUNIC)) { equipment.notify_ability_used(Ability::TUNIC); } } }
/** * @brief Starts this state. * @param previous_state the previous state */ void Hero::SpinAttackState::start(State* previous_state) { State::start(previous_state); // play the sound play_spin_attack_sound(); // start the animation if (get_equipment().has_ability("sword_knowledge")) { get_sprites().set_animation_super_spin_attack(); CircleMovement* movement = new CircleMovement(false); movement->set_center(hero.get_xy()); movement->set_radius_speed(128); movement->set_radius(24); movement->set_angle_speed(540); movement->set_max_rotations(3); movement->set_clockwise(true); hero.set_movement(movement); } else { get_sprites().set_animation_spin_attack(); } }
/** * \brief Returns the damage power of the sword for the current attack. * * Redefine the function if your state changes the power of the sword * (typically for a spin attack). * * \return the current damage factor of the sword */ int Entity::State::get_sword_damage_factor() const { return get_equipment().get_ability(Ability::SWORD); }
/** * \brief Returns whether the player can currently pause the game. * * He can pause the game if the pause command is enabled * and if his life is greater than zero. * * \return \c true if the player can currently pause the game. */ bool Game::can_pause() const { return !is_suspended() && is_pause_allowed() // see if the map currently allows the pause command && get_equipment().get_life() > 0; // don't allow to pause the game if the gameover sequence is about to start }
/** * \brief Creates a game. * \param main_loop The Solarus root object. * \param savegame The saved data of this game. Will be deleted in the * destructor unless someone is still using it (the refcount info is used). */ Game::Game(MainLoop& main_loop, Savegame* savegame): main_loop(main_loop), savegame(savegame), pause_allowed(true), paused(false), dialog_box(*this), showing_game_over(false), started(false), restarting(false), keys_effect(NULL), current_map(NULL), next_map(NULL), previous_map_surface(NULL), transition_style(Transition::IMMEDIATE), transition(NULL), crystal_state(false) { // notify objects RefCountable::ref(savegame); savegame->set_game(this); // initialize members commands = new GameCommands(*this); hero = new Hero(get_equipment()); RefCountable::ref(hero); keys_effect = new KeysEffect(); update_keys_effect(); // Maybe we are restarting after a game-over sequence. if (get_equipment().get_life() <= 0) { get_equipment().restore_all_life(); } // Launch the starting map. std::string starting_map_id = savegame->get_string(Savegame::KEY_STARTING_MAP); std::string starting_destination_name = savegame->get_string(Savegame::KEY_STARTING_POINT); bool valid_map_saved = false; if (!starting_map_id.empty()) { if (QuestResourceList::exists(QuestResourceList::RESOURCE_MAP, starting_map_id)) { // We are okay: the savegame file refers to an existing map. valid_map_saved = true; } else { // The savegame refers to a map that no longer exists. // Maybe the quest is in an intermediate development phase. // Show an error and fallback to the default map. Debug::error(std::string("The savegame refers to a non-existing map: '") + starting_map_id + "'"); } } if (!valid_map_saved) { // When no valid starting map is set, use the first one declared in the // resource list file. const std::vector<QuestResourceList::Element>& maps = QuestResourceList::get_elements(QuestResourceList::RESOURCE_MAP); if (maps.empty()) { Debug::die("This quest has no map"); } starting_map_id = maps[0].first; starting_destination_name = ""; // Default destination. } set_current_map(starting_map_id, starting_destination_name, Transition::FADE); }
static void get_allies(region * r, unit * u) { unit *newunit = NULL; const char *name; const char *equip; int number; message *msg; assert(u->number); switch (rterrain(r)) { case T_PLAIN: if (!r_isforest(r)) { if (get_money(u) / u->number < 100 + rng_int() % 200) return; name = "random_plain_men"; equip = "random_plain"; number = rng_int() % 8 + 2; break; } else { if (eff_skill(u, SK_LONGBOW, r) < 3 && eff_skill(u, SK_HERBALISM, r) < 2 && eff_skill(u, SK_MAGIC, r) < 2) { return; } name = "random_forest_men"; equip = "random_forest"; number = rng_int() % 6 + 2; } break; case T_SWAMP: if (eff_skill(u, SK_MELEE, r) <= 1) { return; } name = "random_swamp_men"; equip = "random_swamp"; number = rng_int() % 6 + 2; break; case T_DESERT: if (eff_skill(u, SK_RIDING, r) <= 2) { return; } name = "random_desert_men"; equip = "random_desert"; number = rng_int() % 12 + 2; break; case T_HIGHLAND: if (eff_skill(u, SK_MELEE, r) <= 1) { return; } name = "random_highland_men"; equip = "random_highland"; number = rng_int() % 8 + 2; break; case T_MOUNTAIN: if (eff_skill(u, SK_MELEE, r) <= 1 || eff_skill(u, SK_TRADE, r) <= 2) { return; } name = "random_mountain_men"; equip = "random_mountain"; number = rng_int() % 6 + 2; break; case T_GLACIER: if (eff_skill(u, SK_MELEE, r) <= 1 || eff_skill(u, SK_TRADE, r) <= 1) { return; } name = "random_glacier_men"; equip = "random_glacier"; number = rng_int() % 4 + 2; break; default: return; } newunit = create_unit(r, u->faction, number, u->faction->race, 0, LOC(u->faction->locale, name), u); equip_unit(newunit, get_equipment(equip)); u_setfaction(newunit, u->faction); set_racename(&newunit->attribs, get_racename(u->attribs)); if (u_race(u)->flags & RCF_SHAPESHIFT) { newunit->irace = u->irace; } if (fval(u, UFL_ANON_FACTION)) fset(newunit, UFL_ANON_FACTION); fset(newunit, UFL_ISNEW); msg = msg_message("encounter_allies", "unit name", u, name); r_addmessage(r, u->faction, msg); msg_release(msg); }
/** * \brief Returns the damage power of the sword for the current attack. * * Redefine the function if your state changes the power of the sword * (typically for a spin attack). * * \return the current damage factor of the sword */ int Hero::State::get_sword_damage_factor() { static const int sword_factors[] = {0, 1, 2, 4, 8}; int sword = get_equipment().get_ability("sword"); return sword_factors[sword]; }