Skip to content

Commit

Permalink
Merge pull request #5 from Cosmo/development
Browse files Browse the repository at this point in the history
Version 2.0
  • Loading branch information
Cosmo authored Sep 9, 2019
2 parents 4808159 + ebaca59 commit cb8d6d6
Show file tree
Hide file tree
Showing 74 changed files with 1,548 additions and 1,825 deletions.
21 changes: 0 additions & 21 deletions Clippy Shared/Agent/Agent.swift

This file was deleted.

12 changes: 0 additions & 12 deletions Clippy Shared/Agent/AgentError.swift

This file was deleted.

40 changes: 0 additions & 40 deletions Clippy Shared/Agent/AgentExtensions.swift

This file was deleted.

14 changes: 0 additions & 14 deletions Clippy Shared/Agent/AgentFrame.swift

This file was deleted.

168 changes: 168 additions & 0 deletions Clippy Shared/AgentParser/Agent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// AgentCharacterDescription.swift
// Clippy
//
// Created by Devran on 06.09.19.
// Copyright © 2019 Devran. All rights reserved.
//

import Foundation
import SpriteKit

enum AgentError: Error {
case frameOutOfBounds
}

struct Agent {
var character: AgentCharacter
var balloon: AgentBalloon
var animations: [AgentAnimation]
var states: [AgentState]

var agentURL: URL
var resourceName: String
var resourceNameWithSuffix: String
var spriteMap: CGImage
let soundsURL: URL

init?(agentURL: URL) {
self.agentURL = agentURL
self.soundsURL = agentURL.appendingPathComponent("sounds")
self.resourceNameWithSuffix = agentURL.lastPathComponent
self.resourceName = resourceNameWithSuffix.replacingOccurrences(of: ".agent", with: "")

let fileURL = agentURL.appendingPathComponent("\(resourceName).acd")
let imageURL = agentURL.appendingPathComponent("\(resourceName)_sprite_map.png")

guard let fileContent = try? String(contentsOf: fileURL, encoding: String.Encoding.utf8) else { return nil }

// Character
guard let characterText = fileContent.fetchInclusive("DefineCharacter", until: "EndCharacter").first else { return nil }
let character = AgentCharacter.parse(content: characterText)

// Balloon
guard let balloonText = fileContent.fetchInclusive("DefineBalloon", until: "EndBalloon").first else { return nil }
let balloon = AgentBalloon.parse(content: balloonText)

// Animations
let animationTexts = fileContent.fetchInclusive("DefineAnimation", until: "EndAnimation")
let animations = animationTexts.compactMap { AgentAnimation.parse(content: $0) }

// States
let stateTexts = fileContent.fetchInclusive("DefineState", until: "EndState")
let states = stateTexts.compactMap { AgentState.parse(content: $0) }

// Sprite Map
guard let image = NSImage(contentsOf: imageURL)?.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
spriteMap = image

if let character = character, let balloon = balloon {
self.character = character
self.balloon = balloon
self.animations = animations
self.states = states
} else {
return nil
}
}

init?(resourceName: String) {
let directoryName = "\(resourceName).agent"
self.init(agentURL: Agent.agentsURL().appendingPathComponent(directoryName))
}

func soundURL(forIndex index: Int) -> URL {
let fileName = "\(resourceName)_\(index).mp3"
return soundsURL.appendingPathComponent(fileName)
}

func findAnimation(_ name: String) -> AgentAnimation? {
return animations.first(where: { $0.name == name })
}
}

extension Agent {
var columns: Int {
let columns = Int(spriteMap.width) / character.width
return columns
}
var rows: Int {
let rows = Int(spriteMap.height) / character.height
return rows
}

func textureAtPosition(x: Int, y: Int) throws -> CGImage {
guard (0...rows ~= y && 0...columns ~= x) else { throw AgentError.frameOutOfBounds }
let textureWidth = character.width
let textureHeight = character.height
let rect = CGRect(x: x * textureWidth, y: y * textureHeight, width: textureWidth, height: textureHeight)
return spriteMap.cropping(to: rect)!
}

func textureAtIndex(index: Int) throws -> CGImage {
let x = index % columns
let y = index / columns
return try! textureAtPosition(x: x, y: y)
}

func imageForFrame(_ frame: AgentFrame) -> CGImage {
let cgImages = frame.images.reversed().map{ try! textureAtIndex(index: $0.imageNumber) }
if let mergedImage = CGImage.mergeImages(cgImages) {
return mergedImage
} else {
return try! textureAtIndex(index: 0)
}
}
}

extension Agent {
static func agentsURL() -> URL {
let fileManager = FileManager.default

guard let applicationSupportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
fatalError("Cant create Agents directory")
}

let agentsURL = applicationSupportURL.appendingPathComponent("Clippy/Agents", isDirectory: true)
createAgentsDirectoriesIfNeeded(url: agentsURL)

return agentsURL
}

static func createAgentsDirectoriesIfNeeded(url: URL) {
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: url.path) {
try? fileManager.createDirectory(at: url,
withIntermediateDirectories: true,
attributes: nil)
["clippit", "links", "merlin"].forEach {
guard let agentsArchiveURL = Bundle.main.url(forResource: "\($0).agent", withExtension: "zip") else {
return
}
try? fileManager.copyItem(at: agentsArchiveURL, to: url.appendingPathComponent("\($0).agent.zip"))
}
}
}

static func agentNames() -> [String] {
var agentNames: [String] = []
let fileManager = FileManager.default
guard let items = try? fileManager.contentsOfDirectory(at: agentsURL(),
includingPropertiesForKeys: nil,
options: []) else {
return []
}

for item in items {
if item.hasDirectoryPath && item.lastPathComponent.hasSuffix(".agent") {
agentNames.append(item.lastPathComponent.replacingOccurrences(of: ".agent", with: ""))
}
}
return agentNames.sorted()
}

static func randomAgentName() -> String? {
agentNames().randomElement()
}
}

39 changes: 39 additions & 0 deletions Clippy Shared/AgentParser/AgentAnimation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// AgentAnimation.swift
// Clippy
//
// Created by Devran on 07.09.19.
// Copyright © 2019 Devran. All rights reserved.
//

import Foundation

struct AgentAnimation {
let name: String
let transitionType: Int
let frames: [AgentFrame]
}

extension AgentAnimation {
static func parse(content: String) -> AgentAnimation? {
var animationName: String?
var transitionType: Int?
let frameTexts = content.fetchInclusive("DefineFrame", until: "EndFrame")
let frames = frameTexts.compactMap { AgentFrame.parse(content: $0) }

content.enumerateLines(invoking: { (line: String, stop: inout Bool) in
let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if trimmedLine.starts(with: "DefineAnimation ") {
animationName = trimmedLine.stringValueOfDefinition(onKey: "DefineAnimation")?.removedQuotes()
}
if trimmedLine.starts(with: "TransitionType ") {
transitionType = trimmedLine.intValueOfKeyValue(onKey: "TransitionType")
}
})

if let animationName = animationName, let transitionType = transitionType {
return AgentAnimation(name: animationName, transitionType: transitionType, frames: frames)
}
return nil
}
}
61 changes: 61 additions & 0 deletions Clippy Shared/AgentParser/AgentBalloon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// AgentBalloon.swift
// Clippy
//
// Created by Devran on 07.09.19.
// Copyright © 2019 Devran. All rights reserved.
//

import Foundation

struct AgentBalloon {
let numberOfLines: Int
let charactersPerLine: Int
let fontName: String
let fontHeight: Int
let foregroundColor: String
let backgroundColor: String
let borderColor: String
}

extension AgentBalloon {
static func parse(content: String) -> AgentBalloon? {
var numberOfLines: Int?
var charactersPerLine: Int?
var fontName: String?
var fontHeight: Int?
var foregroundColor: String?
var backgroundColor: String?
var borderColor: String?

content.enumerateLines(invoking: { (line: String, stop: inout Bool) in
let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if trimmedLine.starts(with: "NumLines ") {
numberOfLines = trimmedLine.intValueOfKeyValue(onKey: "NumLines")
}
if trimmedLine.starts(with: "CharsPerLine ") {
charactersPerLine = trimmedLine.intValueOfKeyValue(onKey: "CharsPerLine")
}
if trimmedLine.starts(with: "FontName ") {
fontName = trimmedLine.stringValueOfKeyValue(onKey: "FontName")?.removedQuotes()
}
if trimmedLine.starts(with: "FontHeight ") {
fontHeight = trimmedLine.intValueOfKeyValue(onKey: "FontHeight")
}
if trimmedLine.starts(with: "ForeColor ") {
foregroundColor = trimmedLine.stringValueOfKeyValue(onKey: "ForeColor")
}
if trimmedLine.starts(with: "BackColor ") {
backgroundColor = trimmedLine.stringValueOfKeyValue(onKey: "BackColor")
}
if trimmedLine.starts(with: "BorderColor ") {
borderColor = trimmedLine.stringValueOfKeyValue(onKey: "BorderColor")
}
})

if let numberOfLines = numberOfLines, let charactersPerLine = charactersPerLine, let fontName = fontName, let fontHeight = fontHeight, let foregroundColor = foregroundColor, let backgroundColor = backgroundColor, let borderColor = borderColor {
return AgentBalloon(numberOfLines: numberOfLines, charactersPerLine: charactersPerLine, fontName: fontName, fontHeight: fontHeight, foregroundColor: foregroundColor, backgroundColor: backgroundColor, borderColor: borderColor)
}
return nil
}
}
Loading

0 comments on commit cb8d6d6

Please sign in to comment.