Skip to content

Commit cb8d6d6

Browse files
authored
Merge pull request #5 from Cosmo/development
Version 2.0
2 parents 4808159 + ebaca59 commit cb8d6d6

File tree

74 files changed

+1548
-1825
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1548
-1825
lines changed

Clippy Shared/Agent/Agent.swift

-21
This file was deleted.

Clippy Shared/Agent/AgentError.swift

-12
This file was deleted.

Clippy Shared/Agent/AgentExtensions.swift

-40
This file was deleted.

Clippy Shared/Agent/AgentFrame.swift

-14
This file was deleted.

Clippy Shared/AgentParser/Agent.swift

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//
2+
// AgentCharacterDescription.swift
3+
// Clippy
4+
//
5+
// Created by Devran on 06.09.19.
6+
// Copyright © 2019 Devran. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import SpriteKit
11+
12+
enum AgentError: Error {
13+
case frameOutOfBounds
14+
}
15+
16+
struct Agent {
17+
var character: AgentCharacter
18+
var balloon: AgentBalloon
19+
var animations: [AgentAnimation]
20+
var states: [AgentState]
21+
22+
var agentURL: URL
23+
var resourceName: String
24+
var resourceNameWithSuffix: String
25+
var spriteMap: CGImage
26+
let soundsURL: URL
27+
28+
init?(agentURL: URL) {
29+
self.agentURL = agentURL
30+
self.soundsURL = agentURL.appendingPathComponent("sounds")
31+
self.resourceNameWithSuffix = agentURL.lastPathComponent
32+
self.resourceName = resourceNameWithSuffix.replacingOccurrences(of: ".agent", with: "")
33+
34+
let fileURL = agentURL.appendingPathComponent("\(resourceName).acd")
35+
let imageURL = agentURL.appendingPathComponent("\(resourceName)_sprite_map.png")
36+
37+
guard let fileContent = try? String(contentsOf: fileURL, encoding: String.Encoding.utf8) else { return nil }
38+
39+
// Character
40+
guard let characterText = fileContent.fetchInclusive("DefineCharacter", until: "EndCharacter").first else { return nil }
41+
let character = AgentCharacter.parse(content: characterText)
42+
43+
// Balloon
44+
guard let balloonText = fileContent.fetchInclusive("DefineBalloon", until: "EndBalloon").first else { return nil }
45+
let balloon = AgentBalloon.parse(content: balloonText)
46+
47+
// Animations
48+
let animationTexts = fileContent.fetchInclusive("DefineAnimation", until: "EndAnimation")
49+
let animations = animationTexts.compactMap { AgentAnimation.parse(content: $0) }
50+
51+
// States
52+
let stateTexts = fileContent.fetchInclusive("DefineState", until: "EndState")
53+
let states = stateTexts.compactMap { AgentState.parse(content: $0) }
54+
55+
// Sprite Map
56+
guard let image = NSImage(contentsOf: imageURL)?.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
57+
spriteMap = image
58+
59+
if let character = character, let balloon = balloon {
60+
self.character = character
61+
self.balloon = balloon
62+
self.animations = animations
63+
self.states = states
64+
} else {
65+
return nil
66+
}
67+
}
68+
69+
init?(resourceName: String) {
70+
let directoryName = "\(resourceName).agent"
71+
self.init(agentURL: Agent.agentsURL().appendingPathComponent(directoryName))
72+
}
73+
74+
func soundURL(forIndex index: Int) -> URL {
75+
let fileName = "\(resourceName)_\(index).mp3"
76+
return soundsURL.appendingPathComponent(fileName)
77+
}
78+
79+
func findAnimation(_ name: String) -> AgentAnimation? {
80+
return animations.first(where: { $0.name == name })
81+
}
82+
}
83+
84+
extension Agent {
85+
var columns: Int {
86+
let columns = Int(spriteMap.width) / character.width
87+
return columns
88+
}
89+
var rows: Int {
90+
let rows = Int(spriteMap.height) / character.height
91+
return rows
92+
}
93+
94+
func textureAtPosition(x: Int, y: Int) throws -> CGImage {
95+
guard (0...rows ~= y && 0...columns ~= x) else { throw AgentError.frameOutOfBounds }
96+
let textureWidth = character.width
97+
let textureHeight = character.height
98+
let rect = CGRect(x: x * textureWidth, y: y * textureHeight, width: textureWidth, height: textureHeight)
99+
return spriteMap.cropping(to: rect)!
100+
}
101+
102+
func textureAtIndex(index: Int) throws -> CGImage {
103+
let x = index % columns
104+
let y = index / columns
105+
return try! textureAtPosition(x: x, y: y)
106+
}
107+
108+
func imageForFrame(_ frame: AgentFrame) -> CGImage {
109+
let cgImages = frame.images.reversed().map{ try! textureAtIndex(index: $0.imageNumber) }
110+
if let mergedImage = CGImage.mergeImages(cgImages) {
111+
return mergedImage
112+
} else {
113+
return try! textureAtIndex(index: 0)
114+
}
115+
}
116+
}
117+
118+
extension Agent {
119+
static func agentsURL() -> URL {
120+
let fileManager = FileManager.default
121+
122+
guard let applicationSupportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
123+
fatalError("Cant create Agents directory")
124+
}
125+
126+
let agentsURL = applicationSupportURL.appendingPathComponent("Clippy/Agents", isDirectory: true)
127+
createAgentsDirectoriesIfNeeded(url: agentsURL)
128+
129+
return agentsURL
130+
}
131+
132+
static func createAgentsDirectoriesIfNeeded(url: URL) {
133+
let fileManager = FileManager.default
134+
if !fileManager.fileExists(atPath: url.path) {
135+
try? fileManager.createDirectory(at: url,
136+
withIntermediateDirectories: true,
137+
attributes: nil)
138+
["clippit", "links", "merlin"].forEach {
139+
guard let agentsArchiveURL = Bundle.main.url(forResource: "\($0).agent", withExtension: "zip") else {
140+
return
141+
}
142+
try? fileManager.copyItem(at: agentsArchiveURL, to: url.appendingPathComponent("\($0).agent.zip"))
143+
}
144+
}
145+
}
146+
147+
static func agentNames() -> [String] {
148+
var agentNames: [String] = []
149+
let fileManager = FileManager.default
150+
guard let items = try? fileManager.contentsOfDirectory(at: agentsURL(),
151+
includingPropertiesForKeys: nil,
152+
options: []) else {
153+
return []
154+
}
155+
156+
for item in items {
157+
if item.hasDirectoryPath && item.lastPathComponent.hasSuffix(".agent") {
158+
agentNames.append(item.lastPathComponent.replacingOccurrences(of: ".agent", with: ""))
159+
}
160+
}
161+
return agentNames.sorted()
162+
}
163+
164+
static func randomAgentName() -> String? {
165+
agentNames().randomElement()
166+
}
167+
}
168+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// AgentAnimation.swift
3+
// Clippy
4+
//
5+
// Created by Devran on 07.09.19.
6+
// Copyright © 2019 Devran. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
struct AgentAnimation {
12+
let name: String
13+
let transitionType: Int
14+
let frames: [AgentFrame]
15+
}
16+
17+
extension AgentAnimation {
18+
static func parse(content: String) -> AgentAnimation? {
19+
var animationName: String?
20+
var transitionType: Int?
21+
let frameTexts = content.fetchInclusive("DefineFrame", until: "EndFrame")
22+
let frames = frameTexts.compactMap { AgentFrame.parse(content: $0) }
23+
24+
content.enumerateLines(invoking: { (line: String, stop: inout Bool) in
25+
let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
26+
if trimmedLine.starts(with: "DefineAnimation ") {
27+
animationName = trimmedLine.stringValueOfDefinition(onKey: "DefineAnimation")?.removedQuotes()
28+
}
29+
if trimmedLine.starts(with: "TransitionType ") {
30+
transitionType = trimmedLine.intValueOfKeyValue(onKey: "TransitionType")
31+
}
32+
})
33+
34+
if let animationName = animationName, let transitionType = transitionType {
35+
return AgentAnimation(name: animationName, transitionType: transitionType, frames: frames)
36+
}
37+
return nil
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// AgentBalloon.swift
3+
// Clippy
4+
//
5+
// Created by Devran on 07.09.19.
6+
// Copyright © 2019 Devran. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
struct AgentBalloon {
12+
let numberOfLines: Int
13+
let charactersPerLine: Int
14+
let fontName: String
15+
let fontHeight: Int
16+
let foregroundColor: String
17+
let backgroundColor: String
18+
let borderColor: String
19+
}
20+
21+
extension AgentBalloon {
22+
static func parse(content: String) -> AgentBalloon? {
23+
var numberOfLines: Int?
24+
var charactersPerLine: Int?
25+
var fontName: String?
26+
var fontHeight: Int?
27+
var foregroundColor: String?
28+
var backgroundColor: String?
29+
var borderColor: String?
30+
31+
content.enumerateLines(invoking: { (line: String, stop: inout Bool) in
32+
let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
33+
if trimmedLine.starts(with: "NumLines ") {
34+
numberOfLines = trimmedLine.intValueOfKeyValue(onKey: "NumLines")
35+
}
36+
if trimmedLine.starts(with: "CharsPerLine ") {
37+
charactersPerLine = trimmedLine.intValueOfKeyValue(onKey: "CharsPerLine")
38+
}
39+
if trimmedLine.starts(with: "FontName ") {
40+
fontName = trimmedLine.stringValueOfKeyValue(onKey: "FontName")?.removedQuotes()
41+
}
42+
if trimmedLine.starts(with: "FontHeight ") {
43+
fontHeight = trimmedLine.intValueOfKeyValue(onKey: "FontHeight")
44+
}
45+
if trimmedLine.starts(with: "ForeColor ") {
46+
foregroundColor = trimmedLine.stringValueOfKeyValue(onKey: "ForeColor")
47+
}
48+
if trimmedLine.starts(with: "BackColor ") {
49+
backgroundColor = trimmedLine.stringValueOfKeyValue(onKey: "BackColor")
50+
}
51+
if trimmedLine.starts(with: "BorderColor ") {
52+
borderColor = trimmedLine.stringValueOfKeyValue(onKey: "BorderColor")
53+
}
54+
})
55+
56+
if let numberOfLines = numberOfLines, let charactersPerLine = charactersPerLine, let fontName = fontName, let fontHeight = fontHeight, let foregroundColor = foregroundColor, let backgroundColor = backgroundColor, let borderColor = borderColor {
57+
return AgentBalloon(numberOfLines: numberOfLines, charactersPerLine: charactersPerLine, fontName: fontName, fontHeight: fontHeight, foregroundColor: foregroundColor, backgroundColor: backgroundColor, borderColor: borderColor)
58+
}
59+
return nil
60+
}
61+
}

0 commit comments

Comments
 (0)