Управление игровыми событиями в Онлайн-Игре
Классы EventActivationHandler и EventAction играют ключевую роль в динамическом управлении событиями в нашей онлайн-игре. Они обеспечивают активацию, управление и оповещение игроков о текущих событиях в игровом мире. Давайте подробно рассмотрим их функционал и возможности.
1. Класс EventActivationHandler
Инициализация и Настройка
- Инициализируется с экземплярами моделей для работы с событиями и биомами.
- Настраивает Telegram API для отправки уведомлений игрокам.
- Часовой пояс установлен на ‘Europe/Kiev’ для соответствия локальному времени.
Метод process()
- Активация событий: Определяет доступные для активации события, исключая уже активные на текущей неделе.
- Фильтрация: Выбирает события с учётом их частоты проведения и последней активации.
- Уведомления: Отправляет информацию об активированных событиях всем игрокам через Telegram.
Управление Статусами
- Обновление статусов истекших событий, автоматически переводя их в состояние «завершено».
2. Класс EventAction
Взаимодействие с Игроками
- Обрабатывает нажатия на кнопку «События» в телеграм-боте, предоставляя игрокам актуальную информацию о текущих событиях.
Информирование об Событиях
- Отображение списка активных событий, их описаний, типов, влияния на персонажа и времени окончания.
- Динамическое сообщение об активных событиях или уведомление об их отсутствии.
Выводы
Классы EventActivationHandler и EventAction обеспечивают эффективное управление игровыми событиями, автоматизируя их активацию и информирование игроков. Это позволяет создавать динамичный и вовлекающий игровой процесс, адаптируя игру к текущему времени и предпочтениям игроков. Использование данных классов значительно повышает качество игрового опыта, делая каждый сеанс уникальным и непредсказуемым.
P.S. Код самих классов:
<?php
namespace App\Controllers\Telegram\Commands\Actions;
use Longman\TelegramBot\Request;
use Longman\TelegramBot\Entities\ServerResponse;
use App\Models\CharacterModel;
use App\Models\ActiveEventModel;
use App\Models\EventModel;
use DateTime;
class EventAction extends BaseAction
{
protected $characterModel;
protected $activeEvents;
protected $events;
public function __construct($callbackQuery)
{
parent::__construct($callbackQuery);
$this->characterModel = new CharacterModel();
$this->activeEvents = new ActiveEventModel();
$this->events = new EventModel();
}
public function handle(): ServerResponse
{
[$user, $character] = $this->getUserAndCharacter();
if (!$user || !$character) {
return Request::sendMessage([
'chat_id' => $this->callbackQuery->getMessage()->getChat()->getId(),
'text' => 'Пользователь не найден в базе данных или персонаж не определён.',
]);
}
// Получаем активные события из таблицы `active_events`
$activeEvents = $this->activeEvents->where('status', 'active')->findAll();
$text = "🌟 *На текущий момент в мире развернулись следующие события:* 🌟\n\n";
if (!empty($activeEvents)) {
foreach ($activeEvents as $index => $event) {
// Из таблицы `events` получаем детали события по ID
$eventDetails = $this->events->find($event['event_id']);
$endTime = new DateTime($event['end_time']);
$now = new DateTime();
$interval = $now->diff($endTime);
// Формирование строки события
$text .= "№" . ($index + 1) . " *{$eventDetails['name']}*\n\n"
. "📜 *Описание:* _{$eventDetails['description']}_\n\n"
. "🌍 *Тип события:* _" . $this->translateEventType($eventDetails['event_type']) . "_\n\n"
. "🚀 *Влияние на персонажа:* _" . $this->translateEffectType($eventDetails['effect_type']) . "_\n\n"
. "⏳ *Событие закончится через:* _" . $interval->format('%H чс. %I мин.') . "_\n\n";
}
} else {
$text = "🎉 На текущий момент активных событий нет. Исследуйте мир и готовьтесь к новым приключениям! 🎉";
}
$keyboard = $this->getKeyboard();
Request::answerCallbackQuery(['callback_query_id' => $this->callbackQuery->getId()]);
return Request::sendMessage([
'chat_id' => $this->callbackQuery->getMessage()->getChat()->getId(),
'text' => $text,
'parse_mode' => 'Markdown',
'reply_markup' => json_encode($keyboard),
]);
}
protected function translateEventType($type)
{
$translations = [
'local' => 'Выборочно в указанных биомах',
'global' => 'Повсеместно в указанных биомах'
];
return $translations[$type] ?? $type;
}
protected function translateEffectType($type)
{
$translations = [
'damage' => 'Урон',
'heal' => 'Лечение',
'buff' => 'Усиление',
'debuff' => 'Ослабление',
'none' => 'Без эффекта'
];
return $translations[$type] ?? $type;
}
protected function getKeyboard()
{
// Метод для получения клавиатуры, если нужно изменить структуру
return [
'inline_keyboard' => [
[
['text' => '🎮 Развлечения', 'callback_data' => 'entertainment'],
['text' => '🧑🌾 Действия 🛠️', 'callback_data' => 'characterActions'],
['text' => '🎉 События', 'callback_data' => 'events']
],
[
['text' => '🎒 Инвентарь', 'callback_data' => 'inventory'],
['text' => '🛒 Магазин', 'callback_data' => 'shop'],
]
]
];
}
}
<?php
namespace App\TaskHandlers;
use App\Models\ActiveEventModel;
use App\Models\EventModel;
use App\Models\BiomeModel;
use CodeIgniter\I18n\Time;
use Longman\TelegramBot\Request;
use Longman\TelegramBot\Telegram;
use Longman\TelegramBot\Exception\TelegramException;
use App\Models\TelegramUserModel;
class EventActivationHandler
{
protected $activeEventModel;
protected $eventModel;
protected $biomeModel;
protected $telegramUserModel;
private $telegram;
protected $startOfWeek;
protected $endOfWeek;
protected $now;
public function __construct()
{
$this->activeEventModel = new ActiveEventModel();
$this->eventModel = new EventModel();
$this->biomeModel = new BiomeModel();
$this->telegramUserModel = new TelegramUserModel();
$API_KEY = getenv('telegram.API_KEY');
$BOT_USERNAME = getenv('telegram.BOT_USERNAME');
try {
$this->telegram = new Telegram($API_KEY, $BOT_USERNAME);
Request::initialize($this->telegram);
} catch (TelegramException $e) {
log_message('error', $e->getMessage());
}
$this->now = Time::now('Europe/Kiev');
// День недели: 0 (воскресенье) - 6 (суббота)
$dayOfWeek = $this->now->getDayOfWeek();
// CodeIgniter начинает неделю с воскресенья, нам нужно адаптировать логику, если ваша неделя начинается с понедельника
$this->startOfWeek = $this->now->subDays($dayOfWeek)->setTime(0, 0, 0);
$this->endOfWeek = $this->now->addDays(7 - $dayOfWeek - 1)->setTime(23, 59, 59);
}
public function process() {
// Обновление статусов истекших событий
$this->updateExpiredEventsStatus();
// Получаем доступные для активации события, исключая уже активированные на этой неделе
$availableEvents = $this->eventModel->whereNotIn('event_id', function($builder) {
return $builder->select('event_id')
->from('active_events')
->where('start_time >=', $this->startOfWeek)
->where('start_time <=', $this->endOfWeek);
})->findAll();
// Проверяем время последней активации
if (!$this->isRecentActivationTooClose()) {
die();
}
// Фильтрация событий с учетом частоты активации
$filteredEvents = array_filter($availableEvents, function($event) {
$activationCountThisWeek = $this->getActivationCountThisWeek($event['event_id'], $this->startOfWeek, $this->endOfWeek);
return $activationCountThisWeek < $event['frequency_per_week'];
});
// Выбор и активация случайного события
if (!empty($filteredEvents)) {
$randomEvent = $filteredEvents[array_rand($filteredEvents)];
$this->activateEvent($randomEvent);
$this->notifyPlayersAboutEvent($randomEvent);
}
}
protected function getActivationCountThisWeek($eventId, $startOfWeek, $endOfWeek) {
return $this->activeEventModel
->where('event_id', $eventId)
->where('start_time >=', $startOfWeek)
->where('start_time <=', $endOfWeek)
->countAllResults();
}
protected function updateExpiredEventsStatus() {
// Получаем все активные события, у которых время окончания уже прошло
$expiredEvents = $this->activeEventModel
->where('status', 'active')
->where('end_time <', $this->now)
->findAll();
foreach ($expiredEvents as $event) {
// Обновляем статус события на 'completed'
$this->activeEventModel->update($event['id'], ['status' => 'completed']);
}
}
protected function getLastActivationDay($eventId) {
// Ищем последнее активное событие с данным ID
$lastActiveEvent = $this->activeEventModel
->where('event_id', $eventId)
->orderBy('created_at', 'DESC')
->first();
if ($lastActiveEvent) {
// Если событие найдено, возвращаем день недели его активации
$lastActivationTime = Time::createFromFormat('Y-m-d H:i:s', $lastActiveEvent['created_at'], 'Europe/Kiev');
return $lastActivationTime->getDayOfWeek();
}
return 0;
}
protected function activateEvent($event) {
$data = [
'event_id' => $event['event_id'],
'start_time' => $this->now->toDateTimeString(),
'end_time' => $this->now->addMinutes($event['duration'])->toDateTimeString(),
'status' => 'active',
'effect_applied' => false,
];
$this->activeEventModel->insert($data);
}
protected function isRecentActivationTooClose() {
// Получаем время последнего активированного события
$lastActiveEvent = $this->activeEventModel
->select('start_time')
->orderBy('start_time', 'DESC')
->first();
if ($lastActiveEvent) {
$lastActiveEventTime = Time::createFromFormat('Y-m-d H:i:s', $lastActiveEvent['start_time'], 'Europe/Kiev');
$timeDifference = $this->now->difference($lastActiveEventTime);
// Возвращает true, если последнее событие было активировано более чем 45 минут назад
return $timeDifference->getMinutes() > 45;
}
// Если нет активированных событий, то ограничение по времени не применяется
return false;
}
protected function notifyPlayersAboutEvent($event) {
$message = '';
$allBiomes = json_decode($event['biome_ids'], true); // Декодирование строки JSON в массив
$biomsStr = '';
$imgPath = $event['img_path'];
foreach ($allBiomes as $biom) {
$biomsStr .= "🔹 {$this->biomeModel->where('id', $biom)->first()['name']}\n";
}
// Форматирование продолжительности
$durationFormatted = $this->formatDuration($event['duration']);
// Перевод эффекта влияния
$effectTypeTranslated = $this->translateEffectType($event['effect_type']);
// Перевод типа территории
$territoryTranslated = $this->translateTerritory($event['event_type']);
$message .= "⚠️ *Внимание, началось событие: ➡️ {$event['name']}.*\n\n";
$message .= "ℹ️ *Описание:*\n";
$message .= "_{$event['description']}_\n\n";
$message .= "⌛️ Продлится : $durationFormatted\n\n";
$message .= "🔔 Эффект влияния: $effectTypeTranslated\n\n";
$message .= "🗾 Территория: $territoryTranslated\n\n";
$message .= $biomsStr;
$message .= "\nПринимайте меры, если вы не готовы к таковому!";
$allTelegramUsers = $this->telegramUserModel->findAll();
$counter = 0; // Счетчик отправленных сообщений
foreach ($allTelegramUsers as $telegramUser) {
if ($counter > 0 && $counter % 50 == 0) { // После каждых 50 сообщений
sleep(5); // Пауза на 5 секунд
}
$this->sendMessageToAllUsers($message, $imgPath, $telegramUser['telegram_id']);
$counter++;
}
}
protected function formatDuration($minutes) {
$days = floor($minutes / 1440);
$hours = floor(($minutes - $days * 1440) / 60);
$mins = $minutes % 60;
return "{$days} дн. {$hours} чс. {$mins} мин.";
}
protected function translateEffectType($type) {
$translations = [
'damage' => 'Урон',
'heal' => 'Лечение',
'buff' => 'Усиление',
'debuff' => 'Ослабление',
'none' => 'Без эффекта'
];
return $translations[$type] ?? $type;
}
protected function translateTerritory($territory) {
$translations = [
'local' => 'Выборочно в указанных биомах',
'global' => 'Повсеместно в указанных биомах'
];
return $translations[$territory] ?? $territory;
}
protected function sendMessageToAllUsers($message, $imgPath, $chatId) {
$keyboard = [
'inline_keyboard' => [
[
['text' => '👨🎤 Персонаж', 'callback_data' => 'character'],
['text' => '🧑🌾 Действия 🛠️', 'callback_data' => 'characterActions'],
['text' => '🎉 События', 'callback_data' => 'events']
]
]
];
Request::answerCallbackQuery(['callback_query_id' => $chatId]);
try {
$imagePath = base_url($imgPath); // Предполагается, что $imgPath - это локальный путь к файлу
$result = Request::sendPhoto([
'chat_id' => $chatId,
'photo' => Request::encodeFile($imagePath),
'caption' => $message,
'parse_mode' => 'Markdown',
'reply_markup' => json_encode($keyboard),
]);
} catch (TelegramException $e) {
log_message('error', "Ошибка при отправке фото: " . $e->getMessage());
// Можете отправить сообщение без изображения или с другим изображением
}
}
}
