diff --git a/data/json/item_actions.json b/data/json/item_actions.json index 515f5782ff74..7b22887d26cf 100644 --- a/data/json/item_actions.json +++ b/data/json/item_actions.json @@ -959,6 +959,11 @@ "id": "reveal_map", "name": { "str": "Read" } }, + { + "type": "item_action", + "id": "sonar_scan", + "name": { "str": "Scan with sonar" } + }, { "type": "item_action", "id": "change_scent", diff --git a/data/json/items/tool/electronics.json b/data/json/items/tool/electronics.json index bd3fd3c83a59..5059e3b8f889 100644 --- a/data/json/items/tool/electronics.json +++ b/data/json/items/tool/electronics.json @@ -544,6 +544,39 @@ "flags": [ "TRADER_AVOID" ], "magazine_well": "250 ml" }, + { + "id": "sonar_device", + "type": "TOOL", + "category": "electronics", + "name": { "str": "handheld sonar" }, + "description": "A rugged sonar unit with a compact transducer and display. Activate it to ping nearby underwater terrain and sketch what you find onto the overmap.", + "weight": "500 g", + "volume": "500 ml", + "price": "450 USD", + "price_postapoc": "15 USD", + "bashing": 2, + "material": [ "plastic", "steel" ], + "symbol": ";", + "color": "light_blue", + "ammo": "battery", + "charges_per_use": 100, + "use_action": "sonar_scan", + "magazines": [ + [ + "battery", + [ + "light_battery_cell", + "light_plus_battery_cell", + "light_minus_battery_cell", + "light_atomic_battery_cell", + "light_minus_atomic_battery_cell", + "light_minus_disposable_cell", + "light_disposable_cell" + ] + ] + ], + "magazine_well": "250 ml" + }, { "id": "hand_crank_charger", "type": "TOOL", diff --git a/data/json/main.lua b/data/json/main.lua index 508f242fe6f1..7ecb0a54c1b1 100644 --- a/data/json/main.lua +++ b/data/json/main.lua @@ -1,4 +1,7 @@ local voltmeter = require("./voltmeter") +local nyctophobia = require("./nyctophobia") + +local sonar = require("./sonar") local slimepit = require("./slimepit") local artifact_analyzer = require("./artifact_analyzer") local lua_traits = require("./lua_traits") @@ -9,5 +12,7 @@ local storage = game.mod_storage[game.current_mod] mod.voltmeter = voltmeter mod.slimepit = slimepit mod.artifact_analyzer = artifact_analyzer +nyctophobia.register(mod) +sonar.register(mod) mod.lua_traits = lua_traits lua_traits.register(mod) diff --git a/data/json/mapgen/lake_buildings/freshwater_research_station.json b/data/json/mapgen/lake_buildings/freshwater_research_station.json index f4c42e3aaf76..a25846a97549 100644 --- a/data/json/mapgen/lake_buildings/freshwater_research_station.json +++ b/data/json/mapgen/lake_buildings/freshwater_research_station.json @@ -38,6 +38,7 @@ { "item": "rope_30", "x": 9, "y": [ 13, 15 ], "chance": 30 }, { "item": "rope_30", "x": 14, "y": [ 13, 15 ], "chance": 70 }, { "item": "sea_scooter", "x": [ 14, 15 ], "y": [ 11, 16 ], "chance": 50 }, + { "item": "sonar_device", "x": [ 14, 15 ], "y": [ 11, 16 ], "chance": 50 }, { "item": "sea_scooter", "x": [ 9, 10 ], "y": [ 11, 16 ], "chance": 50 } ], "items": { "#": { "item": "fishing_items", "chance": 10 } }, diff --git a/data/json/preload.lua b/data/json/preload.lua index ed77ead05d91..eb0b9c3eb9c3 100644 --- a/data/json/preload.lua +++ b/data/json/preload.lua @@ -6,6 +6,7 @@ local storage = game.mod_storage[game.current_mod] mod.storage = storage game.iuse_functions["VOLTMETER"] = function(...) return mod.voltmeter.menu(...) end +game.iuse_functions["sonar_scan"] = function(...) return mod.sonar_scan(...) end game.iuse_functions["ARTIFACT_ANALYZER"] = function(...) return mod.artifact_analyzer.menu(...) end game.mapgen_functions["slimepit"] = function(...) return mod.slimepit.draw(...) end diff --git a/data/json/sonar.lua b/data/json/sonar.lua new file mode 100644 index 000000000000..b70954859acf --- /dev/null +++ b/data/json/sonar.lua @@ -0,0 +1,58 @@ +local sonar = {} + +local underwater_patterns = { + "river", + "lake", + "lake_bed_lab", + "ocean", + "bay", + "sea", + "water", + "cargo_ship", + "shipwreck", + "sealab", +} + +local function oter_id_to_string(oter_id) + if oter_id.str then return oter_id:str() end + if oter_id.str_id then return oter_id:str_id():str() end + return tostring(oter_id) +end + +local function is_underwater_oter(oter_id) + local id_str = oter_id_to_string(oter_id) + for _, pattern in ipairs(underwater_patterns) do + if string.find(id_str, pattern, 1, true) then return true end + end + return false +end + +sonar.register = function(mod) + mod.sonar_scan = function(who, item, pos) + local map = gapi.get_map() + local abs_ms = map:get_abs_ms(pos) + local center_omt = coords.ms_to_omt(abs_ms) + local radius = 7 + local depth_steps = 5 + local any_revealed = false + local start_omt = center_omt + if center_omt.z >= 0 and is_underwater_oter(overmapbuffer.ter(center_omt)) then + start_omt = Tripoint.new(center_omt.x, center_omt.y, center_omt.z - 1) + end + for depth_index = 0, depth_steps - 1 do + local scan_omt = Tripoint.new(start_omt.x, start_omt.y, start_omt.z - depth_index) + if not is_underwater_oter(overmapbuffer.ter(scan_omt)) then break end + if overmapbuffer.reveal(scan_omt, radius, is_underwater_oter) then any_revealed = true end + end + + if any_revealed then + gapi.add_msg(locale.gettext("The sonar pulse maps nearby underwater terrain.")) + else + gapi.add_msg(locale.gettext("The sonar pulse finds nothing new.")) + end + + return 1 + end +end + +return sonar diff --git a/data/json/vehicleparts/controls.json b/data/json/vehicleparts/controls.json index cf1431a19843..9fb3e811ead4 100644 --- a/data/json/vehicleparts/controls.json +++ b/data/json/vehicleparts/controls.json @@ -225,6 +225,31 @@ "flags": [ "VISION", "CAMERA", "CAMERA_CONTROL", "ENABLED_DRAINS_EPOWER", "SHOCK_RESISTANT" ], "breaks_into": [ { "item": "e_scrap", "count": [ 4, 10 ] }, { "item": "plastic_chunk", "count": [ 2, 8 ] } ] }, + { + "type": "vehicle_part", + "id": "sonar_array", + "name": { "str": "sonar array" }, + "symbol": "#", + "color": "light_blue", + "broken_symbol": "#", + "broken_color": "blue", + "damage_modifier": 15, + "durability": 150, + "description": "A set of sonar transducers and electronics used to scan underwater terrain. Activate it from the vehicle controls to reveal local underwater overmap tiles.", + "folded_volume": "2 L", + "item": "sonar_device", + "requirements": { + "install": { "skills": [ [ "mechanics", 3 ] ], "time": "60 m", "using": [ [ "vehicle_screw", 1 ] ] }, + "removal": { "skills": [ [ "mechanics", 2 ] ], "time": "30 m", "using": [ [ "vehicle_screw", 1 ] ] }, + "repair": { + "skills": [ [ "mechanics", 4 ] ], + "time": "60 m", + "using": [ [ "adhesive", 1 ], [ "vehicle_repair_electronics", 1 ] ] + } + }, + "flags": [ "SONAR", "SHOCK_RESISTANT" ], + "breaks_into": [ { "item": "e_scrap", "count": [ 4, 8 ] }, { "item": "plastic_chunk", "count": [ 2, 6 ] } ] + }, { "type": "vehicle_part", "id": "omnicam", diff --git a/data/json/vehicleparts/vp_flags.json b/data/json/vehicleparts/vp_flags.json index ec8aa26cde0e..1dd1538ae08b 100644 --- a/data/json/vehicleparts/vp_flags.json +++ b/data/json/vehicleparts/vp_flags.json @@ -145,6 +145,12 @@ "context": [ "vehicle_part" ], "info": "Armor plate. Will partially protect other components on the same frame from the shock damage of a distant collision but not from direct attacks." }, + { + "id": "SONAR", + "type": "json_flag", + "context": [ "vehicle_part" ], + "info": "This part can be activated to perform a sonar scan from the vehicle." + }, { "id": "STABLE", "type": "json_flag", diff --git a/data/json/vehicles/boats.json b/data/json/vehicles/boats.json index ff1e6a89eaca..09b58906dfba 100644 --- a/data/json/vehicles/boats.json +++ b/data/json/vehicles/boats.json @@ -1560,7 +1560,7 @@ { "x": -2, "y": 0, "parts": [ "battery_car", "alternator_car", "engine_inline4" ] }, { "x": 3, "y": -1, "part": "halfboard_nw" }, { "x": 3, "y": 0, "part": "halfboard_ne" }, - { "x": 1, "y": -1, "part": "minifridge" }, + { "x": 1, "y": -1, "parts": [ "minifridge", "sonar_array" ] }, { "x": -1, "y": -1, "part": "trunk_floor" }, { "x": -2, "y": 0, "part": "folding_seat" }, { "x": -3, "y": 0, "part": "folding_seat" }, diff --git a/src/catalua_bindings_overmap.cpp b/src/catalua_bindings_overmap.cpp index 224d79f76830..a9b60cbbffb7 100644 --- a/src/catalua_bindings_overmap.cpp +++ b/src/catalua_bindings_overmap.cpp @@ -6,6 +6,7 @@ #include "catalua_bindings.h" #include "catalua.h" #include "catalua_bindings_utils.h" +#include "catalua_impl.h" #include "catalua_luna.h" #include "catalua_luna_doc.h" @@ -172,6 +173,24 @@ void cata::detail::reg_overmap( sol::state &lua ) overmap_buffer.set_seen( tripoint_abs_omt( p ), seen_val.value_or( true ) ); } ); + DOC( "Reveal a square area around a center point on the overmap. Returns true if any new tiles were revealed." ); + DOC( "Optional filter callback receives oter_id and should return true to reveal that tile." ); + luna::set_fx( lib, "reveal", + []( const tripoint & center, int radius, + sol::optional filter_fn ) -> bool { + if( filter_fn.has_value() ) + { + auto filter = filter_fn.value(); + const auto wrapped_filter = [filter]( const oter_id & ter ) -> bool { + sol::protected_function_result res = filter( ter ); + check_func_result( res ); + return res.get(); + }; + return overmap_buffer.reveal( tripoint_abs_omt( center ), radius, wrapped_filter ); + } + return overmap_buffer.reveal( tripoint_abs_omt( center ), radius ); + } ); + DOC( "Check if the terrain at the given position has been explored by the player. Returns boolean." ); luna::set_fx( lib, "is_explored", []( const tripoint & p ) -> bool { diff --git a/src/vehicle_use.cpp b/src/vehicle_use.cpp index 66efe96d35fb..4ba63a7f9b3c 100644 --- a/src/vehicle_use.cpp +++ b/src/vehicle_use.cpp @@ -76,6 +76,7 @@ static const itype_id itype_battery( "battery" ); static const itype_id itype_fungal_seeds( "fungal_seeds" ); static const itype_id itype_hotplate( "hotplate" ); static const itype_id itype_marloss_seed( "marloss_seed" ); +static const auto itype_sonar_device = itype_id( "sonar_device" ); static const itype_id itype_water( "water" ); static const itype_id itype_water_clean( "water_clean" ); static const itype_id itype_water_purifier( "water_purifier" ); @@ -1886,11 +1887,12 @@ void vehicle::interact_with( const tripoint &pos, int interact_part ) const bool has_planter = avail_part_with_feature( interact_part, "PLANTER", true ) >= 0; const int door_lock_part = avail_part_with_feature( interact_part, "DOOR_LOCKING", true ); const bool has_door_lock = door_lock_part >= 0; + const bool has_sonar = avail_part_with_feature( interact_part, "SONAR", true ) >= 0; enum { EXAMINE, TRACK, HANDBRAKE, CONTROL, CONTROL_ELECTRONICS, GET_ITEMS, GET_ITEMS_ON_GROUND, FOLD_VEHICLE, UNLOAD_TURRET, RELOAD_TURRET, USE_HOTPLATE, FILL_CONTAINER, DRINK, USE_CRAFTER, USE_PURIFIER, PURIFY_TANK, USE_AUTOCLAVE, USE_AUTODOC, - USE_MONSTER_CAPTURE, USE_BIKE_RACK, USE_HARNESS, RELOAD_PLANTER, USE_TOWEL, PEEK_CURTAIN, PICK_LOCK + USE_MONSTER_CAPTURE, USE_BIKE_RACK, USE_HARNESS, RELOAD_PLANTER, USE_TOWEL, USE_SONAR, PEEK_CURTAIN, PICK_LOCK }; uilist selectmenu; @@ -1964,6 +1966,9 @@ void vehicle::interact_with( const tripoint &pos, int interact_part ) if( has_planter ) { selectmenu.addentry( RELOAD_PLANTER, true, 's', _( "Reload seed drill with seeds" ) ); } + if( has_sonar && fuel_left( itype_battery, true ) > 0 ) { + selectmenu.addentry( USE_SONAR, true, 'S', _( "Activate sonar" ) ); + } int choice; if( selectmenu.entries.size() == 1 ) { @@ -2017,6 +2022,10 @@ void vehicle::interact_with( const tripoint &pos, int interact_part ) iuse::towel_common( &you, nullptr, false ); return; } + case USE_SONAR: { + veh_tool( itype_sonar_device ); + return; + } case USE_AUTOCLAVE: { iexamine::autoclave_empty( you, pos ); return;