summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/inputmacro/init.lua
blob: 9c520f3a503990e6d1f5151377fab4be602876dc (plain) (blame)
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
-- license:BSD-3-Clause
-- copyright-holders:Vas Crabb
local exports = {
	name = 'inputmacro',
	version = '0.0.1',
	description = 'Input macro plugin',
	license = 'BSD-3-Clause',
	author = { name = 'Vas Crabb' } }


local inputmacro = exports

local frame_subscription, stop_subscription

function inputmacro.startplugin()
	--[[
	  Configuration data:
	  * name: display name (string)
	  * binding: activation sequence (input sequence)
	  * bindingcfg: activation sequence configuration (string)
	  * earlycancel: cancel or complete on release (Boolean)
	  * loop: -1 = release, 0 = prolong, >0 = loop to step on hold (integer)
	  * steps:
	    * inputs:
	      * port: port tag (string)
	      * mask: port field mask (integer)
	      * type: port field type (integer)
	      * field: field (I/O port field)
	    * delay: delay before activating inputs in frames (integer)
	    * duration: duration to activate inputs for (integer)

	  Live state:
	  * step: current step (integer or nil)
	  * frame: frame of current step, starting at 1 (integer)
	]]
	local macros = { }
	local active_inputs = { }
	local menu
	local input

	local function activate_inputs(inputs)
		for index, input in ipairs(inputs) do
			if input.field then
				active_inputs[string.format('%s.%d.%d', input.port, input.mask, input.type)] = input.field
			end
		end
	end

	local function process_frame()
		local previous_inputs = active_inputs
		active_inputs = { }

		for index, macro in ipairs(macros) do
			if macro.step then
				if macro.earlycancel and (not input:seq_pressed(macro.binding)) then
					-- stop immediately on release if early cancel set
					macro.step = nil
				else
					-- advance frame
					macro.frame = macro.frame + 1
					local step = macro.steps[macro.step]
					if macro.frame > (step.delay + step.duration) then
						if macro.step < #macro.steps then
							-- not the last step, advance step
							macro.step = macro.step + 1
							macro.frame = 1
							step = macro.steps[macro.step]
						elseif not input:seq_pressed(macro.binding) then
							-- input released and macro completed
							macro.step = nil
							step = nil
						elseif macro.loop > 0 then
							-- loop to step
							macro.step = macro.loop
							macro.frame = 1
						elseif macro.loop < 0 then
							-- release if held
							step = nil
						end
					end
					if step and (macro.frame > step.delay) then
						activate_inputs(step.inputs)
					end
				end
			elseif input:seq_pressed(macro.binding) then
				-- initial activation
				macro.step = 1
				macro.frame = 1
				local step = macro.steps[1]
				if step.delay == 0 then
					-- no delay on first step, activate inputs
					activate_inputs(step.inputs)
				end
			end
		end

		for key, field in pairs(active_inputs) do
			field:set_value(1)
		end
		for key, field in pairs(previous_inputs) do
			if not active_inputs[key] then
				field:clear_value()
			end
		end
	end

	local function start()
		input = manager.machine.input
		local persister = require('inputmacro/inputmacro_persist')
		macros = persister.load_settings()
	end

	local function stop()
		local persister = require('inputmacro/inputmacro_persist')
		persister:save_settings(macros)

		macros = { }
		active_inputs = { }
		menu = nil
	end

	local function menu_callback(index, event)
		return menu:handle_event(index, event)
	end

	local function menu_populate()
		if not menu then
			menu = require('inputmacro/inputmacro_menu')
			menu:init(macros)
		end
		return menu:populate()
	end

	frame_subscription = emu.add_machine_frame_notifier(process_frame)
	emu.register_prestart(start)
	stop_subscription = emu.add_machine_stop_notifier(stop)
	emu.register_menu(menu_callback, menu_populate, _p('plugin-inputmacro', 'Input Macros'))
end

return exports