import Foundation import Display import AsyncDisplayKit import TelegramCore import SwiftSignalKit import StickerResources import ManagedAnimationNode import AnimatedStickerNode import TelegramAnimatedStickerNode import AppBundle private struct SlotMachineValue { enum ReelValue { case rolling case bar case berries case lemon case seven case sevenWin var isResult: Bool { if case .rolling = self { return false } else { return true } } } let left: ReelValue let center: ReelValue let right: ReelValue init(rawValue: Int32?) { if let rawValue = rawValue, rawValue > 0 { let rawValue = rawValue - 1 let leftRawValue = rawValue & 3 let centerRawValue = rawValue >> 2 & 3 let rightRawValue = rawValue >> 4 func reelValue(for rawValue: Int32) -> ReelValue { switch rawValue { case 0: return .bar case 1: return .berries case 2: return .lemon case 3: return .seven default: return .rolling } } var leftReelValue = reelValue(for: leftRawValue) var centerReelValue = reelValue(for: centerRawValue) var rightReelValue = reelValue(for: rightRawValue) if leftReelValue == .seven && centerReelValue == .seven && rightReelValue == .seven { leftReelValue = .sevenWin centerReelValue = .sevenWin rightReelValue = .sevenWin } self.left = leftReelValue self.center = centerReelValue self.right = rightReelValue } else { self.left = .rolling self.center = .rolling self.right = .rolling } } var isThreeOfSame: Bool { return self.left == self.center && self.center == self.right && self.left.isResult } var is777: Bool { return self.left == .sevenWin && self.center == .sevenWin && self.right == .sevenWin } } private func leftReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem { let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil switch value { case .rolling: return ManagedAnimationItem(source: .local("Slot_L_Spinning"), loop: true) case .bar: return ManagedAnimationItem(source: .local("Slot_L_Bar"), frames: frames, loop: false) case .berries: return ManagedAnimationItem(source: .local("Slot_L_Berries"), frames: frames, loop: false) case .lemon: return ManagedAnimationItem(source: .local("Slot_L_Lemon"), frames: frames, loop: false) case .seven: return ManagedAnimationItem(source: .local("Slot_L_7"), frames: frames, loop: false) case .sevenWin: return ManagedAnimationItem(source: .local("Slot_L_7_Win"), frames: frames, loop: false) } } private func centerReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem { let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil switch value { case .rolling: return ManagedAnimationItem(source: .local("Slot_M_Spinning"), frames: frames, loop: true) case .bar: return ManagedAnimationItem(source: .local("Slot_M_Bar"), frames: frames, loop: false) case .berries: return ManagedAnimationItem(source: .local("Slot_M_Berries"), frames: frames, loop: false) case .lemon: return ManagedAnimationItem(source: .local("Slot_M_Lemon"), frames: frames, loop: false) case .seven: return ManagedAnimationItem(source: .local("Slot_M_7"), frames: frames, loop: false) case .sevenWin: return ManagedAnimationItem(source: .local("Slot_M_7_Win"), frames: frames, loop: false) } } private func rightReelAnimationItem(value: SlotMachineValue.ReelValue, immediate: Bool = false) -> ManagedAnimationItem { let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil switch value { case .rolling: return ManagedAnimationItem(source: .local("Slot_R_Spinning"), frames: frames, loop: true) case .bar: return ManagedAnimationItem(source: .local("Slot_R_Bar"), frames: frames, loop: false) case .berries: return ManagedAnimationItem(source: .local("Slot_R_Berries"), frames: frames, loop: false) case .lemon: return ManagedAnimationItem(source: .local("Slot_R_Lemon"), frames: frames, loop: false) case .seven: return ManagedAnimationItem(source: .local("Slot_R_7"), frames: frames, loop: false) case .sevenWin: return ManagedAnimationItem(source: .local("Slot_R_7_Win"), frames: frames, loop: false) } } public enum ManagedSlotMachineAnimationState: Equatable { case rolling case value(Int32, Bool) } public final class SlotMachineAnimationNode: ASDisplayNode { private let backNode: ManagedAnimationNode private let leftReelNode: DiceAnimatedStickerNode private let centerReelNode: DiceAnimatedStickerNode private let rightReelNode: DiceAnimatedStickerNode private let frontNode: ManagedAnimationNode private var diceState: ManagedSlotMachineAnimationState? = nil private let disposables = DisposableSet() private let animationSize: CGSize public var success: ((Bool) -> Void)? public init(account: Account, size: CGSize = CGSize(width: 184.0, height: 184.0)) { self.animationSize = size self.backNode = ManagedAnimationNode(size: self.animationSize) let reelSize = CGSize(width: 384.0, height: 384.0) self.leftReelNode = DiceAnimatedStickerNode(account: account, size: reelSize) self.centerReelNode = DiceAnimatedStickerNode(account: account,size: reelSize) self.rightReelNode = DiceAnimatedStickerNode(account: account,size: reelSize) self.frontNode = ManagedAnimationNode(size: self.animationSize) super.init() self.addSubnode(self.backNode) self.addSubnode(self.leftReelNode) self.addSubnode(self.centerReelNode) self.addSubnode(self.rightReelNode) self.addSubnode(self.frontNode) } deinit { self.disposables.dispose() } public override func layout() { super.layout() self.backNode.frame = self.bounds self.leftReelNode.frame = self.bounds self.centerReelNode.frame = self.bounds self.rightReelNode.frame = self.bounds self.frontNode.frame = self.bounds } public func setState(_ diceState: ManagedSlotMachineAnimationState) { let previousState = self.diceState self.diceState = diceState if let previousState = previousState { switch previousState { case .rolling: switch diceState { case let .value(value, _): let slotValue = SlotMachineValue(rawValue: value) if slotValue.isThreeOfSame { Queue.mainQueue().after(1.5) { self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), loop: false)) self.success?(!slotValue.is777) } } else { self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false)) } self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center)) self.rightReelNode.trackTo(item: rightReelAnimationItem(value: slotValue.right)) self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: .still(.end), loop: false)) case .rolling: break } case .value: switch diceState { case .rolling: self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false)) self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling)) self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling)) self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), loop: false)) case .value: break } } } else { self.backNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Back_Win"), frames: .still(.start), loop: false)) switch diceState { case let .value(value, immediate): let slotValue = SlotMachineValue(rawValue: value) self.leftReelNode.trackTo(item: leftReelAnimationItem(value: slotValue.left, immediate: immediate)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: slotValue.center, immediate: immediate)) self.rightReelNode.trackTo(item: rightReelAnimationItem(value: slotValue.right, immediate: immediate)) let frames: ManagedAnimationFrameRange? = immediate ? .still(.end) : nil self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), frames: frames, loop: false)) case .rolling: self.leftReelNode.trackTo(item: leftReelAnimationItem(value: .rolling)) self.centerReelNode.trackTo(item: centerReelAnimationItem(value: .rolling)) self.rightReelNode.trackTo(item: rightReelAnimationItem(value: .rolling)) self.frontNode.trackTo(item: ManagedAnimationItem(source: .local("Slot_Front_Pull"), loop: false)) } } } public func setOverlayColor(_ color: UIColor?, animated: Bool) { } } class DiceAnimatedStickerNode: ASDisplayNode { private let account: Account public let intrinsicSize: CGSize private let animationNode: AnimatedStickerNode public var state: ManagedAnimationState? public var trackStack: [ManagedAnimationItem] = [] public var didTryAdvancingState = false init(account: Account, size: CGSize) { self.account = account self.intrinsicSize = size self.animationNode = AnimatedStickerNode() self.animationNode.autoplay = true super.init() self.addSubnode(self.animationNode) self.animationNode.completed = { [weak self] willStop in guard let strongSelf = self, !strongSelf.didTryAdvancingState, let state = strongSelf.state else { return } if state.item.loop && strongSelf.trackStack.isEmpty { } else { strongSelf.didTryAdvancingState = true strongSelf.advanceState() } } } var initialized = false override func didLoad() { super.didLoad() self.initialized = true self.advanceState() } private func advanceState() { guard !self.trackStack.isEmpty else { return } let item = self.trackStack.removeFirst() if let state = self.state, state.item.source == item.source { self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: state) } else { self.state = ManagedAnimationState(displaySize: self.intrinsicSize, item: item, current: nil) } var source: AnimatedStickerNodeSource? switch item.source { case let .local(animationName): source = AnimatedStickerNodeLocalFileSource(name: animationName) case let .resource(account, resource): source = AnimatedStickerResourceSource(account: account, resource: resource) } let playbackMode: AnimatedStickerPlaybackMode if item.loop { playbackMode = .loop } else if let frames = item.frames, case let .still(position) = frames { playbackMode = .still(position == .start ? .start : .end) } else { playbackMode = .once } if let source = source { self.animationNode.setup(source: source, width: Int(self.intrinsicSize.width), height: Int(self.intrinsicSize.height), playbackMode: playbackMode, mode: .direct(cachePathPrefix: nil)) } self.didTryAdvancingState = false } func trackTo(item: ManagedAnimationItem) { if let currentItem = self.state?.item { if currentItem.source == item.source && currentItem.frames == item.frames && currentItem.loop == item.loop { return } } self.trackStack.append(item) self.didTryAdvancingState = false if !self.animationNode.isPlaying && self.initialized { self.advanceState() } } override func layout() { super.layout() self.animationNode.updateLayout(size: self.bounds.size) self.animationNode.frame = self.bounds } public func setOverlayColor(_ color: UIColor?, animated: Bool) { } }