· 4 years ago · Jul 13, 2021, 04:10 AM
1local next, assert, pairs, type, tostring, setmetatable, baseMt, _instances, _classes, _class = next, assert, pairs, type, tostring, setmetatable, {}, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
2local function assert_call_from_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end; local function assert_call_from_instance(instance, method) assert(_instances[instance], ('Wrong method call. Expected instance:%s.'):format(method)) end
3local function bind(f, v) return function(...) return f(v, ...) end end
4local default_filter = function() return true end
5local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {}; for k,v in pairs(t) do if aType ~= nil and type(v) == aType then r[k] = (type(v) == 'table') and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v elseif aType == nil then r[k] = (type(v) == 'table') and k~= '__index' and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v end; end return r end
6local function instantiate(call_init,self,...) assert_call_from_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); deep_copy(self, instance, 'table')
7 instance.__index, instance.__subclasses, instance.__instances, instance.mixins = nil, nil, nil, nil; setmetatable(instance,self); if call_init and self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end end; return instance
8end
9local function extend(self, name, extra_params)
10 assert_call_from_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); self.__subclasses[heir] = true; deep_copy(extra_params, deep_copy(self, heir))
11 heir.name, heir.__index, heir.super, heir.mixins = extra_params and extra_params.name or name, heir, self, {}; return setmetatable(heir,self)
12end
13baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...)
14 if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end; return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?', _classes[self]) or self end
15}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring})
16local class = {isClass = function(t) return not not _classes[t] end, isInstance = function(t) return not not _instances[t] end}
17_class = function(name, attr) local c = deep_copy(attr); _classes[c] = tostring(c)
18 c.name, c.__tostring, c.__call, c.new, c.create, c.extend, c.__index, c.mixins, c.__instances, c.__subclasses = name or c.name, baseMt.__tostring, baseMt.__call, bind(instantiate, true), bind(instantiate, false), extend, c, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
19 c.subclasses = function(self, filter, ...) assert_call_from_class(self, 'subclasses(class)'); filter = filter or default_filter; local subclasses = {}; for class in pairs(_classes) do if class ~= baseMt and class:subclassOf(self) and filter(class,...) then subclasses[#subclasses + 1] = class end end; return subclasses end
20 c.instances = function(self, filter, ...) assert_call_from_class(self, 'instances(class)'); filter = filter or default_filter; local instances = {}; for instance in pairs(_instances) do if instance:instanceOf(self) and filter(instance, ...) then instances[#instances + 1] = instance end end; return instances end
21 c.subclassOf = function(self, superclass) assert_call_from_class(self, 'subclassOf(superclass)'); assert(class.isClass(superclass), 'Wrong argument given to method "subclassOf()". Expected a class.'); local super = self.super; while super do if super == superclass then return true end; super = super.super end; return false end
22 c.classOf = function(self, subclass) assert_call_from_class(self, 'classOf(subclass)'); assert(class.isClass(subclass), 'Wrong argument given to method "classOf()". Expected a class.'); return subclass:subclassOf(self) end
23 c.instanceOf = function(self, fromclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(fromclass), 'Wrong argument given to method "instanceOf()". Expected a class.'); return ((self.class == fromclass) or (self.class:subclassOf(fromclass))) end
24 c.cast = function(self, toclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(toclass), 'Wrong argument given to method "cast()". Expected a class.'); setmetatable(self, toclass); self.class = toclass; return self end
25 c.with = function(self,...) assert_call_from_class(self, 'with(mixin)'); for _, mixin in ipairs({...}) do assert(self.mixins[mixin] ~= true, ('Attempted to include a mixin which was already included in %s'):format(tostring(self))); self.mixins[mixin] = true; deep_copy(mixin, self, 'function') end return self end
26 c.includes = function(self, mixin) assert_call_from_class(self,'includes(mixin)'); return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end
27 c.without = function(self, ...) assert_call_from_class(self, 'without(mixin)'); for _, mixin in ipairs({...}) do
28 assert(self.mixins[mixin] == true, ('Attempted to remove a mixin which is not included in %s'):format(tostring(self))); local classes = self:subclasses(); classes[#classes + 1] = self
29 for _, class in ipairs(classes) do for method_name, method in pairs(mixin) do if type(method) == 'function' then class[method_name] = nil end end end; self.mixins[mixin] = nil end; return self end; return setmetatable(c, baseMt) end
30setmetatable(class,{__call = function(_,...) return _class(...) end })
31function print(string, ...) if ... then client.print(string.format(string, ...)) else client.print(string) end end
32------------------ END REQUIRED 30LOG ------------------------
33local Module = class("Module")
34function Module:init(module_name)
35 self.__VERSION = 'ModuleClass v1.0 by Twinklenugget'self.events = {}; self.name = module_name
36 self.wrappers = setmetatable({__module=self}, {__index=function(table, key) if table.__module[key] then return function(...) table.__module[key](table.__module, ...) end end end})
37 self.proxy = setmetatable({module=self}, {__index = function(table, key) if table.module.events[key] ~= nil then for _, callable in ipairs(table.module.events[key]) do if table.module.callable then callable(table.module, key) else callable(key) end end end return table.module.wrappers[key] end})
38 module_manager.register(module_name, self.proxy)
39end
40function Module:option(option_name) return module_manager.option(self.name, option_name) end
41function Module:__is_callable(pointer)
42 local the_type = type(pointer)
43 if the_type ~= 'table' and the_type ~= 'function' then print("Error: callable '%s' must be a table or function", pointer) elseif the_type == 'table' and type(getmetatable(callable).__call) ~= 'function' then client.print("Error: Table must be callable") else return true end return false
44end
45function Module:wrap(func, wrapper)
46 local wrap_name = string.format("%s_%s_%s", func, 'wrapper', client.time())
47 self.wrappers[wrap_name] = wrapper
48 self.wrappers[func] = function(...) self.wrappers[wrap_name](self, ...) end
49end
50function Module:bind(event, callable, ...)
51 local args = {...}
52 if not event and not callable then client.print("Please pass an event name and a callable") return end
53 if type(event) ~= 'string' then client.print("Error: Event must be a string") return end
54 if type(callable) == 'string' then if not self:__is_callable(self[callable]) then return end else if not self:__is_callable(callable) then return end end
55 if not self.events[event] then self.events[event] = {} end
56 if type(callable) == 'string' then self.events[event][#self.events[event] + 1] = function(...) return self[callable](self, ...) end
57 elseif type(callable) == 'function' and type(args[1]) == 'table' then self.events[event][#self.events[event] + 1] = function() return callable(table.unpack(args)) end --This elseif provides support for 30log classes, pass instance as third argument
58 else self.events[event][#self.events[event] + 1] = callable end
59end
60function Module:bindKey(key_code, callable, event)
61 local the_type = type(callable); if not event then event = 'on_pre_update' end
62 if not key_code and not callable then client.print("Please pass a key code and a callable") return elseif type(key_code) ~= 'number' then client.print("Key code must be a number") return end
63 if the_type == 'string' then if not self:__is_callable(self[callable]) then return end else if not self:__is_callable(callable) then return end end
64 if the_type == 'string' then self:bind(event, function() if input.is_key_down(key_code) then self[callable](self, key_code) end end) else self:bind(event, function() if input.is_key_down(key_code) then callable(key_code) end end) end
65end
66function Module:addOption(option_name, ...)
67 if type(option_name) ~= 'string' then print("Error, string expected at addOption with name: '%s'", option_name) return end
68 local args = { n = select("#", ...), ... }
69 if args.n == 1 and type(args[1]) == 'boolean' then module_manager.register_boolean(self.name, option_name, args[1]) return elseif args.n == 1 then print("Error, boolean expected at addOption with name '%s'", option_name) return end
70 if args.n > 1 then for i=1, args.n do if type(args[i]) ~= 'number' then print("Error, number expected at position %s at addOption with name: '%s'", i, option_name) return end end end
71 module_manager.register_number(self.name, option_name, args[1], args[2], args[3])
72end
73------------------ END ModuleClass ------------------------
74
75local Player = class("Player")
76
77function Player:init()
78 self.__VERSION = 'PlayerClass v1.0 by Twinklenugget'
79 self._player = player
80 self.history = {}
81 for key, func in pairs(player) do self[key] = func end --Add all API functions
82end
83
84function Player:update(record)
85 self.x, self.y, self.z = self._player.position()
86 self.motion_x, self.motion_y, self.motion_z = self._player.motion()
87 self.yaw, self.pitch = self._player.angles()
88 if record then
89 table.insert(self.history, {time=client.time(), x=self.x, y=self.y, z=self.z, m_x=self.motion_x, m_y=self.motion_y, m_z=self.motion_z, yaw=self.yaw, pitch=self.pitch})
90 end
91end
92--- Entity Utilities ---
93function Player:entityInFOV(entity, fov)
94 if fov > 360 or fov < 20 then print("FOV must be between 20 and 360") return end
95 local e_x, e_y, e_z = world.position(entity_id)
96 local pitch_for_entity, _ = self._player.angles_for_cords(e_x, e_y, e_z)
97
98 local res = math.abs(pitch_for_entity-self.yaw) % 360
99 if res >= 180 then res = math.abs(res - 360) end
100 return res <= (fov/2)
101end
102
103function Player:entityOnTeam(entityID)
104 if not self.display_name then return false end
105 if string.find(self.display_name, "\194\167") ~= nil then
106 local substring = string.sub(self.display_name, string.find(self.display_name, "\194\167"), string.find(self.display_name, "\194\167") + 2)
107 if string.find(world.display_name(entityID), substring) ~= nil then
108 return true
109 end
110 end
111 return false
112end
113--- Feature Wrappers ---
114function Player:ray_cast(yaw, pitch, distance)
115 local res, side, b_x, b_y, b_z, h_x, h_y, h_z = self._player.ray_cast(yaw, pitch, distance)
116 if res ~= 2 then return {res=1} end
117 return {res=res, side=side, b_x=b_x, b_y=b_y, b_z=b_z, h_x=h_x, h_y=h_y, h_z=h_z,
118 print = function (self)
119 if self.res == 1 then client.print("No hit")
120 else
121 client.print(string.format("%s %s %s %s %s %s %s",
122 self.side, self.b_x, self.b_y, self.b_z, self.h_x, self.h_y, self.h_z))
123 end
124 end,
125 hitting = function (self, x, y, z)
126 return (math.abs(x-self.h_x) < 1 and
127 math.abs(y-self.h_y) < 1 and
128 math.abs(z-self.h_z) < 1)
129 end
130 }
131end
132
133function Player:over_mouse()
134 local res, side, b_x, b_y, b_z, h_x, h_y, h_z = self.player.over_mouse()
135 if res ~= 2 then return {res=1} end
136 return {res=res,side=side,b_x=b_x,b_y=b_y,b_z=b_z,h_x=h_x,h_y=h_y,h_z=h_z}
137end
138
139function Player:angles_for_coords(x, y, z)
140 --Simply correct pitch
141 local function correctYaw(goal_yaw)
142 --Correct yaw to playeres current yaw
143 local yaw_multi = math.floor(self.yaw/360)*360
144 local needed_yaw = goal_yaw + yaw_multi
145 local diff = self.yaw - needed_yaw
146
147 local left = self.yaw - diff
148 local right = self.yaw + (360- diff)
149
150 if math.abs(self.yaw - right) < math.abs(self.yaw - left) then return right else return left end
151 end
152
153 local new_yaw, new_pitch = self._player.angles_for_cords(x, y, z)
154 return correctYaw(new_yaw), new_pitch
155end
156
157--- Utilities ---
158function Player:matchHotbarSlot(pattern)
159 local orig_type = type(pattern)
160 for i = 36, 44 do
161 slot = self._player.inventory.slot(i)
162 if slot then
163 if orig_type == 'table' then
164 for _, item in ipairs(pattern) do
165 if string.match(slot, item) then return i-35 end
166 end
167 elseif orig_type == 'string' then
168 if string.match(slot, pattern) then return i-35 end
169 end
170 end
171 end
172 return -1
173end
174
175function Player:getDisplayName()
176 for _, entity_id in ipairs(world.entities()) do
177 if world.name(entity_id) == self._player.name() then return world.display_name(entity_id) end
178 end
179end
180player = Player:new()
181------------------ END PlayerClass ------------------------
182
183--------------------------------------------------------------------------------------
184---------------------------------Helper Functions-------------------------------------
185--------------------------------------------------------------------------------------
186
187function Player:isPlayerEntity(entity_id)
188 local player_id = player.id()
189 local e_name = world.name(entity_id)
190
191 if not e_name or string.match(e_name, 'item') then
192 return false
193 end
194
195 if world.is_player(entity_id) and not world.is_bot(entity_id) and entity_id ~= player_id and world.name(entity_id) ~= 'unknown' then
196 return true
197 end
198 return false
199end
200
201local function getAllPlayers(teams)
202 local entities = world.entities()
203 local players = {}
204
205 for i = 1, # entities do
206 entity_id = entities[i]
207 if player:isPlayerEntity(entity_id) then
208 if teams then
209 if not player:entityOnTeam(entity_id) then
210 table.insert(players, entity_id)
211 end
212 else
213 table.insert(players, entity_id)
214 end
215 end
216 end
217 return players
218end
219
220local function getAbsoluteDistance(entity_id)
221 -- Returns absolute distance between player and entity
222
223 local p_x, p_y, p_z = player.position()
224 local e_x, e_y, e_z = world.position(entity_id)
225
226 if not e_x or not e_y or not e_z then
227 return 10000
228 end
229
230 local res = math.pow(p_x - e_x, 2) + math.pow(p_y - e_y, 2) + math.pow(p_z - e_z, 2)
231 return math.sqrt(res)
232
233end
234
235local function getClosestPlayer(players)
236 local closest_player_distance = 30000
237 local closest_player_id = -1
238
239 for i = 1, # players do
240 local player_id = players[i]
241 local player_distance = getAbsoluteDistance(player_id)
242
243 if player_distance < closest_player_distance and player_id ~= player.id() then
244 closest_player_distance = player_distance
245 closest_player_id = player_id
246 end
247 end
248 return closest_player_id
249end
250
251local function getAngleForPitch(player_y_pos, entity_y_pos, entity_height, entity_distance)
252 local adj_height = entity_height * .66
253 local h_diff = (player.eye_height() - adj_height) + (player_y_pos - entity_y_pos)
254 local angle = (360*h_diff)/(2*math.pi*entity_distance)
255
256 return math.max(-90, math.min(90, angle))
257end
258
259local function createPointArray(min, max, step)
260 local diff = (max - min) / step
261 local res = {}
262
263 for i = 1, step+1 do res[i] = min + (diff*(i-1)) end
264 return res
265end
266
267local function getClosestBBox(p_val, lo, hi)
268 local arr = createPointArray(lo, hi, 10)
269
270 local best = nil
271 local best_diff = 420
272
273 for _, val in ipairs(arr) do
274 diff = math.abs(p_val - val)
275 if diff < best_diff then
276 best = val
277 best_diff = diff
278 end
279 end
280
281 return best
282end
283
284local function actual_angles_for_coords(entity_id)
285 -- Get positions
286 local e_x, _, e_z = world.position(entity_id)
287 local p_x, _, p_z = player.position()
288 local minX, _, minZ, maxX, _, maxZ = world.bounding_box(entity_id)
289
290 -- Dynamic edge
291 local e_x = getClosestBBox(player.x, minX, maxX)
292 local e_z = getClosestBBox(player.z, minZ, maxZ)
293
294
295 local cur_yaw, _ = player.angles()
296 local needed_yaw, _ = player.angles_for_cords(e_x, 0, e_z)
297
298 local yaw_multi = math.floor(cur_yaw/360)*360
299
300 local needed_yaw = needed_yaw + yaw_multi
301
302 local diff = cur_yaw - needed_yaw
303
304 local left = cur_yaw - diff
305 local right = cur_yaw + (360 - diff)
306
307 if math.abs(cur_yaw-right) < math.abs(cur_yaw-left) then
308 final_yaw = right
309 else
310 final_yaw = left
311 end
312
313 return final_yaw
314
315end
316
317local function getClosestAngleForEntity(entity_id, distance)
318 -- Return's the angles needed to hit the closest point on an
319 -- entities hitbox
320 local e_x, e_y, e_z = world.position(entity_id)
321 local _, height = world.width_height(entity_id)
322
323 return actual_angles_for_coords(entity_id), getAngleForPitch(player.y, e_y, height, distance)
324end
325
326local function sword_or_hand_valid()
327 if not module_manager.option("BetterAimAssist", "sword-or-hand") then
328 return true
329 end
330
331 local held_item = player.held_item()
332
333 if not held_item then
334 if player.is_potion_active(373) then
335 return false
336 end
337
338 return true
339 end
340 if string.match(held_item, "Sword") or string.match(held_item, "Rod") or string.match(held_item, "Stick") then
341 return true
342 end
343 if string.match(held_item, "Wool") then
344 return true
345 end
346 return false
347end
348
349local function mouse_down_valid()
350 if not module_manager.option("BetterAimAssist", "mouse-down") then
351 return true
352 end
353
354 return input.is_mouse_down(0)
355end
356
357local function getPlayersWithinThreshold(players, threshold, fov)
358 local close_players = {}
359
360 for i = 1, # players do
361 player_id = players[i]
362 if getAbsoluteDistance(player_id) <= threshold and player:entityInFOV(player_id, fov) then
363 close_players[#close_players+1] = player_id
364 end
365 end
366 return close_players
367end
368
369local function through_wall_valid(yaw, pitch, range)
370 if not module_manager.option("BetterAimAssist", "mouse-down") then
371 return true
372 end
373 if player.ray_cast(yaw, pitch, range) == 2 then
374 return false
375 end
376 return true
377end
378
379local function entityYawDiff(entity_id)
380 -- Returns the difference in fov
381 local yaw, pitch = player.angles()
382 local yaw = yaw % 360
383 local e_yaw, e_pitch = player.angles_for_cords(world.position(entity_id))
384
385 return math.abs(e_yaw-yaw)
386end
387
388function degreesOnTargetPlayer(distance) return math.deg(math.atan(0.3/distance)*2)*2 end
389
390local function lowHealthOverride(near_players, planned_player)
391 -- Return's the ID of the lowest health player in near_players
392 if not module_manager.option("BetterAimAssist", "target-low-health") then
393 return planned_player
394 end
395 health_threshold = 10
396end
397
398local function lerp(v0, v1, t) return (1 - t) * v0 + t * v1 end
399--------------------------------------------------------------------------------------
400-----------------------------------Registration---------------------------------------
401--------------------------------------------------------------------------------------
402
403local AimAssist = Module:new("BetterAimAssist")
404
405AimAssist:addOption("active_distance", 30, 80, 60)
406AimAssist:addOption("fov", 40, 360, 360)
407AimAssist:addOption("sword-or-hand", true)
408-- module_manager.register_number("BetterAimAssist", "randomization", 1, 10, 5)
409-- module_manager.register_number("BetterAimAssist", "randomization_threshold", 1, 10, 5)
410AimAssist:addOption("smoothing", 0, 100, 60)
411AimAssist:addOption("target-switch", false)
412AimAssist:addOption("mouse-down", true)
413AimAssist:addOption("through-walls", true)
414AimAssist:addOption("teams", true)
415AimAssist:addOption("target_hold_boost", true)
416AimAssist:addOption("target_hold_factor", 11, 30, 17)
417
418
419
420--------------------------------------------------------------------------------------
421------------------------------------Main Logic----------------------------------------
422--------------------------------------------------------------------------------------
423
424function AimAssist:on_enable()
425 self.prev_entity = -1
426 self.prev_yaw, self.prev_pitch = 0, 0
427 self.cur_hold_time = 0 -- Hold time in ticks for switch
428 self.hold_time = 6
429 self.cur_e_index = 1
430 self.on_target = false
431 self.poll_time = 0
432end
433
434
435
436function AimAssist:poll()
437 -- Update stuff on an interval for performance
438 if self.poll_time == 0 then
439 player.display_name = player:getDisplayName()
440 self.worldPlayers = getAllPlayers(self:option("teams"))
441
442 -- Poll main options
443 self.active_distance = self:option("active_distance") / 10
444 self.fov = self:option("fov")
445 self.target_switch = self:option("target-switch")
446 self.smoothing = 1 - self:option("smoothing") / 100
447
448
449 self.poll_time = 20
450 else
451 self.poll_time = self.poll_time - 1
452 end
453end
454
455function AimAssist:on_pre_motion()
456 -- Main logic
457 local valid_players = getPlayersWithinThreshold(self.worldPlayers, self.active_distance, self.fov)
458 local closest_player_id = getClosestPlayer(valid_players)
459 if #valid_players > 0 and sword_or_hand_valid() then
460 if self.target_switch and #valid_players > 1 then
461 if self.cur_hold_time < self.hold_time then
462 -- We switch to new entity
463 if #valid_players < self.cur_e_index then
464 self.cur_e_index = #valid_players -- Catch exception
465 end
466 -- Ensure we only count if we're on target
467 if self.on_target then
468 self.cur_hold_time = self.cur_hold_time + 1
469 end
470 else
471 if self.cur_e_index < #valid_players then
472 self.cur_e_index = self.cur_e_index + 1
473 elseif cur_e_index == # valid_players then
474 self.cur_e_index = 1
475 end
476 self.cur_hold_time = 0
477 end
478 closest_player_id = valid_players[self.cur_e_index] --Override closest player
479 end
480
481 local entity_distance = getAbsoluteDistance(closest_player_id)
482
483 if mouse_down_valid() and closest_player_id ~= nil then
484 local lerp_factor = 1
485 local new_yaw, new_pitch = getClosestAngleForEntity(closest_player_id, entity_distance)
486 local diff = math.abs(player.yaw - new_yaw)
487 if diff < degreesOnTargetPlayer(entity_distance) then
488 self.on_target = true
489 else
490 self.on_target = false
491 end
492
493 if through_wall_valid(new_yaw, new_pitch, self.active_distance) then
494 if self.on_target and self:option("target_hold_boost") then
495 lerp_factor = self:option("target_hold_factor")/10
496 end
497
498 local new_yaw = lerp(player.yaw, new_yaw, self.smoothing*lerp_factor)
499 local new_pitch = lerp(player.pitch, new_pitch, self.smoothing*lerp_factor)
500 player.set_angles(new_yaw, new_pitch)
501 self.prev_yaw, self.prev_pitch = new_yaw, new_pitch
502
503 self.prev_entity = closest_player_id
504 end
505 end
506 else
507 self.prev_entity = -1
508 end
509end
510--------------------------------------------------------------------------------------
511----------------------------------Apply Bindings--------------------------------------
512--------------------------------------------------------------------------------------
513
514
515AimAssist:bind("on_pre_motion", player.update, player)
516AimAssist:bind("on_pre_update", "poll")