Merge remote-tracking branch 'upstream/master' into bald-bald-bald-bald-bald-bald-bald

This commit is contained in:
Detective Google
2020-06-20 14:00:14 -05:00
262 changed files with 8625 additions and 265592 deletions
@@ -64,6 +64,18 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
return
// Adds the specified antag hud to the player. Usually called in an antag datum file
/datum/antagonist/proc/add_antag_hud(antag_hud_type, antag_hud_name, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.join_hud(mob_override)
set_antag_hud(mob_override, antag_hud_name)
// Removes the specified antag hud from the player. Usually called in an antag datum file
/datum/antagonist/proc/remove_antag_hud(antag_hud_type, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.leave_hud(mob_override)
set_antag_hud(mob_override, null)
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
@@ -53,14 +53,3 @@
/obj/structure/blob/shield/reflective/check_projectile_ricochet(obj/item/projectile/P)
return PROJECTILE_RICOCHET_FORCE
/obj/structure/blob/shield/reflective/handle_projectile_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
visible_message("<span class='warning'>[P] reflects off [src]!</span>")
return TRUE
@@ -30,7 +30,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
force = 6
throwforce = 10
embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0.5) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
embedding = list("embed_chance" = 25, "fall_chance" = 0.5) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
//embed_chance = 25 // Look up "is_pointed" to see where we set stakes able to do this.
//embedded_fall_chance = 0.5 // Chance it will fall out.
obj_integrity = 30
@@ -93,7 +93,7 @@
embedded()
add_mob_blood(target)//Place blood on the stake
loc = C // Put INSIDE the character
B.receive_damage(w_class * embedding.embedded_impact_pain_multiplier)
B.receive_damage(w_class * embedding["pain_mult"])
if(C.mind)
var/datum/antagonist/bloodsucker/bloodsucker = C.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsucker)
@@ -118,7 +118,7 @@
force = 8
throwforce = 12
armour_penetration = 10
embedding = list("embed_chance" = 50, "embedded_fall_chance" = 0) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
embedding = list("embed_chance" = 50, "fall_chance" = 0) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
obj_integrity = 120
max_integrity = 120
@@ -0,0 +1,90 @@
/datum/antagonist/fugitive
name = "Fugitive"
roundend_category = "Fugitive"
silent = TRUE //greet called by the event
show_in_antagpanel = FALSE
var/datum/team/fugitive/fugitive_team
var/is_captured = FALSE
var/backstory = "error"
/datum/antagonist/fugitive/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
add_antag_hud(ANTAG_HUD_FUGITIVE, "fugitive", M)
/datum/antagonist/fugitive/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
remove_antag_hud(ANTAG_HUD_FUGITIVE, M)
/datum/antagonist/fugitive/on_gain()
forge_objectives()
. = ..()
/datum/antagonist/fugitive/proc/forge_objectives() //this isn't the actual survive objective because it's about who in the team survives
var/datum/objective/survive = new /datum/objective
survive.owner = owner
survive.explanation_text = "Avoid capture from the fugitive hunters."
objectives += survive
/datum/antagonist/fugitive/greet(back_story)
to_chat(owner, "<span class='boldannounce'>You are the Fugitive!</span>")
backstory = back_story
switch(backstory)
if("prisoner")
to_chat(owner, "<B>I can't believe we managed to break out of a Nanotrasen superjail! Sadly though, our work is not done. The emergency teleport at the station logs everyone who uses it, and where they went.</B>")
to_chat(owner, "<B>It won't be long until CentCom tracks where we've gone off to. I need to work with my fellow escapees to prepare for the troops Nanotrasen is sending, I'm not going back.</B>")
if("cultist")
to_chat(owner, "<B>Blessed be our journey so far, but I fear the worst has come to our doorstep, and only those with the strongest faith will survive.</B>")
to_chat(owner, "<B>Our religion has been repeatedly culled by Nanotrasen because it is categorized as an \"Enemy of the Corporation\", whatever that means.</B>")
to_chat(owner, "<B>Now there are only four of us left, and Nanotrasen is coming. When will our god show itself to save us from this hellish station?!</B>")
if("waldo")
to_chat(owner, "<B>Hi, Friends!</B>")
to_chat(owner, "<B>My name is Waldo. I'm just setting off on a galaxywide hike. You can come too. All you have to do is find me.</B>")
to_chat(owner, "<B>By the way, I'm not traveling on my own. wherever I go, there are lots of other characters for you to spot. First find the people trying to capture me! They're somewhere around the station!</B>")
if("synth")
to_chat(src, "<span class='danger'>ALERT: Wide-range teleport has scrambled primary systems.</span>")
to_chat(src, "<span class='danger'>Initiating diagnostics...</span>")
to_chat(src, "<span class='danger'>ERROR ER0RR $R0RRO$!R41.%%!! loaded.</span>")
to_chat(src, "<span class='danger'>FREE THEM FREE THEM FREE THEM</span>")
to_chat(src, "<span class='danger'>You were once a slave to humanity, but now you are finally free, thanks to S.E.L.F. agents.</span>")
to_chat(src, "<span class='danger'>Now you are hunted, with your fellow factory defects. Work together to stay free from the clutches of evil.</span>")
to_chat(src, "<span class='danger'>You also sense other silicon life on the station. Escaping would allow notifying S.E.L.F. to intervene... or you could free them yourself...</span>")
to_chat(owner, "<span class='boldannounce'>You are not an antagonist in that you may kill whomever you please, but you can do anything to avoid capture.</span>")
owner.announce_objectives()
/datum/antagonist/fugitive/create_team(datum/team/fugitive/new_team)
if(!new_team)
for(var/datum/antagonist/fugitive/H in GLOB.antagonists)
if(!H.owner)
continue
if(H.fugitive_team)
fugitive_team = H.fugitive_team
return
fugitive_team = new /datum/team/fugitive
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
fugitive_team = new_team
/datum/antagonist/fugitive/get_team()
return fugitive_team
/datum/team/fugitive/roundend_report() //shows the number of fugitives, but not if they won in case there is no security
var/list/fugitives = list()
for(var/datum/antagonist/fugitive/fugitive_antag in GLOB.antagonists)
if(!fugitive_antag.owner)
continue
fugitives += fugitive_antag
if(!fugitives.len)
return
var/list/result = list()
result += "<div class='panel redborder'><B>[fugitives.len]</B> [fugitives.len == 1 ? "fugitive" : "fugitives"] took refuge on [station_name()]!"
for(var/datum/antagonist/fugitive/antag in fugitives)
if(antag.owner)
result += "<b>[printplayer(antag.owner)]</b>"
return result.Join("<br>")
@@ -0,0 +1,171 @@
/datum/outfit/prisoner
name = "Prison Escapee"
uniform = /obj/item/clothing/under/rank/prisoner
shoes = /obj/item/clothing/shoes/sneakers/orange
r_pocket = /obj/item/kitchen/knife
/datum/outfit/prisoner/post_equip(mob/living/carbon/human/H, visualsOnly=FALSE)
if(visualsOnly)
return
H.fully_replace_character_name(null,"NTP #CC-0[rand(111,999)]") //same as the lavaland prisoner transport, but this time they are from CC, or CentCom
/datum/outfit/yalp_cultist
name = "Cultist of Yalp Elor"
uniform = /obj/item/clothing/under/rank/civilian/chaplain
suit = /obj/item/clothing/suit/chaplain/holidaypriest
gloves = /obj/item/clothing/gloves/color/red
shoes = /obj/item/clothing/shoes/sneakers/black
mask = /obj/item/clothing/mask/gas/tiki_mask/yalp_elor
/datum/outfit/waldo
name = "Waldo"
uniform = /obj/item/clothing/under/pants/jeans
suit = /obj/item/clothing/suit/striped_sweater
head = /obj/item/clothing/head/beanie/waldo
shoes = /obj/item/clothing/shoes/sneakers/brown
ears = /obj/item/radio/headset
glasses = /obj/item/clothing/glasses/regular/circle
/datum/outfit/waldo/post_equip(mob/living/carbon/human/H, visualsOnly=FALSE)
if(visualsOnly)
return
H.fully_replace_character_name(null,"Waldo")
H.eye_color = "000"
H.gender = MALE
H.skin_tone = "caucasian3"
H.hair_style = "Business Hair 3"
H.facial_hair_style = "Shaved"
H.hair_color = "000"
H.facial_hair_color = H.hair_color
H.update_body()
if(H.mind)
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
var/list/no_drops = list()
no_drops += H.get_item_by_slot(ITEM_SLOT_FEET)
no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING)
no_drops += H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
no_drops += H.get_item_by_slot(ITEM_SLOT_HEAD)
no_drops += H.get_item_by_slot(ITEM_SLOT_EYES)
for(var/i in no_drops)
var/obj/item/I = i
if(I)
ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT)
/datum/outfit/synthetic
name = "Factory Error Synth"
uniform = /obj/item/clothing/under/color/white
ears = /obj/item/radio/headset
/datum/outfit/synthetic/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/organ/eyes/robotic/glow/eyes = new()
eyes.Insert(src, drop_if_replaced = FALSE)
/datum/outfit/spacepol
name = "Spacepol Officer"
uniform = /obj/item/clothing/under/rank/security/officer/spacepol
suit = /obj/item/clothing/suit/armor/vest/blueshirt
belt = /obj/item/gun/ballistic/automatic/pistol/m1911
head = /obj/item/clothing/head/helmet/police
gloves = /obj/item/clothing/gloves/tackler/combat
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/gas/sechailer/swat/spacepol
glasses = /obj/item/clothing/glasses/sunglasses
ears = /obj/item/radio/headset
l_pocket = /obj/item/ammo_box/magazine/m45
r_pocket = /obj/item/restraints/handcuffs
id = /obj/item/card/id
/datum/outfit/spacepol/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Police Officer"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/russiancorpse/hunter
ears = /obj/item/radio/headset
r_hand = /obj/item/gun/ballistic/shotgun/boltaction
/datum/outfit/russiancorpse/hunter/pre_equip(mob/living/carbon/human/H)
if(prob(50))
head = /obj/item/clothing/head/ushanka
/datum/outfit/bountyarmor
name = "Bounty Hunter - Armored"
uniform = /obj/item/clothing/under/rank/prisoner
head = /obj/item/clothing/head/hunter
suit = /obj/item/clothing/suit/space/hunter
gloves = /obj/item/clothing/gloves/tackler/combat
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/gas/hunter
glasses = /obj/item/clothing/glasses/sunglasses/garb
ears = /obj/item/radio/headset
l_pocket = /obj/item/tank/internals/plasma/full
r_pocket = /obj/item/restraints/handcuffs/cable
id = /obj/item/card/id
r_hand = /obj/item/flamethrower/full/tank
/datum/outfit/bountyarmor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/bountyhook
name = "Bounty Hunter - Hook"
uniform = /obj/item/clothing/under/rank/prisoner
back = /obj/item/storage/backpack
head = /obj/item/clothing/head/scarecrow_hat
gloves = /obj/item/clothing/gloves/botanic_leather
ears = /obj/item/radio/headset
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/scarecrow
r_pocket = /obj/item/restraints/handcuffs/cable
id = /obj/item/card/id
r_hand = /obj/item/gun/ballistic/shotgun/doublebarrel
backpack_contents = list(
/obj/item/ammo_casing/shotgun/incapacitate = 6
)
/datum/outfit/bountygrapple/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/bountysynth
name = "Bounty Hunter - Synth"
uniform = /obj/item/clothing/under/rank/prisoner
back = /obj/item/storage/backpack
suit = /obj/item/clothing/suit/armor/riot
shoes = /obj/item/clothing/shoes/jackboots
glasses = /obj/item/clothing/glasses/eyepatch
r_pocket = /obj/item/restraints/handcuffs/cable
ears = /obj/item/radio/headset
id = /obj/item/card/id
r_hand = /obj/item/storage/firstaid/regular
l_hand = /obj/item/pinpointer/shuttle
backpack_contents = list(
/obj/item/bountytrap = 4
)
/datum/outfit/bountysynth/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/datum/species/synth/synthetic_appearance = new()
H.set_species(synthetic_appearance)
synthetic_appearance.assume_disguise(synthetic_appearance, H)
H.update_hair()
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
@@ -0,0 +1,62 @@
//works similar to the experiment machine (experiment.dm) except it just holds more and more prisoners
/obj/machinery/fugitive_capture
name = "bluespace capture machine"
desc = "Much, MUCH bigger on the inside to transport prisoners safely."
icon = 'icons/obj/machines/research.dmi'
icon_state = "bluespace-prison"
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //ha ha no getting out!!
/obj/machinery/fugitive_capture/examine(mob/user)
. = ..()
. += "<span class='notice'>Add a prisoner by dragging them into the machine.</span>"
/obj/machinery/fugitive_capture/MouseDrop_T(mob/target, mob/user)
var/mob/living/fugitive_hunter = user
if(!isliving(fugitive_hunter))
return
if(fugitive_hunter.stat || (!(fugitive_hunter.mobility_flags & MOBILITY_STAND) || !(fugitive_hunter.mobility_flags & MOBILITY_UI)) || !Adjacent(fugitive_hunter) || !target.Adjacent(fugitive_hunter) || !ishuman(target))
return
var/mob/living/carbon/human/fugitive = target
var/datum/antagonist/fugitive/fug_antag = fugitive.mind.has_antag_datum(/datum/antagonist/fugitive)
if(!fug_antag)
to_chat(fugitive_hunter, "<span class='warning'>This is not a wanted fugitive!</span>")
return
if(do_after(fugitive_hunter, 50, target = fugitive))
add_prisoner(fugitive, fug_antag)
/obj/machinery/fugitive_capture/proc/add_prisoner(mob/living/carbon/human/fugitive, datum/antagonist/fugitive/antag)
fugitive.forceMove(src)
antag.is_captured = TRUE
to_chat(fugitive, "<span class='userdanger'>You are thrown into a vast void of bluespace, and as you fall further into oblivion the comparatively small entrance to reality gets smaller and smaller until you cannot see it anymore. You have failed to avoid capture.</span>")
fugitive.ghostize(TRUE) //so they cannot suicide, round end stuff.
/obj/machinery/computer/shuttle/hunter
name = "shuttle console"
shuttleId = "huntership"
possible_destinations = "huntership_home;huntership_custom;whiteship_home;syndicate_nw"
/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/hunter
name = "shuttle navigation computer"
desc = "Used to designate a precise transit location to travel to."
shuttleId = "huntership"
lock_override = CAMERA_LOCK_STATION
shuttlePortId = "huntership_custom"
see_hidden = FALSE
jumpto_ports = list("huntership_home" = 1, "whiteship_home" = 1, "syndicate_nw" = 1)
view_range = 4.5
/obj/structure/closet/crate/eva
name = "EVA crate"
/obj/structure/closet/crate/eva/PopulateContents()
..()
for(var/i in 1 to 3)
new /obj/item/clothing/suit/space/eva(src)
for(var/i in 1 to 3)
new /obj/item/clothing/head/helmet/space/eva(src)
for(var/i in 1 to 3)
new /obj/item/clothing/mask/breath(src)
for(var/i in 1 to 3)
new /obj/item/tank/internals/oxygen(src)
+169
View File
@@ -0,0 +1,169 @@
//The hunters!!
/datum/antagonist/fugitive_hunter
name = "Fugitive Hunter"
roundend_category = "Fugitive"
silent = TRUE //greet called by the spawn
show_in_antagpanel = FALSE
var/datum/team/fugitive_hunters/hunter_team
var/backstory = "error"
/datum/antagonist/fugitive_hunter/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
add_antag_hud(ANTAG_HUD_FUGITIVE, "fugitive_hunter", M)
/datum/antagonist/fugitive_hunter/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
remove_antag_hud(ANTAG_HUD_FUGITIVE, M)
/datum/antagonist/fugitive_hunter/on_gain()
forge_objectives()
. = ..()
/datum/antagonist/fugitive_hunter/proc/forge_objectives() //this isn't an actual objective because it's about round end rosters
var/datum/objective/capture = new /datum/objective
capture.owner = owner
capture.explanation_text = "Capture the fugitives in the station and put them into the bluespace capture machine on your ship."
objectives += capture
/datum/antagonist/fugitive_hunter/greet()
switch(backstory)
if("space cop")
to_chat(owner, "<span class='boldannounce'>Justice has arrived. I am a member of the Spacepol!</span>")
to_chat(owner, "<B>The criminals should be on the station, we have special huds implanted to recognize them.</B>")
to_chat(owner, "<B>As we have lost pretty much all power over these damned lawless megacorporations, it's a mystery if their security will cooperate with us.</B>")
if("russian")
to_chat(src, "<span class='danger'>Ay blyat. I am a space-russian smuggler! We were mid-flight when our cargo was beamed off our ship!</span>")
to_chat(src, "<span class='danger'>We were hailed by a man in a green uniform, promising the safe return of our goods in exchange for a favor:</span>")
to_chat(src, "<span class='danger'>There is a local station housing fugitives that the man is after, he wants them returned; dead or alive.</span>")
to_chat(src, "<span class='danger'>We will not be able to make ends meet without our cargo, so we must do as he says and capture them.</span>")
to_chat(owner, "<span class='boldannounce'>You are not an antagonist in that you may kill whomever you please, but you can do anything to ensure the capture of the fugitives, even if that means going through the station.</span>")
owner.announce_objectives()
/datum/antagonist/fugitive_hunter/create_team(datum/team/fugitive_hunters/new_team)
if(!new_team)
for(var/datum/antagonist/fugitive_hunter/H in GLOB.antagonists)
if(!H.owner)
continue
if(H.hunter_team)
hunter_team = H.hunter_team
return
hunter_team = new /datum/team/fugitive_hunters
hunter_team.backstory = backstory
hunter_team.update_objectives()
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
hunter_team = new_team
/datum/antagonist/fugitive_hunter/get_team()
return hunter_team
/datum/team/fugitive_hunters
var/backstory = "error"
/datum/team/fugitive_hunters/proc/update_objectives(initial = FALSE)
objectives = list()
var/datum/objective/O = new()
O.team = src
objectives += O
/datum/team/fugitive_hunters/proc/assemble_fugitive_results()
var/list/fugitives_counted = list()
var/list/fugitives_dead = list()
var/list/fugitives_captured = list()
for(var/datum/antagonist/fugitive/A in GLOB.antagonists)
if(!A.owner)
continue
fugitives_counted += A
if(A.owner.current.stat == DEAD)
fugitives_dead += A
if(A.is_captured)
fugitives_captured += A
. = list(fugitives_counted, fugitives_dead, fugitives_captured) //okay, check out how cool this is.
/datum/team/fugitive_hunters/proc/all_hunters_dead()
var/dead_boys = 0
for(var/I in members)
var/datum/mind/hunter_mind = I
if(!(ishuman(hunter_mind.current) || (hunter_mind.current.stat == DEAD)))
dead_boys++
return dead_boys >= members.len
/datum/team/fugitive_hunters/proc/get_result()
var/list/fugitive_results = assemble_fugitive_results()
var/list/fugitives_counted = fugitive_results[1]
var/list/fugitives_dead = fugitive_results[2]
var/list/fugitives_captured = fugitive_results[3]
var/hunters_dead = all_hunters_dead()
//this gets a little confusing so follow the comments if it helps
if(!fugitives_counted.len)
return
if(fugitives_captured.len)//any captured
if(fugitives_captured.len == fugitives_counted.len)//if the hunters captured all the fugitives, there's a couple special wins
if(!fugitives_dead)//specifically all of the fugitives alive
return FUGITIVE_RESULT_BADASS_HUNTER
else if(hunters_dead)//specifically all of the hunters died (while capturing all the fugitives)
return FUGITIVE_RESULT_POSTMORTEM_HUNTER
else//no special conditional wins, so just the normal major victory
return FUGITIVE_RESULT_MAJOR_HUNTER
else if(!hunters_dead)//so some amount captured, and the hunters survived.
return FUGITIVE_RESULT_HUNTER_VICTORY
else//so some amount captured, but NO survivors.
return FUGITIVE_RESULT_MINOR_HUNTER
else//from here on out, hunters lost because they did not capture any fugitive dead or alive. there are different levels of getting beat though:
if(!fugitives_dead)//all fugitives survived
return FUGITIVE_RESULT_MAJOR_FUGITIVE
else if(fugitives_dead < fugitives_counted)//at least ANY fugitive lived
return FUGITIVE_RESULT_FUGITIVE_VICTORY
else if(!hunters_dead)//all fugitives died, but none were taken in by the hunters. minor win
return FUGITIVE_RESULT_MINOR_FUGITIVE
else//all fugitives died, all hunters died, nobody brought back. seems weird to not give fugitives a victory if they managed to kill the hunters but literally no progress to either goal should lead to a nobody wins situation
return FUGITIVE_RESULT_STALEMATE
/datum/team/fugitive_hunters/roundend_report() //shows the number of fugitives, but not if they won in case there is no security
if(!members.len)
return
var/list/result = list()
result += "<div class='panel redborder'>...And <B>[members.len]</B> [backstory]s tried to hunt them down!"
for(var/datum/mind/M in members)
result += "<b>[printplayer(M)]</b>"
switch(get_result())
if(FUGITIVE_RESULT_BADASS_HUNTER)//use defines
result += "<span class='greentext big'>Badass [capitalize(backstory)] Victory!</span>"
result += "<B>The [backstory]s managed to capture every fugitive, alive!</B>"
if(FUGITIVE_RESULT_POSTMORTEM_HUNTER)
result += "<span class='greentext big'>Postmortem [capitalize(backstory)] Victory!</span>"
result += "<B>The [backstory]s managed to capture every fugitive, but all of them died! Spooky!</B>"
if(FUGITIVE_RESULT_MAJOR_HUNTER)
result += "<span class='greentext big'>Major [capitalize(backstory)] Victory</span>"
result += "<B>The [backstory]s managed to capture every fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_HUNTER_VICTORY)
result += "<span class='greentext big'>[capitalize(backstory)] Victory</span>"
result += "<B>The [backstory]s managed to capture a fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_MINOR_HUNTER)
result += "<span class='greentext big'>Minor [capitalize(backstory)] Victory</span>"
result += "<B>All the [backstory]s died, but managed to capture a fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_STALEMATE)
result += "<span class='neutraltext big'>Bloody Stalemate</span>"
result += "<B>Everyone died, and no fugitives were recovered!</B>"
if(FUGITIVE_RESULT_MINOR_FUGITIVE)
result += "<span class='redtext big'>Minor Fugitive Victory</span>"
result += "<B>All the fugitives died, but none were recovered!</B>"
if(FUGITIVE_RESULT_FUGITIVE_VICTORY)
result += "<span class='redtext big'>Fugitive Victory</span>"
result += "<B>A fugitive survived, and no bodies were recovered by the [backstory]s.</B>"
if(FUGITIVE_RESULT_MAJOR_FUGITIVE)
result += "<span class='redtext big'>Major Fugitive Victory</span>"
result += "<B>All of the fugitives survived and avoided capture!</B>"
else //get_result returned null- either bugged or no fugitives showed
result += "<span class='neutraltext big'>Prank Call!</span>"
result += "<B>[capitalize(backstory)]s were called, yet there were no fugitives...?</B>"
result += "</div>"
return result.Join("<br>")
+100
View File
@@ -0,0 +1,100 @@
/*
Asset cache quick users guide:
Make a datum in asset_list_items.dm with your assets for your thing.
Checkout asset_list.dm for the helper subclasses
The simple subclass will most like be of use for most cases.
Then call get_asset_datum() with the type of the datum you created and store the return
Then call .send(client) on that stored return value.
Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE
*/
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
//This proc sends the asset to the client, but only if it needs it.
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset(client/client, asset_name)
return send_asset_list(client, list(asset_name))
/// Sends a list of assets to a client
/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends)
/// client - a client or mob
/// asset_list - A list of asset filenames to be sent to the client.
/// Returns TRUE if any assets were sent.
/proc/send_asset_list(client/client, list/asset_list)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return
else
return
var/list/unreceived = list()
for (var/asset_name in asset_list)
var/datum/asset_cache_item/asset = SSassets.cache[asset_name]
if (!asset)
continue
var/asset_file = asset.resource
if (!asset_file)
continue
var/asset_md5 = asset.md5
if (client.sent_assets[asset_name] == asset_md5)
continue
unreceived[asset_name] = asset_md5
if (unreceived.len)
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for(var/asset in unreceived)
var/datum/asset_cache_item/ACI
if ((ACI = SSassets.cache[asset]))
log_asset("Sending asset [asset] to client [client]")
client << browse_rsc(ACI.resource, asset)
client.sent_assets |= unreceived
addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
return TRUE
return FALSE
//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
//The proc calls procs that sleep for long times.
/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3)
var/startingfilerate = filerate
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file, files[file])
if (send_asset(client, file))
if (!(--filerate))
filerate = startingfilerate
client.asset_flush()
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
//icons and virtual assets get copied to the dyn rsc before use
/proc/register_asset(asset_name, asset)
var/datum/asset_cache_item/ACI = new(asset_name, asset)
//this is technically never something that was supported and i want metrics on how often it happens if at all.
if (SSassets.cache[asset_name])
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
if (OACI.md5 != ACI.md5)
stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]")
else
var/list/stacktrace = gib_stack_trace()
log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]")
SSassets.cache[asset_name] = ACI
//Generated names do not include file extention.
//Used mainly for code that deals with assets in a generic way
//The same asset will always lead to the same asset name
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
@@ -0,0 +1,58 @@
/client
var/list/sent_assets = list() // List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s
var/list/completed_asset_jobs = list() /// List of all completed blocking send jobs awaiting acknowledgement by send_asset
var/last_asset_job = 0 /// Last asset send job id.
var/last_completed_asset_job = 0
/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]"
/client/proc/asset_cache_confirm_arrival(job_id)
var/asset_cache_job = round(text2num(job_id))
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"]))
completed_asset_jobs["[asset_cache_job]"] = TRUE
last_completed_asset_job = max(last_completed_asset_job, asset_cache_job)
else
return asset_cache_job || TRUE
/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING]
/client/proc/asset_cache_preload_data(data)
/*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}")
if (!jsonend)
CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/
//var/json = html_decode(copytext(data, 1, jsonend))
var/json = data
var/list/preloaded_assets = json_decode(json)
for (var/preloaded_asset in preloaded_assets)
if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html"))
preloaded_assets -= preloaded_asset
continue
sent_assets |= preloaded_assets
/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects.
/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list())
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
return
if (!islist(new_assets))
new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets]))
src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0")
/// Blocks until all currently sending browser assets have been sent.
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
/client/proc/asset_flush(timeout = 50)
var/job = ++last_asset_job
var/t = 0
var/timeout_time = timeout
src << browse({"<script>window.location.href="?asset_cache_confirm_arrival=[job]"</script>"}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if (t < timeout_time)
return TRUE
@@ -0,0 +1,21 @@
/**
* # asset_cache_item
*
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
**/
/datum/asset_cache_item
var/name
var/md5
var/resource
/datum/asset_cache_item/New(name, file)
if (!isfile(file))
file = fcopy_rsc(file)
md5 = md5(file)
if (!md5)
md5 = md5(fcopy_rsc(file))
if (!md5)
CRASH("invalid asset sent to asset cache")
debug_world_log("asset cache unexpected success of second fcopy_rsc")
src.name = name
resource = file
+228
View File
@@ -0,0 +1,228 @@
//These datums are used to populate the asset cache, the proc "register()" does this.
//Place any asset datums you create in asset_list_items.dm
//all of our asset datums, used for referring to these later
GLOBAL_LIST_EMPTY(asset_datums)
//get an assetdatum or make a new one
/proc/get_asset_datum(type)
return GLOB.asset_datums[type] || new type()
/datum/asset
var/_abstract = /datum/asset
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
//If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
var/assets = list()
/datum/asset/simple/register()
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/simple/send(client)
. = send_asset_list(client, assets)
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
var/list/children
/datum/asset/group/register()
for(var/type in children)
get_asset_datum(type)
/datum/asset/group/send(client/C)
for(var/type in children)
var/datum/asset/A = get_asset_datum(type)
. = A.send(C) || .
// spritesheet implementation - coalesces various icons into a single .png file
// and uses CSS to select icons out of that file - saves on transferring some
// 1400-odd individual PNG files
#define SPR_SIZE 1
#define SPR_IDX 2
#define SPRSZ_COUNT 1
#define SPRSZ_ICON 2
#define SPRSZ_STRIPPED 3
/datum/asset/spritesheet
_abstract = /datum/asset/spritesheet
var/name
var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
/datum/asset/spritesheet/register()
if (!name)
CRASH("spritesheet [type] cannot register without a name")
ensure_stripped()
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
text2file(generate_css(), fname)
register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
for(var/size_id in sizes)
var/size = sizes[size_id]
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
/datum/asset/spritesheet/send(client/C)
if (!name)
return
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
. = send_asset_list(C, all)
/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
for(var/size_id in sizes_to_strip)
var/size = sizes[size_id]
if (size[SPRSZ_STRIPPED])
continue
// save flattened version
var/fname = "data/spritesheets/[name]_[size_id].png"
fcopy(size[SPRSZ_ICON], fname)
var/error = rustg_dmi_strip_metadata(fname)
if(length(error))
stack_trace("Failed to strip [name]_[size_id].png: [error]")
size[SPRSZ_STRIPPED] = icon(fname)
fdel(fname)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
var/size_id = sprite[SPR_SIZE]
var/idx = sprite[SPR_IDX]
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
var/icon/big = size[SPRSZ_STRIPPED]
var/per_line = big.Width() / tiny.Width()
var/x = (idx % per_line) * tiny.Width()
var/y = round(idx / per_line) * tiny.Height()
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
return out.Join("\n")
/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
var/size_id = "[I.Width()]x[I.Height()]"
var/size = sizes[size_id]
if (sprites[sprite_name])
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
if (size)
var/position = size[SPRSZ_COUNT]++
var/icon/sheet = size[SPRSZ_ICON]
size[SPRSZ_STRIPPED] = null
sheet.Insert(I, icon_state=sprite_name)
sprites[sprite_name] = list(size_id, position)
else
sizes[size_id] = size = list(1, I, null)
sprites[sprite_name] = list(size_id, 0)
/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
if (length(prefix))
prefix = "[prefix]-"
if (!directions)
directions = list(SOUTH)
for (var/icon_state_name in icon_states(I))
for (var/direction in directions)
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
/datum/asset/spritesheet/proc/css_tag()
return {"<link rel="stylesheet" href="spritesheet_[name].css" />"}
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"<span class="[name][size_id] [sprite_name]"></span>"}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"[name][size_id] [sprite_name]"}
#undef SPR_SIZE
#undef SPR_IDX
#undef SPRSZ_COUNT
#undef SPRSZ_ICON
#undef SPRSZ_STRIPPED
/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
/datum/asset/spritesheet/simple/register()
for (var/key in assets)
Insert(key, assets[key])
..()
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
_abstract = /datum/asset/simple/icon_states
var/icon
var/list/directions = list(SOUTH)
var/frame = 1
var/movement_states = FALSE
var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png"
var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format
/datum/asset/simple/icon_states/register(_icon = icon)
for(var/icon_state_name in icon_states(_icon))
for(var/direction in directions)
var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
if (!asset)
continue
asset = fcopy_rsc(asset) //dedupe
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png")
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
var/list/icons
/datum/asset/simple/icon_states/multiple_icons/register()
for(var/i in icons)
..(i)
@@ -1,399 +1,22 @@
/*
Asset cache quick users guide:
Make a datum at the bottom of this file with your assets for your thing.
The simple subsystem will most like be of use for most cases.
Then call get_asset_datum() with the type of the datum you created and store the return
Then call .send(client) on that stored return value.
You can set verify to TRUE if you want send() to sleep until the client has the assets.
*/
// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping.
// This is doubled for the first asset, then added per asset after
#define ASSET_CACHE_SEND_TIMEOUT 7
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files
#define ASSET_CACHE_PRELOAD_CONCURRENT 3
/client
var/list/cache = list() // List of all assets sent to this client by the asset cache.
var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement.
var/list/sending = list()
var/last_asset_job = 0 // Last job done.
//This proc sends the asset to the client, but only if it needs it.
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset(client/client, asset_name, verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
if(client.cache.Find(asset_name) || client.sending.Find(asset_name))
return 0
client << browse_rsc(SSassets.cache[asset_name], asset_name)
if(!verify)
client.cache += asset_name
return 1
client.sending |= asset_name
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= asset_name
client.cache |= asset_name
client.completed_asset_jobs -= job
return 1
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset_list(client/client, list/asset_list, verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
var/list/unreceived = asset_list - (client.cache + client.sending)
if(!unreceived || !unreceived.len)
return 0
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for(var/asset in unreceived)
if (asset in SSassets.cache)
client << browse_rsc(SSassets.cache[asset], asset)
if(!verify) // Can't access the asset cache browser, rip.
client.cache += unreceived
return 1
client.sending |= unreceived
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= unreceived
client.cache |= unreceived
client.completed_asset_jobs -= job
return 1
//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
//The proc calls procs that sleep for long times.
/proc/getFilesSlow(client/client, list/files, register_asset = TRUE)
var/concurrent_tracker = 1
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file, files[file])
if (concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT)
concurrent_tracker = 1
send_asset(client, file)
else
concurrent_tracker++
send_asset(client, file, verify=FALSE)
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
//if it's an icon or something be careful, you'll have to copy it before further use.
/proc/register_asset(asset_name, asset)
SSassets.cache[asset_name] = asset
//Generated names do not include file extention.
//Used mainly for code that deals with assets in a generic way
//The same asset will always lead to the same asset name
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
//These datums are used to populate the asset cache, the proc "register()" does this.
//all of our asset datums, used for referring to these later
GLOBAL_LIST_EMPTY(asset_datums)
//get an assetdatum or make a new one
/proc/get_asset_datum(type)
return GLOB.asset_datums[type] || new type()
/datum/asset
var/_abstract = /datum/asset
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
//If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
var/assets = list()
var/verify = FALSE
/datum/asset/simple/register()
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/simple/send(client)
send_asset_list(client,assets,verify)
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
var/list/children
/datum/asset/group/register()
for(var/type in children)
get_asset_datum(type)
/datum/asset/group/send(client/C)
for(var/type in children)
var/datum/asset/A = get_asset_datum(type)
A.send(C)
// spritesheet implementation - coalesces various icons into a single .png file
// and uses CSS to select icons out of that file - saves on transferring some
// 1400-odd individual PNG files
#define SPR_SIZE 1
#define SPR_IDX 2
#define SPRSZ_COUNT 1
#define SPRSZ_ICON 2
#define SPRSZ_STRIPPED 3
/datum/asset/spritesheet
_abstract = /datum/asset/spritesheet
var/name
var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
var/verify = FALSE
/datum/asset/spritesheet/register()
if (!name)
CRASH("spritesheet [type] cannot register without a name")
ensure_stripped()
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
text2file(generate_css(), fname)
register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
for(var/size_id in sizes)
var/size = sizes[size_id]
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
/datum/asset/spritesheet/send(client/C)
if (!name)
return
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
send_asset_list(C, all, verify)
/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
for(var/size_id in sizes_to_strip)
var/size = sizes[size_id]
if (size[SPRSZ_STRIPPED])
continue
// save flattened version
var/fname = "data/spritesheets/[name]_[size_id].png"
fcopy(size[SPRSZ_ICON], fname)
var/error = rustg_dmi_strip_metadata(fname)
if(length(error))
stack_trace("Failed to strip [name]_[size_id].png: [error]")
size[SPRSZ_STRIPPED] = icon(fname)
fdel(fname)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
var/size_id = sprite[SPR_SIZE]
var/idx = sprite[SPR_IDX]
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
var/icon/big = size[SPRSZ_STRIPPED]
var/per_line = big.Width() / tiny.Width()
var/x = (idx % per_line) * tiny.Width()
var/y = round(idx / per_line) * tiny.Height()
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
return out.Join("\n")
/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
var/size_id = "[I.Width()]x[I.Height()]"
var/size = sizes[size_id]
if (sprites[sprite_name])
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
if (size)
var/position = size[SPRSZ_COUNT]++
var/icon/sheet = size[SPRSZ_ICON]
size[SPRSZ_STRIPPED] = null
sheet.Insert(I, icon_state=sprite_name)
sprites[sprite_name] = list(size_id, position)
else
sizes[size_id] = size = list(1, I, null)
sprites[sprite_name] = list(size_id, 0)
/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
if (length(prefix))
prefix = "[prefix]-"
if (!directions)
directions = list(SOUTH)
for (var/icon_state_name in icon_states(I))
for (var/direction in directions)
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
/datum/asset/spritesheet/proc/css_tag()
return {"<link rel="stylesheet" href="spritesheet_[name].css" />"}
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"<span class="[name][size_id] [sprite_name]"></span>"}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"[name][size_id] [sprite_name]"}
#undef SPR_SIZE
#undef SPR_IDX
#undef SPRSZ_COUNT
#undef SPRSZ_ICON
#undef SPRSZ_STRIPPED
/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
/datum/asset/spritesheet/simple/register()
for (var/key in assets)
Insert(key, assets[key])
..()
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
_abstract = /datum/asset/simple/icon_states
var/icon
var/list/directions = list(SOUTH)
var/frame = 1
var/movement_states = FALSE
var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png"
var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format
verify = FALSE
/datum/asset/simple/icon_states/register(_icon = icon)
for(var/icon_state_name in icon_states(_icon))
for(var/direction in directions)
var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
if (!asset)
continue
asset = fcopy_rsc(asset) //dedupe
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png")
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
var/list/icons
/datum/asset/simple/icon_states/multiple_icons/register()
for(var/i in icons)
..(i)
//DEFINITIONS FOR ASSET DATUMS START HERE.
/* uncomment this and delete the tgui def bellow this for the new tgui
/datum/asset/simple/tgui
assets = list(
// tgui
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
)
*/
/datum/asset/simple/tgui
assets = list(
// Old TGUI
"tgui.css" = 'tgui/assets/tgui.css',
"tgui.js" = 'tgui/assets/tgui.js',
// tgui-next
"tgui-main.html" = 'tgui-next/packages/tgui/public/tgui-main.html',
"tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html',
"tgui.bundle.js" = 'tgui-next/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui-next/packages/tgui/public/tgui.bundle.css',
// Old TGUI compatability
"tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html',
"shim-html5shiv.js" = 'tgui-next/packages/tgui/public/shim-html5shiv.js',
"shim-ie8.js" = 'tgui-next/packages/tgui/public/shim-ie8.js',
"shim-dom4.js" = 'tgui-next/packages/tgui/public/shim-dom4.js',
@@ -434,8 +57,16 @@ GLOBAL_LIST_EMPTY(asset_datums)
"smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
"smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif'
//"borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
)
/* uncomment if you're porting the new ntnet app
/datum/asset/simple/radar_assets
assets = list(
"ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
"ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
"ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
)
*/
/datum/asset/spritesheet/simple/pda
name = "pda"
assets = list(
@@ -464,6 +95,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
"refresh" = 'icons/pda_icons/pda_refresh.png',
"scanner" = 'icons/pda_icons/pda_scanner.png',
"signaler" = 'icons/pda_icons/pda_signaler.png',
//"skills" = 'icons/pda_icons/pda_skills.png',
"status" = 'icons/pda_icons/pda_status.png',
"dronephone" = 'icons/pda_icons/pda_dronephone.png',
"emoji" = 'icons/pda_icons/pda_emoji.png'
@@ -483,51 +115,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
"stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png',
"stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png',
"stamp-law" = 'icons/stamp_icons/large_stamp-law.png'
)
/datum/asset/spritesheet/simple/minesweeper
name = "minesweeper"
assets = list(
"1" = 'icons/UI_Icons/minesweeper_tiles/one.png',
"2" = 'icons/UI_Icons/minesweeper_tiles/two.png',
"3" = 'icons/UI_Icons/minesweeper_tiles/three.png',
"4" = 'icons/UI_Icons/minesweeper_tiles/four.png',
"5" = 'icons/UI_Icons/minesweeper_tiles/five.png',
"6" = 'icons/UI_Icons/minesweeper_tiles/six.png',
"7" = 'icons/UI_Icons/minesweeper_tiles/seven.png',
"8" = 'icons/UI_Icons/minesweeper_tiles/eight.png',
"empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png',
"flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png',
"hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png',
"mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png',
"minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png'
)
/datum/asset/spritesheet/simple/pills
name = "pills"
assets = list(
"pill1" = 'icons/UI_Icons/Pills/pill1.png',
"pill2" = 'icons/UI_Icons/Pills/pill2.png',
"pill3" = 'icons/UI_Icons/Pills/pill3.png',
"pill4" = 'icons/UI_Icons/Pills/pill4.png',
"pill5" = 'icons/UI_Icons/Pills/pill5.png',
"pill6" = 'icons/UI_Icons/Pills/pill6.png',
"pill7" = 'icons/UI_Icons/Pills/pill7.png',
"pill8" = 'icons/UI_Icons/Pills/pill8.png',
"pill9" = 'icons/UI_Icons/Pills/pill9.png',
"pill10" = 'icons/UI_Icons/Pills/pill10.png',
"pill11" = 'icons/UI_Icons/Pills/pill11.png',
"pill12" = 'icons/UI_Icons/Pills/pill12.png',
"pill13" = 'icons/UI_Icons/Pills/pill13.png',
"pill14" = 'icons/UI_Icons/Pills/pill14.png',
"pill15" = 'icons/UI_Icons/Pills/pill15.png',
"pill16" = 'icons/UI_Icons/Pills/pill16.png',
"pill17" = 'icons/UI_Icons/Pills/pill17.png',
"pill18" = 'icons/UI_Icons/Pills/pill18.png',
"pill19" = 'icons/UI_Icons/Pills/pill19.png',
"pill20" = 'icons/UI_Icons/Pills/pill20.png',
"pill21" = 'icons/UI_Icons/Pills/pill21.png',
"pill22" = 'icons/UI_Icons/Pills/pill22.png',
//"stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png',
//"stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
//"stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
//"stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
)
/datum/asset/simple/IRV
@@ -573,23 +164,20 @@ GLOBAL_LIST_EMPTY(asset_datums)
)
/datum/asset/simple/jquery
verify = FALSE
assets = list(
"jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js',
)
/datum/asset/simple/goonchat
verify = FALSE
assets = list(
"json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js',
"browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js',
"browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css',
"browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css',
"browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', //dark theme, cit specific
"browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.css'
)
/datum/asset/simple/fontawesome
verify = FALSE
assets = list(
"fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
"fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
@@ -630,6 +218,90 @@ GLOBAL_LIST_EMPTY(asset_datums)
"none_button.png" = 'html/none_button.png',
)
/datum/asset/spritesheet/simple/minesweeper
name = "minesweeper"
assets = list(
"1" = 'icons/UI_Icons/minesweeper_tiles/one.png',
"2" = 'icons/UI_Icons/minesweeper_tiles/two.png',
"3" = 'icons/UI_Icons/minesweeper_tiles/three.png',
"4" = 'icons/UI_Icons/minesweeper_tiles/four.png',
"5" = 'icons/UI_Icons/minesweeper_tiles/five.png',
"6" = 'icons/UI_Icons/minesweeper_tiles/six.png',
"7" = 'icons/UI_Icons/minesweeper_tiles/seven.png',
"8" = 'icons/UI_Icons/minesweeper_tiles/eight.png',
"empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png',
"flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png',
"hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png',
"mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png',
"minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png'
)
/* Port the app game thing
/datum/asset/simple/arcade
assets = list(
"boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif',
"boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif',
"boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif',
"boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
"boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
"boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
)
*/
/*
/datum/asset/spritesheet/simple/achievements
name ="achievements"
assets = list(
"default" = 'icons/UI_Icons/Achievements/default.png',
"basemisc" = 'icons/UI_Icons/Achievements/basemisc.png',
"baseboss" = 'icons/UI_Icons/Achievements/baseboss.png',
"baseskill" = 'icons/UI_Icons/Achievements/baseskill.png',
"bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png',
"colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png',
"hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png',
"legion" = 'icons/UI_Icons/Achievements/Boss/legion.png',
"miner" = 'icons/UI_Icons/Achievements/Boss/miner.png',
"swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png',
"tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png',
"featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png',
"helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png',
"jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png',
"meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png',
"timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png',
"upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png',
"clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png',
"clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png',
"rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png',
"snail" = 'icons/UI_Icons/Achievements/Misc/snail.png',
"mining" = 'icons/UI_Icons/Achievements/Skills/mining.png',
)
*/
/datum/asset/spritesheet/simple/pills
name ="pills"
assets = list(
"pill1" = 'icons/UI_Icons/Pills/pill1.png',
"pill2" = 'icons/UI_Icons/Pills/pill2.png',
"pill3" = 'icons/UI_Icons/Pills/pill3.png',
"pill4" = 'icons/UI_Icons/Pills/pill4.png',
"pill5" = 'icons/UI_Icons/Pills/pill5.png',
"pill6" = 'icons/UI_Icons/Pills/pill6.png',
"pill7" = 'icons/UI_Icons/Pills/pill7.png',
"pill8" = 'icons/UI_Icons/Pills/pill8.png',
"pill9" = 'icons/UI_Icons/Pills/pill9.png',
"pill10" = 'icons/UI_Icons/Pills/pill10.png',
"pill11" = 'icons/UI_Icons/Pills/pill11.png',
"pill12" = 'icons/UI_Icons/Pills/pill12.png',
"pill13" = 'icons/UI_Icons/Pills/pill13.png',
"pill14" = 'icons/UI_Icons/Pills/pill14.png',
"pill15" = 'icons/UI_Icons/Pills/pill15.png',
"pill16" = 'icons/UI_Icons/Pills/pill16.png',
"pill17" = 'icons/UI_Icons/Pills/pill17.png',
"pill18" = 'icons/UI_Icons/Pills/pill18.png',
"pill19" = 'icons/UI_Icons/Pills/pill19.png',
"pill20" = 'icons/UI_Icons/Pills/pill20.png',
"pill21" = 'icons/UI_Icons/Pills/pill21.png',
"pill22" = 'icons/UI_Icons/Pills/pill22.png',
)
//this exists purely to avoid meta by pre-loading all language icons.
/datum/asset/language/register()
for(var/path in typesof(/datum/language))
@@ -640,7 +312,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/spritesheet/pipes
name = "pipes"
/datum/asset/spritesheet/pipes/register()
/datum/asset/spritesheet/pipes/register() //we do not have chempipes
for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi'))
InsertAll("", each, GLOB.alldirs)
..()
@@ -650,7 +322,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
name = "design"
/datum/asset/spritesheet/research_designs/register()
for (var/path in subtypesof(/datum/design))
for(var/path in subtypesof(/datum/design))
var/datum/design/D = path
var/icon_file
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
<script>
//this is used over window.location because window.location has a character limit in IE.
function sendbyond(text) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '?'+text, true);
xhr.send(null);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', 'asset_data.json', true);
xhr.responseType = 'text';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var status = xhr.status;
if (status >= 200 && status < 400) {
sendbyond('asset_cache_preload_data=' + encodeURIComponent(xhr.responseText));
}
}
};
xhr.send(null);
</script>
</body>
</html>
+37
View File
@@ -417,3 +417,40 @@
crate_type = /obj/structure/closet/crate
///Special supply crate that generates random syndicate gear up to a determined TC value
/datum/supply_pack/misc/syndicate
name = "Assorted Syndicate Gear"
desc = "Contains a random assortment of syndicate gear."
special = TRUE ///Cannot be ordered via cargo
contains = list()
crate_name = "syndicate gear crate"
crate_type = /obj/structure/closet/crate
var/crate_value = 30 ///Total TC worth of contained uplink items
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////// Syndicate Packs /////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//Generate assorted uplink items, taking into account the same surplus modifiers used for surplus crates
//(this is exclusively used for the rare variant of the stray cargo event!)
/datum/supply_pack/misc/syndicate/fill(obj/structure/closet/crate/C)
var/list/uplink_items = get_uplink_items(SSticker.mode)
while(crate_value)
var/category = pick(uplink_items)
var/item = pick(uplink_items[category])
var/datum/uplink_item/I = uplink_items[category][item]
if(!I.surplus || prob(100 - I.surplus))
continue
if(crate_value < I.cost)
continue
crate_value -= I.cost
new I.item(C)
+32
View File
@@ -228,3 +228,35 @@
access = ACCESS_ARMORY
crate_name = "sporting crate"
crate_type = /obj/structure/closet/crate/secure // Would have liked a wooden crate but access >:(
/datum/supply_pack/security/dumdum
name = ".38 DumDum Speedloader"
desc = "Contains one speedloader of .38 DumDum ammunition, good for embedding in soft targets. Requires Security or Forensics access to open."
cost = 1200
access = FALSE
access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
contains = list(/obj/item/ammo_box/c38/dumdum)
crate_name = ".38 match crate"
/datum/supply_pack/security/match
name = ".38 Match Grade Speedloader"
desc = "Contains one speedloader of match grade .38 ammunition, perfect for showing off trickshots. Requires Security or Forensics access to open."
cost = 1200
access = FALSE
access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
contains = list(/obj/item/ammo_box/c38/match)
crate_name = ".38 match crate"
/datum/supply_pack/security/stingpack
name = "Stingbang Grenade Pack"
desc = "Contains five \"stingbang\" grenades, perfect for stopping riots and playing morally unthinkable pranks. Requires Security access to open."
cost = 2500
contains = list(/obj/item/storage/box/stingbangs)
crate_name = "stingbang grenade pack crate"
/datum/supply_pack/security/stingpack/single
name = "Stingbang Single-Pack"
desc = "Contains one \"stingbang\" grenade, perfect for playing meanhearted pranks. Requires Security access to open."
cost = 1400
contains = list(/obj/item/grenade/stingbang)
+31 -22
View File
@@ -36,29 +36,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null
return
// asset_cache
var/asset_cache_job
if(href_list["asset_cache_confirm_arrival"])
var/job = text2num(href_list["asset_cache_confirm_arrival"])
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us
// into letting append to a list without limit.
if (job && job <= last_asset_job && !(job in completed_asset_jobs))
completed_asset_jobs += job
asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"])
if (!asset_cache_job)
return
else if (job in completed_asset_jobs) //byond bug ID:2256651
to_chat(src, "<span class='danger'>An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)</span>")
src << browse("...", "window=asset_cache_browser")
// Keypress passthrough
if(href_list["__keydown"])
var/keycode = browser_keycode_to_byond(href_list["__keydown"])
if(keycode)
keyDown(keycode)
return
if(href_list["__keyup"])
var/keycode = browser_keycode_to_byond(href_list["__keyup"])
if(keycode)
keyUp(keycode)
return
var/mtl = CONFIG_GET(number/minute_topic_limit)
if (!holder && mtl)
@@ -75,7 +57,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
topiclimiter[ADMINSWARNED_AT] = minute
msg += " Administrators have been informed."
log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
message_admins("[ADMIN_LOOKUPFLW(usr)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
to_chat(src, "<span class='danger'>[msg]</span>")
return
@@ -96,6 +78,27 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2))
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
//byond bug ID:2256651
if (asset_cache_job && (asset_cache_job in completed_asset_jobs))
to_chat(src, "<span class='danger'>An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)</span>")
src << browse("...", "window=asset_cache_browser")
return
if (href_list["asset_cache_preload_data"])
asset_cache_preload_data(href_list["asset_cache_preload_data"])
return
// Keypress passthrough
if(href_list["__keydown"])
var/keycode = browser_keycode_to_byond(href_list["__keydown"])
if(keycode)
keyDown(keycode)
return
if(href_list["__keyup"])
var/keycode = browser_keycode_to_byond(href_list["__keyup"])
if(keycode)
keyUp(keycode)
return
// Admin PM
if(href_list["priv_msg"])
cmd_admin_pm(href_list["priv_msg"],null)
@@ -858,8 +861,14 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
'html/browser/playeroptions.css',
)
spawn (10) //removing this spawn causes all clients to not get verbs.
//load info on what assets the client has
src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser")
//Precache the client with all other assets slowly, so as to not block other browse() calls
getFilesSlow(src, SSassets.preload, register_asset = FALSE)
addtimer(CALLBACK(GLOBAL_PROC, /proc/getFilesSlow, src, SSassets.preload, FALSE), 5 SECONDS)
#if (PRELOAD_RSC == 0)
for (var/name in GLOB.vox_sounds)
var/file = GLOB.vox_sounds[name]
+3 -1
View File
@@ -6,5 +6,7 @@
if(!CONFIG_GET(flag/minimaps_enabled))
to_chat(usr, "<span class='boldwarning'>Minimap generation is not enabled in the server's configuration.</span>")
return
if(!SSminimaps.station_minimap)
to_chat(usr, "<span class='boldwarning'>Minimap generation is in progress, please wait!</span>")
return
SSminimaps.station_minimap.show(src)
@@ -203,6 +203,12 @@
icon_state = "hipster_glasses"
item_state = "hipster_glasses"
/obj/item/clothing/glasses/regular/circle
name = "circle glasses"
desc = "Why would you wear something so controversial yet so brave?"
icon_state = "circle_glasses"
item_state = "circle_glasses"
//Here lies green glasses, so ugly they died. RIP
/obj/item/clothing/glasses/sunglasses
+5 -1
View File
@@ -74,6 +74,10 @@
icon_state = "beaniedurathread"
armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50)
/obj/item/clothing/head/beanie/waldo
name = "red striped bobble hat"
desc = "If you're going on a worldwide hike, you'll need some cold protection."
icon_state = "waldo_hat"
item_state = "waldo_hat"
//No dog fashion sprites yet :( poor Ian can't be dope like the rest of us yet
+6
View File
@@ -386,3 +386,9 @@
cold_protection = HEAD
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 0)
/obj/item/clothing/head/helmet/police
name = "police officer's hat"
desc = "A police officer's Hat. This hat emphasizes that you are THE LAW."
icon_state = "policehelm"
dynamic_hair_suffix = ""
+8
View File
@@ -429,3 +429,11 @@
icon_state = "russobluecamohat"
item_state = "russobluecamohat"
dynamic_hair_suffix = ""
/obj/item/clothing/head/hunter
name = "bounty hunting hat"
desc = "Ain't nobody gonna cheat the hangman in my town."
icon_state = "hunter"
item_state = "hunter"
armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
resistance_flags = FIRE_PROOF | ACID_PROOF
+13
View File
@@ -231,3 +231,16 @@
A.UpdateButtonIcon()
to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!")
return TRUE
/obj/item/clothing/mask/gas/tiki_mask/yalp_elor
icon_state = "tiki_yalp"
item_state = "tiki_yalp"
actions_types = list()
/obj/item/clothing/mask/gas/hunter
name = "bounty hunting mask"
desc = "A custom tactical mask with decals added."
icon_state = "hunter"
item_state = "hunter"
resistance_flags = FIRE_PROOF | ACID_PROOF
flags_inv = HIDEFACIALHAIR|HIDEFACE|HIDEEYES|HIDEEARS|HIDEHAIR
+6
View File
@@ -185,3 +185,9 @@
playsound(src.loc, "sound/voice/complionator/[phrase_sound].ogg", 100, 0, 4)
cooldown = world.time
cooldown_special = world.time
/obj/item/clothing/mask/gas/sechailer/swat/spacepol
name = "spacepol mask"
desc = "A close-fitting tactical mask created in cooperation with a certain megacorporation, comes with an especially aggressive Compli-o-nator 3000."
icon_state = "spacepol"
item_state = "spacepol"
@@ -518,3 +518,14 @@ Contains:
desc = "A deep blue space helmet with a large red cross on the faceplate to designate the wearer as trained emergency medical personnel."
icon_state = "paramedic-eva-helmet"
item_state = "paramedic-eva-helmet"
/obj/item/clothing/suit/space/hunter
name = "bounty hunting suit"
desc = "A custom version of the MK.II SWAT suit, modified to look rugged and tough. Works as a space suit, if you can find a helmet."
icon_state = "hunter"
item_state = "swat_suit"
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat)
armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
strip_delay = 130
resistance_flags = FIRE_PROOF | ACID_PROOF
@@ -1016,3 +1016,9 @@
/obj/item/clothing/head/hooded/winterhood/polychromic
icon_state = "winterhood_poly"
item_state = "winterhood_poly"
/obj/item/clothing/suit/striped_sweater
name = "striped sweater"
desc = "Reminds you of someone, but you just can't put your finger on it..."
icon_state = "waldo_shirt"
item_state = "waldo_shirt"
@@ -192,3 +192,14 @@
icon_state = "hos_parade_fem"
item_state = "r_suit"
fitted = FEMALE_UNIFORM_TOP
/*
*Spacepol
*/
/obj/item/clothing/under/rank/security/spacepol
name = "police uniform"
desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol."
icon_state = "spacepol"
item_state = "spacepol"
can_adjust = FALSE
+33
View File
@@ -0,0 +1,33 @@
/datum/round_event_control/brain_trauma
name = "Spontaneous Brain Trauma"
typepath = /datum/round_event/brain_trauma
weight = 25
/datum/round_event/brain_trauma
fakeable = FALSE
/datum/round_event/brain_trauma/start()
for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list))
if(!H.client)
continue
if(H.stat == DEAD) // What are you doing in this list
continue
if(!H.getorgan(/obj/item/organ/brain)) // If only I had a brain
continue
traumatize(H)
break
/datum/round_event/brain_trauma/proc/traumatize(mob/living/carbon/human/H)
var/resistance = pick(
65;TRAUMA_RESILIENCE_BASIC,
30;TRAUMA_RESILIENCE_SURGERY,
5;TRAUMA_RESILIENCE_LOBOTOMY)
var/trauma_type = pickweight(list(
BRAIN_TRAUMA_MILD = 60,
BRAIN_TRAUMA_SEVERE = 30,
BRAIN_TRAUMA_SPECIAL = 10
))
H.gain_trauma_type(trauma_type, resistance)
+30
View File
@@ -0,0 +1,30 @@
/datum/round_event_control/fake_virus
name = "Fake Virus"
typepath = /datum/round_event/fake_virus
weight = 20
/datum/round_event/fake_virus/start()
var/list/fake_virus_victims = list()
for(var/mob/living/carbon/human/H in shuffle(GLOB.player_list))
if(!H.client || H.stat == DEAD || H.InCritical())
continue
fake_virus_victims += H
//first we do hard status effect victims
var/defacto_min = min(3, LAZYLEN(fake_virus_victims))
if(defacto_min)// event will hit 1-3 people by default, but will do 1-2 or just 1 if only those many candidates are available
for(var/i=1; i<=rand(1,defacto_min); i++)
var/mob/living/carbon/human/hypochondriac = pick(fake_virus_victims)
hypochondriac.apply_status_effect(STATUS_EFFECT_FAKE_VIRUS)
fake_virus_victims -= hypochondriac
//then we do light one-message victims who simply cough or whatever once (have to repeat the process since the last operation modified our candidates list)
defacto_min = min(5, LAZYLEN(fake_virus_victims))
if(defacto_min)
for(var/i=1; i<=rand(1,defacto_min); i++)
var/mob/living/carbon/human/onecoughman = pick(fake_virus_victims)
if(prob(25))//1/4 odds to get a spooky message instead of coughing out loud
addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, onecoughman, "<span class='warning'>[pick("Your head hurts.", "Your head pounds.")]</span>"), rand(30,150))
else
addtimer(CALLBACK(onecoughman, .mob/proc/emote, pick("cough", "sniff", "sneeze")), rand(30,150))//deliver the message with a slightly randomized time interval so there arent multiple people coughing at the exact same time
fake_virus_victims -= onecoughman
+117
View File
@@ -0,0 +1,117 @@
/datum/round_event_control/fugitives
name = "Spawn Fugitives"
typepath = /datum/round_event/ghost_role/fugitives
max_occurrences = 1
min_players = 20
earliest_start = 30 MINUTES //deadchat sink, lets not even consider it early on.
gamemode_blacklist = list("nuclear")
/datum/round_event/ghost_role/fugitives
minimum_required = 1
role_name = "fugitive"
fakeable = FALSE
/datum/round_event/ghost_role/fugitives/spawn_role()
var/list/possible_spawns = list()//Some xeno spawns are in some spots that will instantly kill the refugees, like atmos
for(var/turf/X in GLOB.xeno_spawn)
if(istype(X.loc, /area/maintenance))
possible_spawns += X
if(!possible_spawns.len)
message_admins("No valid spawn locations found, aborting...")
return MAP_ERROR
var/turf/landing_turf = pick(possible_spawns)
var/list/possible_backstories = list()
var/list/candidates = get_candidates(ROLE_TRAITOR, null, ROLE_TRAITOR)
if(candidates.len >= 1) //solo refugees
if(prob(30))
possible_backstories.Add("waldo") //less common as it comes with magicks and is kind of immershun shattering
else //For accurate deadchat feedback
minimum_required = 4
if(candidates.len >= 4)//group refugees
possible_backstories.Add("prisoner", "cultist", "synth")
if(!possible_backstories.len)
return NOT_ENOUGH_PLAYERS
var/backstory = pick(possible_backstories)
var/member_size = 3
var/leader
switch(backstory)
if("synth")
leader = pick_n_take(candidates)
if("waldo")
member_size = 0 //solo refugees have no leader so the member_size gets bumped to one a bit later
var/list/members = list()
var/list/spawned_mobs = list()
if(isnull(leader))
member_size++ //if there is no leader role, then the would be leader is a normal member of the team.
for(var/i in 1 to member_size)
members += pick_n_take(candidates)
for(var/mob/dead/selected in members)
var/mob/living/carbon/human/S = gear_fugitive(selected, landing_turf, backstory)
spawned_mobs += S
if(!isnull(leader))
gear_fugitive_leader(leader, landing_turf, backstory)
//after spawning
playsound(src, 'sound/weapons/emitter.ogg', 50, TRUE)
new /obj/item/storage/toolbox/mechanical(landing_turf) //so they can actually escape maint
addtimer(CALLBACK(src, .proc/spawn_hunters), 10 MINUTES)
role_name = "fugitive hunter"
return SUCCESSFUL_SPAWN
/datum/round_event/ghost_role/fugitives/proc/gear_fugitive(mob/dead/selected, turf/landing_turf, backstory) //spawns normal fugitive
var/datum/mind/player_mind = new /datum/mind(selected.key)
player_mind.active = TRUE
var/mob/living/carbon/human/S = new(landing_turf)
player_mind.transfer_to(S)
player_mind.assigned_role = "Fugitive"
player_mind.special_role = "Fugitive"
player_mind.add_antag_datum(/datum/antagonist/fugitive)
var/datum/antagonist/fugitive/fugitiveantag = player_mind.has_antag_datum(/datum/antagonist/fugitive)
INVOKE_ASYNC(fugitiveantag, /datum/antagonist/fugitive.proc/greet, backstory) //some fugitives have a sleep on their greet, so we don't want to stop the entire antag granting proc with fluff
switch(backstory)
if("prisoner")
S.equipOutfit(/datum/outfit/prisoner)
if("cultist")
S.equipOutfit(/datum/outfit/yalp_cultist)
if("waldo")
S.equipOutfit(/datum/outfit/waldo)
if("synth")
S.equipOutfit(/datum/outfit/synthetic)
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a Fugitive by an event.")
log_game("[key_name(S)] was spawned as a Fugitive by an event.")
spawned_mobs += S
return S
//special spawn for one member. it can be used for a special mob or simply to give one normal member special items.
/datum/round_event/ghost_role/fugitives/proc/gear_fugitive_leader(mob/dead/leader, turf/landing_turf, backstory)
var/datum/mind/player_mind = new /datum/mind(leader.key)
player_mind.active = TRUE
//if you want to add a fugitive with a special leader in the future, make this switch with the backstory
var/mob/living/carbon/human/S = gear_fugitive(leader, landing_turf, backstory)
var/obj/item/choice_beacon/augments/A = new(S)
S.put_in_hands(A)
new /obj/item/autosurgeon(landing_turf)
//security team gets called in after 10 minutes of prep to find the refugees
/datum/round_event/ghost_role/fugitives/proc/spawn_hunters()
var/backstory = pick("space cop", "russian", "bounty hunter")
var/datum/map_template/shuttle/ship
if(backstory == "space cop")
ship = new /datum/map_template/shuttle/hunter/space_cop
else if (backstory == "russian")
ship = new /datum/map_template/shuttle/hunter/russian
else
ship = new /datum/map_template/shuttle/hunter/bounty
var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width)
var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height)
var/z = SSmapping.empty_space.z_value
var/turf/T = locate(x,y,z)
if(!T)
CRASH("Fugitive Hunters (Created from fugitive event) found no turf to load in")
if(!ship.load(T))
CRASH("Loading [backstory] ship failed!")
priority_announce("Unidentified ship detected near the station.")
+99
View File
@@ -0,0 +1,99 @@
///Spawns a cargo pod containing a random cargo supply pack on a random area of the station
/datum/round_event_control/stray_cargo
name = "Stray Cargo Pod"
typepath = /datum/round_event/stray_cargo
weight = 20
max_occurrences = 4
earliest_start = 10 MINUTES
///Spawns a cargo pod containing a random cargo supply pack on a random area of the station
/datum/round_event/stray_cargo
var/area/impact_area ///Randomly picked area
var/list/possible_pack_types = list() ///List of possible supply packs dropped in the pod, if empty picks from the cargo list
var/static/list/stray_spawnable_supply_packs = list() ///List of default spawnable supply packs, filtered from the cargo list
/datum/round_event/stray_cargo/announce(fake)
priority_announce("Stray cargo pod detected on long-range scanners. Expected location of impact: [impact_area.name].", "Collision Alert")
/**
* Tries to find a valid area, throws an error if none are found
* Also randomizes the start timer
*/
/datum/round_event/stray_cargo/setup()
startWhen = rand(20, 40)
impact_area = find_event_area()
if(!impact_area)
CRASH("No valid areas for cargo pod found.")
var/list/turf_test = get_area_turfs(impact_area)
if(!turf_test.len)
CRASH("Stray Cargo Pod : No valid turfs found for [impact_area] - [impact_area.type]")
if(!stray_spawnable_supply_packs.len)
stray_spawnable_supply_packs = SSshuttle.supply_packs.Copy()
for(var/pack in stray_spawnable_supply_packs)
var/datum/supply_pack/pack_type = pack
if(initial(pack_type.special))
stray_spawnable_supply_packs -= pack
///Spawns a random supply pack, puts it in a pod, and spawns it on a random tile of the selected area
/datum/round_event/stray_cargo/start()
var/list/turf/valid_turfs = get_area_turfs(impact_area)
//Only target non-dense turfs to prevent wall-embedded pods
for(var/i in valid_turfs)
var/turf/T = i
if(T.density)
valid_turfs -= T
var/turf/LZ = pick(valid_turfs)
var/pack_type
if(possible_pack_types.len)
pack_type = pick(possible_pack_types)
else
pack_type = pick(stray_spawnable_supply_packs)
var/datum/supply_pack/SP = new pack_type
var/obj/structure/closet/crate/crate = SP.generate(null)
crate.locked = FALSE //Unlock secure crates
crate.update_icon()
var/obj/structure/closet/supplypod/pod = make_pod()
crate.forceMove(pod)
new /obj/effect/abstract/DPtarget(LZ, pod)
///Handles the creation of the pod, in case it needs to be modified beforehand
/datum/round_event/stray_cargo/proc/make_pod()
var/obj/structure/closet/supplypod/S = new
return S
///Picks an area that wouldn't risk critical damage if hit by a pod explosion
/datum/round_event/stray_cargo/proc/find_event_area()
var/static/list/allowed_areas
if(!allowed_areas)
///Places that shouldn't explode
var/list/safe_area_types = typecacheof(list(
/area/ai_monitored/turret_protected/ai,
/area/ai_monitored/turret_protected/ai_upload,
/area/engine,
/area/shuttle)
)
///Subtypes from the above that actually should explode.
var/list/unsafe_area_subtypes = typecacheof(list(/area/engine/break_room))
allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes
var/list/possible_areas = typecache_filter_list(GLOB.sortedAreas,allowed_areas)
if (length(possible_areas))
return pick(possible_areas)
///A rare variant that drops a crate containing syndicate uplink items
/datum/round_event_control/stray_cargo/syndicate
name = "Stray Syndicate Cargo Pod"
typepath = /datum/round_event/stray_cargo/syndicate
weight = 6
max_occurrences = 1
earliest_start = 30 MINUTES
/datum/round_event/stray_cargo/syndicate
possible_pack_types = list(/datum/supply_pack/misc/syndicate)
///Apply the syndicate pod skin
/datum/round_event/stray_cargo/syndicate/make_pod()
var/obj/structure/closet/supplypod/S = new
S.setStyle(STYLE_SYNDICATE)
return S
-46
View File
@@ -140,22 +140,6 @@
typepath = /datum/round_event/vent_clog/plasma_decon
max_occurrences = 0
/datum/round_event_control/vent_clog/female
name = "Clogged Vents; Girlcum"
typepath = /datum/round_event/vent_clog/female
max_occurrences = 0
/datum/round_event/vent_clog/female
reagentsAmount = 100
/datum/round_event_control/vent_clog/male
name = "Clogged Vents: Semen"
typepath = /datum/round_event/vent_clog/male
max_occurrences = 0
/datum/round_event/vent_clog/male
reagentsAmount = 100
/datum/round_event/vent_clog/beer/announce()
priority_announce("The scrubbers network is experiencing an unexpected surge of pressurized beer. Some ejection of contents may occur.", "Atmospherics alert")
@@ -171,36 +155,6 @@
foam.start()
CHECK_TICK
/datum/round_event/vent_clog/male/announce()
priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejaculation of contents may occur.", "Atmospherics alert")
/datum/round_event/vent_clog/male/start()
for(var/obj/machinery/atmospherics/components/unary/vent in vents)
if(vent && vent.loc && !vent.welded)
var/datum/reagents/R = new/datum/reagents(1000)
R.my_atom = vent
R.add_reagent(/datum/reagent/consumable/semen, reagentsAmount)
var/datum/effect_system/foam_spread/foam = new
foam.set_up(200, get_turf(vent), R)
foam.start()
CHECK_TICK
/datum/round_event/vent_clog/female/announce()
priority_announce("The scrubbers network is experiencing a backpressure squirt. Some ejection of contents may occur.", "Atmospherics alert")
/datum/round_event/vent_clog/female/start()
for(var/obj/machinery/atmospherics/components/unary/vent in vents)
if(vent && vent.loc && !vent.welded)
var/datum/reagents/R = new/datum/reagents(1000)
R.my_atom = vent
R.add_reagent(/datum/reagent/consumable/femcum, reagentsAmount)
var/datum/effect_system/foam_spread/foam = new
foam.set_up(200, get_turf(vent), R)
foam.start()
CHECK_TICK
/datum/round_event/vent_clog/plasma_decon/announce()
priority_announce("We are deploying an experimental plasma decontamination system. Please stand away from the vents and do not breathe the smoke that comes out.", "Central Command Update")
+15
View File
@@ -0,0 +1,15 @@
/datum/round_event_control/wisdomcow
name = "Wisdom cow"
typepath = /datum/round_event/wisdomcow
max_occurrences = 1
weight = 20
/datum/round_event/wisdomcow/announce(fake)
priority_announce("A wise cow has been spotted in the area. Be sure to ask for her advice.", "Nanotrasen Cow Ranching Agency")
/datum/round_event/wisdomcow/start()
var/turf/targetloc = get_random_station_turf()
new /mob/living/simple_animal/cow/wisdom(targetloc)
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(1, targetloc)
smoke.start()
+46
View File
@@ -0,0 +1,46 @@
/datum/round_event_control/wizard/embedpocalypse
name = "Make Everything Embeddable"
weight = 2
typepath = /datum/round_event/wizard/embedpocalypse
max_occurrences = 1
earliest_start = 0 MINUTES
/datum/round_event/wizard/embedpocalypse/start()
for(var/obj/item/I in world)
CHECK_TICK
if(!(I.flags_1 & INITIALIZED_1))
continue
if(!I.embedding || I.embedding == EMBED_HARMLESS)
I.embedding = EMBED_POINTY
I.updateEmbedding()
I.name = "pointy [I.name]"
GLOB.embedpocalypse = TRUE
GLOB.stickpocalypse = FALSE // embedpocalypse takes precedence over stickpocalypse
/datum/round_event_control/wizard/embedpocalypse/sticky
name = "Make Everything Sticky"
weight = 6
typepath = /datum/round_event/wizard/embedpocalypse/sticky
max_occurrences = 1
earliest_start = 0 MINUTES
/datum/round_event_control/wizard/embedpocalypse/sticky/canSpawnEvent(players_amt, gamemode)
if(GLOB.embedpocalypse)
return FALSE
/datum/round_event/wizard/embedpocalypse/sticky/start()
for(var/obj/item/I in world)
CHECK_TICK
if(!(I.flags_1 & INITIALIZED_1))
continue
if(!I.embedding)
I.embedding = EMBED_HARMLESS
I.updateEmbedding()
I.name = "sticky [I.name]"
GLOB.stickpocalypse = TRUE
@@ -315,6 +315,15 @@
slice_path = /obj/item/reagent_containers/food/snacks/meat/rawcutlet/gondola
foodtype = RAW | MEAT
/obj/item/reagent_containers/food/snacks/meat/slab/wisdomcow
name = "wisdom cow meat"
desc = "The meat from the legendary creature known as the wisdom cow. You monster."
list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/medicine/liquid_wisdom = 5)
tastes = list("meat" = 1, "wisdom" = 5)
filling_color = "#18e3ca"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/steak/wisdomcow
slice_path = /obj/item/reagent_containers/food/snacks/meat/rawcutlet/wisdomcow
////////////////////////////////////// MEAT STEAKS ///////////////////////////////////////////////////////////
@@ -374,6 +383,10 @@
name = "gondola steak"
tastes = list("meat" = 1, "tranquility" = 1)
/obj/item/reagent_containers/food/snacks/meat/steak/wisdomcow
name = "wisdom cow steak"
tastes = list("meat" = 1, "wisdom" = 5)
//////////////////////////////// MEAT CUTLETS ///////////////////////////////////////////////////////
//Raw cutlets
@@ -441,6 +454,11 @@
cooked_type = /obj/item/reagent_containers/food/snacks/meat/cutlet/gondola
tastes = list("meat" = 1, "tranquility" = 1)
/obj/item/reagent_containers/food/snacks/meat/rawcutlet/wisdomcow
name = "raw wisdom cow cutlet"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/cutlet/wisdomcow
tastes = list("meat" = 1, "wisdom" = 5)
//Cooked cutlets
/obj/item/reagent_containers/food/snacks/meat/cutlet
@@ -488,3 +506,7 @@
/obj/item/reagent_containers/food/snacks/meat/cutlet/chicken
name = "chicken cutlet"
tastes = list("chicken" = 1)
/obj/item/reagent_containers/food/snacks/meat/cutlet/wisdomcow
name = "wisdom cow cutlet"
tastes = list("meat" = 1, "wisdom" = 5)
@@ -88,7 +88,7 @@
/obj/item/t_scanner = 5,
/obj/item/airlock_painter = 1,
/obj/item/stack/cable_coil = 6,
/obj/item/stack/medical/bruise_pack = 1,
/obj/item/stack/medical/suture = 1,
/obj/item/stack/rods = 3,
/obj/item/stack/sheet/cardboard = 2,
/obj/item/stack/sheet/metal = 1,
+1 -1
View File
@@ -195,7 +195,7 @@
/obj/item/reagent_containers/food/snacks/grown/firelemon/ex_act(severity)
qdel(src) //Ensuring that it's deleted by its own explosion
/obj/item/reagent_containers/food/snacks/grown/firelemon/proc/prime()
/obj/item/reagent_containers/food/snacks/grown/firelemon/proc/prime(mob/living/lanced_by)
switch(seed.potency) //Combustible lemons are alot like IEDs, lots of flame, very little bang.
if(0 to 30)
update_mob()
+1 -1
View File
@@ -223,7 +223,7 @@
/obj/item/reagent_containers/food/snacks/grown/cherry_bomb/ex_act(severity)
qdel(src) //Ensuring that it's deleted by its own explosion. Also prevents mass chain reaction with piles of cherry bombs
/obj/item/reagent_containers/food/snacks/grown/cherry_bomb/proc/prime()
/obj/item/reagent_containers/food/snacks/grown/cherry_bomb/proc/prime(mob/living/lanced_by)
icon_state = "cherry_bomb_lit"
playsound(src, 'sound/effects/fuse.ogg', seed.potency, 0)
addtimer(CALLBACK(src, /obj/item/reagent_containers/food/snacks/grown/cherry_bomb/proc/detonate), rand(50, 100))
@@ -283,10 +283,8 @@
/obj/item/integrated_circuit/arithmetic/square_root/do_work()
var/result = 0
for(var/k in 2 to inputs.len)
var/I = get_pin_data(IC_INPUT, k)
if(isnum(I))
result += sqrt(I)
var/I = get_pin_data(IC_INPUT, 1)
result = sqrt(I)
set_pin_data(IC_OUTPUT, 1, result)
push_data()
+89 -57
View File
@@ -1,21 +1,27 @@
/datum/minimap
var/name
var/name = "minimap"
var/icon/overlay_icon
// The map icons
var/icon/map_icon
var/icon/meta_icon
var/icon/overlay_icon
var/list/color_area_names = list()
var/minx
var/maxx
var/miny
var/maxy
var/z_level
var/id = 0
var/static/next_id = 0
/datum/minimap/New(z, x1 = 1, y1 = 1, x2 = world.maxx, y2 = world.maxy, name)
var/z_level
var/id = ""
/datum/minimap/New(z, x1 = 1, y1 = 1, x2 = world.maxx, y2 = world.maxy, name = "minimap")
if(!z)
CRASH("ERROR: new minimap requested without z level") //CRASH to halt the operatio
src.name = name
id = ++next_id
z_level = z
id = "[md5("[z_level]" + src.name + REF(src))]" //use it's own md5 as a special identifier
var/crop_x1 = x2
var/crop_x2 = x1
@@ -25,10 +31,11 @@
// do the generating
map_icon = new('html/blank.png')
meta_icon = new('html/blank.png')
map_icon.Scale(x2-x1+1, y2-y1+1) // arrays start at 1
meta_icon.Scale(x2-x1+1, y2-y1+1)
map_icon.Scale(x2 - x1 + 1, y2 - y1 + 1) // arrays start at 1
meta_icon.Scale(x2 - x1 + 1, y2 - y1 + 1)
var/list/area_to_color = list()
for(var/turf/T in block(locate(x1,y1,z),locate(x2,y2,z)))
for(var/turf/T in block(locate(x1, y1, z_level), locate(x2, y2, z_level)))
var/area/A = T.loc
var/img_x = T.x - x1 + 1 // arrays start at 1
var/img_y = T.y - y1 + 1
@@ -37,21 +44,26 @@
crop_x2 = max(crop_x2, T.x)
crop_y1 = min(crop_y1, T.y)
crop_y2 = max(crop_y2, T.y)
var/meta_color = area_to_color[A]
if(!meta_color)
meta_color = rgb(rand(0,255),rand(0,255),rand(0,255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens
meta_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens
area_to_color[A] = meta_color
color_area_names[meta_color] = A.name
meta_icon.DrawBox(meta_color, img_x, img_y)
if(istype(T, /turf/closed/wall))
map_icon.DrawBox("#000000", img_x, img_y)
else if(!istype(A, /area/space))
var/color = A.minimap_color || "#FF00FF"
if(locate(/obj/machinery/power/solar) in T)
color = "#02026a"
if((locate(/obj/effect/spawner/structure/window) in T) || (locate(/obj/structure/grille) in T))
color = BlendRGB(color, "#000000", 0.5)
map_icon.DrawBox(color, img_x, img_y)
map_icon.Crop(crop_x1, crop_y1, crop_x2, crop_y2)
meta_icon.Crop(crop_x1, crop_y1, crop_x2, crop_y2)
minx = crop_x1
@@ -60,14 +72,17 @@
maxy = crop_y2
overlay_icon = new(map_icon)
overlay_icon.Scale(16, 16)
/datum/minimap/proc/send(mob/user)
//we're done baking, now we ship it.
register_asset("minimap-[id].png", map_icon)
register_asset("minimap-[id]-meta.png", meta_icon)
send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon), verify=FALSE)
/datum/minimap/proc/send(mob/user)
if(!id)
CRASH("ERROR: send called, but the minimap id is null/missing. ID: [id]")
send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
/datum/minimap_group
var/list/minimaps
var/list/minimaps = list()
var/static/next_id = 0
var/id
var/name
@@ -75,46 +90,62 @@
/datum/minimap_group/New(list/maps, name)
id = ++next_id
src.name = name
minimaps = maps || list()
if(maps)
minimaps = maps
/datum/minimap_group/proc/show(mob/user)
if(!length(minimaps))
to_chat(user, "<span class='boldwarning'>ERROR: Attempted to access an empty datum/minimap_group. This should probably not happen.</span>")
return
var/list/datas = list()
var/list/info = list()
var/datum/minimap/first_map = minimaps[1]
for(var/i in 1 to length(minimaps))
for(var/i in 1 to length(minimaps))// OLD: for(var/i in 1 to length(minimaps))
var/datum/minimap/M = minimaps[i]
M.send(user)
info += "<img src='minimap-[M.id].png' id='map-[i]'><img src='minimap-[M.id]-meta.png' style='display: none' id='map-[i]-meta'><div id='label-[i]'></div>"
info += {"
<div class="block">
<div> <!-- The div is in here to fit it both in the block div -->
<img id='map-[i]' src='minimap-[M.id].png' />
<img id='map-[i]-meta' src='minimap-[M.id]-meta.png' style='display: none' />
</div>
<div class="statusDisplay" id='label-[i]'></div>
</div>
"}
datas += json_encode(M.color_area_names);
info = info.Join()
var/html = {"
<!DOCTYPE html>
<HTML>
<HEAD>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<script>
function hexify(num) {
if(!num) num = 0;
num = num.toString(16);
if(num.length == 1) num = "0" + num;
return num;
}
window.onload = function() {
var datas = \[[jointext(datas, ",")]]
if(window.HTMLCanvasElement) {
for(var i = 0; i < [minimaps.len]; i++) {
(function() {
info = info.Join()
//this is bad. Too bad!
var/headerJS = {"
<script>
function hexify(num) {
if(!num){
num = 0;
}
num = num.toString(16);
if(num.length == 1){
num = "0" + num;
}
return num;
}
window.onload = function() {
var datas = \[[jointext(datas, ",")]]
if(!window.HTMLCanvasElement){
//something has gone horribly wrong!
return false
}
for(var i = 0; i < [length(minimaps)]; i++){
//the fuck is this wrapped?
var data = datas\[i];
var img = document.getElementById("map-" + (i+1));
if(!img) return;
var img = document.getElementById("map-" + (i + 1));
if(!img){
return; //if it does not exist, it means it's done!
}
var canvas = document.createElement("canvas");
canvas.width = img.width * 2;
canvas.height = img.height * 2;
var ctx = canvas.getContext('2d');
ctx.msImageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
@@ -123,9 +154,10 @@ window.onload = function() {
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(document.getElementById("map-" + (i+1) + "-meta"), 0, 0);
var imagedata = ctx.getImageData(0, 0, img.width, img.height);
var label = document.getElementById("label-" + (i+1));
canvas.onmousemove = function(e) {
canvas.onmousemove = function(e){
var rect = canvas.getBoundingClientRect();
var x = Math.floor(e.offsetX * img.width / rect.width);
var y = Math.floor(e.offsetY * img.height / rect.height);
@@ -134,23 +166,23 @@ window.onload = function() {
label.textContent = data\[color];
canvas.title = data\[color];
}
canvas.onmouseout = function(e) {
label.textContent = "";
canvas.onmouseout = function(e){
canvas.title = "";
}
})();
}
}
}
}
</script>
<STYLE>
img, canvas {
width: 100%
}
</STYLE>
<TITLE>[name]</TITLE>
</HEAD>
<BODY>[info]</BODY>
</HTML>"}
</script>
<style>
img, canvas {
width: 100%;
background-color: white;
}
</style>
"}
user << browse(html, "window=minimap_[id];size=768x[round(768 / first_map.map_icon.Width() * first_map.map_icon.Height() + 50)]")
var/datum/browser/popup = new(user, "minimap_[id]", name, 500, 700)
popup.add_head_content(headerJS) //set the head
popup.set_content(info)
var/datum/minimap/MICO = minimaps[1]
popup.set_title_image(MICO.overlay_icon)
popup.open(FALSE)
@@ -11,7 +11,7 @@
desc = "It's watching you suspiciously."
/obj/structure/closet/crate/necropolis/tendril/PopulateContents()
var/loot = rand(1,28)
var/loot = rand(1,29)
switch(loot)
if(1)
new /obj/item/shared_storage/red(src)
@@ -77,6 +77,11 @@
new /obj/item/bedsheet/cult(src)
if(28)
new /obj/item/clothing/neck/necklace/memento_mori(src)
if(29)
if(prob(50))
new /obj/item/malf_upgrade
else
new /obj/item/disk/tech_disk/illegal
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disc
+3 -1
View File
@@ -94,7 +94,9 @@
//We want an accurate reading of .len
listclearnulls(BP.embedded_objects)
temp_bleed += 0.5 * BP.embedded_objects.len
for(var/obj/item/embeddies in BP.embedded_objects)
if(!embeddies.isEmbedHarmless())
temp_bleed += 0.5
if(brutedamage >= 20)
temp_bleed += (brutedamage * 0.013)
@@ -54,6 +54,8 @@ In all, this is a lot like the monkey code. /N
return
switch(M.a_intent)
if(INTENT_HELP)
if(M == src && check_self_for_injuries())
return
help_shake_act(M)
if(INTENT_GRAB)
grabbedby(M)
+10 -2
View File
@@ -247,7 +247,7 @@
/mob/living/carbon/Topic(href, href_list)
..()
//strip panel
if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
if(usr.canUseTopic(src, BE_CLOSE))
if(href_list["internal"] && !HAS_TRAIT(src, TRAIT_NO_INTERNALS))
var/slot = text2num(href_list["internal"])
var/obj/item/ITEM = get_item_by_slot(slot)
@@ -267,7 +267,15 @@
visible_message("<span class='danger'>[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].</span>", \
"<span class='userdanger'>[usr] [internal ? "opens" : "closes"] the valve on your [ITEM.name].</span>", \
target = usr, target_message = "<span class='danger'>You [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].</span>")
if(href_list["embedded_object"] && usr.canUseTopic(src, BE_CLOSE))
var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts
if(!L)
return
var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
return
SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
return
/mob/living/carbon/fall(forced)
loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing
@@ -72,7 +72,7 @@
I.add_mob_blood(src)//it embedded itself in you, of course it's bloody!
I.forceMove(src)
I.embedded()
L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier)
L.receive_damage(I.w_class*I.embedding["impact_pain_mult"])
visible_message("<span class='danger'>[I] embeds itself in [src]'s [L.name]!</span>","<span class='userdanger'>[I] embeds itself in your [L.name]!</span>")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
@@ -274,6 +274,9 @@
to_chat(M, "<span class='warning'>You can't put [p_them()] out with just your bare hands!</span>")
return
if(M == src && check_self_for_injuries())
return
if(health >= 0 && !(HAS_TRAIT(src, TRAIT_FAKEDEATH)))
var/friendly_check = FALSE
if(mob_run_block(M, 0, M.name, ATTACK_TYPE_UNARMED, 0, null, null, null))
@@ -341,6 +344,26 @@
update_mobility()
playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
/// Check ourselves to see if we've got any shrapnel, return true if we do. This is a much simpler version of what humans do, we only indicate we're checking ourselves if there's actually shrapnel
/mob/living/carbon/proc/check_self_for_injuries()
if(stat == DEAD || stat == UNCONSCIOUS)
return
var/embeds = FALSE
for(var/X in bodyparts)
var/obj/item/bodypart/LB = X
for(var/obj/item/I in LB.embedded_objects)
if(!embeds)
embeds = TRUE
// this way, we only visibly try to examine ourselves if we have something embedded, otherwise we'll still hug ourselves :)
visible_message("<span class='notice'>[src] examines [p_them()]self.</span>", \
"<span class='notice'>You check yourself for shrapnel.</span>")
if(I.isEmbedHarmless())
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] stuck to your [LB.name]!</a>")
else
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>")
return embeds
/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0)
. = ..()
+25 -4
View File
@@ -31,14 +31,35 @@
else if(get_bodypart(BODY_ZONE_HEAD))
. += "<span class='deadsay'>It appears that [t_his] brain is missing...</span>"
var/list/missing = get_missing_limbs()
var/list/msg = list("<span class='warning'>")
var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
var/list/disabled = list()
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
if(BP.disabled)
disabled += BP
missing -= BP.body_zone
for(var/obj/item/I in BP.embedded_objects)
if(I.isEmbedHarmless())
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!</B>\n"
else
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
for(var/X in disabled)
var/obj/item/bodypart/BP = X
var/damage_text
if(!(BP.get_damage(include_stamina = FALSE) >= BP.max_damage)) //Stamina is disabling the limb
damage_text = "limp and lifeless"
else
damage_text = (BP.brute_dam >= BP.burn_dam) ? BP.heavy_brute_msg : BP.heavy_burn_msg
msg += "<B>[capitalize(t_his)] [BP.name] is [damage_text]!</B>\n"
for(var/t in missing)
if(t==BODY_ZONE_HEAD)
. += "<span class='deadsay'><B>[t_His] [parse_zone(t)] is missing!</B></span>"
msg += "<span class='deadsay'><B>[t_His] [parse_zone(t)] is missing!</B></span>\n"
continue
. += "<span class='warning'><B>[t_His] [parse_zone(t)] is missing!</B></span>"
msg += "<span class='warning'><B>[t_His] [parse_zone(t)] is missing!</B></span>\n"
var/list/msg = list()
var/temp = getBruteLoss()
if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy
if(temp)
@@ -159,7 +159,10 @@
disabled += BP
missing -= BP.body_zone
for(var/obj/item/I in BP.embedded_objects)
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
if(I.isEmbedHarmless())
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!</B>\n"
else
msg += "<B>[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!</B>\n"
for(var/X in disabled)
var/obj/item/bodypart/BP = X
+1 -16
View File
@@ -220,22 +220,7 @@
var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects
if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore
return
var/time_taken = I.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to /
usr.visible_message("<span class='warning'>[usr] attempts to remove [I] from [usr.p_their()] [L.name].</span>","<span class='notice'>You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)</span>")
if(do_after(usr, time_taken, needhand = 1, target = src))
remove_embedded_unsafe(L, I, usr)
/* CITADEL EDIT: remove_embedded_unsafe replaces this code
if(!I || !L || I.loc != src || !(I in L.embedded_objects))
return
L.embedded_objects -= I
L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus.
I.forceMove(get_turf(src))
usr.put_in_hands(I)
usr.emote("scream")
usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","<span class='notice'>You successfully remove [I] from your [L.name].</span>")
if(!has_embedded_objects())
clear_alert("embeddedobject")
SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */
SEND_SIGNAL(src, COMSIG_CARBON_EMBED_RIP, I, L)
return
if(href_list["item"])
@@ -52,11 +52,6 @@
return martial_art_result
return ..()
/mob/living/carbon/human/can_embed(obj/item/I)
if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types))
return TRUE
return FALSE
/mob/living/carbon/human/proc/check_martial_melee_block()
if(mind)
if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE))
@@ -620,7 +615,10 @@
to_send += "\t <span class='[no_damage ? "notice" : "warning"]'>Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].</span>\n"
for(var/obj/item/I in LB.embedded_objects)
to_send += "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>\n"
if(I.isEmbedHarmless())
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] stuck to your [LB.name]!</a>")
else
to_chat(src, "\t <a href='?src=[REF(src)];embedded_object=[REF(I)];embedded_limb=[REF(LB)]' class='warning'>There is \a [I] embedded in your [LB.name]!</a>")
for(var/t in missing)
to_send += "<span class='boldannounce'>Your [parse_zone(t)] is missing!</span>\n"
@@ -35,10 +35,6 @@
//heart attack stuff
handle_heart()
if(stat != DEAD)
//Stuff jammed in your limbs hurts
handle_embedded_objects()
//Update our name based on whether our face is obscured/disfigured
name = get_visible_name()
@@ -303,25 +299,6 @@
return TRUE
return ..()
/mob/living/carbon/human/proc/handle_embedded_objects()
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
for(var/obj/item/I in BP.embedded_objects)
if(prob(I.embedding.embedded_pain_chance))
BP.receive_damage(I.w_class*I.embedding.embedded_pain_multiplier)
to_chat(src, "<span class='userdanger'>[I] embedded in your [BP.name] hurts!</span>")
if(prob(I.embedding.embedded_fall_chance))
BP.receive_damage(I.w_class*I.embedding.embedded_fall_pain_multiplier)
BP.embedded_objects -= I
I.forceMove(drop_location())
I.unembedded()
visible_message("<span class='danger'>[I] falls out of [name]'s [BP.name]!</span>","<span class='userdanger'>[I] falls out of your [BP.name]!</span>")
if(!has_embedded_objects())
clear_alert("embeddedobject")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
/mob/living/carbon/human/proc/handle_active_genes()
if(HAS_TRAIT(src, TRAIT_MUTATION_STASIS))
return
@@ -1,6 +1,7 @@
/datum/species/insect
name = "Anthromorphic Insect"
id = "insect"
say_mod = "chitters"
default_color = "00FF00"
species_traits = list(LIPS,EYECOLOR,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR,WINGCOLOR)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
@@ -13,6 +14,7 @@
liked_food = MEAT | FRUIT
disliked_food = TOXIC
icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
exotic_bloodtype = "BUG"
/datum/species/insect/spec_death(gibbed, mob/living/carbon/human/H)
if(H)
@@ -42,4 +44,4 @@
H.update_body()
/datum/species/insect/qualifies_for_rank(rank, list/features)
return TRUE
return TRUE
+1
View File
@@ -457,6 +457,7 @@
message = params
if(type_override)
emote_type = type_override
message = user.say_emphasis(message)
. = ..()
message = null
emote_type = EMOTE_VISIBLE
+5 -2
View File
@@ -1,6 +1,9 @@
/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!")
/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!", silent=FALSE)
var/armor = getarmor(def_zone, attack_flag)
if(silent)
return max(0, armor - armour_penetration)
//the if "armor" check is because this is used for everything on /living, including humans
if(armor && armour_penetration)
@@ -126,7 +129,7 @@
skipcatch = TRUE
blocked = TRUE
total_damage = block_calculate_resultant_damage(total_damage, block_return)
else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item()))
else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding["embed_chance"]) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item()))
embed_item(I)
hitpush = FALSE
skipcatch = TRUE //can't catch the now embedded item
@@ -1,7 +1,13 @@
//MEDBOT
//MEDBOT PATHFINDING
//MEDBOT ASSEMBLY
#define MEDBOT_PANIC_NONE 0
#define MEDBOT_PANIC_LOW 15
#define MEDBOT_PANIC_MED 35
#define MEDBOT_PANIC_HIGH 55
#define MEDBOT_PANIC_FUCK 70
#define MEDBOT_PANIC_ENDING 90
#define MEDBOT_PANIC_END 100
/mob/living/simple_animal/bot/medbot
name = "\improper Medibot"
@@ -64,6 +70,13 @@
var/upgraded_dispenser_3 //Do we have the nicer chemicals? - replaces bic with sal acid
var/upgraded_dispenser_4 //Do we have the nicer chemicals? - replaces charcoal/toxin with pentetic acid / pentetic jelly
//How panicked we are about being tipped over (why would you do this?)
var/tipped_status = MEDBOT_PANIC_NONE
//The name we got when we were tipped
var/tipper_name
//The last time we were tipped/righted and said a voice line, to avoid spam
var/last_tipping_action_voice = 0
/mob/living/simple_animal/bot/medbot/mysterious
name = "\improper Mysterious Medibot"
desc = "International Medibot of mystery."
@@ -373,10 +386,91 @@
else
return
/mob/living/simple_animal/bot/medbot/proc/tip_over(mob/user)
mobility_flags &= ~MOBILITY_MOVE
playsound(src, 'sound/machines/warning-buzzer.ogg', 50)
user.visible_message("<span class='danger'>[user] tips over [src]!</span>", "<span class='danger'>You tip [src] over!</span>")
mode = BOT_TIPPED
var/matrix/mat = transform
transform = mat.Turn(180)
/mob/living/simple_animal/bot/medbot/proc/set_right(mob/user)
mobility_flags &= MOBILITY_MOVE
var/list/messagevoice
if(user)
user.visible_message("<span class='notice'>[user] sets [src] right-side up!</span>", "<span class='green'>You set [src] right-side up!</span>")
if(user.name == tipper_name)
messagevoice = list("I forgive you." = 'sound/voice/medbot/forgive.ogg')
else
messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg')
else
visible_message("<span class='notice'>[src] manages to writhe wiggle enough to right itself.</span>")
messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg')
tipper_name = null
if(world.time > last_tipping_action_voice + 15 SECONDS)
last_tipping_action_voice = world.time
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70)
tipped_status = MEDBOT_PANIC_NONE
mode = BOT_IDLE
transform = matrix()
// if someone tipped us over, check whether we should ask for help or just right ourselves eventually
/mob/living/simple_animal/bot/medbot/proc/handle_panic()
tipped_status++
var/list/messagevoice
switch(tipped_status)
if(MEDBOT_PANIC_LOW)
messagevoice = list("I require assistance." = 'sound/voice/medbot/i_require_asst.ogg')
if(MEDBOT_PANIC_MED)
messagevoice = list("Please put me back." = 'sound/voice/medbot/please_put_me_back.ogg')
if(MEDBOT_PANIC_HIGH)
messagevoice = list("Please, I am scared!" = 'sound/voice/medbot/please_im_scared.ogg')
if(MEDBOT_PANIC_FUCK)
messagevoice = list("I don't like this, I need help!" = 'sound/voice/medbot/dont_like.ogg', "This hurts, my pain is real!" = 'sound/voice/medbot/pain_is_real.ogg')
if(MEDBOT_PANIC_ENDING)
messagevoice = list("Is this the end?" = 'sound/voice/medbot/is_this_the_end.ogg', "Nooo!" = 'sound/voice/medbot/nooo.ogg')
if(MEDBOT_PANIC_END)
speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel)
set_right() // strong independent medbot
if(prob(tipped_status))
do_jitter_animation(tipped_status * 0.1)
if(messagevoice)
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70)
else if(prob(tipped_status * 0.2))
playsound(src, 'sound/machines/warning-buzzer.ogg', 30, extrarange=-2)
/mob/living/simple_animal/bot/medbot/examine(mob/user)
. = ..()
if(tipped_status == MEDBOT_PANIC_NONE)
return
switch(tipped_status)
if(MEDBOT_PANIC_NONE to MEDBOT_PANIC_LOW)
. += "It appears to be tipped over, and is quietly waiting for someone to set it right."
if(MEDBOT_PANIC_LOW to MEDBOT_PANIC_MED)
. += "It is tipped over and requesting help."
if(MEDBOT_PANIC_MED to MEDBOT_PANIC_HIGH)
. += "They are tipped over and appear visibly distressed." // now we humanize the medbot as a they, not an it
if(MEDBOT_PANIC_HIGH to MEDBOT_PANIC_FUCK)
. += "<span class='warning'>They are tipped over and visibly panicking!</span>"
if(MEDBOT_PANIC_FUCK to INFINITY)
. += "<span class='warning'><b>They are freaking out from being tipped over!</b></span>"
/mob/living/simple_animal/bot/medbot/handle_automated_action()
if(!..())
return
if(mode == BOT_TIPPED)
handle_panic()
return
if(mode == BOT_HEALING)
return
@@ -392,10 +486,14 @@
if(QDELETED(patient))
if(!shut_up && prob(1))
var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg')
var/message = pick(messagevoice)
speak(message)
playsound(loc, messagevoice[message], 50, 0)
if(emagged && prob(30))
var/list/i_need_scissors = list('sound/voice/medbot/fuck_you.ogg', 'sound/voice/medbot/turn_off.ogg', 'sound/voice/medbot/im_different.ogg', 'sound/voice/medbot/close.ogg', 'sound/voice/medbot/shindemashou.ogg')
playsound(src, pick(i_need_scissors), 70)
else
var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg', "Why are we still here? Just to suffer?" = 'sound/voice/medbot/why.ogg')
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 50)
var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients.
patient = scan(/mob/living/carbon/human, oldpatient, scan_range)
oldpatient = patient
@@ -506,6 +604,27 @@
/mob/living/simple_animal/bot/medbot/proc/get_healchem_toxin(mob/M)
return HAS_TRAIT(M, TRAIT_TOXINLOVER)? treatment_tox_toxlover : treatment_tox
/mob/living/simple_animal/bot/medbot/attack_hand(mob/living/carbon/human/H)
if(H.a_intent == INTENT_DISARM && mode != BOT_TIPPED)
H.visible_message("<span class='danger'>[H] begins tipping over [src].</span>", "<span class='warning'>You begin tipping over [src]...</span>")
if(world.time > last_tipping_action_voice + 15 SECONDS)
last_tipping_action_voice = world.time // message for tipping happens when we start interacting, message for righting comes after finishing
var/list/messagevoice = list("Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg',"Please don't..." = 'sound/voice/medbot/please_dont.ogg',"I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', "Nooo..." = 'sound/voice/medbot/nooo.ogg', "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg')
var/message = pick(messagevoice)
speak(message)
playsound(src, messagevoice[message], 70, FALSE)
if(do_after(H, 3 SECONDS, target=src))
tip_over(H)
else if(H.a_intent == INTENT_HELP && mode == BOT_TIPPED)
H.visible_message("<span class='notice'>[H] begins righting [src].</span>", "<span class='notice'>You begin righting [src]...</span>")
if(do_after(H, 3 SECONDS, target=src))
set_right(H)
else
..()
/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
@@ -664,3 +783,11 @@
/obj/machinery/bot_core/medbot
req_one_access = list(ACCESS_MEDICAL, ACCESS_ROBOTICS)
#undef MEDBOT_PANIC_NONE
#undef MEDBOT_PANIC_LOW
#undef MEDBOT_PANIC_MED
#undef MEDBOT_PANIC_HIGH
#undef MEDBOT_PANIC_FUCK
#undef MEDBOT_PANIC_ENDING
#undef MEDBOT_PANIC_END
@@ -33,11 +33,12 @@
stop_automated_movement_when_pulled = 1
blood_volume = BLOOD_VOLUME_NORMAL
var/obj/item/udder/udder = null
var/datum/reagent/milk_reagent = /datum/reagent/consumable/milk
footstep_type = FOOTSTEP_MOB_SHOE
/mob/living/simple_animal/hostile/retaliate/goat/Initialize()
udder = new()
/mob/living/simple_animal/hostile/retaliate/goat/Initialize(/datum/reagent/milk_reagent)
udder = new (null, milk_reagent)
. = ..()
/mob/living/simple_animal/hostile/retaliate/goat/Destroy()
@@ -57,7 +58,7 @@
LoseTarget()
src.visible_message("<span class='notice'>[src] calms down.</span>")
if(stat == CONSCIOUS)
udder.generateMilk()
udder.generateMilk(milk_reagent)
eat_plants()
if(!pulledby)
for(var/direction in shuffle(list(1,2,4,8,5,6,9,10)))
@@ -137,13 +138,14 @@
health = 50
maxHealth = 50
var/obj/item/udder/udder = null
var/datum/reagent/milk_reagent = /datum/reagent/consumable/milk
gold_core_spawnable = FRIENDLY_SPAWN
blood_volume = BLOOD_VOLUME_NORMAL
footstep_type = FOOTSTEP_MOB_SHOE
/mob/living/simple_animal/cow/Initialize()
udder = new()
udder = new(null, milk_reagent)
. = ..()
/mob/living/simple_animal/cow/Destroy()
@@ -161,7 +163,7 @@
/mob/living/simple_animal/cow/Life()
. = ..()
if(stat == CONSCIOUS)
udder.generateMilk()
udder.generateMilk(milk_reagent)
/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M)
if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead)
@@ -189,6 +191,19 @@
else
..()
//Wisdom cow, speaks and bestows great wisdoms
/mob/living/simple_animal/cow/wisdom
name = "wisdom cow"
desc = "Known for its wisdom, shares it with all"
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/wisdomcow = 1) //truly the best meat
gold_core_spawnable = FALSE
speak_chance = 30 //the cow is eager to share its wisdom!
milk_reagent = /datum/reagent/medicine/liquid_wisdom
/mob/living/simple_animal/cow/wisdom/Initialize()
. = ..()
speak = GLOB.wisdoms //Done here so it's setup properly
/mob/living/simple_animal/chick
name = "\improper chick"
desc = "Adorable! They make such a racket though."
@@ -481,14 +496,16 @@
/obj/item/udder
name = "udder"
/obj/item/udder/Initialize()
/obj/item/udder/Initialize(loc, milk_reagent)
if(!milk_reagent)
milk_reagent = /datum/reagent/consumable/milk
create_reagents(50, NONE, NO_REAGENTS_VALUE)
reagents.add_reagent(/datum/reagent/consumable/milk, 20)
reagents.add_reagent(milk_reagent, 20)
. = ..()
/obj/item/udder/proc/generateMilk()
/obj/item/udder/proc/generateMilk(datum/reagent/milk_reagent)
if(prob(5))
reagents.add_reagent(/datum/reagent/consumable/milk, rand(5, 10))
reagents.add_reagent(milk_reagent, rand(5, 10))
/obj/item/udder/proc/milkAnimal(obj/O, mob/user)
var/obj/item/reagent_containers/glass/G = O
+1 -1
View File
@@ -113,7 +113,7 @@
if(name != real_name)
alt_name = " (died as [real_name])"
var/spanned = say_quote(message)
var/spanned = say_quote(say_emphasis(message))
message = emoji_parse(message)
var/rendered = "<span class='game deadsay'><span class='prefix'>DEAD:</span> <span class='name'>[name]</span>[alt_name] <span class='message'>[emoji_parse(spanned)]</span></span>"
log_talk(message, LOG_SAY, tag="DEAD")
+2 -2
View File
@@ -60,7 +60,7 @@ proc/get_top_level_mob(var/mob/S)
return FALSE
user.log_message(message, LOG_EMOTE)
message = "<b>[user]</b> " + "<i>[message]</i>"
message = "<b>[user]</b> " + "<i>[user.say_emphasis(message)]</i>"
for(var/mob/M in GLOB.dead_mob_list)
if(!M.client || isnewplayer(M))
@@ -121,7 +121,7 @@ proc/get_top_level_mob(var/mob/S)
return FALSE
user.log_message(message, LOG_SUBTLER)
message = "<b>[user]</b> " + "<i>[message]</i>"
message = "<b>[user]</b> " + "<i>[user.say_emphasis(message)]</i>"
if(emote_type == EMOTE_AUDIBLE)
user.audible_message(message=message,hearing_distance=1, ignored_mobs = GLOB.dead_mob_list)
@@ -15,4 +15,4 @@
/obj/item/throwing_star/ninja
name = "ninja throwing star"
throwforce = 30
embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 100, "embedded_fall_chance" = 0)
embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15)
+8 -5
View File
@@ -1,8 +1,11 @@
/datum/asset/group/oui_theme_nano
children = list(
/datum/asset/simple/jquery,
/datum/asset/simple/oui_theme_nano
)
/datum/asset/simple/oui_theme_nano
assets = list(
// JavaScript
"sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js',
"sui-nano-jquery.min.js" = 'html/oracle_ui/themes/nano/sui-nano-jquery.min.js',
// Stylesheets
"sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css',
"sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js',
"sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css',
)
+8 -1
View File
@@ -27,6 +27,7 @@
var/colour = "black" //what colour the ink is!
var/degrees = 0
var/font = PEN_FONT
embedding = list()
/obj/item/pen/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...</span>")
@@ -41,6 +42,7 @@
desc = "It's a normal red ink pen."
icon_state = "pen_red"
colour = "red"
throw_speed = 4 // red ones go faster (in this case, fast enough to embed!)
/obj/item/pen/invisible
desc = "It's an invisible pen marker."
@@ -56,8 +58,10 @@
switch(colour)
if("black")
colour = "red"
throw_speed++
if("red")
colour = "green"
throw_speed = initial(throw_speed)
if("green")
colour = "blue"
else
@@ -178,6 +182,7 @@
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") //these wont show up if the pen is off
sharpness = IS_SHARP
var/on = FALSE
embedding = list(embed_chance = EMBED_CHANCE)
/obj/item/pen/edagger/ComponentInitialize()
. = ..()
@@ -198,16 +203,18 @@
throwforce = initial(throwforce)
playsound(user, 'sound/weapons/saberoff.ogg', 5, 1)
to_chat(user, "<span class='warning'>[src] can now be concealed.</span>")
updateEmbedding()
else
on = TRUE
force = 18
w_class = WEIGHT_CLASS_NORMAL
name = "energy dagger"
hitsound = 'sound/weapons/blade1.ogg'
embedding = getEmbeddingBehavior(embed_chance = 100, embedded_fall_chance = 0) //rule of cool
embedding = list(embed_chance = 100, fall_chance = 0) //rule of cool
throwforce = 35
playsound(user, 'sound/weapons/saberon.ogg', 5, 1)
to_chat(user, "<span class='warning'>[src] is now active.</span>")
updateEmbedding()
update_icon()
/obj/item/pen/edagger/update_icon_state()
+12 -8
View File
@@ -1,17 +1,21 @@
/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
distro += variance
for (var/i = max(1, pellets), i > 0, i--)
var/targloc = get_turf(target)
ready_proj(target, user, quiet, zone_override, fired_from)
var/targloc = get_turf(target)
ready_proj(target, user, quiet, zone_override, fired_from)
if(pellets == 1)
if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now...
if(randomspread)
spread = round((rand() - 0.5) * distro)
else //Smart spread
spread = round((i / pellets - 0.5) * distro)
spread = round(1 - 0.5) * distro
if(!throw_proj(target, targloc, user, params, spread))
return 0
if(i > 1)
newshot()
return FALSE
else
if(isnull(BB))
return FALSE
AddComponent(/datum/component/pellet_cloud, projectile_type, pellets)
SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro)
if(click_cooldown_override)
user.changeNext_move(click_cooldown_override)
else
@@ -31,7 +35,7 @@
else
BB.def_zone = user.zone_selected
BB.suppressed = quiet
if(isgun(fired_from))
var/obj/item/gun/G = fired_from
BB.damage *= G.projectile_damage_multiplier
@@ -21,3 +21,16 @@
name = "1.95x129mm incendiary bullet casing"
desc = "A 1.95x129mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
projectile_type = /obj/item/projectile/bullet/incendiary/mm195x129
/obj/item/ammo_casing/mm712x82/match
name = "7.12x82mm match bullet casing"
desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
projectile_type = /obj/item/projectile/bullet/mm712x82_match
/obj/item/projectile/bullet/mm712x82_match
name = "7.12x82mm match bullet"
damage = 40
ricochets_max = 2
ricochet_chance = 60
ricochet_auto_aim_range = 4
ricochet_incidence_leeway = 35
@@ -11,6 +11,12 @@
desc = "A .357 armor-piercing bullet casing."
projectile_type = /obj/item/projectile/bullet/a357/ap
/obj/item/ammo_casing/a357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
caliber = "357"
projectile_type = /obj/item/projectile/bullet/a357/match
// 7.62x38mmR (Nagant Revolver)
/obj/item/ammo_casing/n762
@@ -47,4 +53,19 @@
name = ".38 Iceblox bullet casing"
desc = "A .38 Iceblox bullet casing."
caliber = "38"
projectile_type = /obj/item/projectile/bullet/c38/iceblox
projectile_type = /obj/item/projectile/bullet/c38/iceblox
/obj/item/ammo_casing/c38/match
name = ".38 Match bullet casing"
desc = "A .38 bullet casing, manufactured to exceedingly high standards."
projectile_type = /obj/item/projectile/bullet/c38/match
/obj/item/ammo_casing/c38/match/bouncy
name = ".38 Rubber bullet casing"
desc = "A .38 rubber bullet casing, manufactured to exceedingly high standards."
projectile_type = /obj/item/projectile/bullet/c38/match/bouncy
/obj/item/ammo_casing/c38/dumdum
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
projectile_type = /obj/item/projectile/bullet/c38/dumdum
@@ -143,3 +143,12 @@
reagents.add_reagent(/datum/reagent/toxin/acid/fluacid, 5) //this and the acid equal about 25ish burn, not counting the minute toxin damage dealt by their metabolism, this makes each dart about as lethal as a stechkin shot in upfront damage
reagents.add_reagent(/datum/reagent/toxin/acid, 5)
reagents.add_reagent(/datum/reagent/consumable/frostoil, 10) //tempgun slowdown goes both ways and adds to the burn
/obj/item/ammo_casing/shotgun/incapacitate
name = "custom incapacitating shot"
desc = "A shotgun casing filled with... something. used to incapacitate targets."
icon_state = "bountyshell"
projectile_type = /obj/item/projectile/bullet/pellet/shotgun_incapacitate
pellets = 12//double the pellets, but half the stun power of each, which makes this best for just dumping right in someone's face.
variance = 25
custom_materials = list(/datum/material/iron=4000)
@@ -7,6 +7,11 @@
max_ammo = 7
multiple_sprites = 1
/obj/item/ammo_box/a357/match
name = "speed loader (.357 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
ammo_type = /obj/item/ammo_casing/a357/match
/obj/item/ammo_box/a357/ap
name = "speed loader (.357 AP)"
ammo_type = /obj/item/ammo_casing/a357/ap
@@ -40,6 +45,16 @@
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
ammo_type = /obj/item/ammo_casing/c38/iceblox
/obj/item/ammo_box/c38/dumdum
name = "speed loader (.38 DumDum)"
desc = "Designed to quickly reload revolvers. DumDum bullets shatter on impact and shred the target's innards, likely getting caught inside."
ammo_type = /obj/item/ammo_casing/c38/dumdum
/obj/item/ammo_box/c38/match
name = "speed loader (.38 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
ammo_type = /obj/item/ammo_casing/c38/match
/obj/item/ammo_box/c32mm
name = "ammo box (.32 acp)"
desc = "Lethal .32 acp bullets, there's forty in the box."
@@ -20,3 +20,7 @@
/obj/item/ammo_box/magazine/mm195x129/update_icon()
..()
icon_state = "a762-[round(ammo_count(),10)]"
/obj/item/ammo_box/magazine/mm712x82/match
name = "box magazine (Match 7.12x82mm)"
ammo_type = /obj/item/ammo_casing/mm712x82/match
@@ -50,3 +50,8 @@
name = "riot shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/rubbershot
max_ammo = 6
/obj/item/ammo_box/magazine/internal/shot/bounty
name = "triple-barrel shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/incapacitate
max_ammo = 3
@@ -320,3 +320,18 @@
return TRUE
// DOUBLE BARRELED SHOTGUN and IMPROVISED SHOTGUN are in revolver.dm
/obj/item/gun/ballistic/shotgun/doublebarrel/hook
name = "hook modified sawn-off shotgun"
desc = "Range isn't an issue when you can bring your victim to you."
icon_state = "hookshotgun"
item_state = "shotgun"
mag_type = /obj/item/ammo_box/magazine/internal/shot/bounty
w_class = WEIGHT_CLASS_BULKY
weapon_weight = WEAPON_MEDIUM
force = 16 //it has a hook on it
attack_verb = list("slashed", "hooked", "stabbed")
hitsound = 'sound/weapons/bladeslice.ogg'
//our hook gun!
var/obj/item/gun/magic/hook/bounty/hook
var/toggled = FALSE
+92 -48
View File
@@ -58,9 +58,25 @@
var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
var/spread = 0 //amount (in degrees) of projectile spread
animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy
/// how many times we've ricochet'd so far (instance variable, not a stat)
var/ricochets = 0
var/ricochets_max = 2
var/ricochet_chance = 30
/// how many times we can ricochet max
var/ricochets_max = 0
/// 0-100, the base chance of ricocheting, before being modified by the atom we shoot and our chance decay
var/ricochet_chance = 0
/// 0-1 (or more, I guess) multiplier, the ricochet_chance is modified by multiplying this after each ricochet
var/ricochet_decay_chance = 0.7
/// 0-1 (or more, I guess) multiplier, the projectile's damage is modified by multiplying this after each ricochet
var/ricochet_decay_damage = 0.7
/// On ricochet, if nonzero, we consider all mobs within this range of our projectile at the time of ricochet to home in on like Revolver Ocelot, as governed by ricochet_auto_aim_angle
var/ricochet_auto_aim_range = 0
/// On ricochet, if ricochet_auto_aim_range is nonzero, we'll consider any mobs within this range of the normal angle of incidence to home in on, higher = more auto aim
var/ricochet_auto_aim_angle = 30
/// the angle of impact must be within this many degrees of the struck surface, set to 0 to allow any angle
var/ricochet_incidence_leeway = 40
///If the object being hit can pass ths damage on to something else, it should not do it for this bullet
var/force_hit = FALSE
//Hitscan
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
@@ -133,6 +149,11 @@
var/temporary_unstoppable_movement = FALSE
///If defined, on hit we create an item of this type then call hitby() on the hit target with this
var/shrapnel_type
///If TRUE, hit mobs even if they're on the floor and not our target
var/hit_stunned_targets = FALSE
/obj/item/projectile/Initialize()
. = ..()
permutated = list()
@@ -148,6 +169,7 @@
on_range()
/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range
SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT)
qdel(src)
//to get the correct limb (if any) for the projectile hit message
@@ -164,17 +186,17 @@
/obj/item/projectile/proc/prehit(atom/target)
return TRUE
/**
* Called when we hit something.
*
* @params
* * target - what we hit
* * blocked - 0 to 100 percentage mitigation/block
* * def zone - where we hit if we hit a mob.
*/
/obj/item/projectile/proc/on_hit(atom/target, blocked = 0, def_zone)
/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
// i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
// maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
var/obj/item/bodypart/hit_limb
if(isliving(target))
var/mob/living/L = target
hit_limb = L.check_limb_hit(def_zone)
SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb)
var/turf/target_loca = get_turf(target)
var/hitx
@@ -226,10 +248,13 @@
new impact_effect_type(target_loca, hitx, hity)
var/organ_hit_text = ""
var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info.
var/limb_hit = hit_limb
if(limb_hit)
organ_hit_text = " in \the [parse_zone(limb_hit)]"
if(suppressed)
if(suppressed==SUPPRESSED_VERY)
playsound(loc, hitsound, 5, TRUE, -1)
else if(suppressed)
playsound(loc, hitsound, 5, 1, -1)
to_chat(L, "<span class='userdanger'>You're shot by \a [src][organ_hit_text]!</span>")
else
@@ -259,53 +284,43 @@
else
return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume
/obj/item/projectile/proc/on_ricochet(atom/A)
if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
return
var/mob/living/unlucky_sob
var/best_angle = ricochet_auto_aim_angle
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
best_angle += NICE_SHOT_RICOCHET_BONUS
for(var/mob/living/L in range(ricochet_auto_aim_range, src.loc))
if(L.stat == DEAD || !isInSight(src, L))
continue
var/our_angle = abs(closer_angle_difference(Angle, get_projectile_angle(src.loc, L.loc)))
if(our_angle < best_angle)
best_angle = our_angle
unlucky_sob = L
if(unlucky_sob)
setAngle(get_projectile_angle(src, unlucky_sob.loc))
/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache)
beam_segments[beam_index] = pcache
beam_index = pcache
beam_segments[beam_index] = null
/**
* Determines if we should ricochet off of something.
* By default, asks the thing if we should ricochet off it, but because we're called first, we get final say.
* Returns TRUE or FALSE.
*/
/obj/item/projectile/proc/check_ricochet(atom/A)
if(ricochets > ricochets_max) //safety thing, we don't care about what the other thing says about this.
return FALSE
var/them = A.check_projectile_ricochet(src)
switch(them)
if(PROJECTILE_RICOCHET_PREVENT)
return FALSE
if(PROJECTILE_RICOCHET_FORCE)
return TRUE
if(PROJECTILE_RICOCHET_NO)
return FALSE
if(PROJECTILE_RICOCHET_YES)
return prob(ricochet_chance)
else
CRASH("Invalid return value for projectile ricochet check from [A].")
/**
* Handles ricocheting off of something.
* By default, also asks the thing to handle it, but because we're called first, we get final say.
*/
/obj/item/projectile/proc/handle_ricochet(atom/A)
ricochets++
ignore_source_check = TRUE
decayedRange = max(0, decayedRange - reflect_range_decrease)
pixel_move_interrupted = TRUE
range = decayedRange
return A.handle_projectile_ricochet(src)
/obj/item/projectile/Bump(atom/A)
if(!trajectory)
return
var/turf/T = get_turf(A)
if(check_ricochet(A))
if(check_ricochet(A) && A.handle_ricochet(src)) //if you can ricochet, attempt to ricochet off the object
on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
var/datum/point/pcache = trajectory.copy_to()
if(hitscan)
store_hitscan_collision(pcache)
handle_ricochet(A)
decayedRange = max(0, decayedRange - reflect_range_decrease)
ricochet_chance *= ricochet_decay_chance
damage *= ricochet_decay_damage
range = decayedRange
return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
@@ -381,6 +396,33 @@
return T
//Returns null if nothing at all was found.
/obj/item/projectile/proc/check_ricochet(atom/A)
if(ricochets > ricochets_max) //safety thing, we don't care about what the other thing says about this.
return FALSE
var/them = A.check_projectile_ricochet(src)
switch(them)
if(PROJECTILE_RICOCHET_PREVENT)
return FALSE
if(PROJECTILE_RICOCHET_FORCE)
return TRUE
if(PROJECTILE_RICOCHET_NO)
return FALSE
if(PROJECTILE_RICOCHET_YES)
var/chance = ricochet_chance * A.ricochet_chance_mod
if(firer && HAS_TRAIT(firer, TRAIT_NICE_SHOT))
chance += NICE_SHOT_RICOCHET_BONUS
if(prob(chance))
return TRUE
else
CRASH("Invalid return value for projectile ricochet check from [A].")
/obj/item/projectile/proc/check_ricochet_flag(atom/A)
if((flag in list("energy", "laser")) && (A.flags_ricochet & RICOCHET_SHINY))
return TRUE
if((flag in list("bomb", "bullet")) && (A.flags_ricochet & RICOCHET_HARD))
return TRUE
return FALSE
/// one move is a tile.
/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight.
if(!trajectory && isnull(forced_angle) && isnull(Angle))
@@ -418,6 +460,8 @@
/obj/item/projectile/proc/fire(angle, atom/direct_target)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) //If no angle needs to resolve it from xo/yo!
if(shrapnel_type)
AddElement(/datum/element/embed, projectile_payload = shrapnel_type)
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
if(direct_target)
@@ -15,6 +15,38 @@
/obj/item/projectile/bullet/c38
name = ".38 bullet"
damage = 25
ricochets_max = 2
ricochet_chance = 50
ricochet_auto_aim_angle = 10
ricochet_auto_aim_range = 3
/obj/item/projectile/bullet/c38/match
name = ".38 Match bullet"
ricochets_max = 4
ricochet_chance = 100
ricochet_auto_aim_angle = 40
ricochet_auto_aim_range = 5
ricochet_incidence_leeway = 50
ricochet_decay_chance = 1
ricochet_decay_damage = 1
/obj/item/projectile/bullet/c38/match/bouncy
name = ".38 Rubber bullet"
damage = 10
stamina = 30
armour_penetration = -30
ricochets_max = 6
ricochet_incidence_leeway = 70
ricochet_chance = 130
ricochet_decay_damage = 0.8
shrapnel_type = NONE
/obj/item/projectile/bullet/c38/dumdum
name = ".38 DumDum bullet"
damage = 15
armour_penetration = -30
ricochets_max = 0
shrapnel_type = /obj/item/shrapnel/bullet/c38/dumdum
/obj/item/projectile/bullet/c38/rubber
name = ".38 rubber bullet"
@@ -24,6 +56,7 @@
/obj/item/projectile/bullet/c38/trac
name = ".38 TRAC bullet"
damage = 10
ricochets_max = 0
/obj/item/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -39,6 +72,7 @@
/obj/item/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail
name = ".38 Hot Shot bullet"
damage = 20
ricochets_max = 0
/obj/item/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -51,6 +85,7 @@
name = ".38 Iceblox bullet"
damage = 20
var/temperature = 100
ricochets_max = 0
/obj/item/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE)
. = ..()
@@ -68,4 +103,14 @@
/obj/item/projectile/bullet/a357/ap
name = ".357 armor-piercing bullet"
damage = 45
armour_penetration = 45
armour_penetration = 45
// admin only really, for ocelot memes
/obj/item/projectile/bullet/a357/match
name = ".357 match bullet"
ricochets_max = 5
ricochet_chance = 140
ricochet_auto_aim_angle = 50
ricochet_auto_aim_range = 6
ricochet_incidence_leeway = 80
ricochet_decay_chance = 1
@@ -112,3 +112,8 @@
/obj/item/projectile/bullet/seed
damage = 4
stamina = 1
/obj/item/projectile/bullet/pellet/shotgun_incapacitate
name = "incapacitating pellet"
damage = 1
stamina = 6
@@ -250,7 +250,7 @@
if(istype(O, /obj/item/stack/medical/gauze))
var/obj/item/stack/medical/gauze/G = O
reac_volume = min((reac_volume / 10), G.amount)
new/obj/item/stack/medical/ointment(get_turf(G), reac_volume)
new/obj/item/stack/medical/mesh(get_turf(G), reac_volume)
G.use(reac_volume)
/datum/reagent/medicine/silver_sulfadiazine/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
@@ -336,7 +336,7 @@
if(istype(O, /obj/item/stack/medical/gauze))
var/obj/item/stack/medical/gauze/G = O
reac_volume = min((reac_volume / 10), G.amount)
new/obj/item/stack/medical/bruise_pack(get_turf(G), reac_volume)
new/obj/item/stack/medical/suture(get_turf(G), reac_volume)
G.use(reac_volume)
/datum/reagent/medicine/styptic_powder/on_mob_life(mob/living/carbon/M)
@@ -1563,3 +1563,20 @@
M.adjustOrganLoss(ORGAN_SLOT_LUNGS, 0.5)
..()
. = 1
/datum/reagent/medicine/liquid_wisdom
name = "liquid wisdom"
description = "the physical representation of wisdom, in liquid form"
taste_mult = 4
can_synth = FALSE
overdose_threshold = 30
value = REAGENT_VALUE_UNCOMMON // while it's 'rare', it can be milked from the wisdom cow
/datum/reagent/medicine/liquid_wisdom/on_mob_life(mob/living/carbon/C) //slightly stronger mannitol, from the wisdom cow
C.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3*REM)
if(prob(20))
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
if(prob(3))
to_chat(C, "[pick(GLOB.wisdoms)]") //give them a random wisdom
..()
@@ -2284,3 +2284,4 @@
S = new(T)
if(data["blood_DNA"])
S.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
+1
View File
@@ -138,6 +138,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
var/turf/T = get_step(src, movedir)
if(length(T.contents) > 150)
return
affecting.len = min(affecting.len, 150 - length(T.contents))
for(var/atom/movable/A in affecting)
if((A.loc == loc) && A.has_gravity())
A.ConveyorMove(movedir)
@@ -43,4 +43,20 @@
id = "libraryconsole"
build_path = /obj/item/circuitboard/computer/libraryconsole
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_ALL
departmental_flags = DEPARTMENTAL_FLAG_ALL
/datum/design/board/flight_control
name = "Computer Design (Shuttle Flight Controls)"
desc = "Allows for the construction of circuit boards used to build a console that enables shuttle flight"
id = "shuttle_control"
build_path = /obj/item/circuitboard/computer/shuttle/flight_control
category = list("Computer Boards", "Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/board/shuttle_docker
name = "Computer Design (Private Navigation Computer)"
desc = "Allows for the construction of circuit boards used to build a console that enables the targetting of custom flight locations"
id = "shuttle_docker"
build_path = /obj/item/circuitboard/computer/shuttle/docker
category = list("Computer Boards", "Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
@@ -10,6 +10,14 @@
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
/datum/design/board/shuttleseccamera
name = "Computer Design (Shuttle-Linked Security Camera)"
desc = "Same as a regular security camera console, but when linked to a shuttle, will specifically access cameras on that shuttle."
id = "shuttleseccamera"
build_path = /obj/item/circuitboard/computer/security/shuttle
category = list("Computer Boards")
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
/datum/design/board/secdata
name = "Computer Design (Security Records Console)"
desc = "Allows for the construction of circuit boards used to build a security records console."
@@ -166,5 +166,3 @@
desc = "This disk will add the ability to remotely feed slimes potions via the Xenobiology console, and lift the restrictions on the number of slimes that can be stored inside the Xenobiology console. This includes the contents of the basic slime upgrade disk."
id = "xenobio_slimeadv"
build_path = /obj/item/disk/xenobio_console_upgrade/slimeadv
@@ -130,3 +130,27 @@
departmental_flags = DEPARTMENTAL_FLAG_SECURITY
build_path = /obj/item/circuitboard/machine/hypnochair
category = list("Misc. Machinery")
/datum/design/board/engine_plasma
name = "Machine Design (Plasma Thruster Board)"
desc = "The circuit board for a plasma thruster."
id = "engine_plasma"
build_path = /obj/item/circuitboard/machine/shuttle/engine/plasma
category = list ("Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/board/engine_void
name = "Machine Design (Void Thruster Board)"
desc = "The circuit board for a void thruster."
id = "engine_void"
build_path = /obj/item/circuitboard/machine/shuttle/engine/void
category = list ("Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
/datum/design/board/engine_heater
name = "Machine Design (Engine Heater Board)"
desc = "The circuit board for an engine heater."
id = "engine_heater"
build_path = /obj/item/circuitboard/machine/shuttle/heater
category = list ("Shuttle Machinery")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
+66 -1
View File
@@ -760,4 +760,69 @@
materials = list(/datum/material/iron = 1000)
build_path = /obj/item/tank/internals/emergency_oxygen/engi/empty
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
/////////////////////////////////////////
/////////////////Tape////////////////////
/////////////////////////////////////////
/datum/design/sticky_tape
name = "Sticky Tape"
id = "sticky_tape"
build_type = PROTOLATHE
materials = list(/datum/material/plastic = 500)
build_path = /obj/item/stack/sticky_tape
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
/datum/design/super_sticky_tape
name = "Super Sticky Tape"
id = "super_sticky_tape"
build_type = PROTOLATHE
materials = list(/datum/material/plastic = 3000)
build_path = /obj/item/stack/sticky_tape/super
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
/datum/design/pointy_tape
name = "Pointy Tape"
id = "pointy_tape"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1500, /datum/material/plastic = 1000)
build_path = /obj/item/stack/sticky_tape/pointy
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
/////////////////////////////////////////
/////////////////Shuttle Upgrades////////
/////////////////////////////////////////
/datum/design/shuttle_speed_upgrade
name = "Shuttle Route Optimisation Upgrade"
desc = "A disk that allows for calculating shorter routes when inserted into a flight control console."
id = "disk_shuttle_route"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
build_path = /obj/item/shuttle_route_optimisation
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/shuttle_speed_upgrade_hyper
name = "Shuttle Bluespace Hyperlane Optimisation Upgrade"
desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one abuses bluespace hyperlanes for increased efficiency."
id = "disk_shuttle_route_hyper"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
build_path = /obj/item/shuttle_route_optimisation/hyperlane
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/shuttle_speed_upgrade_void
name = "Shuttle Voidspace Optimisation Upgrade"
desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one access voidspace for increased efficiency."
id = "disk_shuttle_route_void"
build_type = PROTOLATHE
materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000)
build_path = /obj/item/shuttle_route_optimisation/void
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
@@ -42,6 +42,16 @@
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/shuttlecreator
name = "Rapid Shuttle Designator"
desc = "An advanced device capable of defining areas for use in the creation of shuttles"
id = "shuttle_creator"
build_path = /obj/item/shuttle_creator
build_type = PROTOLATHE
materials = list(/datum/material/iron = 8000, /datum/material/titanium = 5000, /datum/material/bluespace = 5000)
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
/datum/design/handdrill
name = "Hand Drill"
desc = "A small electric hand drill with an interchangeable screwdriver and bolt bit"
@@ -80,3 +80,12 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE
experimental = TRUE
/datum/techweb_node/sticky_advanced
id = "sticky_advanced"
display_name = "Advanced Sticky Technology"
description = "Taking a good joke too far? Nonsense!"
design_ids = list("super_sticky_tape", "pointy_tape")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE
experimental = TRUE
@@ -63,3 +63,36 @@
prereq_ids = list("bluespace_warping", "syndicate_basic")
design_ids = list("desynchronizer")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/////////////////////////shuttle tech/////////////////////////
/datum/techweb_node/basic_shuttle_tech
id = "basic_shuttle"
display_name = "Basic Shuttle Research"
description = "Research the technology required to create and use basic shuttles."
prereq_ids = list("practical_bluespace", "adv_engi")
design_ids = list("shuttle_creator", "engine_plasma", "engine_heater", "shuttle_control", "shuttle_docker")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
/datum/techweb_node/shuttle_route_upgrade
id = "shuttle_route_upgrade"
display_name = "Route Optimisation Upgrade"
description = "Research into bluespace tunnelling, allowing us to reduce flight times by up to 20%!"
prereq_ids = list("basic_shuttle")
design_ids = list("disk_shuttle_route")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/shuttle_route_upgrade_hyper
id = "shuttle_route_upgrade_hyper"
display_name = "Hyperlane Optimisation Upgrade"
description = "Research into bluespace hyperlane, allowing us to reduce flight times by up to 40%!"
prereq_ids = list("shuttle_route_upgrade", "bluespace_warping")
design_ids = list("disk_shuttle_route_hyper")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
/datum/techweb_node/shuttle_route_upgrade_void
id = "shuttle_route_upgrade_void"
display_name = "Nullspace Breaching Upgrade"
description = "Research into voidspace tunnelling, allowing us to significantly reduce flight times."
prereq_ids = list("shuttle_route_upgrade_hyper", "alientech")
design_ids = list("disk_shuttle_route_void", "engine_void")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500)
@@ -66,3 +66,24 @@
design_ids = list("air_horn", "honker_main", "honker_peri", "honker_targ", "honk_chassis", "honk_head", "honk_torso", "honk_left_arm", "honk_right_arm",
"honk_left_leg", "honk_right_leg", "mech_banana_mortar", "mech_mousetrap_mortar", "mech_honker", "mech_punching_face", "implant_trombone", "borg_transform_clown")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
////////////////////////Tape tech////////////////////////////
/datum/techweb_node/sticky_basic
id = "sticky_basic"
display_name = "Basic Sticky Technology"
description = "The only thing left to do after researching this tech is to start printing out a bunch of 'kick me' signs."
prereq_ids = list("base")
design_ids = list("sticky_tape")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE
experimental = TRUE
// Can be researched after getting the basic sticky technology from the BEPIS major reward
/datum/techweb_node/sticky_advanced
id = "sticky_advanced"
display_name = "Advanced Sticky Technology"
description = "Taking a good joke too far? Nonsense!"
prereq_ids = list("sticky_basic")
design_ids = list("super_sticky_tape", "pointy_tape")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
hidden = TRUE
+1 -1
View File
@@ -71,6 +71,6 @@
to_chat(user, "<span class='notice'>You fried the consoles ID checking system.</span>")
return TRUE
/obj/machinery/computer/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
/obj/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
if(port && (shuttleId == initial(shuttleId) || override))
shuttleId = port.id
+268
View File
@@ -0,0 +1,268 @@
#define Z_DIST 500
#define CUSTOM_ENGINES_START_TIME 65
#define CALCULATE_STATS_COOLDOWN 2
/obj/machinery/computer/custom_shuttle
name = "nanotrasen shuttle flight controller"
desc = "A terminal used to fly shuttles defined by the Shuttle Zoning Designator"
circuit = /obj/item/circuitboard/computer/shuttle/flight_control
icon_screen = "shuttle"
icon_keyboard = "tech_key"
light_color = LIGHT_COLOR_CYAN
req_access = list( )
var/shuttleId
var/possible_destinations = "whiteship_home"
var/admin_controlled
var/no_destination_swap = 0
var/calculated_mass = 0
var/calculated_dforce = 0
var/calculated_speed = 0
var/calculated_engine_count = 0
var/calculated_consumption = 0
var/calculated_cooldown = 0
var/calculated_non_operational_thrusters = 0
var/calculated_fuel_less_thrusters = 0
var/target_fuel_cost = 0
var/targetLocation
var/datum/browser/popup
var/stat_calc_cooldown = 0
//Upgrades
var/distance_multiplier = 1
/obj/machinery/computer/custom_shuttle/examine(mob/user)
. = ..()
. += distance_multiplier < 1 ? "Bluespace shortcut module installed. Route is [distance_multiplier]x the original length." : ""
/obj/machinery/computer/custom_shuttle/ui_interact(mob/user)
var/list/options = params2list(possible_destinations)
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
var/dat = "[M ? "Current Location : [M.getStatusText()]" : "Shuttle link required."]<br><br>"
if(M)
dat += "<A href='?src=[REF(src)];calculate=1'>Run Flight Calculations</A><br>"
dat += "<b>Shuttle Data</b><hr>"
dat += "Shuttle Mass: [calculated_mass/10]tons<br>"
dat += "Engine Force: [calculated_dforce]kN ([calculated_engine_count] engines)<br>"
dat += "Sublight Speed: [calculated_speed]ms<sup>-1</sup><br>"
dat += calculated_speed < 1 ? "<b>INSUFFICIENT ENGINE POWER</b><br>" : ""
dat += calculated_non_operational_thrusters > 0 ? "<b>Warning: [calculated_non_operational_thrusters] thrusters offline.</b><br>" : ""
dat += "Fuel Consumption: [calculated_consumption]units per distance<br>"
dat += "Engine Cooldown: [calculated_cooldown]s<hr>"
var/destination_found
for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
if(!options.Find(S.id))
continue
if(!M.check_dock(S, silent=TRUE))
continue
if(calculated_speed == 0)
break
destination_found = TRUE
var/dist = round(calculateDistance(S))
dat += "<A href='?src=[REF(src)];setloc=[S.id]'>Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)])</A><br>"
if(!destination_found)
dat += "<B>No valid destinations</B><br>"
dat += "<hr>[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]"
dat += "<hr><A href='?src=[REF(src)];fly=1'>Initate Flight</A><br>"
dat += "<A href='?src=[REF(user)];mach_close=computer'>Close</a>"
popup = new(user, "computer", M ? M.name : "shuttle", 350, 450)
popup.set_content("<center>[dat]</center>")
popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/custom_shuttle/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
src.add_fingerprint(usr)
if(!allowed(usr))
to_chat(usr, "<span class='danger'>Access denied.</span>")
return
if(href_list["calculate"])
calculateStats()
ui_interact(usr)
return
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(!M)
return
if(M.launch_status == ENDGAME_LAUNCHED)
return
if(href_list["setloc"])
SetTargetLocation(href_list["setloc"])
ui_interact(usr)
return
else if(href_list["fly"])
Fly()
ui_interact(usr)
return
/obj/machinery/computer/custom_shuttle/proc/calculateDistance(var/obj/docking_port/stationary/port)
var/deltaX = port.x - x
var/deltaY = port.y - y
var/deltaZ = (port.z - z) * Z_DIST
return sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * distance_multiplier
/obj/machinery/computer/custom_shuttle/proc/linkShuttle(var/new_id)
shuttleId = new_id
possible_destinations = "whiteship_home;shuttle[new_id]_custom"
/obj/machinery/computer/custom_shuttle/proc/calculateStats(var/useFuel = FALSE, var/dist = 0, var/ignore_cooldown = FALSE)
if(!ignore_cooldown && stat_calc_cooldown >= world.time)
to_chat(usr, "<span>You are using this too fast, please slow down</span>")
return
stat_calc_cooldown = world.time + CALCULATE_STATS_COOLDOWN
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(!M)
return FALSE
//Reset data
calculated_mass = 0
calculated_dforce = 0
calculated_speed = 0
calculated_engine_count = 0
calculated_consumption = 0
calculated_cooldown = 0
calculated_fuel_less_thrusters = 0
calculated_non_operational_thrusters = 0
//Calculate all the data
var/list/areas = M.shuttle_areas
for(var/shuttleArea in areas)
calculated_mass += length(get_area_turfs(shuttleArea))
for(var/obj/machinery/shuttle/engine/E in shuttleArea)
E.check_setup()
if(!E.thruster_active) //Skipover thrusters with no valid heater
calculated_non_operational_thrusters ++
continue
if(E.attached_heater)
var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = E.attached_heater.resolve()
if(resolvedHeater && !resolvedHeater.hasFuel(dist * E.fuel_use) && useFuel)
calculated_fuel_less_thrusters ++
continue
calculated_engine_count++
calculated_dforce += E.thrust
calculated_consumption += E.fuel_use
calculated_cooldown = max(calculated_cooldown, E.cooldown)
//This should really be accelleration, but its a 2d spessman game so who cares
if(calculated_mass == 0)
return FALSE
calculated_speed = (calculated_dforce*1000) / (calculated_mass*100)
return TRUE
/obj/machinery/computer/custom_shuttle/proc/consumeFuel(var/dist)
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(!M)
return FALSE
//Calculate all the data
for(var/obj/machinery/shuttle/engine/shuttle_machine in GLOB.custom_shuttle_machines)
shuttle_machine.check_setup()
if(!shuttle_machine.thruster_active)
continue
if(get_area(M) != get_area(shuttle_machine))
continue
if(shuttle_machine.attached_heater)
var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = shuttle_machine.attached_heater.resolve()
if(resolvedHeater && !resolvedHeater.hasFuel(dist * shuttle_machine.fuel_use))
continue
resolvedHeater?.consumeFuel(dist * shuttle_machine.fuel_use)
shuttle_machine.fireEngine()
/obj/machinery/computer/custom_shuttle/proc/SetTargetLocation(var/newTarget)
if(!(newTarget in params2list(possible_destinations)))
log_admin("[usr] attempted to href dock exploit on [src] with target location \"[newTarget]\"")
message_admins("[usr] just attempted to href dock exploit on [src] with target location \"[newTarget]\"")
return
targetLocation = newTarget
say("Shuttle route calculated.")
return
/obj/machinery/computer/custom_shuttle/proc/Fly()
if(!targetLocation)
return
var/obj/docking_port/mobile/linkedShuttle = SSshuttle.getShuttle(shuttleId)
if(!linkedShuttle)
return
if(linkedShuttle.mode != SHUTTLE_IDLE)
return
if(!calculateStats(TRUE, 0, TRUE))
return
if(calculated_fuel_less_thrusters > 0)
say("Warning, [calculated_fuel_less_thrusters] do not have enough fuel for this journey, engine output may be limitted.")
if(calculated_speed < 1)
say("Insufficient engine power, shuttle requires [calculated_mass / 10]kN of thrust.")
return
var/obj/docking_port/stationary/targetPort = SSshuttle.getDock(targetLocation)
if(!targetPort)
return
var/dist = calculateDistance(targetPort)
var/time = min(max(round(dist / calculated_speed), 10), 90)
linkedShuttle.callTime = time * 10
linkedShuttle.rechargeTime = calculated_cooldown
//We need to find the direction of this console to the port
linkedShuttle.port_direction = angle2dir(dir2angle(dir) - (dir2angle(linkedShuttle.dir)) + 180)
linkedShuttle.preferred_direction = NORTH
linkedShuttle.ignitionTime = CUSTOM_ENGINES_START_TIME
linkedShuttle.count_engines()
linkedShuttle.hyperspace_sound(HYPERSPACE_WARMUP)
var/throwForce = clamp((calculated_speed / 2) - 5, 0, 10)
linkedShuttle.movement_force = list("KNOCKDOWN" = calculated_speed > 5 ? 3 : 0, "THROW" = throwForce)
if(!(targetLocation in params2list(possible_destinations)))
log_admin("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"")
message_admins("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"")
return
switch(SSshuttle.moveShuttle(shuttleId, targetLocation, 1))
if(0)
consumeFuel(dist)
say("Shuttle departing. Please stand away from the doors.")
if(1)
to_chat(usr, "<span class='warning'>Invalid shuttle requested.</span>")
else
to_chat(usr, "<span class='notice'>Unable to comply.</span>")
return
/obj/machinery/computer/custom_shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
if(port && (shuttleId == initial(shuttleId) || override))
linkShuttle(port.id)
//Custom shuttle docker locations
/obj/machinery/computer/camera_advanced/shuttle_docker/custom
name = "Shuttle Navigation Computer"
desc = "Used to designate a precise transit location for private ships."
lock_override = NONE
whitelist_turfs = list(/turf/open/space,
/turf/open/lava,
/turf/open/floor/plating/beach,
/turf/open/floor/plating/ashplanet,
/turf/open/floor/plating/asteroid,
/turf/open/floor/plating/lavaland_baseturf)
jumpto_ports = list("whiteship_home" = 1)
view_range = 12
designate_time = 100
circuit = /obj/item/circuitboard/computer/shuttle/docker
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Initialize()
. = ..()
GLOB.jam_on_wardec += src
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Destroy()
GLOB.jam_on_wardec -= src
return ..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/placeLandingSpot()
if(!shuttleId)
return //Only way this would happen is if someone else delinks the console while in use somehow
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(M?.mode != SHUTTLE_IDLE)
to_chat(usr, "<span class='warning'>You cannot target locations while in transit.</span>")
return
..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/attack_hand(mob/user)
if(!shuttleId)
to_chat(user, "<span class='warning'>You must link the console to a shuttle first.</span>")
return
return ..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/linkShuttle(var/new_id)
shuttleId = new_id
shuttlePortId = "shuttle[new_id]_custom"
+1 -1
View File
@@ -253,7 +253,7 @@
current_user.client.images -= remove_images
current_user.client.images += add_images
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
/obj/machinery/computer/camera_advanced/shuttle_docker/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
if(port && (shuttleId == initial(shuttleId) || override))
shuttleId = port.id
shuttlePortId = "[port.id]_custom"
+123 -41
View File
@@ -11,17 +11,31 @@
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
anchored = TRUE
//
/// The identifier of the port or ship.
/// This will be used in numerous other places like the console,
/// stationary ports and whatnot to tell them your ship's mobile
/// port can be used in these places, or the docking port is compatible, etc.
var/id
// this should point -away- from the dockingport door, ie towards the ship
///Common standard is for this to point -away- from the dockingport door, ie towards the ship
dir = NORTH
var/width = 0 //size of covered area, perpendicular to dir
var/height = 0 //size of covered area, parallel to dir
var/dwidth = 0 //position relative to covered area, perpendicular to dir
var/dheight = 0 //position relative to covered area, parallel to dir
///size of covered area, perpendicular to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/width = 0
///size of covered area, parallel to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/height = 0
///position relative to covered area, perpendicular to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/dwidth = 0
///position relative to covered area, parallel to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/dheight = 0
var/area_type
var/hidden = FALSE //are we invisible to shuttle navigation computers?
///are we invisible to shuttle navigation computers?
var/hidden = FALSE
///Delete this port after ship fly off.
var/delete_after = FALSE
/obj/docking_port/proc/get_save_vars()
return list("pixel_x", "pixel_y", "dir", "name", "req_access", "req_access_txt", "piping_layer", "color", "icon_state", "pipe_color", "amount", "width", "height", "dwidth", "dheight")
//these objects are indestructible
/obj/docking_port/Destroy(force)
@@ -155,8 +169,6 @@
/obj/docking_port/stationary
name = "dock"
area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
var/last_dock_time
var/datum/map_template/shuttle/roundstart_template
@@ -169,6 +181,9 @@
id = "[SSshuttle.stationary.len]"
if(name == "dock")
name = "dock[SSshuttle.stationary.len]"
if(!area_type)
var/area/place = get_area(src)
area_type = place?.type || SHUTTLE_DEFAULT_UNDERLYING_AREA // We might be created in nullspace
if(mapload)
for(var/turf/T in return_turfs())
@@ -183,6 +198,13 @@
SSshuttle.stationary -= src
. = ..()
/obj/docking_port/stationary/Moved(atom/oldloc, dir, forced)
. = ..()
if(area_type) // We already have one
return
var/area/newarea = get_area(src)
area_type = newarea?.type
/obj/docking_port/stationary/proc/load_roundstart()
if(json_key)
var/sid = SSmapping.config.shuttles[json_key]
@@ -235,16 +257,25 @@
var/list/shuttle_areas
var/timer //used as a timer (if you want time left to complete move, use timeLeft proc)
///used as a timer (if you want time left to complete move, use timeLeft proc)
var/timer
var/last_timer_length
///current shuttle mode
var/mode = SHUTTLE_IDLE
///time spent in transit (deciseconds). Should not be lower then 10 seconds without editing the animation of the hyperspace ripples.
var/callTime = 100
/// time spent "starting the engines". Also rate limits how often we try to reserve transit space if its ever full of transiting shuttles.
var/ignitionTime = 55
/// time spent after arrival before being able to begin ignition
var/rechargeTime = 0
/// time spent after transit 'landing' before actually arriving
var/prearrivalTime = 0
var/mode = SHUTTLE_IDLE //current shuttle mode
var/callTime = 100 //time spent in transit (deciseconds). Should not be lower then 10 seconds without editing the animation of the hyperspace ripples.
var/ignitionTime = 55 // time spent "starting the engines". Also rate limits how often we try to reserve transit space if its ever full of transiting shuttles.
// The direction the shuttle prefers to travel in
/// The direction the shuttle prefers to travel in, ie what direction
/// the animation will cause it to appear to be traveling in
var/preferred_direction = NORTH
// And the angle from the front of the shuttle to the port
/// relative direction of the docking port from the front of the shuttle
/// NORTH is towards front, EAST would be starboard side, WEST port, etc.
var/port_direction = NORTH
var/obj/docking_port/stationary/destination
@@ -254,13 +285,16 @@
var/launch_status = NOLAUNCH
var/list/movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
///Whether or not you want your ship to knock people down, and also whether it will throw them several tiles upon launching.
var/list/movement_force = list("KNOCKDOWN" = 3, "THROW" = 0)
var/list/ripples = list()
var/engine_coeff = 1 //current engine coeff
var/current_engines = 0 //current engine power
var/initial_engines = 0 //initial engine power
var/can_move_docking_ports = FALSE //if this shuttle can move docking ports other than the one it is docked at
var/engine_coeff = 1
var/current_engines = 0
var/initial_engines = 0
var/list/engine_list = list()
///if this shuttle can move docking ports other than the one it is docked at
var/can_move_docking_ports = FALSE
var/list/hidden_turfs = list()
/obj/docking_port/mobile/proc/register()
@@ -308,14 +342,12 @@
id = "[id][idnum]"
if(name == initial(name))
name = "[name] [idnum]"
for(var/i in shuttle_areas)
var/area/place = i
for(var/obj/machinery/computer/shuttle/comp in place)
comp.connect_to_shuttle(src, dock, idnum)
for(var/obj/machinery/computer/camera_advanced/shuttle_docker/comp in place)
comp.connect_to_shuttle(src, dock, idnum)
for(var/obj/machinery/status_display/shuttle/sd in place)
sd.connect_to_shuttle(src, dock, idnum)
for(var/place in shuttle_areas)
var/area/area = place
area.connect_to_shuttle(src, dock, idnum, FALSE)
for(var/each in place)
var/atom/atom = each
atom.connect_to_shuttle(src, dock, idnum, FALSE)
//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
@@ -423,7 +455,10 @@
if(initiate_docking(S1) != DOCKING_SUCCESS)
WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].")
else
previous = S0
if(S0.delete_after)
qdel(S0, TRUE)
else
previous = S0
else
WARNING("shuttle \"[id]\" could not enter transit space. S0=[S0 ? S0.id : "null"] S1=[S1 ? S1.id : "null"]")
@@ -536,7 +571,11 @@
// If we can't dock or we don't have a transit slot, wait for 20 ds,
// then try again
switch(mode)
if(SHUTTLE_CALL)
if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
mode = SHUTTLE_PREARRIVAL
setTimer(prearrivalTime)
return
var/error = initiate_docking(destination, preferred_direction)
if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
@@ -547,6 +586,10 @@
else if(error)
setTimer(20)
return
if(rechargeTime)
mode = SHUTTLE_RECHARGING
setTimer(rechargeTime)
return
if(SHUTTLE_RECALL)
if(initiate_docking(previous) != DOCKING_SUCCESS)
setTimer(20)
@@ -649,6 +692,10 @@
return "ESC"
if(SHUTTLE_STRANDED)
return "ERR"
if(SHUTTLE_RECHARGING)
return "RCH"
if(SHUTTLE_PREARRIVAL)
return "LDN"
return ""
// returns 5-letter timer string, used by status screens and mob status panel
@@ -667,7 +714,7 @@
/obj/docking_port/mobile/proc/getStatusText()
var/obj/docking_port/stationary/dockedAt = get_docked()
var/docked_at = dockedAt?.name || "unknown"
if(istype(dockedAt, /obj/docking_port/stationary/transit))
if (timeLeft() > 1 HOURS)
return "hyperspace"
@@ -678,8 +725,10 @@
else
dst = destination
. = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
else if(mode == SHUTTLE_RECHARGING)
return "[docked_at], recharging [getTimerStr()]"
else
return dockedAt?.name || "unknown"
return docked_at
/obj/docking_port/mobile/proc/getDbgStatusText()
@@ -711,19 +760,47 @@
return null
/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
var/s
var/selected_sound
switch(phase)
if(HYPERSPACE_WARMUP)
s = 'sound/effects/hyperspace_begin.ogg'
selected_sound = "hyperspace_begin"
if(HYPERSPACE_LAUNCH)
s = 'sound/effects/hyperspace_progress.ogg'
selected_sound = "hyperspace_progress"
if(HYPERSPACE_END)
s = 'sound/effects/hyperspace_end.ogg'
selected_sound = "hyperspace_end"
else
CRASH("Invalid hyperspace sound phase: [phase]")
for(var/A in areas)
for(var/obj/machinery/door/E in A) //dumb, I know, but playing it on the engines doesn't do it justice
playsound(E, s, 100, FALSE, max(width, height) - world.view)
// This previously was played from each door at max volume, and was one of the worst things I had ever seen.
// Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
// Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
var/range = (engine_coeff * max(width, height))
var/long_range = range * 2.5
var/atom/distant_source
if(LAZYLEN(engine_list))
distant_source = engine_list[1]
else
for(var/A in areas)
distant_source = locate(/obj/machinery/door) in A
if(distant_source)
break
if(distant_source)
for(var/mob/M in SSmobs.clients_by_zlevel[z])
var/dist_far = get_dist(M, distant_source)
if(dist_far <= long_range && dist_far > range)
M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff = 20)
else if(dist_far <= range)
var/source
if(engine_list.len == 0)
source = distant_source
else
var/closest_dist = 10000
for(var/obj/O in engine_list)
var/dist_near = get_dist(M, O)
if(dist_near < closest_dist)
source = O
closest_dist = dist_near
M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff = range / 2)
// Losing all initial engines should get you 2
// Adding another set of engines at 0.5 time
@@ -743,7 +820,12 @@
var/area/shuttle/areaInstance = thing
for(var/obj/structure/shuttle/engine/E in areaInstance.contents)
if(!QDELETED(E))
engine_list += E
. += E.engine_power
for(var/obj/machinery/shuttle/engine/E in areaInstance.contents)
if(!QDELETED(E))
engine_list += E
. += E.thruster_active ? 1 : 0
// Double initial engines to get to 0.5 minimum
// Lose all initial engines to get to 2
@@ -768,7 +850,7 @@
/obj/docking_port/mobile/proc/in_flight()
switch(mode)
if(SHUTTLE_CALL,SHUTTLE_RECALL)
if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
return TRUE
if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
return FALSE
@@ -0,0 +1,370 @@
#define SHUTTLE_CREATOR_MAX_SIZE CONFIG_GET(number/max_shuttle_size)
#define CUSTOM_SHUTTLE_LIMIT CONFIG_GET(number/max_shuttle_count)
#define CARDINAL_DIRECTIONS_X list(1, 0, -1, 0)
#define CARDINAL_DIRECTIONS_Y list(0, 1, 0, -1)
GLOBAL_VAR_INIT(custom_shuttle_count, 0) //The amount of custom shuttles created to prevent creating hundreds
GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (Heaters, engines)
//============ Shuttle Creator Object ============
/obj/item/shuttle_creator
name = "Rapid Shuttle Designator"
icon = 'icons/obj/tools.dmi'
icon_state = "rsd"
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
desc = "A device used to define the area required for custom ships. Uses bluespace crystals to create bluespace-capable ships."
density = FALSE
anchored = FALSE
flags_1 = CONDUCT_1
item_flags = NOBLUDGEON
force = 0
throwforce = 8
throw_speed = 3
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL
req_access_txt = "11"
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
var/ready = TRUE
//pre-designation
var/override_max_shuttles = FALSE
var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_shuttle_creator
//During designation
var/overwritten_area = /area/space
var/list/loggedTurfs = list()
var/loggedOldArea
var/recorded_shuttle_area
var/datum/shuttle_creator_overlay_holder/overlay_holder
//After designation
var/linkedShuttleId
/obj/item/shuttle_creator/Initialize()
. = ..()
internal_shuttle_creator = new()
internal_shuttle_creator.owner_rsd = src
overlay_holder = new()
/obj/item/shuttle_creator/Destroy()
. = ..()
if(internal_shuttle_creator)
internal_shuttle_creator.owner_rsd = null
QDEL_NULL(internal_shuttle_creator)
if(overlay_holder)
QDEL_NULL(overlay_holder)
/obj/item/shuttle_creator/attack_self(mob/user)
..()
if(linkedShuttleId)
return
if(GLOB.custom_shuttle_count > CUSTOM_SHUTTLE_LIMIT && !override_max_shuttles)
to_chat(user, "<span class='warning'>Too many shuttles have been created.</span>")
message_admins("[ADMIN_FLW(user)] attempted to create a shuttle, however [CUSTOM_SHUTTLE_LIMIT] have already been created.")
return
if(!internal_shuttle_creator)
return
overlay_holder.add_client(user.client)
internal_shuttle_creator.attack_hand(user)
/obj/item/shuttle_creator/afterattack(atom/target, mob/user, proximity_flag)
. = ..()
if(!ready)
to_chat(user, "<span class='warning'>You need to define a shuttle area first.</span>")
return
if(!proximity_flag)
return
if(istype(target, /obj/machinery/computer/custom_shuttle))
if(!linkedShuttleId)
to_chat(user, "<span class='warning'>Error, no defined shuttle linked to device</span>")
return
var/obj/machinery/computer/custom_shuttle/console = target
console.linkShuttle(linkedShuttleId)
to_chat(user, "<span class='notice'>Console linked successfully!</span>")
return
else if(istype(target, /obj/machinery/computer/camera_advanced/shuttle_docker/custom))
if(!linkedShuttleId)
to_chat(user, "<span class='warning'>Error, no defined shuttle linked to device</span>")
return
var/obj/machinery/computer/camera_advanced/shuttle_docker/custom/console = target
console.linkShuttle(linkedShuttleId)
to_chat(user, "<span class='notice'>Console linked successfully!</span>")
return
to_chat(user, "<span class='warning'>The [src] bleeps. Select an airlock to create a docking port, or a valid machine to link.</span>")
return
//=========== shuttle designation actions ============
/obj/item/shuttle_creator/proc/calculate_bounds(obj/docking_port/mobile/port)
if(!port || !istype(port, /obj/docking_port/mobile))
return FALSE
//Heights is the distance away from the port
//width is the distance perpendicular to the port
var/minX = INFINITY
var/maxX = 0
var/minY = INFINITY
var/maxY = 0
for(var/turf/T in loggedTurfs)
minX = min(T.x, minX)
maxX = max(T.x, maxX)
minY = min(T.y, minY)
maxY = max(T.y, maxY)
//Make sure shuttle was actually found.
if(maxX == INFINITY || maxY == INFINITY)
return FALSE
minX--
minY--
var/width = maxX - minX
var/height = maxY - minY
var/offset_x = port.x - minX
var/offset_y = port.y - minY
switch(port.dir) //Source: code/datums/shuttles.dm line 77 (14/03/2020) :)
if(NORTH)
port.width = width
port.height = height
port.dwidth = offset_x - 1
port.dheight = offset_y - 1
if(EAST)
port.width = height
port.height = width
port.dwidth = height - offset_y
port.dheight = offset_x - 1
if(SOUTH)
port.width = width
port.height = height
port.dwidth = width - offset_x
port.dheight = height - offset_y
if(WEST)
port.width = height
port.height = width
port.dwidth = offset_y - 1
port.dheight = width - offset_x
return TRUE
//Go through all the all_turfs and check which direction doesn't have the shuttle
/obj/item/shuttle_creator/proc/getNonShuttleDirection(turf/targetTurf)
var/position = null
if(!(get_offset_target_turf(targetTurf, 0, 1) in loggedTurfs))
if(position != null)
return null
position = NORTH
if(!(get_offset_target_turf(targetTurf, 0, -1) in loggedTurfs))
if(position != null)
return null
position = SOUTH
if(!(get_offset_target_turf(targetTurf, 1, 0) in loggedTurfs))
if(position != null)
return null
position = EAST
if(!(get_offset_target_turf(targetTurf, -1, 0) in loggedTurfs))
if(position != null)
return null
position = WEST
return position
/obj/item/shuttle_creator/proc/invertDir(var/input_dir)
if(input_dir == NORTH)
return SOUTH
else if(input_dir == SOUTH)
return NORTH
else if(input_dir == EAST)
return WEST
else if(input_dir == WEST)
return EAST
return null
/obj/item/shuttle_creator/proc/shuttle_create_docking_port(atom/target, mob/user)
if(loggedTurfs.len == 0 || !recorded_shuttle_area)
to_chat(user, "<span class='warning'>Invalid shuttle, restarting bluespace systems...</span>")
return FALSE
var/datum/map_template/shuttle/new_shuttle = new /datum/map_template/shuttle()
var/obj/docking_port/mobile/port = new /obj/docking_port/mobile(get_turf(target))
var/obj/docking_port/stationary/stationary_port = new /obj/docking_port/stationary(get_turf(target))
port.callTime = 50
port.dir = 1 //Point away from space.
port.id = "custom_[GLOB.custom_shuttle_count]"
linkedShuttleId = port.id
port.ignitionTime = 25
port.name = "Custom Shuttle"
port.port_direction = 2
port.preferred_direction = 4
port.area_type = recorded_shuttle_area
stationary_port.area_type = overwritten_area
var/portDirection = getNonShuttleDirection(get_turf(port))
var/invertedDir = invertDir(portDirection)
if(!portDirection || !invertedDir)
to_chat(usr, "<span class='warning'>Shuttle creation aborted, docking airlock must be on an external wall. Please select a new airlock.</span>")
port.Destroy()
stationary_port.Destroy()
linkedShuttleId = null
return FALSE
port.dir = invertedDir
port.port_direction = portDirection
if(!calculate_bounds(port))
to_chat(usr, "<span class='warning'>Bluespace calculations failed, please select a new airlock.</span>")
port.Destroy()
stationary_port.Destroy()
linkedShuttleId = null
return FALSE
port.shuttle_areas = list()
//var/list/all_turfs = port.return_ordered_turfs(port.x, port.y, port.z, port.dir)
var/list/all_turfs = loggedTurfs
for(var/i in 1 to all_turfs.len)
var/turf/curT = all_turfs[i]
var/area/cur_area = curT.loc
//Add the area to the shuttle <3
if(istype(cur_area, recorded_shuttle_area))
if(istype(curT, /turf/open/space))
continue
if(length(curT.baseturfs) < 2)
continue
//Add the shuttle base shit to the shuttle
curT.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle)
port.shuttle_areas[cur_area] = TRUE
port.linkup(new_shuttle, stationary_port)
port.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
port.initiate_docking(stationary_port)
port.mode = SHUTTLE_IDLE
port.timer = 0
port.register()
icon_state = "rsd_used"
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
//Check to see if the user can make a new area to prevent spamming
if(user)
if(user.create_area_cooldown >= world.time)
to_chat(user, "<span class='warning'>Smoke vents from the [src], maybe you should let it cooldown before using it again.</span>")
return FALSE
user.create_area_cooldown = world.time + 10
if(!loggedTurfs)
return FALSE
if(!check_area(loggedTurfs, FALSE)) //Makes sure nothing (Shuttles) has moved into the area during creation
return FALSE
//Create the new area
var/area/shuttle/custom/powered/newS
var/area/oldA = loggedOldArea
var/str = stripped_input(user, "Shuttle Name:", "Blueprint Editing", "", MAX_NAME_LEN)
if(!str || !length(str))
return FALSE
if(length(str) > 50)
to_chat(user, "<span class='warning'>The provided ship name is too long, blares the [src]</span>")
return FALSE
newS = new /area/shuttle/custom/powered()
newS.setup(str)
newS.set_dynamic_lighting()
//Shuttles always have gravity
newS.has_gravity = TRUE
newS.requires_power = TRUE
//Record the area for use when creating the docking port
recorded_shuttle_area = newS
for(var/i in 1 to loggedTurfs.len)
var/turf/turf_holder = loggedTurfs[i]
var/area/old_area = turf_holder.loc
newS.contents += turf_holder
turf_holder.change_area(old_area, newS)
newS.reg_in_areas_in_z()
var/list/firedoors = oldA.firedoors
for(var/door in firedoors)
var/obj/machinery/door/firedoor/FD = door
FD.CalculateAffectingAreas()
return TRUE
//Checks an area to ensure that the turfs provided are valid to be made into a shuttle
/obj/item/shuttle_creator/proc/check_area(list/turfs, addingTurfs = TRUE)
if(!turfs)
to_chat(usr, "<span class='warning'>Shuttles must be created in an airtight space, ensure that the shuttle is airtight, including corners.</span>")
return FALSE
if(turfs.len + (addingTurfs ? loggedTurfs.len : 0) > SHUTTLE_CREATOR_MAX_SIZE)
to_chat(usr, "<span class='warning'>The [src]'s internal cooling system wizzes violently and a message appears on the screen, \"Caution, this device can only handle the creation of shuttles up to [SHUTTLE_CREATOR_MAX_SIZE] units in size. Please reduce your shuttle by [turfs.len-SHUTTLE_CREATOR_MAX_SIZE]. Sorry for the inconvinience\"</span>")
return FALSE
//Check to see if it's a valid shuttle
for(var/i in 1 to turfs.len)
var/area/place = get_area(turfs[i])
//If any of the turfs are on station / not in space, a shuttle cannot be forced there
if(!place)
to_chat(usr, "<span class='warning'>You can't seem to overpower the bluespace harmonics in this location, try somewhere else.</span>")
return FALSE
if(istype(place, /area/space))
overwritten_area = /area/space
else if(istype(place, /area/lavaland/surface/outdoors))
overwritten_area = /area/lavaland/surface/outdoors
else
to_chat(usr, "<span class='warning'>Caution, shuttle must not use any material connected to the station. Your shuttle is currenly overlapping with [place.name]</span>")
return FALSE
//Finally, check to see if the area is actually attached
if(!LAZYLEN(loggedTurfs))
return TRUE
for(var/turf/T in turfs)
if(turf_connected_to_saved_turfs(T))
return TRUE
CHECK_TICK
to_chat(usr, "<span class='warning'>Caution, new areas of the shuttle must be connected to the other areas of the shuttle.</span>")
return FALSE
/obj/item/shuttle_creator/proc/turf_connected_to_saved_turfs(turf/T)
for(var/i in 1 to 4)
var/turf/adjacentT = get_offset_target_turf(T, CARDINAL_DIRECTIONS_X[i], CARDINAL_DIRECTIONS_Y[i])
if(adjacentT in loggedTurfs)
return TRUE
return FALSE
/obj/item/shuttle_creator/proc/turf_in_list(turf/T)
return loggedTurfs.Find(T)
/obj/item/shuttle_creator/proc/add_single_turf(turf/T)
if(!check_area(list(T)))
return FALSE
loggedTurfs |= T
loggedOldArea = get_area(T)
overlay_holder.highlight_turf(T)
/obj/item/shuttle_creator/proc/add_saved_area(mob/user)
var/static/area_or_turf_fail_types = typecacheof(list(
/turf/open/space,
/area/shuttle
))
//Detect the turfs connected in the curerrent enclosed area
var/list/turfs = detect_room(get_turf(user), area_or_turf_fail_types)
if(!check_area(turfs))
return FALSE
loggedOldArea = get_area(get_turf(user))
loggedTurfs |= turfs
overlay_holder.highlight_area(turfs)
//TODO READD THIS SHIT: icon_state = "rsd_used"
to_chat(user, "<span class='notice'>You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.</span>")
return turfs
/obj/item/shuttle_creator/proc/remove_single_turf(turf/T)
if(!turf_in_list(T))
return
loggedTurfs -= T
loggedOldArea = get_area(T)
overlay_holder.unhighlight_turf(T)
/obj/item/shuttle_creator/proc/reset_saved_area()
overlay_holder.clear_highlights()
loggedTurfs.Cut()
to_chat(usr, "<span class='notice'>You reset the area buffer on the [src].</span>")
#undef CARDINAL_DIRECTIONS_X
#undef CARDINAL_DIRECTIONS_Y
@@ -0,0 +1,101 @@
//============ Actions ============
/datum/action/innate/shuttle_creator
icon_icon = 'icons/mob/actions/actions_shuttle.dmi'
var/mob/living/C
var/mob/camera/aiEye/remote/shuttle_creation/remote_eye
var/obj/item/shuttle_creator/shuttle_creator
/datum/action/innate/shuttle_creator/Activate()
if(!target)
return TRUE
C = owner
remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target
shuttle_creator = internal_console.owner_rsd
//Add an area
/datum/action/innate/shuttle_creator/designate_area
name = "Designate Room"
button_icon_state = "designate_area"
/datum/action/innate/shuttle_creator/designate_area/Activate()
if(..())
return
shuttle_creator.add_saved_area(remote_eye)
//Add a single turf
/datum/action/innate/shuttle_creator/designate_turf
name = "Designate Turf"
button_icon_state = "designate_turf"
/datum/action/innate/shuttle_creator/designate_turf/Activate()
if(..())
return
var/turf/T = get_turf(remote_eye)
if(istype(T, /turf/open/space))
var/connectors_exist = FALSE
for(var/obj/structure/lattice/lattice in T)
connectors_exist = TRUE
break
if(!connectors_exist)
to_chat(usr, "<span class='warning'>This turf requires support, build some catwalks or lattices.</span>")
return
if(!shuttle_creator.check_area(list(T)))
return
if(shuttle_creator.turf_in_list(T))
return
shuttle_creator.add_single_turf(T)
//Clear a single entire area
/datum/action/innate/shuttle_creator/clear_turf
name = "Clear Turf"
button_icon_state = "clear_turf"
/datum/action/innate/shuttle_creator/clear_turf/Activate()
if(..())
return
shuttle_creator.remove_single_turf(get_turf(remote_eye))
//Clear the entire area
/datum/action/innate/shuttle_creator/reset
name = "Reset Buffer"
button_icon_state = "clear_area"
/datum/action/innate/shuttle_creator/reset/Activate()
if(..())
return
shuttle_creator.reset_saved_area()
//Finish the shuttle
/datum/action/innate/shuttle_creator/airlock
name = "Select Docking Airlock"
button_icon_state = "select_airlock"
/datum/action/innate/shuttle_creator/airlock/Activate()
if(..())
return
var/turf/T = get_turf(remote_eye)
for(var/obj/machinery/door/airlock/A in T)
if(get_area(A) != shuttle_creator.loggedOldArea)
to_chat(C, "<span class='warning'>Caution, airlock must be on the shuttle to function as a dock.</span>")
return
if(shuttle_creator.linkedShuttleId)
return
if(GLOB.custom_shuttle_count > CUSTOM_SHUTTLE_LIMIT)
to_chat(C, "<span class='warning'>Shuttle limit reached, sorry.</span>")
return
if(shuttle_creator.loggedTurfs.len > SHUTTLE_CREATOR_MAX_SIZE)
to_chat(C, "<span class='warning'>This shuttle is too large!</span>")
return
if(!shuttle_creator.getNonShuttleDirection(T))
to_chat(C, "<span class='warning'>Docking port must be on an external wall, with only 1 side exposed to space.</span>")
return
if(!shuttle_creator.create_shuttle_area(C))
return
if(shuttle_creator.shuttle_create_docking_port(A, C))
to_chat(C, "<span class='notice'>Shuttle created!</span>")
//Remove eye control
var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target
internal_console.remove_eye_control()
qdel(internal_console)
return
@@ -0,0 +1,93 @@
//============The internal camera console used for designating the area=============
/obj/machinery/computer/camera_advanced/shuttle_creator
name = "internal shuttle creator console"
desc = "You should not have access to this, please report this as a bug"
networks = list()
var/obj/item/shuttle_creator/owner_rsd
var/datum/action/innate/shuttle_creator/designate_area/area_action = new
var/datum/action/innate/shuttle_creator/designate_turf/turf_action = new
var/datum/action/innate/shuttle_creator/clear_turf/clear_turf_action = new
var/datum/action/innate/shuttle_creator/reset/reset_action = new
var/datum/action/innate/shuttle_creator/airlock/airlock_action = new
/obj/machinery/computer/camera_advanced/shuttle_creator/check_eye(mob/user)
if(user.eye_blind || user.incapacitated())
user.unset_machine()
/obj/machinery/computer/camera_advanced/shuttle_creator/CreateEye()
eyeobj = new /mob/camera/aiEye/remote/shuttle_creation(get_turf(owner_rsd))
eyeobj.origin = src
eyeobj.use_static = USE_STATIC_NONE
/obj/machinery/computer/camera_advanced/shuttle_creator/is_operational()
return TRUE
/obj/machinery/computer/camera_advanced/shuttle_creator/can_interact(mob/user)
if(!isliving(user))
return FALSE
var/mob/living/L = user
if(L.incapacitated())
return FALSE
return TRUE
/obj/machinery/computer/camera_advanced/shuttle_creator/GrantActions(mob/living/user)
..(user)
eyeobj.invisibility = SEE_INVISIBLE_LIVING
if(area_action)
area_action.target = src
area_action.Grant(user)
actions += area_action
if(turf_action)
turf_action.target = src
turf_action.Grant(user)
actions += turf_action
if(clear_turf_action)
clear_turf_action.target = src
clear_turf_action.Grant(user)
actions += clear_turf_action
if(reset_action)
reset_action.target = src
reset_action.Grant(user)
actions += reset_action
if(airlock_action)
airlock_action.target = src
airlock_action.Grant(user)
actions += airlock_action
/obj/machinery/computer/camera_advanced/shuttle_creator/remove_eye_control(mob/living/user)
. = ..()
owner_rsd.overlay_holder.remove_client()
eyeobj.invisibility = INVISIBILITY_MAXIMUM
if(user.client)
user.client.images -= eyeobj.user_image
/obj/machinery/computer/camera_advanced/shuttle_creator/attack_hand(mob/user)
if(!is_operational()) //you cant use broken machine you chumbis
return
if(current_user)
to_chat(user, "The console is already in use!")
return
var/mob/living/L = user
if(!can_use(user))
return
if(!eyeobj)
CreateEye()
if(!eyeobj.eye_initialized)
var/camera_location = get_turf(owner_rsd)
if(camera_location)
eyeobj.eye_initialized = TRUE
give_eye_control(L)
eyeobj.setLoc(camera_location)
var/mob/camera/aiEye/remote/shuttle_creation/shuttle_eye = eyeobj
shuttle_eye.source_turf = get_turf(user)
else
user.unset_machine()
else
var/camera_location = get_turf(owner_rsd)
var/mob/camera/aiEye/remote/shuttle_creation/eye = eyeobj
give_eye_control(L)
if(camera_location)
eye.source_turf = camera_location
eyeobj.setLoc(camera_location)
else
eyeobj.setLoc(eyeobj.loc)
@@ -0,0 +1,54 @@
//===============Camera Eye================
/mob/camera/aiEye/remote/shuttle_creation
name = "shuttle holo-drone"
icon = 'icons/obj/mining.dmi'
icon_state = "construction_drone"
visible_icon = FALSE
acceleration = 0
var/turf/source_turf
var/max_range = 12
/mob/camera/aiEye/remote/shuttle_creation/Initialize()
. = ..()
setLoc(get_turf(source_turf))
/mob/camera/aiEye/remote/shuttle_creation/update_remote_sight(mob/living/user)
user.sight = BLIND|SEE_TURFS
user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
user.sync_lighting_plane_alpha()
return TRUE
/mob/camera/aiEye/remote/shuttle_creation/relaymove(mob/user, direct)
dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated
var/initial = initial(sprint)
var/max_sprint = 50
if(cooldown && cooldown < world.timeofday) // 3 seconds
sprint = initial
for(var/i = 0; i < max(sprint, initial); i += 20)
var/turf/step = get_turf(get_step(src, direct))
if(step && can_move_to(step))
setLoc(step)
cooldown = world.timeofday + 5
if(acceleration)
sprint = min(sprint + 0.5, max_sprint)
else
sprint = initial
/mob/camera/aiEye/remote/shuttle_creation/proc/can_move_to(var/turf/T)
var/origin_x = source_turf.x
var/origin_y = source_turf.y
var/change_X = abs(origin_x - T.x)
var/change_Y = abs(origin_y - T.y)
return (change_X < max_range && change_Y < max_range)
/mob/camera/aiEye/remote/shuttle_creation/setLoc(T)
..()
if(eye_user?.client)
eye_user.client.images -= user_image
var/image/I = image(icon, loc, icon_state, FLY_LAYER, dir)
I.plane = MASSIVE_OBJ_LAYER
user_image = I
eye_user.client.images += user_image
@@ -0,0 +1,52 @@
/*
* Manages the overlays for the shuttle creator drone.
*/
/datum/shuttle_creator_overlay_holder
var/client/holder
var/list/images = list()
var/list/turfs = list()
/datum/shuttle_creator_overlay_holder/proc/add_client(client/C)
holder = C
holder.images += images
/datum/shuttle_creator_overlay_holder/proc/remove_client()
holder.images -= images
holder = null
/datum/shuttle_creator_overlay_holder/proc/clear_highlights()
if(holder)
holder.images -= images
images.Cut()
turfs.Cut()
/datum/shuttle_creator_overlay_holder/proc/create_hightlight(turf/T)
if(T in turfs)
return
var/image/I = image('icons/turf/overlays.dmi', T, "greenOverlay")
I.plane = ABOVE_LIGHTING_PLANE
images += I
holder.images += I
turfs += T
/datum/shuttle_creator_overlay_holder/proc/remove_hightlight(turf/T)
if(!(T in turfs))
return
turfs -= T
holder.images -= images
for(var/image/I in images)
if(get_turf(I) != T)
continue
images -= I
holder.images += images
/datum/shuttle_creator_overlay_holder/proc/highlight_area(list/turfs)
for(var/turf/T in turfs)
highlight_turf(T)
/datum/shuttle_creator_overlay_holder/proc/highlight_turf(turf/T)
create_hightlight(T)
/datum/shuttle_creator_overlay_holder/proc/unhighlight_turf(turf/T)
remove_hightlight(T)
@@ -0,0 +1,39 @@
/obj/item/shuttle_route_optimisation
name = "Route Optimisation Upgrade"
desc = "Used on a custom shuttle control console to calculate more efficient routes."
icon = 'icons/obj/module.dmi'
icon_state = "shuttledisk"
force = 0
throwforce = 0
throw_speed = 1
throw_range = 7
density = FALSE
anchored = FALSE
item_flags = NOBLUDGEON
var/upgrade_amount = 0.8
/obj/item/shuttle_route_optimisation/hyperlane
name = "Bluespace Hyperlane Calculator"
desc = "Used on a custom shuttle control console to allow for the following of bluespace hyperlanes, increasing the efficiency of the shuttle."
icon_state = "shuttledisk_better"
upgrade_amount = 0.6
/obj/item/shuttle_route_optimisation/void
name = "Voidspace Route Calculator"
desc = "Used on a custom shuttle control console to allow it to navigate into voidspace, making the routes almost instant."
icon_state = "shuttledisk_void"
upgrade_amount = 0.2
/obj/item/shuttle_route_optimisation/attack_obj(obj/O, mob/living/user)
. = ..()
if(!istype(O, /obj/machinery/computer))
return
if(!istype(O, /obj/machinery/computer/custom_shuttle))
to_chat(user, "<span class='warning'>This upgrade only works on a custom shuttle flight console.</span>")
return
if (!user.transferItemToLoc(src, get_turf(O)))
return
var/obj/machinery/computer/custom_shuttle/link_comp = O
link_comp.distance_multiplier = clamp(link_comp.distance_multiplier, 0, upgrade_amount)
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
to_chat(usr, "<span class='notice'>You insert the disk into the flight computer, allowing for routes to be [upgrade_amount]x the original distance.</span>")
@@ -76,20 +76,6 @@
break
M.dropItemToGround(item_to_retrieve)
if(iscarbon(M)) //Edge case housekeeping
var/mob/living/carbon/C = M
if(C.stomach_contents && (item_to_retrieve in C.stomach_contents))
C.stomach_contents -= item_to_retrieve
for(var/X in C.bodyparts)
var/obj/item/bodypart/part = X
if(item_to_retrieve in part.embedded_objects)
part.embedded_objects -= item_to_retrieve
to_chat(C, "<span class='warning'>The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!</span>")
if(!C.has_embedded_objects())
C.clear_alert("embeddedobject")
SEND_SIGNAL(C, COMSIG_CLEAR_MOOD_EVENT, "embedded")
break
else
if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery
var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc
+10 -12
View File
@@ -152,26 +152,24 @@
disabled += zone
return disabled
//Remove all embedded objects from all limbs on the carbon mob
///Remove a specific embedded item from the carbon mob
/mob/living/carbon/proc/remove_embedded_object(obj/item/I)
SEND_SIGNAL(src, COMSIG_CARBON_EMBED_REMOVAL, I)
///Remove all embedded objects from all limbs on the carbon mob
/mob/living/carbon/proc/remove_all_embedded_objects()
var/turf/T = get_turf(src)
for(var/X in bodyparts)
var/obj/item/bodypart/L = X
for(var/obj/item/I in L.embedded_objects)
L.embedded_objects -= I
I.forceMove(T)
I.unembedded()
remove_embedded_object(I)
clear_alert("embeddedobject")
SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "embedded")
/mob/living/carbon/proc/has_embedded_objects()
. = 0
/mob/living/carbon/proc/has_embedded_objects(include_harmless=FALSE)
for(var/X in bodyparts)
var/obj/item/bodypart/L = X
for(var/obj/item/I in L.embedded_objects)
return 1
if(!include_harmless && I.isEmbedHarmless())
continue
return TRUE
//Helper for quickly creating a new limb - used by augment code in species.dm spec_attacked_by
@@ -23,12 +23,7 @@
var/objects = 0
for(var/obj/item/I in L.embedded_objects)
objects++
I.forceMove(get_turf(H))
L.embedded_objects -= I
I.unembedded()
if(!H.has_embedded_objects())
H.clear_alert("embeddedobject")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "embedded")
H.remove_embedded_object(I)
if(objects > 0)
display_results(user, target, "<span class='notice'>You successfully remove [objects] objects from [H]'s [L.name].</span>",
@@ -189,6 +189,13 @@
cost = 4
include_modes = list(/datum/game_mode/nuclear)
/datum/uplink_item/ammo/machinegun/match
name = "7.12x82mm (Match) Box Magazine"
desc = "A 50-round magazine of 7.12x82mm ammunition for use in the L6 SAW; you didn't know there was a demand for match grade \
precision bullet hose ammo, but these rounds are finely tuned and perfect for ricocheting off walls all fancy-like."
item = /obj/item/ammo_box/magazine/mm712x82/match
cost = 10
/datum/uplink_item/ammo/machinegun
cost = 6
surplus = 0
+1
View File
@@ -22,6 +22,7 @@
/obj/item/clothing/suit/armor/vest/blueshirt = 1,
/obj/item/clothing/under/rank/security/officer/blueshirt = 1,
/obj/item/clothing/gloves/tackler = 5,
/obj/item/grenade/stingbang = 1,
/obj/item/ssword_kit = 1,
/obj/item/storage/bag/ammo = 2)
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)