import { animate, keyframes, style, transition, trigger } from '@angular/animations'; import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; import { getAudioPath } from 'src/app/helper/tts.helper'; import { VoiceService } from "../../services/voice.service"; interface RuleItem { text: string; action?: () => void; voice?: number; screpa?: boolean; hideWithoutVoice?: boolean; } @Component({ selector: 'app-onboarding', templateUrl: './onboarding.component.html', styleUrls: ['./onboarding.component.scss'], animations: [ trigger('fadeInAnimation', [ transition('void => *', []), // when the item is created transition('* => void', []), // when the item is removed transition('* => *', [ // when the item is changed animate(1200, keyframes([ // animate for 1200 ms style ({ opacity: 0.0}), style ({ opacity: 1.0 }), ])), ]), ])] }) export class OnboardingComponent implements OnInit, OnDestroy { @ViewChild('avoidPenaltyCard') private avoidPenaltyCardEl: ElementRef; @ViewChild('stolePrizeCard') private stolePrizeCardEl: ElementRef; @ViewChild('versusCard') private versusCardEl: ElementRef; @ViewChild('luckyCard') private luckyCardEl: ElementRef; @ViewChild('banPlayerCard') private banPlayerEl: ElementRef; @ViewChild('doubleTreasureCard') private doubleTreasureCardEl: ElementRef; private rules: RuleItem[] = [ { text: 'Игра состоит из вопросов с четырьмя вариантами ответов, правильный - только один.'}, { text: 'Вопросы и ответы будут отображаться на экране и в Боте Благодарения.' }, { text: 'Каждый игрок в начале игры имеет на руках 4 карты, набор карт определяется случайно. Описание карт ты найдешь ниже. После использования карты ты получаешь новую случайную карту.' }, { text: 'На разыгрывание карты время ограничено, примерно 10 секунд.' }, { text: 'Вы долго просили оптимизировать геймплей для медленных и глупых, и мы это сделали!'}, { text: 'Задача игрока - ответить правильно и быстрее других, ну или хотя бы просто правильно в течение 20 секунд' }, { text: 'Первый игрок, ответивший правильно, получает два очка' }, { text: 'Все остальные, ответившие правильно, получают одно очко'}, { text: 'Иногда за неправильные ответы игроки будут получать наказания' }, { text: 'Избежать наказания можно только с помощью соотвествуещей карты, данную карту ты можешь сыграть перед озвучиванием наказания', action: () => { this.shakeCard(this.avoidPenaltyCardEl); }}, { text: 'Карту "украсть приз" ты можешь сыграть в момент, когда кто-то собирается получить награду, но до момента того, как ты узнаешь, что это именно за приз', action: () => { this.shakeCard(this.stolePrizeCardEl); }}, { text: 'Карту "Поединок" ты можешь разыграть в любой момент, чтобы вызвать игрока на дуэль', action: () => { this.shakeCard(this.versusCardEl); }}, { text: '"Лаки карту" ты сможешь сыграть после своего правильного ответа, она увеличит твои шансы на получение приза', action: () => { this.shakeCard(this.luckyCardEl); }}, { text: 'Карту бана можно сыграть на любого игрока (даже себя), чтобы отправить его на пару ходов во Владикавказ. Игрок не сможет участвовать в случайном количестве раундов', action: () => { this.shakeCard(this.banPlayerEl); } }, { text: 'Ну и самая редкая карта - карта удвоения приза, играй ее перед тем, как получить награду, и вместо одной награды ты получишь две!', action: () => { this.shakeCard(this.doubleTreasureCardEl); } }, { text: 'Не торопись с ответами, игра идет до той поры, пока мы не разыграем все призы' }, { text: 'Ах да, так как создатель игры, работает на Microsoft, мы добавили привычные вам баги, но все их оставим в секрете', }, { text: 'Кажется, правила закончились' } ]; public currentRule: string; private currentRulePosition = 0; private allRulesAnnounced = false; private destroyed$ = new Subject(); private voiceSubscription: Subscription; public screpaText = ''; public showScrepa = false; constructor(private voiceService: VoiceService, private renderer: Renderer2) { } ngOnInit(): void { this.voiceService.playAudio(getAudioPath('Итак, друзья, перейдем к правилам')); setTimeout(() => { this.beginRuleSwitching(); }, 3500); } ngOnDestroy(): void { this.destroyed$.complete(); this.voiceSubscription?.unsubscribe(); } shakeCard(card: ElementRef) { this.renderer.addClass(card.nativeElement, 'shake'); this.renderer.addClass(card.nativeElement, 'zoom-in'); if(!this.allRulesAnnounced) { this.voiceService.audioEndedSubject .pipe(takeUntil(this.destroyed$),take(1)) .subscribe(() => { this.renderer.addClass(card.nativeElement, 'zoom-out'); setTimeout(() => { this.renderer.removeClass(card.nativeElement, 'zoom-out'); this.renderer.removeClass(card.nativeElement, 'zoom-in'); }, 3000); this.stopShaking(card); }) } else { setTimeout(() => { this.renderer.removeClass(card.nativeElement, 'zoom-out'); this.renderer.removeClass(card.nativeElement, 'zoom-in'); }, 5000); this.stopShaking(card); } } stopShaking(card: ElementRef) { this.renderer.removeClass(card.nativeElement, 'shake'); } private handleRule(rule: RuleItem) { if(this.currentRulePosition > this.rules.length) { this.currentRulePosition = 0; } console.log(`handle rule ${this.currentRulePosition}`); this.currentRule = rule.screpa ? this.currentRule : rule.text.replace(/(?<=\[)(.*?)(?=\])/, '').replace('[','').replace(']','') if (!this.allRulesAnnounced) { const voice = rule.voice ? rule.voice : 1; this.showScrepa = !!rule.screpa; this.screpaText = rule.text; this.voiceService.playAudio(getAudioPath(rule.text, voice)); } else { if(rule.hideWithoutVoice || rule.screpa) { this.playNextRule(); } } if (rule.action) { rule.action(); } } private playNextRule() { this.currentRulePosition++; if (this.currentRulePosition === this.rules.length && !this.allRulesAnnounced) { this.allRulesAnnounced = true; this.voiceService.playAudio(getAudioPath(`Это все правила, надеюсь, все понятно. А если нет - сейчас Кирилл и Оксана вам все пояснят, ну и совсем для тупых - пустила по кругу правила на экране, а если ты их не поймешь - то очень жаль тебя глупенького`)); this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$),take(1)).subscribe(() => { setInterval(() => { this.playNextRule() }, 6000); this.currentRulePosition = 0 }); } else { this.handleRule(this.rules[this.currentRulePosition]); } } private beginRuleSwitching() { this.handleRule(this.rules[this.currentRulePosition]); this.voiceSubscription = this.voiceService.audioEndedSubject.pipe(takeUntil(this.destroyed$)).subscribe(() => { setTimeout(() => this.playNextRule(), 500); console.log(`audioEnded`); }) } }