Вход Регистрация
Файл: system/automation.php
Строк: 463
<?php
/**
 * Единый игровой календарь.
 * Все периодические циклы запускаются из одного фонового обработчика и защищены
 * от повторной выдачи наград/повторного завершения одним и тем же period_key.
 */
declare(strict_types=1);

function 
game_automation_bootstrap(): void {
    static 
$done=false;
    if(
$done) return;
    
$done=true;
    
$probe=db_query("SHOW TABLES LIKE 'automation_log'");
    if(
$probe && db_num_rows($probe)>0) return;
    
db_query("CREATE TABLE IF NOT EXISTS `automation_log` (
        `code` varchar(64) NOT NULL,
        `period_key` varchar(64) NOT NULL,
        `created_at` int NOT NULL,
        PRIMARY KEY (`code`,`period_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
}

function 
game_automation_once(string $codestring $periodKey, callable $callback): bool {
    
game_automation_bootstrap();
    
$lockName='game:'.substr(preg_replace('/[^a-z0-9:_-]/i','_'$code.':'.$periodKey) ?: 'job',0,60);
    
$got=(int)(db_fetch_one('SELECT GET_LOCK(:n,0) AS l',['n'=>$lockName])['l'] ?? 0);
    if(
$got!==1) return false;
    
$pdo=db_pdo();
    try {
        
$pdo->beginTransaction();
        
$exists=db_fetch_one('SELECT 1 FROM `automation_log` WHERE `code`=:c AND `period_key`=:p LIMIT 1 FOR UPDATE',['c'=>$code,'p'=>$periodKey]);
        if(
$exists){ $pdo->commit(); return false; }
        
$callback();
        
db_execute('INSERT INTO `automation_log` (`code`,`period_key`,`created_at`) VALUES (:c,:p,:t)',['c'=>$code,'p'=>$periodKey,'t'=>time()]);
        
$pdo->commit();
        return 
true;
    } catch(
Throwable $e) {
        if(
$pdo->inTransaction()) $pdo->rollBack();
        throw 
$e;
    } finally {
        try { 
db_fetch_one('SELECT RELEASE_LOCK(:n) AS r',['n'=>$lockName]); } catch(Throwable) {}
    }
}

function 
game_automation_table_exists(string $table): bool {
    
$row=db_fetch_one('SELECT COUNT(*) AS c FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=:t',['t'=>$table]);
    return (int)(
$row['c'] ?? 0)>0;
}

function 
game_automation_dragon_revive(): void {
    if(!
game_automation_table_exists('aluko')) return;
    
$row=db_fetch_one('SELECT `id`,`health` FROM `aluko` ORDER BY `id` LIMIT 1');
    if(!
$row || (int)$row['health']>0) return;
    
$hp=random_int(1500000,3000000);
    
db_execute('UPDATE `aluko` SET `health`=:h,`max_health`=:h2 WHERE `id`=:i',['h'=>$hp,'h2'=>$hp,'i'=>(int)$row['id']]);
}

function 
game_automation_league_daily(): void {
    if(!
game_automation_table_exists('users')) return;
    
$rows=db_prepare('SELECT `id`,`league_place` FROM `users` WHERE `league_place`<1000')->fetchAll();
    foreach(
$rows as $row){
        
$place=(int)$row['league_place'];
        if(
$place>850$next=1000;
        else 
$next=min(1000,$place+random_int(200,240));
        
db_execute('UPDATE `users` SET `league_place`=:p WHERE `id`=:u',['p'=>$next,'u'=>(int)$row['id']]);
    }
    
db_execute('UPDATE `users` SET `league_fights`=25');
}

function 
game_automation_league_reseed(): void {
    if(!
game_automation_table_exists('users')) return;
    
db_execute("UPDATE `users` SET `league_place`=1000,`league_fights`=25,`league`=CASE
        WHEN `level`<=20 THEN 1
        WHEN `level`<=30 THEN 2
        WHEN `level`<=50 THEN 3
        WHEN `level`<=80 THEN 4
        WHEN `level`<=100 THEN 5
        ELSE 6 END"
);
}

function 
game_automation_chests_daily(): void {
    if(
game_automation_table_exists('sunduk')) db_query('DELETE FROM `sunduk`');
}

function 
game_automation_runtime_cleanup(): void {
    
$root=dirname(__DIR__);
    
$logs=$root.'/logs';
    foreach([
'app-error-*.log','php-error-*.log'] as $pattern){
        
$files=(array)glob($logs.'/'.$pattern);
        
usort($files,static fn($a,$b)=>(filemtime($b)?:0)<=>(filemtime($a)?:0));
        foreach(
array_slice($files,2) as $file) @unlink($file);
    }
    
$tmp=sys_get_temp_dir().'/game_rate';
    
$now=time();
    foreach((array)
glob($tmp.'/*.json') as $file){
        if(
$now-(filemtime($file)?:$now)>172800) @unlink($file);
    }
}

function 
game_automation_clan_dungeon(bool $open): void {
    if(!
game_automation_table_exists('clans')) return;
    if(
$open){
        
db_execute('UPDATE `clans` SET `clandungeon`=1');
        return;
    }
    
db_execute('UPDATE `clans` SET `clandungeon`=0');
    if(
game_automation_table_exists('clan_dungeon')) db_query('DELETE FROM `clan_dungeon`');
    if(
game_automation_table_exists('clan_dungeon_user')) db_query('DELETE FROM `clan_dungeon_user`');
    if(
game_automation_table_exists('users')) db_execute('UPDATE `users` SET `fight_duengeon_time`=0');
}

function 
game_automation_clan_battle(bool $open): void {
    if(!
game_automation_table_exists('clans')) return;
    if(
game_automation_table_exists('clan_battle')) db_query('DELETE FROM `clan_battle`');
    if(
game_automation_table_exists('clan_battle_log')) db_query('DELETE FROM `clan_battle_log`');
    if(
$opendb_execute('UPDATE `clans` SET `battle`=0,`battle_cool`=1,`global_battle`=:t',['t'=>time()+172800]);
    else 
db_execute('UPDATE `clans` SET `battle`=0,`battle_cool`=1,`global_battle`=0');
}

function 
game_automation_mail(int $uid,string $text): void {
    if(
$uid<=|| !game_automation_table_exists('mail')) return;
    
db_execute('INSERT INTO `mail` (`from`,`to`,`text`,`time`,`read`) VALUES (0,:u,:x,:t,0)',['u'=>$uid,'x'=>$text,'t'=>time()]);
}

function 
game_automation_finalize_coliseum(string $weekKey): void {
    if(!
game_automation_table_exists('users')) return;
    
$rows=db_prepare('SELECT `id`,`login`,`r`,`skull` FROM `users` WHERE `skull`>0 ORDER BY `skull` DESC,`id` ASC LIMIT 10')->fetchAll();
    
$news="[center][b][color=#e8c66a]Турнир Колизея завершён[/color][/b][/center]nn";
    if(!
$rows){
        
$news.="[center]На этой неделе призовые места остались свободны.[/center]n";
    } else {
        
$place=0;
        foreach(
$rows as $row){
            
$place++; $skulls=max(0,(int)$row['skull']); $rewardSkulls=min(1000,$skulls);
            
$gold=$skulls>=250?1000:($skulls>=125?750:500);
            
$silver=min(2000000000,72*$rewardSkulls); $exp=min(9000000000000000000,108*$rewardSkulls);
            
db_execute('UPDATE `users` SET `g`=`g`+:g,`s`=LEAST(2000000000,`s`+:s),`exp`=LEAST(9000000000000000000,`exp`+:e) WHERE `id`=:u',[
                
'g'=>$gold,'s'=>$silver,'e'=>$exp,'u'=>(int)$row['id']
            ]);
            
game_automation_mail((int)$row['id'],'[b]Турнир Колизея завершён.[/b]nВаш результат: '.$skulls.' черепов.nНаграда: '.$gold.' золота, '.$silver.' серебра и '.$exp.' опыта.');
            
$news.=$place.'. [url=/user/'.(int)$row['id'].'/]'.(string)$row['login'].'[/url] — [b]'.$skulls.'[/b] черепов, награда [color=#ffd66b]'.$gold.' золота[/color]n';
        }
    }
    if(
function_exists('game_forum_admin_news')) game_forum_admin_news('Итоги турнира Колизея',$news."n[center][url=/coliseum/]Вернуться в Колизей[/url][/center]");
    
db_execute('UPDATE `users` SET `rang`=25,`rang_star`=0,`skull`=0');
}

function 
game_automation_finalize_valor(string $periodKey): void {
    if(!
game_automation_table_exists('users') || !game_automation_table_exists('clans')) return;
    
// Сезонные знаки доблести получают все герои, достигшие порогов сезона.
    
db_execute("UPDATE `users` SET `med`=CASE
        WHEN `lvl_dobl`>=75 THEN 4
        WHEN `lvl_dobl`>=50 THEN 3
        WHEN `lvl_dobl`>=25 THEN 2
        WHEN `lvl_dobl`>=10 THEN 1
        ELSE 0 END"
);
    
db_execute('UPDATE `clans` SET `g`=`g`+1000,`exp`=`exp`+5000000 WHERE `dobl`>=250');
    
$news="[center][b][color=#e8c66a]Сезон доблести завершён[/color][/b][/center]nn";
    
$news.="[b]Лучшие кланы[/b]n";
    
$clans=db_prepare('SELECT `id`,`name`,`dobl` FROM `clans` WHERE `dobl`>0 ORDER BY `dobl` DESC,`id` ASC LIMIT 10')->fetchAll();
    
$clanGold=[1=>5000,2=>4500,3=>4000,4=>3500,5=>3250,6=>3000,7=>2750,8=>2500,9=>2250,10=>2000];
    
$clanExp=[1=>50000000,2=>45000000,3=>40000000,4=>35000000,5=>32500000,6=>30000000,7=>27500000,8=>25000000,9=>22500000,10=>20000000];
    
$pos=0; foreach($clans as $clan){$pos++;$g=$clanGold[$pos];$e=$clanExp[$pos];db_execute('UPDATE `clans` SET `g`=`g`+:g,`exp`=`exp`+:e WHERE `id`=:c',['g'=>$g,'e'=>$e,'c'=>(int)$clan['id']]);$news.=$pos.'. [url=/clan/'.(int)$clan['id'].'/]'.(string)$clan['name'].'[/url] — [b]'.(int)$clan['dobl'].'[/b] доблестиn';}
    if(!
$clans$news.="Нет участников.n";
    
$news.="n[b]Лучшие герои[/b]n";
    
$users=db_prepare('SELECT `id`,`login`,`lvl_dobl` FROM `users` WHERE `lvl_dobl`>0 ORDER BY `lvl_dobl` DESC,`id` ASC LIMIT 10')->fetchAll();
    
$medals=[1=>5,2=>5,3=>5,4=>5,5=>5,6=>4,7=>4,8=>4,9=>4,10=>4];
    
$gold=[1=>3000,2=>2500,3=>2000,4=>1500,5=>1000,6=>0,7=>0,8=>0,9=>0,10=>0];
    
$pos=0; foreach($users as $hero){$pos++;$m=$medals[$pos];$g=$gold[$pos];db_execute('UPDATE `users` SET `med`=:m,`g`=`g`+:g WHERE `id`=:u',['m'=>$m,'g'=>$g,'u'=>(int)$hero['id']]);$news.=$pos.'. [url=/user/'.(int)$hero['id'].'/]'.(string)$hero['login'].'[/url] — [b]'.(int)$hero['lvl_dobl'].'[/b] доблести'.($g>0?' · [color=#ffd66b]'.$g.' золота[/color]':'').'n';}
    if(!
$users$news.="Нет участников.n";
    if(
function_exists('game_forum_admin_news')) game_forum_admin_news('Итоги сезона доблести',$news."n[center]Новый сезон уже начался. Слава ждёт сильнейших![/center]");
    
db_execute('UPDATE `users` SET `alex_dobl`=0,`lvl_dobl`=0');
    
db_execute('UPDATE `clans` SET `dobl`=0');
    if(
game_automation_table_exists('clan_memb')) db_execute('UPDATE `clan_memb` SET `dobl`=0');
}

function 
game_automation_news(string $code,string $periodKey,string $title,string $text): void {
    
game_automation_once($code,$periodKey,static function() use($title,$text): void {
        if(
function_exists('game_forum_admin_news')) game_forum_admin_news($title,$text);
    });
}

function 
game_automation_tick(): void {
    static 
$done=false; if($done) return; $done=true;
    
$now=time();

    
// Веб-запросы служат только резервным запуском. Основной календарь запускается
    // фоновым обработчиком раз в минуту; этот барьер не даёт обычным страницам
    // выполнять весь набор периодических задач чаще одного раза примерно в минуту.
    
$lastTick=(int)game_meta_get('automation_tick_at','0');
    if(
$lastTick>&& ($now-$lastTick)<50) return;
    
game_meta_set('automation_tick_at',(string)$now);
    
game_automation_bootstrap();

    
// Сражения по расписанию продвигаются независимо от открытых страниц.
    
if(function_exists('game_v51_events_tick')) game_v51_events_tick();
    if(
function_exists('game_weekly_tick')) game_weekly_tick();

    
// Каждые два часа проверяем пробуждение дракона.
    
$twoHour=(int)floor((int)date('G',$now)/2)*2;
    
$dragonKey=date('Ymd',$now).'-'.str_pad((string)$twoHour,2,'0',STR_PAD_LEFT);
    
game_automation_once('dragon_awaken',$dragonKey,'game_automation_dragon_revive');

    
// Ежедневные циклы.
    
$day=date('Y-m-d',$now);
    if((int)
date('G',$now)>=0){
        
game_automation_once('league_daily',$day,'game_automation_league_daily');
        
game_automation_once('chests_daily',$day,'game_automation_chests_daily');
        
game_automation_once('runtime_cleanup',$day,'game_automation_runtime_cleanup');
    }

    
$weekday=(int)date('N',$now); $weekKey=date('o-W',$now);
    
// Понедельник: новая турнирная неделя, перераспределение лиг и закрытие кланового подземелья.
    
if($weekday===1){
        
game_automation_once('league_weekly',$weekKey,'game_automation_league_reseed');
        
game_automation_once('clan_dungeon_close',$weekKey,static fn()=>game_automation_clan_dungeon(false));
        
game_automation_news('weekly_tournaments_start',$weekKey,'Новая турнирная неделя',
            
'[center][b][color=#e8c66a]Новая турнирная неделя началась![/color][/b][/center]'."nn".
            
'С понедельника по пятницу герои собирают реликвии, а кланы — кристаллы.'."n".
            
'Обладатели Божественного трофея также сражаются за недельную награду.'."nn".
            
'[center][url=/relictournament/]Турнир реликвий[/url] · [url=/clantournament/]Клановый турнир[/url] · [url=/trophytournament/]Турнир трофеев[/url][/center]');
    }
    
// Клановые битвы активны с пятницы до конца субботы. Пропущенный пятничный запуск догоняется в субботу.
    
if(in_array($weekday,[5,6],true)) game_automation_once('clan_battle_open',$weekKey,static fn()=>game_automation_clan_battle(true));
    if(
$weekday===7game_automation_once('clan_battle_close',$weekKey,static fn()=>game_automation_clan_battle(false));

    
// Клановое подземелье доступно в выходные. Если субботний запуск был пропущен, воскресенье восстановит цикл.
    
if(in_array($weekday,[6,7],true)) game_automation_once('clan_dungeon_open',$weekKey,static fn()=>game_automation_clan_dungeon(true));

    
// Колизей идёт с субботы по среду; итоги подводятся в ночь на четверг.
    
if($weekday===4){
        
$prevWeekKey=date('o-W',$now-86400);
        
game_automation_once('coliseum_weekly',$prevWeekKey,static fn()=>game_automation_finalize_coliseum($prevWeekKey));
    }
    if(
$weekday===6){
        
game_automation_news('coliseum_start',$weekKey,'Турнир Колизея начался',
            
'[center][b][color=#e8c66a]Турнир Колизея начался![/color][/b][/center]'."nn".
            
'Сражайтесь в Колизее, собирайте черепа и поднимайтесь в десятку сильнейших.'."n".
            
'Итоги будут подведены после среды.'."nn".
            
'[center][url=/coliseum/]Войти в Колизей[/url][/center]');
    }

    
// Сезон доблести переключается по фактической смене месяца и не теряется после простоя.
    
$currentMonth=date('Y-m',$now); $activeMonth=game_meta_get('valor_active_month','');
    if(
$activeMonth===''game_meta_set('valor_active_month',$currentMonth);
    elseif(
$activeMonth!==$currentMonth){
        
game_automation_once('valor_monthly',$activeMonth,static fn()=>game_automation_finalize_valor($activeMonth));
        
game_meta_set('valor_active_month',$currentMonth);
        
game_automation_news('valor_month_start',$currentMonth,'Новый сезон доблести',
            
'[center][b][color=#e8c66a]Новый сезон доблести открыт![/color][/b][/center]'."nn".
            
'Сражайтесь, повышайте личную доблесть и ведите свой клан к вершине рейтинга.'."nn".
            
'[center][url=/valor_info2/]Узнать о наградах[/url][/center]');
    }
}
Онлайн: 3
Реклама