Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.
Closed
150 changes: 144 additions & 6 deletions code/game/objects/items/robot/ai_upgrades.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
icon = 'icons/obj/module.dmi'
icon_state = "datadisk3"


/obj/item/malf_upgrade/afterattack(mob/living/silicon/ai/AI, mob/user)
. = ..()
if(!istype(AI))
Expand All @@ -20,12 +19,11 @@
to_chat(AI, span_userdanger("[user] has upgraded you with combat software!"))
to_chat(AI, span_userdanger("Your current laws and objectives remain unchanged.")) //this unlocks malf powers, but does not give the license to plasma flood
AI.add_malf_picker()
log_game("[key_name(user)] has upgraded [key_name(AI)] with a [src].")
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with a [src].")
log_game("[key_name(user)] has upgraded [key_name(AI)] with \a [src].")
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with \a [src].")
to_chat(user, span_notice("You upgrade [AI]. [src] is consumed in the process."))
qdel(src)


//Lipreading
/obj/item/surveillance_upgrade
name = "surveillance software upgrade"
Expand All @@ -42,6 +40,146 @@
to_chat(AI, span_userdanger("[user] has upgraded you with surveillance software!"))
to_chat(AI, "Via a combination of hidden microphones and lip reading software, you are able to use your cameras to listen in on conversations.")
to_chat(user, span_notice("You upgrade [AI]. [src] is consumed in the process."))
log_game("[key_name(user)] has upgraded [key_name(AI)] with a [src].")
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with a [src].")
log_game("[key_name(user)] has upgraded [key_name(AI)] with \a [src].")
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with \a [src].")
qdel(src)

/obj/item/cameragun_upgrade
name = "camera laser upgrade"
desc = "A software package that will allow an artificial intelligence to briefly increase the amount of light an camera outputs to an outrageous amount to the point it burns skins. Must be installed using an unlocked AI control console." // In short, laser gun!
icon = 'icons/obj/module.dmi'
icon_state = "datadisk3"

/obj/item/cameragun_upgrade/afterattack(mob/living/silicon/ai/AI, mob/user)
. = ..()
if(!istype(AI))
return

// No giving multiple copies.
for(var/datum/action/action in AI.actions)
if(action.type == /datum/action/innate/ai/ranged/cameragun)
to_chat(user, span_notice("[AI] has already been upgraded with \a [src]."))
return

var/datum/action/innate/ai/ranged/cameragun/ability = new
ability.Grant(AI)

to_chat(user, span_notice("You upgrade [AI]. [src] is consumed in the process."))
log_game("[key_name(user)] has upgraded [key_name(AI)] with \a [src].")
message_admins("[ADMIN_LOOKUPFLW(user)] has upgraded [ADMIN_LOOKUPFLW(AI)] with \a [src].")
qdel(src)

/// An ability that allows the user to shoot a laser beam at a target from the nearest camera.
/datum/action/innate/ai/ranged/cameragun
name = "Camera Laser Gun"
desc = "Shoots a laser from the nearest available camera toward a chosen destination if it is highly probable to reach said destination."
button_icon = 'icons/obj/guns/energy.dmi'
button_icon_state = "laser"
enable_text = span_notice("You prepare to overcharge a camera. Click a target for a nearby camera to shoot a laser at.")
disable_text = span_notice("You dissipate the overcharged energy.")
click_action = FALSE // Even though that we are a click action, we want to use Activate() and Deactivate().
COOLDOWN_DECLARE(next_shot)
var/cooldown = 10 SECONDS

/// Checks if it is possible for a (hitscan) projectile to reach a target in a straight line from a camera.
/datum/action/innate/ai/ranged/cameragun/proc/can_shoot_to(obj/machinery/camera/C, atom/target)
var/obj/projectile/proj = new /obj/projectile
proj.icon = null
proj.icon_state = null
proj.hitsound = ""
proj.suppressed = TRUE
proj.ricochets_max = 0
proj.ricochet_chance = 0
proj.damage = 0
proj.nodamage = TRUE // Prevents this projectile from detonating certain objects (e.g. welding tanks).
proj.log_override = TRUE
proj.hitscan = TRUE
proj.pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE

proj.preparePixelProjectile(target, C)
proj.fire()

var/turf/target_turf = get_turf(target)
var/turf/last_turf = proj.hitscan_last
if(last_turf == target_turf)
return TRUE
return FALSE

/datum/action/innate/ai/ranged/cameragun/New()
..()
START_PROCESSING(SSfastprocess, src)

/datum/action/innate/ai/ranged/cameragun/Destroy()
STOP_PROCESSING(SSfastprocess, src)
return ..()

/datum/action/innate/ai/ranged/cameragun/process()
build_all_button_icons() // To update the button to display if active/available.

/datum/action/innate/ai/ranged/cameragun/Activate(loud = TRUE)
set_ranged_ability(owner, loud ? enable_text : null)
active = TRUE
background_icon_state = "bg_default_on"
build_all_button_icons()

/datum/action/innate/ai/ranged/cameragun/Deactivate(loud = TRUE)
unset_ranged_ability(owner, loud ? disable_text : null)
active = FALSE
background_icon_state = "bg_default"
build_all_button_icons()

/datum/action/innate/ai/ranged/cameragun/IsAvailable(feedback = FALSE)
. = ..()
if(!. || !COOLDOWN_FINISHED(src, next_shot))
return FALSE

/datum/action/innate/ai/ranged/cameragun/do_ability(mob/living/caller, params, atom/target)
var/turf/loc_target = get_turf(target)
var/obj/machinery/camera/chosen_camera
for(var/obj/machinery/camera/cam in GLOB.cameranet.cameras)
if(!isturf(cam.loc))
continue
if(cam == target)
continue
if(!cam.status || cam.emped) // Non-functional camera.
continue
var/turf/loc_camera = get_turf(cam)
if(loc_target.z != loc_camera.z)
continue
if(get_dist(cam, target) <= 1) // Pointblank shot.
chosen_camera = cam
break
if(get_dist(cam, target) > 12)
continue
if(!can_shoot_to(cam, target)) // No chance to hit.
continue
if(!chosen_camera)
chosen_camera = cam
continue
if(get_dist(chosen_camera, target) > get_dist(cam, target)) // Closest camera that can hit.
chosen_camera = cam
continue
if(!chosen_camera)
Deactivate(FALSE)
to_chat(caller, span_notice("Unable to find nearby available cameras for this target."))
return FALSE

COOLDOWN_START(src, next_shot, cooldown)
var/turf/loc_chosen = get_turf(chosen_camera)
var/obj/projectile/beam/laser/proj = new(loc_chosen)
proj.preparePixelProjectile(target, chosen_camera)
proj.firer = caller

// Fire the shot.
var/pointblank = get_dist(chosen_camera, target) <= 1 ? TRUE : FALSE // Same tile or right next.
if(pointblank)
chosen_camera.visible_message(span_danger("[chosen_camera] fires a laser point blank at [target]!"))
proj.fire(direct_target = target)
else
chosen_camera.visible_message(span_danger("[chosen_camera] fires a laser!"))
proj.fire()
Deactivate(FALSE)
to_chat(caller, span_danger("Camera overcharged."))

chosen_camera.emp_act(EMP_HEAVY) // 90 seconds downtime -- definitely enough time to toolbox this camera (unless it is emp-proof).
return TRUE
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,17 @@ GLOBAL_VAR_INIT(ai_control_code, random_nukecode(6))
return ..()
var/obj/item/surveillance_upgrade/upgrade = W
upgrade.afterattack(AI, user)

return FALSE
if(istype(W, /obj/item/cameragun_upgrade))
if(!authenticated)
to_chat(user, span_warning("You need to be logged in to do this!"))
return ..()
var/mob/living/silicon/ai/AI = input("Select an AI", "Select an AI", null, null) as null|anything in GLOB.ai_list
if(!AI)
return ..()
var/obj/item/cameragun_upgrade/upgrade = W
upgrade.afterattack(AI, user)
return FALSE
if(istype(W, /obj/item/malf_upgrade))
if(!authenticated)
to_chat(user, span_warning("You need to be logged in to do this!"))
Expand All @@ -90,7 +100,7 @@ GLOBAL_VAR_INIT(ai_control_code, random_nukecode(6))
return ..()
var/obj/item/malf_upgrade/upgrade = W
upgrade.afterattack(AI, user)

return FALSE
return ..()

/obj/machinery/computer/ai_control_console/emag_act(mob/user, obj/item/card/emag/emag_card)
Expand Down
6 changes: 3 additions & 3 deletions code/modules/projectiles/projectile.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation.
var/datum/point/beam_index
var/turf/hitscan_last //last turf touched during hitscanning.
/// The ending/last touched turf during hitscanning.
var/turf/hitscan_last
var/tracer_type
var/muzzle_type
var/impact_type
Expand Down Expand Up @@ -584,10 +585,9 @@
pixel_x = trajectory.return_px()
pixel_y = trajectory.return_py()
forcemoved = TRUE
hitscan_last = loc
else if(T != loc)
step_towards(src, T)
hitscan_last = loc
hitscan_last = T
if(!hitscanning && !forcemoved)
pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move
pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move
Expand Down
7 changes: 7 additions & 0 deletions code/modules/uplink/uplink_items.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2680,6 +2680,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 4 // Not as "destructive" as the emag. In addition, less features than the normal emag. Increase price once more impactful features are added.
restricted_roles = list("Clown")

/datum/uplink_item/role_restricted/ai_cameragun
name = "AI Camera Laser Gun Upgrade"
desc = "A disk containing experimental and illegal software that allows an AI to temporarily override the safety features on their cameras, enabling them to shoot a laser beam out of them."
item = /obj/item/cameragun_upgrade
cost = 8 // Considering that you have to: subvert an AI, trust the AI not to be a traitor/unsubverted, accept that every camera is gonna get disabled later, and pay more than an emag for this... 8 is fair.
restricted_roles = list("Roboticist", "Research Director")

// Pointless
/datum/uplink_item/badass
category = "(Pointless) Badassery"
Expand Down