932 lines
28 KiB
Plaintext
932 lines
28 KiB
Plaintext
|
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 .. " <20> " .. 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
|