mirror of
https://github.com/goonstation/goonstation-2020.git
synced 2026-07-10 22:52:21 +01:00
1169 lines
33 KiB
Plaintext
1169 lines
33 KiB
Plaintext
/*
|
|
Contents:
|
|
Skull of Souls
|
|
Tommyize proc
|
|
Donald Trumpet
|
|
SpyGUI
|
|
Chemistry spectrometer prototype
|
|
Retractable solar panel prototype
|
|
Fibre wire
|
|
*/
|
|
|
|
|
|
//////////////////////////////
|
|
//The pretty darn mean skull
|
|
//That's nice to ghosts
|
|
// Yay
|
|
////////////////////////////
|
|
/obj/item/soulskull
|
|
name = "ominous skull"
|
|
desc = "This skull gives you the heebie-jeebies."
|
|
icon = 'icons/obj/surgery.dmi'
|
|
icon_state = "skull_ominous"
|
|
var/being_mean = 0
|
|
|
|
attack_hand(mob/M)
|
|
if(!being_mean)
|
|
..()
|
|
M.show_text("<B><I>It burns...!</I></B>", "red")
|
|
if(ishuman(M)) evil_act(M)
|
|
/* oops didn't quite think this through
|
|
attackby(obj/item/W as obj, mob/user as mob)
|
|
if (istype(W, /obj/item/parts/robot_parts/leg))
|
|
var/obj/machinery/bot/skullbot/B = new /obj/machinery/bot/skullbot
|
|
B.icon = icon('icons/obj/aibots.dmi', "skullbot-ominous")
|
|
B.name = "ominous skullbot"
|
|
boutput(user, "<span style=\"color:blue\">You add [W] to [src]. That's neat.</span>")
|
|
B.set_loc(get_turf(user))
|
|
qdel(W)
|
|
qdel(src)
|
|
return
|
|
*/
|
|
proc/evil_act(mob/living/carbon/human/H)
|
|
|
|
var/list/mob/dead/observer/possible_targets = list()
|
|
var/list/mob/dead/observer/priority_targets = list()
|
|
|
|
|
|
if(ticker && ticker.mode) //Yes, I'm sure my runtimes will matter if the goddamn TICKER is gone.
|
|
for(var/datum/mind/M in (ticker.mode.Agimmicks | ticker.mode.traitors)) //We want an EVIL ghost
|
|
if(!M.dnr && M.current && isobserver(M.current) && M.current.client && M.special_role != "vampthrall" && M.special_role != "mindslave")
|
|
priority_targets.Add(M.current)
|
|
|
|
if(!priority_targets.len) //Okay, fine. Any ghost. *sigh
|
|
for(var/mob/dead/observer/O in mobs)
|
|
if(O && O.client && O.mind && !O.mind.dnr)
|
|
possible_targets.Add(O)
|
|
|
|
|
|
if(!priority_targets.len && !possible_targets.len) return //Gotta have a ghostie
|
|
|
|
being_mean = 1
|
|
H.canmove = 0
|
|
H.drop_item(src)
|
|
src.set_loc(H.loc)
|
|
src.layer = EFFECTS_LAYER_4
|
|
playsound(src.loc, 'sound/ambience/spooky/Void_Calls.ogg', 40, 1)
|
|
SPAWN_DBG(0) animate_levitate(src, -1)
|
|
H.emote("scream")
|
|
|
|
H.changeStatus("weakened", 10 SECONDS)
|
|
|
|
SPAWN_DBG(7 SECONDS)
|
|
if(!H)
|
|
being_mean = 0
|
|
return
|
|
H.emote("faint")
|
|
H.changeStatus("paralysis", 150)
|
|
H.show_text("<I><font size=5>You feel your mind drifting away from your body!</font></I>", "red")
|
|
|
|
playsound(src.loc, 'sound/effects/ghost.ogg', 50, 1)
|
|
|
|
if(!H.mind)
|
|
H.ghostize()
|
|
else
|
|
if(priority_targets.len) //Do we have an evil ghost?
|
|
H.mind.swap_with(pick(priority_targets))
|
|
H.show_text("<I><B>You hear a sinister voice in your head... \"I have brought you back to do evil once more!\"</B></I>")
|
|
else if(possible_targets.len) //Do we have a plain ol' ghost?
|
|
H.mind.swap_with(pick(possible_targets))
|
|
else //How the fuck did we even get here??
|
|
H.ghostize()
|
|
|
|
SPAWN_DBG(1.5 SECONDS) playsound(src.loc, 'sound/effects/ghostlaugh.ogg', 70, 1)
|
|
flick("skull_ominous_explode", src)
|
|
sleep(30)
|
|
qdel(src)
|
|
|
|
//////////////////////////////
|
|
//Tommyize
|
|
////////////////////////////
|
|
proc/Create_Tommyname()
|
|
return pick("Toh", "Tho", "To") + pick("mmh", "mh", "mm", "m") + pick("i", "eh", "yh", "ee", "u") + " " + pick("Wa", "Wi", "Wu", "Wee", "We") + pick("z", "zh", "se", "seh") + pick("oo", "oh", "eeh", "au", "ay", "uu", "uh")
|
|
|
|
/mob/proc/tommyize()
|
|
src.transforming = 1
|
|
src.canmove = 0
|
|
src.invisibility = 101
|
|
for(var/obj/item/clothing/O in src)
|
|
src.u_equip(O)
|
|
if (O)
|
|
O.set_loc(src.loc)
|
|
O.dropped(src)
|
|
O.layer = initial(O.layer)
|
|
|
|
var/mob/living/carbon/human/tommy/T = new(src.loc)
|
|
if(src.mind)
|
|
src.mind.transfer_to(T)
|
|
else
|
|
T.key = src.key
|
|
|
|
SPAWN_DBG(1 SECOND)
|
|
qdel(src)
|
|
|
|
/mob/living/carbon/human/proc/tommyize_reshape()
|
|
//Set up the new appearance
|
|
var/datum/appearanceHolder/AH = new
|
|
AH.gender = "male"
|
|
AH.customization_first = "Dreadlocks"
|
|
AH.gender = "male"
|
|
AH.s_tone = "#FAD7D0"
|
|
AH.owner = src
|
|
AH.parentHolder = src.bioHolder
|
|
|
|
src.gender = "male"
|
|
src.real_name = Create_Tommyname()
|
|
src.sound_list_laugh = list('sound/voice/tommy_hahahah.ogg', 'sound/voice/tommy_hahahaha.ogg')
|
|
src.sound_list_scream = list('sound/voice/tommy_you-are-tearing-me-apart-lisauh.ogg', 'sound/voice/tommy_did-not-hit-hehr.ogg')
|
|
src.sound_list_flap = list('sound/voice/tommy_weird-chicken-noise.ogg')
|
|
|
|
for(var/obj/item/clothing/O in src)
|
|
src.u_equip(O)
|
|
if (O)
|
|
if(istype(O, /obj/item/clothing/shoes/black) || istype(O, /obj/item/clothing/under/suit))
|
|
O.cant_drop = 1
|
|
O.cant_other_remove = 1
|
|
O.cant_self_remove = 1
|
|
continue
|
|
|
|
O.set_loc(src.loc)
|
|
O.dropped(src)
|
|
O.layer = initial(O.layer)
|
|
|
|
src.equip_if_possible(new /obj/item/clothing/shoes/black {cant_drop = 1; cant_other_remove = 1; cant_self_remove = 1} (src), slot_shoes)
|
|
src.equip_if_possible(new /obj/item/clothing/under/suit {cant_drop = 1; cant_other_remove = 1; cant_self_remove = 1} (src), slot_w_uniform)
|
|
src.equip_if_possible(new /obj/item/football(src), slot_in_backpack)
|
|
|
|
src.sound_scream = 'sound/voice/tommy_you-are-tearing-me-apart-lisauh.ogg'
|
|
src.sound_fingersnap = 'sound/voice/tommy_did-not-hit-hehr.ogg'
|
|
|
|
if(src.bioHolder)
|
|
src.bioHolder.mobAppearance = AH
|
|
src.bioHolder.AddEffect("accent_tommy")
|
|
SPAWN_DBG(1 SECOND)
|
|
src.bioHolder.mobAppearance.UpdateMob()
|
|
|
|
//////////////////////////////
|
|
//Tommy gun
|
|
////////////////////////////
|
|
|
|
/datum/projectile/tommy
|
|
name = "space-tommy disruption"
|
|
icon = 'icons/obj/projectiles.dmi'
|
|
icon_state = "random_thing"
|
|
//How much of a punch this has, tends to be seconds/damage before any resist
|
|
power = 10
|
|
//How much ammo this costs
|
|
cost = 10
|
|
//How fast the power goes away
|
|
dissipation_rate = 1
|
|
//How many tiles till it starts to lose power
|
|
dissipation_delay = 10
|
|
//Kill/Stun ratio
|
|
ks_ratio = 0.0
|
|
//name of the projectile setting, used when you change a guns setting
|
|
sname = "Tommify"
|
|
//file location for the sound you want it to play
|
|
shot_sound = 'sound/voice/tommy_hauh.ogg'
|
|
//How many projectiles should be fired, each will cost the full cost
|
|
shot_number = 1
|
|
//What is our damage type
|
|
damage_type = 0
|
|
//With what % do we hit mobs laying down
|
|
hit_ground_chance = 10
|
|
//Can we pass windows
|
|
window_pass = 0
|
|
|
|
on_hit(atom/hit)
|
|
if(ishuman(hit))
|
|
hit:tommyize_reshape()
|
|
playsound(hit.loc, 'sound/voice/tommy_hey-everybody.ogg', 50, 1)
|
|
else if(ismob(hit))
|
|
hit:tommyize()
|
|
playsound(hit.loc, 'sound/voice/tommy_hey-everybody.ogg', 50, 1)
|
|
|
|
///////////////////////////////////////Tommy Gun
|
|
|
|
/obj/item/gun/energy/tommy_gun
|
|
name = "Tommy Gun"
|
|
icon = 'icons/obj/gun.dmi'
|
|
icon_state = "tommy1"
|
|
m_amt = 4000
|
|
rechargeable = 1
|
|
force = 0.0
|
|
desc = "It smells of cheap cologne and..."
|
|
|
|
New()
|
|
cell = new/obj/item/ammo/power_cell/high_power
|
|
current_projectile = new/datum/projectile/tommy
|
|
projectiles = list(new/datum/projectile/tommy)
|
|
..()
|
|
|
|
update_icon()
|
|
return
|
|
|
|
shoot(var/target,var/start,var/mob/user,var/POX,var/POY)
|
|
for(var/mob/O in AIviewers(user, null))
|
|
O.show_message("<span style=\"color:red\"><B>[user] fires the [src] at [target]!</B></span>", 1, "<span style=\"color:red\">You hear a loud crackling noise.</span>", 2)
|
|
sleep(1)
|
|
return ..(target, start, user)
|
|
|
|
update_icon()
|
|
if(cell)
|
|
src.icon_state = "tommy[src.cell.charge > 0]"
|
|
return
|
|
|
|
///////////////////////////////////////Analysis datum for the spectrometer
|
|
|
|
/datum/spectro_analysis
|
|
|
|
proc/analyze_reagents(var/datum/reagents/R, var/check_recipes = 0)
|
|
if(R && R.reagent_list && R.reagent_list.len)
|
|
if(check_recipes)
|
|
. = analyze_reagent_components(R.reagent_list)
|
|
else
|
|
. = analyze_reagent_list(R.reagent_list)
|
|
|
|
|
|
|
|
//Analyze the recipe for each reagent id. If there's more than one id this will be a fucking mess.
|
|
proc/analyze_reagent_components(var/list/reagent_ids)
|
|
if(reagent_ids.len)
|
|
var/output = list()
|
|
for (var/id in reagent_ids)
|
|
|
|
var/datum/chemical_reaction/recipe = chem_reactions_by_id[id]
|
|
if(recipe && recipe.required_reagents && recipe.required_reagents.len)
|
|
analyze_reagent_list(recipe.required_reagents, output)
|
|
else
|
|
for(var/i=0, i<rand(2,7), i++) //If it doesn't have a recipe, just spit out some random data
|
|
analyze_single(output, md5(rand(100,100000)))
|
|
|
|
return output
|
|
|
|
return null
|
|
|
|
//Calculates the result for every reagent ID in a list
|
|
proc/analyze_reagent_list(var/list/reagent_ids, var/list/output)
|
|
if(reagent_ids.len)
|
|
if(!output) output = list()
|
|
for(var/RID in reagent_ids)
|
|
if(reagents_cache[RID])
|
|
output = analyze_single(output, RID)
|
|
else
|
|
logTheThing("debug", null, null, "<B>SpyGuy/spectro:</B> attempted to analyze invalid reagent id: [RID]")
|
|
|
|
return output
|
|
|
|
//This is mainly a helper
|
|
proc/analyze_single(var/list/base, var/id)
|
|
var/hash = md5("AReally[id]ShittySalt")
|
|
var/listPos = calc_start_point(hash)
|
|
|
|
for(var/i=1, i <= lentext(hash), i+=2)
|
|
var/block = copytext(hash, i, i+2)
|
|
if (isnull(base["[listPos]"]))
|
|
base["[listPos]"] = hex2num(block)
|
|
else
|
|
base["[listPos]"] += hex2num(block)
|
|
listPos += (hex2num(copytext(block,1,2)) + hex2num(copytext(block,2)))
|
|
return base
|
|
|
|
//So is this
|
|
proc/calc_start_point(var/hash)
|
|
for(var/i = 1; i <= lentext(hash); i++)
|
|
var/temp = copytext(hash, i, i+1)
|
|
temp = hex2num(temp)
|
|
. += temp
|
|
|
|
. = round(. * 1.5)
|
|
|
|
/////////////////////////////// Trigger
|
|
|
|
/obj/trigger
|
|
name = "trigger"
|
|
desc = "warning"
|
|
icon = 'icons/mob/screen1.dmi'
|
|
icon_state = "x2"
|
|
anchored = 1
|
|
invisibility = 101
|
|
event_handler_flags = USE_HASENTERED
|
|
|
|
HasEntered(atom/movable/AM)
|
|
..()
|
|
on_trigger(AM)
|
|
|
|
proc/on_trigger(var/atom/movable/triggerer)
|
|
|
|
|
|
/obj/trigger/critter //Wakes up all critters in an area
|
|
name = "critter trigger"
|
|
var/area/assigned_area = null
|
|
|
|
on_trigger(atom/movable/triggerer)
|
|
if(isliving(triggerer) || locate(/mob) in triggerer)
|
|
if(!assigned_area) assigned_area = get_area(src)
|
|
assigned_area.wake_critters()
|
|
|
|
/obj/trigger/throw
|
|
name = "throw trigger"
|
|
var/throw_dir = NORTH
|
|
|
|
on_trigger(var/atom/movable/triggerer)
|
|
if(isobserver(triggerer)) return
|
|
var/atom/target = get_edge_target_turf(src, src.throw_dir)
|
|
SPAWN_DBG(0)
|
|
if (target)
|
|
triggerer.throw_at(target, 50, 1)
|
|
|
|
|
|
/obj/trigger/cluwnegib
|
|
name = "floor cluwne trigger"
|
|
desc = "The name belies the fact that floor cluwnes are not real."
|
|
|
|
on_trigger(var/atom/movable/triggerer)
|
|
if(isobserver(triggerer)) return
|
|
var/mob/M = triggerer
|
|
if(istype(M))
|
|
M.cluwnegib(10)
|
|
|
|
/obj/trigger/badmantrigger
|
|
name = "Death Badman Meeting Zone"
|
|
desc = "He's very angry he lost the election."
|
|
var/activated = 0
|
|
|
|
on_trigger(var/atom/movable/triggerer)
|
|
//Sanity check, thanks readster.
|
|
if(isobserver(triggerer)) return
|
|
var/mob/M = triggerer
|
|
if(!istype(M))
|
|
return
|
|
if(istype(M, /mob/living/carbon/human) && !activated)
|
|
switch(M.ckey)
|
|
if("hydrofloric")
|
|
return
|
|
if("kyle2143")
|
|
return
|
|
if("johnwarcrimes")
|
|
return
|
|
else
|
|
activated = 1
|
|
sleep(20)
|
|
var/startx = 1
|
|
var/starty = 1
|
|
var/mob/badmantarget = M
|
|
boutput(badmantarget, "<span style=\"color:black\"> <B> You hear a voice in your head, 'You're not supposed to be here'. </B>")
|
|
playsound(badmantarget, 'sound/misc/american_patriot.ogg', 50, 1, -1)
|
|
sleep(100)
|
|
startx = badmantarget.x - rand(-11, 11)
|
|
starty = badmantarget.y - rand(-11, 11)
|
|
var/turf/pickedstart = locate(startx, starty, badmantarget.z)
|
|
new /obj/badman(pickedstart, badmantarget)
|
|
sleep(150)
|
|
activated = 0
|
|
|
|
|
|
/obj/trigger/readsteroffice
|
|
name = "that horrible thing in Readster's office trigger"
|
|
desc = "There's something terrible here."
|
|
|
|
var/running = 0
|
|
|
|
on_trigger(var/atom/movable/triggerer)
|
|
//Sanity check
|
|
if(isobserver(triggerer) || running) return
|
|
var/mob/M = triggerer
|
|
if(!istype(M))
|
|
return
|
|
if(istype(M, /mob/living/carbon/human))
|
|
var/mob/living/carbon/human/H = M
|
|
H.unkillable = 0
|
|
//Get the area and ensure it's the correct type. Otherwise straight-up gib.
|
|
var/area/centcom/offices/readster/readster_office = get_area(src)
|
|
if(!istype(readster_office))
|
|
M.cluwnegib(10)
|
|
return
|
|
|
|
try
|
|
running = 1
|
|
var/obj/machinery/door/unpowered/D = locate() in readster_office
|
|
//Close the door
|
|
close_door(D)
|
|
sleep(15)
|
|
|
|
//Lights out
|
|
for(var/obj/machinery/light/L in readster_office)
|
|
L.seton(0)
|
|
sleep(30)
|
|
|
|
//Here comes the meanie
|
|
M.mind.karma -= 1000 //stay out of my office!!
|
|
M.cluwnegib(10)
|
|
sleep(20)
|
|
|
|
//Lights on
|
|
for(var/obj/machinery/light/L in readster_office)
|
|
L.seton(1)
|
|
sleep(10)
|
|
D.locked = 0
|
|
D.open()
|
|
running = 0
|
|
catch
|
|
//The best error handling
|
|
if(M) M.cluwnegib(10)
|
|
running = 0
|
|
|
|
|
|
proc/close_door(var/obj/machinery/door/unpowered/D)
|
|
if(istype(D))
|
|
var/turf/T = get_turf(D)
|
|
T.set_density(1)
|
|
for(var/mob/living/L in T)
|
|
L.set_loc(get_step(D, WEST))
|
|
|
|
D.close()
|
|
D.locked = 1
|
|
T.set_density(0)
|
|
|
|
////////////////////////////// Donald Trumpet
|
|
/datum/projectile/energy_bolt_v/trumpet
|
|
name = "trumpet bolt"
|
|
shot_sound = 'sound/musical_instruments/Bikehorn_2.ogg'
|
|
|
|
on_hit(atom/hit)
|
|
..()
|
|
|
|
if(ishuman(hit))
|
|
var/mob/living/carbon/human/H = hit
|
|
if(!istype(H.head, /obj/item/clothing/head/wig))
|
|
var/obj/item/clothing/head/wig/W = H.create_wig()
|
|
H.bioHolder.mobAppearance.customization_first = "None"
|
|
H.cust_one_state = customization_styles["None"]
|
|
H.drop_from_slot(H.head)
|
|
H.force_equip(W, H.slot_head)
|
|
H.set_clothing_icon_dirty()
|
|
|
|
/obj/item/gun/energy/dtrumpet
|
|
name = "Donald Trumpet"
|
|
desc = "You can tell this gun has been fired!"
|
|
icon = 'icons/obj/instruments.dmi'
|
|
icon_state = "trumpet"
|
|
New()
|
|
cell = new/obj/item/ammo/power_cell/high_power
|
|
current_projectile = new/datum/projectile/energy_bolt_v/trumpet
|
|
projectiles = list(new/datum/projectile/energy_bolt_v/trumpet)
|
|
..()
|
|
|
|
////////////////////////////// Power machine
|
|
/obj/machinery/power/debug_generator
|
|
name = "mysterious petrol generator"
|
|
desc = "Holds untold powers. Literally. Untold power. Get it? Power. Watts? Ok, fine. This thing spits out unlimited watt-volts!! There. I said it!"
|
|
icon_state = "ggenoff"
|
|
density = 1
|
|
var/generating = 0
|
|
New()
|
|
..()
|
|
//UnsubscribeProcess()
|
|
attack_hand(mob/user)
|
|
if(!user) return
|
|
generating = (input("Select the amount of power this machine should generate (in MW))", "Playing with power") as num) * 1e6
|
|
if(generating > 0)
|
|
SubscribeToProcess()
|
|
powernet = get_direct_powernet()
|
|
icon_state = "ggenoff"
|
|
else
|
|
UnsubscribeProcess()
|
|
powernet = null
|
|
icon_state = "ggen"
|
|
|
|
process()
|
|
..()
|
|
if(!generating) UnsubscribeProcess()
|
|
add_avail(generating)
|
|
|
|
////////////////////////////// Spy GUI (ha ha ha ha)
|
|
#define INIT_CHECK if(!src.initialized) src.initialize()
|
|
/datum/spyGUI
|
|
var/target_file = null
|
|
var/desired_file = ""
|
|
var/target_window = ""
|
|
var/target_params = ""
|
|
var/list/mob/subscribed_mobs = new
|
|
var/list/mob/connecting = new
|
|
var/datum/master = null
|
|
|
|
var/max_retries = 5
|
|
var/time_per_try = 2
|
|
|
|
var/validate_user = 0
|
|
var/initialized = 0
|
|
|
|
New(var/filename, var/windowname, var/parameters, var/datum/master)
|
|
..()
|
|
target_window = windowname
|
|
target_params = parameters
|
|
desired_file = filename
|
|
src.master = master
|
|
|
|
proc/initialize()
|
|
target_file = grabResource(desired_file)
|
|
initialized = 1
|
|
|
|
proc/getFile()
|
|
if(!target_file)
|
|
target_file = grabResource(desired_file)
|
|
return target_file
|
|
|
|
|
|
proc/displayInterface(var/mob/target, var/initData)
|
|
INIT_CHECK //Initialize the SpyGUI instance on use
|
|
if((target in connecting))
|
|
return
|
|
connecting[target] = initData
|
|
var/retries = max_retries
|
|
var/extrasleep = 0
|
|
target.Browse(getFile(), "window=[target_window];[target_params]")
|
|
onclose(target, target_window, src)
|
|
|
|
do
|
|
if(winexists(target, "[target_window].browser")) //Fuck if I know
|
|
target << output("\ref[src]", "[target_window].browser:setRef")
|
|
sleep(time_per_try + extrasleep++)
|
|
while(retries-- > 0 && target in connecting) //Keep trying to send the UI update until it times out or they get it.
|
|
|
|
if(target in connecting)
|
|
connecting -= target
|
|
target.Browse(null, target_window)
|
|
|
|
proc/unsubscribeTarget(var/mob/target, close=1)
|
|
INIT_CHECK //Initialize the SpyGUI instance on use
|
|
if(close)
|
|
target.Browse(null, target_window)
|
|
subscribed_mobs -= target
|
|
|
|
|
|
Topic(href, href_list)
|
|
INIT_CHECK //Initialize the SpyGUI instance on use
|
|
..()
|
|
DEBUG_MESSAGE("Received: [href]")
|
|
if (href_list["ackref"] )
|
|
var/D = connecting[usr]
|
|
if(D)
|
|
connecting -= usr
|
|
subscribed_mobs |= usr
|
|
sendData(usr, D, "setUIState")
|
|
return
|
|
if (href_list["close"])
|
|
subscribed_mobs -= usr
|
|
|
|
|
|
if(master != src)
|
|
master.Topic(href, href_list) //Pass the href on
|
|
|
|
proc/validateSubscriber(var/mob/sub)
|
|
if(!sub.client) //If the subscriber lacks a client then rip they
|
|
return 0
|
|
|
|
if(!validate_user)
|
|
return 1
|
|
|
|
. = 1
|
|
if(sub.stat) . = 0 //Not dead / unconscious
|
|
if ( . && istype(master, /atom)) //Range check.
|
|
. = (sub in range(1, master))
|
|
|
|
proc/sendToSubscribers(var/data, var/handler)
|
|
INIT_CHECK //Initialize the SpyGUI instance on use
|
|
//DEBUG_MESSAGE("Sending: [data] to [handler ? handler : "-nothing-"]")
|
|
for(var/mob/M in subscribed_mobs)
|
|
if(validateSubscriber(M))
|
|
sendData(M, data, handler)
|
|
else
|
|
unsubscribeTarget(M)
|
|
|
|
proc/sendData(var/mob/target, var/data, var/handler)
|
|
var/list/L = new
|
|
L += handler
|
|
L += data
|
|
var/O = list2params(L)
|
|
target << output(O, "[target_window].browser:receiveData")
|
|
|
|
#undef INIT_CHECK
|
|
|
|
#define STAT_STANDBY 0
|
|
#define STAT_MOVING 1
|
|
#define STAT_EXTENDED 2
|
|
|
|
#define DEFAULT_ANIMATION_TIME 10
|
|
////////////////////////////// Solar Panel thingamajig
|
|
/obj/solar_control
|
|
name = "solar panel servo"
|
|
desc = "This machine contains a neatly-folded solar panel, for use when the ship is at little risk of external impacts and low on power."
|
|
//invisibility = 100
|
|
icon = 'icons/obj/machines/nuclear.dmi'
|
|
icon_state = "engineoff"
|
|
var/extension_dir = WEST
|
|
var/num_panels = 4
|
|
var/panel_width = 2
|
|
var/panel_length = 5
|
|
var/panel_space = 1
|
|
var/controller_padding = 1
|
|
var/station_padding = 2
|
|
|
|
var/status = 0
|
|
|
|
var/list/atom/created_atoms = list()
|
|
|
|
//TODO:
|
|
//Pooling and reuse of components
|
|
|
|
////Debug verbs
|
|
/obj/solar_control/verb/extend()
|
|
set src in range(1)
|
|
set category = "Local"
|
|
extend_panel()
|
|
|
|
/obj/solar_control/verb/retract()
|
|
set src in range(1)
|
|
set category = "Local"
|
|
retract_panel()
|
|
|
|
/obj/solar_control/proc/extend_panel()
|
|
if(status != STAT_STANDBY) return
|
|
icon_state = "engineon"
|
|
status = STAT_MOVING
|
|
var/paneldir1 = turn(extension_dir, 90)
|
|
var/paneldir2 = turn(extension_dir, -90)
|
|
var/list/turf/panelturfs = list()
|
|
var/turf/walker = get_turf(src)
|
|
DEBUG_MESSAGE("Extending panel at [showCoords(src.x, src.y, src.z)]. extension_dir: [extension_dir] ([dir2text(extension_dir)]), paneldir1: [paneldir1] ([dir2text(paneldir1)]), paneldir2: [paneldir2] ([dir2text(paneldir2)])")
|
|
var/total_len = station_padding + controller_padding + (panel_space * (num_panels -1)) + num_panels * panel_width
|
|
DEBUG_MESSAGE("Determined total length of panel to be [total_len] tiles.")
|
|
|
|
//Create the initial padding
|
|
DEBUG_MESSAGE("Creating stationside padding.")
|
|
var/list/catwalk = list(/turf/simulated/floor/airless/plating/catwalk, /obj/grille/catwalk)
|
|
for(var/i = 0; i < station_padding;i++)
|
|
move_create_obj(catwalk, walker, extension_dir, extension_dir) //Then we walk outwards, creating stuff as we go along
|
|
walker = get_step(walker,extension_dir)
|
|
/*
|
|
if(i == 0)
|
|
SPAWN_DBG(0)
|
|
move_create_obj(list(new /obj/lattice{icon_state="lattice-dir-b"}), walker, paneldir1, paneldir2 | extension_dir)
|
|
move_create_obj(list(new /obj/lattice{icon_state="lattice-dir-b"}), walker, paneldir2, paneldir1 | turn(extension_dir, 180))
|
|
*/
|
|
|
|
DEBUG_MESSAGE("Creating panel segments.")
|
|
//Create the panels themselves
|
|
for(var/i = 0; i < num_panels; i++)
|
|
for(var/j = 0; j < (panel_space + panel_width);j++)
|
|
move_create_obj(catwalk, walker, extension_dir, (j >= panel_space) ? paneldir1 : extension_dir)
|
|
walker = get_step(walker, extension_dir)
|
|
if(j >= panel_space) panelturfs += walker
|
|
|
|
DEBUG_MESSAGE("Creating controller padding")
|
|
for(var/i = 0; i < controller_padding; i++)
|
|
move_create_obj(catwalk, walker, extension_dir, extension_dir) //Then we walk outwards, creating stuff as we go along
|
|
walker = get_step(walker,extension_dir)
|
|
|
|
|
|
DEBUG_MESSAGE("Creating solar panels")
|
|
var/list/solar_list = list(/turf/simulated/floor/airless/solar, /obj/machinery/power/solar)
|
|
for(var/turf/T in panelturfs)
|
|
SPAWN_DBG(0)
|
|
var/turf/w1 = T
|
|
var/turf/w2 = T
|
|
for(var/i = 0; i < panel_length; i++)
|
|
SPAWN_DBG(-1)
|
|
move_create_obj(solar_list, w1, paneldir1, paneldir1)
|
|
w1 = get_step(w1, paneldir1)
|
|
move_create_obj(solar_list, w2, paneldir2, paneldir2)
|
|
w2 = get_step(w2, paneldir2)
|
|
|
|
DEBUG_MESSAGE("Creating solar controller")
|
|
move_create_obj(list(/turf/simulated/floor/plating/airless, /obj/machinery/power/tracker), walker, extension_dir)
|
|
walker = get_step(walker,extension_dir)
|
|
SPAWN_DBG(0) move_create_obj(list(new /obj/lattice{icon_state="lattice-dir-b"}), walker, paneldir1, paneldir2)
|
|
move_create_obj(list(new /obj/lattice{icon_state="lattice-dir-b"}), walker, paneldir2, paneldir1)
|
|
|
|
status = STAT_EXTENDED
|
|
icon_state = "engineoff"
|
|
|
|
/obj/solar_control/proc/retract_panel()
|
|
if(status != STAT_EXTENDED) return
|
|
status = STAT_MOVING
|
|
|
|
var/list/atom/panels = get_panels()
|
|
|
|
for(var/i = panels.len; i > 0; i--)
|
|
var/list/atom/L = panels[i]
|
|
for(var/atom/A in L)
|
|
SPAWN_DBG(0)
|
|
move_and_delete_object(A)
|
|
sleep(DEFAULT_ANIMATION_TIME)
|
|
|
|
while(created_atoms.len > 0)
|
|
var/atom/A = created_atoms[created_atoms.len]
|
|
created_atoms.len--
|
|
if(istype(A, /turf))
|
|
var/turf/T = A
|
|
T.ReplaceWithSpace()
|
|
else if(istype(A,/obj))
|
|
move_and_delete_object(A)
|
|
|
|
|
|
status = STAT_STANDBY
|
|
|
|
|
|
/obj/solar_control/proc/get_panels()
|
|
var/list/atom/out = list()
|
|
var/list/atom/temp
|
|
|
|
out.len = panel_length //A list containing all the solar panels sorted on distance from centreline
|
|
for(var/i = created_atoms.len; i > 0; i--)
|
|
var/atom/A = created_atoms[i]
|
|
if(istype(A, /obj/machinery/power/solar) || istype(A, /turf/simulated/floor/airless/solar))
|
|
var/dist = get_dist_from_centreline(A)
|
|
temp = out[dist]
|
|
if(!temp)
|
|
temp = list()
|
|
out[dist] = temp
|
|
temp += A
|
|
|
|
return out
|
|
|
|
/obj/solar_control/proc/move_and_delete_object(var/obj/O, var/animtime = DEFAULT_ANIMATION_TIME)
|
|
//calculate new px / py
|
|
if(istype(O, /turf))
|
|
var/turf/T = O
|
|
var/obj/movedummy/MD = unpool(/obj/movedummy)
|
|
MD.mimic_turf(T.type, 0)
|
|
MD.set_loc(T)
|
|
T.ReplaceWithSpace()
|
|
O = MD
|
|
|
|
var/tdir = get_reciprocal(O)
|
|
var/npx = 0
|
|
if(tdir & (EAST | WEST))
|
|
if(tdir & WEST)
|
|
npx = -32
|
|
else if(tdir & EAST)
|
|
npx = 32
|
|
var/npy = 0
|
|
if(tdir & (NORTH | SOUTH))
|
|
if(tdir & NORTH)
|
|
npy = 32
|
|
else if (tdir & SOUTH)
|
|
npy = -32
|
|
|
|
animate_slide(O, npx, npy, animtime)
|
|
sleep(animtime)
|
|
if(istype(O, /obj/movedummy))
|
|
pool(O)
|
|
else
|
|
qdel(O)
|
|
|
|
|
|
/obj/solar_control/proc/move_create_obj(var/list/atom/to_create, var/turf/startturf, var/movedir, var/setdir=null, var/animtime = DEFAULT_ANIMATION_TIME)
|
|
//calculate initial px / py
|
|
var/ipx = 0
|
|
if(movedir & (EAST | WEST))
|
|
if(movedir & WEST)
|
|
ipx = 32
|
|
else if(movedir & EAST)
|
|
ipx = -32
|
|
var/ipy = 0
|
|
if(movedir & (NORTH | SOUTH))
|
|
if(movedir & NORTH)
|
|
ipy = -32
|
|
else if (movedir & SOUTH)
|
|
ipy = 32
|
|
|
|
|
|
DEBUG_MESSAGE("Initial offsets calculated based on movedir: [movedir] ([dir2text(movedir)]) as ipx: [ipx], ipy: [ipy]")
|
|
var/is_turf = 0
|
|
var/turf/T = get_step(startturf, movedir)
|
|
var/turf_type = null
|
|
for(var/t_type in to_create)
|
|
if(ispath(t_type, /turf))
|
|
turf_type = t_type
|
|
break
|
|
SPAWN_DBG(0)
|
|
for(var/t_type in to_create)
|
|
var/obj/O
|
|
is_turf = ispath(t_type, /turf) //If it's a turf we need some special handling.
|
|
|
|
if(istype(t_type, /obj))
|
|
O = t_type
|
|
else if(ispath(t_type))
|
|
if(!is_turf)
|
|
O = new t_type(null)
|
|
else
|
|
var/obj/movedummy/MD = unpool(/obj/movedummy)
|
|
MD.mimic_turf(t_type, animtime)
|
|
O = MD
|
|
|
|
else if(!ispath(t_type))
|
|
CRASH("move_create_obj not provided with type")
|
|
if(!is_turf)
|
|
created_atoms += O
|
|
O.pixel_x = ipx
|
|
O.pixel_y = ipy
|
|
if(setdir)
|
|
O.dir = setdir
|
|
O.set_loc(T)
|
|
animate_slide(O, 0, 0, animtime, LINEAR_EASING)
|
|
|
|
playsound(T, "sound/effects/airbridge_dpl.ogg", 50, 1)
|
|
sleep(animtime)
|
|
if(turf_type)
|
|
DEBUG_MESSAGE("Creating [turf_type] at [showCoords(T.x, T.y, T.z)]")
|
|
var/turf/NT = new turf_type(T)
|
|
if(setdir) NT.dir = setdir
|
|
created_atoms += NT
|
|
|
|
|
|
//Helpers
|
|
/obj/solar_control/proc/get_reciprocal(var/atom/A)
|
|
var/d = get_dir(A,src)
|
|
d &= ~(extension_dir | turn(extension_dir, 180)) //Turn off the bits parallel to the extension_dir
|
|
if(!d) d = turn(extension_dir, 180) //If this wound up turning off all the bits, the dir is on the extension line
|
|
//Look, I'm Swedish, I don't know your goddamn mathwords
|
|
return d
|
|
|
|
/obj/solar_control/proc/get_dist_from_centreline(var/atom/A) //Finds the distance from the closest point on the extension line
|
|
if(extension_dir & (NORTH|SOUTH) )
|
|
.= abs(A.x - src.x)
|
|
else if ( extension_dir & (EAST|WEST) )
|
|
.= abs(A.y - src.y)
|
|
|
|
DEBUG_MESSAGE("get_dist from [showCoords(A.x, A.y, A.z)] returned: [.]")
|
|
|
|
|
|
//The dummy object that imitates a turf
|
|
/obj/movedummy
|
|
name = "Dummy object."
|
|
invisibility = 101
|
|
|
|
/obj/movedummy/pooled()
|
|
..()
|
|
invisibility = 101
|
|
|
|
/obj/movedummy/proc/mimic_turf(var/turf_type, var/TTL)
|
|
ASSERT(ispath(turf_type, /turf))
|
|
var/turf/T = turf_type
|
|
src.name = initial(T.name)
|
|
src.desc = initial(T.desc)
|
|
src.icon = initial(T.icon)
|
|
src.icon_state = initial(T.icon_state)
|
|
src.set_density(initial(T.density))
|
|
src.opacity = initial(T.opacity)
|
|
src.dir = initial(T.dir)
|
|
src.layer = initial(T.layer)
|
|
src.invisibility = 0
|
|
if(TTL)
|
|
SPAWN_DBG(TTL)
|
|
pool(src)
|
|
|
|
#undef STAT_STANDBY
|
|
#undef STAT_MOVING
|
|
#undef STAT_EXTENDED
|
|
#undef DEFAULT_ANIMATION_TIME
|
|
|
|
//Aw heck
|
|
|
|
|
|
////////////////////////////// Fibre wire
|
|
|
|
/*
|
|
TO DO:
|
|
- = Must have
|
|
? = Nice to have
|
|
* = Done
|
|
|
|
* Only allow someone to initiate a stranglehold from behind. Eg - someone facing NORTH should only be strangleable from SOUTHWEST, SOUTH and SOUTHEAST
|
|
* setTwoHanded does not really cover cases where both hands have contents during the switch, if done while the item is equipped. This needs handling
|
|
* There's some weirdness with dropping the garrote and trying to pick it back up calling attack_self() instead of the pick up proc. Test & fix whatever causes it.
|
|
* The art needs considerable improvement, good grief
|
|
* Stunned targets should not take facing into consideration
|
|
* Faster death
|
|
* Lower slowdown when opened
|
|
? Break free message in case whoever manages to avoid the strangle in the action bar
|
|
? Improve grab strangles to jostle the target mob slightly? Make it look more fierce. Make_Jittery() or something similar, perhaps?
|
|
*/
|
|
|
|
/obj/item/garrote
|
|
name = "fibre wire"
|
|
desc = "A sturdy wire between two handles. Could be used with both hands to really ruin someone's day."
|
|
w_class = 1
|
|
|
|
icon = 'icons/obj/items.dmi'
|
|
icon_state = "garrote0"
|
|
|
|
event_handler_flags = USE_GRAB_CHOKE | USE_FLUID_ENTER
|
|
special_grab = /obj/item/grab/garrote_grab
|
|
|
|
// Are we ready to do something mean here?
|
|
var/wire_readied = 0
|
|
|
|
|
|
/obj/item/garrote/proc/toggle_wire_readiness()
|
|
set_readiness(!wire_readied)
|
|
|
|
|
|
/obj/item/garrote/proc/set_readiness(new_readiness)
|
|
wire_readied = new_readiness
|
|
// Try to stretch the wire
|
|
if(!src.setTwoHanded(new_readiness))
|
|
wire_readied = 0
|
|
return
|
|
|
|
|
|
if(wire_readied)
|
|
playsound(usr, 'sound/items/garrote_twang.ogg', 25,5)
|
|
w_class = 4
|
|
else
|
|
drop_grab()
|
|
w_class = 1
|
|
|
|
update_state()
|
|
|
|
/obj/item/garrote/proc/update_state()
|
|
if(src.chokehold)
|
|
var/obj/item/grab/garrote_grab/GG = src.chokehold
|
|
if(!GG.extra_deadly)
|
|
icon_state = "garrote2"
|
|
//We're choking someone out - apply a hefty slowdown
|
|
src.setProperty("movespeed", 6)
|
|
else
|
|
icon_state = "garrote3"
|
|
// We're really putting our back into it now - apply a heftier slowdown
|
|
src.setProperty("movespeed", 12)
|
|
else
|
|
icon_state = "garrote[wire_readied]"
|
|
//Slow us down slightly when we have the thing readied to encourage late-readying
|
|
src.setProperty("movespeed", 1 * wire_readied)
|
|
|
|
|
|
|
|
/obj/item/garrote/proc/is_behind_target(var/mob/living/assailant, var/mob/living/target)
|
|
var/assailant_dir = get_dir(target, assailant)
|
|
var/target_rear = turn(target.dir, 180)
|
|
|
|
return (assailant_dir & target_rear) > 0 || target.lying || target.stat
|
|
|
|
|
|
// Try to grab someone
|
|
/obj/item/garrote/proc/attempt_grab(var/mob/living/assailant, var/mob/living/target)
|
|
// Can't strangle someone who doesn't exist. Or if we're already strangling someone.
|
|
// Also no strangling with flaccid wires, that's just weird.
|
|
|
|
if(!assailant || !target)
|
|
return 1
|
|
|
|
if(!wire_readied)
|
|
assailant.show_message("<span class='combat'>You have to have a firm grip of the wire before you can strangle [target]!</span>")
|
|
return 1
|
|
|
|
if(chokehold)
|
|
assailant.show_message("<span class='combat'>You're too busy strangling [chokehold.affecting] to strangle someone else!</span>")
|
|
return 1
|
|
|
|
// TODO: check that target has their back turned
|
|
if(is_behind_target(assailant, target))
|
|
// Try to grab a dude
|
|
actions.start(new/datum/action/bar/private/icon/garrote_target(target, src), assailant)
|
|
else
|
|
assailant.show_message("<span class='combat'>You have to be behind your target or they'll see you coming!</span>")
|
|
return 1
|
|
|
|
// Actually apply the grab (called via action bar)
|
|
/obj/item/garrote/try_grab(var/mob/living/target, var/mob/living/assailant)
|
|
if(..())
|
|
assailant.visible_message("<span class='combat bold'>[assailant] wraps \the [src] around [target]'s neck!</span>")
|
|
chokehold.state = GRAB_NECK
|
|
chokehold.upgrade_to_kill()
|
|
update_state()
|
|
|
|
// Drop the grab
|
|
/obj/item/garrote/drop_grab()
|
|
..()
|
|
update_state()
|
|
|
|
// It will crumple when dropped
|
|
/obj/item/garrote/dropped()
|
|
..()
|
|
set_readiness(0)
|
|
|
|
|
|
/obj/item/garrote/disposing()
|
|
drop_grab()
|
|
..()
|
|
|
|
// Repeatedly process when in a chokehold, to verify things are as they should be
|
|
/obj/item/garrote/process_grab()
|
|
..()
|
|
if(src.chokehold && src.loc != src.chokehold.assailant)
|
|
set_readiness(0)
|
|
|
|
// Change the size of the garrote or the posture
|
|
/obj/item/garrote/attack_self()
|
|
if(!chokehold)
|
|
..()
|
|
toggle_wire_readiness()
|
|
else
|
|
var/obj/item/grab/garrote_grab/GG = src.chokehold
|
|
GG.extra_deadly = !GG.extra_deadly
|
|
if(GG.extra_deadly)
|
|
GG.assailant.visible_message("<span class='combat bold'>[GG.assailant] tightens their grip on \the [src], it digs into [GG.affecting]'s neck!</span>")
|
|
else
|
|
GG.assailant.visible_message("<span class='combat bold'>[GG.assailant] releases their hold on [GG.affecting] slightly!</span>")
|
|
|
|
update_state()
|
|
|
|
/obj/item/garrote/attack(mob/target, mob/user, def_zone, is_special = 0)
|
|
attempt_grab(user, target)
|
|
|
|
// Need a nice drop button
|
|
/obj/item/garrote/Click(location, control, params)
|
|
|
|
if(src.chokehold)
|
|
if(usr == src.loc)
|
|
var/list/P = params2list(params)
|
|
var/x = text2num(P["icon-x"])
|
|
var/y = text2num(P["icon-y"])
|
|
DEBUG_MESSAGE("Click on [src] - params x,y: ([x], [y]) - [params]")
|
|
|
|
//The "drop" icon bounding box
|
|
// 16, 8
|
|
//CODER LOG: Click on the fibre wire - params x,y: (16, 8) - icon-x=16;icon-y=8;left=1;screen-loc=11:32,1:8
|
|
if((x >= 6 && x <= 28 ) && (y >= 4 && y <= 13 ))
|
|
drop_grab()
|
|
return
|
|
|
|
..()
|
|
|
|
|
|
/datum/action/bar/private/icon/garrote_target
|
|
duration = 10
|
|
interrupt_flags = INTERRUPT_MOVE | INTERRUPT_ACT | INTERRUPT_STUNNED
|
|
id = "garrote_target"
|
|
icon = 'icons/mob/critter_ui.dmi'
|
|
icon_state = "neck_over"
|
|
var/mob/living/target
|
|
var/obj/item/garrote/the_garrote
|
|
|
|
New(target, garrote)
|
|
src.target = target
|
|
the_garrote=garrote
|
|
..()
|
|
|
|
|
|
proc/check_conditions()
|
|
. = 0
|
|
if(get_dist(owner, target) > 1 || !target || !owner || !the_garrote || !the_garrote.wire_readied)
|
|
interrupt(INTERRUPT_ALWAYS)
|
|
. = 1
|
|
|
|
onUpdate()
|
|
..()
|
|
|
|
if(check_conditions())
|
|
return
|
|
|
|
onStart()
|
|
..()
|
|
if(check_conditions())
|
|
return
|
|
|
|
onEnd()
|
|
..()
|
|
if(check_conditions())
|
|
return
|
|
|
|
the_garrote.try_grab(target, owner)
|
|
|
|
// Special grab obj that doesn't care if it's in someone's hands
|
|
/obj/item/grab/garrote_grab
|
|
// No breaking out under own power
|
|
break_prob = 0
|
|
var/extra_deadly = 0
|
|
check()
|
|
if(!assailant || !affecting)
|
|
qdel(src)
|
|
return 1
|
|
|
|
if(!isturf(assailant.loc) || (!isturf(affecting.loc) || assailant.loc != affecting.loc && get_dist(assailant, affecting) > 1) )
|
|
qdel(src)
|
|
return 1
|
|
|
|
return 0
|
|
|
|
// An extra dangerous grab
|
|
process_kill(var/mob/living/carbon/human/H, mult = 1)
|
|
if(extra_deadly)
|
|
affecting.TakeDamage(zone="All", brute=rand(3, 7) * mult)
|
|
affecting.losebreath += (1 * mult)
|
|
if(prob(25))
|
|
// Wire digging into a neck.
|
|
take_bleeding_damage(affecting, assailant, rand(0, 20) * mult)
|
|
..()
|
|
|
|
attack_self(user)
|
|
|
|
|
|
/proc/trigger_anti_cheat(var/mob/M, var/message, var/external_alert = 1)
|
|
if(M)
|
|
message_admins("[key_name(M)] [message].")
|
|
logTheThing("admin", M, null, message)
|
|
logTheThing("diary", M, null, message, "admin")
|
|
|
|
if(external_alert)
|
|
//IRCbot alert, for fun
|
|
var/ircmsg[] = new()
|
|
ircmsg["key"] = M.key
|
|
ircmsg["name"] = M.real_name
|
|
ircmsg["msg"] = "[message] and got themselves got by the anti-cheat cluwne."
|
|
ircbot.export("admin", ircmsg)
|
|
|
|
M.cluwnegib(15, 1)
|
|
|
|
|
|
/proc/list_item_damage_stats()
|
|
var/list/L = typesof(/obj/item)
|
|
var/result = "<table><tr><th>Name</th><th>Type</th><th>Force</th><th>Stamina damage</th><th>Stamina cost</th><th>Damage Type</th><th>Throw force</th></tr>"
|
|
|
|
for(var/type in L)
|
|
result += {"<tr>
|
|
<td>[initial(type:name)]</td>
|
|
<td>[type]</td>
|
|
<td>[initial(type:force)]</td>
|
|
<td>[initial(type:stamina_damage)]</td>
|
|
<td>[initial(type:stamina_cost)]</td>
|
|
<td>[initial(type:hit_type)]</td>
|
|
<td>[initial(type:throwforce)]</td>
|
|
</tr>"}
|
|
|
|
result += "</table>"
|
|
|
|
usr.Browse(result, "window=item_dam_stats;size=400x400") |