Adds support for adding, removing, activating, and deactivating (hard-coded) cheats

This commit is contained in:
Riley Testut 2016-05-21 01:21:40 -05:00
parent c08e2a2de7
commit 509cb4b136
5 changed files with 241 additions and 21 deletions

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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> -

View File

@ -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
}
}