Skip to content

Commit 4359d1a

Browse files
authored
Launcher: controller switch and stick dead zone (#16)
A Controller card in the launcher: turn the game controller on or off and set the stick dead zone (NFSU2_PAD / NFSU2_PAD_DEADZONE; values set by hand still win).
1 parent 47e1cd3 commit 4359d1a

5 files changed

Lines changed: 47 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ portable app with the game files inside (APFS clones, seconds on the same disk),
289289
so nobody needs `pack.sh --embed` or a terminal for that. Untick "Show this
290290
window when the app opens" to go straight into the game (hold Option to get the
291291
window back). `NFSU2_SKIP_LAUNCHER=1` / `NFSU2_ENGINE=original|hd` do the same
292-
from scripts.
292+
from scripts. The Controller card turns the game controller on or off and sets its stick
293+
dead zone (`NFSU2_PAD` / `NFSU2_PAD_DEADZONE`, which still win when set by hand).
293294

294295
![Launcher window](screenshots/selected/launcher.png)
295296

‎launcher/Game.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ enum Prefs {
6464
// Shared with the engine (same bundle id, same defaults domain).
6565
static let gameFolder = "OyunDizini" // name kept for existing installs
6666
static let fullscreen = "NFSUFullScreen"
67+
// Launcher only; passed to the engine as NFSU2_PAD / NFSU2_PAD_DEADZONE.
68+
static let controller = "LauncherController"
69+
static let stickDeadzone = "LauncherStickDeadzone"
6770
// Launcher only.
6871
static let engine = "LauncherEngine" // "original" | "hd"
6972
static let showAtStartup = "LauncherShowAtStartup"

‎launcher/Model.swift‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ final class LauncherModel: ObservableObject {
2121
@Published var showAtStartup: Bool { didSet { defaults.set(showAtStartup, forKey: Prefs.showAtStartup) } }
2222
@Published var wideHUD: Bool { didSet { defaults.set(wideHUD, forKey: Prefs.wideHUD) } }
2323
private var setHUDPOS = false // HUDPOS was set by us (not by the caller)
24+
@Published var controller: Bool { didSet { defaults.set(controller, forKey: Prefs.controller) } }
25+
@Published var stickDeadzone: Double { didSet { defaults.set(stickDeadzone, forKey: Prefs.stickDeadzone) } }
26+
private var setPad = false // NFSU2_PAD / NFSU2_PAD_DEADZONE were set by us
2427
@Published var job: Job? = nil
2528
@Published var notice: String? = nil
2629

@@ -41,7 +44,7 @@ final class LauncherModel: ObservableObject {
4144
let fm = FileManager.default
4245
let hasOriginal = fm.isExecutableFile(atPath: Paths.engine(nil, hd: false).path)
4346
let hasHD = fm.isExecutableFile(atPath: Paths.engine(nil, hd: true).path)
44-
defaults.register(defaults: [Prefs.showAtStartup: true])
47+
defaults.register(defaults: [Prefs.showAtStartup: true, Prefs.controller: true, Prefs.stickDeadzone: 0.10])
4548
var e = Engine(rawValue: defaults.string(forKey: Prefs.engine) ?? "") ?? .original
4649
if let env = ProcessInfo.processInfo.environment["NFSU2_ENGINE"], let f = Engine(rawValue: env) { e = f }
4750
if e == .hd && !hasHD { e = .original }
@@ -51,6 +54,8 @@ final class LauncherModel: ObservableObject {
5154
fullscreen = defaults.bool(forKey: Prefs.fullscreen)
5255
showAtStartup = defaults.bool(forKey: Prefs.showAtStartup)
5356
wideHUD = defaults.bool(forKey: Prefs.wideHUD)
57+
controller = defaults.bool(forKey: Prefs.controller)
58+
stickDeadzone = min(0.5, max(0, defaults.double(forKey: Prefs.stickDeadzone)))
5459
}
5560

5661
var location: GameLocation? {
@@ -118,6 +123,13 @@ final class LauncherModel: ObservableObject {
118123
if wideHUD { setenv("HUDPOS", "1", 1); setHUDPOS = true }
119124
else if setHUDPOS { unsetenv("HUDPOS"); setHUDPOS = false }
120125
}
126+
// Controller settings; NFSU2_PAD / NFSU2_PAD_DEADZONE set by hand still win. Ours
127+
// are overwritten on every Play, so a change takes effect without restarting.
128+
if setPad || (env["NFSU2_PAD"] == nil && env["NFSU2_PAD_DEADZONE"] == nil) {
129+
setenv("NFSU2_PAD", controller ? "1" : "0", 1)
130+
setenv("NFSU2_PAD_DEADZONE", String(format: "%.2f", stickDeadzone), 1)
131+
setPad = true
132+
}
121133
var e = engine
122134
if !hasEngine(e) { e = (e == .hd) ? .original : .hd }
123135
let path = Paths.engine(loc.build, hd: e == .hd).path

‎launcher/Views.swift‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,32 @@ struct GraphicsCard: View {
154154
}
155155
}
156156

157+
struct ControllerCard: View {
158+
@ObservedObject var model: LauncherModel
159+
var body: some View {
160+
Card("Controller") {
161+
HStack(alignment: .center) {
162+
VStack(alignment: .leading, spacing: 1) {
163+
Text("Use a game controller").font(.system(size: 12.5)).foregroundColor(Theme.text)
164+
Text("Xbox, PlayStation or MFi. Connect it before Play; buttons are set in the game's Controls menu.")
165+
.font(.system(size: 11)).foregroundColor(Theme.dim)
166+
.fixedSize(horizontal: false, vertical: true)
167+
}
168+
Spacer(minLength: 8)
169+
Toggle("", isOn: $model.controller).labelsHidden().toggleStyle(SwitchToggleStyle())
170+
}
171+
HStack(alignment: .center, spacing: 10) {
172+
Text("Stick dead zone").font(.system(size: 12.5)).foregroundColor(Theme.text)
173+
Slider(value: $model.stickDeadzone, in: 0...0.5, step: 0.01)
174+
Text("\(Int((model.stickDeadzone * 100).rounded())) %")
175+
.font(.system(size: 11.5).monospacedDigit()).foregroundColor(Theme.dim)
176+
.frame(width: 40, alignment: .trailing)
177+
}
178+
.disabled(!model.controller).opacity(model.controller ? 1 : 0.5)
179+
}
180+
}
181+
}
182+
157183
struct MoreCard: View {
158184
@ObservedObject var model: LauncherModel
159185
var body: some View {
@@ -253,6 +279,7 @@ struct LauncherView: View {
253279

254280
GameCard(model: model)
255281
GraphicsCard(model: model)
282+
ControllerCard(model: model)
256283
MoreCard(model: model)
257284

258285
Spacer(minLength: 0)
@@ -274,6 +301,6 @@ struct LauncherView: View {
274301
.padding(.horizontal, 22).padding(.top, 10).padding(.bottom, 18)
275302
if let j = model.job { JobOverlay(model: model, job: j) }
276303
}
277-
.frame(width: 560, height: 624)
304+
.frame(width: 560, height: 724)
278305
}
279306
}

‎launcher/main.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
4949

5050
if let s = env["NFSU2_LAUNCHER_STATE"] { fakeState(s) }
5151
buildMenu()
52-
let w = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 560, height: 624),
52+
let w = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 560, height: 724),
5353
styleMask: [.titled, .closable, .miniaturizable],
5454
backing: .buffered, defer: false)
5555
w.titlebarAppearsTransparent = true

0 commit comments

Comments
 (0)