/** * \brief Creates a collision test info. * \param lua_context The Lua context. * \param collision_test_ref Lua ref to a custom collision test. * \param callback_ref Lua ref to a function to call when this collision is * detected. */ CustomEntity::CollisionInfo::CollisionInfo( LuaContext& lua_context, const ScopedLuaRef& custom_test_ref, const ScopedLuaRef& callback_ref ): lua_context(&lua_context), built_in_test(COLLISION_CUSTOM), custom_test_ref(custom_test_ref), callback_ref(callback_ref) { Debug::check_assertion(!callback_ref.is_empty(), "Missing callback ref"); }
/** * \brief Registers a function to be called when the specified test detects a * collision. * \param collision_test_ref Lua ref to a custom collision test. * \param callback_ref Lua ref to a function to call when this collision is * detected. */ void CustomEntity::add_collision_test( const ScopedLuaRef& collision_test_ref, const ScopedLuaRef& callback_ref ) { Debug::check_assertion(!callback_ref.is_empty(), "Missing collision callback"); add_collision_mode(COLLISION_CUSTOM); collision_tests.emplace_back( get_lua_context(), collision_test_ref, callback_ref ); }
/** * \brief Creates a new music. * \param music_id Id of the music (file name without extension). * \param loop Whether the music should loop when reaching its end. * \param callback_ref Lua function to call when the music ends * or an empty ref. * There cannot be both a loop and a callback at the same time. */ Music::Music( const std::string& music_id, bool loop, const ScopedLuaRef& callback_ref): id(music_id), format(OGG), loop(loop), callback_ref(callback_ref), source(AL_NONE) { Debug::check_assertion(!loop || callback_ref.is_empty(), "Attempt to set both a loop and a callback to music" ); for (int i = 0; i < nb_buffers; i++) { buffers[i] = AL_NONE; } }
/** * \brief Registers a function to be called when the specified test detects a * collision. * \param collision_test A built-in collision test. * \param callback_ref Lua ref to a function to call when this collision is * detected. */ void CustomEntity::add_collision_test( CollisionMode collision_test, const ScopedLuaRef& callback_ref ) { Debug::check_assertion(collision_test != COLLISION_NONE, "Invalid collision mode"); Debug::check_assertion(!callback_ref.is_empty(), "Missing collision callback"); if (collision_test == COLLISION_SPRITE) { add_collision_mode(COLLISION_SPRITE); } else { add_collision_mode(COLLISION_CUSTOM); } collision_tests.emplace_back( get_lua_context(), collision_test, callback_ref ); }
/** * \brief Registers a timer into a context (table or a userdata). * \param timer A timer. * \param context_index Index of the table or userdata in the stack. * \param callback_ref Lua ref to the function to call when the timer finishes. */ void LuaContext::add_timer( const TimerPtr& timer, int context_index, const ScopedLuaRef& callback_ref ) { const void* context; if (lua_type(l, context_index) == LUA_TUSERDATA) { ExportableToLuaPtr* userdata = static_cast<ExportableToLuaPtr*>( lua_touserdata(l, context_index) ); context = userdata->get(); } else { context = lua_topointer(l, context_index); } callback_ref.push(); #ifndef NDEBUG // Sanity check: check the uniqueness of the ref. for (const auto& kvp: timers) { if (kvp.second.callback_ref.get() == callback_ref.get()) { std::ostringstream oss; oss << "Callback ref " << callback_ref.get() << " is already used by a timer (duplicate luaL_unref?)"; Debug::die(oss.str()); } } #endif Debug::check_assertion(timers.find(timer) == timers.end(), "Duplicate timer in the system"); timers[timer].callback_ref = callback_ref; timers[timer].context = context; Game* game = main_loop.get_game(); if (game != nullptr) { // We are during a game: depending on the timer's context, // suspend the timer or not. if (is_map(l, context_index) || is_entity(l, context_index) || is_item(l, context_index)) { bool initially_suspended = false; // By default, we want the timer to be automatically suspended when a // camera movement, a dialog or the pause menu starts. if (!is_entity(l, context_index)) { // The timer normally gets suspended/resumed with the map. timer->set_suspended_with_map(true); // But in the initial state, we override that rule. // We initially suspend the timer only during a dialog. // In particular, we don't want to suspend timers created during a // camera movement. // This would be very painful for users. initially_suspended = game->is_dialog_enabled(); } else { // Entities are more complex: they also get suspended when disabled // and when far from the camera. Therefore, they don't simply follow // the map suspended state. EntityPtr entity = check_entity(l, context_index); initially_suspended = entity->is_suspended() || !entity->is_enabled(); } timer->set_suspended(initially_suspended); } } }
/** * \brief Opens the dialog box to show a dialog. * * No other dialog should be already running. * * \param dialog_id Id of the dialog to show. * \param info_ref Lua ref to an optional info parameter to pass to the * dialog box, or an empty ref. * \param callback_ref Lua ref to a function to call when the dialog finishes, * or an empty ref. */ void DialogBoxSystem::open( const std::string& dialog_id, const ScopedLuaRef& info_ref, const ScopedLuaRef& callback_ref ) { Debug::check_assertion(!is_enabled(), "A dialog is already active"); this->dialog_id = dialog_id; this->dialog = DialogResource::get_dialog(dialog_id); this->callback_ref = callback_ref; // Save commands. KeysEffect& keys_effect = game.get_keys_effect(); keys_effect.save_action_key_effect(); keys_effect.set_action_key_effect(KeysEffect::ACTION_KEY_NONE); keys_effect.save_sword_key_effect(); keys_effect.set_sword_key_effect(KeysEffect::SWORD_KEY_NONE); keys_effect.save_pause_key_effect(); keys_effect.set_pause_key_effect(KeysEffect::PAUSE_KEY_NONE); // A dialog was just started: notify Lua. LuaContext& lua_context = game.get_lua_context(); lua_State* l = lua_context.get_internal_state(); built_in = !lua_context.notify_dialog_started( game, dialog, info_ref ); if (built_in) { // Show a built-in default dialog box. keys_effect.set_action_key_effect(KeysEffect::ACTION_KEY_NEXT); // Prepare the text. std::string text = dialog.get_text(); this->is_question = false; if (dialog_id == "_shop.question") { // Built-in dialog with the "do you want to buy" question and the price. this->is_question = true; size_t index = text.find("$v"); if (index != std::string::npos) { // Replace the special sequence '$v' by the price of the shop item. info_ref.push(); int price = LuaTools::check_int(l, -1); lua_pop(l, -1); std::ostringstream oss; oss << price; text = text.replace(index, 2, oss.str()); } } remaining_lines.clear(); std::istringstream iss(text); std::string line; while (std::getline(iss, line)) { remaining_lines.push_back(line); } // Determine the position. const Rectangle& camera_position = game.get_current_map().get_camera_position(); bool top = false; if (game.get_hero()->get_y() >= camera_position.get_y() + 130) { top = true; } int x = camera_position.get_width() / 2 - 110; int y = top ? 32 : camera_position.get_height() - 96; text_position = { x, y }; // Start showing text. show_more_lines(); } }