diff --git a/code/game/objects/items/weapons/chrono_eraser.dm b/code/game/objects/items/weapons/chrono_eraser.dm new file mode 100644 index 00000000000..bcf7d80f5c5 --- /dev/null +++ b/code/game/objects/items/weapons/chrono_eraser.dm @@ -0,0 +1,264 @@ +#define CHRONO_BEAM_RANGE 3 +#define CHRONO_FRAME_COUNT 22 +/obj/item/weapon/chrono_eraser + name = "Timestream Eradication Device" + desc = "The result of outlawed time-bluespace research, this device is capable of wiping a being from the timestream. They never are, they never were, they never will be." + icon = 'icons/obj/chronos.dmi' + icon_state = "chronobackpack" + item_state = "backpack" + w_class = 4.0 + slot_flags = SLOT_BACK + slowdown = 1 + action_button_name = "Equip/Unequip TED Gun" + var/obj/item/weapon/gun/energy/chrono_gun/PA = null + var/list/erased_minds = list() //a collection of minds from the dead + +/obj/item/weapon/chrono_eraser/proc/pass_mind(var/datum/mind/M) + erased_minds += M + +/obj/item/weapon/chrono_eraser/dropped() + if(PA) + qdel(PA) + +/obj/item/weapon/chrono_eraser/Destroy() + dropped() + ..() + +/obj/item/weapon/chrono_eraser/ui_action_click() + var/mob/living/carbon/user = src.loc + if(iscarbon(user) && (user.back == src)) + if(PA) + qdel(PA) + else + PA = new(src) + user.put_in_hands(PA) + + + +/obj/item/weapon/gun/energy/chrono_gun + name = "T.E.D. Projection Apparatus" + desc = "It's as if they never existed in the first place." + icon = 'icons/obj/chronos.dmi' + icon_state = "chronogun" + item_state = "chronogun" + w_class = 3.0 + flags = NODROP + ammo_type = list(/obj/item/ammo_casing/energy/chrono_beam) + fire_delay = 50 + var/obj/item/weapon/chrono_eraser/TED = null + var/obj/effect/chrono_field/field = null + var/turf/startpos = null + +/obj/item/weapon/gun/energy/chrono_gun/New(var/obj/item/weapon/chrono_eraser/T) + . = ..() + if(istype(T)) + TED = T + else //admin must have spawned it + TED = new(src.loc) + qdel(src) + +/obj/item/weapon/gun/energy/chrono_gun/dropped() + qdel(src) + +/obj/item/weapon/gun/energy/chrono_gun/update_icon() + return + +/obj/item/weapon/gun/energy/chrono_gun/process_fire() + if(field) + field_disconnect(field) + ..() + +/obj/item/weapon/gun/energy/chrono_gun/Destroy() + if(TED) + TED.PA = null + TED = null + if(field) + field_disconnect(field) + ..() + +/obj/item/weapon/gun/energy/chrono_gun/proc/field_connect(var/obj/effect/chrono_field/F) + var/mob/living/user = src.loc + if(F.gun) + if(isliving(user) && F.captured) + user << "FAIL: [F.captured] already has an existing connection." + src.field_disconnect(F) + else + startpos = get_turf(src) + field = F + F.gun = src + if(isliving(user) && F.captured) + user << "Connection established with target: [F.captured]" + + +/obj/item/weapon/gun/energy/chrono_gun/proc/field_disconnect(var/obj/effect/chrono_field/F) + if(F && field == F) + var/mob/living/user = src.loc + if(F.gun == src) + F.gun = null + if(isliving(user) && F.captured) + user << "Disconnected from target: [F.captured]" + field = null + startpos = null + +/obj/item/weapon/gun/energy/chrono_gun/proc/field_check(var/obj/effect/chrono_field/F) + if(F) + if(field == F) + var/turf/currentpos = get_turf(src) + var/mob/living/user = src.loc + if((currentpos == startpos) && (field in view(CHRONO_BEAM_RANGE, currentpos)) && !user.lying && (user.stat == CONSCIOUS)) + return 1 + field_disconnect(F) + return 0 + +/obj/item/weapon/gun/energy/chrono_gun/proc/pass_mind(var/datum/mind/M) + if(TED) + TED.pass_mind(M) + + +/obj/item/projectile/energy/chrono_beam + name = "eradication beam" + icon_state = "chronobolt" + range = CHRONO_BEAM_RANGE + color = null + nodamage = 1 + var/obj/item/weapon/gun/energy/chrono_gun/gun = null + +/obj/item/projectile/energy/chrono_beam/fire() + gun = firer.get_active_hand() + if(istype(gun)) + return ..() + else + return 0 + +/obj/item/projectile/energy/chrono_beam/on_hit(var/atom/target) + if(target && gun && isliving(target)) + var/obj/effect/chrono_field/F = new(target.loc, target, gun) + gun.field_connect(F) + + +/obj/item/ammo_casing/energy/chrono_beam + name = "eradication beam" + projectile_type = /obj/item/projectile/energy/chrono_beam + icon_state = "chronobolt" + e_cost = 0 + + +/obj/effect/chrono_field + name = "eradication field" + desc = "An aura of time-bluespace energy." + icon = 'icons/effects/effects.dmi' + icon_state = "chronofield" + density = 1 + anchored = 1 + unacidable = 1 + blend_mode = BLEND_MULTIPLY + var/mob/living/captured = null + var/obj/item/weapon/gun/energy/chrono_gun/gun = null + var/tickstokill = 30 + var/image/mob_underlay = null + var/preloaded = 0 + var/RPpos = null + +/obj/effect/chrono_field/New(loc, var/mob/living/target, var/obj/item/weapon/gun/energy/chrono_gun/G) + if(target && isliving(target) && G) + target.loc = src + src.captured = target + var/icon/mob_snapshot = getFlatIcon(target) + var/icon/cached_icon = new() + + for(var/i=1, i<=CHRONO_FRAME_COUNT, i++) + var/icon/removing_frame = icon('icons/obj/chronos.dmi', "erasing", SOUTH, i) + var/icon/mob_icon = icon(mob_snapshot) + mob_icon.Blend(removing_frame, ICON_MULTIPLY) + cached_icon.Insert(mob_icon, "frame[i]") + + mob_underlay = new(cached_icon, "frame1") + update_icon() + + desc = initial(desc) + "
It appears to contain [target.name]." + SSobj.processing.Add(src) + +/obj/effect/chrono_field/Destroy() + if(gun && gun.field_check(src)) + gun.field_disconnect(src) + ..() + +/obj/effect/chrono_field/update_icon() + var/ttk_frame = 1 - (tickstokill / initial(tickstokill)) + ttk_frame = Clamp(Ceiling(ttk_frame * CHRONO_FRAME_COUNT), 1, CHRONO_FRAME_COUNT) + if(ttk_frame != RPpos) + RPpos = ttk_frame + mob_underlay.icon_state = "frame[RPpos]" + underlays = list() //hack: BYOND refuses to update the underlay to match the icon_state otherwise + underlays += mob_underlay + +/obj/effect/chrono_field/process() + if(captured) + if(tickstokill > initial(tickstokill)) + for(var/atom/movable/AM in contents) + AM.loc = loc + qdel(src) + else if(tickstokill <= 0) + captured << "As the last essence of your being is erased from time, you begin to re-experience your most enjoyable memory. You feel happy..." + var/mob/dead/observer/ghost = captured.ghostize(1) + if(captured.mind) + if(ghost) + ghost.mind = null + if(gun) + gun.pass_mind(captured.mind) + qdel(captured) + qdel(src) + else + captured.Paralyse(4) + if(captured.reagents) + captured.reagents.del_reagent("synaptizine") //you pesky thing you + if(captured.loc != src) //If they manage to escape, immediately kill them, this is so that even if there IS a way to get out, they won't use it + captured.loc = src + tickstokill = 0 + return .() + update_icon() + if(gun) + if(gun.field_check(src)) + tickstokill-- + else + gun = null + return .() + else + tickstokill++ + else + qdel(src) + +/obj/effect/chrono_field/bullet_act(var/obj/item/projectile/P) + if(istype(P, /obj/item/projectile/energy/chrono_beam)) + var/obj/item/projectile/energy/chrono_beam/beam = P + var/obj/item/weapon/gun/energy/chrono_gun/Pgun = beam.gun + if(Pgun && istype(Pgun)) + Pgun.field_connect(src) + else + return 0 + +/obj/effect/chrono_field/assume_air() + return 0 + +/obj/effect/chrono_field/return_air() //we always have nominal air and temperature + var/datum/gas_mixture/GM = new + GM.oxygen = MOLES_O2STANDARD + GM.nitrogen = MOLES_N2STANDARD + GM.temperature = T20C + return GM + +/obj/effect/chrono_field/Move() + return + +/obj/effect/chrono_field/singularity_act() + return + +/obj/effect/chrono_field/ex_act() + return + +/obj/effect/chrono_field/blob_act() + return + + +#undef CHRONO_BEAM_RANGE +#undef CHRONO_FRAME_COUNT \ No newline at end of file diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm new file mode 100644 index 00000000000..7876f61368c --- /dev/null +++ b/code/modules/clothing/spacesuits/chronosuit.dm @@ -0,0 +1,213 @@ +/obj/item/clothing/head/helmet/space/chronos + name = "Chronosuit Helmet" + desc = "A white helmet with an opaque blue visor." + icon_state = "chronohelmet" + item_state = "chronohelmet" + slowdown = 1 + armor = list(melee = 60, bullet = 30/*bullet through the visor*/, laser = 60, energy = 60, bomb = 30, bio = 90, rad = 90) + var/obj/item/clothing/suit/space/chronos/suit = null + +/obj/item/clothing/head/helmet/space/chronos/dropped() + if(suit) + suit.deactivate() + ..() + +/obj/item/clothing/head/helmet/space/chronos/Destroy() + dropped() + ..() + + +/obj/item/clothing/suit/space/chronos + name = "Chronosuit" + desc = "An advanced spacesuit equipped with teleportation and anti-compression technology" + icon_state = "chronosuit" + item_state = "chronosuit" + action_button_name = "Toggle Chronosuit" + slowdown = 2 + armor = list(melee = 60, bullet = 60, laser = 60, energy = 60, bomb = 30, bio = 90, rad = 90) + var/obj/item/clothing/head/helmet/space/chronos/helmet = null + var/obj/effect/chronos_cam/camera = null + var/activating = 0 + var/activated = 0 + var/cooldowntime = 50 //deciseconds + var/cooldown = 0 + var/teleporting = 0 + + +/obj/item/clothing/suit/space/chronos/proc/new_camera(var/mob/user) + if(camera) + qdel(camera) + camera = new /obj/effect/chronos_cam(get_turf(user)) + camera.holder = user + user.remote_control = camera + +/obj/item/clothing/suit/space/chronos/ui_action_click() + if((cooldown <= world.time) && !teleporting && !activating) + if(!activated) + activate() + else + deactivate() + +/obj/item/clothing/suit/space/chronos/dropped() + if(activated) + deactivate() + ..() + +/obj/item/clothing/suit/space/chronos/Destroy() + dropped() + ..() + +/obj/item/clothing/suit/space/chronos/emp_act(severity) + var/mob/living/carbon/human/user = src.loc + switch(severity) + if(1) + if(user && ishuman(user) && (user.wear_suit == src)) + user << "Elecrtromagnetic pulse detected, shutting down systems to preserve integrity..." + deactivate() + +/obj/item/clothing/suit/space/chronos/proc/chronowalk(var/mob/living/carbon/human/user) + if(!teleporting && user && (user.stat == CONSCIOUS)) + teleporting = 1 + var/turf/from_turf = get_turf(user) + if(!from_turf) //sanity, things happen + teleporting = 0 + return + var/turf/to_turf = from_turf + var/atom/movable/overlay/phaseanim = new(from_turf) + var/obj/holder = new(camera) + phaseanim.name = "phasing [user.name]" + phaseanim.icon = 'icons/mob/mob.dmi' + phaseanim.icon_state = "chronostuck" + phaseanim.density = 1 + phaseanim.layer = FLY_LAYER + phaseanim.master = user + user.ExtinguishMob() + if(user.buckled) + user.buckled.unbuckle() + user.loc = holder + flick("chronophase", phaseanim) + spawn(7) + if(user) + if(phaseanim) + if(camera && camera.loc) + to_turf = camera.loc + flick("chronounphase", phaseanim) + else + flick("chronostuck", phaseanim) + phaseanim.loc = to_turf + sleep(7) + if(holder) + if(user && user in holder.contents) + user.loc = to_turf + if(user.client) + if(camera) + user.client.eye = camera + else + user.client.eye = user + qdel(holder) + else if(user) + user.loc = from_turf + if(phaseanim) + qdel(phaseanim) + teleporting = 0 + if(user && !user.loc) //ubersanity + user.loc = locate(0,0,1) + user.gib() + +/obj/item/clothing/suit/space/chronos/process() + if(activated) + var/mob/living/carbon/human/user = src.loc + if(user && ishuman(user) && (user.wear_suit == src)) + if(camera && (user.remote_control == camera)) + if(!teleporting && !((camera.x == user.x) && (camera.y == user.y) && (camera.z == user.z))) //cheaper than a couple get_turf calls??? + chronowalk(user) + else + new_camera(user) + else + SSobj.processing.Remove(src) + +/obj/item/clothing/suit/space/chronos/proc/activate() + if(!activating && !activated && !teleporting) + activating = 1 + var/mob/living/carbon/human/user = src.loc + if(user && ishuman(user)) + if(user.wear_suit == src) + user << "\nChronosuitMK4 login: root" + user << "Password:\n" + user << "root@ChronosuitMK4# chronowalk4 --start\n" + if(user.head && istype(user.head, /obj/item/clothing/head/helmet/space/chronos)) + user << "\[ ok \] Mounting /dev/helmet" + helmet = user.head + helmet.flags |= NODROP + helmet.suit = src + src.flags |= NODROP + user << "\[ ok \] Starting brainwave scanner" + user << "\[ ok \] Starting ui display driver" + user << "\[ ok \] Initializing chronowalk4-view" + new_camera(user) + SSobj.processing.Add(src) + activated = 1 + else + user << "\[ fail \] Mounting /dev/helmet" + user << "FATAL: Unable to locate /dev/helmet. Aborting..." + cooldown = world.time + cooldowntime + activating = 0 + return 0 + +/obj/item/clothing/suit/space/chronos/proc/deactivate() + if(activated) + activating = 1 + var/mob/living/carbon/human/user = src.loc + if(user && ishuman(user)) + if(user.wear_suit == src) + user << "\nroot@ChronosuitMK4# chronowalk4 --stop\n" + if(camera) + user << "\[ ok \] Sending TERM signal to chronowalk4-view" //yes I know they aren't a different color when shutting down, but they were too similar at a glance + qdel(camera) + if(helmet) + user << "\[ ok \] Stopping ui display driver" + user << "\[ ok \] Stopping brainwave scanner" + user << "\[ ok \] Unmounting /dev/helmet" + helmet.flags &= ~NODROP + helmet.suit = null + helmet = null + user << "logout" + src.flags &= ~NODROP + cooldown = world.time + cooldowntime * 1.5 + activated = 0 + activating = 0 + + +/obj/effect/chronos_cam + name = "Chronosuit View" + density = 0 + anchored = 1 + invisibility = 101 + opacity = 0 + mouse_opacity = 0 + var/mob/holder = null + +/obj/effect/chronos_cam/relaymove(var/mob/user, direction) + if(holder) + if(user == holder) + if(user.client && user.client.eye != src) + src.loc = get_turf(user) + user.client.eye = src + var/step = get_step(src, direction) + if(step) + if(istype(step, /turf/space)) + if(!src.Move(step)) + src.loc = step + else + src.loc = step + else + qdel(src) + +/obj/effect/chronos_cam/Destroy() + if(holder) + if(holder.remote_control == src) + holder.remote_control = null + if(holder.client && (holder.client.eye == src)) + holder.client.eye = holder + ..() + diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index d48d0456900..7a18315e8cb 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -157,5 +157,6 @@ var/force_compose = 0 //If this is nonzero, the mob will always compose it's own hear message instead of using the one given in the arguments. var/obj/control_object //Used by admins to possess objects. All mobs should have this var + var/atom/movable/remote_control //Calls relaymove() to whatever it is var/turf/listed_turf = null //the current turf being examined in the stat panel diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 0c26dc9a84b..7c542c14e3e 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -116,6 +116,9 @@ if(mob.buckled) //if we're buckled to something, tell it we moved. return mob.buckled.relaymove(mob, direct) + if(mob.remote_control) //we're controlling something, our movement is relayed to it + return mob.remote_control.relaymove(mob, direct) + if(!mob.canmove) return 0 diff --git a/icons/Chrono_Legionairre.dmi b/icons/Chrono_Legionairre.dmi new file mode 100644 index 00000000000..77f8bdfeeeb Binary files /dev/null and b/icons/Chrono_Legionairre.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 7bf76901789..f1a60e810d8 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/back.dmi b/icons/mob/back.dmi index d613dedcefe..e8d4b61b58b 100644 Binary files a/icons/mob/back.dmi and b/icons/mob/back.dmi differ diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index 5c3f4f14fa1..4474ee9dee8 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/items_lefthand.dmi b/icons/mob/items_lefthand.dmi index 19c313fbf4c..b8f19bf080c 100644 Binary files a/icons/mob/items_lefthand.dmi and b/icons/mob/items_lefthand.dmi differ diff --git a/icons/mob/items_righthand.dmi b/icons/mob/items_righthand.dmi index cb7b5a8acfe..d8b02e4e920 100644 Binary files a/icons/mob/items_righthand.dmi and b/icons/mob/items_righthand.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 390f3f00953..99d27ba25c6 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi index c0b2de054c7..e83029f9fe5 100644 Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ diff --git a/icons/obj/chronos.dmi b/icons/obj/chronos.dmi new file mode 100644 index 00000000000..dcc3984fd02 Binary files /dev/null and b/icons/obj/chronos.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 7e6dcb6a65d..774e2c7f9c1 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index 1767a121202..21b71bf0c87 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 598999653f8..d723b6d6286 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/tgstation.dme b/tgstation.dme index a0ecfa42024..e0d96f0935e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -579,6 +579,7 @@ #include "code\game\objects\items\weapons\AI_modules.dm" #include "code\game\objects\items\weapons\airlock_painter.dm" #include "code\game\objects\items\weapons\cards_ids.dm" +#include "code\game\objects\items\weapons\chrono_eraser.dm" #include "code\game\objects\items\weapons\cigs_lighters.dm" #include "code\game\objects\items\weapons\clown_items.dm" #include "code\game\objects\items\weapons\cosmetics.dm" @@ -843,6 +844,7 @@ #include "code\modules\clothing\shoes\colour.dm" #include "code\modules\clothing\shoes\magboots.dm" #include "code\modules\clothing\shoes\miscellaneous.dm" +#include "code\modules\clothing\spacesuits\chronosuit.dm" #include "code\modules\clothing\spacesuits\hardsuit.dm" #include "code\modules\clothing\spacesuits\miscellaneous.dm" #include "code\modules\clothing\spacesuits\ninja.dm"