From d47c50b4fc3a9f75046df1b7bd45fdefc663325b Mon Sep 17 00:00:00 2001 From: kru Date: Tue, 1 Oct 2024 15:57:00 +0200 Subject: [PATCH] broken stuff --- SwiftVN.xcodeproj/project.pbxproj | 4 ++ SwiftVN/ContentView.swift | 11 ++-- SwiftVN/VNDS/ChoiceManager.swift | 59 ++++++++++++++++- SwiftVN/VNDS/NovelScene.swift | 7 ++ SwiftVN/VNDS/ScriptExecutor.swift | 105 ++++++++++++++++++++++-------- SwiftVN/VNDS/TextNode.swift | 12 +++- 6 files changed, 163 insertions(+), 35 deletions(-) diff --git a/SwiftVN.xcodeproj/project.pbxproj b/SwiftVN.xcodeproj/project.pbxproj index 47bf7e8..40eb162 100644 --- a/SwiftVN.xcodeproj/project.pbxproj +++ b/SwiftVN.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 7859C39C2CA849C200051A60 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 7859C39B2CA849C200051A60 /* ZIPFoundation */; }; 785C03A32CAB556400C27C66 /* HistoryOverlayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785C03A22CAB556000C27C66 /* HistoryOverlayNode.swift */; }; 785C03A52CABE07800C27C66 /* sazanami-gothic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 785C03A42CABE07800C27C66 /* sazanami-gothic.ttf */; }; + 785C03A72CAC102600C27C66 /* ChoiceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785C03A62CAC102500C27C66 /* ChoiceManager.swift */; }; 788EC63B2CA848F1006A3562 /* ArchiveManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788EC63A2CA848EF006A3562 /* ArchiveManager.swift */; }; 788FBCEF2CA92DCD00B976A9 /* TextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 788FBCEE2CA92DCA00B976A9 /* TextNode.swift */; }; 78918ACB2CA8C28900632961 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78918AC72CA8C28900632961 /* AudioManager.swift */; }; @@ -79,6 +80,7 @@ 7824DE3B2CA819E0003383A7 /* SwiftVN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftVN.swift; sourceTree = ""; }; 785C03A22CAB556000C27C66 /* HistoryOverlayNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryOverlayNode.swift; sourceTree = ""; }; 785C03A42CABE07800C27C66 /* sazanami-gothic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "sazanami-gothic.ttf"; sourceTree = ""; }; + 785C03A62CAC102500C27C66 /* ChoiceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChoiceManager.swift; sourceTree = ""; }; 787942252CA99B4200C50CC1 /* TODO.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = ""; }; 787942262CA99B4700C50CC1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 787942272CA9A3FA00C50CC1 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; @@ -195,6 +197,7 @@ 78918ACA2CA8C28900632961 /* VNDS */ = { isa = PBXGroup; children = ( + 785C03A62CAC102500C27C66 /* ChoiceManager.swift */, 78918AC72CA8C28900632961 /* AudioManager.swift */, 785C03A22CAB556000C27C66 /* HistoryOverlayNode.swift */, 78918ACE2CA8C29200632961 /* NovelScene.swift */, @@ -371,6 +374,7 @@ 781741612CA89C9400D0B3B9 /* LoggerFactory.swift in Sources */, 78918ACB2CA8C28900632961 /* AudioManager.swift in Sources */, 78BA5B9E2CAA73E600EBDE6B /* TextManager.swift in Sources */, + 785C03A72CAC102600C27C66 /* ChoiceManager.swift in Sources */, 78918ACD2CA8C28900632961 /* ScriptExecutor.swift in Sources */, 7824DE3C2CA819E9003383A7 /* SwiftVN.swift in Sources */, ); diff --git a/SwiftVN/ContentView.swift b/SwiftVN/ContentView.swift index 9999e3f..3fbf945 100644 --- a/SwiftVN/ContentView.swift +++ b/SwiftVN/ContentView.swift @@ -24,11 +24,12 @@ struct ContentView: View { if let scene = scene { SpriteView(scene: scene) .ignoresSafeArea() - .onTapGesture { - if (!showHistoryOverlay) { - scene.next() - } - } + .gesture( + DragGesture(minimumDistance: 0) + .onEnded { value in + scene.handleTap(at: value.location) + } + ) } // Overlay for title and FPS diff --git a/SwiftVN/VNDS/ChoiceManager.swift b/SwiftVN/VNDS/ChoiceManager.swift index 4556495..d0956bc 100644 --- a/SwiftVN/VNDS/ChoiceManager.swift +++ b/SwiftVN/VNDS/ChoiceManager.swift @@ -1,7 +1,64 @@ // -// Untitled.swift +// ChoiceManager.swift // SwiftVN // // Created by Kru on 01/10/24. // +import SpriteKit + +class ChoiceManager { + private let scene: SKScene + private var choiceNodes: [SKNode] = [] + private var onSelection: ((Int) -> Void)? + + init(scene: SKScene) { + self.scene = scene + } + + func presentChoices(_ options: [String], onSelection: @escaping (Int) -> Void) { + self.onSelection = onSelection + + let choiceContainer = SKNode() + choiceContainer.name = "choiceContainer" + + for (index, option) in options.enumerated() { + let button = SKSpriteNode(color: .blue, size: CGSize(width: 200, height: 50)) + button.position = CGPoint(x: 0, y: -CGFloat(index * 60)) + button.name = "choice_\(index)" + + let label = SKLabelNode(text: option) + label.fontColor = .white + label.verticalAlignmentMode = .center + button.addChild(label) + + choiceContainer.addChild(button) + choiceNodes.append(button) + } + + choiceContainer.position = CGPoint(x: scene.size.width / 2, y: scene.size.height - 100) + scene.addChild(choiceContainer) + } + + func handleTap(at position: CGPoint) -> Bool { + guard let choiceContainer = scene.childNode(withName: "choiceContainer") else { return false } + + for (index, choiceNode) in choiceNodes.enumerated() { + if choiceNode.contains(scene.convert(position, to: choiceNode)) { + onSelection?(index + 1) + clearChoices() + return true + } + } + return false + } + + func clearChoices() { + scene.childNode(withName: "choiceContainer")?.removeFromParent() + choiceNodes.removeAll() + } + + var hasActiveChoices: Bool { + return !choiceNodes.isEmpty + } +} diff --git a/SwiftVN/VNDS/NovelScene.swift b/SwiftVN/VNDS/NovelScene.swift index 9177870..f7db7cb 100644 --- a/SwiftVN/VNDS/NovelScene.swift +++ b/SwiftVN/VNDS/NovelScene.swift @@ -30,6 +30,13 @@ class NovelScene: SKScene, ObservableObject { executor = ScriptExecutor(scene: self) executor?.loadScript(named: "main.scr") + + // Start the script + executor?.next() + } + + func handleTap(at location: CGPoint) { + executor?.handleTap(at: location) } func next() { diff --git a/SwiftVN/VNDS/ScriptExecutor.swift b/SwiftVN/VNDS/ScriptExecutor.swift index 5e5746f..cc11524 100644 --- a/SwiftVN/VNDS/ScriptExecutor.swift +++ b/SwiftVN/VNDS/ScriptExecutor.swift @@ -5,6 +5,11 @@ // Created by Kru on 29/09/24. // +// +// TODO +// - Interpolate in most instructions, even choices? +// + import SwiftUI import SpriteKit @@ -15,17 +20,20 @@ class ScriptExecutor: ObservableObject { private var globalVariables: [String: Any] = [:] private var labels: [String: Int] = [:] - @Published var isWaitingForInput: Bool = true - @Published var isLoadingMusic: Bool = false + @Published var isLoadingMusic = false + @Published var isWaitingForInput = true + @Published var isWaitingForChoice = false var skip = false private let scene: NovelScene private let archiveManager: ArchiveManager = ArchiveManager(zipFileName: "script.zip") + private let choiceManager: ChoiceManager private let logger = LoggerFactory.shared init(scene: NovelScene) { self.scene = scene + self.choiceManager = ChoiceManager(scene: scene) } func loadScript(named scriptName: String) { @@ -54,6 +62,11 @@ class ScriptExecutor: ObservableObject { } func next() { + if isWaitingForChoice { + // Do nothing, wait for choice selection + return + } + if isWaitingForInput { isWaitingForInput = false currentLine += 1 @@ -80,6 +93,10 @@ class ScriptExecutor: ObservableObject { case "text": executeText(components) return + case "cleartext": + // Simulate `text ~` + executeText(["text", "~"]) + return case "choice": executeChoice(components) currentLine += 1 @@ -152,9 +169,9 @@ class ScriptExecutor: ObservableObject { if components[1] == "~" { scene.audioManager.clearMusic() } else { - self.isLoadingMusic = true + //self.isLoadingMusic = true scene.audioManager.playMusic(songPath: components[1]) { - self.isLoadingMusic = false + //self.isLoadingMusic = false } } } @@ -214,10 +231,28 @@ class ScriptExecutor: ObservableObject { } private func executeChoice(_ components: [String]) { - // Get components separated by "|" - // TODO: do let choices = components.dropFirst().joined(separator: " ").components(separatedBy: "|") - variables["selected"] = 1 // Will be updated when user makes a choice + + choiceManager.presentChoices(choices) { [weak self] selectedIndex in + guard let self = self else { return } + self.globalVariables["selected"] = selectedIndex + self.isWaitingForChoice = false + self.currentLine += 1 + self.executeUntilStopped() + } + + isWaitingForChoice = true + } + + func handleTap(at position: CGPoint) { + if isWaitingForChoice && choiceManager.hasActiveChoices { + let choiceHandled = choiceManager.handleTap(at: position) + if choiceHandled { + isWaitingForChoice = false + return + } + } + next() } private func executeSetVar(_ components: [String], isGlobal: Bool) { @@ -252,29 +287,27 @@ class ScriptExecutor: ObservableObject { private func executeIf(_ components: [String]) { guard components.count >= 4 else { return } + let varName = components[1] let operation = components[2] - let value = components[3] + let comparisonValue = components[3] - let variableValue = (variables[varName] ?? globalVariables[varName]) as? String ?? "" + guard let variableValue = variables[varName] ?? globalVariables[varName] else { + logger.error("Variable \(varName) not found") + fatalError() + } let condition: Bool - switch operation { - case "==": - condition = variableValue == value - case "!=": - condition = variableValue != value - case ">": - condition = (Int(variableValue) ?? 0) > (Int(value) ?? 0) - case "<": - condition = (Int(variableValue) ?? 0) < (Int(value) ?? 0) - case ">=": - condition = (Int(variableValue) ?? 0) >= (Int(value) ?? 0) - case "<=": - condition = (Int(variableValue) ?? 0) <= (Int(value) ?? 0) - default: - print("Unknown operation: \(operation)") - condition = false + if let intVariableValue = variableValue as? Int, let intComparisonValue = Int(comparisonValue) { + // Compare as integers if both are Ints + condition = evaluateCondition(intVariableValue, operation: operation, value: intComparisonValue) + } else if let strVariableValue = variableValue as? String { + // Compare as strings if the variable is a String + condition = evaluateCondition(strVariableValue, operation: operation, value: comparisonValue) + } else { + // Handle unsupported types + logger.error("Unsupported variable type for \(varName)") + fatalError() } if !condition { @@ -295,9 +328,29 @@ class ScriptExecutor: ObservableObject { } } + private func evaluateCondition(_ variableValue: T, operation: String, value: T) -> Bool { + switch operation { + case "==": + return variableValue == value + case "!=": + return variableValue != value + case ">": + return variableValue > value + case "<": + return variableValue < value + case ">=": + return variableValue >= value + case "<=": + return variableValue <= value + default: + logger.error("Unknown operation: \(operation)") + return false + } + } + private func executeJump(_ components: [String]) { guard components.count >= 2 else { return } - let scriptName = components[1] + let scriptName = interpolateText(components[1]) loadScript(named: scriptName) currentLine = 0 diff --git a/SwiftVN/VNDS/TextNode.swift b/SwiftVN/VNDS/TextNode.swift index ae1e5bc..0db268b 100644 --- a/SwiftVN/VNDS/TextNode.swift +++ b/SwiftVN/VNDS/TextNode.swift @@ -21,14 +21,20 @@ class TextNode: SKNode { var isAnimating: Bool = false var isAnimationComplete: Bool = true + private let logger = LoggerFactory.shared + init(fontSize: CGFloat = 16, maxLines: Int = 3, padding: CGFloat = 20) { self.fontSize = fontSize self.maxLines = maxLines self.padding = padding - // let fontURL = SwiftVN.baseDirectory.appendingPathComponent("default.ttf") - let fontURL = Bundle.main.bundleURL.appendingPathComponent("sazanami-gothic.ttf") - + logger.info("Loading font...") + var fontURL = SwiftVN.baseDirectory.appendingPathComponent("default.ttf") + if !FileManager.default.fileExists(atPath: fontURL.path) { + logger.info("Falling back to sazanami-gothic.ttf") + fontURL = Bundle.main.bundleURL.appendingPathComponent("sazanami-gothic.ttf") + } + // Load novel custom font if let fontDataProvider = CGDataProvider(url: fontURL as CFURL), let font = CGFont(fontDataProvider) {