726 lines
28 KiB
Plaintext
726 lines
28 KiB
Plaintext
|
--[[
|
||
|
DYNAMIC ZONE
|
||
|
|
||
|
Original Author(s)
|
||
|
Singustromo <singustromo at disroot.org>
|
||
|
|
||
|
Edited by
|
||
|
|
||
|
License
|
||
|
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
|
||
|
(https://creativecommons.org/licenses/by-nc-sa/4.0)
|
||
|
|
||
|
Synopsis
|
||
|
Simple module responsible for updating the route discovery state based
|
||
|
on being close enough to one of the anomalies spawned or alternatively
|
||
|
discovering them via the binoculars.
|
||
|
Also controls mapspot appearance of transitions and adds a blip for
|
||
|
transitions whose state is unknown (not recently discovered).
|
||
|
--]]
|
||
|
|
||
|
parent = _G["dynamic_zone"]
|
||
|
if not (parent and parent.VERSION and parent.VERSION >= 20241224) then return end
|
||
|
|
||
|
--------------------------
|
||
|
-- Dependencies --
|
||
|
--------------------------
|
||
|
|
||
|
local opt = dynamic_zone_mcm
|
||
|
local utils = dynamic_zone_utils
|
||
|
local debug = dynamic_zone_debug
|
||
|
local route_manager = dynamic_zone_routes
|
||
|
|
||
|
------------------------------------------
|
||
|
-- Global variables & Constants --
|
||
|
------------------------------------------
|
||
|
|
||
|
CONST_LOGGING_PREFIX = "Discovery"
|
||
|
local log = debug.log_register("info", CONST_LOGGING_PREFIX)
|
||
|
|
||
|
get_anomaly_rootpos = dynamic_zone_anomalies.get_rootposition
|
||
|
|
||
|
CONST_DISCOVERY_CHECK_INTERVAL_MS = 5000
|
||
|
|
||
|
CONST_MAPSPOT_BLIP_OFFSET_PIXELS = 13
|
||
|
CONST_MAPSPOT_BLOCKED_TEXTURE = [[dynamic_zone_exit_point_blocked]]
|
||
|
CONST_MAPSPOT_BLOCKED_COLOR = {255, 240, 240, 240}
|
||
|
|
||
|
CONST_BINOCULAR_CHECK_IN_SIGHT_INTERVAL_MS = 250
|
||
|
CONST_BINOCULAR_REFRESHRATE_MS = 125
|
||
|
|
||
|
CONST_BINOCULAR_HUD_ICON_TEXTURE = [[dynamic_zone_blip_unknown_state]]
|
||
|
CONST_BINOCULAR_HUD_ICON_TEXTURE_SIZE = {19, 21}
|
||
|
CONST_BINOCULAR_HUD_ICON_HOVER_METRES = 1.45 *2 -- Stalker height is roughly 1.45
|
||
|
CONST_BINOCULAR_HUD_MAX_VISIBLE_TRANSITIONS = 8
|
||
|
|
||
|
sound_objs = {
|
||
|
discovery = sound_object("dynamic_zone\\block_discovered"),
|
||
|
binoc_in_progress = sound_object("dynamic_zone\\binoc_discovery_in_progress"),
|
||
|
binoc_aborted = sound_object("dynamic_zone\\binoc_discovery_aborted"),
|
||
|
}
|
||
|
|
||
|
BINOCULAR_HUD = nil -- current CUIScriptWnd subclass instance
|
||
|
BINOCULAR_DISCOVERY_IN_PROGRESS = false
|
||
|
DISCOVERABLE_TRANSITIONS_ON_CURRENT_LEVEL = false
|
||
|
|
||
|
-- Temporary Data -> Not saved to m_data
|
||
|
anomalies_to_check = {} -- [anomaly_obj_id] = route_id
|
||
|
discoverable_on_level = {} -- [transition_obj_id] = route_id
|
||
|
mapspot_blips = {} -- [transition_obj_id] = <blip_instance>
|
||
|
|
||
|
---------------------------
|
||
|
-- Globally Used --
|
||
|
---------------------------
|
||
|
|
||
|
-- used on `parent.actor_on_first_update` and in `parent.main_routine(..)`
|
||
|
function clear_eligible_transitions()
|
||
|
discoverable_on_level = {}
|
||
|
end
|
||
|
|
||
|
function no_eligible_transitions()
|
||
|
return (not DISCOVERABLE_TRANSITIONS_ON_CURRENT_LEVEL)
|
||
|
end
|
||
|
|
||
|
function toggle_known_route_state(route_id, notify_on_new_state)
|
||
|
if not utils.valid_type{ caller = "toggle_known_route_state",
|
||
|
"int", route_id } then return end
|
||
|
|
||
|
if (not eligible_for_discovery(route_id)) then
|
||
|
log("Route #%s was already discovered since last emission.", route_id)
|
||
|
return
|
||
|
end
|
||
|
route_manager.set_route_property(route_id, "recently_discovered", true)
|
||
|
|
||
|
if (route_manager.route_known_state_differs(route_id)) then
|
||
|
local discovered = route_manager.get_route_property(route_id, "block_discovered")
|
||
|
log("Player discovered %s route #%s!", ((discovered)
|
||
|
and "previously blocked" or "blocked"), route_id)
|
||
|
|
||
|
route_manager.set_route_property(route_id, "block_discovered", (not discovered))
|
||
|
if (notify_on_new_state) then
|
||
|
inform_player()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for name, attributes in route_manager.iterate_transitions(route_id) do
|
||
|
update_transition_mapspot(name)
|
||
|
end
|
||
|
|
||
|
remove_anomaly_check_for(route_id)
|
||
|
for k, v in pairs(discoverable_on_level) do
|
||
|
if (v == route_id) then
|
||
|
discoverable_on_level[k] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Changes the mapspot texture and hint for the given transition and adds a blip
|
||
|
-- based on the routes flags. We do this via modded exes functionality.
|
||
|
-- Adds mapspot info blip to transitions to indicate those with unknown state.
|
||
|
-- @param transition_name
|
||
|
-- @param blocked (override; optional)
|
||
|
-- @param only_text (optional; used to only update the text)
|
||
|
function update_transition_mapspot(transition_name, blocked, only_text)
|
||
|
if not utils.valid_type{ caller = "update_transition_mapspot",
|
||
|
"str", transition_name } then return end
|
||
|
|
||
|
local id, spot, hint = parent.get_transition_marker_info(transition_name)
|
||
|
if utils.assert_failed(id and spot and hint) then return end
|
||
|
|
||
|
-- Important when e.g. calling this on 'fake_start'
|
||
|
if (not utils.map_spot_exists(id, spot)) then return end
|
||
|
|
||
|
local route = route_manager.get_route_by_transition(transition_name)
|
||
|
if utils.assert_failed(route, "Transition %s got no assigned route!", transition_name) then return end
|
||
|
|
||
|
local mark_as_blocked = blocked
|
||
|
if (mark_as_blocked == nil) then
|
||
|
mark_as_blocked = route.block_discovered
|
||
|
end
|
||
|
|
||
|
local new_hint = get_suited_mapspot_hint(id, hint, mark_as_blocked)
|
||
|
utils.map_spot_change_hint(id, spot, new_hint)
|
||
|
|
||
|
mapspots_blip_update_for(id, spot)
|
||
|
|
||
|
if (only_text) then return end
|
||
|
change_mapspot_marker(id, spot, mark_as_blocked)
|
||
|
end
|
||
|
|
||
|
-- Ignores route flags; To be used before removal of the addon
|
||
|
-- Reverts all map spots to their default look, also removes the blips
|
||
|
function revert_map_spots()
|
||
|
for route_id, route in route_manager.iterate_routes(false) do
|
||
|
if (not route.block_discovered) then goto continue end
|
||
|
for name, _ in route_manager.iterate_transitions(route_id) do
|
||
|
update_transition_mapspot(name, false)
|
||
|
end
|
||
|
|
||
|
:: continue ::
|
||
|
end
|
||
|
|
||
|
mapspots_blips_remove()
|
||
|
end
|
||
|
|
||
|
-----------------------
|
||
|
-- Callbacks --
|
||
|
-----------------------
|
||
|
|
||
|
function on_game_start()
|
||
|
RegisterScriptCallback("dynzone_on_before_execute", dynzone_on_before_execute)
|
||
|
RegisterScriptCallback("dynzone_changed_block_state", dynzone_changed_block_state)
|
||
|
RegisterScriptCallback("actor_on_update", actor_on_throttled_update)
|
||
|
|
||
|
-- Discovery via spawned anomalies
|
||
|
RegisterScriptCallback("actor_on_feeling_anomaly", actor_on_feeling_anomaly)
|
||
|
|
||
|
-- Binocular discovery
|
||
|
RegisterScriptCallback("actor_on_weapon_zoom_in", actor_on_weapon_zoom_in)
|
||
|
RegisterScriptCallback("actor_on_weapon_zoom_out", actor_on_weapon_zoom_out)
|
||
|
RegisterScriptCallback("actor_on_net_destroy", actor_on_weapon_zoom_out) -- Because of hud visor
|
||
|
RegisterScriptCallback("actor_on_before_death", actor_on_weapon_zoom_out)
|
||
|
|
||
|
RegisterScriptCallback("on_option_change", on_option_change)
|
||
|
RegisterScriptCallback("on_game_load", cache_mapmarker_info)
|
||
|
end
|
||
|
|
||
|
-- This is triggered whenever our main routine is triggered, thus after every emission
|
||
|
function dynzone_on_before_execute()
|
||
|
mapspots_blips_show(true)
|
||
|
end
|
||
|
|
||
|
-- We need to clear our data, it's highly probable that it is invalid.
|
||
|
function dynzone_changed_block_state(previous, new)
|
||
|
clear_eligible_transitions()
|
||
|
|
||
|
-- If it has unregistered itself
|
||
|
RegisterScriptCallback("actor_on_update", actor_on_throttled_update)
|
||
|
end
|
||
|
|
||
|
-- Periodically executes as long as it has determined eligible transitions
|
||
|
-- We clear the eligibility table to refresh the list, this way.
|
||
|
actor_on_throttled_update = utils.throttle(CONST_DISCOVERY_CHECK_INTERVAL_MS, true, function()
|
||
|
if (get_eligible_transitions_on_level()) then return end
|
||
|
UnregisterScriptCallback("actor_on_update", actor_on_throttled_update)
|
||
|
end)
|
||
|
|
||
|
-- Toggles state of route discovery if any anomaly was spawned by our script
|
||
|
function actor_on_feeling_anomaly(obj, tbl)
|
||
|
if (utils.is_table_empty(anomalies_to_check)) then return end
|
||
|
|
||
|
local obj_id = obj and obj.id and obj:id()
|
||
|
local route_id = anomalies_to_check[obj_id]
|
||
|
if (not route_id) then return end
|
||
|
|
||
|
log("[%s] Anomaly belongs to route #%s", obj_id, route_id)
|
||
|
toggle_known_route_state(route_id, true)
|
||
|
end
|
||
|
|
||
|
function actor_on_weapon_zoom_in()
|
||
|
if (no_eligible_transitions()) then return end
|
||
|
RegisterScriptCallback("actor_on_update", check_transition_in_sight)
|
||
|
end
|
||
|
|
||
|
function actor_on_weapon_zoom_out()
|
||
|
UnregisterScriptCallback("actor_on_update", check_transition_in_sight)
|
||
|
binocular_hud_off()
|
||
|
end
|
||
|
|
||
|
function on_option_change()
|
||
|
mapspots_blips_show(opt.get("mapspot_blips_enabled"), true)
|
||
|
end
|
||
|
|
||
|
-- The only purpose of this function is to prevent stuttering due to the usage
|
||
|
-- of the utility function map_spot_get_texture_info(..)
|
||
|
-- This works on the basis of the assumption that each spot got the same attributes
|
||
|
local _cached_mapmarker_info_for
|
||
|
function cache_mapmarker_info()
|
||
|
local spots = {}
|
||
|
|
||
|
for route_id, route in route_manager.iterate_routes() do
|
||
|
for name, attributes in route_manager.iterate_transitions(route_id) do
|
||
|
local _, spot, __ = parent.get_transition_marker_info(name)
|
||
|
if (utils.map_spot_get_texture_info(spot)) then
|
||
|
_cached_mapmarker_info_for = spot
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
----------------------------
|
||
|
-- Monkey patches --
|
||
|
----------------------------
|
||
|
|
||
|
SRTeleportMsgBoxOk = ui_sr_teleport.msg_box_ui.OnMsgOk
|
||
|
|
||
|
-- We only update the discovery state, if player takes the transition
|
||
|
-- Executed through ltx script logic (xr_effects)
|
||
|
function ui_sr_teleport.msg_box_ui.OnMsgOk(self)
|
||
|
-- section from teleport_ini (should also be in txr_routes.routes table)
|
||
|
-- e.g. yan_space_restrictor_to_agroprom_1
|
||
|
local route_id = route_manager.get_route_id(self.name)
|
||
|
utils.assert(route_id, "SR-Teleport: '%s' got no assigned route", self.name)
|
||
|
|
||
|
if (not no_eligible_transitions()) then
|
||
|
toggle_known_route_state(route_id)
|
||
|
end
|
||
|
|
||
|
return SRTeleportMsgBoxOk(self)
|
||
|
end
|
||
|
|
||
|
------------------------
|
||
|
-- Main logic --
|
||
|
------------------------
|
||
|
|
||
|
function eligible_for_discovery(route_id)
|
||
|
local route = route_id and route_manager.get_route(route_id)
|
||
|
if utils.assert_failed(route) then return end
|
||
|
|
||
|
return (not route.recently_discovered)
|
||
|
end
|
||
|
|
||
|
function remove_anomaly_check_for(route_id)
|
||
|
if not utils.valid_type{ caller = "remove_anomaly_check_for",
|
||
|
"int", route_id } then return end
|
||
|
|
||
|
for k, v in pairs(anomalies_to_check) do
|
||
|
if (v == route_id) then
|
||
|
anomalies_to_check[k] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Registers eligible transitions for discovery on the current level
|
||
|
-- TODO: Use transition name as key
|
||
|
-- Writes data like this: tbl[transition_ID] = route_id
|
||
|
-- @param force (forces a refresh)
|
||
|
-- @returns true (if eligible transitions exist)
|
||
|
function get_eligible_transitions_on_level(force)
|
||
|
if not (utils.is_table_empty(discoverable_on_level) or force) then return true end
|
||
|
local actor_level = utils.get_mapname()
|
||
|
|
||
|
if (utils.debug_level_loaded()) then
|
||
|
return
|
||
|
elseif (utils.is_underground(actor_level)) then
|
||
|
log("Underground level loaded, will not check for eligible transitions.")
|
||
|
return
|
||
|
end
|
||
|
|
||
|
log("Getting eligible transitions for '%s'", actor_level)
|
||
|
|
||
|
-- We only do this implicitly as this depends on the eligbility list
|
||
|
anomalies_to_check = {}
|
||
|
_g.iempty_table(discoverable_on_level)
|
||
|
|
||
|
for route_id, route in route_manager.iterate_routes(false) do
|
||
|
if (route.recently_discovered) then goto next_route end
|
||
|
|
||
|
for name, attributes in route_manager.iterate_transitions(route_id) do
|
||
|
local id = name and get_story_object_id(name)
|
||
|
local se_obj = id and alife_object(id)
|
||
|
if (not se_obj) then goto continue end
|
||
|
|
||
|
local transition_level = utils.get_mapname(se_obj)
|
||
|
if (actor_level ~= transition_level) then goto continue end
|
||
|
|
||
|
discoverable_on_level[id] = route_id
|
||
|
|
||
|
if (route.blocked == route.block_discovered) then break end
|
||
|
for _, anomaly_id in pairs(attributes.spawned_anomalies) do
|
||
|
anomalies_to_check[anomaly_id] = route_id
|
||
|
end
|
||
|
|
||
|
:: continue ::
|
||
|
end
|
||
|
|
||
|
:: next_route ::
|
||
|
end
|
||
|
|
||
|
if (utils.is_table_empty(discoverable_on_level)) then
|
||
|
log("No discoverable transitions on the current level.")
|
||
|
DISCOVERABLE_TRANSITIONS_ON_CURRENT_LEVEL = false
|
||
|
return
|
||
|
end
|
||
|
|
||
|
log("Discoverable transitions on the current Level:\n%s",
|
||
|
utils_data.print_table(discoverable_on_level, false, true))
|
||
|
|
||
|
DISCOVERABLE_TRANSITIONS_ON_CURRENT_LEVEL = true
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function inform_player(show_msg)
|
||
|
if (not sound_objs.discovery:playing()) then
|
||
|
sound_objs.discovery:play(player, 0, sound_object.s2d)
|
||
|
end
|
||
|
|
||
|
if (not show_msg) then return end
|
||
|
local message = game.translate_string("st_dynzone_discovery_message")
|
||
|
news_manager.send_tip(db.actor, message, 0, "recent_surge",
|
||
|
opt.get("discovery_msg_showtime"))
|
||
|
end
|
||
|
|
||
|
-------------------------
|
||
|
-- Map Markers --
|
||
|
-------------------------
|
||
|
|
||
|
function get_suited_mapspot_hint(id, hint)
|
||
|
if not utils.valid_type{ caller = "get_suited_mapspot_hint",
|
||
|
"int", id, "str", hint } then return end
|
||
|
|
||
|
local route = route_manager.get_route_by_transition(id)
|
||
|
if (utils.assert_failed(route, "ID #%s not a valid transition", id)) then return end
|
||
|
local inactive = route.id and route_manager.route_inactive(route.id)
|
||
|
|
||
|
local gts = game.translate_string
|
||
|
if (not inactive) and (route.blocked or not route.recently_discovered) then
|
||
|
new_hint = string.format("%s (%s)", gts(hint),
|
||
|
(not route.recently_discovered) and gts("st_dynzone_hint_unknown")
|
||
|
or gts("st_dynzone_hint_blocked"))
|
||
|
else
|
||
|
new_hint = gts(hint)
|
||
|
end
|
||
|
|
||
|
-- Route info in mapspot tooltip (debug)
|
||
|
if opt.get("verbose") then
|
||
|
local name = get_object_story_id(id)
|
||
|
new_hint = name and string.format("%s\\n[%s, #%s]", new_hint, name, route.id)
|
||
|
or new_hint
|
||
|
end
|
||
|
|
||
|
return new_hint
|
||
|
end
|
||
|
|
||
|
-- Changes the mapspot marker according to the blocked state of it's transition
|
||
|
-- Properly resets to original mapspot texture and color
|
||
|
-- @param id game object of the transition
|
||
|
-- @param spot default spot texture
|
||
|
-- @param blocked (optional)
|
||
|
function change_mapspot_marker(id, spot, blocked)
|
||
|
if not utils.valid_type{ caller = "mapspot_mark_blocked",
|
||
|
"int", id, "str", spot, "bool", blocked } then return end
|
||
|
|
||
|
if (blocked == nil) then -- get info via route manager
|
||
|
local name = get_object_story_id(id)
|
||
|
local route = name and route_manager.get_route_by_transition(name)
|
||
|
if utils.assert_failed(route) then return end
|
||
|
|
||
|
blocked = route.block_discovered
|
||
|
end
|
||
|
|
||
|
local texture, color = CONST_MAPSPOT_BLOCKED_TEXTURE
|
||
|
if (not blocked) then
|
||
|
local get_spot = _cached_mapmarker_info_for or spot
|
||
|
local texture_info = utils.map_spot_get_texture_info(get_spot)
|
||
|
|
||
|
texture = texture_info and texture_info.texture
|
||
|
color = texture_info and texture_info.color
|
||
|
else
|
||
|
color = GetARGB(_g.unpack(CONST_MAPSPOT_BLOCKED_COLOR))
|
||
|
end
|
||
|
|
||
|
if utils.assert_failed(texture, "Unable to determine a texture for %s", spot) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
log("Changing mapspot texture of (%s, %s) to '%s'", id, spot, texture)
|
||
|
utils.map_spot_change_texture(id, spot, texture)
|
||
|
|
||
|
if (color) then
|
||
|
log("Changing mapspot color of (%s, %s) to %s (pixelvalue)", id, spot, color)
|
||
|
utils.map_spot_change_color(id, spot, color)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Toggles the visibility of a mapspot blip. Creates it, if needed.
|
||
|
function mapspots_blip_update_for(transition_id, spot)
|
||
|
if not utils.valid_type{ caller = "mapspot_blip_update_for",
|
||
|
"int", transition_id, "str", spot } then return end
|
||
|
|
||
|
local name = get_object_story_id(transition_id)
|
||
|
local route = name and route_manager.get_route_by_transition(name)
|
||
|
if (utils.assert_failed(route, "Transition with ID %s has no defined route!", transition_id)) then return end
|
||
|
|
||
|
if (route_manager.route_inactive(route.id)) then return end
|
||
|
local blip = mapspot_blips[transition_id]
|
||
|
if (not blip) then
|
||
|
mapspot_blips[transition_id] = init_mapspot_blip(transition_id, spot)
|
||
|
blip = mapspot_blips[transition_id]
|
||
|
end
|
||
|
|
||
|
if (utils.assert_failed(blip, "Failed to initialize a blip for transition '%s'", transition_id)) then return end
|
||
|
blip.Show(opt.get("mapspot_blips_enabled") and not route.recently_discovered)
|
||
|
end
|
||
|
|
||
|
-- Toggles the display of blips for all mapspots
|
||
|
function mapspots_blips_show(state, conserve_route_flag)
|
||
|
if not utils.valid_type{ caller = "mapspot_blips_show", "bool", state } then return end
|
||
|
|
||
|
for route_id, route in route_manager.iterate_routes() do
|
||
|
for name, attributes in route_manager.iterate_transitions(route_id) do
|
||
|
local id, spot, _ = parent.get_transition_marker_info(name)
|
||
|
if (utils.assert_failed(id and spot)) then return end
|
||
|
|
||
|
if (not conserve_route_flag) then
|
||
|
route.recently_discovered = (not state)
|
||
|
end
|
||
|
|
||
|
mapspots_blip_update_for(id, spot)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Removed all blips and their references, also resets the route flag
|
||
|
-- @param conserve_route_flag (don't alter route flag)
|
||
|
function mapspots_blips_remove(conserve_route_flag)
|
||
|
for id, blip in pairs(mapspot_blips) do
|
||
|
local name = get_object_story_id(id)
|
||
|
local route = name and route_manager.get_route_by_transition(name)
|
||
|
if not (conserve_route_flag or utils.assert_failed(route)) then
|
||
|
route.recently_discovered = false
|
||
|
end
|
||
|
|
||
|
blip.Show(false)
|
||
|
mapspot_blips[id] = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Courtesy of Catspaw (Addon: Personal Adjustable Waypoints)
|
||
|
-- Init's a mapspot blip fir a given mapspot (id, spot)
|
||
|
-- @returns table
|
||
|
function init_mapspot_blip(id, spot, anchor)
|
||
|
if not utils.valid_type{ caller = "init_mapspot_blip",
|
||
|
"int", id, "str", spot } then return end
|
||
|
|
||
|
local anchor = anchor or level.map_get_object_spot_static(id, spot)
|
||
|
local xml = CScriptXmlInit()
|
||
|
local xmlroot = "dzt_ui_modifiers"
|
||
|
xml:ParseFile("dzt_ui_elements.xml")
|
||
|
|
||
|
local blip = {}
|
||
|
blip.id = id
|
||
|
|
||
|
blip.box = xml:InitStatic(xmlroot, anchor) -- CUIScriptWnd
|
||
|
blip.box:SetWndPos(vector2():set(CONST_MAPSPOT_BLIP_OFFSET_PIXELS,
|
||
|
CONST_MAPSPOT_BLIP_OFFSET_PIXELS +3)) -- sets (x,y) but root is top left
|
||
|
|
||
|
blip.blip = xml:InitStatic(xmlroot .. ":blip_unknown", blip.box)
|
||
|
blip.blip:SetWndSize(vector2():set(_g.round(19 *0.8), _g.round(21 *0.8)))
|
||
|
|
||
|
blip.Show = function(tf)
|
||
|
blip.box:Show(tf)
|
||
|
end
|
||
|
|
||
|
return blip
|
||
|
end
|
||
|
|
||
|
-- Removes mapspots added by the old method prior to version 20241119
|
||
|
function mapspots_remove_old()
|
||
|
mapspots_blips_remove(true)
|
||
|
|
||
|
for route_id, route in route_manager.iterate_routes() do
|
||
|
for transition_name, attributes in route_manager.iterate_transitions(route_id) do
|
||
|
local id, spot, hint = parent.get_transition_marker_info(transition_name)
|
||
|
assert(id and spot and hint)
|
||
|
|
||
|
level.map_remove_all_object_spots(id)
|
||
|
if (not utils.map_spot_exists(id, spot)) then -- sanity check
|
||
|
utils.map_spot_add(id, spot, hint)
|
||
|
end
|
||
|
|
||
|
update_transition_mapspot(transition_name)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---------------------------------
|
||
|
-- Binocular Discovery --
|
||
|
---------------------------------
|
||
|
|
||
|
-- TODO: use anomalies.get_spawn_parameters(transition_name).rootpos for distance check
|
||
|
check_transition_in_sight = utils.throttle(CONST_BINOCULAR_CHECK_IN_SIGHT_INTERVAL_MS, false, function()
|
||
|
if (BINOCULAR_DISCOVERY_IN_PROGRESS or no_eligible_transitions()) then return end
|
||
|
|
||
|
local wpn = db.actor:active_item() -- wait, until this is updated
|
||
|
if not (wpn and wpn:section() == "wpn_binoc_inv") then return end
|
||
|
|
||
|
-- TODO: Use a TimedEvent with an ID to delay HUD display whilst being able to cancel prematurely
|
||
|
binocular_hud_on()
|
||
|
|
||
|
local look_pos = utils.get_target_pos()
|
||
|
for transition_id, route_id in pairs(discoverable_on_level) do
|
||
|
if (utils.get_proximity_by_id(transition_id) > opt.get("binoc_discovery_max_distance")) then
|
||
|
goto next_transition
|
||
|
elseif (utils.get_point_proximity_by_id(transition_id, look_pos) > opt.get("binoc_discovery_dist")) then
|
||
|
goto next_transition
|
||
|
end
|
||
|
utils.timed_call(CONST_BINOCULAR_REFRESHRATE_MS, check_binocular_state,
|
||
|
{ id = transition_id, tg = time_global(), route_id = route_id })
|
||
|
|
||
|
log("Forked timed binocular check for transition '%s' (route #%s)",
|
||
|
get_object_story_id(transition_id), route_id)
|
||
|
|
||
|
BINOCULAR_DISCOVERY_IN_PROGRESS = true
|
||
|
play_binocular_sound_cue()
|
||
|
do break end -- Don't check other transitions
|
||
|
|
||
|
:: next_transition ::
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
-- Periodically checks for discovery conditions and aborts accordingly.
|
||
|
function check_binocular_state(data)
|
||
|
local wpn = db.actor:active_item()
|
||
|
local look_pos = utils.get_target_pos()
|
||
|
local hold_time = time_global() - data.tg
|
||
|
local proximity = utils.get_point_proximity_by_id(data.id, look_pos)
|
||
|
|
||
|
-- no need to repeat this over and over.. avoiding goto's aswell
|
||
|
local function abort_discovery()
|
||
|
BINOCULAR_DISCOVERY_IN_PROGRESS = false
|
||
|
play_binocular_sound_cue()
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
if (no_eligible_transitions()) then
|
||
|
log("No eligible transitions left to check")
|
||
|
return abort_discovery()
|
||
|
elseif not (wpn and wpn:section() == "wpn_binoc_inv") then
|
||
|
log("Not holding binoculars. Aborting.")
|
||
|
return abort_discovery()
|
||
|
elseif (not axr_main.weapon_is_zoomed) then
|
||
|
log("Binoculars are not zoomed in")
|
||
|
return abort_discovery()
|
||
|
elseif (proximity > opt.get("binoc_discovery_dist")) then
|
||
|
log("Point proximity is now too large. Aborting.")
|
||
|
return abort_discovery()
|
||
|
elseif (hold_time < opt.get("binoc_discovery_holdtime")) then
|
||
|
log("Target has not been focused long enough (%sms)", hold_time)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
toggle_known_route_state(data.route_id, true)
|
||
|
BINOCULAR_DISCOVERY_IN_PROGRESS = false
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function play_binocular_sound_cue()
|
||
|
if (BINOCULAR_DISCOVERY_IN_PROGRESS) then
|
||
|
sound_objs.binoc_in_progress:play(player, 0, sound_object.s2d)
|
||
|
return
|
||
|
elseif (sound_objs.binoc_in_progress:playing()) then
|
||
|
sound_objs.binoc_in_progress:stop()
|
||
|
end
|
||
|
|
||
|
sound_objs.binoc_aborted:play(player, 0, sound_object.s2d)
|
||
|
end
|
||
|
|
||
|
------------------------
|
||
|
-- Visor HUD --
|
||
|
-- Courtesy of xcvb --
|
||
|
------------------------
|
||
|
|
||
|
function binocular_hud_on()
|
||
|
if BINOCULAR_HUD or (not opt.get("binoc_discovery_hud_enabled")) then return end
|
||
|
BINOCULAR_HUD = binocular_hud()
|
||
|
get_hud():AddDialogToRender(BINOCULAR_HUD)
|
||
|
log("Enabled Binocular-HUD")
|
||
|
end
|
||
|
|
||
|
function binocular_hud_off()
|
||
|
if (not BINOCULAR_HUD) then return end
|
||
|
get_hud():RemoveDialogToRender(BINOCULAR_HUD)
|
||
|
BINOCULAR_HUD = nil
|
||
|
log("Disabled Binocular-HUD")
|
||
|
end
|
||
|
|
||
|
class "binocular_hud" (CUIScriptWnd)
|
||
|
function binocular_hud:__init() super()
|
||
|
self:InitControls()
|
||
|
end
|
||
|
|
||
|
function binocular_hud:__finalize() end
|
||
|
|
||
|
function binocular_hud:InitControls()
|
||
|
self:SetWndRect(Frect():set(0,0,1024,768))
|
||
|
self:SetAutoDelete(true)
|
||
|
|
||
|
self.xml = CScriptXmlInit()
|
||
|
local xml = self.xml
|
||
|
xml:ParseFile("actor_menu.xml")
|
||
|
|
||
|
self.hud_update_timer = 0
|
||
|
|
||
|
self.transitions = {}
|
||
|
self.elements = {}
|
||
|
self.texture = CONST_BINOCULAR_HUD_ICON_TEXTURE
|
||
|
|
||
|
local size = CONST_BINOCULAR_HUD_ICON_TEXTURE_SIZE
|
||
|
for i = 1, CONST_BINOCULAR_HUD_MAX_VISIBLE_TRANSITIONS do
|
||
|
self.elements[i] = xml:InitStatic("helmet_over", self)
|
||
|
self.elements[i]:InitTexture("ui_mmap_stask_last_02")
|
||
|
self.elements[i]:SetWndSize(vector2():set(size[1], size[2]))
|
||
|
self.elements[i]:Show(false)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function binocular_hud:Update()
|
||
|
CUIScriptWnd.Update(self)
|
||
|
self:GatherTransitions()
|
||
|
|
||
|
-- display eligible transitions in close proximity
|
||
|
for i = 1, CONST_BINOCULAR_HUD_MAX_VISIBLE_TRANSITIONS do
|
||
|
self.elements[i]:Show(false)
|
||
|
|
||
|
local transition = self.transitions[i]
|
||
|
if (not transition) then goto continue end
|
||
|
|
||
|
local id = transition.id
|
||
|
|
||
|
-- To make sure that it is updated immediately after discovery
|
||
|
if not (discoverable_on_level[id]) then goto continue end
|
||
|
|
||
|
local obj = level.object_by_id(id)
|
||
|
local name = obj and obj:name()
|
||
|
|
||
|
local pos = name and get_anomaly_rootpos(name)
|
||
|
if (not pos) then
|
||
|
local obj_pos = obj:position()
|
||
|
pos = utils.get_closest_vertex_pos(obj_pos)
|
||
|
or vector():set(obj_pos.x, obj_pos.y, obj_pos.z)
|
||
|
end
|
||
|
|
||
|
-- Note: The position variable should be temporary
|
||
|
pos.y = pos.y + CONST_BINOCULAR_HUD_ICON_HOVER_METRES
|
||
|
|
||
|
local wui_pos = pos and vector2():set(game.world2ui(pos))
|
||
|
if wui_pos then
|
||
|
self.elements[i]:InitTexture(self.texture)
|
||
|
self.elements[i]:SetWndPos(vector2():set(wui_pos.x, wui_pos.y))
|
||
|
self.elements[i]:Show(true)
|
||
|
end
|
||
|
|
||
|
:: continue ::
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function binocular_hud:GatherTransitions()
|
||
|
local tg = time_global()
|
||
|
if (self.hud_update_timer > tg) then return end
|
||
|
self.hud_update_timer = tg + CONST_DISCOVERY_CHECK_INTERVAL_MS
|
||
|
|
||
|
iempty_table(self.transitions)
|
||
|
if (no_eligible_transitions()) then return end
|
||
|
|
||
|
local max_distance = opt.get("binoc_discovery_max_distance")
|
||
|
for transition_id, route_id in pairs(discoverable_on_level) do
|
||
|
local distance = utils.get_proximity_by_id(transition_id)
|
||
|
if (distance > max_distance) then goto continue end
|
||
|
|
||
|
self.transitions[#self.transitions +1] = { id = transition_id, }
|
||
|
|
||
|
:: continue ::
|
||
|
end
|
||
|
end
|