1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
|
# Copyright 2019 DragonRuby LLC
# MIT License
# controller_config.rb has been released under MIT (*only this file*).
# !!! FIXME: add console command to forget custom binding(s)
# !!! FIXME: add console command to forget replace existing binding(s)
# !!! FIXME: add console command go into play_around mode to make sure controller isn't wonky.
module GTK
class ControllerConfig
def initialize runtime
@runtime = runtime
@raw_joysticks = {} # things that aren't game controllers to try to configure.
@target = nil
@animation_duration = (1.5).seconds
@toggled_at = 0
@fading = 0
@current_part = 0
@part_alpha = 0
@part_alpha_increment = 10
@joystick_state = {}
@playing_around = false
@used_bindings = {}
@bindings = []
@parts = [
[ 919, 282, 'A button', 'a' ],
[ 960, 323, 'B button', 'b' ],
[ 878, 323, 'X button', 'x' ],
[ 919, 365, 'Y button', 'y' ],
[ 433, 246, 'left stick left', '-leftx' ],
[ 497, 246, 'left stick right', '+leftx' ],
[ 466, 283, 'left stick up', '-lefty' ],
[ 466, 218, 'left stick down', '+lefty' ],
[ 466, 246, 'left stick button', 'leftstick' ],
[ 741, 246, 'right stick left', '-rightx' ],
[ 802, 246, 'right stick right', '+rightx' ],
[ 773, 283, 'right stick up', '-righty' ],
[ 773, 218, 'right stick down', '+righty' ],
[ 772, 246, 'right stick button', 'rightstick' ],
[ 263, 465, 'left shoulder button', 'leftshoulder' ],
[ 263, 503, 'left trigger', 'lefttrigger' ],
[ 977, 465, 'right shoulder button', 'rightshoulder' ],
[ 977, 503, 'right trigger', 'righttrigger' ],
[ 318, 365, 'D-pad up', 'dpup' ],
[ 360, 322, 'D-pad right', 'dpright' ],
[ 318, 280, 'D-pad down', 'dpdown' ],
[ 275, 322, 'D-pad left', 'dpleft' ],
[ 570, 402, 'select/back button', 'back'],
[ 619, 448, 'guide/home button', 'guide' ],
[ 669, 402, 'start button', 'start' ],
]
end
def rawjoystick_connected jid, joystickname, guid
return if jid < 0
@raw_joysticks[jid] = { name: joystickname, guid: guid }
end
def rawjoystick_disconnected jid
return if jid < 0
if @raw_joysticks[jid] != nil
@raw_joysticks.delete(jid)
@runtime.ffi_misc.close_raw_joystick(jid)
# Fade out the config screen if we were literally configuring this controller right now.
if [email protected]? && @target[0] == jid
@target[0] = nil
@toggled_at = Kernel.global_tick_count
@fading = -1
end
end
end
def build_binding_string
bindingstr = ''
skip = false
for i in [email protected]
if skip ; skip = false ; next ; end
binding = @bindings[i]
next if binding.nil?
part = @parts[i][3]
# clean up string:
# if axis uses -a0 for negative and +a0 for positive, just make it "leftx:a0" instead of "-leftx:-a0,+leftx:+a0"
# if axis uses +a0 for negative and -a0 for positive, just make it "leftx:a0~" instead of "-leftx:+a0,+leftx:-a0"
if part == '-leftx' || part == '-lefty' || part == '-rightx' || part == '-righty'
nextbinding = @bindings[i+1]
if binding.start_with?('-a') && nextbinding.start_with?('+a') && binding[2..-1] == nextbinding[2..-1]
skip = true
part = part[1..-1]
binding = binding[1..-1]
elsif binding.start_with?('+a') && nextbinding.start_with?('-a') && binding[2..-1] == nextbinding[2..-1]
skip = true
part = part[1..-1]
binding = "#{binding[1..-1]}~"
end
end
bindingstr += "#{!bindingstr.empty? ? ',' : ''}#{part}:#{binding}"
end
details = @target[1]
# !!! FIXME: no String.delete in mRuby?!?! Maybe so when upgrading.
#name = details[:name].delete(',')
# !!! FIXME: ...no regexp either... :/
#name = details[:name].gsub(/,/, ' ') # !!! FIXME: will SDL let you escape these instead?
unescaped = details[:name]
name = ''
for i in 0..unescaped.length-1
ch = unescaped[i]
name += (ch == ',') ? ' ' : ch
end
return "#{details[:guid]},#{name},platform:#{@runtime.platform},#{bindingstr}"
end
def move_to_different_part part
if !@joystick_state[:axes].nil?
@joystick_state[:axes].each { |i| i[:farthestval] = i[:startingval] if !i.nil? }
end
@current_part = part
end
def previous_part
if @current_part > 0
# remove the binding that we previous had here so it can be reused.
bindstr = @bindings[@current_part - 1]
@bindings[@current_part - 1] = nil
@used_bindings[bindstr] = nil
move_to_different_part @current_part - 1
end
end
def next_part
if @current_part < (@parts.length - 1)
move_to_different_part @current_part + 1
else
@playing_around = true
end
end
def set_binding bindstr
return false if !@used_bindings[bindstr].nil?
@used_bindings[bindstr] = @current_part
@bindings[@current_part] = bindstr
return true
end
# Called when a lowlevel joystick moves an axis.
def rawjoystick_axis jid, axis, value
return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick.
@joystick_state[:axes] ||= []
@joystick_state[:axes][axis] ||= {
moving: false,
startingval: 0,
currentval: 0,
farthestval: 0
}
# this is the logic from SDL's controllermap.c, more or less, since this is hard to get right from scratch.
state = @joystick_state[:axes][axis]
state[:currentval] = value
if !state[:moving]
state[:moving] = true
state[:startingval] = value
state[:farthestval] = value
end
current_distance = (value - state[:startingval]).abs
farthest_distance = (state[:farthestval] - state[:startingval]).abs
if current_distance > farthest_distance
state[:farthestval] = value
farthest_distance = (state[:farthestval] - state[:startingval]).abs
end
# If we've gone out far enough and started to come back, let's bind this axis
if (farthest_distance >= 16000) && (current_distance <= 10000)
next_part if set_binding("#{(state[:farthestval] < 0) ? '-' : '+'}a#{axis}")
end
end
# Called when a lowlevel joystick moves a hat.
def rawjoystick_hat jid, hat, value
return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick.
@joystick_state[:hats] ||= []
@joystick_state[:hats][hat] = value
return if value == 0 # 0 == centered, skip it
next_part if set_binding("h#{hat}.#{value}")
end
# Called when a lowlevel joystick moves a button.
def rawjoystick_button jid, button, pressed
return if @target.nil? || jid != @target[0] || @fading != 0 # skip if not currently considering this joystick.
@joystick_state[:buttons] ||= []
@joystick_state[:buttons][button] = pressed
return if !pressed
next_part if set_binding("b#{button}")
end
def calc_fading
if @fading == 0
return 255
elsif @fading > 0 # fading in
percent = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip)
if percent >= 1.0
percent = 1.0
@fading = 0
end
else # fading out
percent = @toggled_at.global_ease(@animation_duration, :flip, :quint)
if percent <= 0.0
percent = 0.0
@fading = 0
end
end
return (percent * 255.0).to_i
end
def render_basics args, msg, fade=255
joystickname = @target[1][:name]
args.outputs.primitives << [0, 0, GAME_WIDTH, GAME_HEIGHT, 255, 255, 255, fade].solid
args.outputs.primitives << [0, 0, GAME_WIDTH, GAME_HEIGHT, 'dragonruby-controller.png', 0, fade, 255, 255, 255].sprite
args.outputs.primitives << [GAME_WIDTH / 2, 700, joystickname, 2, 1, 0, 0, 0, fade].label
args.outputs.primitives << [GAME_WIDTH / 2, 650, msg, 0, 1, 0, 0, 0, 255].label if !msg.empty?
end
def render_part_highlight args, part, alpha=255
partsize = 41
args.outputs.primitives << [part[0], part[1], partsize, partsize, 255, 0, 0, alpha].border
args.outputs.primitives << [part[0]-1, part[1]-1, partsize+2, partsize+2, 255, 0, 0, alpha].border
args.outputs.primitives << [part[0]-2, part[1]-2, partsize+4, partsize+4, 255, 0, 0, alpha].border
end
def choose_target
if @target.nil?
while !@raw_joysticks.empty?
t = @raw_joysticks.shift # see if there's a joystick waiting on us.
next if t[0] < 0 # just in case.
next if t[1][:guid].nil? # did we already handle this guid? Dump it.
@target = t
break
end
return false if @target.nil? # nothing to configure at the moment.
@toggled_at = Kernel.global_tick_count
@fading = 1
@current_part = 0
@part_alpha = 0
@part_alpha_increment = 10
@joystick_state = {}
@used_bindings = {}
@playing_around = false
@bindings = []
end
return true
end
def render_part_highlight_from_bindstr args, bindstr, alpha=255
partidx = @used_bindings[bindstr]
return if partidx.nil?
render_part_highlight args, @parts[partidx], alpha
end
def play_around args
return false if !@playing_around
if args.inputs.keyboard.key_down.escape
@current_part = 0
@part_alpha = 0
@part_alpha_increment = 10
@used_bindings = {}
@playing_around = false
@bindings = []
elsif args.inputs.keyboard.key_down.space
jid = @target[0]
bindingstr = build_binding_string
#puts("new controller binding: '#{bindingstr}'")
@runtime.ffi_misc.add_controller_config bindingstr
@runtime.ffi_misc.convert_rawjoystick_to_controller jid
@target[0] = -1 # Conversion closes the raw joystick.
# Handle any other pending joysticks that have the same GUID (so if you plug in four of the same model, we're already done!)
guid = @target[1][:guid]
@raw_joysticks.each { |jid, details|
if details[:guid] == guid
@runtime.ffi_misc.convert_rawjoystick_to_controller jid
details[:guid] = nil
end
}
# Done with this guy.
@playing_around = false
@toggled_at = Kernel.global_tick_count
@fading = -1
return false
end
render_basics args, 'Now play around with the controller, and make sure it feels right!'
args.outputs.primitives << [GAME_WIDTH / 2, 90, '[ESCAPE]: Reconfigure, [SPACE]: Save this configuration', 0, 1, 0, 0, 0, 255].label
axes = @joystick_state[:axes]
if !axes.nil?
for i in 0..axes.length-1
next if axes[i].nil?
value = axes[i][:currentval]
next if value.nil? || (value.abs < 16000)
render_part_highlight_from_bindstr args, "#{value < 0 ? '-' : '+'}a#{i}"
end
end
hats = @joystick_state[:hats]
if !hats.nil?
for i in 0..hats.length-1
value = hats[i]
next if value.nil? || (value == 0)
render_part_highlight_from_bindstr args, "h#{i}.#{value}"
end
end
buttons = @joystick_state[:buttons]
if !buttons.nil?
for i in 0..buttons.length-1
value = buttons[i]
next if value.nil? || !value
render_part_highlight_from_bindstr args, "b#{i}"
end
end
return true
end
def should_tick?
return true if @play_around
return true if @target
return false
end
def tick args
return true if play_around args
return false if !choose_target
jid = @target[0]
if @fading == 0
# Cancel config?
if args.inputs.keyboard.key_down.escape
# !!! FIXME: prompt to ignore this joystick forever or just this run
@toggled_at = Kernel.global_tick_count
@fading = -1
end
end
if @fading == 0
if args.inputs.keyboard.key_down.backspace
previous_part
elsif args.inputs.keyboard.key_down.space
next_part
end
end
fade = calc_fading
if (@fading < 0) && (fade == 0)
@runtime.ffi_misc.close_raw_joystick(jid) if jid >= 0
@target = nil # done with this controller
return false
end
render_basics args, (@fading >= 0) ? "We don't recognize this controller, so tell us about it!" : '', fade
return true if fade < 255 # all done for now
part = @parts[@current_part]
args.outputs.primitives << [GAME_WIDTH / 2, 575, "Please press the #{part[2]}.", 0, 1, 0, 0, 0, 255].label
render_part_highlight args, part, @part_alpha
args.outputs.primitives << [GAME_WIDTH / 2, 90, '[ESCAPE]: Ignore controller, [BACKSPACE]: Go back one button, [SPACE]: Skip this button', 0, 1, 0, 0, 0, 255].label
@part_alpha += @part_alpha_increment
if (@part_alpha_increment > 0) && (@part_alpha >= 255)
@part_alpha = 255
@part_alpha_increment = -10
elsif (@part_alpha_increment < 0) && (@part_alpha <= 0)
@part_alpha = 0
@part_alpha_increment = 10
end
return true
end
end
end
|