diff --git a/code/game/objects/items/weapons/inducer.dm b/code/game/objects/items/weapons/inducer.dm new file mode 100644 index 0000000000..fdd44795ad --- /dev/null +++ b/code/game/objects/items/weapons/inducer.dm @@ -0,0 +1,183 @@ +/obj/item/weapon/inducer + name = "inducer" + desc = "A tool for inductively charging internal power cells." + icon = 'icons/obj/tools.dmi' + icon_state = "inducer-engi" + item_state = "inducer-engi" + origin_tech = "engineering=4;magnets=4;powerstorage=4" + force = 7 + var/powertransfer = 1000 + var/opened = FALSE + var/cell_type = /obj/item/weapon/stock_parts/cell/high + var/obj/item/weapon/stock_parts/cell/cell + var/recharging = FALSE + +/obj/item/weapon/inducer/Initialize() + . = ..() + if(!cell && cell_type) + cell = new cell_type + +/obj/item/weapon/inducer/proc/induce(obj/item/weapon/stock_parts/cell/target, coefficient) + var/totransfer = min(cell.charge,(powertransfer * coefficient)) + var/transferred = target.give(totransfer) + cell.use(transferred) + cell.update_icon() + target.update_icon() + +/obj/item/weapon/inducer/get_cell() + return cell + +/obj/item/weapon/inducer/emp_act(severity) + ..() + if(cell) + cell.emp_act() + +/obj/item/weapon/inducer/attack_obj(obj/O, mob/living/carbon/user) + if(user.a_intent == INTENT_HARM) + return ..() + + if(cantbeused(user)) + return + + if(recharge(O, user)) + return + + return ..() + +/obj/item/weapon/inducer/proc/cantbeused(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to use \the [src]!") + return TRUE + + if(!cell) + to_chat(user, "\The [src] doesn't have a power cell installed!") + return TRUE + + if(!cell.charge) + to_chat(user, "\The [src]'s battery is dead!") + return TRUE + return FALSE + + +/obj/item/weapon/inducer/attackby(obj/item/weapon/W, mob/user) + if(istype(W,/obj/item/weapon/screwdriver)) + playsound(src, W.usesound, 50, 1) + if(!opened) + to_chat(user, "You unscrew the battery compartment.") + opened = TRUE + update_icon() + return + else + to_chat(user, "You close the battery compartment.") + opened = FALSE + update_icon() + return + if(istype(W,/obj/item/weapon/stock_parts/cell)) + if(opened) + if(!cell) + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You insert \the [W] into \the [src].") + cell = W + update_icon() + return + else + to_chat(user, "\The [src] already has \a [cell] installed!") + return + + if(cantbeused(user)) + return + + if(recharge(W, user)) + return + + return ..() + +/obj/item/weapon/inducer/proc/recharge(atom/movable/A, mob/user) + if(recharging) + return TRUE + else + recharging = TRUE + var/obj/item/weapon/stock_parts/cell/C = A.get_cell() + var/obj/item/weapon/gun/energy/E + var/obj/O + var/coefficient = 1 + if(istype(A, /obj/item/weapon/gun/energy)) + coefficient = 0.075 // 14 loops to recharge an egun from 0-1000 + E = A + if(istype(A, /obj)) + O = A + if(C) + if(C.charge >= C.maxcharge) + to_chat(user, "\The [A] is fully charged!") + recharging = FALSE + return TRUE + user.visible_message("[user] starts recharging \the [A] with \the [src]","You start recharging [A] with \the [src]") + while(C.charge < C.maxcharge) + if(E) + E.chambered = null // Prevents someone from firing continuously while recharging the gun. + if(do_after(user, 10, target = user) && cell.charge) + induce(C, coefficient) + do_sparks(1, FALSE, A) + if(O) + O.update_icon() + else + break + if(E) + E.recharge_newshot() //We're done charging, so we'll let someone fire it now. + user.visible_message("[user] recharged \the [A]!","You recharged \the [A]!") + recharging = FALSE + return TRUE + + +/obj/item/weapon/inducer/attack(mob/M, mob/user) + if(user.a_intent == INTENT_HARM) + return ..() + + if(cantbeused(user)) + return + + if(recharge(M, user)) + return + return ..() + + +/obj/item/weapon/inducer/attack_self(mob/user) + if(opened && cell) + user.visible_message("[user] removes \the [cell] from \the [src]!","You remove \the [cell].") + cell.update_icon() + user.put_in_hands(cell) + cell = null + update_icon() + + +/obj/item/weapon/inducer/examine(mob/living/M) + ..() + if(cell) + to_chat(M, "It's display shows: [cell.charge]W") + else + to_chat(M,"It's display is dark.") + if(opened) + to_chat(M,"It's battery compartment is open.") + +/obj/item/weapon/inducer/update_icon() + cut_overlays() + if(opened) + if(!cell) + add_overlay("inducer-nobat") + else + add_overlay("inducer-bat") + +/obj/item/weapon/inducer/sci + icon_state = "inducer-sci" + item_state = "inducer-sci" + desc = "A tool for inductively charging internal power cells. This one has a science color scheme, and is less potent than it's engineering counterpart." + cell_type = null + powertransfer = 500 + opened = TRUE + +/obj/item/weapon/inducer/sci/Initialize() + . = ..() + update_icon() + + diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm new file mode 100644 index 0000000000..f2f767f4e6 --- /dev/null +++ b/code/game/objects/structures/manned_turret.dm @@ -0,0 +1,209 @@ +/////// MANNED TURRET //////// + +/obj/machinery/manned_turret + name = "machine gun turret" + desc = "While the trigger is held down, this gun will redistribute recoil to allow its user to easily shift targets." + icon = 'icons/obj/turrets.dmi' + icon_state = "machinegun" + can_buckle = TRUE + density = TRUE + max_integrity = 100 + obj_integrity = 100 + buckle_lying = FALSE + layer = ABOVE_MOB_LAYER + var/view_range = 10 + var/cooldown = 0 + var/projectile_type = /obj/item/projectile/bullet/weakbullet3 + var/rate_of_fire = 1 + var/number_of_shots = 40 + var/cooldown_duration = 90 + var/atom/target + var/turf/target_turf + var/warned = FALSE + var/list/calculated_projectile_vars + +/obj/machinery/manned_turret/Destroy() + target = null + target_turf = null + ..() + +//BUCKLE HOOKS + +/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE) + playsound(src,'sound/mecha/mechmove01.ogg', 50, 1) + for(var/obj/item/I in buckled_mob.held_items) + if(istype(I, /obj/item/gun_control)) + qdel(I) + if(istype(buckled_mob)) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.reset_perspective() + anchored = FALSE + . = ..() + STOP_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/user_buckle_mob(mob/living/M, mob/living/carbon/user) + if(user.incapacitated() || !istype(user)) + return + M.forceMove(get_turf(src)) + ..() + for(var/V in M.held_items) + var/obj/item/I = V + if(istype(I)) + if(M.dropItemToGround(I)) + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand + var/obj/item/gun_control/TC = new(src) + M.put_in_hands(TC) + M.pixel_y = 14 + layer = ABOVE_MOB_LAYER + setDir(SOUTH) + playsound(src,'sound/mecha/mechmove01.ogg', 50, 1) + anchored = TRUE + if(user.client) + user.client.change_view(view_range) + START_PROCESSING(SSfastprocess, src) + +/obj/machinery/manned_turret/process() + if(!LAZYLEN(buckled_mobs)) + return PROCESS_KILL + update_positioning() + +/obj/machinery/manned_turret/proc/update_positioning() + var/mob/living/controller = buckled_mobs[1] + if(!istype(controller)) + return + var/client/C = controller.client + if(C) + var/atom/A = C.mouseObject + var/turf/T = get_turf(A) + if(istype(T)) //They're hovering over something in the map. + direction_track(controller, T) + calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams) + +/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted) + setDir(get_dir(src,targeted)) + user.setDir(dir) + switch(dir) + if(NORTH) + layer = BELOW_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = -14 + if(NORTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = -4 + if(EAST) + layer = ABOVE_MOB_LAYER + user.pixel_x = -14 + user.pixel_y = 0 + if(SOUTHEAST) + layer = BELOW_MOB_LAYER + user.pixel_x = -8 + user.pixel_y = 4 + if(SOUTH) + layer = ABOVE_MOB_LAYER + user.pixel_x = 0 + user.pixel_y = 14 + if(SOUTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = 4 + if(WEST) + layer = ABOVE_MOB_LAYER + user.pixel_x = 14 + user.pixel_y = 0 + if(NORTHWEST) + layer = BELOW_MOB_LAYER + user.pixel_x = 8 + user.pixel_y = -4 + +/obj/machinery/manned_turret/proc/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || target == get_turf(src)) + return + if(world.time < cooldown) + if(!warned && world.time > (cooldown - cooldown_duration + rate_of_fire*number_of_shots)) // To capture the window where one is done firing + warned = TRUE + playsound(src, 'sound/weapons/sear.ogg', 100, 1) + return + else + cooldown = world.time + cooldown_duration + warned = FALSE + volley(user) + +/obj/machinery/manned_turret/proc/volley(mob/user) + target_turf = get_turf(target) + for(var/i in 1 to number_of_shots) + addtimer(CALLBACK(src, /obj/machinery/manned_turret/.proc/fire_helper, user), i*rate_of_fire) + +/obj/machinery/manned_turret/proc/fire_helper(mob/user) + update_positioning() //REFRESH MOUSE TRACKING!! + var/turf/targets_from = get_turf(src) + if(QDELETED(target)) + target = target_turf + var/obj/item/projectile/P = new projectile_type(targets_from) + P.current = targets_from + P.starting = targets_from + P.firer = user + P.original = target + playsound(src, 'sound/weapons/Gunshot_smg.ogg', 75, 1) + P.xo = target.x - targets_from.x + P.yo = target.y - targets_from.y + P.Angle = calculated_projectile_vars[1] + rand(-9, 9) + P.p_x = calculated_projectile_vars[2] + P.p_y = calculated_projectile_vars[3] + P.fire() + +/obj/machinery/manned_turret/ultimate // Admin-only proof of concept for autoclicker automatics + name = "Infinity Gun" + view_range = 12 + projectile_type = /obj/item/projectile/bullet/weakbullet3 + +/obj/machinery/manned_turret/ultimate/checkfire(atom/targeted_atom, mob/user) + target = targeted_atom + if(target == user || target == get_turf(src)) + return + target_turf = get_turf(target) + fire_helper(user) + +/obj/item/gun_control + name = "turret controls" + icon = 'icons/obj/weapons.dmi' + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + flags = ABSTRACT | NODROP | NOBLUDGEON + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/obj/machinery/manned_turret/turret + +/obj/item/gun_control/Initialize() + . = ..() + turret = loc + if(!istype(turret)) + return INITIALIZE_HINT_QDEL + +/obj/item/gun_control/Destroy() + turret = null + ..() + +/obj/item/gun_control/CanItemAutoclick() + return TRUE + +/obj/item/gun_control/attack_obj(obj/O, mob/living/user) + user.changeNext_move(CLICK_CD_MELEE) + O.attacked_by(src, user) + +/obj/item/gun_control/attack(mob/living/M, mob/living/user) + user.lastattacked = M + M.lastattacker = user + M.attacked_by(src, user) + add_fingerprint(user) + +/obj/item/gun_control/afterattack(atom/targeted_atom, mob/user, flag, params) + ..() + var/obj/machinery/manned_turret/E = user.buckled + E.calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(user, params) + E.direction_track(user, targeted_atom) + E.checkfire(targeted_atom, user)