/** * @brief Makes the door immediately open or closed. * @param door_open true to make it opened, false to make it closed. */ void Door::set_open(bool door_open) { state = door_open ? OPEN : CLOSED; if (door_open) { set_collision_modes(COLLISION_NONE); // to avoid being the hero's facing entity } else { get_sprite().set_current_animation("closed"); set_collision_modes(COLLISION_FACING_POINT | COLLISION_SPRITE); // ensure that we are not closing the door on the hero if (is_on_map() && overlaps(get_hero())) { get_hero().avoid_collision(*this, (get_direction() + 2) % 4); } } if (is_on_map()) { update_dynamic_tiles(); if (is_saved()) { get_savegame().set_boolean(savegame_variable, door_open); } if (door_open) { get_lua_context().door_on_opened(*this); } else { get_lua_context().door_on_closed(*this); } } }
/** * \brief This function is called when the enemy has just been hurt. * \param source the source of the attack * \param attack the attack that was just successful * \param life_points the number of life points lost by this enemy */ void Enemy::notify_hurt(MapEntity& source, EnemyAttack attack, int life_points) { get_lua_context().enemy_on_hurt(*this, attack, life_points); if (get_life() <= 0) { get_lua_context().enemy_on_dying(*this); } }
/** * \brief Gives the item to the player. */ void Pickable::try_give_item_to_player() { EquipmentItem& item = treasure.get_item(); if (!can_be_picked || given_to_player || get_game().is_dialog_enabled() || !get_hero().can_pick_treasure(item)) { return; } given_to_player = true; remove_from_map(); // play the sound const std::string& sound_id = item.get_sound_when_picked(); if (!sound_id.empty()) { Sound::play(sound_id); } // give the item if (item.get_brandish_when_picked()) { // The treasure is brandished. // on_obtained() will be called after the dialog. get_hero().start_treasure(treasure, ScopedLuaRef()); } else { treasure.give_to_player(); // Call on_obtained() immediately since the treasure is not brandished. get_lua_context().item_on_obtained(item, treasure); get_lua_context().map_on_obtained_treasure(get_map(), treasure); } }
/** * \brief This function is called by the engine when there is a collision with another entity. * * If the entity is the hero, we allow him to interact with this entity. * * \param entity_overlapping the entity overlapping the detector * \param collision_mode the collision mode that detected the collision */ void Npc::notify_collision(Entity& entity_overlapping, CollisionMode collision_mode) { if (collision_mode == COLLISION_FACING && entity_overlapping.is_hero()) { Hero& hero = static_cast<Hero&>(entity_overlapping); if (get_commands_effects().get_action_key_effect() == CommandsEffects::ACTION_KEY_NONE && hero.is_free()) { if (subtype == USUAL_NPC // the hero can talk to usual NPCs from any direction || get_direction() == -1 || hero.is_facing_direction4((get_direction() + 2) % 4)) { // show the appropriate action icon get_commands_effects().set_action_key_effect(subtype == USUAL_NPC ? CommandsEffects::ACTION_KEY_SPEAK : CommandsEffects::ACTION_KEY_LOOK); } else if (can_be_lifted() && get_equipment().has_ability(Ability::LIFT)) { get_commands_effects().set_action_key_effect(CommandsEffects::ACTION_KEY_LIFT); } } } else if (collision_mode == COLLISION_OVERLAPPING && entity_overlapping.get_type() == EntityType::FIRE) { if (behavior == BEHAVIOR_ITEM_SCRIPT) { EquipmentItem& item = get_equipment().get_item(item_name); get_lua_context()->item_on_npc_collision_fire(item, *this); } else { get_lua_context()->npc_on_collision_fire(*this); } } }
/** * \brief Draws the entity on the map. */ void Enemy::draw_on_map() { if (!is_drawn()) { return; } get_lua_context().enemy_on_pre_draw(*this); Detector::draw_on_map(); get_lua_context().enemy_on_post_draw(*this); }
/** * \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); } }
/** * \copydoc MapEntity::notify_enabled */ void CustomEntity::notify_enabled(bool enabled) { Detector::notify_enabled(enabled); if (enabled) { get_lua_context().entity_on_enabled(*this); } else { get_lua_context().entity_on_disabled(*this); } }
/** * @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); } } } } }
/** * \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 Notifies this entity that it was just enabled or disabled. * \param enabled true if the entity is now enabled */ void Enemy::notify_enabled(bool enabled) { Detector::notify_enabled(enabled); if (enabled) { if (!initialized) { initialize(); } restart(); get_lua_context().enemy_on_enabled(*this); } else { get_lua_context().enemy_on_disabled(*this); } }
/** * \brief This function is called when the map is started and * the opening transition is finished. */ void Map::notify_opening_transition_finished() { visible_surface->set_opacity(255); // because the transition effect may have changed the opacity check_suspended(); entities->notify_map_opening_transition_finished(); get_lua_context().map_on_opening_transition_finished(*this, get_destination()); }
/** * \brief Initializes the enemy. */ void Enemy::initialize() { if (!initialized) { initialized = true; get_lua_context().run_enemy(*this); } }
/** * @brief Implementation of sol.menu.stop(). * @param l the Lua context that is calling this function * @return number of values to return to Lua */ int LuaContext::menu_api_stop(lua_State* l) { LuaContext& lua_context = get_lua_context(l); luaL_checktype(l, 1, LUA_TTABLE); int menu_ref = LUA_REFNIL; std::list<LuaMenuData>& menus = lua_context.menus; std::list<LuaMenuData>::iterator it; for (it = menus.begin(); it != menus.end(); it++) { int ref = it->ref; push_ref(l, ref); if (lua_equal(l, 1, -1)) { menu_ref = ref; lua_context.menu_on_finished(menu_ref); menus.erase(it); lua_context.destroy_ref(menu_ref); break; } } if (menu_ref == LUA_REFNIL) { push_string(l, "Unknown menu."); lua_error(l); } return 0; }
/** * \brief Notifies this entity that it was just enabled or disabled. * \param enabled true if the entity is now enabled */ void Enemy::notify_enabled(bool enabled) { Detector::notify_enabled(enabled); if (!is_on_map()) { return; } if (enabled) { restart(); get_lua_context().entity_on_enabled(*this); } else { get_lua_context().entity_on_disabled(*this); } }
/** * \copydoc Detector::notify_collision(MapEntity&,CollisionMode) */ void CustomEntity::notify_collision(MapEntity& entity_overlapping, CollisionMode collision_mode) { if (collision_mode == COLLISION_FACING) { // This collision mode is only useful to set the facing entity, which // is already done by Detector. return; } // One or several collisions were detected with an another entity. // The collision tests could have been of any kind // (even a custom Lua collision test function), // except COLLISION_SPRITE that is handled separately. Debug::check_assertion(collision_mode == COLLISION_CUSTOM, "Unexpected collision mode"); // There is a collision: execute the callbacks. for (const CollisionInfo& info: successful_collision_tests) { get_lua_context().do_custom_entity_collision_callback( info.get_callback_ref(), *this, entity_overlapping ); } successful_collision_tests.clear(); }
/** * \brief Registers a Lua function to decide if this custom entity can traverse * other entities. * * This applies to entities that are not overridden by * set_can_traverse_entities(EntityType, bool) * or * set_can_traverse_entities(EntityType, const ScopedLuaRef&). * * \param traversable_test_ref Lua ref to a function that will do the test. */ void CustomEntity::set_can_traverse_entities(const ScopedLuaRef& traversable_test_ref) { can_traverse_entities_general = TraversableInfo( get_lua_context(), traversable_test_ref ); }
/** * \brief Sets whether this custom entity can traverse other entities. * * This applies to entities that are not overridden by * set_can_traverse_entities(EntityType, bool) * or * set_can_traverse_entities(EntityType, const ScopedLuaRef&). * * \param traversable \c true to allow this entity to traverse other entities. */ void CustomEntity::set_can_traverse_entities(bool traversable) { can_traverse_entities_general = TraversableInfo( get_lua_context(), traversable ); }
/** * @brief Sets a Lua function to be called when this destructible item is * destroyed. * @param destruction_callback_ref a Lua ref to the callback in the registry * (if you pass LUA_REFNIL, this function removes the previous callback that * was set, if any) */ void Destructible::set_destruction_callback(int destruction_callback_ref) { if (this->destruction_callback_ref != LUA_REFNIL) { get_lua_context().cancel_callback(this->destruction_callback_ref); } this->destruction_callback_ref = destruction_callback_ref; }
/** * \brief Suspends or resumes the movement and animations of the entities. * * This function is called when the game is being suspended * or resumed. * * \param suspended true to suspend the movement and the animations, * false to resume them */ void Map::set_suspended(bool suspended) { this->suspended = suspended; entities->set_suspended(suspended); get_lua_context().notify_map_suspended(*this, suspended); }
/** * \brief Updates the chest. * * This function is called repeatedly by the map. * This is a redefinition of MapEntity::update() * the handle the chest opening. */ void Chest::update() { if (is_open() && !is_suspended()) { if (!treasure_given && treasure_date != 0 && System::now() >= treasure_date) { treasure_date = 0; treasure.ensure_obtainable(); // Make the chest empty if the treasure is not allowed. if (!treasure.is_empty()) { // Give a treasure to the player. get_hero().start_treasure(treasure, ScopedLuaRef()); treasure_given = true; } else { // The chest is empty. if (treasure.is_saved()) { // Mark the treasure as found in the savegame. get_savegame().set_boolean(treasure.get_savegame_variable(), true); } treasure_given = true; bool done = get_lua_context().chest_on_empty(*this); if (!done) { // The script does not define any behavior: // by default, do nothing and unfreeze the hero. get_hero().start_free(); } } } } MapEntity::update(); }
/** * \copydoc MapEntity::notify_creating */ void CustomEntity::notify_creating() { Detector::notify_creating(); ground_modifier = false; get_lua_context().run_custom_entity(*this); }
/** * \brief Implementation of sol.surface.create(). * \param l the Lua context that is calling this function * \return number of values to return to Lua */ int LuaContext::surface_api_create(lua_State* l) { Surface* surface = NULL; if (lua_gettop(l) == 0) { // create an empty surface with the screen size surface = new Surface(VideoManager::get_instance()->get_quest_size()); } else if (lua_type(l, 1) == LUA_TNUMBER) { // create an empty surface with the specified size int width = luaL_checkint(l, 1); int height = luaL_checkint(l, 2); surface = new Surface(width, height); } else if (lua_type(l, 1) == LUA_TSTRING) { // load from a file const std::string& file_name = lua_tostring(l, 1); bool language_specific = lua_toboolean(l, 2); // default is false surface = Surface::create_from_file(file_name, language_specific ? Surface::DIR_LANGUAGE : Surface::DIR_SPRITES); } else { luaL_typerror(l, 1, "number, string or no value"); } if (surface == NULL) { // Image file not found or not valid. lua_pushnil(l); } else { get_lua_context(l).add_drawable(surface); push_surface(l, *surface); } return 1; }
/** * \brief Implementation of drawable:fade_out(). * \param l The Lua context that is calling this function. * \return Number of values to return to Lua. */ int LuaContext::drawable_api_fade_out(lua_State* l) { uint32_t delay = 20; int callback_ref = LUA_REFNIL; Drawable& drawable = check_drawable(l, 1); if (lua_gettop(l) >= 2) { // the second argument can be the delay or the callback int index = 2; if (lua_isnumber(l, index)) { delay = lua_tonumber(l, index); index++; } // the next argument (if any) is the callback if (lua_gettop(l) >= index) { luaL_checktype(l, index, LUA_TFUNCTION); lua_settop(l, index); callback_ref = luaL_ref(l, LUA_REGISTRYINDEX); } } TransitionFade* transition = new TransitionFade( Transition::TRANSITION_CLOSING, drawable.get_transition_surface()); transition->set_delay(delay); drawable.start_transition(*transition, callback_ref, &get_lua_context(l)); return 0; }
/** * \brief Detects whether there were input events and if yes, handles them. */ void MainLoop::check_input() { // Check SDL events. std::unique_ptr<InputEvent> event = InputEvent::get_event(); while (event != nullptr) { notify_input(*event); event = InputEvent::get_event(); } // Check Lua requests. if (!lua_commands.empty()) { std::lock_guard<std::mutex> lock(lua_commands_mutex); for (const std::string& command : lua_commands) { std::cout << "\n"; // To make sure that the command delimiter starts on a new line. Logger::info("====== Begin Lua command #" + String::to_string(num_lua_commands_done) + " ======"); const bool success = LuaTools::do_string(get_lua_context().get_internal_state(), command, "Lua command"); if (success) { std::cout << "\n"; Logger::info("====== End Lua command #" + String::to_string(num_lua_commands_done) + ": success ======"); } else { std::cout << "\n"; Logger::info("====== End Lua command #" + String::to_string(num_lua_commands_done) + ": error ======"); } ++num_lua_commands_done; } lua_commands.clear(); } }
/** * \brief Updates this entity. */ void Destructible::update() { MapEntity::update(); if (is_suspended()) { return; } if (is_being_cut && get_sprite().is_animation_finished()) { if (!get_can_regenerate()) { // Remove this destructible from the map. remove_from_map(); } else { is_being_cut = false; regeneration_date = System::now() + 10000; } } else if (is_waiting_for_regeneration() && System::now() >= regeneration_date && !overlaps(get_hero())) { get_sprite().set_current_animation("regenerating"); is_regenerating = true; regeneration_date = 0; get_lua_context().destructible_on_regenerating(*this); } else if (is_regenerating && get_sprite().is_animation_finished()) { get_sprite().set_current_animation("on_ground"); is_regenerating = false; } }
/** * \brief Notifies the game objects that the another map has just become active. */ void Game::notify_map_changed() { // Call game:on_map_changed() in Lua. get_lua_context().game_on_map_changed(*this, *current_map); // Notify the equipment. get_equipment().notify_map_changed(*current_map); }
/** * \brief Creates an explosion on this object. */ void Destructible::explode() { get_entities().add_entity(std::make_shared<Explosion>( "", get_layer(), get_xy(), true )); Sound::play("explosion"); get_lua_context().destructible_on_exploded(*this); }
/** * \brief Notifies this enemy that it should restart his movement. * * This function is called when the enemy needs to restart its movement * because something happened (for example the enemy has just been created, * or it was just hurt). * By default, the "walking" animation is set on the enemy's sprites. */ void Enemy::restart() { if (is_immobilized()) { stop_immobilized(); } set_animation("walking"); get_lua_context().enemy_on_restarted(*this); }
/** * \brief Implementation of timer:stop(). * \param l the Lua context that is calling this function * \return number of values to return to Lua */ int LuaContext::timer_api_stop(lua_State* l) { LuaContext& lua_context = get_lua_context(l); Timer& timer = check_timer(l, 1); lua_context.remove_timer(&timer); return 0; }
/** * \brief This function is called when this enemy detects a collision with another enemy. * \param other the other enemy * \param other_sprite the other enemy's sprite that overlaps a sprite of this enemy * \param this_sprite this enemy's sprite that overlaps the other */ void Enemy::notify_collision_with_enemy(Enemy& other, Sprite& other_sprite, Sprite& this_sprite) { if (is_in_normal_state()) { get_lua_context().enemy_on_collision_enemy( *this, other, other_sprite, this_sprite); } }