gc = game.translate_string -- util stuff local function pr(...) local debug = true if debug then printf("placeable_furniture: " .. ...) end end local function print_msg(string_id) local msg = gc(string_id) if ui_popup_messages then ui_popup_messages.GUI:AddMessage(msg) else actor_menu.set_msg(1, msg, 3) end end -- keybinds local mcm_keybinds = ui_mcm and ui_mcm.key_hold local key_place = bind_to_dik(key_bindings.kUSE) local key_toggle_collision = DIK_keys.DIK_TAB local key_toggle_align = DIK_keys.DIK_CAPITAL local key_toggle_controls = DIK_keys.DIK_HOME states = { IDLE=1, HOLDING=2, ADV_CTRL=3, PREVIEW=4 } local state = states.IDLE align_states = { ACTOR = 1, SURFACE = 2, } align_state = align_states.ACTOR local check_collision = true local place_coordinates = nil local place_sound = xr_sound.get_safe_sound_object( "interface\\place_object" ) local item_id = nil local phy_obj_section = nil local location_offset = vector():set(0, 0, 0) local rotation_offset = 0 local base_rot = 0 local base_loc = vector():set(0, 0, 0) local player_pos = nil local player_dir = nil local player_dist = nil local bbox = nil local bbox_drawer = aol_bshape.bshape_renderer() --------------------- -- State Management --------------------- function reset_offsets() location_offset = utils_data.string_to_vector(ui_mcm.get("aol_hf/debug/location_offset")) rotation_offset = 0 end transition_functors = { [states.HOLDING] = { [states.ADV_CTRL] = function () player_pos = vector():set(device().cam_pos) player_dir = vector():set(device().cam_dir) player_dist = level.get_target_dist() end }, [states.ADV_CTRL] = { [states.HOLDING] = reset_offsets }, [states.PREVIEW] = { [states.HOLDING] = reset_offsets }, [states.IDLE] = { [states.HOLDING] = reset_offsets } } -- Perform some function when a state transitions occurs function on_state_transition(old_state, new_state) local func = transition_functors[old_state] and transition_functors[old_state][new_state] if func then func() end end function get_state() return state end function set_state(new_state) local old_state = state state = new_state on_state_transition(old_state, new_state) end function is_state(check_state) return state == check_state end function in_placing_state() return get_state() ~= states.IDLE end --------------------- -- Objects and their data --------------------- ---Creates and places an object in the world. ---Also initialises additional data depending on their placeable_type as defined in their section ---@param placeable_section string ---@param location vector ---@param rotation aol_rotation.Quaternion|vector ---@param lvid integer? Level Vertex ID ---@param gvid integer? Game Vertex ID ---@return integer|nil obj.id function create_object(placeable_section, location, rotation, lvid, gvid) -- Exit early if placeable_section is not supplied if not placeable_section then pr("Could not find associated object with item") return end location = location or vector():set(0,0,0) rotation = rotation or vector():set(0,0,0) local obj = alife_create(placeable_section, location, lvid or db.actor:level_vertex_id(), gvid or db.actor:game_vertex_id()) if not obj then return end -- Rotate object with quaternion or vector if rotation.w then obj.angle = rotation:to_euler_angles() else obj.angle = vector():set(rotation.x, rotation.y, rotation.z) end -- Initialise data store hf_obj_manager.set_data(obj.id, {}) -- Initialise additional data local placeable_type = ini_sys:r_string_ex(placeable_section, "placeable_type") or "prop" local type_functor = hf_furniture_types.get_func(placeable_type) if type_functor then type_functor(obj.id) end -- Remove flags local data = utils_stpk.get_physic_data(obj) local remove_flags = 128 local flag_mask = bit_not(remove_flags) data.object_flags = bit_and(data.object_flags, flag_mask) utils_stpk.set_physic_data(data, obj) return obj.id end function transfer_item_data(item_id, obj_id) if not item_id or not obj_id then return end -- Copy data from item to obj and delete item data local hf_data = hf_obj_manager.get_data(item_id) if hf_data then hf_obj_manager.update_data(obj_id, hf_data) hf_obj_manager.delete_data(item_id) end -- Update condition if item is online local item = get_object_by_id(item_id) if not item then return end hf_obj_manager.update_data(obj_id, {condition=item:condition()}) end --------------------- -- Placement System --------------------- local function actor_on_update() if not in_placing_state() then return end local cam_pos = nil local cam_dir = nil local dist = nil if is_state(states.HOLDING) then cam_pos = device().cam_pos cam_dir = device().cam_dir dist = level.get_target_dist() else cam_pos = player_pos cam_dir = player_dir dist = player_dist end -- get position of point that the player is looking at local pos = vector() pos:mad(cam_pos, cam_dir,dist) -- rotate to point towards player + offset if align_state == align_states.ACTOR then local rot = cam_dir:getH() + (((base_rot+rotation_offset) * math.pi) / 180) local q0 = aol_rotation.get_rotation_around(vector():set(0, 1, 0), rot) bbox.rotation = q0 local angle = cam_dir:getH() -- Rotate vector about y local c = math.cos (angle) local s = math.sin (angle) local rotated_x = location_offset.x * c - location_offset.z * s local rotated_z = location_offset.x * s + location_offset.z * c pos:add(vector():set(rotated_x, location_offset.y, rotated_z)) -- automatic alignment to surface elseif align_state == align_states.SURFACE then -- Rotation local rot_z = ((base_rot+rotation_offset) * math.pi) / 180 local u = vector():set(0, 1, 0) local v = demonized_geometry_ray.get_surface_normal(cam_pos, cam_dir) if not v then return end -- rotate from upwards vector to normal vector on surface local q0 = aol_rotation.get_rotation_between(u, v) -- rotate to point downwards + offset local angle_to_downwards = 0 local similarity_to_upwards_axis = v:dotproduct(vector():set(0,1,0)) if similarity_to_upwards_axis > 0.9999 then -- pointing upwards angle_to_downwards = cam_dir:getH() elseif similarity_to_upwards_axis < -0.9999 then -- pointing downwards angle_to_downwards = math.pi - cam_dir:getH() else angle_to_downwards = angle_to_downwards + v:getH() + math.pi end local q1 = aol_rotation.get_rotation_around(v, angle_to_downwards + rot_z) -- perform each rotation in succession local q = aol_rotation.Quaternion():multiply(q0):multiply(q1) -- Location local q1_2 = aol_rotation.get_rotation_around(v, angle_to_downwards) local q_loc = aol_rotation.Quaternion():multiply(q0):multiply(q1_2) local rotated_loc_offset = q_loc:rotate_vector(location_offset) bbox.rotation = q pos:add(rotated_loc_offset) end bbox:SetWorldOrigin(vector():set(pos)) -- Update position of vertices in bounding box bbox:UpdateBBox() if check_collision then bbox:CheckForCollisions() else bbox.is_colliding = false end bbox_drawer:DrawBShapeCollider(bbox) place_coordinates = pos end local direction_keys = { [key_bindings.kFWD] = true, [key_bindings.kBACK] = true, [key_bindings.kL_STRAFE] = true, [key_bindings.kR_STRAFE] = true, [key_bindings.kCROUCH] = true, [key_bindings.kACCEL] = true, [key_bindings.kWPN_FIRE] = true, [key_bindings.kWPN_ZOOM] = true, [key_bindings.kL_LOOKOUT] = true, [key_bindings.kR_LOOKOUT] = true, [key_bindings.kJUMP] = true, [key_bindings.kQUIT] = true, [7] = true, [key_bindings.kCONSOLE] = true, } ---@return game_object|nil function get_obj_at_crosshair() if level.get_target_dist() > 5 then return end local obj = level.get_target_obj() if not obj then return end return obj end ---@return bind_hf_base.hf_binder_wrapper|nil function get_wrapper_at_crosshair() local obj = get_obj_at_crosshair() if not obj then return end local binder = obj:binded_object() if not binder then return end local wrapper = binder.wrapper if not wrapper then return end return wrapper end local function on_key_hold(dik) if dik_to_bind(dik) ~= key_bindings.kUSE then return end if Check_UI("UIRadialMenu") then return end if not in_placing_state() then if ui_mcm.key_hold("hf_interact_adv", dik) then local wrapper = get_wrapper_at_crosshair() if not wrapper then return end if Check_UI() then return end open_interact_gui(wrapper) end end end local function on_key_press(dik) if dik_to_bind(dik) == key_bindings.kUSE then if Check_UI("UIRadialMenu") then return end if not in_placing_state() then ui_mcm.simple_press("hf_interact_simple", dik, function () local wrapper = get_wrapper_at_crosshair() if not wrapper then return end wrapper:use_callback_simple() end) end end if dik == key_place then if in_placing_state() then -- Prevent placement if target pos is too far away if level.get_target_dist() > 5 and not DEV_DEBUG then print_msg("st_far_popup") pr("Cannot place object that far away - max range of 5 units") return end -- Prevent placement if bounding box is colliding with something if bbox.is_colliding then print_msg("st_collision_warning") return end local obj_id = create_object(phy_obj_section, place_coordinates, bbox.rotation) if obj_id then transfer_item_data(item_id, obj_id) end SendScriptCallback("hf_on_furniture_place", obj_id) place_sound:play_no_feedback(db.actor, sound_object.s3d, 0, place_coordinates, 1.0, 1.0) bbox_drawer:Stop() -- Reset variables base_rot = 0 phy_obj_section = nil -- Delete item if item_id then alife():release(alife():object(item_id), true) item_id = nil end set_state(states.IDLE) pr("state=PLACING") end elseif dik == key_toggle_collision then if in_placing_state() then check_collision = not check_collision if check_collision then print_msg("st_enable_collision") else print_msg("st_disable_collision") end end elseif dik == key_toggle_align then if in_placing_state() then if align_state == align_states.ACTOR then align_state = align_states.SURFACE print_msg("st_align_surface") elseif align_state == align_states.SURFACE then align_state = align_states.ACTOR print_msg("st_align_actor") end end elseif dik == key_toggle_controls then if state == states.IDLE then return elseif state == states.HOLDING or state == states.PREVIEW then set_state(states.ADV_CTRL) end open_gui() else local bind = dik_to_bind(dik) if state ~= states.IDLE and not direction_keys[bind] then pr("Interrupting placement") set_state(states.IDLE) bbox_drawer:Stop() end end end -- Cancel placement state with ESCAPE key -- turns out this fires after on_key_press local function on_before_key_press(key, bind, dis, flags) if bind == key_bindings.kQUIT and (is_state(states.HOLDING) or is_state(states.PREVIEW)) then flags.ret_value = false set_state(states.IDLE) bbox_drawer:Stop() end end ------ function start_placing_item(section) if state ~= states.IDLE then item_id = nil return end print_msg("st_place_popup") hide_hud_inventory() db.actor:activate_slot(0) set_state(states.HOLDING) local bbox_size_str = ini_sys:r_string_ex(section, "bounding_box_size", "0.5,0.5,0.5") local bbox_size = str_explode(bbox_size_str,",") local bbox_origin_str = ini_sys:r_string_ex(section, "bounding_box_origin", "0,0.25,0") local bbox_origin = str_explode(bbox_origin_str,",") base_rot = ini_sys:r_float_ex(section, "base_rotation", 0) or 0 base_loc = vector():set(tonumber(bbox_origin[1]), tonumber(bbox_origin[2]), tonumber(bbox_origin[3])) bbox = hf_bbox.bbox_collider(tonumber(bbox_size[1]), tonumber(bbox_size[2]), tonumber(bbox_size[3])) bbox:OffsetVertices(base_loc) local crosshair_vertex = bbox:GetCrosshairVertex() crosshair_vertex:sub(base_loc) bbox:SetCrosshairVertex(crosshair_vertex) phy_obj_section = section end function place_item(obj) return gc("st_place_furniture") end function func_place_item(obj) item_id = obj:id() print_msg("st_place_popup") physic_section = ini_sys:r_string_ex(obj:section(), "placeable_section") if physic_section == nil then pr("No section of physical object to place") return end start_placing_item(physic_section) end ------ -- Helper to determine if item is a placeable furniture that requires fuel -- function is_fueled_furniture(obj) -- local section = obj and obj:section() or false -- if section then -- return SYS_GetParam(0, section, "placeable_type") and SYS_GetParam(0, section, "use_condition") -- end -- return false -- end -- Monkey patch for adding fuel amount on items // DEPRECATED: using item_device binder instead, which makes these items devices -- local clr_r = utils_xml.get_color("d_red") -- local clr_g = utils_xml.get_color("d_green") -- local clr_y = utils_xml.get_color("yellow") -- local clr_2 = utils_xml.get_color("ui_gray_1") -- original_build_desc_header = ui_item.build_desc_header -- function ui_item.build_desc_header(obj, sec, str) -- local _str = "" -- local _str2 = original_build_desc_header(obj, sec, str) -- -- display power + psu -- if obj and is_fueled_furniture(obj) then -- local fuel = math.ceil(obj:condition() * 100) -- local clr = utils_xml.get_color_con(fuel) -- _str = clr .. " � " .. clr_2 .. gc("st_fuel") .. ": " .. clr .. tostring(fuel) .. "%" .. "\\n \\n" .. clr_2 -- end -- _str = _str .. _str2 -- return _str -- end ------- -- UI ------- -- Advanced Controls ---@class placeable_furniture.UIAdvancedControls GUI = nil -- instance, don't touch function open_gui() if not DEV_DEBUG then return end hide_hud_inventory() if (not GUI) then GUI = UIAdvancedControls() end if (GUI) and (not GUI:IsShown()) then GUI:Reset() GUI:ShowDialog(true) Register_UI("UIAdvancedControls","hf_advanced_controls") end end class "UIAdvancedControls" (CUIScriptWnd) function UIAdvancedControls:__init() super() self:InitControls() self:InitCallBacks() self.states = { IDLE = 1, -- idle XZ_DRAG_LOC = 2, -- Horizontal Plane Y_DRAG_LOC = 3, -- Up/Down Y_DRAG_ROT = 4, -- Rotate about upward axis } self.state = self.states.IDLE self.snap = false self.shift = false self.pos = {x=0, y=0} self.prev_pos = nil self.prev_offset = vector():set(location_offset) self.init = false end function UIAdvancedControls:__finalize() end function UIAdvancedControls:KeyPress(dik, kb_action) local funcs = self.key_to_functor[dik] if funcs then local func = funcs[kb_action] if func then func() end end end function UIAdvancedControls:InitControls() self:SetWndRect(Frect():set(0,0,1024,768)) self:SetAutoDelete(true) -- Questionable table, but i got tired of if-elses self.key_to_functor = { -- XZ Displacement [DIK_keys.MOUSE_1] = { [ui_events.WINDOW_KEY_PRESSED] = function() if self.shift then self.state = self.state == self.states.IDLE and self.states.Y_DRAG_LOC or self.state else self.state = self.state == self.states.IDLE and self.states.XZ_DRAG_LOC or self.state end end, [ui_events.WINDOW_KEY_RELEASED] = function() self.prev_offset = vector():set(location_offset) self.state = self.states.IDLE self.prev_pos = nil end }, -- Y Displacement [DIK_keys.DIK_LSHIFT] = { [ui_events.WINDOW_KEY_PRESSED] = function() self.shift = true end, [ui_events.WINDOW_KEY_RELEASED] = function() self.shift = false end }, -- Y Rotation [DIK_keys.MOUSE_2] = { [ui_events.WINDOW_KEY_PRESSED] = function() self.state = self.state == self.states.IDLE and self.states.Y_DRAG_ROT or self.state end, [ui_events.WINDOW_KEY_RELEASED] = function() self.prev_rot = rotation_offset self.state = self.states.IDLE self.prev_pos = nil end }, -- Finer control, adjust at 1/10th speed [DIK_keys.DIK_LCONTROL] = { [ui_events.WINDOW_KEY_PRESSED] = function() self.fine_control = true end, [ui_events.WINDOW_KEY_RELEASED] = function() self.fine_control = false end }, -- SNAP [DIK_keys.DIK_LMENU] = { [ui_events.WINDOW_KEY_PRESSED] = function() self.snap = true end, [ui_events.WINDOW_KEY_RELEASED] = function() self.snap = false end }, -- Place [key_place] = { [ui_events.WINDOW_KEY_PRESSED] = function() on_key_press(key_place) self:Close() end }, -- Toggle Collision [key_toggle_collision] = { [ui_events.WINDOW_KEY_PRESSED] = function() on_key_press(key_toggle_collision) end }, -- Toggle Alignment mode [key_toggle_align] = { [ui_events.WINDOW_KEY_PRESSED] = function() on_key_press(key_toggle_align) end }, -- Swap to PREVIEW mode [key_toggle_controls] = { [ui_events.WINDOW_KEY_PRESSED] = function() set_state(states.PREVIEW) self:Close() end } } end function UIAdvancedControls:InitCallBacks() end function UIAdvancedControls:Reset() self.state = self.states.IDLE self.snap = false self.pos = {x=0, y=0} self.prev_pos = nil self.prev_offset = vector():set(location_offset) self.location_offset_copy = vector():set(location_offset) self.prev_rot = rotation_offset self.prev_rot_copy = rotation_offset self.init = false end function UIAdvancedControls:Update() CUIScriptWnd.Update(self) if self.state == self.states.IDLE then return end if self.prev_pos == nil then self.prev_pos = GetCursorPosition() end local mouse_pos = GetCursorPosition() local diff_x = (mouse_pos.x - self.prev_pos.x) local diff_y = -(mouse_pos.y - self.prev_pos.y) if self.fine_control then diff_x = diff_x / 10 diff_y = diff_y / 10 end if self.state == self.states.XZ_DRAG_LOC then diff_x = diff_x / 200 diff_y = diff_y / 200 if self.snap then if math.abs(diff_x) > math.abs(diff_y) then diff_y = 0 else diff_x = 0 end end location_offset = vector():set(self.prev_offset.x+diff_x, self.prev_offset.y, self.prev_offset.z+diff_y) elseif self.state == self.states.Y_DRAG_LOC then diff_y = diff_y / 200 location_offset = vector():set(self.prev_offset.x, self.prev_offset.y+diff_y, self.prev_offset.z) elseif self.state == self.states.Y_DRAG_ROT then diff_x = diff_x/2 rotation_offset = self.prev_rot + diff_x if self.snap then local n = 15 rotation_offset = (rotation_offset % n) > n/2 and rotation_offset + n - rotation_offset%n or rotation_offset - rotation_offset%n end end end function UIAdvancedControls:OnKeyboard(dik, keyboard_action) local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action) if not self.init then self.init = true return end if (res == false) then self:KeyPress(dik, keyboard_action) if keyboard_action == ui_events.WINDOW_KEY_PRESSED then if dik == DIK_keys.DIK_ESCAPE then if self.state == self.states.IDLE then location_offset = vector():set(self.location_offset_copy) rotation_offset = self.prev_rot_copy set_state(states.HOLDING) self:Close() else location_offset = vector():set(self.prev_offset) rotation_offset = self.prev_rot self.state = self.states.IDLE end end end end return res end function UIAdvancedControls:Close() self:HideDialog() Unregister_UI("UIAdvancedControls") end -- Advanced Interaction Menu ---@class arm.UIRadialMenu RadialGUI = nil function create_interact_gui() local RadialGUI = arm.UIRadialMenu() -- Pickup local opt_pickup = arm.OptionData("opt_pickup", function (state) return arm.get_stateful_texture("ui_hf_radial_icon_pickup", state) end) opt_pickup:SetColour(function (state) return arm.get_stateful_colour(state) -- stateful colour end) -- stateful colour -- Text opt_pickup:SetText({title = gc("st_pickup")}) -- Open UI local opt_ui = arm.OptionData("opt_use", function (state) return arm.get_stateful_texture("ui_hf_radial_icon_use", state) end) opt_ui:SetColour(function (state) return arm.get_stateful_colour(state) -- stateful colour end) -- stateful colour -- Text opt_ui:SetText({title = gc("st_open_adv_ui")}) -- Freeze/Unfreeze local opt_freeze = arm.OptionData("opt_freeze", function(state) if state == arm.States.HIGHLIGHTED then return "ui_hf_radial_icon_frozen" else return "ui_hf_radial_icon_unfrozen" end end) opt_freeze:SetColour(function (state) return arm.get_stateful_colour(state) -- stateful colour end) -- stateful colour -- Text opt_freeze:SetText({title = gc("st_freeze")}) -- Upgrade local opt_upgrade = arm.OptionData("opt_upgrade", "ui_hf_radial_icon_upgrade") opt_upgrade:SetColour(function (state) return arm.get_stateful_colour(state) -- stateful colour end) -- stateful colour -- Text opt_upgrade:SetText({title = gc("st_cap_upgrades"), description = gc("st_not_implemented")}) opt_upgrade:SetState(arm.States.DISABLED) -- Wire Connections local opt_connections = arm.OptionData("opt_connections", "ui_hf_radial_icon_connections") opt_connections:SetColour(function (state) return arm.get_stateful_colour(state) -- stateful colour end) -- Text opt_connections:SetText({title = gc("st_open_connections"), description = gc("st_not_implemented")}) opt_connections:SetState(arm.States.DISABLED) RadialGUI:RegisterCallback("opt_pickup", function () local obj = get_obj_at_crosshair() if not obj then return end pickup_obj(obj) end) RadialGUI:RegisterCallback("opt_use", function () local wrapper = get_wrapper_at_crosshair() if not wrapper then return end wrapper:use_callback() end) RadialGUI:RegisterCallback("opt_freeze", function () local wrapper = get_wrapper_at_crosshair() if not wrapper then return end wrapper:set_frozen(not wrapper.is_frozen) if wrapper.is_frozen then opt_freeze:SetState(arm.States.HIGHLIGHTED) opt_freeze:SetText({title = gc("st_unfreeze")}) else opt_freeze:SetState(arm.States.ENABLED) opt_freeze:SetText({title = gc("st_freeze")}) end end) RadialGUI:AddOption(opt_ui) RadialGUI:AddOption(opt_pickup) RadialGUI:AddOption(opt_freeze) RadialGUI:AddOption(opt_upgrade) RadialGUI:AddOption(opt_connections) RadialGUI:DrawOptions() return RadialGUI end ---@param wrapper bind_hf_base.hf_binder_wrapper function update_radial_gui(wrapper) local is_pickupable = wrapper:is_pickupable() if is_pickupable then RadialGUI:GetOption("opt_pickup"):SetState(arm.States.ENABLED) else RadialGUI:GetOption("opt_pickup"):SetState(arm.States.DISABLED) end local type = hf_furniture_types.get_type(wrapper.object) if type == "prop" or type == nil then RadialGUI:GetOption("opt_use"):SetState(arm.States.DISABLED) else RadialGUI:GetOption("opt_use"):SetState(arm.States.ENABLED) end if wrapper.is_frozen then RadialGUI:GetOption("opt_freeze"):SetState(arm.States.HIGHLIGHTED) RadialGUI:GetOption("opt_freeze"):SetText({title = gc("st_unfreeze")}) else RadialGUI:GetOption("opt_freeze"):SetState(arm.States.ENABLED) RadialGUI:GetOption("opt_freeze"):SetText({title = gc("st_freeze")}) end end ---@param wrapper bind_hf_base.hf_binder_wrapper function open_interact_gui(wrapper) hide_hud_inventory() if (not RadialGUI) then RadialGUI = create_interact_gui() end if (RadialGUI) and (not RadialGUI:IsShown()) then update_radial_gui(wrapper) RadialGUI:ShowDialog(true) _GUIs_keyfree["UIRadialMenu"] = true Register_UI("UIRadialMenu","placeable_furniture") end end ------- function on_option_change(mcm) --new in mcm 1.6.0 mcm passes true to the on_option_change callback if mcm then key_toggle_collision = ui_mcm.get("aol_hf/controls/bind_collision") or key_toggle_collision key_toggle_align = ui_mcm.get("aol_hf/controls/bind_alignment") or key_toggle_align key_toggle_controls = ui_mcm.get("aol_hf/controls/bind_place_mode") or key_toggle_controls end key_place = bind_to_dik(key_bindings.kUSE) end -- Allow drag n drop of fuel onto furniture items local function on_item_drag_dropped(obj_b, obj_d, slot_from, slot_to) -- Check capability if not (slot_from == EDDListType.iActorBag and (slot_to == EDDListType.iActorBag or slot_to == EDDListType.iActorSlot)) then return end local sec_b = obj_b:section() -- fuel local sec_d = obj_d:section() -- light source local req_fuel = ini_sys:r_string_ex(sec_d, "fuel_section") if req_fuel and (sec_b == req_fuel) then if sec_b == "batteries_dead" then alife_create_item("batteries_dead", db.actor, {cond=obj_d:condition()}) obj_d:set_condition(obj_b:condition()) else utils_item.discharge(obj_b, 1) obj_d:set_condition(1.0) end utils_obj.play_sound("interface\\items\\inv_items_generic_1") end end ---@param obj game_object function pickup_obj(obj) local wrapper = bind_hf_base.get_wrapper(obj:id()) if not wrapper then return end if wrapper:is_pickupable() then wrapper:pickup() print_msg("Picked up object") end end ------- function on_game_start() RegisterScriptCallback("on_option_change",on_option_change) on_option_change(mcm_keybinds) RegisterScriptCallback("on_key_press", on_key_press) RegisterScriptCallback("on_key_hold", on_key_hold) RegisterScriptCallback("on_before_key_press", on_before_key_press) RegisterScriptCallback("actor_on_update", actor_on_update) RegisterScriptCallback("ActorMenu_on_item_drag_drop", on_item_drag_dropped) end