Файл: system/v51_events.php
Строк: 1923
<?php
/**
* v50.12 — единая логика Долины бессмертных и Древних алтарей.
* Игровой цикл управляется единым календарём и не зависит от действий посетителя.
*/
declare(strict_types=1);
function game_v51_table_exists(string $name): bool {
static $cache=[];
if(isset($cache[$name])) return $cache[$name];
$row=db_fetch_one('SELECT COUNT(*) c FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=:n',['n'=>$name]);
return $cache[$name]=((int)($row['c']??0)>0);
}
function game_v51_bootstrap(): void {
static $done=false; if($done) return; $done=true;
db_query("CREATE TABLE IF NOT EXISTS `undying_v512_event` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`state` enum('waiting','running','finished') NOT NULL DEFAULT 'waiting',
`start_at` int unsigned NOT NULL,
`battle_end` int unsigned NOT NULL DEFAULT 0,
`last_tick` int unsigned NOT NULL DEFAULT 0,
`winner` tinyint NOT NULL DEFAULT -1,
`created_at` int unsigned NOT NULL,
`finished_at` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), UNIQUE KEY `uniq_start` (`start_at`), KEY `state_time` (`state`,`start_at`,`battle_end`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `undying_v512_signup` (
`slot_start` int unsigned NOT NULL, `user_id` int unsigned NOT NULL,
`name` varchar(80) NOT NULL, `clan_id` int unsigned NOT NULL DEFAULT 0, `race` tinyint unsigned NOT NULL DEFAULT 0,
`hp` bigint unsigned NOT NULL DEFAULT 1, `max_hp` bigint unsigned NOT NULL DEFAULT 1,
`power` bigint unsigned NOT NULL DEFAULT 1, `defense` bigint unsigned NOT NULL DEFAULT 0,
`statue_bonus` int unsigned NOT NULL DEFAULT 0, `created_at` int unsigned NOT NULL,
PRIMARY KEY (`slot_start`,`user_id`), KEY `user_slot` (`user_id`,`slot_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `undying_v512_member` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`event_id` int unsigned NOT NULL, `user_id` int unsigned NOT NULL DEFAULT 0,
`team` tinyint unsigned NOT NULL DEFAULT 0, `is_bot` tinyint(1) NOT NULL DEFAULT 0,
`name` varchar(80) NOT NULL, `clan_id` int unsigned NOT NULL DEFAULT 0, `race` tinyint unsigned NOT NULL DEFAULT 0,
`hp` bigint unsigned NOT NULL DEFAULT 1, `max_hp` bigint unsigned NOT NULL DEFAULT 1,
`power` bigint unsigned NOT NULL DEFAULT 1, `defense` bigint unsigned NOT NULL DEFAULT 0,
`statue_bonus` int unsigned NOT NULL DEFAULT 0, `damage` bigint unsigned NOT NULL DEFAULT 0,
`kills` int unsigned NOT NULL DEFAULT 0, `dead` tinyint(1) NOT NULL DEFAULT 0,
`target_id` int unsigned NOT NULL DEFAULT 0, `joined_at` int unsigned NOT NULL,
`last_action` int unsigned NOT NULL DEFAULT 0, `stone_cd` int unsigned NOT NULL DEFAULT 0,
`grass_cd` int unsigned NOT NULL DEFAULT 0, `stone_until` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), KEY `event_user` (`event_id`,`user_id`,`is_bot`), KEY `event_team` (`event_id`,`team`,`dead`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `undying_v512_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `event_id` int unsigned NOT NULL,
`created_at` int unsigned NOT NULL, `text` varchar(255) NOT NULL,
PRIMARY KEY (`id`), KEY `event_id` (`event_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `undying_v512_result` (
`event_id` int unsigned NOT NULL, `user_id` int unsigned NOT NULL, `team` tinyint unsigned NOT NULL,
`won` tinyint(1) NOT NULL DEFAULT 0, `kills` int unsigned NOT NULL DEFAULT 0, `damage` bigint unsigned NOT NULL DEFAULT 0,
`reward_gold` int unsigned NOT NULL DEFAULT 0, `reward_silver` int unsigned NOT NULL DEFAULT 0,
`reward_exp` bigint unsigned NOT NULL DEFAULT 0, `reward_valor` int unsigned NOT NULL DEFAULT 0,
`finished_at` int unsigned NOT NULL,
PRIMARY KEY (`event_id`,`user_id`), KEY `user_last` (`user_id`,`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_event` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`state` enum('waiting','running','finished') NOT NULL DEFAULT 'waiting',
`start_at` int unsigned NOT NULL, `battle_end` int unsigned NOT NULL DEFAULT 0,
`last_tick` int unsigned NOT NULL DEFAULT 0, `created_at` int unsigned NOT NULL, `finished_at` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), UNIQUE KEY `uniq_start` (`start_at`), KEY `state_time` (`state`,`start_at`,`battle_end`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_signup` (
`slot_start` int unsigned NOT NULL, `user_id` int unsigned NOT NULL,
`clan_id` int unsigned NOT NULL, `name` varchar(80) NOT NULL, `race` tinyint unsigned NOT NULL DEFAULT 0,
`hp` bigint unsigned NOT NULL DEFAULT 1, `max_hp` bigint unsigned NOT NULL DEFAULT 1,
`power` bigint unsigned NOT NULL DEFAULT 1, `defense` bigint unsigned NOT NULL DEFAULT 0,
`statue_bonus` int unsigned NOT NULL DEFAULT 0, `created_at` int unsigned NOT NULL,
PRIMARY KEY (`slot_start`,`user_id`), KEY `user_slot` (`user_id`,`slot_start`), KEY `slot_clan` (`slot_start`,`clan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_member` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `event_id` int unsigned NOT NULL, `user_id` int unsigned NOT NULL DEFAULT 0,
`is_bot` tinyint(1) NOT NULL DEFAULT 0, `clan_id` int unsigned NOT NULL DEFAULT 0, `name` varchar(80) NOT NULL,
`race` tinyint unsigned NOT NULL DEFAULT 0, `hp` bigint unsigned NOT NULL DEFAULT 1, `max_hp` bigint unsigned NOT NULL DEFAULT 1,
`power` bigint unsigned NOT NULL DEFAULT 1, `defense` bigint unsigned NOT NULL DEFAULT 0, `statue_bonus` int unsigned NOT NULL DEFAULT 0,
`damage` bigint unsigned NOT NULL DEFAULT 0, `kills` int unsigned NOT NULL DEFAULT 0, `dead` tinyint(1) NOT NULL DEFAULT 0,
`target_id` int unsigned NOT NULL DEFAULT 0, `joined_at` int unsigned NOT NULL, `last_action` int unsigned NOT NULL DEFAULT 0,
`stone_cd` int unsigned NOT NULL DEFAULT 0, `grass_cd` int unsigned NOT NULL DEFAULT 0, `stone_until` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), KEY `event_user` (`event_id`,`user_id`,`is_bot`), KEY `event_alive` (`event_id`,`dead`), KEY `event_clan` (`event_id`,`clan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, `event_id` int unsigned NOT NULL, `created_at` int unsigned NOT NULL,
`text` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `event_id` (`event_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_clan_progress` (
`clan_id` int unsigned NOT NULL, `altar_level` tinyint unsigned NOT NULL DEFAULT 1,
`wins` int unsigned NOT NULL DEFAULT 0, `losses` int unsigned NOT NULL DEFAULT 0, `updated_at` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`clan_id`), KEY `altar_rank` (`altar_level`,`wins`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_clan_result` (
`event_id` int unsigned NOT NULL, `clan_id` int unsigned NOT NULL, `place` smallint unsigned NOT NULL DEFAULT 0,
`damage` bigint unsigned NOT NULL DEFAULT 0, `points` tinyint unsigned NOT NULL DEFAULT 0,
`altar_before` tinyint unsigned NOT NULL DEFAULT 1, `altar_after` tinyint unsigned NOT NULL DEFAULT 1,
`captured` tinyint(1) NOT NULL DEFAULT 0, `reward_gold` int unsigned NOT NULL DEFAULT 0,
`reward_silver` bigint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`event_id`,`clan_id`), KEY `event_place` (`event_id`,`place`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `altar_v512_result` (
`event_id` int unsigned NOT NULL, `user_id` int unsigned NOT NULL, `clan_id` int unsigned NOT NULL,
`place` smallint unsigned NOT NULL DEFAULT 0, `kills` int unsigned NOT NULL DEFAULT 0, `damage` bigint unsigned NOT NULL DEFAULT 0,
`captured` tinyint(1) NOT NULL DEFAULT 0, `altar_before` tinyint unsigned NOT NULL DEFAULT 1,
`altar_after` tinyint unsigned NOT NULL DEFAULT 1, `reward_silver` bigint unsigned NOT NULL DEFAULT 0,
`reward_exp` bigint unsigned NOT NULL DEFAULT 0, `finished_at` int unsigned NOT NULL,
PRIMARY KEY (`event_id`,`user_id`), KEY `user_last` (`user_id`,`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `cave_v512_state` (
`user_id` int unsigned NOT NULL,
`stage` enum('idle','searching','found','gathering') NOT NULL DEFAULT 'idle',
`ends_at` int unsigned NOT NULL DEFAULT 0,
`res1` tinyint unsigned NOT NULL DEFAULT 0, `chance1` tinyint unsigned NOT NULL DEFAULT 0,
`res2` tinyint unsigned NOT NULL DEFAULT 0, `chance2` tinyint unsigned NOT NULL DEFAULT 0,
`res3` tinyint unsigned NOT NULL DEFAULT 0, `chance3` tinyint unsigned NOT NULL DEFAULT 0,
`result_json` text NULL, `updated_at` int unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`user_id`), KEY `stage_end` (`stage`,`ends_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
// Переносим только прогресс кланов. Заявки/участники старых версий принципиально не импортируются.
if(game_v51_table_exists('altar_v511_clan_progress')){
db_query("INSERT IGNORE INTO `altar_v512_clan_progress` (`clan_id`,`altar_level`,`wins`,`losses`,`updated_at`) SELECT `clan_id`,`altar_level`,`wins`,`losses`,`updated_at` FROM `altar_v511_clan_progress`");
} elseif(game_v51_table_exists('altar_v60_clan_progress')){
db_query("INSERT IGNORE INTO `altar_v512_clan_progress` (`clan_id`,`altar_level`,`wins`,`losses`,`updated_at`) SELECT `clan_id`,`altar_level`,`wins`,`losses`,`updated_at` FROM `altar_v60_clan_progress`");
}
}
function game_v51_clan_buff_value(int $level): int {
$level=max(0,min(50,$level));
if($level===0) return 0;
return min(400,$level<=3 ? $level*4 : (int)round($level*7.84));
}
function game_v51_clan_buff_parts(int $total): array {
$total=max(0,$total); $q=intdiv($total,4); $r=$total%4;
return [$q+($r>0?1:0),$q+($r>1?1:0),$q+($r>2?1:0),$q+($r>3?1:0)];
}
function game_v51_altar_names(): array {
return [1=>'Алтарь волхвов',2=>'Алтарь древних рун',3=>'Алтарь предков',4=>'Алтарь стихий',5=>'Алтарь титанов',6=>'Алтарь Бездны',7=>'Алтарь драконов',8=>'Алтарь бессмертных',9=>'Алтарь королей',10=>'Алтарь Вечности'];
}
function game_v51_altar_name(int $level): string { $n=game_v51_altar_names(); return $n[max(1,min(10,$level))]??$n[1]; }
function game_v51_altar_progress(int $clanId): array {
game_v51_bootstrap();
if($clanId<=0) return ['level'=>1,'name'=>game_v51_altar_name(1),'wins'=>0,'losses'=>0];
$r=db_fetch_one('SELECT * FROM `altar_v512_clan_progress` WHERE `clan_id`=:c LIMIT 1',['c'=>$clanId]);
$level=max(1,min(10,(int)($r['altar_level']??1)));
return ['level'=>$level,'name'=>game_v51_altar_name($level),'wins'=>(int)($r['wins']??0),'losses'=>(int)($r['losses']??0)];
}
function game_v51_snapshot_user(int $uid): ?array {
$u=db_fetch_one('SELECT `id`,`login`,`r`,`str`,`vit`,`agi`,`def`,`hp`,`kamen`,`trava` FROM `users` WHERE `id`=:u LIMIT 1',['u'=>$uid]);
if(!$u) return null;
$cm=db_fetch_one('SELECT cm.`clan`,c.`built_1`,c.`built_2_time` FROM `clan_memb` cm JOIN `clans` c ON c.`id`=cm.`clan` WHERE cm.`user`=:u LIMIT 1',['u'=>$uid]);
$clanId=(int)($cm['clan']??0); $statueLevel=(int)($cm['built_1']??0); $buffUntil=(int)($cm['built_2_time']??0);
$bonus=($clanId>0 && $statueLevel>0 && $buffUntil>time()) ? game_v51_clan_buff_value($statueLevel) : 0;
[$bs,$bv,$ba,$bd]=game_v51_clan_buff_parts($bonus);
$str=max(1,(int)$u['str']+$bs); $vit=max(1,(int)$u['vit']+$bv); $agi=max(1,(int)$u['agi']+$ba); $def=max(0,(int)$u['def']+$bd);
$maxHp=max(100,(int)$u['hp'],$vit*2); $power=max(10,$str+(int)round($agi*.45));
return ['id'=>$uid,'name'=>(string)$u['login'],'race'=>max(0,min(1,(int)$u['r'])),'clan_id'=>$clanId,'statue_level'=>$statueLevel,'statue_bonus'=>$bonus,'statue_until'=>$buffUntil,'hp'=>$maxHp,'max_hp'=>$maxHp,'power'=>$power,'defense'=>$def,'kamen'=>(int)$u['kamen'],'trava'=>(int)$u['trava']];
}
function game_v51_statue_personal_bonus(array $snapshot): int { return max(0,(int)($snapshot['statue_bonus']??0)); }
function game_v51_param_sum_to_power(int $sumBonus): int { [$s,,$a,]=game_v51_clan_buff_parts($sumBonus); return $s+(int)round($a*.45); }
function game_v51_user_mannequin(int $uid,int $fallbackRace=0): string {
$u=db_fetch_one('SELECT `r` FROM `users` WHERE `id`=:u LIMIT 1',['u'=>$uid]); $race=max(0,min(1,(int)($u['r']??$fallbackRace)));
$rows=db_prepare('SELECT `item` FROM `inv` WHERE `user`=:u AND `equip`=1 ORDER BY `place`,`id`',['u'=>$uid])->fetchAll();
$slots=array_fill(0,9,0); $i=0; foreach($rows as $r){if($i>=9)break;$slots[$i++]=max(0,(int)$r['item']);}
return '/manekenImage/'.$race.'/'.implode('/',$slots).'/';
}
function game_v51_member_portrait(array $member,int $fallbackRace=0): string {
if((int)($member['is_bot']??0)===1){$id=(int)($member['id']??1);$imgs=['/images/boss/18.png','/images/barbars/kingundying.png','/images/boss/17.png','/images/boss/22.png','/images/boss/24.png'];return $imgs[$id%count($imgs)];}
return game_v51_user_mannequin((int)$member['user_id'],(int)($member['race']??$fallbackRace));
}
function game_v51_bot_name(int $i,bool $altar=false): string {
$a=$altar?['Страж рун','Воин алтаря','Хранитель предков','Теневой рыцарь','Жрец Бездны','Древний страж']:['Бессмертный','Костяной воин','Страж долины','Титан-страж','Падший рыцарь','Охотник Бездны'];
return $a[$i%count($a)].' '.($i+1);
}
function game_v51_damage(array $a,array $d,float $autoMultiplier=1.0): int {
$base=max(1,(int)$a['power']);$def=max(0,(int)$d['defense']);$roll=random_int(86,114)/100;$crit=random_int(1,100)<=9?1.55:1.0;$stone=((int)($a['stone_until']??0)>time())?1.35:1.0;
$raw=$base*$roll*$crit*$stone*$autoMultiplier-($def*.18);return max(1,min(max(1,(int)$d['hp']),(int)round(max(1,$raw))));
}
function game_v51_log(string $mode,int $eventId,string $text): void {
$table=$mode==='undying'?'undying_v512_log':'altar_v512_log';
db_execute("INSERT INTO `$table` (`event_id`,`created_at`,`text`) VALUES (:e,:t,:x)",['e'=>$eventId,'t'=>time(),'x'=>mb_substr($text,0,250)]);
}
function game_v51_schedule_config(string $mode): array {
return match($mode){
'undying'=>['times'=>['03:00','07:00','11:00','15:00','19:00','23:00'],'battle'=>20*60,'label'=>'03:00, 07:00, 11:00, 15:00, 19:00, 23:00'],
'altar'=>['times'=>['02:30','08:30','14:30','20:30'],'battle'=>25*60,'label'=>'02:30, 08:30, 14:30, 20:30'],
default=>throw new InvalidArgumentException('Unknown event mode')
};
}
function game_v51_next_start(string $mode,?int $from=null): int {
$from=$from??time();$cfg=game_v51_schedule_config($mode);
for($day=0;$day<4;$day++){
$date=date('Y-m-d',$from+$day*86400);
foreach($cfg['times'] as $clock){$ts=strtotime($date.' '.$clock);if($ts!==false&&$ts>$from)return $ts;}
}
return $from+86400;
}
function game_v51_prev_start(string $mode,?int $from=null): int {
$from=$from??time();$cfg=game_v51_schedule_config($mode);
for($day=0;$day<4;$day++){
$date=date('Y-m-d',$from-$day*86400);$candidate=0;
foreach($cfg['times'] as $clock){$ts=strtotime($date.' '.$clock);if($ts!==false&&$ts<=$from)$candidate=max($candidate,$ts);}
if($candidate>0)return $candidate;
}
return max(0,$from-86400);
}
function game_v51_schedule_label(string $mode): string {return $mode==='undying'?'каждые 4 часа':'каждые 6 часов';}
function game_v51_schedule_times(string $mode): string {return (string)game_v51_schedule_config($mode)['label'];}
function game_v51_tables(string $mode): array {
return $mode==='undying'
? ['event'=>'undying_v512_event','signup'=>'undying_v512_signup','member'=>'undying_v512_member','result'=>'undying_v512_result','log'=>'undying_v512_log']
: ['event'=>'altar_v512_event','signup'=>'altar_v512_signup','member'=>'altar_v512_member','result'=>'altar_v512_result','log'=>'altar_v512_log'];
}
function game_v51_event_title(string $mode): string { return $mode==='undying'?'Долина бессмертных':'Древние алтари'; }
function game_v51_event_link(string $mode): string { return $mode==='undying'?'/undying/':'/altars/'; }
function game_v51_admin_news(string $title,string $text): void {
if(!function_exists('game_forum_admin_news')) return;
try {
game_forum_admin_news($title,$text);
} catch(Throwable $e) {
if(function_exists('app_log_write')) app_log_write('EVENTS','Event news publish failed',['title'=>$title,'type'=>get_class($e),'message'=>$e->getMessage()]);
}
}
function game_v51_publish_start_news(string $mode,int $eventId,int $humans,int $startAt,int $battleEnd): void {
if($humans<=0) return;
$title=game_v51_event_title($mode).' — битва началась';
$text='[center][b][color=#e8c66a]'.game_v51_event_title($mode).'[/color][/b][/center]'."
";
$text.='В сражение вступили герои: [b]'.$humans.'[/b].'."
";
$text.='Битва продлится до [b]'.date('H:i',$battleEnd).'[/b].'."
";
$text.='[center][url='.game_v51_event_link($mode).']Перейти к сражению[/url][/center]';
game_v51_admin_news($title,$text);
}
function game_v51_publish_finish_news(string $mode,int $eventId): void {
if($mode==='undying'){
$leaders=db_prepare('SELECT `user_id`,`name`,`kills`,`damage` FROM `undying_v512_member` WHERE `event_id`=:e AND `is_bot`=0 AND `user_id`>0 ORDER BY `damage` DESC,`kills` DESC LIMIT 3',['e'=>$eventId])->fetchAll();
if(!$leaders) return;
$event=db_fetch_one('SELECT `winner` FROM `undying_v512_event` WHERE `id`=:e LIMIT 1',['e'=>$eventId]);
$text='[center][b][color=#e8c66a]Битва в Долине бессмертных завершена[/color][/b][/center]'."
";
$text.='Победившая сторона: [b]'.(((int)($event['winner']??0)===0)?'герои':'войско Бессмертных').'[/b].'."
[b]Лучшие бойцы[/b]
";
$pos=0;foreach($leaders as $r){$pos++;$text.=$pos.'. [url=/user/'.(int)$r['user_id'].'/]'.(string)$r['name'].'[/url] — '.(int)$r['kills'].' побед, '.(int)$r['damage'].' урона'."
";}
$text.="
[center][url=/undying/?history=1]Летопись битв[/url][/center]";
game_v51_admin_news('Итоги Долины бессмертных',$text);return;
}
$top=db_prepare('SELECT r.`clan_id`,r.`place`,r.`damage`,r.`captured`,c.`name` FROM `altar_v512_clan_result` r LEFT JOIN `clans` c ON c.`id`=r.`clan_id` WHERE r.`event_id`=:e ORDER BY r.`place` ASC LIMIT 3',['e'=>$eventId])->fetchAll();
if(!$top) return;
$text='[center][b][color=#e8c66a]Битва за Древние алтари завершена[/color][/b][/center]'."
[b]Лучшие кланы[/b]
";
foreach($top as $r){$text.=(int)$r['place'].'. [url=/clan/'.(int)$r['clan_id'].'/]'.(string)($r['name']?:'Клан').'[/url] — '.(int)$r['damage'].' урона'.((int)$r['captured']===1?' · [color=#7afe4e]алтарь захвачен[/color]':'')."
";}
$text.="
[center][url=/altars/?history=1]Летопись битв[/url][/center]";
game_v51_admin_news('Итоги Древних алтарей',$text);
}
function game_v51_create_waiting(string $mode,int $after=0): array {
game_v51_bootstrap();$start=game_v51_next_start($mode,max(time(),$after));
// Ожидание хранит только ближайший час битвы и не создаёт участника.
return ['id'=>0,'state'=>'waiting','start_at'=>$start,'battle_end'=>0,'last_tick'=>0,'created_at'=>time(),'finished_at'=>0];
}
function game_v51_has_signup(string $mode,int $uid,int $slotStart): bool {
if($uid<=0||$slotStart<=0)return false;game_v51_bootstrap();$t=game_v51_tables($mode);
return (bool)db_fetch_one("SELECT 1 FROM `{$t['signup']}` WHERE `slot_start`=:s AND `user_id`=:u LIMIT 1",['s'=>$slotStart,'u'=>$uid]);
}
function game_v51_start_slot(string $mode,int $slotStart): ?array {
if($slotStart<=0)return null;game_v51_bootstrap();$t=game_v51_tables($mode);$pdo=db_pdo();
$existing=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `start_at`=:s LIMIT 1",['s'=>$slotStart]);
if($existing)return $existing;
$pdo->beginTransaction();
try{
$existing=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `start_at`=:s LIMIT 1 FOR UPDATE",['s'=>$slotStart]);
if($existing){$pdo->commit();return $existing;}
$signups=db_prepare("SELECT * FROM `{$t['signup']}` WHERE `slot_start`=:s ORDER BY `created_at`,`user_id`",['s'=>$slotStart])->fetchAll();
$now=time();$cfg=game_v51_schedule_config($mode);
$battleEnd=$slotStart+(int)$cfg['battle'];
db_execute("INSERT INTO `{$t['event']}` (`state`,`start_at`,`battle_end`,`last_tick`,`created_at`) VALUES ('running',:s,:b,:lt,:c)",['s'=>$slotStart,'b'=>$battleEnd,'lt'=>$slotStart,'c'=>$now]);
$eid=(int)$pdo->lastInsertId();
foreach($signups as $r){
if($mode==='undying'){
db_execute("INSERT INTO `{$t['member']}` (`event_id`,`user_id`,`team`,`is_bot`,`name`,`clan_id`,`race`,`hp`,`max_hp`,`power`,`defense`,`statue_bonus`,`joined_at`) VALUES (:e,:u,0,0,:n,:c,:r,:h,:mh,:p,:d,:sb,:j)",['e'=>$eid,'u'=>(int)$r['user_id'],'n'=>(string)$r['name'],'c'=>(int)$r['clan_id'],'r'=>(int)$r['race'],'h'=>(int)$r['hp'],'mh'=>(int)$r['max_hp'],'p'=>(int)$r['power'],'d'=>(int)$r['defense'],'sb'=>(int)$r['statue_bonus'],'j'=>(int)$r['created_at']]);
}else{
// На момент старта игрок по-прежнему должен состоять в том же клане.
$cm=db_fetch_one('SELECT `clan` FROM `clan_memb` WHERE `user`=:u LIMIT 1',['u'=>(int)$r['user_id']]);
if((int)($cm['clan']??0)!==(int)$r['clan_id'])continue;
db_execute("INSERT INTO `{$t['member']}` (`event_id`,`user_id`,`is_bot`,`clan_id`,`name`,`race`,`hp`,`max_hp`,`power`,`defense`,`statue_bonus`,`joined_at`) VALUES (:e,:u,0,:c,:n,:r,:h,:mh,:p,:d,:sb,:j)",['e'=>$eid,'u'=>(int)$r['user_id'],'c'=>(int)$r['clan_id'],'n'=>(string)$r['name'],'r'=>(int)$r['race'],'h'=>(int)$r['hp'],'mh'=>(int)$r['max_hp'],'p'=>(int)$r['power'],'d'=>(int)$r['defense'],'sb'=>(int)$r['statue_bonus'],'j'=>(int)$r['created_at']]);
}
}
$humans=(int)(db_fetch_one("SELECT COUNT(*) c FROM `{$t['member']}` WHERE `event_id`=:e AND `is_bot`=0 AND `user_id`>0",['e'=>$eid])['c']??0);
if($mode==='undying')game_v51_add_undying_bots($eid);else game_v51_add_altar_bots($eid);
game_v51_log($mode,$eid,'Сражение началось.');
if($humans>0) game_v51_publish_start_news($mode,$eid,$humans,$slotStart,$battleEnd);
$pdo->commit();
return db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `id`=:e",['e'=>$eid]);
}catch(Throwable $x){
if($pdo->inTransaction())$pdo->rollBack();
$race=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `start_at`=:s LIMIT 1",['s'=>$slotStart]);if($race)return $race;
throw $x;
}
}
function game_v51_add_undying_bots(int $eventId): void {
$humans=(int)(db_fetch_one('SELECT COUNT(*) c FROM `undying_v512_member` WHERE `event_id`=:e AND `is_bot`=0',['e'=>$eventId])['c']??0);
$stats=db_fetch_one('SELECT COALESCE(AVG(`max_hp`),600) h,COALESCE(AVG(`power`),80) p,COALESCE(AVG(`defense`),40) d FROM `undying_v512_member` WHERE `event_id`=:e AND `is_bot`=0',['e'=>$eventId])??[];
$avgHp=max(300,(int)round((float)($stats['h']??600)));$avgP=max(30,(int)round((float)($stats['p']??80)));$avgD=max(10,(int)round((float)($stats['d']??40)));
$ally=max(2,6-$humans);$enemy=max(8,$humans+$ally+3);$now=time();
for($i=0;$i<$ally+$enemy;$i++){$team=$i<$ally?0:1;$scale=$team===0?.72:.78;$hp=max(180,(int)round($avgHp*$scale*random_int(90,110)/100));$p=max(18,(int)round($avgP*$scale*random_int(90,110)/100));$d=max(5,(int)round($avgD*$scale));db_execute('INSERT INTO `undying_v512_member` (`event_id`,`user_id`,`team`,`is_bot`,`name`,`race`,`hp`,`max_hp`,`power`,`defense`,`joined_at`) VALUES (:e,0,:tm,1,:n,:r,:h,:mh,:p,:d,:j)',['e'=>$eventId,'tm'=>$team,'n'=>game_v51_bot_name($i,false),'r'=>$i%2,'h'=>$hp,'mh'=>$hp,'p'=>$p,'d'=>$d,'j'=>$now]);}
}
function game_v51_add_altar_bots(int $eventId): void {
$humans=(int)(db_fetch_one('SELECT COUNT(*) c FROM `altar_v512_member` WHERE `event_id`=:e AND `is_bot`=0',['e'=>$eventId])['c']??0);
$stats=db_fetch_one('SELECT COALESCE(AVG(`max_hp`),600) h,COALESCE(AVG(`power`),80) p,COALESCE(AVG(`defense`),40) d FROM `altar_v512_member` WHERE `event_id`=:e AND `is_bot`=0',['e'=>$eventId])??[];
$avgHp=max(300,(int)round((float)($stats['h']??600)));$avgP=max(30,(int)round((float)($stats['p']??80)));$avgD=max(10,(int)round((float)($stats['d']??40)));$bots=max(6,12-$humans);$now=time();
for($i=0;$i<$bots;$i++){$hp=max(180,(int)round($avgHp*.64*random_int(90,110)/100));$p=max(16,(int)round($avgP*.60*random_int(90,110)/100));$d=max(5,(int)round($avgD*.60));db_execute('INSERT INTO `altar_v512_member` (`event_id`,`user_id`,`is_bot`,`clan_id`,`name`,`race`,`hp`,`max_hp`,`power`,`defense`,`joined_at`) VALUES (:e,0,1,0,:n,:r,:h,:mh,:p,:d,:j)',['e'=>$eventId,'n'=>game_v51_bot_name($i,true),'r'=>$i%2,'h'=>$hp,'mh'=>$hp,'p'=>$p,'d'=>$d,'j'=>$now]);}
}
function game_v51_start_waiting(string $mode,array $event): ?array {
// Совместимый wrapper для запуска выбранного часа битвы.
return game_v51_start_slot($mode,(int)($event['start_at']??0));
}
function game_v51_simulate_undying(int $eventId,int $rounds): void {
$rounds=max(0,min(180,$rounds));
for($r=0;$r<$rounds;$r++){
$rows=db_prepare('SELECT * FROM `undying_v512_member` WHERE `event_id`=:e AND `dead`=0 ORDER BY `id`',['e'=>$eventId])->fetchAll();$teams=[0=>[],1=>[]];foreach($rows as $x)$teams[(int)$x['team']][]=$x;if(!$teams[0]||!$teams[1])break;$by=[];foreach($rows as $x)$by[(int)$x['id']]=$x;
foreach(array_keys($by) as $aid){if((int)$by[$aid]['dead']===1)continue;$opp=[];foreach($by as $tid=>$tar)if((int)$tar['dead']===0&&(int)$tar['team']!==(int)$by[$aid]['team'])$opp[]=$tid;if(!$opp)break;$tid=$opp[array_rand($opp)];$multi=(int)$by[$aid]['is_bot']===1?.45:.70;$dmg=game_v51_damage($by[$aid],$by[$tid],$multi);$by[$tid]['hp']=max(0,(int)$by[$tid]['hp']-$dmg);$by[$aid]['damage']=(int)$by[$aid]['damage']+$dmg;if((int)$by[$tid]['hp']===0){$by[$tid]['dead']=1;$by[$aid]['kills']=(int)$by[$aid]['kills']+1;}}
foreach($by as $x)db_execute('UPDATE `undying_v512_member` SET `hp`=:h,`damage`=:dm,`kills`=:k,`dead`=:x WHERE `id`=:i',['h'=>(int)$x['hp'],'dm'=>(int)$x['damage'],'k'=>(int)$x['kills'],'x'=>(int)$x['dead'],'i'=>(int)$x['id']]);
}
}
function game_v51_simulate_altars(int $eventId,int $rounds): void {
$rounds=max(0,min(180,$rounds));
for($r=0;$r<$rounds;$r++){
$rows=db_prepare('SELECT * FROM `altar_v512_member` WHERE `event_id`=:e AND `dead`=0 ORDER BY `id`',['e'=>$eventId])->fetchAll();if(count($rows)<2)break;$by=[];foreach($rows as $x)$by[(int)$x['id']]=$x;
foreach(array_keys($by) as $aid){if((int)$by[$aid]['dead']===1)continue;$opp=[];foreach($by as $tid=>$tar)if($tid!==$aid&&(int)$tar['dead']===0)$opp[]=$tid;if(!$opp)break;$tid=$opp[array_rand($opp)];$multi=(int)$by[$aid]['is_bot']===1?.42:.66;$dmg=game_v51_damage($by[$aid],$by[$tid],$multi);$by[$tid]['hp']=max(0,(int)$by[$tid]['hp']-$dmg);$by[$aid]['damage']=(int)$by[$aid]['damage']+$dmg;if((int)$by[$tid]['hp']===0){$by[$tid]['dead']=1;$by[$aid]['kills']=(int)$by[$aid]['kills']+1;}}
foreach($by as $x)db_execute('UPDATE `altar_v512_member` SET `hp`=:h,`damage`=:dm,`kills`=:k,`dead`=:x WHERE `id`=:i',['h'=>(int)$x['hp'],'dm'=>(int)$x['damage'],'k'=>(int)$x['kills'],'x'=>(int)$x['dead'],'i'=>(int)$x['id']]);
}
}
function game_v51_finalize_undying(int $eventId): void {
$pdo=db_pdo();$pdo->beginTransaction();
try{
$e=db_fetch_one("SELECT * FROM `undying_v512_event` WHERE `id`=:e AND `state`='running' LIMIT 1 FOR UPDATE",['e'=>$eventId]);if(!$e){$pdo->commit();return;}
$sum=db_prepare('SELECT `team`,SUM(`kills`) k,SUM(`damage`) d,SUM(CASE WHEN `dead`=0 THEN 1 ELSE 0 END) a FROM `undying_v512_member` WHERE `event_id`=:e GROUP BY `team`',['e'=>$eventId])->fetchAll();$s=[0=>['k'=>0,'d'=>0,'a'=>0],1=>['k'=>0,'d'=>0,'a'=>0]];foreach($sum as $r)$s[(int)$r['team']]=['k'=>(int)$r['k'],'d'=>(int)$r['d'],'a'=>(int)$r['a']];
$winner=$s[0]['a']>0&&$s[1]['a']===0?0:($s[1]['a']>0&&$s[0]['a']===0?1:($s[0]['k']!==$s[1]['k']?($s[0]['k']>$s[1]['k']?0:1):($s[0]['d']>=$s[1]['d']?0:1)));$now=time();
$members=db_prepare('SELECT * FROM `undying_v512_member` WHERE `event_id`=:e AND `is_bot`=0 AND `user_id`>0',['e'=>$eventId])->fetchAll();
foreach($members as $m){$uid=(int)$m['user_id'];if(db_fetch_one('SELECT 1 FROM `undying_v512_result` WHERE `event_id`=:e AND `user_id`=:u',['e'=>$eventId,'u'=>$uid]))continue;$won=(int)$m['team']===$winner;$gold=min(4,max(0,(int)$m['kills'])+($won?1:0));$silver=min(30000,600+(int)floor((int)$m['damage']/100));$exp=min(50000,600+(int)floor((int)$m['damage']/80));$valor=min(100,1+(int)$m['kills']*2+(int)floor((int)$m['damage']/30000));if(!$won){$silver=(int)round($silver*.65);$exp=(int)round($exp*.65);$valor=max(1,(int)round($valor*.65));$gold=min($gold,2);}db_execute('UPDATE `users` SET `s`=LEAST(2000000000,`s`+:s),`exp`=LEAST(9000000000000000000,`exp`+:x),`alex_dobl`=LEAST(2000000000,`alex_dobl`+:v),`undying`=LEAST(2000000000,`undying`+:k) WHERE `id`=:u',['s'=>$silver,'x'=>$exp,'v'=>$valor,'k'=>(int)$m['kills'],'u'=>$uid]);if($gold>0&&function_exists('game_economy_award_gold'))game_economy_award_gold($uid,$gold,'undying',150);db_execute('INSERT INTO `undying_v512_result` (`event_id`,`user_id`,`team`,`won`,`kills`,`damage`,`reward_gold`,`reward_silver`,`reward_exp`,`reward_valor`,`finished_at`) VALUES (:e,:u,:tm,:w,:k,:d,:g,:s,:x,:v,:f)',['e'=>$eventId,'u'=>$uid,'tm'=>(int)$m['team'],'w'=>$won?1:0,'k'=>(int)$m['kills'],'d'=>(int)$m['damage'],'g'=>$gold,'s'=>$silver,'x'=>$exp,'v'=>$valor,'f'=>$now]);if(function_exists('game_quest_progress_event'))game_quest_progress_event($uid,9,1,$won?1:0);}
db_execute("UPDATE `undying_v512_event` SET `state`='finished',`winner`=:w,`finished_at`=:f WHERE `id`=:e",['w'=>$winner,'f'=>$now,'e'=>$eventId]);game_v51_log('undying',$eventId,'Битва завершена.');game_v51_publish_finish_news('undying',$eventId);$pdo->commit();
}catch(Throwable $x){if($pdo->inTransaction())$pdo->rollBack();throw $x;}
}
function game_v51_finalize_altars(int $eventId): void {
$pdo=db_pdo();$pdo->beginTransaction();
try{
$e=db_fetch_one("SELECT * FROM `altar_v512_event` WHERE `id`=:e AND `state`='running' LIMIT 1 FOR UPDATE",['e'=>$eventId]);if(!$e){$pdo->commit();return;}$now=time();
$clans=db_prepare('SELECT `clan_id`,SUM(`damage`) d FROM `altar_v512_member` WHERE `event_id`=:e AND `is_bot`=0 AND `clan_id`>0 GROUP BY `clan_id` ORDER BY d DESC',['e'=>$eventId])->fetchAll();$place=0;$cr=[];
foreach($clans as $c){$place++;$cid=(int)$c['clan_id'];$damage=(int)$c['d'];$prog=game_v51_altar_progress($cid);$before=(int)$prog['level'];$points=min(10,(int)floor($damage/5000));$captured=$place<=3&&$points>=$before;$after=$captured?min(10,$before+1):max(1,$before-1);$rank=match($place){1=>1.5,2=>1.25,3=>1.1,default=>.75};$rewardGold=(int)min(30,max(1,round((2+$before+$points)*$rank*($captured?1.1:1))));$rewardSilver=(int)min(75000,max(2000,round((3500+$before*1500+$points*750)*$rank)));db_execute('INSERT INTO `altar_v512_clan_result` (`event_id`,`clan_id`,`place`,`damage`,`points`,`altar_before`,`altar_after`,`captured`,`reward_gold`,`reward_silver`) VALUES (:e,:c,:p,:d,:pt,:b,:a,:x,:g,:s)',['e'=>$eventId,'c'=>$cid,'p'=>$place,'d'=>$damage,'pt'=>$points,'b'=>$before,'a'=>$after,'x'=>$captured?1:0,'g'=>$rewardGold,'s'=>$rewardSilver]);db_execute('INSERT INTO `altar_v512_clan_progress` (`clan_id`,`altar_level`,`wins`,`losses`,`updated_at`) VALUES (:c,:l,:w,:f,:t) ON DUPLICATE KEY UPDATE `altar_level`=VALUES(`altar_level`),`wins`=`wins`+VALUES(`wins`),`losses`=`losses`+VALUES(`losses`),`updated_at`=VALUES(`updated_at`)',['c'=>$cid,'l'=>$after,'w'=>$captured?1:0,'f'=>$captured?0:1,'t'=>$now]);db_execute('UPDATE `clans` SET `g`=LEAST(9000000000000000000,`g`+:g),`s`=LEAST(9000000000000000000,`s`+:s) WHERE `id`=:c',['g'=>$rewardGold,'s'=>$rewardSilver,'c'=>$cid]);$cr[$cid]=['place'=>$place,'captured'=>$captured,'before'=>$before,'after'=>$after];}
$members=db_prepare('SELECT * FROM `altar_v512_member` WHERE `event_id`=:e AND `is_bot`=0 AND `user_id`>0',['e'=>$eventId])->fetchAll();
foreach($members as $m){$uid=(int)$m['user_id'];if(db_fetch_one('SELECT 1 FROM `altar_v512_result` WHERE `event_id`=:e AND `user_id`=:u',['e'=>$eventId,'u'=>$uid]))continue;$cid=(int)$m['clan_id'];$r=$cr[$cid]??['place'=>0,'captured'=>false,'before'=>1,'after'=>1];$silver=min(60000,700+(int)floor((int)$m['damage']/45));$exp=min(100000,650+(int)floor((int)$m['damage']/32));db_execute('UPDATE `users` SET `s`=LEAST(2000000000,`s`+:s),`exp`=LEAST(9000000000000000000,`exp`+:x) WHERE `id`=:u',['s'=>$silver,'x'=>$exp,'u'=>$uid]);db_execute('INSERT INTO `altar_v512_result` (`event_id`,`user_id`,`clan_id`,`place`,`kills`,`damage`,`captured`,`altar_before`,`altar_after`,`reward_silver`,`reward_exp`,`finished_at`) VALUES (:e,:u,:c,:p,:k,:d,:cp,:b,:a,:s,:x,:f)',['e'=>$eventId,'u'=>$uid,'c'=>$cid,'p'=>(int)$r['place'],'k'=>(int)$m['kills'],'d'=>(int)$m['damage'],'cp'=>$r['captured']?1:0,'b'=>(int)$r['before'],'a'=>(int)$r['after'],'s'=>$silver,'x'=>$exp,'f'=>$now]);if(function_exists('game_quest_progress_event'))game_quest_progress_event($uid,10,1,$r['captured']?1:0);}
db_execute("UPDATE `altar_v512_event` SET `state`='finished',`finished_at`=:f WHERE `id`=:e",['f'=>$now,'e'=>$eventId]);game_v51_log('altar',$eventId,'Битва за алтари завершена.');game_v51_publish_finish_news('altar',$eventId);$pdo->commit();
}catch(Throwable $x){if($pdo->inTransaction())$pdo->rollBack();throw $x;}
}
function game_v51_process_running(string $mode,array $event): ?array {
$t=game_v51_tables($mode);$eid=(int)$event['id'];$now=time();$last=max((int)$event['last_tick'],(int)$event['start_at']);$until=min($now,(int)$event['battle_end']);$rounds=max(0,min(180,(int)floor(($until-$last)/10)));
if($rounds>0){$newTick=$last+$rounds*10;$claim=db_prepare("UPDATE `{$t['event']}` SET `last_tick`=:n WHERE `id`=:e AND `state`='running' AND `last_tick`=:o",['n'=>$newTick,'e'=>$eid,'o'=>(int)$event['last_tick']]);if($claim->rowCount()===1){if($mode==='undying')game_v51_simulate_undying($eid,$rounds);else game_v51_simulate_altars($eid,$rounds);}}
$event=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `id`=:e",['e'=>$eid])??$event;$early=false;
if($mode==='undying'){$rows=db_prepare('SELECT `team`,COUNT(*) c FROM `undying_v512_member` WHERE `event_id`=:e AND `dead`=0 GROUP BY `team`',['e'=>$eid])->fetchAll();$teams=[];foreach($rows as $r)$teams[(int)$r['team']]=(int)$r['c'];$early=(($teams[0]??0)===0||($teams[1]??0)===0);}else{$alive=(int)(db_fetch_one('SELECT COUNT(*) c FROM `altar_v512_member` WHERE `event_id`=:e AND `dead`=0',['e'=>$eid])['c']??0);$early=$alive<=1;}
if((int)$event['battle_end']<=time()||$early){if($mode==='undying')game_v51_finalize_undying($eid);else game_v51_finalize_altars($eid);return null;}return $event;
}
function game_v51_current(string $mode): ?array {
game_v51_bootstrap();$t=game_v51_tables($mode);$now=time();
$running=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `state`='running' ORDER BY `start_at` DESC,`id` DESC LIMIT 1");
if($running){$running=game_v51_process_running($mode,$running);if($running)return $running;}
$slot=game_v51_prev_start($mode,$now);
if($slot>0){
$event=db_fetch_one("SELECT * FROM `{$t['event']}` WHERE `start_at`=:s LIMIT 1",['s'=>$slot]);
if(!$event)$event=game_v51_start_slot($mode,$slot);
if($event&&$event['state']==='running'){$event=game_v51_process_running($mode,$event);if($event)return $event;}
}
return game_v51_create_waiting($mode,$now);
}
function game_v51_events_tick(): void {
static $done=false;if($done)return;$done=true;
$now=time();$last=(int)game_meta_get('v512_events_last_tick','0');if($last>$now-10)return;game_meta_set('v512_events_last_tick',(string)$now);
foreach(['undying','altar'] as $m){try{game_v51_current($m);}catch(Throwable $e){if(function_exists('app_log_write'))app_log_write('EVENTS','Event tick failed',['mode'=>$m,'type'=>get_class($e),'message'=>$e->getMessage()]);}}
}
function game_v51_current_undying(): ?array {return game_v51_current('undying');}
function game_v51_current_altars(): ?array {return game_v51_current('altar');}
function game_v51_sync_mode(string $mode): ?array {return game_v51_current($mode);} // совместимость без AJAX
function game_v51_join_undying(int $uid): void {
game_v51_bootstrap();$slot=game_v51_next_start('undying',time());$s=game_v51_snapshot_user($uid);if(!$s)throw new RuntimeException('Игрок не найден.');
try{db_execute('INSERT INTO `undying_v512_signup` (`slot_start`,`user_id`,`name`,`clan_id`,`race`,`hp`,`max_hp`,`power`,`defense`,`statue_bonus`,`created_at`) VALUES (:ss,:u,:n,:c,:r,:h,:mh,:p,:d,:b,:t)',['ss'=>$slot,'u'=>$uid,'n'=>$s['name'],'c'=>$s['clan_id'],'r'=>$s['race'],'h'=>$s['hp'],'mh'=>$s['max_hp'],'p'=>$s['power'],'d'=>$s['defense'],'b'=>$s['statue_bonus'],'t'=>time()]);}
catch(PDOException $e){if((string)$e->getCode()==='23000')throw new RuntimeException('Вы уже записаны на эту битву.');throw $e;}
}
function game_v51_join_altars(int $uid): void {
game_v51_bootstrap();$s=game_v51_snapshot_user($uid);if(!$s||(int)$s['clan_id']<=0)throw new RuntimeException('Древние алтари доступны только игрокам клана.');$slot=game_v51_next_start('altar',time());
try{db_execute('INSERT INTO `altar_v512_signup` (`slot_start`,`user_id`,`clan_id`,`name`,`race`,`hp`,`max_hp`,`power`,`defense`,`statue_bonus`,`created_at`) VALUES (:ss,:u,:c,:n,:r,:h,:mh,:p,:d,:b,:t)',['ss'=>$slot,'u'=>$uid,'c'=>$s['clan_id'],'n'=>$s['name'],'r'=>$s['race'],'h'=>$s['hp'],'mh'=>$s['max_hp'],'p'=>$s['power'],'d'=>$s['defense'],'b'=>$s['statue_bonus'],'t'=>time()]);}
catch(PDOException $e){if((string)$e->getCode()==='23000')throw new RuntimeException('Вы уже записаны на эту битву.');throw $e;}
}
function game_v51_activate_undying_bonus(int $uid): int {$s=game_v51_snapshot_user($uid);return $s?game_v51_statue_personal_bonus($s):0;}
function game_v51_activate_altar_bonus(int $uid): int {$s=game_v51_snapshot_user($uid);if(!$s||(int)$s['clan_id']<=0)throw new RuntimeException('Нет клана.');return game_v51_statue_personal_bonus($s);}
function game_v51_event_action(string $mode,int $uid,string $action): array {
if(!in_array($mode,['undying','altar'],true))throw new RuntimeException('Неизвестный бой.');$e=game_v51_current($mode);if(!$e||$e['state']!=='running')throw new RuntimeException('Сражение сейчас не идёт.');$t=game_v51_tables($mode);$eid=(int)$e['id'];$pdo=db_pdo();$pdo->beginTransaction();
try{
$m=db_fetch_one("SELECT * FROM `{$t['member']}` WHERE `event_id`=:e AND `user_id`=:u AND `is_bot`=0 LIMIT 1 FOR UPDATE",['e'=>$eid,'u'=>$uid]);if(!$m)throw new RuntimeException('Вы не были записаны на эту битву.');if((int)$m['dead']===1)throw new RuntimeException('Ваш герой уже выбыл.');$now=time();
if($action==='stone'){if((int)$m['stone_cd']>$now)throw new RuntimeException('Камень ещё восстанавливается.');$st=db_prepare('UPDATE `users` SET `kamen`=`kamen`-1 WHERE `id`=:u AND `kamen`>0',['u'=>$uid]);if($st->rowCount()!==1)throw new RuntimeException('Нет камня.');db_execute("UPDATE `{$t['member']}` SET `stone_until`=:su,`stone_cd`=:cd WHERE `id`=:i",['su'=>$now+18,'cd'=>$now+45,'i'=>(int)$m['id']]);$pdo->commit();return ['message'=>'Камень активирован: +35% к урону на 18 секунд.'];}
if($action==='grass'){if((int)$m['grass_cd']>$now)throw new RuntimeException('Трава ещё восстанавливается.');$st=db_prepare('UPDATE `users` SET `trava`=`trava`-1 WHERE `id`=:u AND `trava`>0',['u'=>$uid]);if($st->rowCount()!==1)throw new RuntimeException('Нет травы.');$heal=max(1,(int)round((int)$m['max_hp']*.40));$hp=min((int)$m['max_hp'],(int)$m['hp']+$heal);db_execute("UPDATE `{$t['member']}` SET `hp`=:h,`grass_cd`=:cd WHERE `id`=:i",['h'=>$hp,'cd'=>$now+60,'i'=>(int)$m['id']]);$pdo->commit();return ['message'=>'Трава восстановила '.($hp-(int)$m['hp']).' здоровья.'];}
$extra=$mode==='undying'?' AND `team`<>:tm':'';$params=['e'=>$eid,'self'=>(int)$m['id']];if($mode==='undying')$params['tm']=(int)$m['team'];
if($action==='target'){$tar=db_fetch_one("SELECT `id` FROM `{$t['member']}` WHERE `event_id`=:e AND `id`<>:self AND `dead`=0$extra ORDER BY RAND() LIMIT 1",$params);if(!$tar)throw new RuntimeException('Живых противников нет.');db_execute("UPDATE `{$t['member']}` SET `target_id`=:t WHERE `id`=:i",['t'=>(int)$tar['id'],'i'=>(int)$m['id']]);$pdo->commit();return ['message'=>'Цель сменена.'];}
if($action!=='attack')throw new RuntimeException('Неизвестное действие.');if($now-(int)$m['last_action']<3)throw new RuntimeException('До следующего удара нужно подождать '.(3-($now-(int)$m['last_action'])).' сек.');$tar=null;if((int)$m['target_id']>0)$tar=db_fetch_one("SELECT * FROM `{$t['member']}` WHERE `id`=:i AND `event_id`=:e AND `dead`=0 LIMIT 1 FOR UPDATE",['i'=>(int)$m['target_id'],'e'=>$eid]);if($tar&&$mode==='undying'&&(int)$tar['team']===(int)$m['team'])$tar=null;if(!$tar)$tar=db_fetch_one("SELECT * FROM `{$t['member']}` WHERE `event_id`=:e AND `id`<>:self AND `dead`=0$extra ORDER BY RAND() LIMIT 1",$params);if(!$tar)throw new RuntimeException('Живых противников нет.');$dmg=game_v51_damage($m,$tar,1.0);$hp=max(0,(int)$tar['hp']-$dmg);$kill=$hp===0?1:0;db_execute("UPDATE `{$t['member']}` SET `hp`=:h,`dead`=:x WHERE `id`=:i",['h'=>$hp,'x'=>$kill,'i'=>(int)$tar['id']]);db_execute("UPDATE `{$t['member']}` SET `damage`=`damage`+:dm,`kills`=`kills`+:k,`last_action`=:n,`target_id`=:tg WHERE `id`=:i",['dm'=>$dmg,'k'=>$kill,'n'=>$now,'tg'=>(int)$tar['id'],'i'=>(int)$m['id']]);game_v51_log($mode,$eid,(string)$m['name'].' нанёс '.(string)$tar['name'].' '.$dmg.' урона'.($kill?' и победил цель.':'.'));$pdo->commit();return ['message'=>'Нанесено '.$dmg.' урона.','damage'=>$dmg,'killed'=>$kill];
}catch(Throwable $x){if($pdo->inTransaction())$pdo->rollBack();throw $x;}
}
function game_v51_clan_altars(int $eventId,int $clanId): array {
$p=game_v51_altar_progress($clanId);if($clanId<=0)return ['damage'=>0,'altars'=>0,'place'=>0,'level'=>$p['level'],'name'=>$p['name'],'required'=>$p['level']*5000];$damage=(int)(db_fetch_one('SELECT COALESCE(SUM(`damage`),0) d FROM `altar_v512_member` WHERE `event_id`=:e AND `clan_id`=:c AND `is_bot`=0',['e'=>$eventId,'c'=>$clanId])['d']??0);$rows=db_prepare('SELECT `clan_id`,SUM(`damage`) d FROM `altar_v512_member` WHERE `event_id`=:e AND `clan_id`>0 AND `is_bot`=0 GROUP BY `clan_id` ORDER BY d DESC',['e'=>$eventId])->fetchAll();$place=0;foreach($rows as $i=>$r)if((int)$r['clan_id']===$clanId){$place=$i+1;break;}return ['damage'=>$damage,'altars'=>min(10,(int)floor($damage/5000)),'place'=>$place,'level'=>(int)$p['level'],'name'=>(string)$p['name'],'required'=>(int)$p['level']*5000];
}