Divergent/mods/Personal Adjustable Waypoint/gamedata/scripts/tasks_placeable_waypoints.s...

3848 lines
131 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- ======================================================================
--[[ Personal Adjustable Waypoint &
Pins Recording Intel, Notes, or Terrain
Yes, I see what I did there, and so do you.
-- ======================================================================
Author: Catspaw
I post gameplay video on my Youtube channel, Catspaw Adventures:
https://www.youtube.com/channel/UCtG8fiWPUZEzWlkUn60btAw
Source: https://www.moddb.com/mods/stalker-anomaly/addons/personal-adjustable-waypoint-for-anomaly-151-152-and-gamma
Version: 2.0.2
Updated: 20231204
You may alter any part of this mod and do whatever you like with it,
just give credit where due.
Right-click on any visible map point to set/move/remove a waypoint
or map pins. Map pins can be named, renamed, removed, and given
custom icons. The waypoint is tracked like a task. Semicolon (default)
toggles the current or most recent waypoint on/off.
To see map points, hold Slash (default, configurable in MCM) to
enter Cartography Mode. In this mode, all terrain smart points are
visible. Both the hold and the terrain display are options in MCM.
While in Cartography Mode, you can also Ping the map from the context
menu, creating a temporary grid of visible mapspots that you can use
for more precise placement.
Press Apostrophe (default) to drop a Quick Pin at the player's
current location.
Press Period or Comma (default) to cycle through active pins in the
current set, or hold Alt while doing this to cycle sets instead.
Credits: HarukaSai, RavenAscendant, Ghen Tuong, demonized, and
many others who've assisted in ways big and small.
-- ======================================================================
-- SHORTCUTS, FLAGS, AND SYSTEM STUFF
(Most of which you probably shouldn't muck with)
-- ===================================================================--]]
script_version = "2.0.2"
release_date = 20231204
local scriptname = "tasks_placeable_waypoints"
local logprefix = "<PAW> "
local language = "eng"
debuglogs = false -- Controls debug logging if you don't have MCM
verbose = true
-- Verbose being on won't do anything if debuglogs isn't also enabled,
-- but it will ensure that if anyone has to turn on debug logging,
-- their logs will contain the maximum amount of info automatically.
-- It's noisy and you can turn it off here if you have to.
-- ======================================================================
modxml_map_spots_paw.load_me = function() end
-- ======================================================================
function dl(logtext,...)
-- Debug logging - to disable, set debuglogs to false
if logtext and debuglogs then
printf (logprefix..logtext,...)
end
end
function vl(logtext,...)
-- Verbose logging - to disable, set either debuglogs or verbose to false
if logtext and debuglogs and verbose then
dl("[V] "..logtext,...)
end
end
-- ======================================================================
mcm_killswitch = false
-- Enabling the killswitch will hard-cutoff any attempt
-- to load values from MCM, in case that is causing issues.
disable_mcm_updates = false
-- This flag will disable the immediate MCM updates that happen
-- when changing icons or sets in-game.
paw_enabled = true -- setting false will killswitch the entire addon
local icoset_changed = nil
started = false -- runtime flag
waypoint_active = false -- runtime flag
waypoint_canceling = false -- runtime flag
disable_load_warning = false
local load_failed = false -- runtime flag
local welcome_msg_shown = false
local get_game_time = game.get_game_time
local get_start_time = level.get_start_time
local ts = game.translate_string
local tm = task_manager.get_task_manager()
local psk = utils_data.parse_string_keys
local task_info = tm.task_info
local floor,ceil = math.floor,math.ceil
local pow,sqrt = math.pow,math.sqrt
-- ======================================================================
mousewheel_override = false
--[[ If set to true, mousewheel_override will skip the autodetection of
mousewheel support and force it to be on. This may have bad results
if your binaries don't actually support it.
Mousewheel support can also be forced on by setting the following
MCM config value (not exposed in the menu):
pawsys/pawbinds/mwheel_override = true
--]]
right_click_override = false
-- Similarly with right_click_override, but for the on_map_right_click callback
mwheel_enabled = false -- managed at runtime
mwheel_notify = true -- news tip on state change
mwheel_poll_interval = 50 -- interval between checks for mouse wheel input
mwheel_next_poll = time_global()
local mwheel_exe_ver = 20230701 -- mouse wheel support added to demonized binaries in this version
local rclick_exe_ver = 20230922 -- right-click map support added to demonized binaries in this version
local game_version = ts("ui_st_game_version")
local gamma_modpack = game_version:find("G.A.M.M.A.")
mwheel_avail = mousewheel_override or (MODDED_EXES_VERSION and (MODDED_EXES_VERSION >= mwheel_exe_ver))
right_click_avail = right_click_override or gamma_modpack or (MODDED_EXES_VERSION and (MODDED_EXES_VERSION >= rclick_exe_ver))
last_clicked_id = nil
-- ======================================================================
unsquish_ratio = 1
local DIK_name = ui_mcm and ui_mcm.dispaly_key or (function() return "" end)-- typo is in the mcm script
local default_id = 15797 -- WP defaults to Rookie Village if missing for any reason to avoid crash
local mcm_update_throttle = 3019 -- minimum time between MCM updates of icon/set data
local next_mcm_update = time_global()
local wp_proxcheck_interval = 837 -- Time between checks for waypoint proximity
local next_wp_proxcheck = time_global()
local garbcollect_interval = 4984 -- Time between pin/sz garbage collection checks
local next_gc_check = time_global()
local wp_clear_dist = 5
local widget_hide_delay = 5000
local next_pin = time_global()
local pin_delay = 1000 -- timeout to prevent dupe actions from registering
local ping_lifetime = 10000
local ping_grid_size = 10
local ping_grid_radius = 10
--local max_dynamic_faves = 5 -- not yet implemented
local current_active_set = "pins"
local active_theme = "classicauto"
local current_set_max = 1
local current_ico_max = 1
placed_waypoint = nil
last_waypoint = nil
-- ======================================================================
local toggle_bind = DIK_keys.DIK_SEMICOLON
local toggle_mod = 2
local quickpin_bind = DIK_keys.DIK_APOSTROPHE
local quickpin_mod = 0
local cart_mode_hold = DIK_keys.DIK_SLASH
local cart_mode_mod = 0
local mcm_keybinds = ui_mcm and ui_mcm.key_hold
cart_mode = false
cartography_must_hold = false
cart_shows_smarts = true
cartmode_unfade = true
tip_on_icoset_change = false
allow_non_wp_targets = false
clear_pin_on_death = true -- pin will be cleared if set on a living thing that dies
pin_near_fade_dist = 5 -- min distance within which pins will begin to fade out, set to 0 to disable
pin_far_fade_dist = 0 -- distance beyond which pins will begin to fade out, set to 0 to disable
pin_far_hide_dist = 10 -- max distance after far_fade at which pins will hide, set to 0 to disable
-- ======================================================================
enable_wp_proxcheck = true
show_pins = true
local custom_task_info = false
local icon_cycle_active = false
local set_cycle_active = false
widget_enabled = true
widget_active = true
hide_widget = true
widget_use_custom_pos = false
widget_custom_pos = {x=491,y=670}
widget_last_used = time_global()
wp_hud_icon_enabled = true
pin_hud_icon_default = true
manual_smart_pins = false
mark_on_positive_id = true
reticle_mustzoom = true
reticle_mode = 2
autotag_milpda_feature = false -- not implemented yet
autotag_persistence = false -- if enabled, autotags are cleared on map change or the below timeout value
autotag_lifetime = 120 * 6 -- time in ms before autotags are cleared (def 2 minutes)
autotags_time_out = autotag_lifetime > 0
ret_fade_attack_time = 400
ret_fade_decay_time = 600
reticle_color = {
["a"] = 150,
["r"] = 255,
["g"] = 255,
["b"] = 255,
}
show_marker_dist = {
["pins"] = 4,
["wp"] = 4,
}
show_marker_hint = {
["pins"] = 2,
["wp"] = 0, -- not implemented yet
}
-- ======================================================================
-- STRINGS AND LTX DATA
-- ======================================================================
local cartmode = "cartmode"
local sep = "_"
local snd_path = "catspaw\\"
local snd_ping = snd_path.."paw_ping"
local snd_cycle_blip = snd_path.."paw_blip"
local snd_tag_target = snd_path.."paw_blip"
local use_ping_snd = true
local use_ui_snd = true
local add_mapspot_functor = scriptname..".func_add_mapspot"
local ren_mapspot_functor = scriptname..".func_ren_mapspot"
local ren_wp_functor = scriptname..".func_ren_waypoint"
local task_id = "task_placeable_waypoint"
local pawmenu_path = "ui_mcm_pawsys_pawmenu_"
-- Don't change any of these icon definitions. If you want to
-- override PAW's pin icons with any of yours, use MCM or
-- see below for how to add your own, it's easy.
current_body_icon = nil
body_icon_mode = "off"
patch_res = "badge"
local custom_pin_icon = false
local default_mapspot = "paw_pin_redpush32"
local smart_terrain_icon = "paw_pin_magnifier32"
local icon_pingspot = "paw_ping_alpha50"
waypoint_mapspot = "paw_task_default"
map_pin_icon = default_mapspot
force_icon_override = nil
-- If force_icon_override is set to anything, PAW will try to use it
-- for pins instead of what it normally would.
-- This is purely a debugging tool. There are better ways to use your own
-- custom icons.
-- ======================================================================
icons_ini = ini_file_ex("scripts\\paw\\icons.ltx")
icon_sets_ini = ini_file_ex("scripts\\paw\\icon_sets.ltx")
actions_ini = ini_file_ex("scripts\\paw\\menu_actions.ltx")
task_ini = ini_file_ex("scripts\\paw\\task_config.ltx")
clsids_ini = ini_file_ex("scripts\\paw\\valid_clsids.ltx")
icon_ltx = icons_ini:collect_section("icons")
iconset_ltx = icon_sets_ini:get_sections(true)
actions_ltx = actions_ini:get_sections(true)
clsids_ltx = clsids_ini:collect_section("valid_clsids")
task_cfg = task_ini:collect_section("task_config")
custom_name = (task_cfg and task_cfg.name)
custom_desc = (task_cfg and task_cfg.desc)
icons = icon_ltx
curr_menu_options = {}
icon_sets = {}
action_codes = {}
action = {}
texture_data = {}
icon_index = {}
set_index = {}
valid_clsids = {}
local script_zones = {}
local pings = {}
local pawdata = {
pins = {},
--dynamic_faves = {},
curr_set_ind = 1,
curr_ico_ind = 1,
curr_set_name = "pins",
curr_ico_name = default_mapspot,
}
pawdata.smart_pins = {
["human_f"] = {
pin = "chevron_friendly",
enabled = false,
},
["human_n"] = {
pin = "chevron_neutral",
enabled = false,
},
["human_e"] = {
pin = "chevron_enemy",
enabled = true,
},
["monster"] = {
pin = "chevron_enemy",
enabled = true,
},
["stash"] = {
pin = "bwhr_loot",
enabled = false,
},
}
pawdata.npc_ident = {
delay_id = true,
id_bodies = false,
id_speed = 0.3,
lenience = 0.99,
tick_speed = 200,
progress = {},
}
npc_ident = pawdata.npc_ident
smart_pins = pawdata.smart_pins
pins = pawdata.pins
dynamic_faves = pawdata.dynamic_faves
-- ======================================================================
pda_defs = {
-- By default, all PDAs support waypointing
["device_pda_1"] = {
show_w = true,
},
["device_pda_2"] = {
show_w = true,
},
["device_pda_3"] = {
show_w = true,
},
["device_pda_milspec"] = {
show_w = true,
},
-- If you use an addon with an unsupported PDA section, add it here
-- ["device_pda_added_by_your_mod"] = {
-- show_w = false,
-- },
}
managed_factions = {
-- editing this may cause crashes or other unexpected results
["army"] = true,
["bandit"] = true,
["csky"] = true,
["dolg"] = true,
["ecolog"] = true,
["freedom"] = true,
["killer"] = true,
["monolith"] = true,
["stalker"] = true,
["greh"] = true,
["isg"] = true,
["renegade"] = true,
}
managed_options = {
-- editing this may cause context menus to behave oddly
["mping"] = true,
["wp_set"] = true,
["wp_mov"] = true,
["wp_del"] = true,
-- ["wp_ren"] = true,
["waypoint_rename"] = true,
["waypoint_redesc"] = true,
["hud_vis_on"] = true,
["hud_vis_off"] = true,
["show_all_pins"] = true,
["hide_all_pins"] = true,
["pn_add"] = true,
["pn_del"] = true,
["pn_ren"] = true,
["pn_clr"] = true,
["lock_pin"] = true,
["unlock_pin"] = true,
["cm_dbg"] = true,
}
hud_themes = {
["classicauto"] = {
name = "classicauto",
node = "pawhudind",
style = "full",
tex = "ui_paw_hud_indicator_classic",
file = "paw_hud_indicator.xml",
pos = {x=890,y=620},
w = 136,
h = 75,
pda_thm = "vertright",
},
["classic"] = {
name = "classic",
node = "pawhudind",
style = "full",
tex = "ui_paw_hud_indicator_classic",
file = "paw_hud_indicator.xml",
pos = {x=890,y=620},
w = 136,
h = 75,
},
["vertright"] = {
name = "vertright",
node = "pawhudind_vertright",
style = "minimal",
tex = "ui_paw_hud_indicator_vertright",
file = "paw_hud_indicator.xml",
pos = {x=922,y=440},
w = 100,
h = 154,
},
["vertleft"] = {
name = "vertleft",
node = "pawhudind_vertleft",
style = "minimal",
tex = "ui_paw_hud_indicator_vertleft",
file = "paw_hud_indicator.xml",
pos = {x=4,y=570},
w = 100,
h = 154,
},
["gamma_right"] = {
name = "gamma_right",
node = "pawhudind_gamma_right",
style = "full",
tex = "ui_paw_hud_indicator_gamma_right",
file = "paw_hud_indicator.xml",
pos = {x=888,y=440},
w = 130,
h = 150,
},
["gamma_left"] = {
name = "gamma_left",
node = "pawhudind_gamma_left",
style = "full",
tex = "ui_paw_hud_indicator_gamma_left",
file = "paw_hud_indicator.xml",
pos = {x=4,y=440},
w = 130,
h = 150,
},
["minimal_h"] = {
name = "minimal_h",
node = "pawhudind_minimal_h",
style = "minimal",
tex = "ui_paw_hud_indicator_minimal_h",
file = "paw_hud_indicator.xml",
pos = {x=924,y=645},
w = 94,
h = 52,
},
["minimal_v"] = {
name = "minimal_v",
node = "pawhudind_minimal_v",
style = "minimal",
tex = "ui_paw_hud_indicator_minimal_v",
file = "paw_hud_indicator.xml",
pos = {x=924,y=440},
w = 92,
h = 112,
},
["minicom"] = {
name = "minicom",
node = "pawhudind_minicom",
style = "compact",
tex = "ui_paw_hud_indicator_minicom",
file = "paw_hud_indicator.xml",
pos = {x=820,y=725},
w = 38,
h = 37,
},
["compact_noui"] = {
name = "compact_noui",
node = "pawhudind_minicom",
style = "compact",
file = "paw_hud_indicator.xml",
pos = {x=820,y=725},
w = 32,
h = 37,
pre_tex = "ui_paw_hud_indicator_minicom",
},
}
colors = {
dimmed = GetARGB(100, 255, 255, 255),
active = GetARGB(255, 255, 255, 255),
}
text_colors = {
["clr_255"] = "%" .. "%c[255,255,255,255]",
["clr_red"] = "%" .. "%c[255,255,0,0]",
["clr_grn"] = "%" .. "%c[255,0,255,0]",
["clr_blu"] = "%" .. "%c[255,0,0,255]",
["clr_lbl"] = "%" .. "%c[255,0,200,220]",
["clr_yel"] = "%" .. "%c[255,250,218,94]",
["clr_wht"] = "%" .. "%c[255,220,220,220]",
["clr_prp"] = "%" .. "%c[255,138,43,226]",
["clr_blk"] = "%" .. "%c[255,0,0,0]",
}
local keybinds = {
["cartmode"] = {
enabled = function() return paw_enabled end,
hold = cartography_must_hold,
bind = DIK_keys.DIK_SLASH,
name = "ui_mcm_pawsys_pawbinds_bind_cartmode",
action = function(tf)
if tf == nil then
tf = not cart_mode
end
cartography_mode(tf)
end,
},
["wp_target_obj"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_SEMICOLON,
mod = 3,
name = "ui_mcm_pawsys_pawbinds_bind_wp_target",
action = function() wp_target_obj() end,
},
["pin_target_obj"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_APOSTROPHE,
mod = 3,
name = "ui_mcm_pawsys_pawbinds_bind_pin_target",
action = function() pin_target_obj(nil,nil,nil,manual_smart_pins) end,
},
["quickpin"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_APOSTROPHE,
name = "ui_mcm_pawsys_pawbinds_bind_quickpin",
action = function() drop_quick_pin() end,
},
["wptoggle"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_SEMICOLON,
name = "ui_mcm_pawsys_pawbinds_bind_wptoggle",
action = function() toggle_waypoint() end,
},
["set_next"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_PERIOD,
mod = 3,
name = "ui_mcm_pawsys_pawbinds_bind_set_next",
action = function() cycle_items(1,"set",tip_on_icoset_change) end,
},
["set_prev"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_COMMA,
mod = 3,
name = "ui_mcm_pawsys_pawbinds_bind_set_prev",
action = function() cycle_items(-1,"set",tip_on_icoset_change) end,
},
["ico_next"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_PERIOD,
name = "ui_mcm_pawsys_pawbinds_bind_ico_next",
action = function() cycle_items(1,"icon",tip_on_icoset_change) end,
},
["ico_prev"] = {
enabled = function() return paw_enabled end,
bind = DIK_keys.DIK_COMMA,
name = "ui_mcm_pawsys_pawbinds_bind_ico_prev",
action = function() cycle_items(-1,"icon",tip_on_icoset_change) end,
},
["ico_scroll"] = {
enabled = function() return paw_enabled and mwheel_enabled end,
bind = DIK_keys.DIK_Z,
name = "ui_mcm_pawsys_pawbinds_bind_ico_scroll",
mod = 3,
hold = true,
action = function(tf) icon_cycle(true,tf) end,
},
["set_scroll"] = {
enabled = function() return paw_enabled and mwheel_enabled end,
bind = DIK_keys.DIK_Z,
name = "ui_mcm_pawsys_pawbinds_bind_set_scroll",
mod = 2,
hold = true,
action = function(tf) set_cycle(true,tf) end,
},
}
-- ======================================================================
-- GENERAL UTILITY FUNCTIONS
-- ======================================================================
function exec(str,...)
if str then
str = str_explode(str,"%.")
if str[1] and str[2] and _G[ str[1] ] and _G[ str[1] ][ str[2] ] then
_G[ str[1] ][ str[2] ](...)
else
dl("Could not exec function %s", str)
end
end
end
function delay_exec_of(func,secs,...)
if not func then return end
secs = secs or 0
CreateTimeEvent("pawdelay","paw"..func..tostring(time_global()),secs,func,...)
end
function dotip(tiptext,dur,src,beep,icon,snd)
vl("Tip call received: dur %s | src \"%s\" | beep %s\n\"%s\"",dur,src,beep,tiptext)
if tiptext == nil then return end
db.actor:give_game_news(src or "PAW System", tiptext, icon or "ui_inGame2_Mesta_evakuatsii", 0, dur or 5000)
if beep then
xr_sound.set_sound_play(AC_ID, snd or "pda_tips")
end
end
function get_time_elapsed()
return math.floor(get_game_time():diffSec(get_start_time()))
end
function round2(n)
return (math.floor(n * 100) / 100)
end
function roll(val,min,max)
if not (val and min and max) then return end
if val > max then
return min
elseif val < min then
return max
else
return val
end
end
function lerp(a, b, f)
if a and b and f then
return a + f * (b - a)
else
return a or b or 0
end
end
function paw_enabled_check()
if not paw_enabled then
actor_menu.set_msg(1,game.translate_string("st_paw_system_disabled"),5)
end
return paw_enabled
end
function valid_bind(kb)
return (kb ~= nil) and (kb ~= "") and (kb ~= "<!!!>") and (kb ~= -1)
end
function bindtext(kb,km)
local kbt = ""
local kmt = ""
local mt = ""
local vbkb = valid_bind(kb)
local vbkm = km and km > 0
if vbkb then
kbt = DIK_name(kb)
end
if vbkm then
if km and km < 29 then
km = ((km == 1) and 42) or ((km == 2) and 29) or ((km == 3) and 56)
end
if km then
kmt = DIK_name(km)
end
end
if vbkb and vbkm then
mt = "+"
elseif not (vbkb or vbkm) then
kmt = "[UNBOUND]"
mt = ""
kbt = ""
end
return string.format("%s%s%s",kmt,mt,kbt)
end
-- ======================================================================
function safeid(obj)
local id = obj and obj.id
if not id and obj then return end
if type(id) ~= "number" then id = obj:id() end
return id
end
function safename(id)
if not (id and (id > 0)) then return end
local text = ts("st_paw_unknown")
local se_obj = alife_object(id)
if not se_obj then return text end
local name = se_obj:name() or text
local cls = se_obj:clsid()
if pins[id] then
-- If there's a pin for this ID, use its label above all else
text = pins[id].text or pins[id].name
vl("%s (%s) is a pin: %s",name,id,text)
elseif (cls == clsid.script_zone) then
text = ts("st_paw_pin_default_name")
vl("%s (%s) is a script zone: %s",name,id,text)
elseif (cls == clsid.script_restr) then
local _,_,hint = txr_routes.get_route_info(name)
if hint then
text = hint and ts(hint) or text
else
text = ts(name)
end
vl("%s (%s) is a level transition: %s",name,id,text)
elseif (cls == clsid.smart_terrain) then
text = ts("st_"..name.."_name"):gsub("%."," -")
vl("%s (%s) is a smart terrain: %s",name,id,text)
elseif (cls == clsid.online_offline_group_s) then
local comm = se_obj.player_id
local sq = ts("st_paw_squad")
if managed_factions[comm] then
text = ts(comm)
end
text = text.." "..sq
if debuglogs then
text = text..": "..name
end
vl("%s (%s) is NPC squad with faction %s: %s",name,id,comm,text)
elseif IsInvbox(nil,cls) then
local locst = "st_"..name.."_name"
text = ts(locst)
if text == locst then
dl("No localization found for stash %s",locst)
text = ts("st_paw_unknown").." "..ts("st_paw_stash")
if debuglogs then
text = text..": "..name
end
end
vl("%s (%s) is a stash marker: %s",name,id,text)
elseif IsStalker(nil,cls) then
text = se_obj:character_name()
vl("%s (%s) is a stalker: %s",name,id,text)
elseif IsMonster(nil,cls) then
local obj = get_object_by_id(id)
local section = obj and obj:section()
if not section then
return text
end
--[[
Hack adapted from ui_enemy_health.script
This logic acts as a translation layer for
any monster kind/species that doesn't line
up with the name of its encyclopedia entry
--]]
local special_cases = {
dog = "blind_dog",
bird = "crow",
giant = "pseudogiant",
SM_KARLIK = "karlik",
SM_LURKER = "lurker",
SM_POLTER_G = "poltergeist",
SM_PYRO_G = "pyrogeist",
SM_PSEUDO_G = "pseudogeist",
SM_PSYSUCKER = "psysucker",
}
if string.find(section,'rotan') then
text = "rat"
elseif string.find(section,'psy_dog') then
text = "psydog"
else
local what = ini_sys:r_string_ex(section,"kind") or ini_sys:r_string_ex(section,"species") or nil
text = special_cases[what] or what
end
--text = string.lower(ts("encyclopedia_mutants_"..text))
text = ts("encyclopedia_mutants_"..text)
vl("%s (%s) is a creature: %s",name,id,text)
else
text = ts(name)
if text == name then
text = ts(name.."_name")
if text == name.."_name" then
text = name
dl("No localization or other name found for object %s (%s)",name,id)
end
end
end
dl("safename returns %s for %s",text,name)
return text
end
function valid_pda()
local dev = db.actor:item_in_slot(8)
local sec = dev and dev:section()
if dev and pda_defs[sec] then
if pda_defs[sec].show_w then return true end
elseif item_device.device_npc_pda[sec] then
local text = ts("st_paw_npc_pda_equipped")
dotip(text)
else
dl("WARNING: Equipped PDA %s not found in pda_defs - if this is a valid player PDA, add to that table to enable support",sec)
end
return false
end
function blacklisted_object(id)
if not (id and (id > 0)) then return true end
local se_obj = alife_object(id)
if not se_obj then return true end
local cls = se_obj:clsid()
if (cls == clsid.poltergeist_s) or
(cls == clsid.psy_dog_phantom_s) then
return true end
end
function valid_waypoint_target(se_obj)
if not se_obj then return end
if blacklisted_object(se_obj.id) then return end
-- can add other smart types to this logic later if needed
local clsid = se_obj:clsid()
local valid = (valid_clsids[clsid] ~= nil) or
IsStalker(se_obj) or
IsMonster(se_obj) or
isLc(se_obj)
return valid
end
function feature_valid_in_mode(mode)
-- -1: debug mode only
-- 0: disabled
-- 1: enabled in normal view only
-- 2: enabled in cartography mode only
-- 4: enabled in both
return ((mode < 0) and debuglogs) or
((mode == 1) and not cart_mode) or
((mode >= 2) and cart_mode) or
(mode >= 4)
end
function play_sound_for_actor(effect)
local snd = xr_sound.get_safe_sound_object(effect)
snd:play(db.actor, 0, sound_object.s2d)
end
function add_mapspot(text,id,icon)
if not id then return end
icon = icon or default_mapspot
vl("Adding mapspot %s for id %s with icon %s",text,id,icon)
level.map_add_object_spot(id,icon,text)
end
function remove_mapspot(id,icon)
if not (id and icon) then return end
vl("Trying to remove mapspot for id %s with icon %s",id,icon)
level.map_remove_object_spot(id,icon)
level.map_remove_object_spot(id,icon)
return true
end
--[[
-- this isn't working yet, don't enable
function rotate_dynamic_favorites(icon)
if not icon then return end
local mdf = tonumber(max_dynamic_faves) or 0
local faves = 0
if (mdf and (mdf == 0)) then return end
vl("rotate_dynamic_favorites called with new mapspot %s",icon)
if dynamic_faves and (type(dynamic_faves) == "table") then
faves = #dynamic_faves or 0
if faves > 1 then
for i = 1,faves do
if (faves[i] == icon) then
vl("%s already in favorites, ignoring")
return
end
if faves < mdf then
faves = faves + 1
elseif (mdf > 1) and (faves >= mdf) then
for i = 2,faves do
vl("%s was in position %s, shuffling down to %s",dynamic_faves[i],i,i-1)
dynamic_faves[i - 1] = dynamic_faves[i]
end
end
else
dynamic_faves = {}
end
dynamic_faves[faves] = icon
vl("%s added to top of dynamic_faves",icon)
end
--]]
function func_add_mapspot(text,args)
local id = args.id
local icon = args.icon or map_pin_icon
if text == "" or text == nil then
set_pin_persistence(id,false)
set_pingspot_persistence(id,false)
pins[id] = nil
vl("Empty text input returned for pin %s, removing",id)
return end
vl("func_add_mapspot called for id %s | %s | %s",id,icon,text)
pins[id].text = text
add_mapspot(text,id,icon)
end
function func_ren_mapspot(text,args)
local id = args.id
local icon = map_pin_icon
local old_icon = args.icon
if text == "" or text == nil then
vl("Empty text input returned for pin %s, making no change",id)
text = pins[id].text
icon = old_icon
end
pins[id].text = text
vl("func_ren_mapspot called for id %s | %s | %s",id,icon,text)
remove_mapspot(id,old_icon)
add_mapspot(text,id,old_icon)
end
function func_ren_waypoint(text,args)
if text == "" then text = nil end
local field = args.field
if field == "name" then
custom_name = text
elseif field == "desc" then
custom_desc = text
end
end
function pins_exist()
return not is_empty(pins)
end
function waypoint_task_cleanup()
end
function cancel_task()
local id = placed_waypoint and placed_waypoint.id
tm.task_info[task_id] = nil
remove_mapspot(id, waypoint_mapspot)
_ = set_pingspot_persistence(id,false)
waypoint_active = false
placed_waypoint = nil
_ = dl("Waypoint task cancellation complete for %s",id)
waypoint_canceling = false
return true
end
function end_waypoint_task()
waypoint_canceling = true
return cancel_task()
end
function set_current_waypoint(se_obj)
if not se_obj then return end
local id = se_obj.id
set_pingspot_persistence(id)
if waypoint_active then
last_waypoint = placed_waypoint
set_pingspot_persistence(id,false)
end
placed_waypoint = {
id = id,
name = safename(id),
}
waypoint_active = true
waypoint_canceling = false
end
function valid_travel_target(id)
-- To enable interaction with the Fair Fast Travel
-- system addon, allowing fast travel to pins, but
-- only those that we know are within the playable
-- area--ones spawned at a spot where the player was
-- standing at the time.
local pin = pins and pins[id]
local valid = pin and pin.quick
vl("valid_travel_target | id %s is pin: %s | is quick pin %s",pin,valid)
return valid
end
function init_new_pin(id,name,text,icon)
if id and not pins[id] then
icon = icon or default_mapspot
set_pingspot_persistence(id)
pins[id] = {
id = id,
name = name or ts("st_paw_pin_default_name"),
text = text or name,
icon = icon,
hud = false
}
--rotate_dynamic_favorites(icon)
vl("New pin initialized: %s (%s) | %s | %s",pins[id].name,id,icon,text)
end
end
function register_script_zone(...)
local se_obj = alife_create("script_zone",...)
if not se_obj then return end
local id = se_obj.id
local now = time_global()
script_zones[id] = {
id = se_obj.id,
pos = se_obj.position,
created = now,
cleanup = now + ping_lifetime,
}
vl("Script zone %s spawned for ping spot",id)
return se_obj
end
function deregister_script_zone(id)
if not id then return end
local se_obj = alife_object(id)
if not se_obj then return end
alife_release(se_obj)
script_zones[id] = nil
vl("Script zone %s released",id)
end
function generate_scripted_mapspot_at(pos,lv,gv)
if not (pos and lv and gv) then return end
local sz = register_script_zone(pos,gv,lv)
if not sz then return end
return sz.id
end
function generate_scripted_mapspot_on(obj)
if not (obj and obj.id) then return end
local sz = register_script_zone(obj)
if not sz then return end
return sz.id
end
function spawn_pingspot(pid,pos,lv,gv)
if not (pid and pos and lv and gv) then return end
local sz = register_script_zone(pos,gv,lv)
if not sz then return end
local text = ts("st_paw_pingspot")
local id = sz.id
pings[id] = script_zones[id]
pings[id].pid = pid
add_mapspot(text,id,icon_pingspot)
-- vl("Spawned temporary pingspot at %s,%s with id %s",pos.x,pos.z,id)
return id
end
function despawn_pingspot(id)
-- vl("despawn_pingspot(%s): deregistering script zone and nulling ping record",id)
deregister_script_zone(id)
pings[id] = nil
end
function script_zone_cleanup()
local now = get_time_elapsed()
for k,v in pairs(script_zones) do
if v.cleanup and (v.cleanup < now) then
deregister_script_zone(k)
end
end
dl("script_zone_cleanup completed")
return true
end
function temp_pin_cleanup(wipe_all)
local now = get_time_elapsed()
for k,v in pairs(pins) do
if v.cleanup then
local expired = autotags_time_out and (v.cleanup < now)
if expired or wipe_all then
do_waypoint("pn_del",nil,nil,{syscall=true,id=k})
end
end
end
dl("temp_pin_cleanup completed")
return true
end
function set_pin_persistence(id,onoff,lifetime)
if not (id and (id > 0)) then return end
if not pins[id] then return end
if onoff or (onoff == nil) then
pins[id].cleanup = nil
dl("Pin %s given persistence, it will not be subject to cleanup",id)
else
local now = get_time_elapsed()
pins[id].cleanup = now + (lifetime or autotag_lifetime or 0)
dl("Pin %s flagged as temporary, it will be subject to cleanup at %s",id,pins[id].cleanup)
end
return true
end
function set_pingspot_persistence(id,onoff)
if not (id and script_zones[id]) then return end
if onoff or (onoff == nil) then
pings[id] = nil
script_zones[id].cleanup = nil
dl("Temporary pingspot %s given persistence, it will not be subject to cleanup",id)
else
script_zones[id].cleanup = get_time_elapsed()
dl("Persistent pingspot %s flagged as unused, it will be subject to cleanup",id)
end
return true
end
function ping_area_around(obj)
if not obj then return end
local now = time_global()
local pid = tostring(now)
local span = ping_grid_radius or 10 -- radius of grid in squares
local grid = ping_grid_size or 10 -- dimension of grid squares in game units
local rad = span * grid -- actual ping radius in game units
local anch_id = obj.id
local anch_pos = obj.position
local ax = anch_pos.x
local ay = anch_pos.y
local az = anch_pos.z
local gv = obj.m_game_vertex_id
local lv = game_graph():vertex(gv):level_id()
for z = (az-(span*grid)),(az+(span*grid)),grid do
for x = (ax-(span*grid)),(ax+(span*grid)),grid do
local pos = vector():set(x,ay,z)
if distance_2d(pos,anch_pos) <= (rad + 1) then
spawn_pingspot(pid,pos,gv,lv)
end
end
end
if use_ping_snd and snd_ping then play_sound_for_actor(snd_ping) end
CreateTimeEvent("paw_ping_cleanup","ping_"..pid,11,tasks_placeable_waypoints.ping_cleanup,pid)
end
function do_waypoint(act,se_obj,acode,args)
args = args or {}
local syscall = args.syscall and true or false
if (not syscall) and valid_pda() and not (paw_enabled and act) then return end
acode = acode or act
local id,name
if se_obj then
id = se_obj.id
name = safename(id)
elseif args and args.id then
id = args.id
se_obj = alife_object(id) or se_obj
end
local ac = action_codes[acode]
dl("Received waypoint call \"%s\" on target %s (id %s)",act,name,id)
if act == "wp_set" then
if not se_obj then
dl("do_waypoint called with action %s but no valid se_obj!",act)
return end
set_current_waypoint(se_obj)
tm:give_task("task_placeable_waypoint")
waypoint_active = true
elseif act == "wp_mov" then
if not se_obj then
dl("do_waypoint called with action %s but no valid se_obj!",act)
return end
if tm.task_info[task_id] then
local id = se_obj.id
local task = tm.task_info[task_id]
local atsk = db.actor:get_task(task_id, true)
task.target = id
task.current_target = id
atsk:change_map_location(task.spot, id)
level.map_add_object_spot(id, waypoint_mapspot, "")
set_current_waypoint(se_obj)
xr_sound.set_sound_play(AC_ID, "pda_tips")
else
dl("task_info[task_id] is nil!")
end
waypoint_active = true
elseif act == "wp_del" then
end_waypoint_task()
last_waypoint = nil
elseif act == "waypoint_rename" then
dl("Received call to rename waypoint")
local func = ren_wp_functor
local title = ts("ui_mcm_pawsys_pawmenu_waypoint_rename")
local itex = "ui_inGame2_PDA_icon_Secondary_mission"
args.field = "name"
cartography_mode(false)
get_text(title,itex,name,func,args)
elseif act == "waypoint_redesc" then
dl("Received call to redesc waypoint")
local func = ren_wp_functor
local title = ts("ui_mcm_pawsys_pawmenu_waypoint_redesc")
local itex = "ui_inGame2_PDA_icon_Secondary_mission"
args.field = "desc"
cartography_mode(false)
get_text(title,itex,name,func,args)
elseif act == "pn_add" then
dl("Received call to add new pin to map for %s (%s)",name,id)
local func = add_mapspot_functor
local icon = icons[ac.icon] or map_pin_icon
local itex = texture_for_icon(icon)
local title = ts("ui_mcm_pawsys_pawmenu_pn_add")
init_new_pin(id,name,ts("st_paw_pin_default_name"),icon)
cartography_mode(false)
--vl("pin data before input call: %s (%s) % | %",pins[id].name,pins[id].id,pins[id].icon,pins[id].text)
get_text(title,itex,nil,func,pins[id])
elseif act == "pn_ren" then
if not (id and pins[id]) then return end
dl("Received call to rename map pin %s (%s)",name,id)
if not pins[id].locked then
remove_mapspot(id,icon)
local func = ren_mapspot_functor
local icon = pins[id] and pins[id].icon
local itex = texture_for_icon(icon)
local name = pins[id] and pins[id].name
local title = ts("ui_mcm_pawsys_pawmenu_pn_ren")
cartography_mode(false)
get_text(title,itex,name,func,pins[id])
end
elseif act == "pn_del" then
if not (id and pins[id]) then return end
if syscall or not pins[id].locked then
local icon = pins[id] and pins[id].icon
if hud_pin_objs[id] then
vl("Removing pin %s from HUD",id)
get_hud():RemoveDialogToRender(hud_pin_objs[id])
hud_pin_objs[id] = nil
end
vl("Pin %s (%s) deleted",pins[id].text,id)
pins[id] = nil
if icon then remove_mapspot(id,icon) end
end
elseif act == "lock_pin" then
if not (id and pins[id]) then return end
pins[id].locked = true
elseif act == "unlock_pin" then
if not (id and pins[id]) then return end
pins[id].locked = false
elseif act == "pn_clr" then
local wipe_all = args and args.wipe_all
if not is_empty(hud_pin_objs) then
for k,v in pairs(hud_pin_objs) do
if wipe_all or not (pins[k] and pins[k].locked) then
vl("Hiding HUD pin for %s",k)
get_hud():RemoveDialogToRender(hud_pin_objs[k])
hud_pin_objs[k] = nil
end
end
end
for k,v in pairs(pins) do
if wipe_all or not v.locked then
local icon = v.icon
remove_mapspot(k,icon)
pins[k] = nil
end
end
elseif act == "mping" then
ping_area_around(se_obj)
elseif act == "hud_vis_on" then
if not (id and pins[id]) then return end
if syscall or not pins[id].locked then
pins[id].hud = true
show_hud_pin(id)
end
elseif act == "hud_vis_off" then
if not (id and pins[id]) then return end
if syscall or not pins[id].locked then
pins[id].hud = false
hide_hud_pin(id)
end
elseif act == "show_pins" then
show_all_pins(true)
elseif act == "hide_pins" then
show_all_pins(false)
elseif act == "cm_dbg" then
local clsid = se_obj and se_obj:clsid() or "empty"
property_ui:AddItem("[PAW Debug] Mapspot for %s (id %s) has clsid %s)",name,id,clsid)
else
dl("invalid action %s passed to do_waypoint",act)
end
return true
end
function add_context_option(property_ui,act,maptbl)
local ac = action_codes[act]
vl("add_context_option \"%s\" | cart_mode is %s",act,cart_mode)
if ac and ac.enable then
--printf("action %s is enabled",act)
if type(ac.mode) ~= "number" then
dl("WARNING: mode (%s) for action %s was not a number, resetting to 0",ac.mode,act)
ac.mode = 0
end
-- -1: debug mode only
-- 0: disabled
-- 1: enabled in normal view only
-- 2: enabled in cartography mode only
-- 4: enabled in both
--[[
if ((ac.mode < 0) and debuglogs) or
(ac.mode == 1 and not cart_mode) or
((ac.mode >= 2) and cart_mode) or
(ac.mode >= 4) then
-]]
if feature_valid_in_mode(ac.mode) then
local text = ts(ac.text)
vl("add_context_option(%s): Adding context option %s",act,text)
property_ui:AddItem(text)
curr_menu_options[text] = maptbl
end
else
vl("Action %s is missing or not enabled, ignoring",act)
end
end
function nearest_smart_to_actor()
vl("nearest_smart_to_actor")
local levelid = alife():level_id()
local smart_nearest,dist_nearest,name_nearest,result
for name,smart in pairs(SIMBOARD.smarts_by_names) do
if levelid == game_graph():vertex(smart.m_game_vertex_id):level_id() then
-- vl("%s is same map as actor",name)
local dist = smart.position:distance_to(db.actor:position())
if (SIMBOARD.smarts[smart.id]) and (not smart_nearest or (dist < dist_nearest)) then
vl("%s (%s) is nearest so far at %s",name,smart.id,dist)
smart_nearest = smart
dist_nearest = dist
name_nearest = name
end
end
end
if not smart_nearest then
dl("!ERROR! No nearby smarts found to actor, this shouldn't be possible")
return {id=0,name="oops"} end
result = {
smart = smart_nearest,
id = smart_nearest.id or 0,
dist = dist_nearest or 0,
name = name_nearest or "oops",
pos = smart_nearest.position, -- not used yet
}
vl("nearest_smart: %s at %s",name_nearest,dist_nearest)
return result
end
function show_smarts(onoff)
vl("show_smarts: %s",onoff)
local toggle = onoff or false
local levelid = alife():level_id()
local smart_nearest,dist_nearest,name_nearest,result
for name,smart in pairs(SIMBOARD.smarts_by_names) do
if (SIMBOARD.smarts[smart.id]) then
local text = name
local id = smart.id
local icon = smart_terrain_icon
if toggle then
-- vl("Adding temporary smart %s (%s) with icon %s",text,id,icon)
level.map_add_object_spot(id,icon,text)
else
-- vl("Removing temporary smart %s (%s)",text,id)
level.map_remove_object_spot(id,icon)
end
end
end
end
function wp_target_obj()
dl("wp_target_obj called")
local obj = level.get_target_obj()
local id = obj and obj:id()
vl("ID returned by level.get_target_obj is %s | obj exists: %s",id,obj ~= nil)
if id and (id > 0) then
local se_obj = alife_object(id)
if not (se_obj and (allow_non_wp_targets or valid_waypoint_target(se_obj))) then return end
if placed_waypoint then
if id == placed_waypoint.id then
vl("Waypoint is currently active on %s (%s), disabling",se_obj:name(),id)
--do_waypoint("wp_del",se_obj)
toggle_waypoint()
else
vl("Waypoint is currently active, moving to %s (%s)",se_obj:name(),id)
do_waypoint("wp_mov",se_obj)
end
else
vl("No current waypoint, setting to target object %s (%s)",se_obj:name(),id)
do_waypoint("wp_set",se_obj)
end
end
end
function get_smart_target_type(obj)
--printf("get_smart_target_type(%s)",obj and obj:name())
local target_type
if obj then
if IsStalker(obj) then
local acomm = get_actor_true_community()
local comm = obj:character_community() or "stalker"
local is_enemy = game_relations.is_factions_enemies(acomm,comm)
--printf("Targeted object %s (%s) is stalker | actor comm %s | target comm %s | is_enemy %s",obj:character_name(),obj:id(),acomm,comm,is_enemy)
if comm == acomm then
target_type = "human_f"
elseif is_enemy then
target_type = "human_e"
else
target_type = "human_n"
end
elseif IsMonster(obj) then
target_type = "monster"
elseif IsInvbox(obj) then
target_type = "stash"
end
end
return target_type
end
function get_smart_icon_for_obj(obj,mapspot)
local mapspot = mapspot or map_pin_icon
local target_type = get_smart_target_type(obj)
local target = target_type and smart_pins[target_type]
mapspot = (target and icons[target.pin]) or mapspot
vl("get_smart_icon_for_obj %s: best icon for %s was %s",obj and obj:character_name(),target_type,mapspot)
return mapspot
end
function pin_target_obj(mapspot,notip,silent,smart)
vl("pin_target_obj: mapspot %s | notip %s | silent %s | smart %s",mapspot,notip,silent,smart)
local obj = level.get_target_obj()
local id = obj and obj:id()
if id and (id > 0) then
if blacklisted_object(id) then return end
if smart then
mapspot = get_smart_icon_for_obj(obj)
vl("pin_target_obj: pin %s is smart, assigned mapspot %s",id,mapspot)
end
local se_obj = alife_object(id)
if not (se_obj and (allow_non_wp_targets or valid_waypoint_target(se_obj))) then return end
local name = safename(id)
if pins[id] then
if not pins[id].locked then
local hint = pins[id].text or pins[id].name or ts("st_paw_pin_default_name")
local text = string.format(ts("st_paw_pin_removed_from_target"),name,hint)
text = psk(text,text_colors)
do_waypoint("pn_del",se_obj)
dotip(text,5,nil,false)
end
else
local hint = name or ts("st_paw_pin_default_name")
drop_quick_pin(id,hint,mapspot,true,true)
if pin_hud_icon_default and pins[id] then
pins[id].hud = true
show_hud_pin(id)
end
if not silent then
play_sound_for_actor(snd_tag_target)
end
if not notip then
local icon = ts("ui_mcm_lst_"..(mapspot or pawdata.curr_ico_name))
local nxtb = keybinds["ico_next"].text
local prvb = keybinds["ico_prev"].text
local text = string.format(ts("st_paw_pin_added_to_target"),name,icon,nxtb,prvb)
text = psk(text,text_colors)
dotip(text,5,nil,false)
end
end
end
return id
end
function create_smart_pin(notip,silent)
--local mapspot = get_smart_icon_for_obj(obj)
return pin_target_obj(nil,notip,silent,true)
end
function autotag_target(notip,silent)
local id = create_smart_pin(true)
set_pingspot_persistence(id,autotag_persistence)
set_pin_persistence(id,autotag_persistence)
return id
end
function drop_quick_pin(id,force_text,force_pin,no_tip,no_fast_travel)
if not paw_enabled_check() then return end
if next_pin > time_global() then return end
next_pin = time_global() + pin_delay
if not id then
id = generate_scripted_mapspot_on(db.actor)
end
local se_obj = alife_object(id)
if not se_obj then return end
local name = se_obj:name() or "none"
local icon = force_pin or map_pin_icon
local mapspot_text = force_text or ts("st_paw_pin_quick_name")
local tip_text = ts("st_paw_pin_dropped")
init_new_pin(id,name,mapspot_text,icon)
pins[id].quick = not no_fast_travel
dl("calling add_mapspot with %s | %s | %s",mapspot_text,id,icon)
add_mapspot(mapspot_text,id,icon)
if not no_tip then dotip(tip_text,10000) end
end
function toggle_waypoint(force)
dl("toggle_waypoint called")
if (force ~= nil) then
waypoint_active = not force
end
if not waypoint_active then
if last_waypoint then
placed_waypoint = last_waypoint
local se_obj = alife_object(placed_waypoint.id)
if not se_obj then return end
do_waypoint("wp_set",se_obj)
else
dotip("st_paw_no_last_wp")
end
else
last_waypoint = placed_waypoint
end_waypoint_task()
end
end
function cartography_mode(onoff)
if (onoff == nil) then
cart_mode = not cart_mode
else
cart_mode = onoff or false
end
vl ("Setting cart_mode to %s and calling show_smarts to toggle",cart_mode)
show_smarts(cart_shows_smarts and cart_mode)
end
function get_context_menu_options(property_ui,id,maptbl)
empty_table(curr_menu_options)
maptbl = maptbl or {}
if not id then
id = maptbl.object_id
end
if not paw_enabled then return end
local se_obj = id and (id > 0) and alife_object(id)
if not se_obj then return end
local name = se_obj:name() or "undefined"
local clsid = se_obj:clsid() or "unknown"
local id = se_obj.id
local pin = id and pins[id]
vl("get_context_menu_options: %s (%s) | cls %s",name,id,clsid)
add_context_option(property_ui,"mping",maptbl)
if id and valid_waypoint_target(se_obj) then
if pins[id] then
vl("Pin exists")
if pin.locked then
add_context_option(property_ui,"unlock_pin",maptbl)
else
add_context_option(property_ui,"pn_del",maptbl)
add_context_option(property_ui,"pn_ren",maptbl)
if pins[id].hud then
add_context_option(property_ui,"hud_vis_off",maptbl)
else
add_context_option(property_ui,"hud_vis_on",maptbl)
end
add_context_option(property_ui,"lock_pin",maptbl)
end
else
vl("No pin")
add_context_option(property_ui,"pn_add",maptbl)
end
local name = se_obj:name()
if placed_waypoint == nil then
vl("No waypoint")
add_context_option(property_ui,"wp_set",maptbl)
else
local wid = placed_waypoint.id or 0
local sid = se_obj.id or 0
if wid ~= sid then
vl("Waypoint exists")
add_context_option(property_ui,"wp_mov",maptbl)
end
vl("Waypoint exists here")
add_context_option(property_ui,"wp_del",maptbl)
add_context_option(property_ui,"waypoint_rename",maptbl)
add_context_option(property_ui,"waypoint_redesc",maptbl)
end
end
if pins_exist() then
if show_pins then
add_context_option(property_ui,"hide_all_pins",maptbl)
else
add_context_option(property_ui,"show_all_pins",maptbl)
end
end
if not (pin and pin.locked) then
add_context_option(property_ui,"pn_clr",maptbl)
for k,v in pairs(action_codes) do
local can_add = true
if v.act == "pn_add" then
can_add = (pins[id] == nil)
end
local mode = tonumber(v.mode) or 0
if can_add and v.enable and (mode > 0) and not managed_options[k] then
add_context_option(property_ui,k,id,maptbl)
end
end
end
end
function execute_context_menu_option(property_ui,id,level_name,prop)
vl("passed id : %s",id)
id = last_clicked_id
last_clicked_id = nil
vl("execute_context_menu_option called for id %s\n| prop: %s\n| action %s)",id,prop,action[prop])
if not (paw_enabled and id and valid_pda()) then return end
if not (id and (id > 0)) then return end
local se_obj = alife_object(id)
if not se_obj then return end
local ac = action[prop]
if ac then
local act = action_codes[ac].act
do_waypoint(act,se_obj,ac)
end
end
function faction_body_icon(comm)
vl("faction_body_icon(%s) called",comm)
local icon = nil
if comm and managed_factions[comm] then
local bi = body_icon_mode or "badge"
icon = "paw_"..patch_res..sep..comm
vl("faction_body_icon generated from quality %s for faction %s: %s",patch_res,comm,icon)
else
vl("not a valid human faction, returning %s from bodies set",current_body_icon)
end
return icon or current_body_icon
end
function local_set_name(setname)
local name = ts(setname or icon_sets[pawdata.curr_set_name].name)
end
function local_icon_name(iconame)
local name = ts(iconame or "ui_mcm_lst_"..pawdata.curr_ico_name)
end
function icon_for_pin(pin_name)
if icons and pin_name then return icons[pin_name] end
end
function pin_for_icon(icon)
if icon and texture_data and texture_data[icon] then return texture_data[icon].id end
end
function texture_for_icon(icon)
--printf("texture_for_icon(%s) called",icon)
local tex = texture_data and texture_data[icon] and texture_data[icon].t
vl("texture_for_icon(%s) returned %s",icon,tex)
return tex
end
function texture_for_set(setname)
if not setname then return end
local setdata = icon_sets[setname]
if not (setdata and setdata.active_icon) then
dl("WARNING: No active icon found for %s",setname)
return end
vl("active_icon for %s: %s",setname,setdata.active_icon)
local td = texture_data[setdata.active_icon]
vl("texture exists for %s: %s",setdata.active_icon,td ~= nil)
local tex = td.t
vl("texture_for_set(%s) returned %s",setname,tex)
return tex
end
function get_current_texture(set_changed)
return set_changed and texture_for_set(pawdata.curr_set_name) or texture_for_icon(pawdata.curr_ico_name)
end
function set_or_icon(str)
return str and ((str ~= "set") and (str ~= "icon"))
end
function set_icon_index(set)
vl("set_icon_index called for set %s",set)
local ii = set_index[set].ii
return ii
end
function set_icon_index_rev(set)
vl("set_icon_index_rev called for set %s",set)
local ri = set_index[set].ri
return ri
end
function set2ind(skey)
local ind = nil
vl("set2ind called for set %s, %s sets listed",skey,#set_index)
for i=1,#set_index,1 do
local k = set_index[i].id
if k == skey then
ind = i
end
end
vl("set2ind(%s) returned %s",skey,ind)
return ind
end
function curr_set_i()
return pawdata.curr_set_ind
end
function next_set_i()
return roll(curr_set_i() + 1, 1, #set_index)
end
function prev_set_i()
return roll(curr_set_i() - 1, 1, #set_index)
end
function curr_ico_i()
--printf("curr_ico_i returns %s",pawdata.curr_ico_ind)
return pawdata.curr_ico_ind
end
function next_ico_i()
local c_ind = curr_ico_i()
local setdata = icon_sets[pawdata.curr_set_name]
return roll(c_ind + 1, 1, #setdata.ii)
end
function prev_ico_i()
local c_ind = curr_ico_i()
local setdata = icon_sets[pawdata.curr_set_name]
return roll(c_ind - 1, 1, #setdata.ii)
end
function root_patch_name(patch)
--printf("root_patch_name called with mapspot %s",patch)
local name = patch
local pos_paw = string.find(name,"paw_badge_")
local pos_qlt = string.find(name,"hr_")
if pos_paw and pos_qlt then
name = "paw_badge_"..string.sub(patch,pos_qlt+3)
vl("root_patch_name strips quality string from %s to get %s",patch,name)
else
vl("root_patch_name returning %s as-is",patch)
end
return name
end
function active_set(newset)
vl("active_set(%s) called",newset)
if newset and type(newset) == "number" then
pawdata.curr_set_ind = clamp(newset,1,#set_index)
pawdata.curr_set_name = set_index[pawdata.curr_set_ind].id
pawdata.curr_set_data = icon_sets[pawdata.curr_set_name]
current_active_set = pawdata.curr_set_name
local setdata = pawdata.curr_set_data
pawdata.curr_ico_name = setdata.active_icon
pawdata.curr_ico_ind = setdata.ri[root_patch_name(pawdata.curr_ico_name)]
map_pin_icon = pawdata.curr_ico_name
vl("active set is now %s (%s), current icon %s (%s)",pawdata.curr_set_name,pawdata.curr_set_ind,pawdata.curr_ico_name,pawdata.curr_ico_ind)
end
return pawdata.curr_set_name
end
function get_next_icon()
local setdata = pawdata.curr_set_data
return setdata.ii[next_ico_i()]
end
function get_prev_icon()
local setdata = pawdata.curr_set_data
return setdata.ii[prev_ico_i()]
end
function get_active_icon(newico)
if newico and type(newico) == "number" then
vl("Setting new icon to index %s",newico)
local setdata = pawdata.curr_set_data
pawdata.curr_ico_ind = clamp(newico,1,setdata.inum)
pawdata.curr_ico_name = setdata.ii[pawdata.curr_ico_ind]
setdata.active_icon = pawdata.curr_ico_name
map_pin_icon = pawdata.curr_ico_name
end
return pawdata.curr_ico_name
end
function notify_icon_change(newset)
local setdata = pawdata.curr_set_data
local icon = setdata.active_icon
local td = texture_data[icon]
local tex = td.t
local tiptext = ts("st_paw_change_icon").." "..ts("ui_mcm_lst_"..icon)
if newset then
local name = ts(setdata.name)
local settext = string.format(ts("st_paw_change_set"),name)
tiptext = settext .. tiptext
end
dotip(tiptext,2,nil,false,tex)
end
function cycle_icons(cycle_dir,dotip)
--printf("cycle_icons: curr_set: %s (%s)",pawdata.curr_set_name,pawdata.curr_set_ind)
if cycle_dir and type(cycle_dir) ~= "number" then return end
local setdata = pawdata.curr_set_data
local next_ind = roll(pawdata.curr_ico_ind + cycle_dir,1,setdata.inum)
get_active_icon(next_ind)
if use_ui_snd then play_sound_for_actor(snd_cycle_blip) end
if dotip then notify_icon_change() end
end
function cycle_sets(cycle_dir,dotip)
if cycle_dir and type(cycle_dir) ~= "number" then return end
local next_ind = roll(pawdata.curr_set_ind + cycle_dir,1,#set_index)
active_set(next_ind)
if use_ui_snd then play_sound_for_actor(snd_cycle_blip) end
if dotip then notify_icon_change(true) end
end
function icon_cycle(change,val)
if not mwheel_enabled then return end
if change then
icon_cycle_active = val
if not mwheel_notify then return icon_cycle_active end
if icon_cycle_active then
dotip(ts("st_paw_cycling_icons"))
else
dotip(ts("st_paw_mwheel_normal"))
end
end
return icon_cycle_active
end
function set_cycle(change,val)
if not mwheel_enabled then return end
if change then
set_cycle_active = val
if not mwheel_notify then return icon_cycle_active end
if set_cycle_active then
dotip(ts("st_paw_cycling_sets"))
else
dotip(ts("st_paw_mwheel_normal"))
end
end
return set_cycle_active
end
function cycle_items(cycle_dir,item_type,dotip)
if (item_type == nil) then
if icon_cycle() then
item_type = "icon"
elseif set_cycle() then
item_type = "set"
end
end
if not icoset_changed then icoset_changed = {} end
if item_type == "icon" then
icoset_changed.i = true
vl("cycling icons: %s",cycle_dir)
cycle_icons(cycle_dir,dotip)
elseif item_type == "set" then
icoset_changed.s = true
vl("cycling sets: %s",cycle_dir)
cycle_sets(cycle_dir,dotip)
end
end
function show_all_pins(onoff)
show_pins = onoff
if onoff then
vl("Showing all recorded pins")
for id,pin in pairs(pins) do
local icon = pin.icon
local mapspot_text = pin.text
vl("calling add_mapspot with %s | %s | %s",mapspot_text,id,icon)
add_mapspot(mapspot_text,id,icon)
end
else
vl("Hiding stored pins from map")
for id,pin in pairs(pins) do
remove_mapspot(id,pin.icon)
end
end
end
function load_mcm(pawpath,default)
local val = ui_mcm and ui_mcm.get("pawsys/"..pawpath)
if val ~= nil then
return val
else
return default
end
end
function marker_hint_shown(wpin)
local mode = show_marker_hint and tonumber(show_marker_hint[wpin])
local shown = feature_valid_in_mode(mode)
return shown
end
function marker_dist_shown(wpin)
local mode = show_marker_dist and tonumber(show_marker_dist[wpin])
local shown = feature_valid_in_mode(mode)
return shown
end
function mcm_reload_general_settings()
if not ui_mcm then return end
local old_paw_en = paw_enabled
paw_enabled = load_mcm("pawgen/enabled",paw_enabled)
if paw_enabled and not old_paw_en then
on_game_start()
dl("PAW was disabled and now is not, re-registering actor_on_update")
elseif not paw_enabled then
if old_paw_en then
unregister_all_callbacks()
end
printf(logprefix.."Personal Adjustable Waypoint has been disabled, aborting all further action!")
return false end
disable_load_warning = load_mcm("pawgen/disable_load_warning",disable_load_warning)
wp_hud_icon_enabled = load_mcm("pawgen/wp_hud_icon_enabled",wp_hud_icon_enabled)
if wp_hud_icon_enabled then
show_marker_dist["wp"] = tonumber(load_mcm("pawgen/show_dist_wp",show_marker_dist["wp"]))
show_hud_waypoint()
else
hide_hud_waypoint()
end
show_marker_dist["pins"] = tonumber(load_mcm("pawgen/show_dist_pins",show_marker_dist["pins"]))
show_marker_hint["pins"] = tonumber(load_mcm("pawgen/show_hint_pins",show_marker_hint["pins"]))
pin_near_fade_dist = tonumber(load_mcm("pawgen/pin_near_fade_dist",pin_near_fade_dist))
pin_far_fade_dist = tonumber(load_mcm("pawgen/pin_far_fade_dist",pin_far_fade_dist))
pin_far_hide_dist = tonumber(load_mcm("pawgen/pin_far_hide_dist",pin_far_hide_dist))
wp_clear_dist = tonumber(load_mcm("pawgen/wp_clear_dist",wp_clear_dist))
tip_on_icoset_change = load_mcm("pawgen/tip_on_icoset_change",tip_on_icoset_change)
welcome_msg_shown = load_mcm("pawgen/welcome_msg_shown",welcome_msg_shown)
enable_wp_proxcheck = wp_clear_dist and wp_clear_dist > 0
mcm_update_throttle = load_mcm("pawgen/mcm_update_throttle",mcm_update_throttle)
mwheel_poll_interval = load_mcm("pawgen/mwheel_poll_interval",mwheel_poll_interval)
-- These last two options are not in the MCM menu, but can be manually set as overrides
return true
end
function mcm_reload_keybind_table()
if not ui_mcm then return end
for k,v in pairs(keybinds) do
local kb = load_mcm("pawbinds/bind_"..k)
local km = load_mcm("pawbinds/modk_"..k)
local vbkb = valid_bind(kb)
local vbkm = valid_bind(km)
if vbkb then
keybinds[k].bind = kb
end
if vbkm then
keybinds[k].mod = km
end
keybinds[k].text = bindtext(kb,km)
end
end
function mcm_reload_keybind_settings()
cartography_must_hold = not load_mcm("pawbinds/cartmode_toggle",cartography_must_hold)
keybinds[cartmode].hold = cartography_must_hold
cart_shows_smarts = load_mcm("pawbinds/cart_shows_smarts",cart_shows_smarts)
mwheel_notify = load_mcm("pawbinds/mwheel_notify")
cartmode_unfade = load_mcm("pawbinds/cartmode_unfade",cartmode_unfade)
local tmp = ui_mcm.get("pawsys/pawbinds/mwheel_enabled")
if tmp and type(tmp) == "number" then
tmp = (tmp ~= 0)
end
mwheel_enabled = tmp and mwheel_avail
tmp = ui_mcm.get("pawsys/pawbinds/mwheel_override")
if tmp ~= nil then
dl("MCM override found for mwheel_avail (old = %s | new = %s)",mwheel_avail,tmp)
mwheel_avail = tmp
end
tmp = ui_mcm.get("pawsys/pawbinds/right_click_override")
-- if set to true, will force on_map_right_click support on
if tmp ~= nil then
dl("MCM override found for right_click_avail (old = %s | new = %s)",right_click_avail,tmp)
right_click_override= tmp
end
tmp = ui_mcm.get("pawsys/pawgen/disable_mcmups")
if tmp ~= nil then
dl("MCM override found for disable_mcm_updates (old = %s | new = %s)",disable_mcm_updates,tmp)
disable_mcm_updates = tmp
end
tmp = load_mcm("pawgen/pingsound")
if tmp ~= nil then
use_ping_snd = tmp
end
end
function mcm_reload_pin_settings()
patch_res = ui_mcm.get("pawsys/pawpins/patch_res") or "badge"
local bi = ui_mcm.get("pawsys/pawpins/milpda_body_icon")
local pig = ui_mcm.get("pawsys/pawpins/pin_icon_group")
--local mp = ui_mcm.get("pawsys/pawpins/poi_icon_"..(pig or ""))
local cbi = ui_mcm.get("pawsys/pawpins/poi_icon_bodies")
for k,v in pairs(icons) do
if managed_factions[k] then
local newbadge = "paw_"..patch_res..sep..k
vl("MCM: icon for %s was %s, is now %s",k,v,newbadge)
icons[k] = newbadge
end
end
load_active_icoset_data()
local icon = pig and icon_sets[pig] and icon_sets[pig].active_icon or default_mapspot
icon_sets["bodies"].active_icon = icons[cbi]
dl("MCM: body icon mode %s | paw icon %s",bi,cbi)
map_pin_icon = icon
current_active_set = pig
dl("MCM: icon %s | icon set: %s",icon,pig)
local cp = ui_mcm.get("pawsys/pawpins/use_custom_pin_icon") or false
if cp then
custom_pin_icon = true
map_pin_icon = ui_mcm.get("pawsys/pawpins/custom_pin_icon") or map_pin_icon
dl("MCM: Overriding default icon with custom value: %s",map_pin_icon)
else
custom_pin_icon = false
end
if force_icon_override then
dl("MCM: Script hardcoded to override map_pin_icon with %s",force_icon_override)
map_pin_icon = force_icon_override
default_mapspot = force_icon_override
end
if (bi and ish_kill_tracker) then
body_icon_mode = bi
current_body_icon = icons[cbi] or default_mapspot
ish_kill_tracker.use_paw_icon = (bi == "set") or (bi == "fac")
end
widget_enabled = load_mcm("pawhud/widget_enable",widget_enabled)
active_theme = load_mcm("pawhud/active_theme",active_theme)
if not hud_themes[active_theme] then active_theme = "classicauto" end
widget_custom_pos = {
x = load_mcm("pawhud/pos_x"),
y = load_mcm("pawhud/pos_y")
}
widget_use_custom_pos = load_mcm("pawhud/custompos")
widget_hide_delay = (tonumber(load_mcm("pawhud/autohide",widget_hide_delay)) * 1000)
use_ui_snd = load_mcm("pawhud/hudsound")
--[[
local mdf = clamp(tonumber(ui_mcm.get("pawsys/pawpins/fave_recent")),0,10)
if mdf and (mdf ~= max_dynamic_faves) then
local old_mdf = max_dynamic_faves
max_dynamic_faves = mdf
dl("new max_dynamic_faves: %s (was %s)",mdf,old_mdf)
if dynamic_faves and (not is_empty(dynamic_faves)) and
(max_dynamic_faves > 0) then
local df = {}
for i = 1,max_dynamic_faves do
if dynamic_faves[i] then
vl("dynamic_fave[%s]: %s",i,dynamic_faves[i])
df[i] = dynamic_faves[i]
end
end
dynamic_faves = df
end
end
--]]
end
function mcm_reload_reticle_settings()
if ui_mcm then
mark_on_positive_id = load_mcm("pawret/enable_autotag",mark_on_positive_id)
autotag_persistence = load_mcm("pawret/autotag_persistence",autotag_persistence)
autotag_lifetime = load_mcm("pawret/autotag_lifetime",(autotag_lifetime / 6)) * 6
autotags_time_out = (autotag_lifetime or 0) > 0
pin_hud_icon_default = load_mcm("pawret/pin_auto_visible",pin_hud_icon_default)
manual_smart_pins = load_mcm("pawret/manual_smart_pins",manual_smart_pins)
reticle_mode = load_mcm("pawret/reticle_mode",reticle_mode)
reticle_mustzoom = load_mcm("pawret/reticle_mustzoom",reticle_mustzoom)
reticle_color.a = load_mcm("pawret/reticle_alpha",reticle_color.a)
ret_fade_attack_time = load_mcm("pawret/ret_fade_attack_time",ret_fade_attack_time)
ret_fade_decay_time = load_mcm("pawret/ret_fade_decay_time",ret_fade_decay_time)
for k,v in pairs(smart_pins) do
smart_pins[k].pin = load_mcm("pawret/"..k,v.pin)
smart_pins[k].enabled = load_mcm("pawret/"..k.."_enable",v.enabled)
end
-- Load Crook's facid settings, if present
local did = ui_mcm.get("targetID/timeID")
if did ~= nil then
npc_ident.delay_id = did
end
did = ui_mcm.get("targetID/deadID")
if did ~= nil then
npc_ident.id_bodies = did
end
npc_ident.id_speed = ui_mcm.get("targetID/speedID") or 0.3
npc_ident.lenience = ui_mcm.get("targetID/targL") or 0.99
end
end
function on_option_change()
if load_failed then return end
-- Sync all MCM values and propagate changes
language = ui_options.curr_localization()
if ui_mcm and not mcm_killswitch then
debuglogs = load_mcm("pawgen/debuglogs",debuglogs)
catsy_paw_mcm.debuglogs = debuglogs
dl("Setting for debuglogs loaded from MCM: %s",debuglogs)
if load_mcm("pawgen/wipe_all") then
ui_mcm.set("pawsys/pawgen/wipe_all",false)
dl("MCM: Wiping all pin data per player request")
local args = {wipe_all=true}
do_waypoint("pn_clr",nil,nil,args)
end
mcm_reload_general_settings()
if not paw_enabled then
return
end
mcm_reload_keybind_table()
mcm_reload_keybind_settings()
mcm_reload_pin_settings()
mcm_reload_reticle_settings()
for k,v in pairs(action_codes) do
local m = load_mcm("pawmenu/"..k)
if m ~= nil then
action_codes[k].mode = tonumber(m) or 0
vl("MCM: Loading action code mode %s for %s",m,k)
else
vl("MCM: No value found for %s, leaving unchanged",k)
end
end
ui_mcm.set("pawsys/pawgen/mcm_ver",script_version)
end
if ish_kill_tracker and item_milpda then ish_kill_tracker.on_option_change() end
active_set(icon_sets[current_active_set].i)
if db.actor and get_hud() then
unsquish_ratio = (device().height / device().width) / (768 / 1024)
reset_indicator()
end
end
function load_active_icoset_data()
if not ui_mcm then return end
vl("Loading MCM values for sets and icons")
local path = "pawsys/pawpins/poi_icon_"
for k,v in pairs(icon_sets) do
local val = ui_mcm.get(path..k)
local icon = icons[val]
--printf("loading "..path..k..": %s | icons %s | icons[%s]: %s",val,icons,val,icons[val])
vl("Loading active icon for %s: %s = %s",k,val,icon)
icon_sets[k].default = val
icon_sets[k].active_icon = icon
end
local setname = ui_mcm.get("pawsys/pawpins/pin_icon_group")
if set_index[setname] then
active_set(set_index[setname])
end
end
function update_mcm_icoset_data()
if not ui_mcm then return end
if icoset_changed then
vl("Updating MCM settings after set/icon change",icoset_changed)
local curr_set = pawdata.curr_set_name
if icoset_changed and icoset_changed.s then
ui_mcm.set("pawsys/pawpins/pin_icon_group",curr_set)
end
local path = "pawsys/pawpins/poi_icon_"..curr_set
local active_pin = pin_for_icon(icon_sets[curr_set].active_icon)
ui_mcm.set(path,active_pin)
end
icoset_changed = nil
end
function check_waypoint_proximity(id)
local se_obj = id and alife_object(id)
if (not se_obj) or not (se_obj and se_obj.online) then return end
local szpos = se_obj.position
local dist = szpos:distance_to(db.actor:position()) or 0
if dist <= wp_clear_dist then
toggle_waypoint(false)
end
end
function tick()
if not paw_enabled then
unregister_all_callbacks()
return
end
local now = time_global()
if enable_wp_proxcheck and waypoint_active and (next_wp_proxcheck <= now) then
next_wp_proxcheck = now + wp_proxcheck_interval
check_waypoint_proximity(placed_waypoint and placed_waypoint.id)
end
if (next_gc_check <= now) then
next_gc_check = now + garbcollect_interval
script_zone_cleanup()
temp_pin_cleanup()
end
if disable_mcm_updates then return end
if (next_mcm_update > now) or not icoset_changed then return end
next_mcm_update = now + mcm_update_throttle
update_mcm_icoset_data()
end
function paw_welcome_message()
if welcome_msg_shown then return true end
vl("Welcome message not shown yet, displaying to player")
welcome_msg_shown = true
if ui_mcm then
ui_mcm.set("pawsys/pawgen/welcome_msg_shown",true)
end
local player_name = alife():actor():character_name()
local kb = keybinds
toggle_bind = kb["wptoggle"].bind
toggle_mod = kb["wptoggle"].mod
quickpin_bind = kb["quickpin"].bind
quickpin_mod = kb["quickpin"].mod
cart_mode_hold = kb["cartmode"].bind
cart_mode_mod = kb["cartmode"].mod
local toggle_btxt = bindtext(toggle_bind,toggle_mod)
local quickpin_btxt = bindtext(quickpin_bind,quickpin_mod)
local cartmode_btxt = bindtext(cart_mode_hold,cart_mode_mod)
local welcome_msg = string.format(ts("st_pawsys_welcome_msg"),toggle_btxt,quickpin_btxt,cartmode_btxt,player_name)
welcome_msg = psk(welcome_msg,text_colors)
dotip(welcome_msg,10000)
return true
end
-- ======================================================================
-- UI ELEMENTS
-- ======================================================================
-- ======================================================================
--[[ TEXT INPUT WINDOW
Originally adapted from parts of the vanilla backpack stash script
A more generic, parameterized version used to ship with PAW as its
own mod (ui_get_input.script)
-- ====================================================================]]
InputGUI = nil
function get_text(title_txt,icon_tex,prepop,func,args)
dl("get_text called:\nTitle: %s\nIcon: %s\nExisting text: %s",title_txt,icon_tex,prepop)
tex = texdef or tex_defs
args = args or {}
if not InputGUI then
InputGUI = UITextInputWnd(title_txt,icon_tex,prepop,func,args)
end
if InputGUI and not InputGUI:IsShown() then
InputGUI:Reset(title_txt,icon_tex,prepop,func,args)
InputGUI:ShowDialog(true)
Register_UI("UITextInputWnd","get_text")
end
end
-- ======================================================================
class "UITextInputWnd" (CUIScriptWnd)
function UITextInputWnd:__init(title_txt,icon_tex,prepop,func,args) super()
vl("UITextInputWnd:__init:\nTitle: %s\nIcon: %s\nExisting text: %s",title_txt,icon_tex,prepop)
self.title_txt = title_txt
self.icon_tex = icon_tex
self.prepop = prepop
self.func = func
self.args = args
self:InitControls()
self:InitCallBacks()
end
function UITextInputWnd:__finalize()
end
function UITextInputWnd:InitControls()
self:SetWndRect(Frect():set(0,0,1024,768))
self:SetAutoDelete(true)
local tex = {
file = "paw_input_window.xml",
tbar = "titlebar",
paw = "paw",
icon = "icon",
text = "text",
root = "dialog",
bg = "background",
input = "input",
ok = "btn_ok",
cancel = "btn_cancel",
}
local xml = CScriptXmlInit()
local tbar = tex.root..":"..tex.tbar
local paw = tbar..":"..tex.paw
local icon = tbar..":"..tex.icon
local text = tbar..":"..tex.text
local bg = tex.root..":"..tex.bg
local cancel = tex.root..":"..tex.cancel
local input = tex.root..":"..tex.input
local ok = tex.root..":"..tex.ok
xml:ParseFile (tex.file)
self.dialog = xml:InitStatic(tex.root, self)
xml:InitStatic(bg, self.dialog)
self.tbar = xml:InitStatic(tbar,self.dialog)
self.paw = xml:InitTextWnd(paw,self.tbar)
self.paw:SetText("PAW".." "..script_version)
self.paw:SetFont(GetFontSmall())
self.icon = xml:InitStatic(icon, self.tbar)
if self.icon_tex then
self.icon:InitTexture(self.icon_tex)
else
dl("Empty icon texture passed to input window")
end
self.text = xml:InitTextWnd(text, self.tbar)
self.text:SetText(self.title_txt)
self.text:SetFont(GetFontGraffiti22Russian())
self.input = xml:InitEditBox(input,self.dialog)
self:Register(self.input,"fld_input")
self.input:SetText(self.prepop)
local btn = xml:Init3tButton(cancel, self.dialog)
self:Register(btn,tex.cancel)
btn = xml:Init3tButton(ok, self.dialog)
self:Register(btn,tex.ok)
end
function UITextInputWnd:InitCallBacks()
self:AddCallback("btn_ok", ui_events.BUTTON_CLICKED, self.OnAccept, self)
self:AddCallback("btn_cancel", ui_events.BUTTON_CLICKED, self.Close, self)
end
function UITextInputWnd:Reset(title_txt,icon_tex,prepop,func,args)
self.icon_tex = icon_tex
self.title_txt = title_txt
self.prepop = prepop
self.func = func
self.args = args
if self.title_txt then
self.text:SetText(self.title_txt)
end
if self.icon_tex then
self.icon:InitTexture(self.icon_tex)
end
self.input:SetText("")
end
function UITextInputWnd:Update()
CUIScriptWnd.Update(self)
end
function UITextInputWnd:OnAccept()
local text_input = self.input:GetText()
if text_input == "" then text_input = nil end
dl("OnAccept: text_input is %s",text_input)
if self.func then
exec(self.func,text_input,self.args)
end
self:Close()
end
function UITextInputWnd:OnKeyboard(dik, keyboard_action)
local res = CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
if (res == false) then
local bind = dik_to_bind(dik)
if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
if dik == DIK_keys.DIK_ESCAPE then
self:Close()
end
end
end
return res
end
function UITextInputWnd:Close()
self:HideDialog()
Unregister_UI("UITextInputWnd")
end
-- ======================================================================
--[[ ICON AND SET HUD INDICATOR
See the hud_themes table and PAW's MCM menu for more info
-- ====================================================================]]
GUI = nil
class "UIPAWIndicator" (CUIScriptWnd)
function UIPAWIndicator:__init() super()
vl("PAW init called, active theme is %s | widget_active %s | widget_enabled %s",active_theme,widget_active,widget_enabled)
if not widget_enabled then return end
self.theme = hud_themes[active_theme]
self:InitControls()
self:DrawIndicator(self.theme)
get_hud():AddDialogToRender(self)
RegisterScriptCallback("actor_on_net_destroy", self)
end
function UIPAWIndicator:__finalize()
end
function UIPAWIndicator:Reset(theme)
vl("UIPAWIndicator:Reset called with theme %s",theme and theme.name)
if not theme then theme = self.theme end
self.dialog:Show(false)
self.dialog = nil
self:InitControls()
self:DrawIndicator(theme)
end
function UIPAWIndicator:Destroy()
get_hud():RemoveDialogToRender(self)
end
function UIPAWIndicator:SetTheme(theme_name)
vl("UIPAWIndicator:SetTheme called with theme_name %s",theme_name)
local theme = hud_themes and hud_themes[theme_name]
if theme then
self.dialog:Show(false)
active_theme = theme_name or active_theme
self.theme = hud_themes[active_theme]
self:Reset(theme)
end
end
function UIPAWIndicator:actor_on_net_destroy()
get_hud():RemoveDialogToRender(self)
end
function UIPAWIndicator:DrawIndicator(theme)
vl("UIPAWIndicator:DrawIndicator called with theme %s | self %s",theme and theme.name,self.theme)
local pos = theme.pos
if widget_use_custom_pos then pos = widget_custom_pos end
local xml = CScriptXmlInit()
xml:ParseFile(theme.file)
self.dialog = xml:InitStatic(theme.node, self)
self.dialog:SetWndPos(vector2():set(pos.x,pos.y))
if theme.tex then
self.dialog:InitTexture(theme.tex)
end
self.box_curr = xml:InitStatic(theme.node..":curr", self.dialog)
self.box_curr:InitTexture(texture_for_icon(self.icon_curr))
if theme.style ~= "compact" then
self.box_prev = xml:InitStatic(theme.node..":prev", self.dialog)
self.box_prev:InitTexture(texture_for_icon(get_prev_icon()))
self.box_next = xml:InitStatic(theme.node..":next", self.dialog)
self.box_next:InitTexture(texture_for_icon(get_next_icon()))
self.box_head = xml:InitStatic(theme.node..":head",self.dialog)
self.box_text = xml:InitTextWnd(theme.node..":head",self.box_head)
self.box_prev:SetTextureColor(colors.dimmed)
self.box_next:SetTextureColor(colors.dimmed)
end
if theme.style == "full" then
self.box_text:SetText(ts("ui_mcm_lst_pawsys_pg_"..self.set_curr))
self.box_head2 = xml:InitStatic(theme.node..":head2",self.dialog)
self.box_text2 = xml:InitTextWnd(theme.node..":head2",self.box_head2)
self.box_text2:SetText(ts("ui_mcm_lst_"..self.icon_curr))
elseif theme.style == "minimal" then
self.box_text:SetText(ts("ui_mcm_lst_"..self.icon_curr))
end
vl("UIPAWIndicator:DrawIndicator completed")
end
function UIPAWIndicator:InitControls()
self.pda_active = item_device.is_pda_active()
self.icon_curr = get_active_icon()
self.set_curr = pawdata.curr_set_name
vl("UIPAWIndicator:InitControls: active icon is %s",self.icon_curr)
end
function UIPAWIndicator:Update(force)
CUIScriptWnd.Update(self)
self.dialog:Show(false)
if not (widget_enabled and main_hud_shown()) then return end
local theme = self.theme
local pda_changed = false
if self.pda_active ~= item_device.is_pda_active() then
pda_changed = true
end
if item_device.is_pda_active() then
local thm = theme.pda_thm
if thm then
theme = hud_themes[thm]
end
end
if pda_changed then
vl("PDA mode has changed, resetting theme")
self:Reset(theme)
end
if theme.tex then self.dialog:InitTexture(theme.tex) end
local pos = theme.pos
if widget_use_custom_pos then
pos = widget_custom_pos
end
self.dialog:SetWndPos(vector2():set(pos.x,pos.y))
local acticon = pawdata.curr_ico_name
if (self.icon_curr ~= acticon) or (self.set_curr ~= pawdata.curr_set_name) then
vl("UIPAWIndicator:Update - icon/set has changed (new curr icon: %s)",acticon)
self.set_curr = pawdata.curr_set_name
self.icon_curr = acticon
self.box_curr:InitTexture(texture_for_icon(self.icon_curr))
if theme.style ~= "compact" then
self.box_prev:InitTexture(texture_for_icon(get_prev_icon()))
self.box_next:InitTexture(texture_for_icon(get_next_icon()))
end
if theme.style == "full" then
self.box_text:SetText(ts("ui_mcm_lst_pawsys_pg_"..self.set_curr))
self.box_text2:SetText(ts("ui_mcm_lst_"..self.icon_curr))
elseif theme.style == "minimal" then
self.box_text:SetText(ts("ui_mcm_lst_"..self.icon_curr))
end
widget_last_used = time_global()
widget_active = true
end
if widget_hide_delay > 0 then
if time_global() > widget_last_used + widget_hide_delay then widget_active = false end
end
self.dialog:Show(widget_active)
end
function reset_indicator()
vl("Resetting PAW indicator")
if GUI then GUI:Destroy() end
if not widget_enabled then return end
vl("Re-initializing PAW indicator")
GUI = UIPAWIndicator()
end
function show_indicator(tf)
if not (GUI and widget_enabled) then return end
if show_indicator == nil then
widget_active = -widget_active
else
widget_active = (tf == true) or false
end
GUI:Show(widget_active)
end
-- ======================================================================
--[[ WAYPOINT AND PIN HUD ICONS
Some pieces adapted from GT's "HUD Ubisoft Friendly" addon
-- ====================================================================]]
function unsquish_aspect(element)
local w = element:GetWidth()
local h = element:GetHeight()
element:SetWndSize(vector2():set((w*unsquish_ratio),h))
end
function dist_from_crosshair(position)
local toPoint = vector():set(position):sub(device().cam_pos):normalize()
return toPoint:dotproduct(device().cam_dir)
end
--[[
animate_texture(
container,
500,
{512.5,384.5},
{512.5,384.5},
{0,0},
{200, 200},
1,
0,
function() printf("anim_finished") end,
function(x) return x^2 end
)
--]]
function easing_outquint(x)
if not x then return end
return 1 - math.pow(1 - x, 5)
end
function easing_inquint(x)
if not x then return end
return x * x * x * x * x
end
function abort_fade_in()
return HUD_RET.fading_out or not HUD_RET.fading_in
end
function abort_fade_out()
return HUD_RET.fading_in or not HUD_RET.fading_out
end
function animate_texture(element, duration, start_pos, end_pos, start_size, end_size, alpha_start, alpha_end , on_finish_func, anim_curve_modifier_func, r, g, b)
r = r or 255
g = g or 255
b = b or 255
local anim_state = 0
local start_time = time_global()
local end_time = time_global() + duration
local anim_curve_modifier_func = anim_curve_modifier_func or function(x) return x end
CreateTimeEvent("animate_texture", time_global(), 0, function()
local x = anim_curve_modifier_func(anim_state)
local pos_x = lerp(start_pos.x, end_pos.x, x)
local pos_y = lerp(start_pos.y, end_pos.y, x)
local size_x = lerp(start_size.x, end_size.x, x)
local size_y = lerp(start_size.y, end_size.y, x)
local alpha = lerp(alpha_start, alpha_end, x)
if anim_state == 1 then
if on_finish_func then on_finish_func() end
return true
end
anim_state = math.min(1, normalize(time_global(), start_time, end_time))
end)
end
local active_anims = {}
function anim_fade(box,alpha_start,alpha_end,duration,event_id,instanced, r, g, b, anim_curve_modifier_func, on_finish_func,abort_func)
if not box then return end
local fade_dir = (alpha_start > alpha_end) and "out" or "in"
dl("anim_fade: start %s end %s dur %s | event_id %s | instanced %s ",alpha_start,alpha_end, duration,event_id,instanced)
if (event_id == nil) then event_id = "anim_fade" end
local anim_id = event_id.."_"..tostring(time_global())
r = r or 255
g = g or 255
b = b or 255
if active_anims[event_id] then
alpha_start = active_anims[event_id].a
dl("Repeat invocation of non-instanced fade event, aborting fade-%s and starting new at current alpha (%s)",fade_dir,alpha_start)
active_anims[event_id] = nil
if on_finish_func then on_finish_func(box,event_id,alpha_end,r,g,b) end
--anim_fade(box,alpha_start,alpha_end,duration,event_id,instanced, r, g, b, anim_curve_modifier_func, on_finish_func,abort_func)
--return true
end
local anim_state = 0
local start_time= time_global()
local end_time = time_global() + duration
local anim_curve_modifier_func = anim_curve_modifier_func or function(x) return x end
active_anims[event_id] = {
box = box,
instanced = instanced and true or false,
anim_id = anim_id,
start_time = start_time,
end_time = end_time,
active = true,
a_start = alpha_start,
a_end = alpha_end,
a = alpha_start,
r = r,
g = g,
b = b,
}
--vl("Creating fade-%s time event %s | %s",fade_dir,event_id,anim_id)
CreateTimeEvent("animate_fade"..event_id..anim_id, time_global(), 0, function()
local abort = abort_func and abort_func()
if abort then
vl("Received abort for %s",event_id)
anim_state = 1
else
local anim = active_anims[event_id]
if anim then
local x = anim_curve_modifier_func(anim_state)
local alpha = lerp(alpha_start, alpha_end, x)
anim.a,anim.r,anim.g,anim.b = alpha,r,g,b
box:SetTextureColor(GetARGB(alpha,r,g,b))
--vl("Fade-%s time event: setting transparency for %s to %s at time %s",fade_dir,event_id,alpha,time_global())
else
vl("Event data for %s has cleared, aborting",event_id)
anim_state = 1
end
end
if (anim_state == 1) then
vl("Fade-%s animation %s completed at %s",fade_dir,event_id,time_global())
if on_finish_func then on_finish_func(box,event_id,alpha_end,r,g,b) end
return true
end
anim_state = math.min(1, normalize(time_global(), start_time, end_time))
end)
end
function fade_by_dist(element,dist,min,mid,max,alpha,r,g,b,mode)
-- 0 : fade Static element
-- 1 : fade text in Static
-- 2 : fade text in TextWnd
alpha = alpha or 255
r = r or 255
g = g or 255
b = b or 255
if not (cart_mode and cartmode_unfade) then
if (dist <= min) then
if dist < min then
alpha = alpha * (dist / min)
end
elseif dist > mid then
if (mid > 0) then
if (dist > (mid + max)) then
alpha = 0
elseif dist > mid then
alpha = alpha * (1 - ((dist - mid) / max))
end
end
end
end
if mode == 1 then
element:TextControl():SetTextColor(GetARGB(alpha,r,g,b))
elseif mode == 2 then
element:SetTextColor(GetARGB(alpha,r,g,b))
else
element:SetTextureColor(GetARGB(alpha,r,g,b))
end
end
function scale_by_dist(element,dist,w,h,min,mid,max,unsquish)
--printf("scale_by_dist for %sx%s element at dist %s | unsquish %s | %s - %s - %s ",w,h,dist,unsquish,min,mid,max)
dist = clamp(dist,0,max)
local nw = w
local nh = h
if (dist <= min) then
nw = w + ((1 - (dist / min)) * (w * 0.5))
nh = h + ((1 - (dist / min)) * (h * 0.5))
elseif dist > mid then
nw = w - (((dist - mid) / (max - mid)) * (w * 0.4)) -- trying 0.4 rather than 0.3 to bring down min size
nh = h - (((dist - mid) / (max - mid)) * (h * 0.4))
end
if unsquish then
nw = nw * unsquish_ratio
end
--printf("scale_by_dist for %sx%s element:\nw %s -> nw %s | h %s -> nh %s | ratio: %s",element:GetWidth(),element:GetHeight(),w,nw,h,nh,unsquish_ratio)
return nw,nh
end
local function zoom_delay_over() zoom_delay = false end
local zoom_delay = false
local last_zoom_coef = 1
local last_zoom_state = false
function get_current_zoom_coef()
if zoom_delay then return last_zoom_coef end
local obj = db.actor:active_item()
if (obj and IsWeapon(obj)) then
local wpn = obj:cast_Weapon()
end
local is_zoomed = axr_main.binoc_is_zoomed or (wpn and wpn:IsZoomed())
if is_zoomed ~= last_zoom_state then
last_zoom_state = is_zoomed
zoom_delay = true
CreateTimeEvent("zoomdelay","paw_zoom"..tostring(time_global()),0.25,tasks_placeable_waypoints.zoom_delay_over)
return last_zoom_coef
end
local fov = device().fov
local zoom_coef = fov / 85
last_zoom_coef = zoom_coef
return zoom_coef
end
function get_screen_coords(id)
local pos = nil
local is_npc,is_place
local obj = db.storage[id] and db.storage[id].object or level.object_by_id(id)
local se_obj = alife_object(id)
--printf("<PAW> get_screen_coords: id %s -> obj %s (%s) | se_obj %s",id,obj and obj:name(),obj and obj:id(),se_obj and se_obj:name())
if not (obj or se_obj) then
local is_wp = (id == (placed_waypoint and placed_waypoint.id)) and (not waypoint_canceling) and end_waypoint_task(id)
local pin = pins[id] and do_waypoint("pn_del",se_obj,nil,{syscall=true,id=id})
local exit = (is_wp and " canceling waypoint and") or (pin and " clearing pin and") or ""
dl("get_screen_coords: no valid game or server object could be found for %s,%s exiting",id,exit)
return
end
if (obj and IsStalker(obj) or IsMonster(obj)) then
pos = obj and utils_obj.safe_bone_pos(obj,"bip01_head")
is_npc = true
--printf("<PAW> Pinned object %s is an npc",id)
elseif (se_obj:clsid() == clsid.smart_terrain) or (se_obj:clsid() == clsid.script_zone) then
pos = se_obj.position
is_place = true
--printf("<PAW> Pinned object %s is a place",id)
elseif (obj and (type(obj.position) == "function")) then
local tmp = obj:position()
pos = tmp and vector():set(tmp.x,tmp.y,tmp.z)
end
if (pos == nil) then
if not (se_obj and se_obj.online) then
return nil
elseif (se_obj:clsid() == clsid.online_offline_group_s) then
local i = se_obj:commander_id()
local npc = i and db.storage[i] and db.storage[i].object or level.object_by_id(i)
pos = npc and utils_obj.safe_bone_pos(npc,"bip01_head")
is_npc = true
--printf("<PAW> Pinned object %s is an npc squad",id)
else
local tmp = se_obj and se_obj.position
pos = tmp and vector():set(tmp.x,tmp.y,tmp.z)
end
end
if not ((pos and pos.x) and (pos and pos.x) and (pos and pos.z)) then
dl("WARNING: unable to get valid pos for %s",id)
return nil
end
--printf("<PAW> pos found for id %s: %s,%s,%s",id,pos and pos.x,pos and pos.y,pos and pos.z)
local dist = db.actor:position():distance_to(pos)
local true_dist = dist -- store this
local min_dist = 0 -- distance floor for proximity adjustment range
local near_dist = 15 -- max dist where proximity adjustment range begins
local mid_dist = 15 -- dist at which far adjustments begin
local max_dist = 60 -- distance ceiling for the far adjustment range
local near_adj = 0 -- max icon vertical position change from proximity
local far_adj = 1.75 -- max icon vertical position change from distance
local vert_adj = 0 -- icon position above object will be adjusted by this
if is_place then
near_adj = 1.2 -- location spots are usually on or in the ground
elseif is_npc then
min_dist = 2 -- to allow for npc bounding box
near_adj = 0.45 -- max increase in height above npc head from proximity < 12m
end
local vert_adj = near_adj
local dist_prog = 0 -- 0 to 1 progress from beginning to end of adjustment range
local dist_coef = 1 -- 0 to 1 multiplier based on distance
dist = clamp(dist,min_dist,max_dist)
if (dist <= near_dist) then
dist_prog = ((near_dist - min_dist) - (dist - min_dist)) * 0.1
dist_coef = math.abs(math.sqrt(math.pow(dist_prog, 2)))
vert_adj = near_adj - (near_adj * dist_coef)
elseif (dist > mid_dist) then
local far_range = max_dist - mid_dist
local far_dist = dist - mid_dist
if dist >= max_dist then
dist_prog = 1
else
dist_prog = (far_dist / far_range)
end
dist_coef = dist_prog -- linear
--dist_coef = math.cos(((1 - dist_prog) * math.pi) / 2) -- eastInSine - this one works ok
--dist_coef = math.sin((dist_prog * math.pi) / 2) -- easeOutSine
--dist_coef = math.abs(math.sqrt(math.pow(dist_prog, 2)))
vert_adj = near_adj + (dist_coef * far_adj)
end
--local zoom_coef = get_current_fov_coef() or 1
local zoom_coef = (device().fov / 85)
pos.y = pos.y + 0.575 + (vert_adj * zoom_coef)
--printf("* Actor is %s from %s (near_dist %s | max_dist %s\n* dist_prog %s | dist_coef %s\n| Final vertical adjustment: %s",true_dist,id,near_dist,max_dist,dist_prog,dist_coef,vert_adj)
local vec = pos and game.world2ui(vector():set(pos.x,pos.y,pos.z),false)
--printf("final vec for %s: %s,%s",id,vec and vec.x,vec and vec.y)
if (vec and (vec.x ~= -9999) and (vec.y ~= 0)) then
return vec
end
return nil
end
-- ======================================================================
HUD_MT = nil
function show_hud_waypoint()
if not db.actor then return end
if (HUD_MT == nil) then
HUD_MT = UIPAWHUDWaypoint()
get_hud():AddDialogToRender(HUD_MT)
end
end
function hide_hud_waypoint()
if not db.actor then return end
if (HUD_MT ~= nil) then
HUD_MT:ShowDialog(false)
get_hud():RemoveDialogToRender(HUD_MT)
HUD_MT = nil
end
end
function update_hud_waypoint()
if not db.actor then return end
if (HUD_MT ~= nil) then
HUD_MT:Update(true)
end
end
-- ======================================================================
class "UIPAWHUDWaypoint" (CUIScriptWnd)
function UIPAWHUDWaypoint:__init() super()
self:InitControls()
RegisterScriptCallback("actor_on_net_destroy", self)
end
function UIPAWHUDWaypoint:__finalize()
end
function UIPAWHUDWaypoint:InitControls()
self:SetAutoDelete(true)
self.xml = CScriptXmlInit()
self.xml:ParseFile("paw_hud_wp_icon.xml")
self.marker = self.xml:InitStatic("marker",self)
self.dist = self.xml:InitStatic("marker:distance_sh:distance",self)
self.dist:TextControl():SetTextColor( GetARGB(255,230,230,230) )
self.dist:TextControl():SetFont(GetFontSmall())
self.dist_sh = self.xml:InitStatic("marker:distance_sh",self.dist)
self.dist_sh:TextControl():SetTextColor( GetARGB(255,0,0,0) )
self.dist_sh:TextControl():SetFont(GetFontSmall())
self.dist_sh:SetWndPos(vector2():set(1,1))
self.wref = self.xml:InitStatic("marker:distance_sh:distance",self.dist)
self.wref:TextControl():SetFont(GetFontSmall())
self.wref:Show(false)
end
function UIPAWHUDWaypoint:Destroy()
get_hud():RemoveDialogToRender(self)
end
function UIPAWHUDWaypoint:actor_on_net_destroy()
get_hud():RemoveDialogToRender(self)
end
function UIPAWHUDWaypoint:ShowMarker(onoff)
self.dist:Show(onoff and marker_dist_shown("wp"))
self.dist_sh:Show(onoff and marker_dist_shown("wp"))
self.marker:Show(onoff)
end
function UIPAWHUDWaypoint:Update()
CUIScriptWnd.Update(self)
if waypoint_active and (main_hud_shown() or axr_main.binoc_is_zoomed or axr_main.scoped_weapon_is_zoomed) and not waypoint_canceling then
local vec = placed_waypoint and placed_waypoint.id and get_screen_coords(placed_waypoint.id)
if (vec) then
self.marker:SetWndPos(vec)
local id = placed_waypoint and placed_waypoint.id
local se_obj = id and alife_object(id)
if se_obj then
local dist = db.actor:position():distance_to(se_obj.position)
local h,v = scale_by_dist(self.marker,dist,20,20,50,100,200,true)
self.marker:SetWndSize(vector2():set(h,v))
if marker_dist_shown("wp") then
local pos = self.marker:GetWndPos()
--local dist_txt = string.format("%.1f",dist)..ts("st_paw_meters_abbr")
local dist_txt = string.format("%.1f",dist).."m"
self.dist:Show(false)
self.dist_sh:Show(false)
self.dist:TextControl():SetText(dist_txt)
self.dist_sh:TextControl():SetText(dist_txt)
self.wref:TextControl():SetText(dist_txt)
self.wref:AdjustWidthToText()
local tw = self.wref:GetWidth()
self.dist:SetWndPos(vector2():set(pos.x - (tw / 2) - 1, pos.y + v - 8))
end
self:ShowMarker(se_obj.online and true)
end
return
end
self:ShowMarker(false)
else
self:ShowMarker(false)
end
end
-- ======================================================================
hud_pin_objs = {}
function update_hud_pins()
for k,v in pairs(hud_pin_objs) do
v:Update(true)
end
end
function hide_hud_pin(id)
if not id then return end
if hud_pin_objs[id] then
get_hud():RemoveDialogToRender(hud_pin_objs[id])
hud_pin_objs[id] = nil
end
end
function show_hud_pin(id)
vl("show_hud_pin called for pin ID %s",id)
if hud_pin_objs[id] then
hide_hud_pin(id)
end
hud_pin_objs[id] = UIPAWHUDPin(id)
get_hud():AddDialogToRender(hud_pin_objs[id])
end
function init_hud_pins()
for k,v in pairs(pins) do
local id = v and v.id
if id and v.hud then
local se_obj = alife_object(id)
if se_obj and se_obj.online then
show_hud_pin(id)
end
end
end
end
-- ======================================================================
class "UIPAWHUDPin" (CUIScriptWnd)
function UIPAWHUDPin:__init(id) super()
self.pin_id = id
self.pin_se_obj = alife_object(id)
self:InitControls(id)
RegisterScriptCallback("actor_on_net_destroy", self)
RegisterScriptCallback("on_localization_change", self)
self.med_hint_font = (language == "rus")
end
function UIPAWHUDPin:__finalize()
end
function UIPAWHUDPin:InitControls(id)
self:SetAutoDelete(true)
self.xml = CScriptXmlInit()
self.xml:ParseFile("paw_hud_pin_icon.xml")
self.marker = self.xml:InitStatic("marker",self)
local tex = texture_for_icon(pins[id].icon)
local name = pins[id].text
self.marker:InitTexture(tex)
self.label = self.xml:InitStatic("marker:label",self)
self.label:TextControl():SetTextColor( GetARGB(255,230,230,230) )
self.label_sh = self.xml:InitStatic("marker:label_sh",self.label)
self.label_sh:TextControl():SetTextColor( GetARGB(255,0,0,0) )
self.label_sh:SetWndPos(vector2():set(1,1))
self.lref = self.xml:InitStatic("marker:label",self.label)
self.label:TextControl():SetText(name)
self.label_sh:TextControl():SetText(name)
self.lref:TextControl():SetFont(GetFontSmall())
self.lref:Show(false)
self:on_localization_change()
vl("Adding label %s to pin %s",name,self.pin_id)
self.dist = self.xml:InitStatic("marker:distance",self)
self.dist:TextControl():SetTextColor( GetARGB(255,230,230,230) )
self.dist:TextControl():SetFont(GetFontSmall())
self.dist_sh = self.xml:InitStatic("marker:distance_sh",self.dist)
self.dist_sh:TextControl():SetTextColor( GetARGB(255,0,0,0) )
self.dist_sh:TextControl():SetFont(GetFontSmall())
self.dist_sh:SetWndPos(vector2():set(1,1))
self.wref = self.xml:InitStatic("marker:distance",self.dist)
self.wref:TextControl():SetFont(GetFontSmall())
self.wref:Show(false)
self.last_dist = nil
--local np,dp = self.label:GetWndPos(),self.dist:GetWndPos()
--local nx,ny,dx,dy = np.x,np.y,dp.x,dp.y
--printf("Init: name pos %s,%s | dist pos %s,%s",nx,ny,dx,dy)
end
function UIPAWHUDPin:Destroy()
hide_hud_pin(self.pin_id)
get_hud():RemoveDialogToRender(self)
end
function UIPAWHUDPin:on_localization_change()
self.med_hint_font = (language == "rus")
if self.med_hint_font then
self.label:TextControl():SetFont(GetFontMedium())
self.label_sh:TextControl():SetFont(GetFontMedium())
self.lref:TextControl():SetFont(GetFontMedium())
else
self.label:TextControl():SetFont(GetFontSmall())
self.label_sh:TextControl():SetFont(GetFontSmall())
self.lref:TextControl():SetFont(GetFontSmall())
end
end
function UIPAWHUDPin:actor_on_net_destroy()
get_hud():RemoveDialogToRender(self)
end
function UIPAWHUDPin:ShowMarker(onoff)
self.dist:Show(onoff and marker_dist_shown("pins"))
self.dist_sh:Show(onoff and marker_dist_shown("pins"))
self.label:Show(onoff and marker_hint_shown("pins"))
self.label_sh:Show(onoff and marker_hint_shown("pins"))
self.marker:Show(onoff)
end
function UIPAWHUDPin:Update()
--printf("UIPAWHUDPin:Update")
if not (pins and pins[self.pin_id]) then
self:Destroy()
end
if item_device.is_pda_active() or not (self.pin_se_obj and self.pin_se_obj.online) then
self.marker:Show(false)
return
end
CUIScriptWnd.Update(self)
if (main_hud_shown() or axr_main.binoc_is_zoomed or axr_main.scoped_weapon_is_zoomed) then
local vec = self.pin_id and get_screen_coords(self.pin_id)
if (vec) then
local pindist = db.actor:position():distance_to(self.pin_se_obj.position)
local szx,szy = scale_by_dist(self.marker,pindist,24,24,10,20,50,true)
self.marker:SetWndPos(vec)
self.marker:SetWndSize(vector2():set(szx,szy))
local f_near = pin_near_fade_dist
local f_far = pin_far_fade_dist
local f_hide = pin_far_hide_dist
fade_by_dist(self.marker, pindist,f_near,f_far,f_hide)
fade_by_dist(self.label, pindist,f_near,f_far,f_hide,255,230,230,230,1)
fade_by_dist(self.label_sh, pindist,f_near,f_far,f_hide,255,0,0,0,1)
fade_by_dist(self.dist, pindist,f_near,f_far,f_hide,255,230,230,230,1)
fade_by_dist(self.dist_sh, pindist,f_near,f_far,f_hide,255,0,0,0,1)
local name = pins[self.pin_id].text
local maxd = 100
local disty = pindist
if pindist >= maxd then
disty = maxd
end
local yadj = (disty / maxd * 6)
local pos = self.marker:GetWndPos()
local nx,ny,dx,dy,tw
if marker_hint_shown("pins") then
self.label:TextControl():SetText(name)
self.label_sh:TextControl():SetText(name)
self.lref:TextControl():SetText(name)
self.lref:AdjustWidthToText()
tw = self.lref:GetWidth()
nx = pos.x - (tw * .5)
ny = pos.y - 22 + yadj
--printf("Container for pin %s is %sx%s",name,nx,ny)
self.label:SetWndPos(vector2():set(nx, ny))
end
if marker_dist_shown("pins") then
self.last_dist = pindist
--local dist_txt = string.format("%.1f",pindist)..ts("st_paw_meters_abbr")
local dist_txt = string.format("%.1f",pindist).."m"
self.dist:TextControl():SetText(dist_txt)
self.dist_sh:TextControl():SetText(dist_txt)
self.wref:TextControl():SetText(dist_txt)
self.wref:AdjustWidthToText()
tw = self.wref:GetWidth()
nx = pos.x - (tw * .5)
ny = pos.y + szy - 11 + yadj
self.dist:SetWndPos(vector2():set(nx, ny))
end
self:ShowMarker(true)
return
end
self:ShowMarker(false)
else
self:ShowMarker(false)
end
end
-- ======================================================================
-- HUD RETICLE (see ui_paw_reticle.script)
-- ======================================================================
HUD_RET = nil
function enable_vo_crosshair()
if not db.actor then return end
if (HUD_RET == nil) then
HUD_RET = ui_paw_reticle and ui_paw_reticle.UIPAWReticle()
if HUD_RET then
get_hud():AddDialogToRender(HUD_RET)
else
printf(logprefix.."ERROR: Unable to load ui_paw_reticle.script or initialize its HUD class")
end
end
end
function disable_vo_crosshair()
if not db.actor then return end
if (HUD_RET ~= nil) then
HUD_RET:ShowDialog(false)
get_hud():RemoveDialogToRender(HUD_RET)
HUD_RET = nil
end
end
function update_vo_crosshair()
if not db.actor then return end
if (HUD_RET ~= nil) then
HUD_RET:Update(true)
end
end
-- ======================================================================
-- CALLBACKS
-- ======================================================================
function npc_on_death_callback(victim,killer)
vl("npc_on_death_callback: %s (%s) | clear_pin_on_death: %s",victim:name(),victim:id(),clear_pin_on_death)
if not clear_pin_on_death then return end
local id = safeid(victim)
if not (id and (id > 0)) then return end
if not (id and pins[id]) then return end
dl("Clearing existing pin for dead body id %s",id)
local se_obj = alife_object(id)
do_waypoint("pn_del",se_obj,nil,{syscall=true})
end
function update_hud_on_show_hide()
update_hud_waypoint()
update_vo_crosshair()
update_hud_pins()
end
local mod_keys = {
[DIK_keys.DIK_LSHIFT] = {pressed=false,code=1},
[DIK_keys.DIK_LCONTROL] = {pressed=false,code=2},
[DIK_keys.DIK_LMENU] = {pressed=false,code=3},
}
local mod_key_codes = {
[1] = DIK_keys.DIK_LSHIFT,
[2] = DIK_keys.DIK_LCONTROL,
[3] = DIK_keys.DIK_LMENU,
}
local function mod_key_pressed(key)
if ui_mcm then
return ui_mcm.get_mod_key(key)
else
return mod_keys and mod_keys[key] and mod_keys[key].pressed
end
end
local function mod_key_held()
return
mod_keys[DIK_keys.DIK_LSHIFT].pressed or
mod_keys[DIK_keys.DIK_LCONTROL].pressed or
mod_keys[DIK_keys.DIK_LMENU].pressed
end
function on_key_press(key)
--if not active_binds[key] then return end
--printf("Monitored keybind %s pressed",key)
local args
if mod_keys[key] then
mod_keys[key].pressed = true
return
end
--local keyname = DIK_name(key)
--vl("on_key_press(%s) : %s\nModifiers held: Shift %s | Ctrl %s | Alt %s",key,keyname,mod_key_pressed(1),mod_key_pressed(2),mod_key_pressed(3))
for k,v in pairs(keybinds) do
if v and v.enabled then
--local bindtxt = bindtext(v.bind,v.mod)
--vl("** Checking keybind %s (bind %s | mod %s | hold %s)",k,bindtxt,v.bind,v.mod,v.hold)
if (key == v.bind) and mod_key_pressed(v.mod) then
--vl("on_key_press(%s): keybind %s matched %s with bind %s and mod %s",key,k,bindtxt,v.bind,mod)
if v.hold then
args = true
end
v.action(args)
return
end
end
end
--vl("on_key_press: no matching keybind for %s",keyname)
end
function on_key_release(key)
local do_action = false
if mod_keys[key] then
mod_keys[key].pressed = false
end
--local keyname = DIK_name(key)
--vl("on_key_release(%s) : %s\nModifiers held: Shift %s | Ctrl %s | Alt %s",key,keyname,mod_key_pressed(1),mod_key_pressed(2),mod_key_pressed(3))
for k,v in pairs(keybinds) do
if v and v.enabled and v.hold then
--local bindtxt = bindtext(v.bind,v.mod)
--vl("** Checking keybind %s: %s (bind %s | mod %s | hold %s)",k,bindtxt,v.bind,v.mod,v.hold)
if (key == v.bind) and mod_key_pressed(v.mod) then
--vl("on_key_release(%s): keybind %s matched %s with bind %s and mod %s",key,k,bindtxt,v.bind,mod)
v.action(false)
return
end
end
end
end
function on_map_right_click(property_ui, map_table)
if not map_table then return end
if map_table.object_id ~= 65535 then return end
local pos = map_table.pos
local se_obj = register_script_zone(pos,map_table.lvid,map_table.gvid)
map_table.object_id = se_obj and se_obj.id
if not se_obj then
dl("<PAW> Right-click capture failed - unable to find or create valid se_obj")
return
else
if mod_key_pressed(1) then
-- LSHIFT : unused
elseif mod_key_pressed(2) then
-- LCTRL : Set/Move Waypoint
if waypoint_active then
do_waypoint("wp_mov",se_obj)
else
do_waypoint("wp_set",se_obj)
end
return
elseif mod_key_pressed(3) then
-- LALT : Add Pin
do_waypoint("pn_add",se_obj)
return
end
dl("<PAW> Passing right-click to get_context_menu_options with id %s and pos %s",se_obj.id,pos)
last_clicked_id = se_obj.id
get_context_menu_options(property_ui,se_obj.id,map_table)
end
end
function map_spot_menu_add_property(property_ui,id)
last_clicked_id = id
get_context_menu_options(property_ui,id)
end
function map_spot_menu_property_clicked(property_ui,id,level_name,prop)
execute_context_menu_option(property_ui,id,level_name,prop)
end
function load_file_data()
dl("<PAW> Loading file data and populating tables")
local attr_tex = "texture"
local minimap = "mini_map"
local levelmap = "level_map"
local ind = 0
xml = CScriptXmlInit()
xml:ParseFile("map_spots.xml")
for k,v in pairs(icons) do
local tex,th,tw
local spot = v
xml:NavigateToRoot()
th = xml:ReadAttribute(spot,0,"height")
tw = xml:ReadAttribute(spot,0,"width")
xml:NavigateToNode(spot,0)
vl("trying to find texture data for %s | %s",k,v)
if xml:NodeExist(attr_tex,0) then
tex = xml:ReadValue(attr_tex,0)
vl("texture node found with value %s",tex)
else
local spotnode = ""
local typ = ""
if xml:NodeExist(minimap) then
spotnode = xml:ReadAttribute(minimap,0,"spot")
typ = minimap
end
if xml:NodeExist(levelmap) then
spotnode = xml:ReadAttribute(levelmap,0,"spot")
typ = levelmap
end
if spotnode ~= "" then
xml:NavigateToRoot()
th = xml:ReadAttribute(spotnode,0,"height")
tw = xml:ReadAttribute(spotnode,0,"width")
vl("ReadAttribute for %s: h %s w %s",v,th,tw)
xml:NavigateToNode(spotnode,0)
tex = xml:ReadValue(attr_tex,0)
vl("%s node found: %s",typ,spotnode)
end
end
if spot == "paw_pin_redpush32" then
if not tex then return false end
end
texture_data[spot] = {
id = k,
t = tex,
h = th,
w = tw,
}
ind = ind + 1
icon_index[ind] = {
id = k,
icon = spot,
data = texture_data[spot],
}
vl("Texture for %s: %s (%sx%s)",spot,tex,tw,th)
end
current_ico_max = ind
for k,v in pairs(texture_data) do
dl("texture_data(%s): tex %s | id %s | %sx%s",k,v.t,v.id,v.w,v.h)
end
ind = 0
for w, _ in pairs(iconset_ltx) do
local iconset_ltx_prefix = "iconset_"
local icons_pos = string.find(w,iconset_ltx_prefix)
if icons_pos == 1 then
local group = string.sub(w,icons_pos+string.len(iconset_ltx_prefix))
local set_cfg = icon_sets_ini:collect_section(w)
local name = set_cfg.name
local def = set_cfg.default
local sec_list = group.."_icons"
ind = ind + 1
icon_sets[group] = {
name = name,
group = group,
default = def,
active_icon = icons[def],
i = ind,
}
local setdata = icon_sets[group]
set_index[ind] = {
id = group,
data = setdata,
}
dl("Found icon set %s with group %s, section %s",setdata.name,group,sec_list)
set_index[ind].ii = {}
setdata.ii = {}
setdata.ri = {}
local iconlist = {}
local il = icon_sets_ini:collect_section(sec_list)
local i = 0
for k,_ in pairs(il) do
i = i + 1
local spot = icons[k]
vl("set %s iconlist: icons[%s] = %s",group,k,spot)
iconlist[spot] = texture_data[spot].t
set_index[ind].ii[i] = spot
setdata.ii[i] = spot
setdata.ri[spot] = i
end
setdata.inum = i
setdata.icons = iconlist
end
end
current_set_max = ind
dl("Loading menu actions from actions_ini")
for w, _ in pairs(actions_ltx) do
action_codes[w] = actions_ini:collect_section(w)
local ac = action_codes[w]
local opt_loc = ts(ac.text)
dl("<PAW> ac.mode: %s | type: %s | as num: %s | >0: %s",ac and ac.mode,ac and type(ac.mode),ac and tonumber(ac.mode),ac and (tonumber(ac.mode) > 0))
ac.mode = clamp(tonumber(ac and ac.mode) or 0,0,4)
ac.enable = (ac.enable == "true")
action[opt_loc] = w
if verbose then
vl("Initializing menu action:\n%s = %s",opt_loc,action[opt_loc])
for k,v in pairs(ac) do vl("%s = %s",k,v) end
end
end
dl("Loading and parsing valid clsids (%s)",size_table(clsids_ltx))
for k,v in pairs(clsids_ltx) do
dl("%s = %s",k,v)
if v == "number" then
local k1 = tonumber(k)
if k1 and k1 > 0 then
vl("Found valid clsid number value: %s",k1)
valid_clsids[tonumber(k1)] = true
end
elseif v == "string" then
if k and clsid[k] then
local k1 = tonumber(clsid[k])
vl("Found valid clsid string value: %s = %s",k,k1)
valid_clsids[tonumber(k1)] = true
end
end
end
table.sort(icons)
table.sort(icon_sets)
catsy_paw_mcm.icon_sets = icon_sets
table.sort(action_codes)
dl(logprefix.."All file data loaded")
--utils_data.print_table(icon_sets)
started = true
local load_set_curr = axr_main.config:r_value("mcm", "pawsys/pawpins/pin_icon_group", {val=0}) or "pins"
local load_pin_curr = axr_main.config:r_value("mcm", "pawsys/pawpins/poi_icon_"..load_set_curr, {val=0})
printf(logprefix.."Personal Adjustable Waypoint started at %s",time_global())
return true
end
function load_state(data)
dl("load_state: Checking for saved PAW data")
local ps = data.pawsys
local pd = data.pawdata
if ps then
vl("load_state: pawsys found, loading")
placed_waypoint = ps.current_waypoint
last_waypoint = ps.last_waypoint
custom_name = ps.custom_name
custom_desc = ps.custom_desc
pins = ps.pins
if placed_waypoint then waypoint_active = true end
welcome_msg_shown = ps.welcome_msg_shown
end
if pd and pd.valid then
dl("load_state: pawdata found, loading")
pawdata = pd
pins = pawdata.pins
else
dl("load_state: pawdata not found, initializing")
pawdata.curr_set_name = "pins"
pawdata.curr_set_data = icon_sets[pawdata.curr_set_name]
local setdata = pawdata.curr_set_data
pawdata.curr_set_data = setdata
pawdata.curr_set_ind = setdata.i
pawdata.curr_ico_name = setdata.active_icon
pawdata.curr_ico_ind = setdata.ri[root_patch_name(setdata.active_icon)]
vl("pawdata: set %s (%s) | icon %s (%s)",pawdata.curr_set_name,pawdata.curr_set_ind,pawdata.curr_ico_name,pawdata.curr_ico_ind)
end
end
function save_state(data)
update_mcm_icoset_data()
dl("save_state: Cleaning up script zones and temp markers")
_ = script_zone_cleanup()
dl("save_state: Saving waypoint and config data")
data.paw_waypoint_info = nil
data.pawsys = {}
data.pawsys.current_waypoint = placed_waypoint
data.pawsys.last_waypoint = last_waypoint
data.pawsys.custom_name = custom_name
data.pawsys.custom_desc = custom_desc
data.pawsys.dynamic_faves = dynamic_faves
data.pawsys.welcome_msg_shown = welcome_msg_shown
data.pawsys.pins = {}
pawdata.valid = true
data.pawdata = pawdata
dl("save_state: Saving player's pins")
data.pawsys.pins = pins
end
function actor_on_update()
tick()
end
function actor_on_first_update()
on_option_change()
if not paw_enabled then
UnregisterScriptCallback("actor_on_update",actor_on_update)
return
end
temp_pin_cleanup(true)
active_set(icon_sets[current_active_set].i)
show_all_pins(true)
if waypoint_active then
local id = placed_waypoint.id
local se_obj = alife_object(id)
if not se_obj then return end
local savelast = last_waypoint
do_waypoint("wp_mov",se_obj)
last_waypoint = savelast
end
reset_indicator()
if wp_hud_icon_enabled then
show_hud_waypoint()
else
hide_hud_waypoint()
end
if reticle_mode > 0 then
enable_vo_crosshair()
else
disable_vo_crosshair()
end
init_hud_pins()
if not welcome_msg_shown then
CreateTimeEvent("paw_welcome_message",0,5,paw_welcome_message)
end
end
function actor_on_net_destroy()
script_zone_cleanup()
hide_hud_waypoint()
empty_table(hud_pin_objs)
end
function on_mouse_wheel(scroll_dir, flags)
if not mwheel_enabled then
UnregisterScriptCallback("on_mouse_wheel",on_mouse_wheel)
return
end
if not (icon_cycle_active or set_cycle_active) then return end
if actor_menu.inventory_opened() then return end
flags.ret_value = false
local now = time_global()
if now < mwheel_next_poll then return end
mwheel_next_poll = now + mwheel_poll_interval
-- local cycle_dir = ((scroll_dir == 0) and -1) or scroll_dir
local cycle_dir = scroll_dir
if cycle_dir == 0 then cycle_dir = -1 end
vl("scroll_dir: %s | cycle_dir: %s",scroll_dir,cycle_dir)
cycle_items(cycle_dir)
end
function unregister_all_callbacks()
UnregisterScriptCallback("actor_on_update",actor_on_update)
UnregisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
UnregisterScriptCallback("actor_on_first_update",actor_on_first_update)
UnregisterScriptCallback("load_state",load_state)
UnregisterScriptCallback("save_state",save_state)
UnregisterScriptCallback("map_spot_menu_add_property",map_spot_menu_add_property)
UnregisterScriptCallback("map_spot_menu_property_clicked",map_spot_menu_property_clicked)
UnregisterScriptCallback("on_key_press",on_key_press)
UnregisterScriptCallback("on_key_release",on_key_release)
UnregisterScriptCallback("GUI_on_show",update_hud_on_show_hide)
UnregisterScriptCallback("GUI_on_hide",update_hud_on_show_hide)
UnregisterScriptCallback("monster_on_death_callback",npc_on_death_callback)
UnregisterScriptCallback("npc_on_death_callback",npc_on_death_callback)
if mwheel_avail then
UnregisterScriptCallback("on_mouse_wheel",on_mouse_wheel)
end
if right_click_avail then
UnregisterScriptCallback("on_map_right_click",on_map_right_click)
end
end
function register_all_callbacks()
RegisterScriptCallback("actor_on_update",actor_on_update)
RegisterScriptCallback("actor_on_net_destroy",actor_on_net_destroy)
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
RegisterScriptCallback("load_state",load_state)
RegisterScriptCallback("save_state",save_state)
RegisterScriptCallback("map_spot_menu_add_property",map_spot_menu_add_property)
RegisterScriptCallback("map_spot_menu_property_clicked",map_spot_menu_property_clicked)
RegisterScriptCallback("on_key_press",on_key_press)
RegisterScriptCallback("on_key_release",on_key_release)
RegisterScriptCallback("GUI_on_show",update_hud_on_show_hide)
RegisterScriptCallback("GUI_on_hide",update_hud_on_show_hide)
RegisterScriptCallback("monster_on_death_callback",npc_on_death_callback)
RegisterScriptCallback("npc_on_death_callback",npc_on_death_callback)
if mwheel_avail then
RegisterScriptCallback("on_mouse_wheel",on_mouse_wheel)
end
if right_click_avail then
RegisterScriptCallback("on_map_right_click",on_map_right_click)
end
end
function on_game_start()
if load_failed then return end
RegisterScriptCallback("on_option_change",on_option_change)
register_all_callbacks()
on_option_change()
end
-- ======================================================================
-- TASK FUNCTORS
-- ======================================================================
task_functor.waypoint_task_target_functor = function (task_id,field,p,tsk)
if not paw_enabled then return end
-- vl("waypoint_task_target_functor called with task_id %s | field %s",task_id,field)
if placed_waypoint == nil then
dl("WARNING: waypoint_task_target_functor called but placed_waypoint is nil, cancelling waypoint task to avoid crash")
end_waypoint_task()
return default_id
end
if (field == "target") then
tsk.target = placed_waypoint.id
tsk.current_target = placed_waypoint.id
return placed_waypoint.id
end
end
task_functor.waypoint_task_text_functor = function (task_id,field,p,tsk)
if not (paw_enabled and placed_waypoint) then return end
-- vl("waypoint_task_text_functor called with task_id %s | field %s",task_id,field)
if (field == "title") then
-- vl("custom_name: %s")
return custom_name or ts("st_paw_placed_waypoint")..(": "..placed_waypoint.name or "")
elseif (field == "descr") then
-- vl("custom_desc: %s")
return custom_desc or ts("st_paw_proceed")
end
end
-- ======================================================================
-- LOAD FILE DATA - KILLSWITCH AND NOTIFY ON FAILURE
-- ======================================================================
if not load_file_data() then
disable_mcm_updates = true
load_failed = true
paw_enabled = false
started = false
_ = unregister_all_callbacks()
if not disable_load_warning then
local tiperr = psk(game.translate_string("st_paw_texture_load_error_tip"),text_colors)
RegisterScriptCallback("actor_on_first_update",
function()
CreateTimeEvent("pawerror",0,5,(
function()
db.actor:give_game_news("PAW System",tiperr,"ui_inGame2_Mesta_evakuatsii",0,15000)
xr_sound.set_sound_play(AC_ID,"pda_tips")
return true
end
))
end
)
end
local errstr = game.translate_string("st_paw_texture_load_error1").."\n\n"..game.translate_string("st_paw_texture_load_error2")
assert(not load_failed, "\n\n"..
"~ ------------------------------------------------------------------------\n"..
errstr.."\n"..
"~ ------------------------------------------------------------------------\n"
)
end
-- ======================================================================