Adds support for adding, removing, activating, and deactivating (hard-coded) cheats
This commit is contained in:
parent
c08e2a2de7
commit
509cb4b136
@ -19,6 +19,7 @@ extension Cheat
|
||||
case name
|
||||
case code
|
||||
case type
|
||||
case enabled
|
||||
case creationDate
|
||||
case modifiedDate
|
||||
|
||||
@ -55,6 +56,7 @@ class Cheat: NSManagedObject, CheatProtocol
|
||||
@NSManaged var name: String?
|
||||
@NSManaged var code: String
|
||||
@NSManaged var modifiedDate: NSDate
|
||||
@NSManaged var enabled: Bool
|
||||
|
||||
@NSManaged private(set) var identifier: String
|
||||
@NSManaged private(set) var creationDate: NSDate
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
|
||||
<attribute name="code" attributeType="String" syncable="YES"/>
|
||||
<attribute name="creationDate" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="YES" syncable="YES"/>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modifiedDate" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
@ -68,9 +69,9 @@
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="165"/>
|
||||
<element name="Game" positionX="-378" positionY="-54" width="128" height="178"/>
|
||||
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
|
||||
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
|
||||
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="148"/>
|
||||
</elements>
|
||||
</model>
|
||||
@ -203,7 +203,9 @@
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
@ -214,7 +216,7 @@
|
||||
<navigationItem key="navigationItem" title="Cheats" id="Gsv-JZ-Yc0">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="add" id="xr9-Op-Op7">
|
||||
<connections>
|
||||
<action selector="addSaveState" destination="OOk-k7-INg" id="xjM-nF-CYA"/>
|
||||
<action selector="addCheat" destination="wb8-5o-1jE" id="FEG-zY-Sde"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
|
||||
@ -106,17 +106,23 @@ class EmulationViewController: UIViewController
|
||||
self.deferredPreparationHandler?()
|
||||
self.deferredPreparationHandler = nil
|
||||
|
||||
// Yes, order DOES matter here, in order to prevent audio from being slightly delayed after peeking with 3D Touch (ugh so tired of that issue)
|
||||
switch self.emulatorCore.state
|
||||
{
|
||||
case .Stopped:
|
||||
self.emulatorCore.startEmulation()
|
||||
self.updateCheats()
|
||||
|
||||
case .Running: break
|
||||
case .Paused:
|
||||
self.updateCheats()
|
||||
self.emulatorCore.resumeEmulation()
|
||||
}
|
||||
|
||||
// Toggle audioManager.enabled to reset the audio buffer and ensure the audio isn't delayed from the beginning
|
||||
// This is especially noticeable when peeking a game
|
||||
self.emulatorCore.audioManager.enabled = false
|
||||
self.emulatorCore.audioManager.enabled = true
|
||||
|
||||
switch self.emulatorCore.state
|
||||
{
|
||||
case .Stopped: self.emulatorCore.startEmulation()
|
||||
case .Running: break
|
||||
case .Paused: self.emulatorCore.resumeEmulation()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
@ -181,22 +187,22 @@ class EmulationViewController: UIViewController
|
||||
// Specifically, if you pause a game, open the save states menu, go back, return to menu, select a new game, then try to pause it, it will crash
|
||||
// As a dirty workaround, we just use a weak reference, and force unwrap it if needed
|
||||
|
||||
let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { [weak self] _ in
|
||||
pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self!)
|
||||
let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in
|
||||
pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self)
|
||||
})
|
||||
|
||||
let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [weak self] _ in
|
||||
pauseViewController.presentSaveStateViewControllerWithMode(.Loading, delegate: self!)
|
||||
let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in
|
||||
pauseViewController.presentSaveStateViewControllerWithMode(.Loading, delegate: self)
|
||||
})
|
||||
|
||||
let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { _ in
|
||||
let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: { [unowned self] _ in
|
||||
pauseViewController.presentCheatsViewController(delegate: self)
|
||||
})
|
||||
|
||||
let sustainButtonItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Button", comment: ""), action: dismissAction)
|
||||
|
||||
var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [weak self] item in
|
||||
self?.emulatorCore.fastForwarding = item.selected
|
||||
var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [unowned self] item in
|
||||
self.emulatorCore.fastForwarding = item.selected
|
||||
})
|
||||
fastForwardItem.selected = self.emulatorCore.fastForwarding
|
||||
|
||||
@ -324,6 +330,8 @@ extension EmulationViewController: SaveStatesViewControllerDelegate
|
||||
{
|
||||
self.emulatorCore.loadSaveState(saveState)
|
||||
|
||||
self.updateCheats()
|
||||
|
||||
self.pauseViewController?.dismiss()
|
||||
}
|
||||
}
|
||||
@ -336,6 +344,70 @@ extension EmulationViewController: CheatsViewControllerDelegate
|
||||
{
|
||||
return self.emulatorCore.game as! Game
|
||||
}
|
||||
|
||||
func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
|
||||
{
|
||||
try self.emulatorCore.activateCheat(cheat)
|
||||
}
|
||||
|
||||
func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws
|
||||
{
|
||||
try self.emulatorCore.deactivateCheat(cheat)
|
||||
}
|
||||
|
||||
private func updateCheats()
|
||||
{
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlockAndWait {
|
||||
|
||||
let running = (self.emulatorCore.state == .Running)
|
||||
|
||||
if running
|
||||
{
|
||||
// Core MUST be paused when activating cheats, or else race conditions could crash the core
|
||||
self.emulatorCore.pauseEmulation()
|
||||
}
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", Cheat.Attributes.game.rawValue, self.emulatorCore.game as! Game)
|
||||
|
||||
let cheats = Cheat.instancesWithPredicate(predicate, inManagedObjectContext: backgroundContext, type: Cheat.self)
|
||||
for cheat in cheats
|
||||
{
|
||||
do
|
||||
{
|
||||
if cheat.enabled
|
||||
{
|
||||
try self.emulatorCore.activateCheat(cheat)
|
||||
}
|
||||
else
|
||||
{
|
||||
try self.emulatorCore.deactivateCheat(cheat)
|
||||
}
|
||||
}
|
||||
catch EmulatorCore.CheatError.invalid
|
||||
{
|
||||
print("Invalid cheat:", cheat.name, cheat.code)
|
||||
}
|
||||
catch EmulatorCore.CheatError.doesNotExist
|
||||
{
|
||||
// Ignore this error, because we could be deactivating a cheat that hasn't yet been activated
|
||||
print("Cheat does not exist:", cheat.name, cheat.code)
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if running
|
||||
{
|
||||
self.emulatorCore.resumeEmulation()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <GameControllerReceiver> -
|
||||
|
||||
@ -9,11 +9,15 @@
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
protocol CheatsViewControllerDelegate: class
|
||||
{
|
||||
func cheatsViewControllerActiveGame(saveStatesViewController: CheatsViewController) -> Game
|
||||
func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
|
||||
func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws
|
||||
}
|
||||
|
||||
class CheatsViewController: UITableViewController
|
||||
@ -95,19 +99,158 @@ private extension CheatsViewController
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Managing Cheats -
|
||||
/// Managing Cheats
|
||||
private extension CheatsViewController
|
||||
{
|
||||
@IBAction func addCheat()
|
||||
{
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlock {
|
||||
|
||||
var game = self.delegate.cheatsViewControllerActiveGame(self)
|
||||
game = backgroundContext.objectWithID(game.objectID) as! Game
|
||||
|
||||
let cheat = Cheat.insertIntoManagedObjectContext(backgroundContext)
|
||||
cheat.game = game
|
||||
cheat.name = "Unlimited Jumps"
|
||||
cheat.code = "3E2C-AF6F"
|
||||
cheat.type = .gameGenie
|
||||
|
||||
do
|
||||
{
|
||||
try self.delegate.cheatsViewController(self, didActivateCheat: cheat)
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
catch EmulatorCore.CheatError.invalid
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Invalid Cheat", comment: ""), message: NSLocalizedString("Please make sure you typed the cheat code in correctly and try again.", comment: ""), preferredStyle: .Alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Default, handler: nil))
|
||||
self.presentViewController(alertController, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
|
||||
print("Invalid cheat:", cheat.name, cheat.code)
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func deleteCheat(cheat: Cheat)
|
||||
{
|
||||
let _ = try? self.delegate.cheatsViewController(self, didDeactivateCheat: cheat)
|
||||
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlock {
|
||||
let temporaryCheat = backgroundContext.objectWithID(cheat.objectID)
|
||||
backgroundContext.deleteObject(temporaryCheat)
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Content -
|
||||
/// Content
|
||||
private extension CheatsViewController
|
||||
{
|
||||
func configure(cell cell: UITableViewCell, forIndexPath indexPath: NSIndexPath)
|
||||
{
|
||||
let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat
|
||||
cell.textLabel?.text = cheat.name
|
||||
cell.textLabel?.font = UIFont.boldSystemFontOfSize(cell.textLabel!.font.pointSize)
|
||||
cell.accessoryType = cheat.enabled ? .Checkmark : .None
|
||||
}
|
||||
}
|
||||
|
||||
extension CheatsViewController
|
||||
{
|
||||
// MARK: - Table view data source
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
|
||||
{
|
||||
// #warning Incomplete implementation, return the number of sections
|
||||
return 0
|
||||
let numberOfSections = self.fetchedResultsController.sections!.count
|
||||
return numberOfSections
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
// #warning Incomplete implementation, return the number of rows
|
||||
return 0
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||
{
|
||||
let section = self.fetchedResultsController.sections![section]
|
||||
return section.numberOfObjects
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
|
||||
{
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier(RSTGenericCellIdentifier, forIndexPath: indexPath)
|
||||
self.configure(cell: cell, forIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
extension CheatsViewController
|
||||
{
|
||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
|
||||
{
|
||||
let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat
|
||||
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlockAndWait {
|
||||
let temporaryCheat = backgroundContext.objectWithID(cheat.objectID) as! Cheat
|
||||
temporaryCheat.enabled = !temporaryCheat.enabled
|
||||
|
||||
do
|
||||
{
|
||||
if temporaryCheat.enabled
|
||||
{
|
||||
try self.delegate.cheatsViewController(self, didActivateCheat: temporaryCheat)
|
||||
}
|
||||
else
|
||||
{
|
||||
try self.delegate.cheatsViewController(self, didDeactivateCheat: temporaryCheat)
|
||||
}
|
||||
}
|
||||
catch EmulatorCore.CheatError.invalid
|
||||
{
|
||||
print("Invalid cheat:", cheat.name, cheat.code)
|
||||
}
|
||||
catch EmulatorCore.CheatError.doesNotExist
|
||||
{
|
||||
print("Cheat does not exist:", cheat.name, cheat.code)
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
|
||||
}
|
||||
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
|
||||
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]?
|
||||
{
|
||||
let deleteAction = UITableViewRowAction(style: .Destructive, title: NSLocalizedString("Delete", comment: "")) { (action, indexPath) in
|
||||
let cheat = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Cheat
|
||||
self.deleteCheat(cheat)
|
||||
}
|
||||
|
||||
let editAction = UITableViewRowAction(style: .Normal, title: NSLocalizedString("Edit", comment: "")) { (action, indexPath) in
|
||||
|
||||
}
|
||||
|
||||
return [deleteAction, editAction]
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
|
||||
{
|
||||
// This method intentionally left blank because someone decided it was a Good Idea™ to require this method be implemented to use UITableViewRowActions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user