Файл: system/v23_features.php
Строк: 330
<?php
/* Автоматизация, кольца, бонусный комплект и рекорды: by Метриум (Стэлп) */
declare(strict_types=1);
function game_v23_bootstrap(): void {
static $done=false; if($done)return; $done=true;
$ver=(int)(game_meta_get('v23_schema','0'));
// Старые установки v48 проходят миграцию один раз; после v52 лишние SHOW/ALTER не выполняются.
if($ver>=53)return;
db_query("CREATE TABLE IF NOT EXISTS `bonus_kit_offer` (`user` int NOT NULL,`expires_at` int NOT NULL,`claimed` tinyint NOT NULL DEFAULT 0,`topup_gold` int NOT NULL DEFAULT 0,`eligible` tinyint NOT NULL DEFAULT 0,`kit_id` int NOT NULL DEFAULT 27,`granted_at` int NOT NULL DEFAULT 0,PRIMARY KEY(`user`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
foreach(['topup_gold'=>'int NOT NULL DEFAULT 0','eligible'=>'tinyint NOT NULL DEFAULT 0','kit_id'=>'int NOT NULL DEFAULT 27','granted_at'=>'int NOT NULL DEFAULT 0'] as $column=>$def){ $c=db_fetch_assoc(db_query("SHOW COLUMNS FROM `bonus_kit_offer` LIKE '".db_real_escape_string($column)."'")); if(!$c) db_query("ALTER TABLE `bonus_kit_offer` ADD COLUMN `".$column."` ".$def); }
db_query("CREATE TABLE IF NOT EXISTS `payment_receipts` (`receipt_hash` char(64) NOT NULL,`user` int NOT NULL,`gold` int NOT NULL DEFAULT 0,`amount` decimal(12,2) NOT NULL DEFAULT 0,`created_at` int NOT NULL,PRIMARY KEY(`receipt_hash`),KEY `user_time`(`user`,`created_at`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `ring_catalog` (`id` tinyint NOT NULL,`name` varchar(80) NOT NULL,`description` varchar(255) NOT NULL,`price` int NOT NULL DEFAULT 250,`kind` varchar(20) NOT NULL,PRIMARY KEY(`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db_query("CREATE TABLE IF NOT EXISTS `user_rings` (`user` int NOT NULL,`ring_id` tinyint NOT NULL,`slot` tinyint NOT NULL DEFAULT 0,`level` smallint NOT NULL DEFAULT 1,PRIMARY KEY(`user`,`ring_id`),KEY `slot`(`user`,`slot`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
$ringColumns=[
'level'=>"smallint NOT NULL DEFAULT 1 AFTER `slot`",
'rune'=>"tinyint NOT NULL DEFAULT 0 AFTER `level`",
'bonus'=>"smallint NOT NULL DEFAULT 0 AFTER `rune`",
'stars'=>"tinyint NOT NULL DEFAULT 0 AFTER `bonus`",
'smith'=>"smallint NOT NULL DEFAULT 0 AFTER `stars`",
];
foreach($ringColumns as $column=>$definition){
$has=db_fetch_assoc(db_query("SHOW COLUMNS FROM `user_rings` LIKE '".db_real_escape_string($column)."'"));
if(!$has){ db_query("ALTER TABLE `user_rings` ADD COLUMN `".$column."` ".$definition); }
}
db_query("INSERT IGNORE INTO `ring_catalog` (`id`,`name`,`description`,`price`,`kind`) VALUES
(1,'Кольцо Ярости','Увеличивает силу критического удара в зависимости от потерянного здоровья',250,'rage'),
(2,'Кольцо Защиты','Увеличивает броню при отражении критического удара в зависимости от потерянного здоровья',250,'guard'),
(3,'Кольцо Воскрешения','Воскрешает владельца в момент получения смертельного удара',250,'revive')");
game_meta_set('v23_schema','53');
}
function game_bonus_offer_ensure(int $uid): array {
$r=db_fetch_one('SELECT * FROM `bonus_kit_offer` WHERE `user`=:u LIMIT 1',['u'=>$uid]);
if(!$r){$exp=time()+3*86400;db_execute('INSERT INTO `bonus_kit_offer` (`user`,`expires_at`,`claimed`,`topup_gold`,`eligible`,`kit_id`,`granted_at`) VALUES (:u,:e,0,0,0,27,0)',['u'=>$uid,'e'=>$exp]);$r=db_fetch_one('SELECT * FROM `bonus_kit_offer` WHERE `user`=:u',['u'=>$uid])??[];}
return $r;
}
function game_bonus_kit_details(int $uid): array {
$offer=game_bonus_offer_ensure($uid); $kitId=max(1,(int)($offer['kit_id']??27));
$kit=db_fetch_one('SELECT * FROM `complects` WHERE `id`=:i',['i'=>$kitId]);
if(!$kit) throw new RuntimeException('Бонусный комплект не найден.');
$ids=[]; for($i=1;$i<=8;$i++) if((int)($kit['w_'.$i]??0)>0)$ids[]=(int)$kit['w_'.$i];
$items=[]; $sum=0;
foreach($ids as $id){
$row=db_fetch_one('SELECT i.`id`,i.`name`,i.`w`,s.`quality`,s.`bonus`,s.`_str`,s.`_vit`,s.`_agi`,s.`_def` FROM `items` i JOIN `shop` s ON s.`id`=i.`id` WHERE i.`id`=:i',['i'=>$id]);
if($row){$sum+=(int)$row['_str']+(int)$row['_vit']+(int)$row['_agi']+(int)$row['_def'];$items[]=$row;}
}
return ['offer'=>$offer,'kit'=>$kit,'items'=>$items,'sum'=>$sum,'ids'=>$ids];
}
function game_bonus_kit_grant(int $uid): bool {
$pdo=db_pdo(); $own=!$pdo->inTransaction(); if($own)$pdo->beginTransaction();
try{
$o=db_fetch_one('SELECT * FROM `bonus_kit_offer` WHERE `user`=:u FOR UPDATE',['u'=>$uid]);
if(!$o || (int)$o['claimed']===1){ if($own)$pdo->commit(); return false; }
if((int)$o['eligible']!==1 || (int)$o['topup_gold']<100) throw new RuntimeException('Для комплекта требуется реальное пополнение минимум на 100 золота.');
$kit=db_fetch_one('SELECT * FROM `complects` WHERE `id`=:i',['i'=>max(1,(int)($o['kit_id']??27))]);
if(!$kit) throw new RuntimeException('Комплект не найден.');
for($slot=1;$slot<=8;$slot++){
$itemId=(int)($kit['w_'.$slot]??0); if($itemId<=0)continue;
$row=db_fetch_one('SELECT i.`id`,s.`quality`,s.`bonus`,s.`_str`,s.`_vit`,s.`_agi`,s.`_def` FROM `items` i JOIN `shop` s ON s.`id`=i.`id` WHERE i.`id`=:i',['i'=>$itemId]);
if(!$row)continue;
db_execute('INSERT INTO `inv` (`user`,`item`,`quality`,`bonus`,`smith`,`equip`,`new`,`rune`,`_str`,`_vit`,`_agi`,`_def`,`place`) VALUES (:u,:i,:q,:b,0,0,1,0,:s,:v,:a,:d,0)',[
'u'=>$uid,'i'=>$itemId,'q'=>(int)$row['quality'],'b'=>(int)$row['bonus'],'s'=>(int)$row['_str'],'v'=>(int)$row['_vit'],'a'=>(int)$row['_agi'],'d'=>(int)$row['_def']]);
}
db_execute('UPDATE `bonus_kit_offer` SET `claimed`=1,`granted_at`=:t WHERE `user`=:u',['t'=>time(),'u'=>$uid]);
if(function_exists('game_system_mail')) game_system_mail($uid,'[color=green]Бонусный комплект за пополнение на 100 золота выдан в вашу сумку.[/color]');
if($own)$pdo->commit(); return true;
}catch(Throwable $e){ if($own&&$pdo->inTransaction())$pdo->rollBack(); throw $e; }
}
function game_bonus_offer_register_topup(int $uid,int $purchasedGold): bool {
if($uid<=0||$purchasedGold<=0)return false; $o=game_bonus_offer_ensure($uid);
if((int)$o['claimed']===1)return false;
if(time()>(int)$o['expires_at'])return false;
db_execute('UPDATE `bonus_kit_offer` SET `topup_gold`=LEAST(1000000,`topup_gold`+:g) WHERE `user`=:u',['g'=>$purchasedGold,'u'=>$uid]);
$o=db_fetch_one('SELECT * FROM `bonus_kit_offer` WHERE `user`=:u',['u'=>$uid]);
if((int)($o['topup_gold']??0)>=100){ db_execute('UPDATE `bonus_kit_offer` SET `eligible`=1 WHERE `user`=:u',['u'=>$uid]); return game_bonus_kit_grant($uid); }
return false;
}
function game_live_time_attr(int $ts): string { return ' data-live-until="'.max(0,$ts).'"'; }
function game_ring_slots(int $uid): array { $out=[1=>0,2=>0,3=>0];$q=db_query("SELECT `ring_id`,`slot` FROM `user_rings` WHERE `user`=$uid AND `slot` IN (1,2,3)");while($r=db_fetch_assoc($q))$out[(int)$r['slot']]=(int)$r['ring_id'];return $out; }
function game_personal_records(array $u): array { return [
['Общий рейтинг',(int)($u['exp']??0),1000,'+200 к параметрам'],
['Золотая лихорадка',(int)($u['g']??0),2500,'+200 к параметрам'],
['Титан арены',(int)($u['arena_pobed']??0),1000,'+200 к параметрам'],
['Поиск с камней',(int)($u['stone']??0),50,'+200 к параметрам'],
['Поиск с травы',(int)($u['grass']??0),50,'+200 к параметрам'],
['Монстры подземелья',(int)($u['bos_1']??0),20,'+200 к параметрам'],
['Титан лиги',(int)($u['league_fights']??0),53,'+200 к параметрам'],
['Алхимик',(int)($u['parse']??0),25,'+200 к параметрам'] ]; }
/** Автоматически переводит Долину бессмертных в фазу боя по таймеру. by Метриум (Стэлп) */
function game_undying_tick(): void {
static $done=false; if($done) return; $done=true;
if(function_exists('game_v51_events_tick')) { game_v51_events_tick(); return; }
}
/** Качество и изображение кольца зависят только от уровня прокачки. by Метриум (Стэлп) */
function game_ring_quality(int $level, string $kind='rage'): array {
$level=max(1,$level);
$tier=min(7,intdiv($level-1,10));
$names=['Простое','Обычное','Редкое','Эпическое','Легендарное','Божественное','Сверхбожественное','Мифическое'];
$colors=['#b6aa8d','#79d35d','#6eb6ff','#d18cff','#ffb14a','#ffdf61','#ff6666','#58f0e8'];
$families=['rage'=>'rage','guard'=>'guard','revive'=>'revive'];
$family=$families[$kind] ?? 'rage';
return [
'tier'=>$tier,
'name'=>$names[$tier],
'color'=>$colors[$tier],
'image'=>'/images/rings/'.$family.'_'.$tier.'.png',
'family'=>$family,
];
}
/** Фиксированный слот кольца по его игровому типу. Игрок слот не выбирает. by Метриум (Стэлп) */
function game_ring_slot_for_catalog(array $ring): int {
$kind=(string)($ring['kind']??'');
return match($kind){'rage'=>1,'guard'=>2,'revive'=>3,default=>max(1,min(3,(int)($ring['id']??1)))};
}
/** Нормализует старые кольца после обновления: каждое возвращается в свой слот. by Метриум (Стэлп) */
function game_ring_normalize_user_slots(int $uid): void {
if($uid<=0) return;
$rows=[];
$q=db_query('SELECT ur.ring_id,ur.slot,rc.kind FROM user_rings ur LEFT JOIN ring_catalog rc ON rc.id=ur.ring_id WHERE ur.user='.(int)$uid.' ORDER BY ur.ring_id');
while($r=db_fetch_assoc($q)) $rows[]=$r;
foreach($rows as $r){
$slot=game_ring_slot_for_catalog($r);
if((int)($r['slot']??0)!==$slot){
db_query('UPDATE user_rings SET slot=0 WHERE user='.(int)$uid.' AND slot='.(int)$slot.' AND ring_id<>'.(int)$r['ring_id']);
db_query('UPDATE user_rings SET slot='.(int)$slot.' WHERE user='.(int)$uid.' AND ring_id='.(int)$r['ring_id']);
}
}
}
function game_ring_slot_details(int $uid): array {
game_ring_normalize_user_slots($uid);
$out=[1=>null,2=>null,3=>null];
$q=db_query('SELECT ur.ring_id,ur.slot,COALESCE(ur.level,1) level,COALESCE(ur.rune,0) rune,COALESCE(ur.bonus,0) bonus,COALESCE(ur.stars,0) stars,COALESCE(ur.smith,0) smith,rc.name,rc.kind FROM user_rings ur LEFT JOIN ring_catalog rc ON rc.id=ur.ring_id WHERE ur.user='.(int)$uid.' AND ur.slot IN (1,2,3)');
while($r=db_fetch_assoc($q)){ $slot=(int)$r['slot']; $r['quality']=game_ring_quality((int)$r['level'],(string)($r['kind']??'rage')); $out[$slot]=$r; }
return $out;
}