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 name
|
||||||
case code
|
case code
|
||||||
case type
|
case type
|
||||||
|
case enabled
|
||||||
case creationDate
|
case creationDate
|
||||||
case modifiedDate
|
case modifiedDate
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ class Cheat: NSManagedObject, CheatProtocol
|
|||||||
@NSManaged var name: String?
|
@NSManaged var name: String?
|
||||||
@NSManaged var code: String
|
@NSManaged var code: String
|
||||||
@NSManaged var modifiedDate: NSDate
|
@NSManaged var modifiedDate: NSDate
|
||||||
|
@NSManaged var enabled: Bool
|
||||||
|
|
||||||
@NSManaged private(set) var identifier: String
|
@NSManaged private(set) var identifier: String
|
||||||
@NSManaged private(set) var creationDate: NSDate
|
@NSManaged private(set) var creationDate: NSDate
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
|
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
|
||||||
<attribute name="code" attributeType="String" syncable="YES"/>
|
<attribute name="code" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="creationDate" attributeType="Date" 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="identifier" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="modifiedDate" attributeType="Date" syncable="YES"/>
|
<attribute name="modifiedDate" attributeType="Date" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
@ -68,9 +69,9 @@
|
|||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<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="Game" positionX="-378" positionY="-54" width="128" height="178"/>
|
||||||
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
|
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
|
||||||
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
|
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
|
||||||
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="148"/>
|
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@ -203,7 +203,9 @@
|
|||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="e8g-ZW-5lQ" id="AHE-Jk-ULE">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</prototypes>
|
</prototypes>
|
||||||
<connections>
|
<connections>
|
||||||
@ -214,7 +216,7 @@
|
|||||||
<navigationItem key="navigationItem" title="Cheats" id="Gsv-JZ-Yc0">
|
<navigationItem key="navigationItem" title="Cheats" id="Gsv-JZ-Yc0">
|
||||||
<barButtonItem key="rightBarButtonItem" systemItem="add" id="xr9-Op-Op7">
|
<barButtonItem key="rightBarButtonItem" systemItem="add" id="xr9-Op-Op7">
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="addSaveState" destination="OOk-k7-INg" id="xjM-nF-CYA"/>
|
<action selector="addCheat" destination="wb8-5o-1jE" id="FEG-zY-Sde"/>
|
||||||
</connections>
|
</connections>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
|
|||||||
@ -106,17 +106,23 @@ class EmulationViewController: UIViewController
|
|||||||
self.deferredPreparationHandler?()
|
self.deferredPreparationHandler?()
|
||||||
self.deferredPreparationHandler = nil
|
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
|
// 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
|
// This is especially noticeable when peeking a game
|
||||||
self.emulatorCore.audioManager.enabled = false
|
self.emulatorCore.audioManager.enabled = false
|
||||||
self.emulatorCore.audioManager.enabled = true
|
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()
|
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
|
// 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
|
// 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
|
let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { [unowned self] _ in
|
||||||
pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self!)
|
pauseViewController.presentSaveStateViewControllerWithMode(.Saving, delegate: self)
|
||||||
})
|
})
|
||||||
|
|
||||||
let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [weak self] _ in
|
let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { [unowned self] _ in
|
||||||
pauseViewController.presentSaveStateViewControllerWithMode(.Loading, delegate: self!)
|
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)
|
pauseViewController.presentCheatsViewController(delegate: self)
|
||||||
})
|
})
|
||||||
|
|
||||||
let sustainButtonItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Sustain Button", comment: ""), action: dismissAction)
|
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
|
var fastForwardItem = PauseItem(image: UIImage(named: "FastForward")!, text: NSLocalizedString("Fast Forward", comment: ""), action: { [unowned self] item in
|
||||||
self?.emulatorCore.fastForwarding = item.selected
|
self.emulatorCore.fastForwarding = item.selected
|
||||||
})
|
})
|
||||||
fastForwardItem.selected = self.emulatorCore.fastForwarding
|
fastForwardItem.selected = self.emulatorCore.fastForwarding
|
||||||
|
|
||||||
@ -324,6 +330,8 @@ extension EmulationViewController: SaveStatesViewControllerDelegate
|
|||||||
{
|
{
|
||||||
self.emulatorCore.loadSaveState(saveState)
|
self.emulatorCore.loadSaveState(saveState)
|
||||||
|
|
||||||
|
self.updateCheats()
|
||||||
|
|
||||||
self.pauseViewController?.dismiss()
|
self.pauseViewController?.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +344,70 @@ extension EmulationViewController: CheatsViewControllerDelegate
|
|||||||
{
|
{
|
||||||
return self.emulatorCore.game as! Game
|
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> -
|
//MARK: - <GameControllerReceiver> -
|
||||||
|
|||||||
@ -9,11 +9,15 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
import DeltaCore
|
||||||
|
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
protocol CheatsViewControllerDelegate: class
|
protocol CheatsViewControllerDelegate: class
|
||||||
{
|
{
|
||||||
func cheatsViewControllerActiveGame(saveStatesViewController: CheatsViewController) -> Game
|
func cheatsViewControllerActiveGame(saveStatesViewController: CheatsViewController) -> Game
|
||||||
|
func cheatsViewController(cheatsViewController: CheatsViewController, didActivateCheat cheat: Cheat) throws
|
||||||
|
func cheatsViewController(cheatsViewController: CheatsViewController, didDeactivateCheat cheat: Cheat) throws
|
||||||
}
|
}
|
||||||
|
|
||||||
class CheatsViewController: UITableViewController
|
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
|
extension CheatsViewController
|
||||||
{
|
{
|
||||||
// MARK: - Table view data source
|
// MARK: - Table view data source
|
||||||
|
|
||||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
|
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
|
||||||
{
|
{
|
||||||
// #warning Incomplete implementation, return the number of sections
|
let numberOfSections = self.fetchedResultsController.sections!.count
|
||||||
return 0
|
return numberOfSections
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
|
||||||
// #warning Incomplete implementation, return the number of rows
|
{
|
||||||
return 0
|
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