-
Notifications
You must be signed in to change notification settings - Fork 0
/
dial_button.txt
269 lines (199 loc) · 7.11 KB
/
dial_button.txt
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
--@name Dial Button
--@author legokidlogan
--@server
--@include lkl/key_globalizer.txt
-- Config:
local ownerOwnly = false
local allowMultipleGrabs = true
local interactRange = 200 -- Based on eye position to eye trace.
local forceUngrabRange = 200 -- Based on player position to dial position.
local roundAmount = 1 -- 2 rounds to the nearest ten, 1 rounds to the nearest whole number, 0 doesn't round, -1 rounds to the nearest tenth, etc. Can also give 'math.log10( r ) + 1' to round to the nearest r.
--roundAmount = math.log10( 3.33333333 ) + 1
local minRotations = -1 -- Set to -1 for infinite
local maxRotations = -1 -- Set to -1 for infinite
local updateInterval = 0.05
local baseDirRemap = {
Forward = function( ent ) return ent:getForward() end,
Right = function( ent ) return ent:getRight() end,
Up = function( ent ) return ent:getUp() end,
}
-- End Config
--[[ Notes:
- Rounding is handled like this: fancyRound( 100 * numFullRotations )
--]]
local vecUp = Vector( 0, 0, 1 )
local dialAngInit = Angle( 0, 0, 0 )
local dialRot = 0
local dialRotDeltaBuildup = 0
local grabberCount = 0
local interactRangeSqr = interactRange ^ 2
local forceUngrabRangeSqr = forceUngrabRange ^ 2
local baseEnt
local dialEnt
local grabbers = {}
local mMin = math.min
local mMax = math.max
local mRound = math.round
local mClamp = math.clamp
local tableInsert = table.insert
inputNames = inputNames or {}
inputTypes = inputTypes or {}
outputNames = outputNames or {}
outputTypes = outputTypes or {}
tableInsert( inputNames, "Base" )
tableInsert( inputTypes, "ENTITY" )
tableInsert( inputNames, "Dial" )
tableInsert( inputTypes, "ENTITY" )
tableInsert( outputNames, "Value_Scale1" )
tableInsert( outputTypes, "NUMBER" )
tableInsert( outputNames, "Value_Scale100" )
tableInsert( outputTypes, "NUMBER" )
wire.adjustInputs( inputNames, inputTypes )
wire.adjustOutputs( outputNames, outputTypes )
require( "lkl/key_globalizer.txt" )
local function getBaseDir( dirStr )
return baseDirRemap[dirStr]( baseEnt )
end
local function fancyRound( x, amt )
amt = amt or roundAmount
if amt == 0 then return x end
if amt < 0 then return mRound( x, -amt ) end
x = mRound( x )
local trunc = x % ( 10 ^ ( amt - 1 ) )
return x - trunc
end
-- Clockwise is positive, opposite to math angles.
local function getAngleOnDial( pos )
if not isValid( baseEnt ) or not isValid( dialEnt ) then return end
--local posLocal = baseEnt:worldToLocal( pos )
pos = pos - dialEnt:getPos()
local x = pos:dot( getBaseDir( "Right" ) )
local y = pos:dot( getBaseDir( "Forward" ) )
return math.deg( -math.atan2( y, x ) )
end
local function angleChange( degFrom, degTo )
local change = degTo - degFrom
if change > 180 then
change = change - 360
elseif change < -180 then
change = change + 360
end
return change
end
local function setDialEnt( ent )
if not isValid( ent ) then return end
dialEnt = ent
if not isValid( baseEnt ) then return end
dialAngInit = baseEnt:worldToLocalAngles( dialEnt:getAngles() )
dialAngInit = dialAngInit:rotateAroundAxis( vecUp, dialRot )
end
local function setBaseEnt( ent )
if not isValid( ent ) then return end
baseEnt = ent
setDialEnt( dialEnt )
end
local function turnTheDial( degDelta, degSet )
if not isValid( dialEnt ) or not isValid( baseEnt ) then return end
if degDelta == 0 and not degSet then return end
dialRot = degSet or ( dialRot + angleChange( dialRot, dialRot + degDelta + dialRotDeltaBuildup ) )
local dialRotPreRounding = dialRot
dialRot = fancyRound( dialRot * 100 / 360 ) * 360 / 100
dialRotDeltaBuildup = dialRotPreRounding - dialRot -- Account for rotations lost from rounding, keep adding it up until we have enough to go to the next rounding chunk.
if minRotations == -1 then
if maxRotations ~= -1 then
dialRot = mMin( dialRot, maxRotations * 360 )
end
elseif maxRotations == -1 then
dialRot = mMax( dialRot, -minRotations * 360 )
else
dialRot = mClamp( dialRot, -minRotations * 360, maxRotations * 360 )
end
local ang = dialAngInit:rotateAroundAxis( vecUp, -dialRot )
dialEnt:setAngles( baseEnt:localToWorldAngles( ang ) )
--local dialValue100 = fancyRound( dialRot * 100 / 360 )
local dialValue100 = dialRot * 100 / 360
wire.ports.Value_Scale1 = dialValue100 / 100
wire.ports.Value_Scale100 = dialValue100
end
local function isLookingAt( ply, ent )
local eyePos = ply:getEyePos()
local eyeTrace = ply:getEyeTrace()
if eyeTrace.Entity ~= ent then return false end
if eyeTrace.HitPos:getDistanceSqr( eyePos ) > interactRangeSqr then return false end
return true
end
local function canAddGrabber( ply )
if not isValid( ply ) or not ply:isValid() then return false end
if ownerOwnly and ply ~= owner() then return false end
if not allowMultipleGrabs and grabberCount > 0 then return false end
if not isLookingAt( ply, dialEnt ) then return false end
return true
end
local function addGrabber( ply )
if grabbers[ply] then return end
local eyeTrace = ply:getEyeTrace()
grabberCount = grabberCount + 1
grabbers[ply] = {
LastRot = getAngleOnDial( eyeTrace.HitPos ),
}
end
local function removeGrabber( ply )
if not grabbers[ply] then return end
grabberCount = grabberCount - 1
grabbers[ply] = nil
end
local function getGrabberRotChange( ply )
local grabData = grabbers[ply]
local eyeTrace = ply:getEyeTrace()
local rot = getAngleOnDial( eyeTrace.HitPos )
local lastRot = grabData.LastRot
grabData.LastRot = rot
return angleChange( lastRot, rot )
end
local function purgeDistantGrabbers()
for ply in pairs( grabbers ) do
local pos = ply:getPos()
if pos:getDistanceSqr( dialEnt:getPos() ) > forceUngrabRangeSqr then
removeGrabber( ply )
end
end
end
local function grabberThink()
if not isValid( dialEnt ) or not isValid( baseEnt ) then return end
purgeDistantGrabbers()
local rotChange = 0
for ply in pairs( grabbers ) do
rotChange = rotChange + getGrabberRotChange( ply )
end
turnTheDial( rotChange )
end
local function keyPress( ply, key, state )
if key == IN_USE then
if ownerOwnly and ply ~= owner() then return end
if state then
if not canAddGrabber( ply ) then return end
addGrabber( ply )
else
removeGrabber( ply )
end
elseif key == IN_RELOAD then
if not state then return end
if not isLookingAt( ply, dialEnt ) then return end
turnTheDial( nil, 0 )
end
end
local function wireInput( name, value )
if name == "Base" then
setBaseEnt( value )
elseif name == "Dial" then
setDialEnt( value )
end
end
hook.add( "input", "LKL_DialButton_WireInput", wireInput )
hook.add( "KeyPress", "LKL_DialButton_KeyPress", function( ply, key )
keyPress( ply, key, true )
end )
hook.add( "KeyRelease", "LKL_DialButton_KeyRelease", function( ply, key )
keyPress( ply, key, false )
end )
timer.create( "LKL_DialButton_UpdateDial", updateInterval, 0, grabberThink )