Merge pull request #78 from Citadel-Station-13/master

4/27
This commit is contained in:
EmeraldSundisk
2021-04-27 10:34:29 -07:00
committed by GitHub
139 changed files with 4154 additions and 1332 deletions
+1 -1
View File
@@ -10444,7 +10444,7 @@
/area/maintenance/port/fore) /area/maintenance/port/fore)
"avV" = ( "avV" = (
/obj/structure/table/wood, /obj/structure/table/wood,
/obj/item/reagent_containers/food/snacks/burger/ghost, /obj/item/ectoplasm,
/turf/open/floor/wood, /turf/open/floor/wood,
/area/maintenance/port/fore) /area/maintenance/port/fore)
"avW" = ( "avW" = (
+3 -3
View File
@@ -16329,7 +16329,7 @@
/obj/docking_port/stationary{ /obj/docking_port/stationary{
dwidth = 1; dwidth = 1;
height = 4; height = 4;
id = "pod4_away"; id = "pod_4_away";
name = "recovery ship"; name = "recovery ship";
width = 3 width = 3
}, },
@@ -16339,7 +16339,7 @@
/obj/docking_port/stationary{ /obj/docking_port/stationary{
dwidth = 1; dwidth = 1;
height = 4; height = 4;
id = "pod3_away"; id = "pod_3_away";
name = "recovery ship"; name = "recovery ship";
width = 3 width = 3
}, },
@@ -16489,7 +16489,7 @@
dir = 4; dir = 4;
dwidth = 1; dwidth = 1;
height = 4; height = 4;
id = "pod2_away"; id = "pod_2_away";
name = "recovery ship"; name = "recovery ship";
width = 3 width = 3
}, },
+2 -3
View File
@@ -60,6 +60,5 @@ GLOBAL_LIST_INIT(podstyles, list(\
)) ))
//cit //cit
#define PACK_GOODY_NONE 0 #define PACK_GOODY_NONE 0 // can be bought by cargo and privates
#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo #define PACK_GOODY_PRIVATE 1 // can be bought only by privates
#define PACK_GOODY_PRIVATE 2 //can be bought only by privates
-3
View File
@@ -159,9 +159,6 @@
#define SHOVE_STAGGER_DURATION 35 #define SHOVE_STAGGER_DURATION 35
/// how long they're off balance for /// how long they're off balance for
#define SHOVE_OFFBALANCE_DURATION 30 #define SHOVE_OFFBALANCE_DURATION 30
//Shove disarming item list
GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
/obj/item/gun)))
//Embedded objects //Embedded objects
+9 -1
View File
@@ -266,6 +266,7 @@
#define COMSIG_MOB_GET_VISIBLE_MESSAGE "mob_get_visible_message" //from base of atom/visible_message(): (atom/A, msg, range, ignored_mobs) #define COMSIG_MOB_GET_VISIBLE_MESSAGE "mob_get_visible_message" //from base of atom/visible_message(): (atom/A, msg, range, ignored_mobs)
#define COMPONENT_NO_VISIBLE_MESSAGE 1 //exactly what's said on the tin. #define COMPONENT_NO_VISIBLE_MESSAGE 1 //exactly what's said on the tin.
#define COMSIG_MOB_ANTAG_ON_GAIN "mob_antag_on_gain" //from base of /datum/antagonist/on_gain(): (antag_datum) #define COMSIG_MOB_ANTAG_ON_GAIN "mob_antag_on_gain" //from base of /datum/antagonist/on_gain(): (antag_datum)
#define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage" //from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness)
#define COMSIG_MOB_SPELL_CAN_CAST "mob_spell_can_cast" //from base of /obj/effect/proc_holder/spell/can_cast(): (spell) #define COMSIG_MOB_SPELL_CAN_CAST "mob_spell_can_cast" //from base of /obj/effect/proc_holder/spell/can_cast(): (spell)
#define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" //from base of mob/swap_hand(): (obj/item) #define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" //from base of mob/swap_hand(): (obj/item)
@@ -303,6 +304,10 @@
#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override) #define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override)
#define COMPONENT_PREVENT_PARRY_START 1 #define COMPONENT_PREVENT_PARRY_START 1
#define COMSIG_LIVING_ATTACKER_SET "living_attacker_set" // from base of /mob/living/set_last_attacker(): (attacker)
#define COMSIG_LIVING_SET_AS_ATTACKER "living_set_as_attacker" // from base of /mob/living/set_last_attacker(): (target)
//ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS! //ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS!
#define COMSIG_LIVING_STATUS_STUN "living_stun" //from base of mob/living/Stun() (amount, update, ignore) #define COMSIG_LIVING_STATUS_STUN "living_stun" //from base of mob/living/Stun() (amount, update, ignore)
#define COMSIG_LIVING_STATUS_KNOCKDOWN "living_knockdown" //from base of mob/living/Knockdown() (amount, update, ignore) #define COMSIG_LIVING_STATUS_KNOCKDOWN "living_knockdown" //from base of mob/living/Knockdown() (amount, update, ignore)
@@ -357,7 +362,6 @@
// /obj/item signals // /obj/item signals
#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user) #define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
#define COMSIG_MOB_APPLY_DAMGE "mob_apply_damage" //from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone)
#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob) #define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
#define COMPONENT_NO_INTERACT 1 #define COMPONENT_NO_INTERACT 1
#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob) #define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
@@ -455,6 +459,10 @@
// /datum/mutation signals // /datum/mutation signals
#define COMSIG_HUMAN_MUTATION_LOSS "human_mutation_loss" //from datum/mutation/human/on_losing(): (datum/mutation/human/lost_mutation) #define COMSIG_HUMAN_MUTATION_LOSS "human_mutation_loss" //from datum/mutation/human/on_losing(): (datum/mutation/human/lost_mutation)
///from base of mob/living/death(): (gibbed)
// Sent before any of the other death code has run, mob is still alive.
#define COMSIG_LIVING_PREDEATH "living_predeath"
/*******Component Specific Signals*******/ /*******Component Specific Signals*******/
//Janitor //Janitor
#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values. #define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values.
+1 -1
View File
@@ -159,7 +159,7 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define BLOOD_COLOR_SLIME "#00ff90" #define BLOOD_COLOR_SLIME "#00ff90"
#define BLOOD_COLOR_LIZARD "#db004D" #define BLOOD_COLOR_LIZARD "#db004D"
#define BLOOD_COLOR_UNIVERSAL "#db3300" #define BLOOD_COLOR_UNIVERSAL "#db3300"
#define BLOOD_COLOR_BUG "#a37c0f" #define BLOOD_COLOR_BUG "#ffc933"
#define BLOOD_COLOR_PLANT "#3d610e" #define BLOOD_COLOR_PLANT "#3d610e"
+1
View File
@@ -215,6 +215,7 @@
#define TRAIT_NO_STAMINA_BUFFER_REGENERATION "block_stamina_buffer_regen" /// Prevents stamina buffer regeneration #define TRAIT_NO_STAMINA_BUFFER_REGENERATION "block_stamina_buffer_regen" /// Prevents stamina buffer regeneration
#define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen" /// Prevents stamina regeneration #define TRAIT_NO_STAMINA_REGENERATION "block_stamina_regen" /// Prevents stamina regeneration
#define TRAIT_ARMOR_BROKEN "armor_broken" //acts as if you are wearing no clothing when taking damage, does not affect non-clothing sources of protection #define TRAIT_ARMOR_BROKEN "armor_broken" //acts as if you are wearing no clothing when taking damage, does not affect non-clothing sources of protection
#define TRAIT_IWASBATONED "iwasbatoned" //some dastardly fellow has struck you with a baton and thought to use another to strike you again, the rogue
/// forces update_density to make us not dense /// forces update_density to make us not dense
#define TRAIT_LIVING_NO_DENSITY "living_no_density" #define TRAIT_LIVING_NO_DENSITY "living_no_density"
/// forces us to not render our overlays /// forces us to not render our overlays
+3
View File
@@ -136,3 +136,6 @@
// paintings // paintings
#define VV_HK_REMOVE_PAINTING "remove_painting" #define VV_HK_REMOVE_PAINTING "remove_painting"
//outfits
#define VV_HK_TO_OUTFIT_EDITOR "outfit_editor"
+1 -1
View File
@@ -15,7 +15,7 @@
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } #define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I; #define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I; #define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
#define LAZYFIND(L, V) L ? L.Find(V) : 0 #define LAZYFIND(L, V) (L ? L.Find(V) : 0)
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) #define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; #define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L) #define LAZYLEN(L) length(L)
+3 -4
View File
@@ -1051,7 +1051,7 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
return 0 return 0
//For creating consistent icons for human looking simple animals //For creating consistent icons for human looking simple animals
/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null) /proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals, outfit_override = null, no_anim = FALSE)
var/static/list/humanoid_icon_cache = list() var/static/list/humanoid_icon_cache = list()
if(!icon_id || !humanoid_icon_cache[icon_id]) if(!icon_id || !humanoid_icon_cache[icon_id])
var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key) var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key)
@@ -1065,10 +1065,9 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
COMPILE_OVERLAYS(body)
for(var/D in showDirs) for(var/D in showDirs)
body.setDir(D) var/icon/partial = getFlatIcon(body, defdir = D, no_anim = no_anim)
COMPILE_OVERLAYS(body)
var/icon/partial = getFlatIcon(body)
out_icon.Insert(partial,dir=D) out_icon.Insert(partial,dir=D)
humanoid_icon_cache[icon_id] = out_icon humanoid_icon_cache[icon_id] = out_icon
+23 -1
View File
@@ -11,6 +11,23 @@ GLOBAL_LIST_INIT(bitfields, list(
"TILE_BOUND" = TILE_BOUND, "TILE_BOUND" = TILE_BOUND,
"PIXEL_SCALE" = PIXEL_SCALE "PIXEL_SCALE" = PIXEL_SCALE
), ),
"area_flags" = list(
"ABDUCTOR_PROOF" = ABDUCTOR_PROOF,
"BLOBS_ALLOWED" = BLOBS_ALLOWED,
"BLOCK_SUICIDE" = BLOCK_SUICIDE,
// "CULT_PERMITTED" = CULT_PERMITTED,
"FLORA_ALLOWED" = FLORA_ALLOWED,
"HIDDEN_AREA" = HIDDEN_AREA,
"MEGAFAUNA_SPAWN_ALLOWED" = MEGAFAUNA_SPAWN_ALLOWED,
"MOB_SPAWN_ALLOWED" = MOB_SPAWN_ALLOWED,
"NO_ALERTS" = NO_ALERTS,
"NOTELEPORT" = NOTELEPORT,
"CAVES_ALLOWED" = CAVES_ALLOWED,
"UNIQUE_AREA" = UNIQUE_AREA,
"VALID_TERRITORY" = VALID_TERRITORY,
"XENOBIOLOGY_COMPATIBLE" = XENOBIOLOGY_COMPATIBLE,
"NO_ALERTS" = NO_ALERTS,
) ,
"sight" = list( "sight" = list(
"SEE_INFRA" = SEE_INFRA, "SEE_INFRA" = SEE_INFRA,
"SEE_SELF" = SEE_SELF, "SEE_SELF" = SEE_SELF,
@@ -32,7 +49,12 @@ GLOBAL_LIST_INIT(bitfields, list(
"UNIQUE_RENAME" = UNIQUE_RENAME, "UNIQUE_RENAME" = UNIQUE_RENAME,
"USES_TGUI" = USES_TGUI, "USES_TGUI" = USES_TGUI,
"FROZEN" = FROZEN, "FROZEN" = FROZEN,
"SHOVABLE_ONTO" = SHOVABLE_ONTO "SHOVABLE_ONTO" = SHOVABLE_ONTO,
"BLOCK_Z_OUT_DOWN" = BLOCK_Z_OUT_DOWN,
"BLOCK_Z_OUT_UP" = BLOCK_Z_OUT_UP,
"BLOCK_Z_IN_DOWN" = BLOCK_Z_IN_DOWN,
"BLOCK_Z_IN_UP" = BLOCK_Z_IN_UP,
"EXAMINE_SKIP" = EXAMINE_SKIP
), ),
"datum_flags" = list( "datum_flags" = list(
"DF_USE_TAG" = DF_USE_TAG, "DF_USE_TAG" = DF_USE_TAG,
+1 -1
View File
@@ -123,7 +123,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_HIGH_BLOOD" = TRAIT_HIGH_BLOOD, "TRAIT_HIGH_BLOOD" = TRAIT_HIGH_BLOOD,
"TRAIT_EMPATH" = TRAIT_EMPATH, "TRAIT_EMPATH" = TRAIT_EMPATH,
"TRAIT_FRIENDLY" = TRAIT_FRIENDLY, "TRAIT_FRIENDLY" = TRAIT_FRIENDLY,
"TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT "TRAIT_IWASBATONED" = TRAIT_IWASBATONED
), ),
/obj/item/bodypart = list( /obj/item/bodypart = list(
"TRAIT_PARALYSIS" = TRAIT_PARALYSIS "TRAIT_PARALYSIS" = TRAIT_PARALYSIS
+47 -9
View File
@@ -85,21 +85,16 @@
makeItemInactive() makeItemInactive()
/obj/screen/storage/volumetric_box/proc/makeItemInactive() /obj/screen/storage/volumetric_box/proc/makeItemInactive()
if(!our_item) return
return
our_item.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
our_item.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
/obj/screen/storage/volumetric_box/proc/makeItemActive() /obj/screen/storage/volumetric_box/proc/makeItemActive()
if(!our_item) return
return
our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
/obj/screen/storage/volumetric_box/center /obj/screen/storage/volumetric_box/center
icon_state = "stored_continue" icon_state = "stored_continue"
var/obj/screen/storage/volumetric_edge/stored_left/left var/obj/screen/storage/volumetric_edge/stored_left/left
var/obj/screen/storage/volumetric_edge/stored_right/right var/obj/screen/storage/volumetric_edge/stored_right/right
var/obj/screen/storage/item_holder/holder
var/pixel_size var/pixel_size
/obj/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item) /obj/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item)
@@ -110,6 +105,9 @@
/obj/screen/storage/volumetric_box/center/Destroy() /obj/screen/storage/volumetric_box/center/Destroy()
QDEL_NULL(left) QDEL_NULL(left)
QDEL_NULL(right) QDEL_NULL(right)
vis_contents.Cut()
if(holder)
QDEL_NULL(holder)
return ..() return ..()
/obj/screen/storage/volumetric_box/center/proc/on_screen_objects() /obj/screen/storage/volumetric_box/center/proc/on_screen_objects()
@@ -123,13 +121,36 @@
return return
pixel_size = pixels pixel_size = pixels
cut_overlays() cut_overlays()
vis_contents.Cut()
//our icon size is 32 pixels. //our icon size is 32 pixels.
transform = matrix((pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE, 0, 0, 0, 1, 0) var/multiplier = (pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE
transform = matrix(multiplier, 0, 0, 0, 1, 0)
if(our_item)
if(holder)
qdel(holder)
holder = new(null, src, our_item)
holder.transform = matrix(1 / multiplier, 0, 0, 0, 1, 0)
holder.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
holder.appearance_flags &= ~RESET_TRANSFORM
makeItemInactive()
vis_contents += holder
left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
add_overlay(left) add_overlay(left)
add_overlay(right) add_overlay(right)
/obj/screen/storage/volumetric_box/center/makeItemInactive()
if(!holder)
return
holder.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
holder.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
/obj/screen/storage/volumetric_box/center/makeItemActive()
if(!holder)
return
holder.our_item.layer = VOLUMETRIC_STORAGE_ACTIVE_ITEM_LAYER //make sure we display infront of the others!
holder.our_item.plane = VOLUMETRIC_STORAGE_ACTIVE_ITEM_PLANE
/obj/screen/storage/volumetric_edge /obj/screen/storage/volumetric_edge
layer = VOLUMETRIC_STORAGE_BOX_LAYER layer = VOLUMETRIC_STORAGE_BOX_LAYER
plane = VOLUMETRIC_STORAGE_BOX_PLANE plane = VOLUMETRIC_STORAGE_BOX_PLANE
@@ -157,3 +178,20 @@
/obj/screen/storage/volumetric_edge/stored_right /obj/screen/storage/volumetric_edge/stored_right
icon_state = "stored_end" icon_state = "stored_end"
appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM
/obj/screen/storage/item_holder
var/obj/item/our_item
vis_flags = NONE
/obj/screen/storage/item_holder/Initialize(mapload, new_master, obj/item/I)
. = ..()
our_item = I
vis_contents += I
/obj/screen/storage/item_holder/Destroy()
vis_contents.Cut()
our_item = null
return ..()
/obj/screen/storage/item_holder/Click(location, control, params)
return our_item.Click(location, control, params)
+1 -2
View File
@@ -94,8 +94,7 @@
else if(hitsound) else if(hitsound)
playsound(loc, hitsound, get_clamped_volume(), 1, -1) playsound(loc, hitsound, get_clamped_volume(), 1, -1)
M.lastattacker = user.real_name M.set_last_attacker(user)
M.lastattackerckey = user.ckey
if(force && M == user && user.client) if(force && M == user && user.client)
user.client.give_award(/datum/award/achievement/misc/selfouch, user) user.client.give_award(/datum/award/achievement/misc/selfouch, user)
+63
View File
@@ -0,0 +1,63 @@
/datum/component/activity
var/activity_level = 0
var/not_moved_counter = 0
var/list/historical_activity_levels = list()
/datum/component/activity/Initialize()
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
var/mob/living/L = parent
RegisterSignal(L, COMSIG_LIVING_SET_AS_ATTACKER, .proc/on_set_as_attacker)
RegisterSignal(L, COMSIG_LIVING_ATTACKER_SET, .proc/on_attacker_set)
RegisterSignal(L, COMSIG_MOB_DEATH, .proc/on_death)
RegisterSignal(L, COMSIG_EXIT_AREA, .proc/on_exit_area)
RegisterSignal(L, COMSIG_LIVING_LIFE, .proc/on_life)
RegisterSignal(L, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE, COMSIG_MOB_APPLY_DAMAGE), .proc/minor_activity)
/datum/component/activity/proc/log_activity()
historical_activity_levels[world.time] = activity_level
/datum/component/activity/proc/minor_activity(datum/source)
activity_level += 1
/datum/component/activity/proc/on_attacker_set(datum/source, mob/attacker)
activity_level += 10
if(attacker?.mind)
activity_level += 10
log_activity()
/datum/component/activity/proc/on_set_as_attacker(datum/source, mob/target)
activity_level += 10
if(target?.mind)
activity_level += 20
log_activity()
/datum/component/activity/proc/on_death(datum/source)
activity_level += 100 // dying means you're doing SOMETHING
log_activity()
/datum/component/activity/proc/on_exit_area(datum/source)
activity_level += 1
not_moved_counter = 0
/datum/component/activity/proc/on_life(datum/source, seconds, times_fired)
var/mob/living/L = source
if(L.stat >= UNCONSCIOUS) // can't expect the unconscious to move
return
not_moved_counter += seconds
var/should_log = FALSE
switch(not_moved_counter)
if(60 to 120)
activity_level -= 1
if(120 to 600)
activity_level -= 5
if(600 to 1200)
activity_level -= 10
should_log = TRUE
if(1200 to INFINITY)
activity_level -= 20
should_log = TRUE
activity_level = max(activity_level, 0)
if(should_log)
log_activity()
+2 -2
View File
@@ -56,10 +56,10 @@
detonate() detonate()
/datum/component/explodable/proc/on_equip(datum/source, mob/equipper, slot) /datum/component/explodable/proc/on_equip(datum/source, mob/equipper, slot)
RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMGE, .proc/explodable_attack_zone, TRUE) RegisterSignal(equipper, COMSIG_MOB_APPLY_DAMAGE, .proc/explodable_attack_zone, TRUE)
/datum/component/explodable/proc/on_drop(datum/source, mob/user) /datum/component/explodable/proc/on_drop(datum/source, mob/user)
UnregisterSignal(user, COMSIG_MOB_APPLY_DAMGE) UnregisterSignal(user, COMSIG_MOB_APPLY_DAMAGE)
/// Checks if we're hitting the zone this component is covering /// Checks if we're hitting the zone this component is covering
/datum/component/explodable/proc/is_hitting_zone(def_zone) /datum/component/explodable/proc/is_hitting_zone(def_zone)
+1 -1
View File
@@ -52,7 +52,7 @@
return return
strength -= strength / hl3_release_date strength -= strength / hl3_release_date
if(strength <= RAD_BACKGROUND_RADIATION) if(strength <= RAD_BACKGROUND_RADIATION)
return PROCESS_KILL qdel(src)
/datum/component/radioactive/proc/glow_loop(atom/movable/master) /datum/component/radioactive/proc/glow_loop(atom/movable/master)
+12 -31
View File
@@ -47,18 +47,8 @@
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
/// "legacy"/default view mode's storage "boxes" /// Ui objects by person. mob = list(objects)
var/obj/screen/storage/boxes/ui_boxes var/list/ui_by_mob = list()
/// New volumetric storage display mode's left side
var/obj/screen/storage/left/ui_left
/// New volumetric storage display mode's center 'blocks'
var/obj/screen/storage/continuous/ui_continuous
/// The close button, used in all modes. Frames right side in volumetric mode.
var/obj/screen/storage/close/ui_close
/// Associative list of list(item = screen object) for volumetric storage item screen blocks
var/list/ui_item_blocks
var/current_maxscreensize
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
@@ -125,18 +115,16 @@
/datum/component/storage/Destroy() /datum/component/storage/Destroy()
close_all() close_all()
QDEL_NULL(ui_boxes) wipe_ui_objects()
QDEL_NULL(ui_close)
QDEL_NULL(ui_continuous)
QDEL_NULL(ui_left)
// DO NOT USE QDEL_LIST_ASSOC.
if(ui_item_blocks)
for(var/i in ui_item_blocks)
qdel(ui_item_blocks[i]) //qdel the screen object not the item
ui_item_blocks.Cut()
LAZYCLEARLIST(is_using) LAZYCLEARLIST(is_using)
return ..() return ..()
/datum/component/storage/proc/wipe_ui_objects()
for(var/i in ui_by_mob)
var/list/objects = ui_by_mob[i]
QDEL_LIST(objects)
ui_by_mob.Cut()
/datum/component/storage/PreTransfer() /datum/component/storage/PreTransfer()
update_actions() update_actions()
@@ -351,13 +339,6 @@
return master._removal_reset(thing) return master._removal_reset(thing)
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) /datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
if(LAZYACCESS(ui_item_blocks, thing))
var/obj/screen/storage/volumetric_box/center/C = ui_item_blocks[thing]
for(var/i in can_see_contents()) //runtimes result if mobs can access post deletion.
var/mob/M = i
M.client?.screen -= C.on_screen_objects()
ui_item_blocks -= thing
qdel(C)
_removal_reset(thing) // THIS NEEDS TO HAPPEN AFTER SO LAYERING DOESN'T BREAK! _removal_reset(thing) // THIS NEEDS TO HAPPEN AFTER SO LAYERING DOESN'T BREAK!
refresh_mob_views() refresh_mob_views()
@@ -467,14 +448,14 @@
return return
A.add_fingerprint(M) A.add_fingerprint(M)
/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE, ghost = FALSE) /datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE)
var/atom/A = parent var/atom/A = parent
if(!istype(M)) if(!istype(M))
return FALSE return FALSE
A.add_fingerprint(M) A.add_fingerprint(M)
if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE))) if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE)))
return FALSE return FALSE
ui_show(M, !ghost) ui_show(M)
/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) /datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
if(isitem(O)) if(isitem(O))
@@ -596,7 +577,7 @@
return can_be_inserted(I, silent, M) return can_be_inserted(I, silent, M)
/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) /datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M)
return user_show_to_mob(M, TRUE, TRUE) return user_show_to_mob(M, TRUE)
/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) /datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE)
return user_show_to_mob(showto, force) return user_show_to_mob(showto, force)
+32 -53
View File
@@ -7,7 +7,7 @@
if(QDELETED(I)) if(QDELETED(I))
continue continue
if(!.[I.type]) if(!.[I.type])
.[I.type] = new /datum/numbered_display(I, 1) .[I.type] = new /datum/numbered_display(I, 1, src)
else else
var/datum/numbered_display/ND = .[I.type] var/datum/numbered_display/ND = .[I.type]
ND.number++ ND.number++
@@ -20,6 +20,8 @@
. = list() . = list()
var/list/accessible_contents = accessible_items() var/list/accessible_contents = accessible_items()
var/adjusted_contents = length(accessible_contents) var/adjusted_contents = length(accessible_contents)
var/obj/screen/storage/close/ui_close
var/obj/screen/storage/boxes/ui_boxes
//Numbered contents display //Numbered contents display
var/list/datum/numbered_display/numbered_contents var/list/datum/numbered_display/numbered_contents
@@ -60,12 +62,13 @@
for(var/obj/O in accessible_items()) for(var/obj/O in accessible_items())
if(QDELETED(O)) if(QDELETED(O))
continue continue
O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" var/obj/screen/storage/item_holder/D = new(null, src, O)
O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" D.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
D.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
O.maptext = "" O.maptext = ""
O.layer = ABOVE_HUD_LAYER O.layer = ABOVE_HUD_LAYER
O.plane = ABOVE_HUD_PLANE O.plane = ABOVE_HUD_PLANE
. += O . += D
cx++ cx++
if(cx - screen_start_x >= columns) if(cx - screen_start_x >= columns)
cx = screen_start_x cx = screen_start_x
@@ -78,6 +81,9 @@
*/ */
/datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns) /datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns)
. = list() . = list()
var/obj/screen/storage/left/ui_left
var/obj/screen/storage/continuous/ui_continuous
var/obj/screen/storage/close/ui_close
// Generate ui_item_blocks for missing ones and render+orient. // Generate ui_item_blocks for missing ones and render+orient.
var/list/atom/contents = accessible_items() var/list/atom/contents = accessible_items()
@@ -128,14 +134,10 @@
var/first = TRUE var/first = TRUE
var/row = 1 var/row = 1
LAZYINITLIST(ui_item_blocks)
for(var/i in percentage_by_item) for(var/i in percentage_by_item)
I = i I = i
var/percent = percentage_by_item[I] var/percent = percentage_by_item[I]
if(!ui_item_blocks[I]) var/obj/screen/storage/volumetric_box/center/B = new /obj/screen/storage/volumetric_box/center(null, src, I)
ui_item_blocks[I] = new /obj/screen/storage/volumetric_box/center(null, src, I)
var/obj/screen/storage/volumetric_box/center/B = ui_item_blocks[I]
var/pixels_to_use = overrun? MINIMUM_PIXELS_PER_ITEM : max(using_horizontal_pixels * percent, MINIMUM_PIXELS_PER_ITEM) var/pixels_to_use = overrun? MINIMUM_PIXELS_PER_ITEM : max(using_horizontal_pixels * percent, MINIMUM_PIXELS_PER_ITEM)
var/addrow = FALSE var/addrow = FALSE
if(CEILING(pixels_to_use, 1) >= FLOOR(horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING, 1)) if(CEILING(pixels_to_use, 1) >= FLOOR(horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING, 1))
@@ -143,25 +145,17 @@
addrow = TRUE addrow = TRUE
// now that we have pixels_to_use, place our thing and add it to the returned list. // now that we have pixels_to_use, place our thing and add it to the returned list.
B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]" B.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
// add the used pixels to pixel after we place the object // add the used pixels to pixel after we place the object
current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING) current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING)
first = FALSE //apply padding to everything after this first = FALSE //apply padding to everything after this
// set various things // set various things
B.set_pixel_size(pixels_to_use) B.set_pixel_size(pixels_to_use)
B.layer = VOLUMETRIC_STORAGE_BOX_LAYER
B.plane = VOLUMETRIC_STORAGE_BOX_PLANE
B.name = I.name B.name = I.name
I.mouse_opacity = MOUSE_OPACITY_ICON
I.maptext = ""
I.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
I.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
// finally add our things. // finally add our things.
. += B.on_screen_objects() . += B.on_screen_objects()
. += I
// go up a row if needed // go up a row if needed
if(addrow) if(addrow)
@@ -185,18 +179,19 @@
/** /**
* Shows our UI to a mob. * Shows our UI to a mob.
*/ */
/datum/component/storage/proc/ui_show(mob/M, set_screen_size = TRUE) /datum/component/storage/proc/ui_show(mob/M)
if(!M.client) if(!M.client)
return FALSE return FALSE
if(ui_by_mob[M] || LAZYFIND(is_using, M))
// something went horribly wrong
// hide first
ui_hide(M)
var/list/cview = getviewsize(M.client.view) var/list/cview = getviewsize(M.client.view)
// in tiles // in tiles
var/maxallowedscreensize = cview[1]-8 var/maxallowedscreensize = cview[1]-8
if(set_screen_size)
current_maxscreensize = maxallowedscreensize
else if(current_maxscreensize)
maxallowedscreensize = current_maxscreensize
// we got screen size, register signal // we got screen size, register signal
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_logout, override = TRUE) RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_logout, override = TRUE)
RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/on_logout, override = TRUE)
if(M.active_storage != src) if(M.active_storage != src)
if(M.active_storage) if(M.active_storage)
M.active_storage.ui_hide(M) M.active_storage.ui_hide(M)
@@ -204,10 +199,14 @@
LAZYOR(is_using, M) LAZYOR(is_using, M)
if(!M.client?.prefs?.no_tetris_storage && volumetric_ui()) if(!M.client?.prefs?.no_tetris_storage && volumetric_ui())
//new volumetric ui bay-style //new volumetric ui bay-style
M.client.screen |= orient2hud_volumetric(M, maxallowedscreensize) var/list/objects = orient2hud_volumetric(M, maxallowedscreensize)
M.client.screen |= objects
ui_by_mob[M] = objects
else else
//old ui //old ui
M.client.screen |= orient2hud_legacy(M, maxallowedscreensize) var/list/objects = orient2hud_legacy(M, maxallowedscreensize)
M.client.screen |= objects
ui_by_mob[M] = objects
return TRUE return TRUE
/** /**
@@ -236,8 +235,10 @@
/datum/component/storage/proc/ui_hide(mob/M) /datum/component/storage/proc/ui_hide(mob/M)
if(!M.client) if(!M.client)
return TRUE return TRUE
UnregisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT) UnregisterSignal(M, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_CLIENT_LOGOUT))
M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide(M) M.client.screen -= ui_by_mob[M]
var/list/objects = ui_by_mob[M]
QDEL_LIST(objects)
if(M.active_storage == src) if(M.active_storage == src)
M.active_storage = null M.active_storage = null
LAZYREMOVE(is_using, M) LAZYREMOVE(is_using, M)
@@ -250,48 +251,26 @@
var/atom/real_location = real_location() var/atom/real_location = real_location()
return (storage_flags & STORAGE_LIMIT_VOLUME) && (length(real_location.contents) <= MAXIMUM_VOLUMETRIC_ITEMS) && !display_numerical_stacking return (storage_flags & STORAGE_LIMIT_VOLUME) && (length(real_location.contents) <= MAXIMUM_VOLUMETRIC_ITEMS) && !display_numerical_stacking
/**
* Gets the ui item objects to ui_hide.
*/
/datum/component/storage/proc/get_ui_item_objects_hide(mob/M)
if(!volumetric_ui() || M.client?.prefs?.no_tetris_storage)
var/atom/real_location = real_location()
return real_location.contents
else
. = list()
for(var/i in ui_item_blocks)
// get both the box and the item
. += ui_item_blocks[i]
. += i
/** /**
* Gets our ui_boxes, making it if it doesn't exist. * Gets our ui_boxes, making it if it doesn't exist.
*/ */
/datum/component/storage/proc/get_ui_boxes() /datum/component/storage/proc/get_ui_boxes()
if(!ui_boxes) return new /obj/screen/storage/boxes(null, src)
ui_boxes = new(null, src)
return ui_boxes
/** /**
* Gets our ui_left, making it if it doesn't exist. * Gets our ui_left, making it if it doesn't exist.
*/ */
/datum/component/storage/proc/get_ui_left() /datum/component/storage/proc/get_ui_left()
if(!ui_left) return new /obj/screen/storage/left(null, src)
ui_left = new(null, src)
return ui_left
/** /**
* Gets our ui_close, making it if it doesn't exist. * Gets our ui_close, making it if it doesn't exist.
*/ */
/datum/component/storage/proc/get_ui_close() /datum/component/storage/proc/get_ui_close()
if(!ui_close) return new /obj/screen/storage/close(null, src)
ui_close = new(null, src)
return ui_close
/** /**
* Gets our ui_continuous, making it if it doesn't exist. * Gets our ui_continuous, making it if it doesn't exist.
*/ */
/datum/component/storage/proc/get_ui_continuous() /datum/component/storage/proc/get_ui_continuous()
if(!ui_continuous) return new /obj/screen/storage/continuous(null, src)
ui_continuous = new(null, src)
return ui_continuous
+3 -2
View File
@@ -329,12 +329,13 @@
uni_identity = generate_uni_identity() uni_identity = generate_uni_identity()
unique_enzymes = generate_unique_enzymes() unique_enzymes = generate_unique_enzymes()
/datum/dna/proc/initialize_dna(newblood_type) /datum/dna/proc/initialize_dna(newblood_type, skip_index = FALSE)
if(newblood_type) if(newblood_type)
blood_type = newblood_type blood_type = newblood_type
unique_enzymes = generate_unique_enzymes() unique_enzymes = generate_unique_enzymes()
uni_identity = generate_uni_identity() uni_identity = generate_uni_identity()
generate_dna_blocks() if(!skip_index) //I hate this
generate_dna_blocks()
features = random_features(species?.id, holder?.gender) features = random_features(species?.id, holder?.gender)
+2 -2
View File
@@ -34,13 +34,13 @@
. = ..() . = ..()
UnregisterSignal(source, COMSIG_MOB_ATTACK_HAND) UnregisterSignal(source, COMSIG_MOB_ATTACK_HAND)
/datum/element/wuv/proc/on_attack_hand(datum/source, mob/user) /datum/element/wuv/proc/on_attack_hand(datum/source, mob/user, act_intent)
var/mob/living/L = source var/mob/living/L = source
if(L.stat == DEAD) if(L.stat == DEAD)
return return
//we want to delay the effect to be displayed after the mob is petted, not before. //we want to delay the effect to be displayed after the mob is petted, not before.
switch(user.a_intent) switch(act_intent)
if(INTENT_HARM) if(INTENT_HARM)
addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1) addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
if(INTENT_HELP) if(INTENT_HELP)
+2 -2
View File
@@ -3,8 +3,8 @@
var/obj/item/sample_object var/obj/item/sample_object
var/number var/number
/datum/numbered_display/New(obj/item/sample, _number = 1) /datum/numbered_display/New(obj/item/sample, _number = 1, datum/component/storage/parent)
if(!istype(sample)) if(!istype(sample))
qdel(src) qdel(src)
sample_object = sample sample_object = new /obj/screen/storage/item_holder(null, parent, sample)
number = _number number = _number
+294 -57
View File
@@ -1,71 +1,185 @@
/**
* # Outfit datums
*
* This is a clean system of applying outfits to mobs, if you need to equip someone in a uniform
* this is the way to do it cleanly and properly.
*
* You can also specify an outfit datum on a job to have it auto equipped to the mob on join
*
* /mob/living/carbon/human/proc/equipOutfit(outfit) is the mob level proc to equip an outfit
* and you pass it the relevant datum outfit
*
* outfits can also be saved as json blobs downloadable by a client and then can be uploaded
* by that user to recreate the outfit, this is used by admins to allow for custom event outfits
* that can be restored at a later date
*/
/datum/outfit /datum/outfit
///Name of the outfit (shows up in the equip admin verb)
var/name = "Naked" var/name = "Naked"
var/uniform = null /// Type path of item to go in the idcard slot
var/suit = null
var/toggle_helmet = TRUE
var/back = null
var/belt = null
var/gloves = null
var/shoes = null
var/head = null
var/mask = null
var/neck = null
var/ears = null
var/glasses = null
var/id = null var/id = null
var/l_pocket = null
var/r_pocket = null /// Type path of item to go in uniform slot
var/uniform = null
/// Type path of item to go in suit slot
var/suit = null
/**
* Type path of item to go in suit storage slot
*
* (make sure it's valid for that suit)
*/
var/suit_store = null var/suit_store = null
var/r_hand = null
/// Type path of item to go in back slot
var/back = null
/**
* list of items that should go in the backpack of the user
*
* Format of this list should be: list(path=count,otherpath=count)
*/
var/list/backpack_contents = null
/// Type path of item to go in belt slot
var/belt = null
/// Type path of item to go in ears slot
var/ears = null
/// Type path of item to go in the glasses slot
var/glasses = null
/// Type path of item to go in gloves slot
var/gloves = null
/// Type path of item to go in head slot
var/head = null
/// Type path of item to go in mask slot
var/mask = null
/// Type path of item to go in neck slot
var/neck = null
/// Type path of item to go in shoes slot
var/shoes = null
/// Type path of item for left pocket slot
var/l_pocket = null
/// Type path of item for right pocket slot
var/r_pocket = null
///Type path of item to go in the right hand
var/l_hand = null var/l_hand = null
var/internals_slot = null //ID of slot containing a gas tank
var/list/backpack_contents = null // In the list(path=count,otherpath=count) format //Type path of item to go in left hand
var/box // Internals box. Will be inserted at the start of backpack_contents var/r_hand = null
var/list/implants = null
/// Any clothing accessory item
var/accessory = null var/accessory = null
var/can_be_admin_equipped = TRUE // Set to FALSE if your outfit requires runtime parameters /// Internals box. Will be inserted at the start of backpack_contents
var/list/chameleon_extras //extra types for chameleon outfit changes, mostly guns var/box
/**
* extra types for chameleon outfit changes, mostly guns
*
* Format of this list is (typepath, typepath, typepath)
*
* These are all added and returns in the list for get_chamelon_diguise_info proc
*/
var/list/chameleon_extras
/**
* Any implants the mob should start implanted with
*
* Format of this list is (typepath, typepath, typepath)
*/
var/list/implants = null
///ID of the slot containing a gas tank
var/internals_slot = null
/// Should the toggle helmet proc be called on the helmet during equip
var/toggle_helmet = TRUE
/// Any undershirt. While on humans it is a string, here we use paths to stay consistent with the rest of the equips.
var/datum/sprite_accessory/undershirt = null
/**
* Called at the start of the equip proc
*
* Override to change the value of the slots depending on client prefs, species and
* other such sources of change
*
* Extra Arguments
* * visualsOnly true if this is only for display (in the character setup screen)
*
* If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
*/
/datum/outfit/proc/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) /datum/outfit/proc/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
//to be overridden for customization depending on client prefs,species etc //to be overridden for customization depending on client prefs,species etc
return return
/**
* Called after the equip proc has finished
*
* All items are on the mob at this point, use this proc to toggle internals
* fiddle with id bindings and accesses etc
*
* Extra Arguments
* * visualsOnly true if this is only for display (in the character setup screen)
*
* If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
*/
/datum/outfit/proc/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) /datum/outfit/proc/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
//to be overridden for toggling internals, id binding, access etc //to be overridden for toggling internals, id binding, access etc
return return
/**
* Equips all defined types and paths to the mob passed in
*
* Extra Arguments
* * visualsOnly true if this is only for display (in the character setup screen)
*
* If visualsOnly is true, you can omit any work that doesn't visually appear on the character sprite
*/
/datum/outfit/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) /datum/outfit/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source)
pre_equip(H, visualsOnly, preference_source) pre_equip(H, visualsOnly, preference_source)
//Start with uniform,suit,backpack for additional slots //Start with uniform,suit,backpack for additional slots
if(uniform) if(uniform)
H.equip_to_slot_or_del(new uniform(H),SLOT_W_UNIFORM) H.equip_to_slot_or_del(new uniform(H), SLOT_W_UNIFORM, TRUE)
if(suit) if(suit)
H.equip_to_slot_or_del(new suit(H),SLOT_WEAR_SUIT) H.equip_to_slot_or_del(new suit(H), SLOT_WEAR_SUIT, TRUE)
if(back) if(back)
H.equip_to_slot_or_del(new back(H),SLOT_BACK) H.equip_to_slot_or_del(new back(H), SLOT_BACK, TRUE)
if(belt) if(belt)
H.equip_to_slot_or_del(new belt(H),SLOT_BELT) H.equip_to_slot_or_del(new belt(H), SLOT_BELT, TRUE)
if(gloves) if(gloves)
H.equip_to_slot_or_del(new gloves(H),SLOT_GLOVES) H.equip_to_slot_or_del(new gloves(H), SLOT_GLOVES, TRUE)
if(shoes) if(shoes)
H.equip_to_slot_or_del(new shoes(H),SLOT_SHOES) H.equip_to_slot_or_del(new shoes(H), SLOT_SHOES, TRUE)
if(head) if(head)
H.equip_to_slot_or_del(new head(H),SLOT_HEAD) H.equip_to_slot_or_del(new head(H), SLOT_HEAD, TRUE)
if(mask) if(mask)
H.equip_to_slot_or_del(new mask(H),SLOT_WEAR_MASK) H.equip_to_slot_or_del(new mask(H), SLOT_WEAR_MASK, TRUE)
if(neck) if(neck)
H.equip_to_slot_or_del(new neck(H),SLOT_NECK) H.equip_to_slot_or_del(new neck(H), SLOT_NECK, TRUE)
if(ears) if(ears)
H.equip_to_slot_or_del(new ears(H),SLOT_EARS) H.equip_to_slot_or_del(new ears(H), SLOT_EARS, TRUE)
if(glasses) if(glasses)
H.equip_to_slot_or_del(new glasses(H),SLOT_GLASSES) H.equip_to_slot_or_del(new glasses(H), SLOT_GLASSES, TRUE)
if(id) if(id)
H.equip_to_slot_or_del(new id(H),SLOT_WEAR_ID) H.equip_to_slot_or_del(new id(H), SLOT_WEAR_ID, TRUE)
if(suit_store) if(suit_store)
H.equip_to_slot_or_del(new suit_store(H),SLOT_S_STORE) H.equip_to_slot_or_del(new suit_store(H), SLOT_S_STORE, TRUE)
if(undershirt)
H.undershirt = initial(undershirt.name)
if(accessory) if(accessory)
var/obj/item/clothing/under/U = H.w_uniform var/obj/item/clothing/under/U = H.w_uniform
@@ -81,9 +195,9 @@
if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon. if(!visualsOnly) // Items in pockets or backpack don't show up on mob's icon.
if(l_pocket) if(l_pocket)
H.equip_to_slot_or_del(new l_pocket(H),SLOT_L_STORE) H.equip_to_slot_or_del(new l_pocket(H), SLOT_L_STORE, TRUE)
if(r_pocket) if(r_pocket)
H.equip_to_slot_or_del(new r_pocket(H),SLOT_R_STORE) H.equip_to_slot_or_del(new r_pocket(H), SLOT_R_STORE, TRUE)
if(box) if(box)
if(!backpack_contents) if(!backpack_contents)
@@ -97,7 +211,7 @@
if(!isnum(number))//Default to 1 if(!isnum(number))//Default to 1
number = 1 number = 1
for(var/i in 1 to number) for(var/i in 1 to number)
H.equip_to_slot_or_del(new path(H),SLOT_IN_BACKPACK) H.equip_to_slot_or_del(new path(H), SLOT_IN_BACKPACK, TRUE)
if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit)) if(!H.head && toggle_helmet && istype(H.wear_suit, /obj/item/clothing/suit/space/hardsuit))
var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
@@ -112,55 +226,178 @@
H.update_action_buttons_icon() H.update_action_buttons_icon()
if(implants) if(implants)
for(var/implant_type in implants) for(var/implant_type in implants)
var/obj/item/implant/I = new implant_type var/obj/item/implant/I = new implant_type(H)
I.implant(H, null, TRUE) I.implant(H, null, TRUE)
H.update_body() H.update_body()
return TRUE return TRUE
/**
* Apply a fingerprint from the passed in human to all items in the outfit
*
* Used for forensics setup when the mob is first equipped at roundstart
* essentially calls add_fingerprint to every defined item on the human
*
*/
/datum/outfit/proc/apply_fingerprints(mob/living/carbon/human/H) /datum/outfit/proc/apply_fingerprints(mob/living/carbon/human/H)
if(!istype(H)) if(!istype(H))
return return
if(H.back) if(H.back)
H.back.add_fingerprint(H,1) //The 1 sets a flag to ignore gloves H.back.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.back.contents) for(var/obj/item/I in H.back.contents)
I.add_fingerprint(H,1) I.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_id) if(H.wear_id)
H.wear_id.add_fingerprint(H,1) H.wear_id.add_fingerprint(H, ignoregloves = TRUE)
if(H.w_uniform) if(H.w_uniform)
H.w_uniform.add_fingerprint(H,1) H.w_uniform.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_suit) if(H.wear_suit)
H.wear_suit.add_fingerprint(H,1) H.wear_suit.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_mask) if(H.wear_mask)
H.wear_mask.add_fingerprint(H,1) H.wear_mask.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_neck) if(H.wear_neck)
H.wear_neck.add_fingerprint(H,1) H.wear_neck.add_fingerprint(H, ignoregloves = TRUE)
if(H.head) if(H.head)
H.head.add_fingerprint(H,1) H.head.add_fingerprint(H, ignoregloves = TRUE)
if(H.shoes) if(H.shoes)
H.shoes.add_fingerprint(H,1) H.shoes.add_fingerprint(H, ignoregloves = TRUE)
if(H.gloves) if(H.gloves)
H.gloves.add_fingerprint(H,1) H.gloves.add_fingerprint(H, ignoregloves = TRUE)
if(H.ears) if(H.ears)
H.ears.add_fingerprint(H,1) H.ears.add_fingerprint(H, ignoregloves = TRUE)
if(H.glasses) if(H.glasses)
H.glasses.add_fingerprint(H,1) H.glasses.add_fingerprint(H, ignoregloves = TRUE)
if(H.belt) if(H.belt)
H.belt.add_fingerprint(H,1) H.belt.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.belt.contents) for(var/obj/item/I in H.belt.contents)
I.add_fingerprint(H,1) I.add_fingerprint(H, ignoregloves = TRUE)
if(H.s_store) if(H.s_store)
H.s_store.add_fingerprint(H,1) H.s_store.add_fingerprint(H, ignoregloves = TRUE)
if(H.l_store) if(H.l_store)
H.l_store.add_fingerprint(H,1) H.l_store.add_fingerprint(H, ignoregloves = TRUE)
if(H.r_store) if(H.r_store)
H.r_store.add_fingerprint(H,1) H.r_store.add_fingerprint(H, ignoregloves = TRUE)
for(var/obj/item/I in H.held_items) for(var/obj/item/I in H.held_items)
I.add_fingerprint(H,1) I.add_fingerprint(H, ignoregloves = TRUE)
return 1 return TRUE
/// Return a list of all the types that are required to disguise as this outfit type
/datum/outfit/proc/get_chameleon_disguise_info() /datum/outfit/proc/get_chameleon_disguise_info()
var/list/types = list(uniform, suit, back, belt, gloves, shoes, head, mask, neck, ears, glasses, id, l_pocket, r_pocket, suit_store, r_hand, l_hand) var/list/types = list(uniform, suit, back, belt, gloves, shoes, head, mask, neck, ears, glasses, id, l_pocket, r_pocket, suit_store, r_hand, l_hand)
types += chameleon_extras types += chameleon_extras
listclearnulls(types) listclearnulls(types)
return types return types
/// Return a json list of this outfit
/datum/outfit/proc/get_json_data()
. = list()
.["outfit_type"] = type
.["name"] = name
.["uniform"] = uniform
.["suit"] = suit
.["toggle_helmet"] = toggle_helmet
.["back"] = back
.["belt"] = belt
.["gloves"] = gloves
.["shoes"] = shoes
.["head"] = head
.["mask"] = mask
.["neck"] = neck
.["ears"] = ears
.["glasses"] = glasses
.["id"] = id
.["l_pocket"] = l_pocket
.["r_pocket"] = r_pocket
.["suit_store"] = suit_store
.["r_hand"] = r_hand
.["l_hand"] = l_hand
.["internals_slot"] = internals_slot
.["backpack_contents"] = backpack_contents
.["box"] = box
.["implants"] = implants
.["accessory"] = accessory
/// Copy most vars from another outfit to this one
/datum/outfit/proc/copy_from(datum/outfit/target)
name = target.name
uniform = target.uniform
suit = target.suit
toggle_helmet = target.toggle_helmet
back = target.back
belt = target.belt
gloves = target.gloves
shoes = target.shoes
head = target.head
mask = target.mask
neck = target.neck
ears = target.ears
glasses = target.glasses
id = target.id
l_pocket = target.l_pocket
r_pocket = target.r_pocket
suit_store = target.suit_store
r_hand = target.r_hand
l_hand = target.l_hand
internals_slot = target.internals_slot
backpack_contents = target.backpack_contents
box = target.box
implants = target.implants
accessory = target.accessory
/// Prompt the passed in mob client to download this outfit as a json blob
/datum/outfit/proc/save_to_file(mob/admin)
var/stored_data = get_json_data()
var/json = json_encode(stored_data)
//Kinda annoying but as far as i can tell you need to make actual file.
var/f = file("data/TempOutfitUpload")
fdel(f)
WRITE_FILE(f,json)
admin << ftp(f,"[name].json")
/// Create an outfit datum from a list of json data
/datum/outfit/proc/load_from(list/outfit_data)
//This could probably use more strict validation
name = outfit_data["name"]
uniform = text2path(outfit_data["uniform"])
suit = text2path(outfit_data["suit"])
toggle_helmet = outfit_data["toggle_helmet"]
back = text2path(outfit_data["back"])
belt = text2path(outfit_data["belt"])
gloves = text2path(outfit_data["gloves"])
shoes = text2path(outfit_data["shoes"])
head = text2path(outfit_data["head"])
mask = text2path(outfit_data["mask"])
neck = text2path(outfit_data["neck"])
ears = text2path(outfit_data["ears"])
glasses = text2path(outfit_data["glasses"])
id = text2path(outfit_data["id"])
l_pocket = text2path(outfit_data["l_pocket"])
r_pocket = text2path(outfit_data["r_pocket"])
suit_store = text2path(outfit_data["suit_store"])
r_hand = text2path(outfit_data["r_hand"])
l_hand = text2path(outfit_data["l_hand"])
internals_slot = outfit_data["internals_slot"]
var/list/backpack = outfit_data["backpack_contents"]
backpack_contents = list()
for(var/item in backpack)
var/itype = text2path(item)
if(itype)
backpack_contents[itype] = backpack[item]
box = text2path(outfit_data["box"])
var/list/impl = outfit_data["implants"]
implants = list()
for(var/I in impl)
var/imptype = text2path(I)
if(imptype)
implants += imptype
accessory = text2path(outfit_data["accessory"])
return TRUE
/datum/outfit/vv_get_dropdown()
. = ..()
VV_DROPDOWN_OPTION("", "---")
VV_DROPDOWN_OPTION(VV_HK_TO_OUTFIT_EDITOR, "Outfit Editor")
/datum/outfit/vv_do_topic(list/href_list)
. = ..()
if(href_list[VV_HK_TO_OUTFIT_EDITOR])
usr.client.open_outfit_editor(src)
+1 -1
View File
@@ -108,7 +108,7 @@
/datum/status_effect/off_balance/on_remove() /datum/status_effect/off_balance/on_remove()
var/active_item = owner.get_active_held_item() var/active_item = owner.get_active_held_item()
if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) if(active_item)
owner.visible_message("<span class='warning'>[owner.name] regains their grip on \the [active_item]!</span>", "<span class='warning'>You regain your grip on \the [active_item]</span>", null, COMBAT_MESSAGE_RANGE) owner.visible_message("<span class='warning'>[owner.name] regains their grip on \the [active_item]!</span>", "<span class='warning'>You regain your grip on \the [active_item]</span>", null, COMBAT_MESSAGE_RANGE)
return ..() return ..()
+18
View File
@@ -412,6 +412,24 @@ If not set, defaults to check_completion instead. Set it. It's used by cryo.
counter++ counter++
return counter >= 8 return counter >= 8
/datum/objective/freedom
name = "freedom"
explanation_text = "Don't get captured by nanotrasen."
team_explanation_text = "Have all members of your team free of nanotrasen custody."
/datum/objective/freedom/check_completion()
var/list/datum/mind/owners = get_owners()
for(var/m in owners)
var/datum/mind/M = m
if(!considered_alive(M))
return FALSE
if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
return FALSE
var/turf/location = get_turf(M.current)
if(!location || istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/red/brig)) // Fails if they are in the shuttle brig
return FALSE
return TRUE
/datum/objective/escape /datum/objective/escape
name = "escape" name = "escape"
explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody." explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody."
+225 -304
View File
@@ -13,7 +13,7 @@
var/temp = "Inactive" var/temp = "Inactive"
var/scantemp_ckey var/scantemp_ckey
var/scantemp_name var/scantemp_name
var/scantemp = "Ready to Scan" var/scantemp = "Inactive"
var/menu = 1 //Which menu screen to display var/menu = 1 //Which menu screen to display
var/datum/data/record/active_record = null var/datum/data/record/active_record = null
var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything. var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything.
@@ -132,7 +132,6 @@
src.diskette = W src.diskette = W
to_chat(user, "<span class='notice'>You insert [W].</span>") to_chat(user, "<span class='notice'>You insert [W].</span>")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
src.updateUsrDialog()
else if(W.tool_behaviour == TOOL_MULTITOOL) else if(W.tool_behaviour == TOOL_MULTITOOL)
if(istype(W.buffer, clonepod_type)) if(istype(W.buffer, clonepod_type))
if(get_area(W.buffer) != get_area(src)) if(get_area(W.buffer) != get_area(src))
@@ -151,311 +150,233 @@
else else
return ..() return ..()
/obj/machinery/computer/cloning/ui_interact(mob/user) /obj/machinery/computer/cloning/AltClick(mob/user)
. = ..() . = ..()
EjectDisk(user)
updatemodules(TRUE) /obj/machinery/computer/cloning/proc/EjectDisk(mob/user)
if(diskette)
scantemp = "Disk Ejected"
diskette.forceMove(drop_location())
usr.put_in_active_hand(diskette)
diskette = null
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
var/dat = "" /obj/machinery/computer/cloning/proc/Save(mob/user, target)
dat += "<a href='byond://?src=[REF(src)];refresh=1'>Refresh</a>" var/datum/data/record/GRAB = null
for(var/datum/data/record/record in records)
if(record.fields["id"] == target)
GRAB = record
break
else
continue
if(!GRAB || !GRAB.fields)
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
scantemp = "Failed saving to disk: Data Corruption"
return FALSE
if(!diskette || diskette.read_only)
scantemp = !diskette ? "Failed saving to disk: No disk." : "Failed saving to disk: Disk refuses override attempt."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
diskette.fields = GRAB.fields.Copy()
diskette.name = "data disk - '[src.diskette.fields["name"]]'"
scantemp = "Saved to disk successfully."
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
/obj/machinery/computer/cloning/proc/DeleteRecord(mob/user, target)
var/datum/data/record/GRAB = null
for(var/datum/data/record/record in records)
if(record.fields["id"] == target)
GRAB = record
break
else
continue
if(!GRAB)
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
scantemp = "Cannot delete: Data Corrupted."
return FALSE
var/obj/item/card/id/C = usr.get_idcard(hand_first = TRUE)
if(istype(C) || istype(C, /obj/item/pda) || istype(C, /obj/item/modular_computer/tablet))
if(check_access(C))
scantemp = "[GRAB.fields["name"]] => Record deleted."
records.Remove(GRAB)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
var/obj/item/circuitboard/computer/cloning/board = circuit
board.records = records
return TRUE
scantemp = "Cannot delete: Access Denied."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
/obj/machinery/computer/cloning/proc/Load(mob/user)
if(!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields)
scantemp = "Failed loading: Load error."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
for(var/datum/data/record/R in records)
if(R.fields["key"] == diskette.fields["key"])
scantemp = "Failed loading: Data already exists!"
return FALSE
var/datum/data/record/R = new(src)
for(var/key in diskette.fields)
R.fields[key] = diskette.fields[key]
records += R
scantemp = "Loaded into internal storage successfully."
var/obj/item/circuitboard/computer/cloning/board = circuit
board.records = records
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
/obj/machinery/computer/cloning/proc/Clone(mob/user, target)
var/datum/data/record/C = find_record("id", target, records)
//Look for that player! They better be dead!
if(C)
var/obj/machinery/clonepod/pod = GetAvailablePod()
//Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
if(!LAZYLEN(pods))
temp = "Error: No Clonepods detected."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(!pod)
temp = "Error: No Clonepods available."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(!CONFIG_GET(flag/revival_cloning))
temp = "Error: Unable to initiate cloning cycle."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(pod.occupant)
temp = "Warning: Cloning cycle already in progress."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["blood_type"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"], C.fields["bank_account"], C.fields["traumas"]))
temp = "Notice: [C.fields["name"]] => Cloning cycle in progress..."
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
records.Remove(C)
else
temp = "Error: [C.fields["name"]] => Initialisation failure."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else
temp = "Failed to clone: Data corrupted."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
. = TRUE
/obj/machinery/computer/cloning/proc/Toggle_lock(mob/user)
if(!scanner.is_operational())
return
if(!scanner.locked && !scanner.occupant) //I figured out that if you're fast enough, you can lock an open pod
return
scanner.locked = !scanner.locked
playsound(src, scanner.locked ? 'sound/machines/terminal_prompt_deny.ogg' : 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
. = TRUE
/obj/machinery/computer/cloning/proc/Scan(mob/user)
if(!scanner.is_operational() || !scanner.occupant)
return
scantemp = "[scantemp_name] => Scanning..."
loading = TRUE
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
say("Initiating scan...")
var/prev_locked = scanner.locked
scanner.locked = TRUE
addtimer(CALLBACK(src, .proc/finish_scan, scanner.occupant, prev_locked), 2 SECONDS)
. = TRUE
/obj/machinery/computer/cloning/proc/Toggle_autoprocess(mob/user)
autoprocess = !autoprocess
if(autoprocess)
START_PROCESSING(SSmachines, src)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
else
STOP_PROCESSING(SSmachines, src)
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
. = TRUE
/obj/machinery/computer/cloning/ui_data(mob/user)
var/list/data = list()
data["useRecords"] = use_records
var/list/records_to_send = list()
if(use_records) if(use_records)
if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL)
if(!autoprocess) data["hasAutoprocess"] = TRUE
dat += "<a href='byond://?src=[REF(src)];task=autoprocess'>Autoclone</a>" if(length(records))
else
dat += "<a href='byond://?src=[REF(src)];task=stopautoprocess'>Stop autoclone</a>"
else
dat += "<span class='linkOff'>Autoclone</span>"
dat += "<h3>Cloning Pod Status</h3>"
dat += "<div class='statusDisplay'>[temp]&nbsp;</div>"
switch(src.menu)
if(1)
// Modules
if (isnull(src.scanner) || !LAZYLEN(pods))
dat += "<h3>Modules</h3>"
//dat += "<a href='byond://?src=[REF(src)];relmodules=1'>Reload Modules</a>"
if (isnull(src.scanner))
dat += "<font class='bad'>ERROR: No Scanner detected!</font><br>"
if (!LAZYLEN(pods))
dat += "<font class='bad'>ERROR: No Pod detected</font><br>"
// Scanner
if (!isnull(src.scanner))
var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant)
dat += "<h3>Scanner Functions</h3>"
dat += "<div class='statusDisplay'>"
if(!scanner_occupant)
dat += "Scanner Unoccupied"
else if(loading)
dat += "[scanner_occupant] => Scanning..."
else
if(use_records)
if(scanner_occupant.ckey != scantemp_ckey || scanner_occupant.name != scantemp_name)
scantemp = "Ready to Scan"
scantemp_ckey = scanner_occupant.ckey
scantemp_name = scanner_occupant.name
else
scantemp = "Ready to Clone"
dat += "[scanner_occupant] => [scantemp]"
dat += "</div>"
if(scanner_occupant)
dat += "<a href='byond://?src=[REF(src)];scan=1'>[use_records ? "Start Scan" : "Clone"]</a>"
dat += "<br><a href='byond://?src=[REF(src)];lock=1'>[scanner.locked ? "Unlock Scanner" : "Lock Scanner"]</a>"
else
dat += "<span class='linkOff'>[use_records ? "Start Scan" : "Clone"]</span>"
if(use_records)
// Database
dat += "<h3>Database Functions</h3>"
if (src.records.len && src.records.len > 0)
dat += "<a href='byond://?src=[REF(src)];menu=2'>View Records ([src.records.len])</a><br>"
else
dat += "<span class='linkOff'>View Records (0)</span><br>"
if (src.diskette)
dat += "<a href='byond://?src=[REF(src)];disk=eject'>Eject Disk</a><br>"
if(2)
dat += "<h3>Current records</h3>"
dat += "<a href='byond://?src=[REF(src)];menu=1'><< Back</a><br><br>"
for(var/datum/data/record/R in records) for(var/datum/data/record/R in records)
dat += "<h4>[R.fields["name"]]</h4>Scan ID [R.fields["id"]] <a href='byond://?src=[REF(src)];view_rec=[R.fields["id"]]'>View Record</a>" var/list/record_entry = list()
if(3) record_entry["name"] = "[R.fields["name"]]"
dat += "<h3>Selected Record</h3>" record_entry["id"] = "[R.fields["id"]]"
dat += "<a href='byond://?src=[REF(src)];menu=2'><< Back</a><br>" var/obj/item/implant/health/H = locate(R.fields["imp"])
if(H && istype(H))
if (!src.active_record) record_entry["damages"] = H.sensehealth(TRUE)
dat += "<font class='bad'>Record not found.</font>"
else
dat += "<h4>[src.active_record.fields["name"]]</h4>"
dat += "Scan ID [src.active_record.fields["id"]] <a href='byond://?src=[REF(src)];clone=[active_record.fields["id"]]'>Clone</a><br>"
var/obj/item/implant/health/H = locate(active_record.fields["imp"])
if ((H) && (istype(H)))
dat += "<b>Health Implant Data:</b><br />[H.sensehealth()]<br><br />"
else else
dat += "<font class='bad'>Unable to locate Health Implant.</font><br /><br />" record_entry["damages"] = FALSE
record_entry["UI"] = "[R.fields["UI"]]"
record_entry["UE"] = "[R.fields["UE"]]"
record_entry["blood_type"] = "[R.fields["blood_type"]]"
records_to_send += list(record_entry)
data["records"] = records_to_send
else
data["records"] = list()
if(diskette && diskette.fields)
var/list/disk_data = list()
disk_data["name"] = "[diskette.fields["name"]]"
disk_data["id"] = "[diskette.fields["id"]]"
disk_data["UI"] = "[diskette.fields["UI"]]"
disk_data["UE"] = "[diskette.fields["UE"]]"
disk_data["blood_type"] = "[diskette.fields["blood_type"]]"
data["diskData"] = disk_data
else
data["diskData"] = list()
else
data["hasAutoprocess"] = FALSE
data["autoprocess"] = autoprocess
var/list/lack_machine = list()
if(isnull(src.scanner))
lack_machine += "ERROR: No Scanner Detected!"
if(!LAZYLEN(pods))
lack_machine += "ERROR: No Pod Detected!"
data["lacksMachine"] = lack_machine
data["temp"] = temp
var/build_temp = null
var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner?.occupant)
if(scanner_occupant?.ckey != scantemp_ckey || scanner_occupant?.name != scantemp_name)
if(use_records)
build_temp = "Ready to Scan"
scantemp_ckey = scanner_occupant?.ckey
scantemp_name = scanner_occupant?.name
else
build_temp = "Ready to Clone"
scantemp = "[scanner_occupant] => [build_temp]"
data["scanTemp"] = scantemp
data["scannerLocked"] = scanner?.locked
data["hasOccupant"] = scanner?.occupant
data["recordsLength"] = "View Records ([length(records)])"
dat += "<b>Unique Identifier:</b><br /><span class='highlight'>[src.active_record.fields["UI"]]</span><br>" return data
dat += "<b>Structural Enzymes:</b><br /><span class='highlight'>[src.active_record.fields["SE"]]</span><br>"
if(diskette && diskette.fields) /obj/machinery/computer/cloning/ui_act(action, params)
dat += "<div class='block'>"
dat += "<h4>Inserted Disk</h4>"
dat += "<b>Contents:</b> "
var/list/L = list()
if(diskette.fields["UI"])
L += "Unique Identifier"
if(diskette.fields["UE"] && diskette.fields["name"] && diskette.fields["blood_type"])
L += "Unique Enzymes"
if(diskette.fields["SE"])
L += "Structural Enzymes"
dat += english_list(L, "Empty", " + ", " + ")
dat += "<br /><a href='byond://?src=[REF(src)];disk=load'>Load from Disk</a>"
dat += "<br /><a href='byond://?src=[REF(src)];disk=save'>Save to Disk</a>"
dat += "</div>"
dat += "<font size=1><a href='byond://?src=[REF(src)];del_rec=1'>Delete Record</a></font>"
if(4)
if (!src.active_record)
src.menu = 2
dat = "[src.temp]<br>"
dat += "<h3>Confirm Record Deletion</h3>"
dat += "<b><a href='byond://?src=[REF(src)];del_rec=1'>Scan card to confirm.</a></b><br>"
dat += "<b><a href='byond://?src=[REF(src)];menu=3'>Cancel</a></b>"
var/datum/browser/popup = new(user, "cloning", "Cloning System Control")
popup.set_content(dat)
popup.open()
/obj/machinery/computer/cloning/Topic(href, href_list)
if(..()) if(..())
return return
switch(action)
if("toggle_autoprocess")
Toggle_autoprocess(usr)
if("scan")
Scan(usr)
if("toggle_lock")
Toggle_lock(usr)
if("clone")
Clone(usr, params["target"])
if("delrecord")
DeleteRecord(usr, params["target"])
if("save")
Save(usr, params["target"])
if("load")
Load(usr)
if("eject")
EjectDisk(usr)
if(loading) /obj/machinery/computer/cloning/ui_interact(mob/user, datum/tgui/ui)
if(..())
return return
updatemodules(TRUE)
if(href_list["task"]) ui = SStgui.try_update_ui(user, src, ui)
switch(href_list["task"]) if(!ui)
if("autoprocess") ui = new(user, src, "CloningConsole", "Cloning System Control")
if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) ui.open()
autoprocess = TRUE
START_PROCESSING(SSmachines, src)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
if("stopautoprocess")
autoprocess = FALSE
STOP_PROCESSING(SSmachines, src)
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
src.updateUsrDialog()
. = TRUE
else if ((href_list["scan"]) && !isnull(scanner) && scanner.is_operational())
scantemp = ""
loading = TRUE
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
say("Initiating scan...")
var/prev_locked = scanner.locked
scanner.locked = TRUE
src.updateUsrDialog()
addtimer(CALLBACK(src, .proc/finish_scan, scanner.occupant, prev_locked), 2 SECONDS)
. = TRUE
//No locking an open scanner.
else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational())
if ((!scanner.locked) && (scanner.occupant))
scanner.locked = TRUE
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else
scanner.locked = FALSE
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
src.updateUsrDialog()
. = TRUE
else if (href_list["refresh"])
src.updateUsrDialog()
playsound(src, "terminal_type", 25, 0)
. = TRUE
if(. || !use_records)
return
if(href_list["view_rec"])
playsound(src, "terminal_type", 25, 0)
src.active_record = find_record("id", href_list["view_rec"], records)
if(active_record)
if(!active_record.fields["ckey"])
records -= active_record
active_record = null
src.temp = "<font class='bad'>Record Corrupt</font>"
else
src.menu = 3
else
src.temp = "Record missing."
src.updateUsrDialog()
. = TRUE
else if (href_list["del_rec"])
if ((!src.active_record) || (src.menu < 3))
return
if (src.menu == 3) //If we are viewing a record, confirm deletion
src.temp = "Delete record?"
src.menu = 4
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0)
else if (src.menu == 4)
var/obj/item/card/id/C = usr.get_active_held_item()
if (istype(C)||istype(C, /obj/item/pda))
if(src.check_access(C))
src.temp = "[src.active_record.fields["name"]] => Record deleted."
src.records.Remove(active_record)
active_record = null
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
src.menu = 2
var/obj/item/circuitboard/computer/cloning/board = circuit
board.records = records
else
src.temp = "<font class='bad'>Access Denied.</font>"
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
. = TRUE
else if (href_list["disk"] && use_records) //Load or eject.
switch(href_list["disk"])
if("load")
if (!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields)
src.temp = "<font class='bad'>Load error.</font>"
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
if (!src.active_record)
src.temp = "<font class='bad'>Record error.</font>"
src.menu = 1
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
for(var/key in diskette.fields)
src.active_record.fields[key] = diskette.fields[key]
src.temp = "Load successful."
src.updateUsrDialog()
var/obj/item/circuitboard/computer/cloning/board = circuit
board.records = records
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
if("eject")
if(src.diskette)
src.diskette.forceMove(drop_location())
src.diskette = null
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
if("save")
if(!diskette || diskette.read_only || !active_record || !active_record.fields)
src.temp = "<font class='bad'>Save error.</font>"
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return
diskette.fields = active_record.fields.Copy()
diskette.name = "data disk - '[src.diskette.fields["name"]]'"
src.temp = "Save successful."
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
. = TRUE
else if (href_list["clone"])
var/datum/data/record/C = find_record("id", href_list["clone"], records)
//Look for that player! They better be dead!
if(C)
var/obj/machinery/clonepod/pod = GetAvailablePod()
//Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
if(!LAZYLEN(pods))
temp = "<font class='bad'>No Clonepods detected.</font>"
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(!pod)
temp = "<font class='bad'>No Clonepods available.</font>"
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(!CONFIG_GET(flag/revival_cloning))
temp = "<font class='bad'>Unable to initiate cloning cycle.</font>"
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(pod.occupant)
temp = "<font class='bad'>Cloning cycle already in progress.</font>"
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["blood_type"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"], C.fields["bank_account"], C.fields["traumas"]))
temp = "[C.fields["name"]] => <font class='good'>Cloning cycle in progress...</font>"
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
records.Remove(C)
if(active_record == C)
active_record = null
menu = 1
src.updateUsrDialog()
else
temp = "[C.fields["name"]] => <font class='bad'>Initialisation failure.</font>"
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else
temp = "<font class='bad'>Data corruption.</font>"
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
. = TRUE
else if (href_list["menu"] && use_records)
menu = text2num(href_list["menu"])
src.updateUsrDialog()
playsound(src, "terminal_type", 25, 0)
. = TRUE
/obj/machinery/computer/cloning/proc/finish_scan(mob/living/L, prev_locked) /obj/machinery/computer/cloning/proc/finish_scan(mob/living/L, prev_locked)
if(!scanner || !L) if(!scanner || !L)
@@ -469,7 +390,6 @@
loading = FALSE loading = FALSE
scanner.locked = prev_locked scanner.locked = prev_locked
src.updateUsrDialog()
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
@@ -568,44 +488,44 @@
var/obj/machinery/clonepod/pod = GetAvailablePod() var/obj/machinery/clonepod/pod = GetAvailablePod()
//Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs.
if(!LAZYLEN(pods)) if(!LAZYLEN(pods))
temp = "<font class='bad'>No Clonepods detected.</font>" temp = "No Clonepods detected."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(!pod) else if(!pod)
temp = "<font class='bad'>No Clonepods available.</font>" temp = "No Clonepods available."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else if(pod.occupant) else if(pod.occupant)
temp = "<font class='bad'>Cloning cycle already in progress.</font>" temp = "Cloning cycle already in progress."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
else else
pod.growclone(null, mob_occupant.real_name, dna.uni_identity, dna.mutation_index, null, dna.blood_type, clone_species, dna.features, mob_occupant.faction) pod.growclone(null, mob_occupant.real_name, dna.uni_identity, dna.mutation_index, null, dna.blood_type, clone_species, dna.features, mob_occupant.faction)
temp = "[mob_occupant.real_name] => <font class='good'>Cloning data sent to pod.</font>" temp = "[mob_occupant.real_name] => Cloning data sent to pod."
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
/obj/machinery/computer/cloning/proc/can_scan(datum/dna/dna, mob/living/mob_occupant, experimental = FALSE, datum/bank_account/account) /obj/machinery/computer/cloning/proc/can_scan(datum/dna/dna, mob/living/mob_occupant, experimental = FALSE, datum/bank_account/account)
if(!istype(dna)) if(!istype(dna))
scantemp = "<font class='bad'>Unable to locate valid genetic data.</font>" scantemp = "Unable to locate valid genetic data."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return return
if(!experimental) if(!experimental)
if(mob_occupant.suiciding || mob_occupant.hellbound) if(mob_occupant.suiciding || mob_occupant.hellbound)
scantemp = "<font class='bad'>Subject's brain is not responding to scanning stimuli.</font>" scantemp = "Subject's brain is not responding to scanning stimuli."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return return
if((HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && (src.scanner.scan_level < 2)) if((HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && (src.scanner.scan_level < 2))
scantemp = "<font class='bad'>Subject no longer contains the fundamental materials required to create a living clone.</font>" scantemp = "Subject no longer contains the fundamental materials required to create a living clone."
playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0)
return return
if (!experimental) if (!experimental)
if(!mob_occupant.ckey || !mob_occupant.client) if(!mob_occupant.ckey || !mob_occupant.client)
scantemp = "<font class='bad'>Mental interface failure.</font>" scantemp = "Mental interface failure."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return return
if (find_record("ckey", mob_occupant.ckey, records)) if (find_record("ckey", mob_occupant.ckey, records))
scantemp = "<font class='average'>Subject already in database.</font>" scantemp = "Subject already in database."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return return
if(SSeconomy.full_ancap && !account) if(SSeconomy.full_ancap && !account)
scantemp = "<font class='average'>Subject is either missing an ID card with a bank account on it, or does not have an account to begin with. Please ensure the ID card is on the body before attempting to scan.</font>" scantemp = "Subject is either missing an ID card with a bank account on it, or does not have an account to begin with. Please ensure the ID card is on the body before attempting to scan."
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
return return
return TRUE return TRUE
@@ -618,3 +538,4 @@
icon_keyboard = "med_key" icon_keyboard = "med_key"
circuit = /obj/item/circuitboard/computer/cloning/prototype circuit = /obj/item/circuitboard/computer/cloning/prototype
clonepod_type = /obj/machinery/clonepod/experimental clonepod_type = /obj/machinery/clonepod/experimental
use_records = FALSE //Wait, so you tell me it lacks records but you never set it as false?
+13 -3
View File
@@ -102,8 +102,14 @@
if ("answerMessage") if ("answerMessage")
if (!authenticated(usr)) if (!authenticated(usr))
return return
var/answer_index = text2num(params["answer"]) var/answer_index = params["answer"]
var/message_index = text2num(params["message"]) var/message_index = params["message"]
// If either of these aren't numbers, then bad voodoo.
if(!isnum(answer_index) || !isnum(message_index))
message_admins("[ADMIN_LOOKUPFLW(usr)] provided an invalid index type when replying to a message on [src] [ADMIN_JMP(src)]. This should not happen. Please check with a maintainer and/or consult tgui logs.")
CRASH("Non-numeric index provided when answering comms console message.")
if (!answer_index || !message_index || answer_index < 1 || message_index < 1) if (!answer_index || !message_index || answer_index < 1 || message_index < 1)
return return
var/datum/comm_message/message = messages[message_index] var/datum/comm_message/message = messages[message_index]
@@ -156,7 +162,11 @@
if ("deleteMessage") if ("deleteMessage")
if (!authenticated(usr)) if (!authenticated(usr))
return return
var/message_index = text2num(params["message"]) var/message_index = params["message"]
if(!isnum(message_index))
message_admins("[ADMIN_LOOKUPFLW(usr)] provided an invalid index type when deleting a message on [src] [ADMIN_JMP(src)]. This should not happen. Please check with a maintainer and/or consult tgui logs.")
CRASH("Non-numeric index provided when deleting comms console message.")
if (!message_index) if (!message_index)
return return
LAZYREMOVE(messages, LAZYACCESS(messages, message_index)) LAZYREMOVE(messages, LAZYACCESS(messages, message_index))
+20
View File
@@ -21,6 +21,7 @@ Buildable meters
level = 2 level = 2
var/piping_layer = PIPING_LAYER_DEFAULT var/piping_layer = PIPING_LAYER_DEFAULT
var/RPD_type var/RPD_type
var/disposable = TRUE
/obj/item/pipe/directional /obj/item/pipe/directional
RPD_type = PIPE_UNARY RPD_type = PIPE_UNARY
@@ -236,3 +237,22 @@ Buildable meters
/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT) /obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT)
piping_layer = new_layer piping_layer = new_layer
PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer) PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer)
/obj/item/pipe/bluespace
pipe_type = /obj/machinery/atmospherics/pipe/bluespace
var/bluespace_network_name = "default"
icon_state = "bluespace"
disposable = FALSE
/obj/item/pipe/bluespace/attack_self(mob/user)
var/new_name = input(user, "Enter identifier for bluespace pipe network", "bluespace pipe", bluespace_network_name) as text|null
if(!isnull(new_name))
bluespace_network_name = new_name
/obj/item/pipe/bluespace/make_from_existing(obj/machinery/atmospherics/pipe/bluespace/make_from)
bluespace_network_name = make_from.bluespace_network_name
return ..()
/obj/item/pipe/bluespace/build_pipe(obj/machinery/atmospherics/pipe/bluespace/A)
A.bluespace_network_name = bluespace_network_name
return ..()
+1 -1
View File
@@ -140,7 +140,7 @@
if (. & EMP_PROTECT_SELF) if (. & EMP_PROTECT_SELF)
return return
if(get_charge()) if(get_charge())
use_power(cell.charge*severity/100) use_power((cell.charge/3)*(severity*0.005))
take_damage(severity/3, BURN, "energy", 1) take_damage(severity/3, BURN, "energy", 1)
mecha_log_message("EMP detected", color="red") mecha_log_message("EMP detected", color="red")
+8 -6
View File
@@ -375,12 +375,14 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
. = TRUE . = TRUE
if((mode & DESTROY_MODE) && istype(A, /obj/item/pipe) || istype(A, /obj/structure/disposalconstruct) || istype(A, /obj/structure/c_transit_tube) || istype(A, /obj/structure/c_transit_tube_pod) || istype(A, /obj/item/pipe_meter)) if((mode & DESTROY_MODE) && istype(A, /obj/item/pipe) || istype(A, /obj/structure/disposalconstruct) || istype(A, /obj/structure/c_transit_tube) || istype(A, /obj/structure/c_transit_tube_pod) || istype(A, /obj/item/pipe_meter))
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>") var/obj/item/pipe/P = A
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1) if(!istype(P) || P.disposable)
if(do_after(user, destroy_speed, target = A)) to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
activate() playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
qdel(A) if(do_after(user, destroy_speed, target = A))
return activate()
qdel(A)
return
if((mode & PAINT_MODE)) if((mode & PAINT_MODE))
if(istype(A, /obj/machinery/atmospherics/pipe) && !istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) if(istype(A, /obj/machinery/atmospherics/pipe) && !istype(A, /obj/machinery/atmospherics/pipe/layer_manifold))
+2 -4
View File
@@ -210,8 +210,7 @@
target.apply_effect(EFFECT_STUTTER, stunforce) target.apply_effect(EFFECT_STUTTER, stunforce)
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
if(user) if(user)
target.lastattacker = user.real_name target.set_last_attacker(user)
target.lastattackerckey = user.ckey
target.visible_message("<span class='danger'>[user] has shocked [target] with [src]!</span>", \ target.visible_message("<span class='danger'>[user] has shocked [target] with [src]!</span>", \
"<span class='userdanger'>[user] has shocked you with [src]!</span>") "<span class='userdanger'>[user] has shocked you with [src]!</span>")
log_combat(user, target, "stunned with an electrostaff") log_combat(user, target, "stunned with an electrostaff")
@@ -237,8 +236,7 @@
target.adjustFireLoss(lethal_force) //good against ointment spam target.adjustFireLoss(lethal_force) //good against ointment spam
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
if(user) if(user)
target.lastattacker = user.real_name target.set_last_attacker(user)
target.lastattackerckey = user.ckey
target.visible_message("<span class='danger'>[user] has seared [target] with [src]!</span>", \ target.visible_message("<span class='danger'>[user] has seared [target] with [src]!</span>", \
"<span class='userdanger'>[user] has seared you with [src]!</span>") "<span class='userdanger'>[user] has seared you with [src]!</span>")
log_combat(user, target, "burned with an electrostaff") log_combat(user, target, "burned with an electrostaff")
+1 -1
View File
@@ -312,7 +312,7 @@
trap_damage = 0 trap_damage = 0
item_flags = DROPDEL item_flags = DROPDEL
flags_1 = NONE flags_1 = NONE
breakouttime = 50 breakouttime = 25
/obj/item/restraints/legcuffs/beartrap/energy/New() /obj/item/restraints/legcuffs/beartrap/energy/New()
..() ..()
@@ -0,0 +1,74 @@
/datum/deathrattle_group
var/name
var/list/datum/weakref/implant_refs = list()
/datum/deathrattle_group/New()
// Give the group a unique name for debugging, and possible future
// use for making custom linked groups.
name = "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
/datum/deathrattle_group/proc/rattle(obj/item/implant/deathrattle/origin, mob/living/owner)
var/name = owner.mind ? owner.mind.name : owner.real_name
var/area = get_area_name(get_turf(owner))
for(var/r in implant_refs)
var/datum/weakref/R = r
var/obj/item/implant/deathrattle/implant = R.resolve()
if(!implant || implant == origin)
continue
// Not all the implants may be actually implanted in people.
if(!implant.imp_in)
continue
// Deliberately the same message framing as nanite message + ghost deathrattle
var/mob/living/recipient = implant.imp_in
to_chat(recipient, "<i>You hear a strange, robotic voice in your head...</i> \"<span class='robot'><b>[name]</b> has died at <b>[area]</b>.</span>\"")
SEND_SOUND(recipient, pick(
'sound/items/knell1.ogg',
'sound/items/knell2.ogg',
'sound/items/knell3.ogg',
'sound/items/knell4.ogg',
))
/datum/deathrattle_group/proc/register(obj/item/implant/deathrattle/implant)
implant.group = src
implant_refs += WEAKREF(implant)
/obj/item/implant/deathrattle
name = "deathrattle implant"
desc = "Hope no one else dies, prepare for when they do."
activated = FALSE
var/datum/deathrattle_group/group = null
/obj/item/implant/deathrattle/Destroy()
group = null
return ..()
/obj/item/implant/deathrattle/can_be_implanted_in(mob/living/target)
// Can be implanted in anything that's a mob. Syndicate cyborgs, talking fish, humans...
return TRUE
/obj/item/implant/deathrattle/proc/on_predeath(datum/source, gibbed)
SIGNAL_HANDLER
if(group)
group.rattle(origin = src, owner = source)
/obj/item/implant/deathrattle/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
. = ..()
if(.)
RegisterSignal(target, COMSIG_LIVING_PREDEATH, .proc/on_predeath)
if(!group)
to_chat(target, "<i>You hear a strange, robotic voice in your head...</i> \"<span class='robot'>Warning: No other linked implants detected.</span>\"")
/obj/item/implantcase/deathrattle
name = "implant case - 'Deathrattle'"
desc = "A glass case containing a deathrattle implant."
imp_type = /obj/item/implant/deathrattle
@@ -139,14 +139,25 @@
name = "health implant" name = "health implant"
activated = 0 activated = 0
var/healthstring = "" var/healthstring = ""
var/list/raw_data = list()
/obj/item/implant/health/proc/sensehealth() /obj/item/implant/health/proc/sensehealth(get_list = FALSE)
if (!imp_in) if (!imp_in)
return "ERROR" return "ERROR"
else else
if(isliving(imp_in)) if(isliving(imp_in))
var/mob/living/L = imp_in var/mob/living/L = imp_in
healthstring = "<small>Oxygen Deprivation Damage => [round(L.getOxyLoss())]<br />Fire Damage => [round(L.getFireLoss())]<br />Toxin Damage => [round(L.getToxLoss())]<br />Brute Force Damage => [round(L.getBruteLoss())]</small>" healthstring = "<small>Oxygen Deprivation Damage => [round(L.getOxyLoss())]<br />Fire Damage => [round(L.getFireLoss())]<br />Toxin Damage => [round(L.getToxLoss())]<br />Brute Force Damage => [round(L.getBruteLoss())]</small>"
if (!healthstring) raw_data = list() //Reset list
raw_data["oxy"] = list("[round(L.getOxyLoss())]") //Suffocation
raw_data["burn"] = list("[round(L.getFireLoss())]") //Burn
raw_data["tox"] = list("[round(L.getToxLoss())]") //Tox
raw_data["brute"] = list("[round(L.getBruteLoss())]") //Brute
if(!healthstring) //I have no idea who made it go this order but okay.
healthstring = "ERROR" healthstring = "ERROR"
return healthstring if(!length(raw_data))
raw_data = list("ERROR")
if(!get_list)
return healthstring
else
return raw_data
+12
View File
@@ -271,11 +271,14 @@
var/force_on // Damage when on - not stunning var/force_on // Damage when on - not stunning
var/force_off // Damage when off - not stunning var/force_off // Damage when off - not stunning
var/weight_class_on // What is the new size class when turned on var/weight_class_on // What is the new size class when turned on
var/sword_point = TRUE
wound_bonus = 15 wound_bonus = 15
/obj/item/melee/classic_baton/Initialize() /obj/item/melee/classic_baton/Initialize()
. = ..() . = ..()
if(sword_point)
AddElement(/datum/element/sword_point)
// Description for trying to stun when still on cooldown. // Description for trying to stun when still on cooldown.
/obj/item/melee/classic_baton/proc/get_wait_description() /obj/item/melee/classic_baton/proc/get_wait_description()
@@ -402,6 +405,8 @@
weight_class_on = WEIGHT_CLASS_BULKY weight_class_on = WEIGHT_CLASS_BULKY
total_mass = TOTAL_MASS_NORMAL_ITEM total_mass = TOTAL_MASS_NORMAL_ITEM
bare_wound_bonus = 5 bare_wound_bonus = 5
sword_point = FALSE
var/silent = FALSE
/obj/item/melee/classic_baton/telescopic/suicide_act(mob/user) /obj/item/melee/classic_baton/telescopic/suicide_act(mob/user)
var/mob/living/carbon/human/H = user var/mob/living/carbon/human/H = user
@@ -431,6 +436,9 @@
w_class = weight_class_on w_class = weight_class_on
force = force_on force = force_on
attack_verb = list("smacked", "struck", "cracked", "beaten") attack_verb = list("smacked", "struck", "cracked", "beaten")
AddElement(/datum/element/sword_point)
if(!silent)
user?.visible_message("<span class='warning'>[user] extends [src] with a flick of their wrist!</span>")
else else
to_chat(user, desc["local_off"]) to_chat(user, desc["local_off"])
icon_state = off_icon_state icon_state = off_icon_state
@@ -439,6 +447,9 @@
w_class = WEIGHT_CLASS_SMALL w_class = WEIGHT_CLASS_SMALL
force = force_off force = force_off
attack_verb = list("hit", "poked") attack_verb = list("hit", "poked")
RemoveElement(/datum/element/sword_point)
if(!silent)
user?.visible_message("<span class='warning'>[user] collapses [src] back down!</span>")
playsound(src.loc, on_sound, 50, 1) playsound(src.loc, on_sound, 50, 1)
add_fingerprint(user) add_fingerprint(user)
@@ -465,6 +476,7 @@
force_on = 16 force_on = 16
force_off = 5 force_off = 5
weight_class_on = WEIGHT_CLASS_NORMAL weight_class_on = WEIGHT_CLASS_NORMAL
silent = TRUE
/obj/item/melee/classic_baton/telescopic/contractor_baton/get_wait_description() /obj/item/melee/classic_baton/telescopic/contractor_baton/get_wait_description()
return "<span class='danger'>The baton is still charging!</span>" return "<span class='danger'>The baton is still charging!</span>"
@@ -272,7 +272,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
. = ..() . = ..()
. += GLOB.plastitaniumglass_recipes . += GLOB.plastitaniumglass_recipes
/obj/item/stack/sheet/titaniumglass/on_solar_construction(obj/machinery/power/solar/S) /obj/item/stack/sheet/plastitaniumglass/on_solar_construction(obj/machinery/power/solar/S)
S.max_integrity *= 2 S.max_integrity *= 2
S.efficiency *= 2 S.efficiency *= 2
+69
View File
@@ -351,6 +351,75 @@
if(!contents.len) if(!contents.len)
. += "[icon_state]_empty" . += "[icon_state]_empty"
//Derringer "Cigarettes"//
/obj/item/storage/fancy/cigarettes/derringer
name = "\improper Robust packet"
desc = "Smoked by the robust."
icon_state = "robust"
spawn_type = /obj/item/gun/ballistic/derringer/traitor
/obj/item/storage/fancy/cigarettes/derringer/ComponentInitialize()
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_items = 6
STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter, /obj/item/gun/ballistic/derringer, /obj/item/ammo_casing/c38, /obj/item/ammo_casing/a357, /obj/item/ammo_casing/g4570))
/obj/item/storage/fancy/cigarettes/derringer/AltClick(mob/living/carbon/user)
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
var/obj/item/W = (locate(/obj/item/ammo_casing/a357) in contents) || (locate(/obj/item/clothing/mask/cigarette) in contents) || locate(/obj/item/ammo_casing/g4570) //Easy access smokes and bullets
if(W && contents.len > 0)
SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user)
user.put_in_hands(W)
contents -= W
to_chat(user, "<span class='notice'>You take \a [W] out of the pack.</span>")
else
to_chat(user, "<span class='notice'>There are no items left in the pack.</span>")
/obj/item/storage/fancy/cigarettes/derringer/PopulateContents()
new spawn_type(src)
new /obj/item/ammo_casing/a357(src)
new /obj/item/ammo_casing/a357(src)
new /obj/item/ammo_casing/a357(src)
new /obj/item/ammo_casing/a357(src)
new /obj/item/clothing/mask/cigarette/syndicate(src)
//For traitors with luck/class
/obj/item/storage/fancy/cigarettes/derringer/gold
name = "\improper Robust Gold packet"
desc = "Smoked by the truly robust."
icon_state = "robustg"
spawn_type = /obj/item/gun/ballistic/derringer/gold
//For operatives, bound in a ka-tet.
/obj/item/storage/fancy/cigarettes/derringer/midworld
name = "\improper Midworld's Lime Bend"
desc = "The wheel of Ka turns, Gunslinger."
icon_state = "slime"
spawn_type = /obj/item/gun/ballistic/derringer/nukeop
/obj/item/storage/fancy/cigarettes/derringer/midworld/PopulateContents()
new spawn_type(src)
new /obj/item/ammo_casing/g4570(src)
new /obj/item/ammo_casing/g4570(src)
new /obj/item/ammo_casing/g4570(src)
new /obj/item/ammo_casing/g4570(src)
new /obj/item/clothing/mask/cigarette/xeno(src)
//For Cargomen, looking for a good deal on arms, with no quarrels as to where they're from.
/obj/item/storage/fancy/cigarettes/derringer/smuggled
name = "\improper Shady Jim's Super Slims packet"
desc = "If you get caught with this, we don't know you, capiche?"
icon_state = "shadyjim"
spawn_type = /obj/item/gun/ballistic/derringer
/obj/item/storage/fancy/cigarettes/derringer/smuggled/PopulateContents()
new spawn_type(src)
new /obj/item/ammo_casing/c38/lethal(src)
new /obj/item/ammo_casing/c38/lethal(src)
new /obj/item/ammo_casing/c38/lethal(src)
new /obj/item/ammo_casing/c38/lethal(src)
new /obj/item/clothing/mask/cigarette/shadyjims (src)
///////////// /////////////
//CIGAR BOX// //CIGAR BOX//
///////////// /////////////
@@ -546,3 +546,21 @@
. = ..() . = ..()
new /obj/item/cardpack/syndicate(src) new /obj/item/cardpack/syndicate(src)
new /obj/item/cardpack/syndicate(src) new /obj/item/cardpack/syndicate(src)
/obj/item/storage/box/syndie_kit/imp_deathrattle
name = "deathrattle implant box"
desc = "Contains eight linked deathrattle implants."
/obj/item/storage/box/syndie_kit/imp_deathrattle/PopulateContents()
new /obj/item/implanter(src)
var/datum/deathrattle_group/group = new
var/implants = list()
for(var/j in 1 to 8)
var/obj/item/implantcase/deathrattle/case = new (src)
implants += case.imp
for(var/i in implants)
group.register(i)
desc += " The implants are registered to the \"[group.name]\" group."
+2 -1
View File
@@ -46,7 +46,8 @@
/obj/item/instrument/harmonica, /obj/item/instrument/harmonica,
/obj/item/mining_voucher, /obj/item/mining_voucher,
/obj/item/suit_voucher, /obj/item/suit_voucher,
/obj/item/reagent_containers/pill)) /obj/item/reagent_containers/pill,
/obj/item/gun/ballistic/derringer))
/obj/item/storage/wallet/Exited(atom/movable/AM) /obj/item/storage/wallet/Exited(atom/movable/AM)
. = ..() . = ..()
+57 -30
View File
@@ -16,17 +16,21 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
attack_speed = CLICK_CD_MELEE attack_speed = CLICK_CD_MELEE
var/stamforce = 35 var/stamina_loss_amount = 35
var/turned_on = FALSE var/turned_on = FALSE
var/knockdown = TRUE var/knockdown = TRUE
var/obj/item/stock_parts/cell/cell var/obj/item/stock_parts/cell/cell
var/hitcost = 750 var/hitcost = 750
var/throw_hit_chance = 35 var/throw_hit_chance = 35
var/preload_cell_type //if not empty the baton starts with this type of cell var/preload_cell_type //if not empty the baton starts with this type of cell
var/cooldown_duration = 5 SECONDS //How long our baton rightclick goes on cooldown for after applying a knockdown
var/status_duration = 5 SECONDS //how long our status effects last for otherwise
COOLDOWN_DECLARE(shove_cooldown)
/obj/item/melee/baton/examine(mob/user) /obj/item/melee/baton/examine(mob/user)
. = ..() . = ..()
. += "<span class='notice'>Right click attack while in combat mode to disarm instead of stun.</span>" . += "<span class='notice'>Right click attack while in combat mode to knockdown, but only once per [cooldown_duration / 10] seconds.</span>"
. += "<span class='notice'>This knockdown will also put them off balance for [status_duration / 20] seconds, allowing you to shove a weapon out of their hand with a right click in Disarm intent.</span>"
/obj/item/melee/baton/get_cell() /obj/item/melee/baton/get_cell()
. = cell . = cell
@@ -53,8 +57,8 @@
/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) /obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..() ..()
//Only mob/living types have stun handling //Only mob/living types have stun handling
if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom)) if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom) && thrownby)
baton_stun(hit_atom) baton_stun(hit_atom, thrownby, shoving = TRUE)
/obj/item/melee/baton/loaded //this one starts with a cell pre-installed. /obj/item/melee/baton/loaded //this one starts with a cell pre-installed.
preload_cell_type = /obj/item/stock_parts/cell/high/plus preload_cell_type = /obj/item/stock_parts/cell/high/plus
@@ -74,6 +78,16 @@
//we're below minimum, turn off //we're below minimum, turn off
switch_status(FALSE) switch_status(FALSE)
///Check for our cell to determine how much penetration our weapon does.
/obj/item/melee/baton/proc/get_cell_zap_pen()
var/obj/item/stock_parts/cell/copper_top = get_cell()
if(copper_top)
var/chargepower = copper_top.maxcharge
var/zap_penetration = (chargepower/1000) //This is our effective penetration. Every 1000 max charge, we get 1 pen power. A high capacity cell is equal to 10 armor pen, as an example.
return zap_penetration
else
return 0
/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) /obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE)
if(turned_on != new_status) if(turned_on != new_status)
turned_on = new_status turned_on = new_status
@@ -101,6 +115,7 @@
var/obj/item/stock_parts/cell/copper_top = get_cell() var/obj/item/stock_parts/cell/copper_top = get_cell()
if(copper_top) if(copper_top)
. += "<span class='notice'>\The [src] is [round(copper_top.percent())]% charged.</span>" . += "<span class='notice'>\The [src] is [round(copper_top.percent())]% charged.</span>"
. += "<span class='notice'>\The [src] currently can penetrate [round(copper_top.maxcharge/1000)]% of enemy armor thanks to it's loaded cell.</span>"
else else
. += "<span class='warning'>\The [src] does not have a power source installed.</span>" . += "<span class='warning'>\The [src] does not have a power source installed.</span>"
@@ -150,10 +165,10 @@
/obj/item/melee/baton/alt_pre_attack(atom/A, mob/living/user, params) /obj/item/melee/baton/alt_pre_attack(atom/A, mob/living/user, params)
if(!user.CheckActionCooldown(CLICK_CD_MELEE)) if(!user.CheckActionCooldown(CLICK_CD_MELEE))
return return
. = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-disarming. . = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-shoving.
//return TRUE to interrupt attack chain. //return TRUE to interrupt attack chain.
/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, disarming = FALSE) /obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, shoving = FALSE)
if(iscyborg(M) || !isliving(M)) //can't baton cyborgs if(iscyborg(M) || !isliving(M)) //can't baton cyborgs
return FALSE return FALSE
if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
@@ -167,21 +182,26 @@
if(check_martial_counter(L, user)) if(check_martial_counter(L, user))
return TRUE return TRUE
if(turned_on) if(turned_on)
if(baton_stun(M, user, disarming)) if(baton_stun(M, user, shoving))
user.do_attack_animation(M) user.do_attack_animation(M)
else if(user.a_intent != INTENT_HARM) //they'll try to bash in the last proc. else if(user.a_intent != INTENT_HARM) //they'll try to bash in the last proc.
M.visible_message("<span class='warning'>[user] has prodded [M] with [src]. Luckily it was off.</span>", \ M.visible_message("<span class='warning'>[user] has prodded [M] with [src]. Luckily it was off.</span>", \
"<span class='warning'>[user] has prodded you with [src]. Luckily it was off</span>") "<span class='warning'>[user] has prodded you with [src]. Luckily it was off</span>")
return disarming || (user.a_intent != INTENT_HARM) return shoving || (user.a_intent != INTENT_HARM)
/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, disarming = FALSE) /obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, shoving = FALSE)
var/list/return_list = list() var/list/return_list = list()
if(L.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, return_list) & BLOCK_SUCCESS) //No message; check_shields() handles that if(L.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, return_list) & BLOCK_SUCCESS) //No message; check_shields() handles that
playsound(L, 'sound/weapons/genhit.ogg', 50, 1) playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
return FALSE return FALSE
var/stunpwr = stamforce var/final_stamina_loss_amount = stamina_loss_amount //Our stunning power for the baton
stunpwr = block_calculate_resultant_damage(stunpwr, return_list) var/shoved = FALSE //Did we succeed on knocking our target over?
var/zap_penetration = get_cell_zap_pen() //Find out what kind of cell we have, and calculating the resultant armor pen we get from it
var/zap_block = L.run_armor_check(BODY_ZONE_CHEST, "melee", null, null, zap_penetration) //armor check, including calculation for armor penetration, for our attack
final_stamina_loss_amount = block_calculate_resultant_damage(final_stamina_loss_amount, return_list)
var/obj/item/stock_parts/cell/our_cell = get_cell() var/obj/item/stock_parts/cell/our_cell = get_cell()
if(!our_cell) if(!our_cell)
switch_status(FALSE) switch_status(FALSE)
return FALSE return FALSE
@@ -194,26 +214,31 @@
L.visible_message("<span class='warning'>[user] has prodded [L] with [src]. Luckily it was out of charge.</span>", \ L.visible_message("<span class='warning'>[user] has prodded [L] with [src]. Luckily it was out of charge.</span>", \
"<span class='warning'>[user] has prodded you with [src]. Luckily it was out of charge.</span>") "<span class='warning'>[user] has prodded you with [src]. Luckily it was out of charge.</span>")
return FALSE return FALSE
stunpwr *= round(stuncharge/hitcost, 0.1) final_stamina_loss_amount *= round(stuncharge/hitcost, 0.1)
if(user && !user.UseStaminaBuffer(getweight(user, STAM_COST_BATON_MOB_MULT), warn = TRUE)) if(user && !user.UseStaminaBuffer(getweight(user, STAM_COST_BATON_MOB_MULT), warn = TRUE))
return FALSE return FALSE
if(!disarming) if(shoving && COOLDOWN_FINISHED(src, shove_cooldown) && !HAS_TRAIT(L, TRAIT_IWASBATONED)) //Rightclicking applies a knockdown, but only once every couple of seconds, based on the cooldown_duration var. If they were recently knocked down, they can't be knocked down again by a baton.
if(knockdown) L.DefaultCombatKnockdown(50, override_stamdmg = 0)
L.DefaultCombatKnockdown(50, override_stamdmg = 0) //knockdown L.apply_status_effect(STATUS_EFFECT_TASED_WEAK, status_duration) //Even if they shove themselves up, they're still slowed.
L.adjustStaminaLoss(stunpwr) L.apply_status_effect(STATUS_EFFECT_OFF_BALANCE, status_duration) //They're very likely to drop items if shoved briefly after a knockdown.
else shoved = TRUE
L.drop_all_held_items() //no knockdown/stamina damage, instead disarm. COOLDOWN_START(src, shove_cooldown, cooldown_duration)
ADD_TRAIT(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT) //Prevents swapping to a new baton to avoid the cooldown by just acquiring more batons
addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT), cooldown_duration)
playsound(loc, 'sound/weapons/zapbang.ogg', 50, 1, -1)
else //If we cannot/don't knock down the target, we apply a stagger, which keeps them from just running off
L.apply_status_effect(STATUS_EFFECT_STAGGERED, status_duration)
L.apply_effect(EFFECT_STUTTER, stamforce) L.apply_damage (final_stamina_loss_amount, STAMINA, BODY_ZONE_CHEST, zap_block)
L.apply_effect(EFFECT_STUTTER, stamina_loss_amount)
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK)
if(user) if(user)
L.lastattacker = user.real_name L.set_last_attacker(user)
L.lastattackerckey = user.ckey L.visible_message("<span class='danger'>[user] has [shoved ? "brutally stunned" : "stunned"] [L] with [src]!</span>", \
L.visible_message("<span class='danger'>[user] has [disarming? "disarmed" : "stunned"] [L] with [src]!</span>", \ "<span class='userdanger'>[user] has [shoved ? "brutally stunnned" : "stunned"] you with [src]!</span>")
"<span class='userdanger'>[user] has [disarming? "disarmed" : "stunned"] you with [src]!</span>") log_combat(user, L, shoved ? "stunned and attempted knockdown" : "stunned")
log_combat(user, L, disarming? "disarmed" : "stunned")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
@@ -228,7 +253,7 @@
user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \ user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>") "<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
user.DefaultCombatKnockdown(stamforce*6) user.DefaultCombatKnockdown(stamina_loss_amount*6)
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
deductcharge(hitcost) deductcharge(hitcost)
@@ -306,18 +331,20 @@
w_class = WEIGHT_CLASS_BULKY w_class = WEIGHT_CLASS_BULKY
force = 3 force = 3
throwforce = 5 throwforce = 5
stamforce = 25 stamina_loss_amount = 25
hitcost = 1000 hitcost = 1000
throw_hit_chance = 10 throw_hit_chance = 10
slot_flags = ITEM_SLOT_BACK slot_flags = ITEM_SLOT_BACK
cooldown_duration = 7 SECONDS //It's a little on the weak side
status_duration = 3 //Slows someone for a tiny bit
var/obj/item/assembly/igniter/sparkler var/obj/item/assembly/igniter/sparkler
/obj/item/melee/baton/cattleprod/Initialize() /obj/item/melee/baton/cattleprod/Initialize()
. = ..() . = ..()
sparkler = new (src) sparkler = new (src)
sparkler.activate_cooldown = 5 sparkler.activate_cooldown = 7 //Helps visualize the knockdown
/obj/item/melee/baton/cattleprod/baton_stun() /obj/item/melee/baton/cattleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE)
sparkler?.activate() sparkler?.activate()
. = ..() . = ..()
@@ -344,8 +371,8 @@
/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) /obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(turned_on) if(turned_on)
var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum) var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum)
if(ishuman(hit_atom) && !caught && prob(throw_hit_chance))//if they are a carbon and they didn't catch it if(ishuman(hit_atom) && !caught && prob(throw_hit_chance) && thrownby)//if they are a carbon and they didn't catch it
baton_stun(hit_atom) baton_stun(hit_atom, thrownby, shoving = TRUE)
if(thrownby && !caught) if(thrownby && !caught)
sleep(1) sleep(1)
if(!QDELETED(src)) if(!QDELETED(src))
+2 -2
View File
@@ -6,7 +6,7 @@
item_state = "teleprod" item_state = "teleprod"
slot_flags = null slot_flags = null
/obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user)//handles making things teleport when hit /obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE)//handles making things teleport when hit
. = ..() . = ..()
if(!. || L.anchored) if(!. || L.anchored)
return return
@@ -16,7 +16,7 @@
user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \ user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>") "<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
user.DefaultCombatKnockdown(stamforce * 6) user.DefaultCombatKnockdown(stamina_loss_amount * 6)
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE)) if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE))
deductcharge(hitcost) deductcharge(hitcost)
@@ -19,3 +19,50 @@
desc = "A sturdier card-locked storage unit used for bulky shipments." desc = "A sturdier card-locked storage unit used for bulky shipments."
max_integrity = 500 // Same as crates. max_integrity = 500 // Same as crates.
melee_min_damage = 25 // Idem. melee_min_damage = 25 // Idem.
/obj/structure/closet/secure_closet/goodies/owned
name = "private locker"
desc = "A locker designed to only open for who purchased its contents."
///Account of the person buying the crate if private purchasing.
var/datum/bank_account/buyer_account
///Department of the person buying the crate if buying via the NIRN app.
var/datum/bank_account/department/department_account
///Is the secure crate opened or closed?
var/privacy_lock = TRUE
///Is the crate being bought by a person, or a budget card?
var/department_purchase = FALSE
/obj/structure/closet/secure_closet/goodies/owned/examine(mob/user)
. = ..()
. += "<span class='notice'>It's locked with a privacy lock, and can only be unlocked by the buyer's ID.</span>"
/obj/structure/closet/secure_closet/goodies/owned/Initialize(mapload, datum/bank_account/_buyer_account)
. = ..()
buyer_account = _buyer_account
if(istype(buyer_account, /datum/bank_account/department))
department_purchase = TRUE
department_account = buyer_account
/obj/structure/closet/secure_closet/goodies/owned/togglelock(mob/living/user, silent)
if(privacy_lock)
if(!broken)
var/obj/item/card/id/id_card = user.get_idcard(TRUE)
if(id_card)
if(id_card.registered_account)
if(id_card.registered_account == buyer_account || (department_purchase && (id_card.registered_account?.account_job?.paycheck_department) == (department_account.department_id)))
if(iscarbon(user))
add_fingerprint(user)
locked = !locked
user.visible_message("<span class='notice'>[user] unlocks [src]'s privacy lock.</span>",
"<span class='notice'>You unlock [src]'s privacy lock.</span>")
privacy_lock = FALSE
update_icon()
else if(!silent)
to_chat(user, "<span class='notice'>Bank account does not match with buyer!</span>")
else if(!silent)
to_chat(user, "<span class='notice'>No linked bank account detected!</span>")
else if(!silent)
to_chat(user, "<span class='notice'>No ID detected!</span>")
else if(!silent)
to_chat(user, "<span class='warning'>[src] is broken!</span>")
else ..()
@@ -590,7 +590,7 @@
/obj/effect/mob_spawn/human/pirate /obj/effect/mob_spawn/human/pirate
name = "space pirate sleeper" name = "space pirate sleeper"
desc = "A cryo sleeper smelling faintly of rum." desc = "A cryo sleeper smelling faintly of rum. The sleeper looks unstable. <i>Perhaps the pirate within can be killed with the right tools...</i>"
job_description = "Space Pirate" job_description = "Space Pirate"
random = TRUE random = TRUE
icon = 'icons/obj/machines/sleeper.dmi' icon = 'icons/obj/machines/sleeper.dmi'
@@ -608,6 +608,54 @@
assignedrole = "Space Pirate" assignedrole = "Space Pirate"
var/rank = "Mate" var/rank = "Mate"
/obj/effect/mob_spawn/human/pirate/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
. = ..()
if(.)
return
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
to_chat(user, "<span class='notice'>Your shipmate sails within their dreams for now. Perhaps they may wake up eventually.</span>")
else
to_chat(user, "<span class='notice'>If you want to kill the pirate off, something to pry open the sleeper might be the best way to do it.</span>")
/obj/effect/mob_spawn/human/pirate/attackby(obj/item/W, mob/user, params)
if(W.tool_behaviour == TOOL_CROWBAR && user.a_intent != INTENT_HARM)
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
to_chat(user,"<span class='warning'>Why would you want to do that to your shipmate? That'd kill them.</span>")
return
user.visible_message("<span class='warning'>[user] start to pry open [src]...</span>",
"<span class='notice'>You start to pry open [src]...</span>",
"<span class='italics'>You hear prying...</span>")
W.play_tool_sound(src)
if(do_after(user, 100*W.toolspeed, target = src))
user.visible_message("<span class='warning'>[user] pries open [src], disrupting the sleep of the pirate within and killing them.</span>",
"<span class='notice'>You pry open [src], disrupting the sleep of the pirate within and killing them.</span>",
"<span class='italics'>You hear prying, followed by the death rattling of bones.</span>")
log_game("[key_name(user)] has successfully pried open [src] and disabled a space pirate spawner.")
W.play_tool_sound(src)
playsound(src.loc, 'modular_citadel/sound/voice/scream_skeleton.ogg', 50, 1, 4, 1.2)
if(rank == "Captain")
new /obj/effect/mob_spawn/human/pirate/corpse/captain(get_turf(src))
else
new /obj/effect/mob_spawn/human/pirate/corpse(get_turf(src))
qdel(src)
else
..()
/obj/effect/mob_spawn/human/pirate/corpse //occurs when someone pries a pirate out of their sleeper.
mob_name = "Dead Space Pirate"
death = TRUE
instant = TRUE
random = FALSE
/obj/effect/mob_spawn/human/pirate/corpse/Destroy()
return ..()
/obj/effect/mob_spawn/human/pirate/corpse/captain
rank = "Captain"
mob_name = "Dead Space Pirate Captain"
outfit = /datum/outfit/pirate/space/captain
/obj/effect/mob_spawn/human/pirate/special(mob/living/new_spawn) /obj/effect/mob_spawn/human/pirate/special(mob/living/new_spawn)
new_spawn.fully_replace_character_name(new_spawn.real_name,generate_pirate_name()) new_spawn.fully_replace_character_name(new_spawn.real_name,generate_pirate_name())
new_spawn.mind.add_antag_datum(/datum/antagonist/pirate) new_spawn.mind.add_antag_datum(/datum/antagonist/pirate)
@@ -199,8 +199,7 @@
return TRUE return TRUE
/obj/item/gun_control/attack(mob/living/M, mob/living/user) /obj/item/gun_control/attack(mob/living/M, mob/living/user)
M.lastattacker = user.real_name M.set_last_attacker(user)
M.lastattackerckey = user.ckey
M.attacked_by(src, user) M.attacked_by(src, user)
add_fingerprint(user) add_fingerprint(user)
@@ -67,6 +67,9 @@
to_chat(user, "<span class='notice'>You put [I] in [src].</span>") to_chat(user, "<span class='notice'>You put [I] in [src].</span>")
update_icon() update_icon()
/obj/structure/tank_dispenser/attack_robot(mob/user)
return _try_interact(user)
/obj/structure/tank_dispenser/ui_state(mob/user) /obj/structure/tank_dispenser/ui_state(mob/user)
return GLOB.physical_state return GLOB.physical_state
+1 -1
View File
@@ -554,7 +554,7 @@
if(B.cell) if(B.cell)
if(B.cell.charge > 0 && B.turned_on) if(B.cell.charge > 0 && B.turned_on)
flick("baton_active", src) flick("baton_active", src)
var/stunforce = B.stamforce var/stunforce = B.stamina_loss_amount
user.DefaultCombatKnockdown(stunforce * 2) user.DefaultCombatKnockdown(stunforce * 2)
user.stuttering = stunforce/20 user.stuttering = stunforce/20
B.deductcharge(B.hitcost) B.deductcharge(B.hitcost)
+1 -1
View File
@@ -428,7 +428,7 @@
output += "<option value='[j]'>[j]</option>" output += "<option value='[j]'>[j]</option>"
for(var/j in GLOB.nonhuman_positions) for(var/j in GLOB.nonhuman_positions)
output += "<option value='[j]'>[j]</option>" output += "<option value='[j]'>[j]</option>"
for(var/j in list(ROLE_TRAITOR, ROLE_CHANGELING, ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_WIZARD)) for(var/j in list(ROLE_TRAITOR, ROLE_CHANGELING, ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_WIZARD, ROLE_HERETIC))
output += "<option value='[j]'>[j]</option>" output += "<option value='[j]'>[j]</option>"
output += "</select></td></tr></table>" output += "</select></td></tr></table>"
output += "<b>Reason:<br></b><textarea name='dbbanreason' cols='50'></textarea><br>" output += "<b>Reason:<br></b><textarea name='dbbanreason' cols='50'></textarea><br>"
+2 -2
View File
@@ -93,7 +93,7 @@ GLOBAL_PROTECT(admin_verbs_ban)
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound)) GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound))
GLOBAL_PROTECT(admin_verbs_sounds) GLOBAL_PROTECT(admin_verbs_sounds)
GLOBAL_LIST_INIT(admin_verbs_fun, list( GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/cmd_admin_dress, /client/proc/cmd_select_equipment,
/client/proc/cmd_admin_gib_self, /client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb, /client/proc/drop_bomb,
/client/proc/set_dynex_scale, /client/proc/set_dynex_scale,
@@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/play_local_sound, /client/proc/play_local_sound,
/client/proc/play_sound, /client/proc/play_sound,
/client/proc/set_round_end_sound, /client/proc/set_round_end_sound,
/client/proc/cmd_admin_dress, /client/proc/cmd_select_equipment,
/client/proc/cmd_admin_gib_self, /client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb, /client/proc/drop_bomb,
/client/proc/drop_dynex_bomb, /client/proc/drop_dynex_bomb,
+4
View File
@@ -101,6 +101,10 @@ GLOBAL_VAR(antag_prototypes)
out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]<br>" out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]<br>"
out += "Assigned role: [assigned_role]. <a href='?src=[REF(src)];role_edit=1'>Edit</a><br>" out += "Assigned role: [assigned_role]. <a href='?src=[REF(src)];role_edit=1'>Edit</a><br>"
out += "Faction and special role: <b><font color='red'>[special_role]</font></b><br>" out += "Faction and special role: <b><font color='red'>[special_role]</font></b><br>"
var/datum/component/activity/activity = current.GetComponent(/datum/component/activity)
if(activity)
out += "Activity level: [activity.activity_level]<br>"
out += "Hasn't changed areas in approximately [activity.not_moved_counter] seconds"
var/special_statuses = get_special_statuses() var/special_statuses = get_special_statuses()
if(length(special_statuses)) if(length(special_statuses))
+196
View File
@@ -0,0 +1,196 @@
/client/proc/open_outfit_editor(datum/outfit/target)
var/datum/outfit_editor/ui = new(usr, target)
ui.ui_interact(usr)
#define OUTFIT_EDITOR_NAME "Outfit-O-Tron 9000"
/datum/outfit_editor
var/client/owner
var/dummy_key
var/datum/outfit/drip
/datum/outfit_editor/New(user, datum/outfit/target)
owner = CLIENT_FROM_VAR(user)
if(ispath(target))
drip = new /datum/outfit
drip.copy_from(new target)
else if(istype(target))
drip = target
else
drip = new /datum/outfit
drip.name = "New Outfit"
/datum/outfit_editor/ui_state(mob/user)
return GLOB.admin_state
/datum/outfit_editor/ui_status(mob/user, datum/ui_state/state)
if(QDELETED(drip))
return UI_CLOSE
return ..()
/datum/outfit_editor/ui_close(mob/user)
clear_human_dummy(dummy_key)
qdel(src)
/datum/outfit_editor/proc/init_dummy()
dummy_key = "outfit_editor_[owner]"
generate_dummy_lookalike(dummy_key, owner.mob)
unset_busy_human_dummy(dummy_key)
/datum/outfit_editor/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OutfitEditor", OUTFIT_EDITOR_NAME)
ui.open()
ui.set_autoupdate(FALSE)
/datum/outfit_editor/proc/entry(data)
if(ispath(data, /obj/item))
var/obj/item/item = data
return list(
"path" = item,
"name" = initial(item.name),
"desc" = initial(item.desc),
// at this point initializing the item is probably faster tbh
"sprite" = icon2base64(icon(initial(item.icon), initial(item.icon_state))),
)
return data
/datum/outfit_editor/proc/serialize_outfit()
var/list/outfit_slots = drip.get_json_data()
. = list()
for(var/key in outfit_slots)
var/val = outfit_slots[key]
. += list("[key]" = entry(val))
/datum/outfit_editor/ui_data(mob/user)
var/list/data = list()
data["outfit"] = serialize_outfit()
data["saveable"] = !GLOB.custom_outfits.Find(drip)
if(!dummy_key)
init_dummy()
var/icon/dummysprite = get_flat_human_icon(null,
dummy_key = dummy_key,
showDirs = list(SOUTH),
outfit_override = drip)
data["dummy64"] = icon2base64(dummysprite)
return data
/datum/outfit_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
. = TRUE
var/slot = params["slot"]
switch(action)
if("click")
choose_item(slot)
if("ctrlClick")
choose_any_item(slot)
if("clear")
if(drip.vars.Find(slot))
drip.vars[slot] = null
if("rename")
var/newname = stripped_input(owner, "What do you want to name this outfit?", OUTFIT_EDITOR_NAME)
if(newname)
drip.name = newname
if("save")
GLOB.custom_outfits |= drip
SStgui.update_user_uis(owner.mob)
if("delete")
GLOB.custom_outfits -= drip
SStgui.update_user_uis(owner.mob)
if("vv")
owner.debug_variables(drip)
/datum/outfit_editor/proc/set_item(slot, obj/item/choice)
if(!choice)
return
if(!ispath(choice))
alert(owner, "Invalid item", OUTFIT_EDITOR_NAME, "oh no")
return
if(initial(choice.icon_state) == null) //hacky check copied from experimentor code
var/msg = "Warning: This item's icon_state is null, indicating it is very probably not actually a usable item."
if(alert(owner, msg, OUTFIT_EDITOR_NAME, "Use it anyway", "Cancel") != "Use it anyway")
return
if(drip.vars.Find(slot))
drip.vars[slot] = choice
/datum/outfit_editor/proc/choose_any_item(slot)
var/obj/item/choice = pick_closest_path(FALSE)
if(!choice)
return
set_item(slot, choice)
//this proc will try to give a good selection of items that the user can choose from
//it does *not* give a selection of all items that can fit in a slot because lag;
//most notably the hand and pocket slots because they accept pretty much anything
//also stuff that fits in the belt and back slots are scattered pretty much all over the place
/datum/outfit_editor/proc/choose_item(slot)
var/list/options = list()
switch(slot)
if("head")
options = typesof(/obj/item/clothing/head)
if("glasses")
options = typesof(/obj/item/clothing/glasses)
if("ears")
options = typesof(/obj/item/radio/headset)
if("neck")
options = typesof(/obj/item/clothing/neck)
if("mask")
options = typesof(/obj/item/clothing/mask)
if("uniform")
options = typesof(/obj/item/clothing/under)
if("suit")
options = typesof(/obj/item/clothing/suit)
if("gloves")
options = typesof(/obj/item/clothing/gloves)
if("suit_store")
var/obj/item/clothing/suit/suit = drip.suit
if(suit)
suit = new suit //initial() doesn't like lists
options = suit.allowed
if(!options.len) //nothing will happen, but don't let the user think it's broken
to_chat(owner, "<span class='warning'>No options available for the current suit.</span>")
if("belt")
options = typesof(/obj/item/storage/belt)
if("id")
options = typesof(/obj/item/card/id)
if("l_hand")
choose_any_item(slot)
if("back")
options = typesof(/obj/item/storage/backpack)
if("r_hand")
choose_any_item(slot)
if("l_pocket")
choose_any_item(slot)
if("shoes")
options = typesof(/obj/item/clothing/shoes)
if("r_pocket")
choose_any_item(slot)
if(length(options))
set_item(slot, tgui_input_list(owner, "Choose an item", OUTFIT_EDITOR_NAME, options))
#undef OUTFIT_EDITOR_NAME
+73
View File
@@ -0,0 +1,73 @@
/client/proc/outfit_manager()
set category = "Debug"
set name = "Outfit Manager"
if(!check_rights(R_DEBUG))
return
var/datum/outfit_manager/ui = new(usr)
ui.ui_interact(usr)
/datum/outfit_manager
var/client/owner
/datum/outfit_manager/New(user)
owner = CLIENT_FROM_VAR(user)
/datum/outfit_manager/ui_state(mob/user)
return GLOB.admin_state
/datum/outfit_manager/ui_close(mob/user)
qdel(src)
/datum/outfit_manager/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OutfitManager")
ui.open()
/datum/outfit_manager/proc/entry(datum/outfit/outfit)
var/vv = FALSE
var/datum/outfit/varedit/varoutfit = outfit
if(istype(varoutfit))
vv = length(varoutfit.vv_values)
return list(
"name" = "[outfit.name] [vv ? "(VV)" : ""]",
"ref" = REF(outfit),
)
/datum/outfit_manager/ui_data(mob/user)
var/list/data = list()
var/list/outfits = list()
for(var/datum/outfit/custom_outfit in GLOB.custom_outfits)
outfits += list(entry(custom_outfit))
data["outfits"] = outfits
return data
/datum/outfit_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return
. = TRUE
switch(action)
if("new")
owner.open_outfit_editor(new /datum/outfit)
if("load")
owner.holder.load_outfit(owner.mob)
if("copy")
var/datum/outfit/outfit = tgui_input_list(owner, "Pick an outfit to copy from", "Outfit Manager", subtypesof(/datum/outfit))
if(ispath(outfit))
owner.open_outfit_editor(new outfit)
var/datum/outfit/target_outfit = locate(params["outfit"])
if(!istype(target_outfit))
return
switch(action) //wow we're switching through action again this is horrible optimization smh
if("edit")
owner.open_outfit_editor(target_outfit)
if("save")
owner.holder.save_outfit(owner.mob, target_outfit)
if("delete")
owner.holder.delete_outfit(owner.mob, target_outfit)
+32
View File
@@ -0,0 +1,32 @@
GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
/datum/admins/proc/save_outfit(mob/admin, datum/outfit/O)
O.save_to_file(admin)
SStgui.update_user_uis(admin)
/datum/admins/proc/delete_outfit(mob/admin, datum/outfit/O)
GLOB.custom_outfits -= O
qdel(O)
to_chat(admin,"<span class='notice'>Outfit deleted.</span>")
SStgui.update_user_uis(admin)
/datum/admins/proc/load_outfit(mob/admin)
var/outfit_file = input("Pick outfit json file:", "File") as null|file
if(!outfit_file)
return
var/filedata = file2text(outfit_file)
var/json = json_decode(filedata)
if(!json)
to_chat(admin,"<span class='warning'>JSON decode error.</span>")
return
var/otype = text2path(json["outfit_type"])
if(!ispath(otype,/datum/outfit))
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>")
return
var/datum/outfit/O = new otype
if(!O.load_from(json))
to_chat(admin,"<span class='warning'>Malformed/Outdated file.</span>")
return
GLOB.custom_outfits += O
SStgui.update_user_uis(admin)
+6
View File
@@ -870,6 +870,12 @@
else else
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=changeling;jobban4=[REF(M)]'>Changeling</a></td>" dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=changeling;jobban4=[REF(M)]'>Changeling</a></td>"
//Heretic
if(jobban_isbanned(M, ROLE_HERETIC) || isbanned_dept)
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=heretic;jobban4=[REF(M)]'><font color=red>Heretic</font></a></td>"
else
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=heretic;jobban4=[REF(M)]'>Heretic</a></td>"
//Nuke Operative //Nuke Operative
if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept) if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept)
dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=operative;jobban4=[REF(M)]'><font color=red>Nuke Operative</font></a></td>" dat += "<td width='20%'><a href='?src=[REF(src)];[HrefToken()];jobban3=operative;jobban4=[REF(M)]'><font color=red>Nuke Operative</font></a></td>"
+22 -44
View File
@@ -484,74 +484,52 @@
set name = "Test Areas (ALL)" set name = "Test Areas (ALL)"
cmd_admin_areatest(FALSE) cmd_admin_areatest(FALSE)
/client/proc/cmd_admin_dress(mob/M in GLOB.mob_list)
set category = "Admin.Events"
set name = "Select equipment"
if(!(ishuman(M) || isobserver(M)))
alert("Invalid mob")
return
var/dresscode = robust_dress_shop()
if(!dresscode)
return
var/delete_pocket
var/mob/living/carbon/human/H
if(isobserver(M))
H = M.change_mob_type(/mob/living/carbon/human, null, null, TRUE)
else
H = M
if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No")
delete_pocket = TRUE
SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
for (var/obj/item/I in H.get_equipped_items(delete_pocket))
qdel(I)
if(dresscode != "Naked")
H.equipOutfit(dresscode)
H.regenerate_icons()
log_admin("[key_name(usr)] changed the equipment of [key_name(H)] to [dresscode].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(H)] to [dresscode].</span>")
/client/proc/robust_dress_shop() /client/proc/robust_dress_shop()
var/list/outfits = list("Cancel","Naked","Custom","As Job...")
var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) var/list/baseoutfits = list("Naked","Custom","As Job...", "As Plasmaman...")
var/list/outfits = list()
var/list/paths = subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman)
for(var/path in paths) for(var/path in paths)
var/datum/outfit/O = path //not much to initalize here but whatever var/datum/outfit/O = path //not much to initalize here but whatever
if(initial(O.can_be_admin_equipped)) outfits[initial(O.name)] = path
outfits[initial(O.name)] = path
var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in outfits var/dresscode = input("Select outfit", "Robust quick dress shop") as null|anything in baseoutfits + sortList(outfits)
if (isnull(dresscode)) if (isnull(dresscode))
return return
if (outfits[dresscode]) if (outfits[dresscode])
dresscode = outfits[dresscode] dresscode = outfits[dresscode]
if(dresscode == "Cancel")
return
if (dresscode == "As Job...") if (dresscode == "As Job...")
var/list/job_paths = subtypesof(/datum/outfit/job) var/list/job_paths = subtypesof(/datum/outfit/job)
var/list/job_outfits = list() var/list/job_outfits = list()
for(var/path in job_paths) for(var/path in job_paths)
var/datum/outfit/O = path var/datum/outfit/O = path
if(initial(O.can_be_admin_equipped)) job_outfits[initial(O.name)] = path
job_outfits[initial(O.name)] = path
dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in job_outfits dresscode = input("Select job equipment", "Robust quick dress shop") as null|anything in sortList(job_outfits)
dresscode = job_outfits[dresscode] dresscode = job_outfits[dresscode]
if(isnull(dresscode)) if(isnull(dresscode))
return return
if (dresscode == "As Plasmaman...")
var/list/plasmaman_paths = typesof(/datum/outfit/plasmaman)
var/list/plasmaman_outfits = list()
for(var/path in plasmaman_paths)
var/datum/outfit/O = path
plasmaman_outfits[initial(O.name)] = path
dresscode = input("Select plasmeme equipment", "Robust quick dress shop") as null|anything in sortList(plasmaman_outfits)
dresscode = plasmaman_outfits[dresscode]
if(isnull(dresscode))
return
if (dresscode == "Custom") if (dresscode == "Custom")
var/list/custom_names = list() var/list/custom_names = list()
for(var/datum/outfit/D in GLOB.custom_outfits) for(var/datum/outfit/D in GLOB.custom_outfits)
custom_names[D.name] = D custom_names[D.name] = D
var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in custom_names var/selected_name = input("Select outfit", "Robust quick dress shop") as null|anything in sortList(custom_names)
dresscode = custom_names[selected_name] dresscode = custom_names[selected_name]
if(isnull(dresscode)) if(isnull(dresscode))
return return
+49 -2
View File
@@ -879,8 +879,6 @@ Traitors and the like can also be revived with the previous role mostly intact.
message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].") message_admins("[ADMIN_LOOKUPFLW(usr)] [N.timing ? "activated" : "deactivated"] a nuke at [ADMIN_VERBOSEJMP(N)].")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
/client/proc/create_outfits() /client/proc/create_outfits()
set category = "Debug" set category = "Debug"
set name = "Create Custom Outfit" set name = "Create Custom Outfit"
@@ -1547,6 +1545,55 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
msg += "</UL></BODY></HTML>" msg += "</UL></BODY></HTML>"
src << browse(msg.Join(), "window=Player_playtime_check") src << browse(msg.Join(), "window=Player_playtime_check")
/obj/effect/temp_visual/fireball
icon = 'icons/obj/wizard.dmi'
icon_state = "fireball"
name = "fireball"
desc = "Get out of the way!"
layer = FLY_LAYER
randomdir = FALSE
duration = 9
pixel_z = 270
/obj/effect/temp_visual/fireball/Initialize()
. = ..()
animate(src, pixel_z = 0, time = duration)
/obj/effect/temp_visual/target
icon = 'icons/mob/actions/actions_items.dmi'
icon_state = "sniper_zoom"
layer = BELOW_MOB_LAYER
light_range = 2
duration = 9
/obj/effect/temp_visual/target/ex_act()
return
/obj/effect/temp_visual/target/Initialize(mapload, list/flame_hit)
. = ..()
INVOKE_ASYNC(src, .proc/fall, flame_hit)
/obj/effect/temp_visual/target/proc/fall(list/flame_hit)
var/turf/T = get_turf(src)
playsound(T,'sound/magic/fleshtostone.ogg', 80, 1)
new /obj/effect/temp_visual/fireball(T)
sleep(duration)
if(ismineralturf(T))
var/turf/closed/mineral/M = T
M.gets_drilled()
playsound(T, "explosion", 80, 1)
new /obj/effect/hotspot(T)
T.hotspot_expose(700, 50, 1)
for(var/mob/living/L in T.contents)
if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon))
continue
if(islist(flame_hit) && !flame_hit[L])
L.adjustFireLoss(40)
to_chat(L, "<span class='userdanger'>You're hit by the drake's fire breath!</span>")
flame_hit[L] = TRUE
else
L.adjustFireLoss(10) //if we've already hit them, do way less damage
/datum/admins/proc/cmd_show_exp_panel(client/client_to_check) /datum/admins/proc/cmd_show_exp_panel(client/client_to_check)
if(!check_rights(R_ADMIN)) if(!check_rights(R_ADMIN))
return return
+227
View File
@@ -0,0 +1,227 @@
/client/proc/cmd_select_equipment(mob/target in GLOB.mob_list)
set category = "Admin.Events"
set name = "Select equipment"
var/datum/select_equipment/ui = new(usr, target)
ui.ui_interact(usr)
/*
* This is the datum housing the select equipment UI.
*
* You may notice some oddities about the way outfits are passed to the UI and vice versa here.
* That's because it handles both outfit typepaths (for normal outfits) *and* outfit objects (for custom outfits).
*
* Custom outfits need to be objects as they're created in runtime.
* "Then just handle the normal outfits as objects too and simplify the handling" - you may say.
* There are about 300 outfit types at the time of writing this. Initializing all of these to objects would be a huge waste.
*
*/
/datum/select_equipment
var/client/user
var/mob/target_mob
var/dummy_key
//static list to share all the outfit typepaths between all instances of this datum.
var/static/list/cached_outfits
//a typepath if the selected outfit is a normal outfit;
//an object if the selected outfit is a custom outfit
var/datum/outfit/selected_outfit = /datum/outfit
//serializable string for the UI to keep track of which outfit is selected
var/selected_identifier = "/datum/outfit"
/datum/select_equipment/New(_user, mob/target)
user = CLIENT_FROM_VAR(_user)
if(!ishuman(target) && !isobserver(target))
alert("Invalid mob")
return
target_mob = target
/datum/select_equipment/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SelectEquipment", "Select Equipment")
ui.open()
ui.set_autoupdate(FALSE)
/datum/select_equipment/ui_state(mob/user)
return GLOB.admin_state
/datum/select_equipment/ui_status(mob/user, datum/ui_state/state)
if(QDELETED(target_mob))
return UI_CLOSE
return ..()
/datum/select_equipment/ui_close(mob/user)
clear_human_dummy(dummy_key)
qdel(src)
/datum/select_equipment/proc/init_dummy()
dummy_key = "selectequipmentUI_[target_mob]"
generate_dummy_lookalike(dummy_key, target_mob)
unset_busy_human_dummy(dummy_key)
return
/**
* Packs up data about an outfit as an assoc list to send to the UI as an outfit entry.
*
* Args:
* * category (string) - The tab it will be under
*
* * identifier (typepath or ref) - This will sent this back to ui_act to preview or spawn in an outfit.
* * Must be unique between all entries.
*
* * name (string) - Will be the text on the button
*
* * priority (bool)(optional) - If True, the UI will sort the entry to the top, right below favorites.
*
* * custom_entry (bool)(optional) - Send the identifier with a "ref" keyword instead of "path",
* * for the UI to tell apart custom outfits from normal ones.
*
* Returns (list) An outfit entry
*/
/datum/select_equipment/proc/outfit_entry(category, identifier, name, priority=FALSE, custom_entry=FALSE)
if(custom_entry)
return list("category" = category, "ref" = identifier, "name" = name, "priority" = priority)
return list("category" = category, "path" = identifier, "name" = name, "priority" = priority)
/datum/select_equipment/proc/make_outfit_entries(category="General", list/outfit_list)
var/list/entries = list()
for(var/path as anything in outfit_list)
var/datum/outfit/outfit = path
entries += list(outfit_entry(category, path, initial(outfit.name)))
return entries
//GLOB.custom_outfits lists outfit *objects* so we'll need to do some custom handling for it
/datum/select_equipment/proc/make_custom_outfit_entries(list/outfit_list)
var/list/entries = list()
for(var/datum/outfit/outfit as anything in outfit_list)
entries += list(outfit_entry("Custom", REF(outfit), outfit.name, custom_entry=TRUE)) //it's either this or special handling on the UI side
return entries
/datum/select_equipment/ui_data(mob/user)
var/list/data = list()
if(!dummy_key)
init_dummy()
var/icon/dummysprite = get_flat_human_icon(null,
dummy_key = dummy_key,
outfit_override = selected_outfit,
no_anim = TRUE)
data["icon64"] = icon2base64(dummysprite)
data["name"] = target_mob
var/datum/preferences/prefs = user?.client?.prefs
data["favorites"] = list()
if(prefs)
data["favorites"] = prefs.favorite_outfits
var/list/custom
custom += make_custom_outfit_entries(GLOB.custom_outfits)
data["custom_outfits"] = custom
data["current_outfit"] = selected_identifier
return data
/datum/select_equipment/ui_static_data(mob/user)
var/list/data = list()
if(!cached_outfits)
cached_outfits = list()
cached_outfits += list(outfit_entry("General", /datum/outfit, "Naked", priority=TRUE))
cached_outfits += make_outfit_entries("General", subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman))
cached_outfits += make_outfit_entries("Jobs", typesof(/datum/outfit/job))
cached_outfits += make_outfit_entries("Plasmamen Outfits", typesof(/datum/outfit/plasmaman))
data["outfits"] = cached_outfits
return data
/datum/select_equipment/proc/resolve_outfit(text)
var/path = text2path(text)
if(ispath(path, /datum/outfit))
return path
else //don't bail yet - could be a custom outfit
var/datum/outfit/custom_outfit = locate(text)
if(istype(custom_outfit))
return custom_outfit
/datum/select_equipment/ui_act(action, params)
if(..())
return
. = TRUE
switch(action)
if("preview")
var/datum/outfit/new_outfit = resolve_outfit(params["path"])
if(ispath(new_outfit)) //got a typepath - that means we're dealing with a normal outfit
selected_identifier = new_outfit //these are keyed by type
//by the way, no, they can't be keyed by name because many of them have duplicate names
else if(istype(new_outfit)) //got an initialized object - means it's a custom outfit
selected_identifier = REF(new_outfit) //and the outfit will be keyed by its ref (cause its type will always be /datum/outfit)
else //we got nothing and should bail
return
selected_outfit = new_outfit
if("applyoutfit")
var/datum/outfit/new_outfit = resolve_outfit(params["path"])
if(new_outfit && ispath(new_outfit)) //initialize it
new_outfit = new new_outfit
if(!istype(new_outfit))
return
user.admin_apply_outfit(target_mob, new_outfit)
if("customoutfit")
user.outfit_manager()
if("togglefavorite")
var/datum/outfit/outfit_path = resolve_outfit(params["path"])
if(!ispath(outfit_path)) //we do *not* want custom outfits (i.e objects) here, they're not even persistent
return
if(user.prefs.favorite_outfits.Find(outfit_path)) //already there, remove it
user.prefs.favorite_outfits -= outfit_path
else //not there, add it
user.prefs.favorite_outfits += outfit_path
user.prefs.save_preferences()
/client/proc/admin_apply_outfit(mob/target, dresscode)
if(!ishuman(target) && !isobserver(target))
alert("Invalid mob")
return
if(!dresscode)
return
var/delete_pocket
var/mob/living/carbon/human/human_target
if(isobserver(target))
human_target = target.change_mob_type(/mob/living/carbon/human, delete_old_mob = TRUE)
else
human_target = target
if(human_target.l_store || human_target.r_store || human_target.s_store) //saves a lot of time for admins and coders alike
if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No")
delete_pocket = TRUE
SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
for(var/obj/item/item in human_target.get_equipped_items(delete_pocket))
qdel(item)
if(dresscode != "Naked")
human_target.equipOutfit(dresscode)
human_target.regenerate_icons()
log_admin("[key_name(usr)] changed the equipment of [key_name(human_target)] to [dresscode].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(human_target)] to [dresscode].</span>")
return dresscode
@@ -111,6 +111,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/datum/skill_modifier/job/M = GLOB.skill_modifiers[GET_SKILL_MOD_ID(A, type)] var/datum/skill_modifier/job/M = GLOB.skill_modifiers[GET_SKILL_MOD_ID(A, type)]
if(istype(M)) if(istype(M))
M.name = "[name] Training" M.name = "[name] Training"
owner.AddComponent(/datum/component/activity)
SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src) SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src)
/datum/antagonist/proc/is_banned(mob/M) /datum/antagonist/proc/is_banned(mob/M)
@@ -141,6 +142,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/datum/team/team = get_team() var/datum/team/team = get_team()
if(team) if(team)
team.remove_member(owner) team.remove_member(owner)
// we don't remove the activity component on purpose--no real point to it
qdel(src) qdel(src)
/datum/antagonist/proc/greet() /datum/antagonist/proc/greet()
@@ -511,8 +511,7 @@
/obj/item/abductor/baton/proc/StunAttack(mob/living/L,mob/living/user) /obj/item/abductor/baton/proc/StunAttack(mob/living/L,mob/living/user)
L.lastattacker = user.real_name L.set_last_attacker(user)
L.lastattackerckey = user.ckey
L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement
L.DefaultCombatKnockdown(140) L.DefaultCombatKnockdown(140)
+1 -2
View File
@@ -385,8 +385,7 @@
qdel(src) qdel(src)
return return
log_combat(user, M, "used a cult spell on", source.name, "") log_combat(user, M, "used a cult spell on", source.name, "")
M.lastattacker = user.real_name M.set_last_attacker(user)
M.lastattackerckey = user.ckey
/obj/item/melee/blood_magic/afterattack(atom/target, mob/living/carbon/user, proximity) /obj/item/melee/blood_magic/afterattack(atom/target, mob/living/carbon/user, proximity)
. = ..() . = ..()
+14 -3
View File
@@ -1,6 +1,6 @@
//Revenants: based off of wraiths from Goon //Revenants: based off of wraiths from Goon
//"Ghosts" that are invisible and move like ghosts, cannot take damage while invisible //"Ghosts" that are invisible and move like ghosts, cannot take damage while invisible
//Don't hear deadchat and are NOT normal ghosts //Can hear deadchat, but are NOT normal ghosts and do NOT have x-ray vision
//Admin-spawn or random event //Admin-spawn or random event
#define INVISIBILITY_REVENANT 50 #define INVISIBILITY_REVENANT 50
@@ -63,6 +63,7 @@
var/essence_regenerating = TRUE //If the revenant regenerates essence or not var/essence_regenerating = TRUE //If the revenant regenerates essence or not
var/essence_regen_amount = 5 //How much essence regenerates var/essence_regen_amount = 5 //How much essence regenerates
var/essence_accumulated = 0 //How much essence the revenant has stolen var/essence_accumulated = 0 //How much essence the revenant has stolen
var/essence_excess = 0 //How much stolen essence available for unlocks
var/revealed = FALSE //If the revenant can take damage from normal sources. var/revealed = FALSE //If the revenant can take damage from normal sources.
var/unreveal_time = 0 //How long the revenant is revealed for, is about 2 seconds times this var. var/unreveal_time = 0 //How long the revenant is revealed for, is about 2 seconds times this var.
var/unstun_time = 0 //How long the revenant is stunned for, is about 2 seconds times this var. var/unstun_time = 0 //How long the revenant is stunned for, is about 2 seconds times this var.
@@ -76,6 +77,7 @@
/mob/living/simple_animal/revenant/Initialize(mapload) /mob/living/simple_animal/revenant/Initialize(mapload)
. = ..() . = ..()
ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT)
AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null)) AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null))
AddSpell(new /obj/effect/proc_holder/spell/targeted/telepathy/revenant(null)) AddSpell(new /obj/effect/proc_holder/spell/targeted/telepathy/revenant(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null)) AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null))
@@ -138,6 +140,7 @@
. = ..() . = ..()
. += "Current essence: [essence]/[essence_regen_cap]E" . += "Current essence: [essence]/[essence_regen_cap]E"
. += "Stolen essence: [essence_accumulated]E" . += "Stolen essence: [essence_accumulated]E"
. += "Unused stolen essence: [essence_excess]E)"
. += "Stolen perfect souls: [perfectsouls]" . += "Stolen perfect souls: [perfectsouls]"
/mob/living/simple_animal/revenant/update_health_hud() /mob/living/simple_animal/revenant/update_health_hud()
@@ -304,16 +307,24 @@
return FALSE return FALSE
return TRUE return TRUE
/mob/living/simple_animal/revenant/proc/unlock(essence_cost)
if(essence_excess < essence_cost)
return FALSE
essence_excess -= essence_cost
update_action_buttons_icon()
return TRUE
/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null) /mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null)
if(!src) if(!src)
return return
if(essence + essence_amt <= 0) if(essence + essence_amt < 0)
return return
essence = max(0, essence+essence_amt) essence = max(0, essence+essence_amt)
update_action_buttons_icon()
update_health_hud() update_health_hud()
if(essence_amt > 0) if(essence_amt > 0)
essence_accumulated = max(0, essence_accumulated+essence_amt) essence_accumulated = max(0, essence_accumulated+essence_amt)
essence_excess = max(0, essence_excess+essence_amt)
update_action_buttons_icon()
if(!silent) if(!silent)
if(essence_amt > 0) if(essence_amt > 0)
to_chat(src, "<span class='revennotice'>Gained [essence_amt]E[source ? " from [source]":""].</span>") to_chat(src, "<span class='revennotice'>Gained [essence_amt]E[source ? " from [source]":""].</span>")
@@ -61,6 +61,8 @@
to_chat(src, "<span class='revenminor'>You begin siphoning essence from [target]'s soul.</span>") to_chat(src, "<span class='revenminor'>You begin siphoning essence from [target]'s soul.</span>")
if(target.stat != DEAD) if(target.stat != DEAD)
to_chat(target, "<span class='warning'>You feel a horribly unpleasant draining sensation as your grip on life weakens...</span>") to_chat(target, "<span class='warning'>You feel a horribly unpleasant draining sensation as your grip on life weakens...</span>")
if(target.stat == SOFT_CRIT)
target.Stun(46)
reveal(46) reveal(46)
stun(46) stun(46)
target.visible_message("<span class='warning'>[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.</span>") target.visible_message("<span class='warning'>[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.</span>")
@@ -144,7 +146,7 @@
if(user.inhibited) if(user.inhibited)
return FALSE return FALSE
if(locked) if(locked)
if(user.essence <= unlock_amount) if(user.essence_excess <= unlock_amount)
return FALSE return FALSE
if(user.essence <= cast_amount) if(user.essence <= cast_amount)
return FALSE return FALSE
@@ -158,7 +160,7 @@
locked = FALSE locked = FALSE
return TRUE return TRUE
if(locked) if(locked)
if(!user.castcheck(-unlock_amount)) if(!user.unlock(unlock_amount))
charge_counter = charge_max charge_counter = charge_max
return FALSE return FALSE
name = "[initial(name)] ([cast_amount]E)" name = "[initial(name)] ([cast_amount]E)"
@@ -185,6 +187,7 @@
range = 5 range = 5
stun = 30 stun = 30
cast_amount = 40 cast_amount = 40
unlock_amount = 25
var/shock_range = 2 var/shock_range = 2
var/shock_damage = 15 var/shock_damage = 15
action_icon_state = "overload_lights" action_icon_state = "overload_lights"
@@ -197,7 +200,7 @@
/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user) /obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user)
for(var/obj/machinery/light/L in T) for(var/obj/machinery/light/L in T)
if(!L.on) if(!L.on)
return continue
L.visible_message("<span class='warning'><b>\The [L] suddenly flares brightly and begins to spark!</span>") L.visible_message("<span class='warning'><b>\The [L] suddenly flares brightly and begins to spark!</span>")
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(4, 0, L) s.set_up(4, 0, L)
@@ -226,7 +229,7 @@
range = 4 range = 4
stun = 20 stun = 20
reveal = 40 reveal = 40
unlock_amount = 75 unlock_amount = 10
cast_amount = 30 cast_amount = 30
action_icon_state = "defile" action_icon_state = "defile"
@@ -277,7 +280,7 @@
charge_max = 200 charge_max = 200
range = 4 range = 4
cast_amount = 60 cast_amount = 60
unlock_amount = 200 unlock_amount = 125
action_icon_state = "malfunction" action_icon_state = "malfunction"
//A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. //A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you.
@@ -324,7 +327,7 @@
charge_max = 200 charge_max = 200
range = 3 range = 3
cast_amount = 50 cast_amount = 50
unlock_amount = 200 unlock_amount = 75
action_icon_state = "blight" action_icon_state = "blight"
/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/cast(list/targets, mob/living/simple_animal/revenant/user = usr) /obj/effect/proc_holder/spell/aoe_turf/revenant/blight/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
@@ -20,7 +20,7 @@
continue continue
if(!SSpersistence.IsValidDebrisLocation(C.loc, allowed_turf_typecache, allowed_z_cache, C.type, FALSE)) if(!SSpersistence.IsValidDebrisLocation(C.loc, allowed_turf_typecache, allowed_z_cache, C.type, FALSE))
continue continue
weight += 0.05 weight += 0.03
return ..() return ..()
/datum/round_event/ghost_role/slaughter /datum/round_event/ghost_role/slaughter
@@ -1,30 +1,7 @@
/datum/traitor_class/human/assassin /datum/traitor_class/human/subterfuge/assassin
name = "Donk Co Operative" name = "Donk Co Operative"
employer = "Donk Corporation" employer = "Donk Corporation"
weight = 0 weight = 6
chaos = 1 chaos = 1
threat = 2 threat = 2
assassin_prob = 70
/datum/traitor_class/human/assassin/forge_single_objective(datum/antagonist/traitor/T)
.=1
var/permakill_prob = 20
var/datum/game_mode/dynamic/mode
if(istype(SSticker.mode,/datum/game_mode/dynamic))
mode = SSticker.mode
permakill_prob = max(0,mode.threat_level-50)
var/list/active_ais = active_ais()
if(active_ais.len && prob(100/GLOB.joined_player_list.len))
var/datum/objective/destroy/destroy_objective = new
destroy_objective.owner = T.owner
destroy_objective.find_target()
T.add_objective(destroy_objective)
else if(prob(permakill_prob))
var/datum/objective/assassinate/kill_objective = new
kill_objective.owner = T.owner
kill_objective.find_target()
T.add_objective(kill_objective)
else
var/datum/objective/assassinate/once/kill_objective = new
kill_objective.owner = T.owner
kill_objective.find_target()
T.add_objective(kill_objective)
@@ -1,12 +1,12 @@
/datum/traitor_class/human/freeform /datum/traitor_class/human/freeform
name = "Waffle Co Agent" name = "Waffle Co Agent"
employer = "Waffle Company" employer = "Waffle Company"
weight = 0 // should not spawn in unless admins bus something in the traitor panel with setting traitor classes weight = 5
chaos = 0 chaos = 0
/datum/traitor_class/human/freeform/forge_objectives(datum/antagonist/traitor/T) /datum/traitor_class/human/freeform/forge_objectives(datum/antagonist/traitor/T)
var/datum/objective/escape/O = new var/datum/objective/freedom/O = new
O.explanation_text = "You have no explicit goals! While we don't approve of mindless slaughter, you may antagonize nanotrasen any way you wish! Make sure to escape alive and not in custody, though!" O.explanation_text = "You have no explicit goals! While we don't approve of mindless slaughter, you may antagonize nanotrasen any way you wish! Don't get captured or killed, but if you've done nothing, you'll be in trouble!"
O.owner = T.owner O.owner = T.owner
T.add_objective(O) T.add_objective(O)
return return
@@ -13,8 +13,12 @@
T.assign_exchange_role(SSticker.mode.exchange_blue) T.assign_exchange_role(SSticker.mode.exchange_blue)
objective_count += 1 //Exchange counts towards number of objectives objective_count += 1 //Exchange counts towards number of objectives
var/toa = CONFIG_GET(number/traitor_objectives_amount) var/toa = CONFIG_GET(number/traitor_objectives_amount)
var/attempts = 0
for(var/i = objective_count, i < toa, i++) for(var/i = objective_count, i < toa, i++)
forge_single_objective(T) var/success = FALSE
while(!success && attempts < max(toa*10, 100))
success = forge_single_objective(T)
attempts += 1
if(!(locate(/datum/objective/escape) in T.objectives)) if(!(locate(/datum/objective/escape) in T.objectives))
var/datum/objective/escape/escape_objective = new var/datum/objective/escape/escape_objective = new
escape_objective.owner = T.owner escape_objective.owner = T.owner
@@ -1,12 +1,11 @@
/datum/traitor_class/human/subterfuge /datum/traitor_class/human/subterfuge
name = "MI13 Operative" name = "MI13 Operative"
employer = "MI13" employer = "MI13"
weight = 36 weight = 25
chaos = -5 chaos = -5
var/assassin_prob = 25
/datum/traitor_class/human/subterfuge/forge_single_objective(datum/antagonist/traitor/T) /datum/traitor_class/human/subterfuge/forge_single_objective(datum/antagonist/traitor/T)
.=1
var/assassin_prob = 30
var/datum/game_mode/dynamic/mode var/datum/game_mode/dynamic/mode
if(istype(SSticker.mode,/datum/game_mode/dynamic)) if(istype(SSticker.mode,/datum/game_mode/dynamic))
mode = SSticker.mode mode = SSticker.mode
@@ -16,24 +15,31 @@
kill_objective.owner = T.owner kill_objective.owner = T.owner
kill_objective.find_target() kill_objective.find_target()
T.add_objective(kill_objective) T.add_objective(kill_objective)
return TRUE
else else
var/list/weights = list() var/list/weights = list()
weights["sabo"] = length(subtypesof(/datum/sabotage_objective)) var/datum/objective/sabotage/sabotage_objective = new
weights["steal"] = length(subtypesof(/datum/objective_item/steal)) sabotage_objective.owner = T.owner
if(sabotage_objective.find_target())
weights["sabo"] = length(subtypesof(/datum/objective_item/steal))
var/datum/objective/steal/steal_objective = new
steal_objective.owner = T.owner
if(steal_objective.find_target())
weights["steal"] = length(subtypesof(/datum/objective_item/steal))
weights["download"] = !(locate(/datum/objective/download) in T.objectives || (T.owner.assigned_role in list("Research Director", "Scientist", "Roboticist"))) weights["download"] = !(locate(/datum/objective/download) in T.objectives || (T.owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
switch(pickweight(weights)) switch(pickweight(weights))
if("sabo") if("sabo")
var/datum/objective/sabotage/sabotage_objective = new
sabotage_objective.owner = T.owner
sabotage_objective.find_target()
T.add_objective(sabotage_objective) T.add_objective(sabotage_objective)
qdel(steal_objective)
return TRUE
if("steal") if("steal")
var/datum/objective/steal/steal_objective = new
steal_objective.owner = T.owner
steal_objective.find_target()
T.add_objective(steal_objective) T.add_objective(steal_objective)
qdel(sabotage_objective)
return TRUE
if("download") if("download")
var/datum/objective/download/download_objective = new var/datum/objective/download/download_objective = new
download_objective.owner = T.owner download_objective.owner = T.owner
download_objective.gen_amount_goal() download_objective.gen_amount_goal()
T.add_objective(download_objective) T.add_objective(download_objective)
return TRUE
return FALSE
@@ -7,7 +7,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
var/chaos = 0 var/chaos = 0
var/threat = 0 var/threat = 0
var/TC = 20 var/TC = 20
/// Minimum players for this to randomly roll via get_random_traitor_class(). var/processing = FALSE
/// Minimum players for this to randomly roll via get_random_traitor_kind().
var/min_players = 0 var/min_players = 0
var/list/uplink_filters var/list/uplink_filters
@@ -43,4 +44,6 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T) /datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T)
// Any effects that need to be cleaned up if traitor class is being swapped. // Any effects that need to be cleaned up if traitor class is being swapped.
/datum/traitor_class/proc/on_process(/datum/antagonist/traitor/T)
// only for processing traitor classes; runs once an SSprocessing tick
@@ -26,6 +26,8 @@
if(traitor_kind) if(traitor_kind)
traitor_kind.remove_innate_effects(owner.current) traitor_kind.remove_innate_effects(owner.current)
traitor_kind.clean_up_traitor(src) traitor_kind.clean_up_traitor(src)
if(traitor_kind.processing)
STOP_PROCESSING(SSprocessing, src)
swap_from_old = TRUE swap_from_old = TRUE
traitor_kind = GLOB.traitor_classes[kind] traitor_kind = GLOB.traitor_classes[kind]
traitor_kind.apply_innate_effects(owner.current) traitor_kind.apply_innate_effects(owner.current)
@@ -33,11 +35,16 @@
for(var/O in objectives) for(var/O in objectives)
qdel(O) qdel(O)
traitor_kind.forge_objectives(src) traitor_kind.forge_objectives(src)
if(traitor_kind.processing)
START_PROCESSING(SSprocessing, src)
if(swap_from_old) if(swap_from_old)
traitor_kind.finalize_traitor(src) traitor_kind.finalize_traitor(src)
traitor_kind.greet(src) traitor_kind.greet(src)
owner.announce_objectives() owner.announce_objectives()
/datum/antagonist/traitor/process()
traitor_kind.on_process(src)
/proc/get_random_traitor_kind(var/list/blacklist = list()) /proc/get_random_traitor_kind(var/list/blacklist = list())
var/chaos_weight = 0 var/chaos_weight = 0
if(istype(SSticker.mode,/datum/game_mode/dynamic)) if(istype(SSticker.mode,/datum/game_mode/dynamic))
@@ -0,0 +1,86 @@
GLOBAL_LIST_EMPTY(bluespace_pipe_networks)
/obj/machinery/atmospherics/pipe/bluespace
name = "bluespace pipe"
desc = "Transmits gas across large distances of space. Developed using bluespace technology."
icon = 'icons/obj/atmospherics/pipes/bluespace.dmi'
icon_state = "map"
pipe_state = "bluespace"
dir = SOUTH
initialize_directions = SOUTH
device_type = UNARY
can_buckle = FALSE
construction_type = /obj/item/pipe/bluespace
var/bluespace_network_name
/obj/machinery/atmospherics/pipe/bluespace/New()
icon_state = "pipe"
if(bluespace_network_name) // in case someone maps one in for some reason
if(!GLOB.bluespace_pipe_networks[bluespace_network_name])
GLOB.bluespace_pipe_networks[bluespace_network_name] = list()
GLOB.bluespace_pipe_networks[bluespace_network_name] |= src
..()
/obj/machinery/atmospherics/pipe/bluespace/on_construction()
. = ..()
if(bluespace_network_name)
if(!GLOB.bluespace_pipe_networks[bluespace_network_name])
GLOB.bluespace_pipe_networks[bluespace_network_name] = list()
GLOB.bluespace_pipe_networks[bluespace_network_name] |= src
/obj/machinery/atmospherics/pipe/bluespace/Destroy()
if(GLOB.bluespace_pipe_networks[bluespace_network_name])
GLOB.bluespace_pipe_networks[bluespace_network_name] -= src
for(var/p in GLOB.bluespace_pipe_networks[bluespace_network_name])
var/obj/machinery/atmospherics/pipe/bluespace/P = p
QDEL_NULL(P.parent)
P.build_network()
return ..()
/obj/machinery/atmospherics/pipe/bluespace/examine(user)
. = ..()
. += "<span class='notice'>This one is connected to the \"[html_encode(bluespace_network_name)]\" network.</span>"
/obj/machinery/atmospherics/pipe/bluespace/SetInitDirections()
initialize_directions = dir
/obj/machinery/atmospherics/pipe/bluespace/pipeline_expansion()
return ..() + GLOB.bluespace_pipe_networks[bluespace_network_name] - src
/obj/machinery/atmospherics/pipe/bluespace/hide()
update_icon()
/obj/machinery/atmospherics/pipe/bluespace/update_icon(showpipe)
underlays.Cut()
var/turf/T = loc
if(level == 2 || !T.intact)
showpipe = TRUE
plane = GAME_PLANE
else
showpipe = FALSE
plane = FLOOR_PLANE
if(!showpipe)
return //no need to update the pipes if they aren't showing
var/connected = 0 //Direction bitset
for(var/i in 1 to device_type) //adds intact pieces
if(nodes[i])
var/obj/machinery/atmospherics/node = nodes[i]
var/image/img = get_pipe_underlay("pipe_intact", get_dir(src, node), node.pipe_color)
underlays += img
connected |= img.dir
for(var/direction in GLOB.cardinals)
if((initialize_directions & direction) && !(connected & direction))
underlays += get_pipe_underlay("pipe_exposed", direction)
/obj/machinery/atmospherics/pipe/bluespace/paint()
return FALSE
/obj/machinery/atmospherics/pipe/bluespace/proc/get_pipe_underlay(state, dir, color = null)
if(color)
. = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', state, dir, color)
else
. = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', state, dir)
@@ -48,3 +48,12 @@
price_max = 300 price_max = 300
stock_max = 3 stock_max = 3
availability_prob = 40 availability_prob = 40
/datum/blackmarket_item/weapon/derringer
name = ".38 Derringer"
desc = "Compact safety! Now at a premium!"
item = /obj/item/gun/ballistic/derringer
price_min = 500
price_max = 1500
stock_max = 3
availability_prob = 30
+5 -1
View File
@@ -15,13 +15,17 @@
var/special_enabled = FALSE var/special_enabled = FALSE
var/DropPodOnly = FALSE //only usable by the Bluespace Drop Pod via the express cargo console var/DropPodOnly = FALSE //only usable by the Bluespace Drop Pod via the express cargo console
var/admin_spawned = FALSE //Can only an admin spawn this crate? var/admin_spawned = FALSE //Can only an admin spawn this crate?
// this might be all in all unnecessary with current code if some changes are made
var/goody = PACK_GOODY_NONE //Small items can be grouped into a single crate.They also come in a closet/lockbox instead of a full crate, so the 700 min doesn't apply var/goody = PACK_GOODY_NONE //Small items can be grouped into a single crate.They also come in a closet/lockbox instead of a full crate, so the 700 min doesn't apply
var/can_private_buy = TRUE //Can it be purchased privately by each crewmember? var/can_private_buy = TRUE //Can it be purchased privately by each crewmember?
/datum/supply_pack/proc/generate(atom/A, datum/bank_account/paying_account) /datum/supply_pack/proc/generate(atom/A, datum/bank_account/paying_account)
var/obj/structure/closet/crate/C var/obj/structure/closet/crate/C
if(paying_account) if(paying_account)
C = new /obj/structure/closet/crate/secure/owned(A, paying_account) if(ispath(crate_type, /obj/structure/closet/secure_closet/goodies)) // lets ensure private orders don't come in crates when the original one comes in lockers
C = new /obj/structure/closet/secure_closet/goodies/owned(A, paying_account) // that would lead to infinite money exploits
else
C = new /obj/structure/closet/crate/secure/owned(A, paying_account)
C.name = "[crate_name] - Purchased by [paying_account.account_holder]" C.name = "[crate_name] - Purchased by [paying_account.account_holder]"
else else
C = new crate_type(A) C = new crate_type(A)
+32
View File
@@ -232,3 +232,35 @@
desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids." desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
cost = 1500 cost = 1500
contains = list(/obj/item/gun/energy/laser/hellgun) contains = list(/obj/item/gun/energy/laser/hellgun)
/datum/supply_pack/security/armory/derringerclassic
name = "Holdout Crate"
crate_name = "dented crate"
desc = "Hey kid.. c'mere. Boss says we need to offload these, to any buyer, no questions asked. You pay us, we give you three of these guns, no strings attached. Locks are to ensure they get to PAYING customers."
cost = 2000
contraband = TRUE
can_private_buy = TRUE
contains = list(/obj/item/storage/fancy/cigarettes/derringer/smuggled,
/obj/item/storage/fancy/cigarettes/derringer/smuggled,
/obj/item/storage/fancy/cigarettes/derringer/smuggled,
/obj/item/storage/wallet)
/datum/supply_pack/security/armory/esoteric_arms
name = "Esoteric Armory Shipment"
desc = "Well.. you're an agent of taste, I can tell that much. For the right price.. we could see our way clear to send you one of our more... unique weapons."
hidden = TRUE
cost = 10000
can_private_buy = TRUE
crate_name = "dusty crate"
var/num_contained = 1
contains = list(/obj/item/gun/ballistic/shotgun/leveraction,
/obj/item/storage/fancy/cigarettes/derringer/gold,
/obj/item/gun/ballistic/revolver/nagant,
/obj/item/gun/ballistic/automatic/pistol/APS,
/obj/item/gun/ballistic/revolver/golden)
/datum/supply_pack/security/armory/esoteric_arms/fill(obj/structure/closet/crate/C)
var/list/L = contains.Copy()
for(var/i in 1 to num_contained)
var/item = pick_n_take(L)
new item(C)
+1
View File
@@ -81,3 +81,4 @@
desc = "Contains one standard epinephrine medipen and one standard emergency first-aid kit medipen. For when you want to prepare for the worst." desc = "Contains one standard epinephrine medipen and one standard emergency first-aid kit medipen. For when you want to prepare for the worst."
cost = 500 cost = 500
contains = list(/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/hypospray/medipen/ekit) contains = list(/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/hypospray/medipen/ekit)
+8 -8
View File
@@ -14,56 +14,56 @@
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/datum/supply_pack/materials/cardboard50 /datum/supply_pack/materials/cardboard50
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Cardboard Sheets" name = "50 Cardboard Sheets"
desc = "Create a bunch of boxes." desc = "Create a bunch of boxes."
cost = 300 //thrice their export value cost = 300 //thrice their export value
contains = list(/obj/item/stack/sheet/cardboard/fifty) contains = list(/obj/item/stack/sheet/cardboard/fifty)
/datum/supply_pack/materials/glass50 /datum/supply_pack/materials/glass50
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Glass Sheets" name = "50 Glass Sheets"
desc = "Let some nice light in with fifty glass sheets!" desc = "Let some nice light in with fifty glass sheets!"
cost = 300 //double their export value cost = 300 //double their export value
contains = list(/obj/item/stack/sheet/glass/fifty) contains = list(/obj/item/stack/sheet/glass/fifty)
/datum/supply_pack/materials/metal50 /datum/supply_pack/materials/metal50
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Metal Sheets" name = "50 Metal Sheets"
desc = "Any construction project begins with a good stack of fifty metal sheets!" desc = "Any construction project begins with a good stack of fifty metal sheets!"
cost = 300 //double their export value cost = 300 //double their export value
contains = list(/obj/item/stack/sheet/metal/fifty) contains = list(/obj/item/stack/sheet/metal/fifty)
/datum/supply_pack/materials/plasteel20 /datum/supply_pack/materials/plasteel20
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "20 Plasteel Sheets" name = "20 Plasteel Sheets"
desc = "Reinforce the station's integrity with twenty plasteel sheets!" desc = "Reinforce the station's integrity with twenty plasteel sheets!"
cost = 4000 cost = 4000
contains = list(/obj/item/stack/sheet/plasteel/twenty) contains = list(/obj/item/stack/sheet/plasteel/twenty)
/datum/supply_pack/materials/plastic50 /datum/supply_pack/materials/plastic50
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Plastic Sheets" name = "50 Plastic Sheets"
desc = "Build a limitless amount of toys with fifty plastic sheets!" desc = "Build a limitless amount of toys with fifty plastic sheets!"
cost = 200 // double their export cost = 200 // double their export
contains = list(/obj/item/stack/sheet/plastic/twenty) contains = list(/obj/item/stack/sheet/plastic/twenty)
/datum/supply_pack/materials/sandstone30 /datum/supply_pack/materials/sandstone30
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "30 Sandstone Blocks" name = "30 Sandstone Blocks"
desc = "Neither sandy nor stoney, these thirty blocks will still get the job done." desc = "Neither sandy nor stoney, these thirty blocks will still get the job done."
cost = 150 // five times their export cost = 150 // five times their export
contains = list(/obj/item/stack/sheet/mineral/sandstone/thirty) contains = list(/obj/item/stack/sheet/mineral/sandstone/thirty)
/datum/supply_pack/materials/wood20 /datum/supply_pack/materials/wood20
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "20 Wood Planks" name = "20 Wood Planks"
desc = "Turn cargo's boring metal groundwork into beautiful panelled flooring and much more with twenty wooden planks!" desc = "Turn cargo's boring metal groundwork into beautiful panelled flooring and much more with twenty wooden planks!"
cost = 400 // 6-7 planks shy from having equal import/export prices cost = 400 // 6-7 planks shy from having equal import/export prices
contains = list(/obj/item/stack/sheet/mineral/wood/twenty) contains = list(/obj/item/stack/sheet/mineral/wood/twenty)
/datum/supply_pack/materials/rcdammo /datum/supply_pack/materials/rcdammo
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "Large RCD ammo Single-Pack" name = "Large RCD ammo Single-Pack"
desc = "A single large compressed RCD matter pack, to help with any holes or projects people might be working on." desc = "A single large compressed RCD matter pack, to help with any holes or projects people might be working on."
cost = 600 cost = 600
+1 -1
View File
@@ -370,7 +370,7 @@
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/datum/supply_pack/misc/carpet /datum/supply_pack/misc/carpet
goody = PACK_GOODY_PUBLIC crate_type = /obj/structure/closet/secure_closet/goodies
name = "Classic Carpet Single-Pack" name = "Classic Carpet Single-Pack"
desc = "Plasteel floor tiles getting on your nerves? This 50 units stack of extra soft carpet will tie any room together." desc = "Plasteel floor tiles getting on your nerves? This 50 units stack of extra soft carpet will tie any room together."
cost = 200 cost = 200
+2
View File
@@ -227,6 +227,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/persistent_scars = TRUE var/persistent_scars = TRUE
///If we want to broadcast deadchat connect/disconnect messages ///If we want to broadcast deadchat connect/disconnect messages
var/broadcast_login_logout = TRUE var/broadcast_login_logout = TRUE
///What outfit typepaths we've favorited in the SelectEquipment menu
var/list/favorite_outfits = list()
/// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person /// We have 5 slots for persistent scars, if enabled we pick a random one to load (empty by default) and scars at the end of the shift if we survived as our original person
var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "") var/list/scars_list = list("1" = "", "2" = "", "3" = "", "4" = "", "5" = "")
/// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)] /// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)]
@@ -385,6 +385,15 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["auto_ooc"] >> auto_ooc S["auto_ooc"] >> auto_ooc
S["no_tetris_storage"] >> no_tetris_storage S["no_tetris_storage"] >> no_tetris_storage
//favorite outfits
S["favorite_outfits"] >> favorite_outfits
var/list/parsed_favs = list()
for(var/typetext in favorite_outfits)
var/datum/outfit/path = text2path(typetext)
if(ispath(path)) //whatever typepath fails this check probably doesn't exist anymore
parsed_favs += path
favorite_outfits = uniqueList(parsed_favs)
//try to fix any outdated data if necessary //try to fix any outdated data if necessary
if(needs_update >= 0) if(needs_update >= 0)
@@ -434,6 +443,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
no_tetris_storage = sanitize_integer(no_tetris_storage, 0, 1, initial(no_tetris_storage)) no_tetris_storage = sanitize_integer(no_tetris_storage, 0, 1, initial(no_tetris_storage))
key_bindings = sanitize_islist(key_bindings, list()) key_bindings = sanitize_islist(key_bindings, list())
modless_key_bindings = sanitize_islist(modless_key_bindings, list()) modless_key_bindings = sanitize_islist(modless_key_bindings, list())
favorite_outfits = SANITIZE_LIST(favorite_outfits)
verify_keybindings_valid() // one of these days this will runtime and you'll be glad that i put it in a different proc so no one gets their saves wiped verify_keybindings_valid() // one of these days this will runtime and you'll be glad that i put it in a different proc so no one gets their saves wiped
@@ -535,6 +545,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["pda_skin"], pda_skin) WRITE_FILE(S["pda_skin"], pda_skin)
WRITE_FILE(S["key_bindings"], key_bindings) WRITE_FILE(S["key_bindings"], key_bindings)
WRITE_FILE(S["modless_key_bindings"], modless_key_bindings) WRITE_FILE(S["modless_key_bindings"], modless_key_bindings)
WRITE_FILE(S["favorite_outfits"], favorite_outfits)
//citadel code //citadel code
WRITE_FILE(S["screenshake"], screenshake) WRITE_FILE(S["screenshake"], screenshake)
+4
View File
@@ -3,6 +3,10 @@
set desc = "Authorizes your account in the panic bunker of any servers connected to this function." set desc = "Authorizes your account in the panic bunker of any servers connected to this function."
set category = "OOC" set category = "OOC"
if(!(prefs.db_flags & DB_FLAG_AGE_CONFIRMATION_INCOMPLETE))
to_chat(src, "<span class='danger'>You are not age verified.</span>")
return
if(autobunker_last_try + 5 SECONDS > world.time) if(autobunker_last_try + 5 SECONDS > world.time)
to_chat(src, "<span class='danger'>Function on cooldown, try again in 5 seconds.</span>") to_chat(src, "<span class='danger'>Function on cooldown, try again in 5 seconds.</span>")
return return
+1 -2
View File
@@ -83,8 +83,7 @@
standard_outfit_options = list() standard_outfit_options = list()
for(var/path in subtypesof(/datum/outfit/job)) for(var/path in subtypesof(/datum/outfit/job))
var/datum/outfit/O = path var/datum/outfit/O = path
if(initial(O.can_be_admin_equipped)) standard_outfit_options[initial(O.name)] = path
standard_outfit_options[initial(O.name)] = path
sortTim(standard_outfit_options, /proc/cmp_text_asc) sortTim(standard_outfit_options, /proc/cmp_text_asc)
outfit_options = standard_outfit_options outfit_options = standard_outfit_options
+4 -2
View File
@@ -21,7 +21,7 @@
startWhen = 60 //2 minutes to answer startWhen = 60 //2 minutes to answer
var/datum/comm_message/threat_msg var/datum/comm_message/threat_msg
var/payoff = 0 var/payoff = 0
var/payoff_min = 20000 var/payoff_min = 1000
var/paid_off = FALSE var/paid_off = FALSE
var/pirate_type var/pirate_type
var/ship_template var/ship_template
@@ -66,7 +66,7 @@
SScommunications.send_message(threat_msg,unique = TRUE) SScommunications.send_message(threat_msg,unique = TRUE)
/datum/round_event/pirates/proc/answered() /datum/round_event/pirates/proc/answered()
if(threat_msg && threat_msg.answered == 1) if(threat_msg?.answered == 1)
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D) if(D)
if(D.adjust_money(-payoff)) if(D.adjust_money(-payoff))
@@ -75,6 +75,8 @@
return return
else else
priority_announce("Trying to cheat us? You'll regret this!",sender_override = ship_name) priority_announce("Trying to cheat us? You'll regret this!",sender_override = ship_name)
else if(threat_msg?.answered == 2)
priority_announce("You won't pay? Fine then, we'll take those credits by force!",sender_override = ship_name)
if(!shuttle_spawned) if(!shuttle_spawned)
spawn_shuttle() spawn_shuttle()
else else
@@ -278,6 +278,19 @@
visible_message("[src] finishes cooking!") visible_message("[src] finishes cooking!")
new /obj/item/reagent_containers/food/snacks/meat/steak/goliath(loc) new /obj/item/reagent_containers/food/snacks/meat/steak/goliath(loc)
qdel(src) qdel(src)
/obj/item/reagent_containers/food/snacks/meat/slab/dragon
name = "ash drake meat"
desc = "Meat from an ash drake. It's probably not a good idea to eat this raw."
list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/toxin = 5, /datum/reagent/consumable/cooking_oil = 3)
icon_state = "goliathmeat"
tastes = list("meat" = 1)
foodtype = RAW | MEAT | TOXIC
/obj/item/reagent_containers/food/snacks/meat/slab/dragon/burn()
visible_message("[src] finishes cooking!")
new /obj/item/reagent_containers/food/snacks/meat/steak/dragon(loc)
qdel(src)
/obj/item/reagent_containers/food/snacks/meat/slab/meatwheat /obj/item/reagent_containers/food/snacks/meat/slab/meatwheat
name = "meatwheat clump" name = "meatwheat clump"
@@ -395,6 +408,16 @@
trash = null trash = null
tastes = list("meat" = 1, "rock" = 1) tastes = list("meat" = 1, "rock" = 1)
foodtype = MEAT foodtype = MEAT
/obj/item/reagent_containers/food/snacks/meat/steak/dragon
name = "dragon steak"
desc = "Spicy."
resistance_flags = LAVA_PROOF | FIRE_PROOF
icon_state = "goliathsteak"
list_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/capsaicin = 3)
trash = null
tastes = list("meat" = 1, "fire" = 1)
foodtype = MEAT
/obj/item/reagent_containers/food/snacks/meat/steak/gondola /obj/item/reagent_containers/food/snacks/meat/steak/gondola
name = "gondola steak" name = "gondola steak"
@@ -36,7 +36,7 @@
desc = "It's watching you suspiciously." desc = "It's watching you suspiciously."
/obj/structure/closet/crate/necropolis/tendril/magic/PopulateContents() /obj/structure/closet/crate/necropolis/tendril/magic/PopulateContents()
var/loot = rand(1,9) var/loot = rand(1,10)
switch(loot) switch(loot)
if(1) if(1)
new /obj/item/soulstone/anybody(src) new /obj/item/soulstone/anybody(src)
@@ -57,6 +57,8 @@
new /obj/item/immortality_talisman(src) new /obj/item/immortality_talisman(src)
if(9) if(9)
new /obj/item/gun/magic/wand/book/healing(src) new /obj/item/gun/magic/wand/book/healing(src)
if(10)
new /obj/item/guardiancreator(src)
/obj/structure/closet/crate/necropolis/tendril/weapon_armor/PopulateContents() /obj/structure/closet/crate/necropolis/tendril/weapon_armor/PopulateContents()
var/loot = rand(1,11) var/loot = rand(1,11)
@@ -128,7 +130,7 @@
new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src) new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
/obj/structure/closet/crate/necropolis/tendril/all/PopulateContents() /obj/structure/closet/crate/necropolis/tendril/all/PopulateContents()
var/loot = rand(1,28) var/loot = rand(1,29)
switch(loot) switch(loot)
if(1) if(1)
new /obj/item/shared_storage/red(src) new /obj/item/shared_storage/red(src)
@@ -192,6 +194,8 @@
new /obj/item/immortality_talisman(src) new /obj/item/immortality_talisman(src)
if(28) if(28)
new /obj/item/gun/magic/wand/book/healing(src) new /obj/item/gun/magic/wand/book/healing(src)
if(29)
new /obj/item/guardiancreator(src)
//KA modkit design discs //KA modkit design discs
/obj/item/disk/design_disk/modkit_disc /obj/item/disk/design_disk/modkit_disc
@@ -1105,14 +1109,12 @@
/obj/structure/closet/crate/necropolis/bubblegum/PopulateContents() /obj/structure/closet/crate/necropolis/bubblegum/PopulateContents()
new /obj/item/clothing/suit/space/hostile_environment(src) new /obj/item/clothing/suit/space/hostile_environment(src)
new /obj/item/clothing/head/helmet/space/hostile_environment(src) new /obj/item/clothing/head/helmet/space/hostile_environment(src)
var/loot = rand(1,3) var/loot = rand(1,2)
switch(loot) switch(loot)
if(1) if(1)
new /obj/item/mayhem(src) new /obj/item/mayhem(src)
if(2) if(2)
new /obj/item/book/granter/spell/asura(src) new /obj/item/book/granter/spell/asura(src)
if(3)
new /obj/item/guardiancreator(src)
/obj/structure/closet/crate/necropolis/bubblegum/crusher /obj/structure/closet/crate/necropolis/bubblegum/crusher
name = "bloody bubblegum chest" name = "bloody bubblegum chest"
@@ -297,6 +297,11 @@
icon = 'icons/obj/surgery.dmi' icon = 'icons/obj/surgery.dmi'
icon_state = "posibrain-ipc" icon_state = "posibrain-ipc"
/obj/item/organ/brain/slime
name = "slime nucleus"
desc = "A slimey membranous mass from a slimeperson."
icon_state = "brain-s"
////////////////////////////////////TRAUMAS//////////////////////////////////////// ////////////////////////////////////TRAUMAS////////////////////////////////////////
@@ -1,7 +1,7 @@
/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) /mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone) SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100 var/hit_percent = (100-blocked)/100
if(!forced && hit_percent <= 0) if(!forced && hit_percent <= 0)
return 0 return 0
+44 -4
View File
@@ -24,7 +24,12 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
/mob/living/carbon/human/dummy/proc/wipe_state() /mob/living/carbon/human/dummy/proc/wipe_state()
delete_equipment() delete_equipment()
icon_render_key = null icon_render_key = null
cut_overlays() cut_overlays(TRUE)
/mob/living/carbon/human/dummy/setup_human_dna()
create_dna(src)
randomize_human(src)
dna.initialize_dna(skip_index = TRUE) //Skip stuff that requires full round init.
//Inefficient pooling/caching way. //Inefficient pooling/caching way.
GLOBAL_LIST_EMPTY(human_dummy_list) GLOBAL_LIST_EMPTY(human_dummy_list)
@@ -42,13 +47,48 @@ GLOBAL_LIST_EMPTY(dummy_mob_list)
D = new D = new
GLOB.human_dummy_list[slotkey] = D GLOB.human_dummy_list[slotkey] = D
GLOB.dummy_mob_list += D GLOB.dummy_mob_list += D
else
D.regenerate_icons() //they were cut in wipe_state()
D.in_use = TRUE D.in_use = TRUE
return D return D
/proc/unset_busy_human_dummy(slotnumber) /proc/generate_dummy_lookalike(slotkey, mob/target)
if(!slotnumber) if(!istype(target))
return generate_or_wait_for_human_dummy(slotkey)
var/mob/living/carbon/human/dummy/copycat = generate_or_wait_for_human_dummy(slotkey)
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
carbon_target.dna.transfer_identity(copycat, transfer_SE = TRUE)
if(ishuman(target))
var/mob/living/carbon/human/human_target = target
human_target.copy_clothing_prefs(copycat)
copycat.updateappearance(icon_update=TRUE, mutcolor_update=TRUE, mutations_overlay_update=TRUE)
else
//even if target isn't a carbon, if they have a client we can make the
//dummy look like what their human would look like based on their prefs
target?.client?.prefs?.copy_to(copycat, icon_updates=TRUE, roundstart_checks=FALSE)
return copycat
/proc/unset_busy_human_dummy(slotkey)
if(!slotkey)
return return
var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber] var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotkey]
if(istype(D)) if(istype(D))
D.wipe_state() D.wipe_state()
D.in_use = FALSE D.in_use = FALSE
/proc/clear_human_dummy(slotkey)
if(!slotkey)
return
var/mob/living/carbon/human/dummy/dummy = GLOB.human_dummy_list[slotkey]
GLOB.human_dummy_list -= slotkey
if(istype(dummy))
GLOB.dummy_mob_list -= dummy
qdel(dummy)
@@ -14,10 +14,7 @@
//initialize limbs first //initialize limbs first
create_bodyparts() create_bodyparts()
//initialize dna. for spawned humans; overwritten by other code setup_human_dna()
create_dna(src)
randomize_human(src)
dna.initialize_dna()
if(dna.species) if(dna.species)
set_species(dna.species.type) set_species(dna.species.type)
@@ -36,6 +33,11 @@
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood)
GLOB.human_list += src GLOB.human_list += src
/mob/living/carbon/human/proc/setup_human_dna()
//initialize dna. for spawned humans; overwritten by other code
create_dna(src)
randomize_human(src)
dna.initialize_dna()
/mob/living/carbon/human/ComponentInitialize() /mob/living/carbon/human/ComponentInitialize()
. = ..() . = ..()
@@ -176,3 +176,9 @@
/mob/living/carbon/human/get_biological_state() /mob/living/carbon/human/get_biological_state()
return dna.species.get_biological_state() return dna.species.get_biological_state()
///copies over clothing preferences like underwear to another human
/mob/living/carbon/human/proc/copy_clothing_prefs(mob/living/carbon/human/destination)
destination.underwear = underwear
destination.undershirt = undershirt
destination.socks = socks
@@ -1620,8 +1620,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
"<span class='userdanger'>[user] [atk_verb]ed you!</span>", null, COMBAT_MESSAGE_RANGE, null, \ "<span class='userdanger'>[user] [atk_verb]ed you!</span>", null, COMBAT_MESSAGE_RANGE, null, \
user, "<span class='danger'>You [atk_verb]ed [target]!</span>") user, "<span class='danger'>You [atk_verb]ed [target]!</span>")
target.lastattacker = user.real_name target.set_last_attacker(user)
target.lastattackerckey = user.ckey
user.dna.species.spec_unarmedattacked(user, target) user.dna.species.spec_unarmedattacked(user, target)
if(user.limb_destroyer) if(user.limb_destroyer)
@@ -2039,8 +2038,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
user, "<span class='danger'>You shove [target.name]!</span>") user, "<span class='danger'>You shove [target.name]!</span>")
target.Stagger(SHOVE_STAGGER_DURATION) target.Stagger(SHOVE_STAGGER_DURATION)
var/obj/item/target_held_item = target.get_active_held_item() var/obj/item/target_held_item = target.get_active_held_item()
if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types))
target_held_item = null
if(!target.has_status_effect(STATUS_EFFECT_OFF_BALANCE)) if(!target.has_status_effect(STATUS_EFFECT_OFF_BALANCE))
if(target_held_item) if(target_held_item)
if(!HAS_TRAIT(target_held_item, TRAIT_NODROP)) if(!HAS_TRAIT(target_held_item, TRAIT_NODROP))
@@ -2058,7 +2055,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
log_combat(user, target, "shoved", append_message) log_combat(user, target, "shoved", append_message)
/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) /datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal
var/hit_percent = (100-(blocked+armor))/100 var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100 hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
if(!forced && hit_percent <= 0) if(!forced && hit_percent <= 0)
@@ -7,6 +7,9 @@
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR,HAS_FLESH) species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR,HAS_FLESH)
mutantlungs = /obj/item/organ/lungs/slime mutantlungs = /obj/item/organ/lungs/slime
mutant_heart = /obj/item/organ/heart/slime mutant_heart = /obj/item/organ/heart/slime
mutantstomach = /obj/item/organ/stomach/slime
mutantliver = /obj/item/organ/liver/slime
mutant_brain = /obj/item/organ/brain/slime
mutant_bodyparts = list("mcolor" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None", "legs" = "Plantigrade") mutant_bodyparts = list("mcolor" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None", "legs" = "Plantigrade")
inherent_traits = list(TRAIT_TOXINLOVER) inherent_traits = list(TRAIT_TOXINLOVER)
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
@@ -22,18 +25,12 @@
heatmod = 0.5 // = 1/4x heat damage heatmod = 0.5 // = 1/4x heat damage
burnmod = 0.5 // = 1/2x generic burn damage burnmod = 0.5 // = 1/2x generic burn damage
species_language_holder = /datum/language_holder/jelly species_language_holder = /datum/language_holder/jelly
mutant_brain = /obj/item/organ/brain/jelly
tail_type = "mam_tail" tail_type = "mam_tail"
wagging_type = "mam_waggingtail" wagging_type = "mam_waggingtail"
species_category = SPECIES_CATEGORY_JELLY species_category = SPECIES_CATEGORY_JELLY
ass_image = 'icons/ass/assslime.png' ass_image = 'icons/ass/assslime.png'
/obj/item/organ/brain/jelly
name = "slime nucleus"
desc = "A slimey membranous mass from a slime person"
icon_state = "brain-slime"
/datum/species/jelly/on_species_loss(mob/living/carbon/C) /datum/species/jelly/on_species_loss(mob/living/carbon/C)
C.faction -= "slime" C.faction -= "slime"
if(ishuman(C)) if(ishuman(C))
+1 -1
View File
@@ -2,7 +2,7 @@
/** /**
* Applies damage to this mob * Applies damage to this mob
* *
* Sends [COMSIG_MOB_APPLY_DAMGE] * Sends [COMSIG_MOB_APPLY_DAMAGE]
* *
* Arguuments: * Arguuments:
* * damage - amount of damage * * damage - amount of damage
+2
View File
@@ -55,6 +55,8 @@
/mob/living/death(gibbed) /mob/living/death(gibbed)
SEND_SIGNAL(src, COMSIG_LIVING_PREDEATH, gibbed)
stat = DEAD stat = DEAD
unset_machine() unset_machine()
timeofdeath = world.time timeofdeath = world.time
+7 -1
View File
@@ -274,7 +274,7 @@
/mob/living/on_attack_hand(mob/user, act_intent = user.a_intent, attackchain_flags) /mob/living/on_attack_hand(mob/user, act_intent = user.a_intent, attackchain_flags)
..() //Ignoring parent return value here. ..() //Ignoring parent return value here.
SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user) SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user, act_intent)
if((user != src) && act_intent != INTENT_HELP && (mob_run_block(user, 0, user.name, ATTACK_TYPE_UNARMED | ATTACK_TYPE_MELEE | ((attackchain_flags & ATTACK_IS_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE), null, user, check_zone(user.zone_selected), null) & BLOCK_SUCCESS)) if((user != src) && act_intent != INTENT_HELP && (mob_run_block(user, 0, user.name, ATTACK_TYPE_UNARMED | ATTACK_TYPE_MELEE | ((attackchain_flags & ATTACK_IS_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE), null, user, check_zone(user.zone_selected), null) & BLOCK_SUCCESS))
log_combat(user, src, "attempted to touch") log_combat(user, src, "attempted to touch")
visible_message("<span class='warning'>[user] attempted to touch [src]!</span>", visible_message("<span class='warning'>[user] attempted to touch [src]!</span>",
@@ -561,3 +561,9 @@
/mob/living/proc/getFireLoss_nonProsthetic() /mob/living/proc/getFireLoss_nonProsthetic()
return getFireLoss() return getFireLoss()
/mob/living/proc/set_last_attacker(mob/attacker)
lastattacker = attacker.real_name
lastattackerckey = attacker.ckey
SEND_SIGNAL(src, COMSIG_LIVING_ATTACKER_SET, attacker)
SEND_SIGNAL(attacker, COMSIG_LIVING_SET_AS_ATTACKER, src)
@@ -1,5 +1,3 @@
#define MEDAL_PREFIX "Bubblegum"
/* /*
BUBBLEGUM BUBBLEGUM
@@ -7,24 +5,25 @@ BUBBLEGUM
Bubblegum spawns randomly wherever a lavaland creature is able to spawn. It is the most powerful slaughter demon in existence. Bubblegum spawns randomly wherever a lavaland creature is able to spawn. It is the most powerful slaughter demon in existence.
Bubblegum's footsteps are heralded by shaking booms, proving its tremendous size. Bubblegum's footsteps are heralded by shaking booms, proving its tremendous size.
It acts as a melee creature, chasing down and attacking its target while also using different attacks to augment its power that increase as it takes damage. It acts as a melee creature, chasing down and attacking its target while also using different attacks to augment its power
It often charges, dealing massive damage to anything unfortunate enough to be standing where it's aiming. It leaves blood trails behind wherever it goes, its clones do as well.
Whenever it isn't chasing something down, it will sink into nearby blood pools (if possible) and springs out of the closest one to its target. It tries to strike at its target through any bloodpools under them; if it fails to do that.
To make this possible, it sprays streams of blood at random. If it does warp it will enter an enraged state, becoming immune to all projectiles, becoming much faster, and dealing damage and knockback to anything that gets in the cloud around it.
From these blood pools Bubblegum may summon slaughterlings - weak, low-damage minions designed to impede the target's progress. It may summon clones charging from all sides, one of these charges being bubblegum himself.
It can charge at its target, and also heavily damaging anything directly hit in the charge.
If at half health it will start to charge from all sides with clones.
When Bubblegum dies, it leaves behind a H.E.C.K. suit+helmet as well as a chest that can contain three things: When Bubblegum dies, it leaves behind a H.E.C.K. mining suit as well as a chest that can contain three things:
1. A spellblade that can slice off limbs at range 1. A bottle that, when activated, drives everyone nearby into a frenzy
2. A bottle that, when activated, drives everyone nearby into a frenzy 2. A scroll that teaches the reader a martial art that sacrifices health for raw demonic power with their bare hands.
3. A super double-barrel shotgun that shoots both shells at the same time.
Difficulty: Hard Difficulty: Hard
*/ */
/mob/living/simple_animal/hostile/megafauna/bubblegum /mob/living/simple_animal/hostile/megafauna/bubblegum
name = "bubblegum" name = "Bubblegum"
desc = "In what passes for a hierarchy among slaughter demons, this one is king." desc = "In what passes for a hierarchy among slaughter demons, this one is king."
health = 2500 health = 2500
maxHealth = 2500 maxHealth = 2500
@@ -42,15 +41,20 @@ Difficulty: Hard
melee_damage_lower = 40 melee_damage_lower = 40
melee_damage_upper = 40 melee_damage_upper = 40
speed = 1 speed = 1
move_to_delay = 10 move_to_delay = 5
ranged_cooldown_time = 10 retreat_distance = 5
minimum_distance = 5
rapid_melee = 8 // every 1/4 second
melee_queue_distance = 20 // as far as possible really, need this because of blood warp
ranged = 1 ranged = 1
pixel_x = -32 pixel_x = -32
gender = MALE gender = MALE
del_on_death = 1 del_on_death = 1
crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher) crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher)
loot = list(/obj/structure/closet/crate/necropolis/bubblegum) loot = list(/obj/structure/closet/crate/necropolis/bubblegum)
blood_volume = BLOOD_VOLUME_MAXIMUM //BLEED FOR ME
var/charging = 0 var/charging = 0
var/enrage_till = null
achievement_type = /datum/award/achievement/boss/bubblegum_kill achievement_type = /datum/award/achievement/boss/bubblegum_kill
crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher
@@ -67,36 +71,60 @@ Difficulty: Hard
desc = "You're not quite sure how a signal can be bloody." desc = "You're not quite sure how a signal can be bloody."
invisibility = 100 invisibility = 100
/mob/living/simple_animal/hostile/megafauna/bubblegum/BiologicalLife(seconds, times_fired) /mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, only_robotic = FALSE, only_organic = TRUE)
if(!(. = ..())) . = ..()
return if(. > 0 && prob(25))
move_to_delay = clamp(round((health/maxHealth) * 10), 3, 10) var/obj/effect/decal/cleanable/blood/gibs/bubblegum/B = new /obj/effect/decal/cleanable/blood/gibs/bubblegum(loc)
if(prob(40))
step(B, pick(GLOB.cardinals))
else
B.setDir(pick(GLOB.cardinals))
/obj/effect/decal/cleanable/blood/gibs/bubblegum
name = "thick blood"
desc = "Thick, splattered blood."
random_icon_states = list("gib3", "gib5", "gib6")
bloodiness = 20
/obj/effect/decal/cleanable/blood/gibs/bubblegum/can_bloodcrawl_in()
return TRUE
/mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire() /mob/living/simple_animal/hostile/megafauna/bubblegum/OpenFire()
anger_modifier = clamp(((maxHealth - health)/50),0,20) anger_modifier = clamp(((maxHealth - health)/60),0,20)
if(charging) if(charging)
return return
ranged_cooldown = world.time + ranged_cooldown_time ranged_cooldown = world.time + 50
if(!try_bloodattack())
blood_warp()
blood_warp() if(health > maxHealth * 0.5)
if(prob(50 + anger_modifier))
if(prob(25)) charge(delay = 6)
INVOKE_ASYNC(src, .proc/blood_spray) charge(delay = 4) // The FitnessGram Pacer Test is a multistage aerobic capacity test that progressively gets more difficult as it continues.
charge(delay = 2)
else if(prob(5+anger_modifier/2)) SetRecoveryTime(15)
slaughterlings()
else
if(health > maxHealth/2 && !client)
INVOKE_ASYNC(src, .proc/charge)
else else
INVOKE_ASYNC(src, .proc/triple_charge) hallucination_charge_around(times = 6, delay = 10 - anger_modifier / 5)
SetRecoveryTime(10)
else
if(prob(50 - anger_modifier))
hallucination_charge_around(times = 4, delay = 9)
hallucination_charge_around(times = 4, delay = 8)
hallucination_charge_around(times = 4, delay = 7)
SetRecoveryTime(15)
else
for(var/i = 1 to 5)
INVOKE_ASYNC(src, .proc/hallucination_charge_around, 2, 10, 2, 0)
sleep(5)
SetRecoveryTime(10)
/mob/living/simple_animal/hostile/megafauna/bubblegum/Initialize() /mob/living/simple_animal/hostile/megafauna/bubblegum/Initialize()
. = ..() . = ..()
for(var/mob/living/simple_animal/hostile/megafauna/bubblegum/B in GLOB.mob_list) if(istype(src, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination))
if(B != src) return
return INITIALIZE_HINT_QDEL //There can be only one for(var/mob/living/simple_animal/hostile/megafauna/bubblegum/B in GLOB.mob_living_list) if(B != src)
return INITIALIZE_HINT_QDEL //There can be only one
var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new
AddSpell(bloodspell) AddSpell(bloodspell)
if(istype(loc, /obj/effect/dummy/phased_mob/slaughter)) if(istype(loc, /obj/effect/dummy/phased_mob/slaughter))
@@ -109,52 +137,78 @@ Difficulty: Hard
SSshuttle.shuttle_purchase_requirements_met |= "bubblegum" SSshuttle.shuttle_purchase_requirements_met |= "bubblegum"
/mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) /mob/living/simple_animal/hostile/megafauna/bubblegum/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(charging) if(!charging)
return ..()
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/AttackingTarget() /mob/living/simple_animal/hostile/megafauna/bubblegum/AttackingTarget()
if(charging) if(!charging)
. = ..()
if(.)
recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority
/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/item/projectile/P)
if(is_enraged())
visible_message("<span class='danger'>[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!</span>", "<span class='userdanger'>You deflect the projectile!</span>")
playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 300, 1)
return 0
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/ex_act(severity, target)
if(severity >= EXPLODE_LIGHT)
return return
..() severity = EXPLODE_LIGHT // puny mortals
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination))
return 1
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/Goto(target, delay, minimum_distance) /mob/living/simple_animal/hostile/megafauna/bubblegum/Goto(target, delay, minimum_distance)
if(charging) if(!charging)
return ..()
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/MoveToTarget(list/possible_targets)
if(!charging)
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/Move() /mob/living/simple_animal/hostile/megafauna/bubblegum/Move()
if(!stat)
playsound(src.loc, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1)
if(charging) if(charging)
new/obj/effect/temp_visual/decoy/fading(loc,src) new /obj/effect/temp_visual/decoy/fading(loc,src)
DestroySurroundings() DestroySurroundings()
. = ..() ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved()
new /obj/effect/decal/cleanable/blood(src.loc)
if(charging) if(charging)
DestroySurroundings() DestroySurroundings()
playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1)
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/triple_charge() /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/charge(atom/chargeat = target, delay = 3, chargepast = 2)
charge() if(!chargeat)
sleep(10)
charge()
sleep(10)
charge()
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/charge()
var/turf/T = get_turf(target)
if(!T || T == loc)
return return
new /obj/effect/temp_visual/dragon_swoop(T) var/chargeturf = get_turf(chargeat)
if(!chargeturf)
return
var/dir = get_dir(src, chargeturf)
var/turf/T = get_ranged_target_turf(chargeturf, dir, chargepast)
if(!T)
return
new /obj/effect/temp_visual/dragon_swoop/bubblegum(T)
charging = 1 charging = 1
DestroySurroundings() DestroySurroundings()
walk(src, 0) walk(src, 0)
setDir(get_dir(src, T)) setDir(dir)
var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(loc,src) var/obj/effect/temp_visual/decoy/D = new /obj/effect/temp_visual/decoy(loc,src)
animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 5) animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3)
sleep(5) sleep(delay)
throw_at(T, get_dist(src, T), 1, src, 0) var/movespeed = 0.7
walk_towards(src, T, movespeed)
sleep(get_dist(src, T) * movespeed)
walk(src, 0) // cancel the movement
try_bloodattack()
charging = 0 charging = 0
Goto(target, move_to_delay, minimum_distance)
/** /**
* Attack by override for bubblegum * Attack by override for bubblegum
@@ -176,36 +230,147 @@ Difficulty: Hard
if(isturf(A) || isobj(A) && A.density) if(isturf(A) || isobj(A) && A.density)
A.ex_act(EXPLODE_HEAVY) A.ex_act(EXPLODE_HEAVY)
DestroySurroundings() DestroySurroundings()
if(isliving(A))
var/mob/living/L = A
L.visible_message("<span class='danger'>[src] slams into [L]!</span>", "<span class='userdanger'>[src] tramples you into the ground!</span>")
src.forceMove(get_turf(L))
L.apply_damage(istype(src, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination) ? 15 : 30, BRUTE)
playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, 1)
shake_camera(L, 4, 3)
shake_camera(src, 2, 3)
..() ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood()
if(!charging) var/list/targets = ListTargets()
return ..() . = list()
for(var/mob/living/L in targets)
var/list/bloodpool = get_pools(get_turf(L), 0)
if(bloodpool.len && (!faction_check_mob(L) || L.stat == DEAD))
. += L
else if(isliving(hit_atom)) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/try_bloodattack()
var/mob/living/L = hit_atom var/list/targets = get_mobs_on_blood()
L.visible_message("<span class='danger'>[src] slams into [L]!</span>", "<span class='userdanger'>[src] slams into you!</span>") if(targets.len)
L.apply_damage(40, BRUTE) INVOKE_ASYNC(src, .proc/bloodattack, targets, prob(50))
playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, 1) return TRUE
shake_camera(L, 4, 3) return FALSE
shake_camera(src, 2, 3)
var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(L, src)))
L.throw_at(throwtarget, 3)
charging = 0 /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/bloodattack(list/targets, handedness)
var/mob/living/target_one = pick_n_take(targets)
var/turf/target_one_turf = get_turf(target_one)
var/mob/living/target_two
if(targets.len)
target_two = pick_n_take(targets)
var/turf/target_two_turf = get_turf(target_two)
if(target_two.stat != CONSCIOUS || prob(10))
bloodgrab(target_two_turf, handedness)
else
bloodsmack(target_two_turf, handedness)
if(target_one)
var/list/pools = get_pools(get_turf(target_one), 0)
if(pools.len)
target_one_turf = get_turf(target_one)
if(target_one_turf)
if(target_one.stat != CONSCIOUS || prob(10))
bloodgrab(target_one_turf, !handedness)
else
bloodsmack(target_one_turf, !handedness)
if(!target_two && target_one)
var/list/poolstwo = get_pools(get_turf(target_one), 0)
if(poolstwo.len)
target_one_turf = get_turf(target_one)
if(target_one_turf)
if(target_one.stat != CONSCIOUS || prob(10))
bloodgrab(target_one_turf, handedness)
else
bloodsmack(target_one_turf, handedness)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/bloodsmack(turf/T, handedness)
if(handedness)
new /obj/effect/temp_visual/bubblegum_hands/rightsmack(T)
else
new /obj/effect/temp_visual/bubblegum_hands/leftsmack(T)
sleep(4)
for(var/mob/living/L in T)
if(!faction_check_mob(L))
to_chat(L, "<span class='userdanger'>[src] rends you!</span>")
playsound(T, attack_sound, 100, 1, -1)
var/limb_to_hit = L.get_bodypart(pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
L.apply_damage(10, BRUTE, limb_to_hit, L.run_armor_check(limb_to_hit, "melee", null, null, armour_penetration))
sleep(3)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/bloodgrab(turf/T, handedness)
if(handedness)
new /obj/effect/temp_visual/bubblegum_hands/rightpaw(T)
new /obj/effect/temp_visual/bubblegum_hands/rightthumb(T)
else
new /obj/effect/temp_visual/bubblegum_hands/leftpaw(T)
new /obj/effect/temp_visual/bubblegum_hands/leftthumb(T)
sleep(6)
for(var/mob/living/L in T)
if(!faction_check_mob(L))
if(L.stat != CONSCIOUS)
to_chat(L, "<span class='userdanger'>[src] drags you through the blood!</span>")
playsound(T, 'sound/magic/enter_blood.ogg', 100, 1, -1)
var/turf/targetturf = get_step(src, dir)
L.forceMove(targetturf)
playsound(targetturf, 'sound/magic/exit_blood.ogg', 100, 1, -1)
addtimer(CALLBACK(src, .proc/devour, L), 2)
sleep(1)
/obj/effect/temp_visual/dragon_swoop/bubblegum
duration = 10
/obj/effect/temp_visual/bubblegum_hands
icon = 'icons/effects/bubblegum.dmi'
duration = 9
/obj/effect/temp_visual/bubblegum_hands/rightthumb
icon_state = "rightthumbgrab"
/obj/effect/temp_visual/bubblegum_hands/leftthumb
icon_state = "leftthumbgrab"
/obj/effect/temp_visual/bubblegum_hands/rightpaw
icon_state = "rightpawgrab"
layer = BELOW_MOB_LAYER
/obj/effect/temp_visual/bubblegum_hands/leftpaw
icon_state = "leftpawgrab"
layer = BELOW_MOB_LAYER
/obj/effect/temp_visual/bubblegum_hands/rightsmack
icon_state = "rightsmack"
/obj/effect/temp_visual/bubblegum_hands/leftsmack
icon_state = "leftsmack"
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_warp() /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_warp()
if(Adjacent(target) || (enrage_till + 30 > world.time))
return FALSE
var/list/can_jaunt = get_pools(get_turf(src), 1)
if(!can_jaunt.len)
return FALSE
var/list/pools = get_pools(get_turf(target), 2)
var/list/pools_to_remove = get_pools(get_turf(target), 1)
pools -= pools_to_remove
if(!pools.len)
return FALSE
var/obj/effect/temp_visual/decoy/DA = new /obj/effect/temp_visual/decoy(loc,src)
DA.color = "#FF0000"
var/oldtransform = DA.transform
DA.transform = matrix()*2
animate(DA, alpha = 255, color = initial(DA.color), transform = oldtransform, time = 3)
sleep(3)
qdel(DA)
var/obj/effect/decal/cleanable/blood/found_bloodpool var/obj/effect/decal/cleanable/blood/found_bloodpool
var/list/pools = list() pools = get_pools(get_turf(target), 2)
var/can_jaunt = FALSE pools_to_remove = get_pools(get_turf(target), 1)
for(var/obj/effect/decal/cleanable/blood/nearby in view(src,2)) pools -= pools_to_remove
can_jaunt = TRUE
break
if(!can_jaunt)
return
for(var/obj/effect/decal/cleanable/blood/nearby in view(get_turf(target),2))
pools += nearby
if(pools.len) if(pools.len)
shuffle_inplace(pools) shuffle_inplace(pools)
found_bloodpool = pick(pools) found_bloodpool = pick(pools)
@@ -215,48 +380,110 @@ Difficulty: Hard
forceMove(get_turf(found_bloodpool)) forceMove(get_turf(found_bloodpool))
playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1) playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1)
visible_message("<span class='danger'>And springs back out!</span>") visible_message("<span class='danger'>And springs back out!</span>")
blood_enrage()
return TRUE
return FALSE
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage(var/boost_time = 30)
enrage_till = world.time + boost_time
retreat_distance = null
minimum_distance = 1
change_move_delay(3.75)
var/newcolor = rgb(149, 10, 10)
add_atom_colour(newcolor, TEMPORARY_COLOUR_PRIORITY)
var/datum/callback/cb = CALLBACK(src, .proc/blood_enrage_end)
addtimer(cb, boost_time)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_spray() /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/blood_enrage_end(var/newcolor = rgb(149, 10, 10))
visible_message("<span class='danger'>[src] sprays a stream of gore!</span>") retreat_distance = initial(retreat_distance)
var/turf/E = get_edge_target_turf(src, src.dir) minimum_distance = initial(minimum_distance)
var/range = 10 change_move_delay()
var/turf/previousturf = get_turf(src) remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, newcolor)
for(var/turf/J in getline(src,E))
if(!range)
break
new /obj/effect/temp_visual/dir_setting/bloodsplatter(previousturf, get_dir(previousturf, J))
if(!previousturf.CanAtmosPass(J))
break
playsound(J,'sound/effects/splat.ogg', 100, 1, -1)
new /obj/effect/decal/cleanable/blood(J)
range--
previousturf = J
sleep(1)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/slaughterlings() /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/is_enraged()
visible_message("<span class='danger'>[src] summons a shoal of slaughterlings!</span>") return (enrage_till > world.time)
for(var/obj/effect/decal/cleanable/blood/H in range(src, 10))
if(prob(25))
new /mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter(H.loc)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/change_move_delay(var/newmove = initial(move_to_delay))
name = "slaughterling" move_to_delay = newmove
desc = "Though not yet strong enough to create a true physical form, it's nonetheless determined to murder you." handle_automated_action() // need to recheck movement otherwise move_to_delay won't update until the next checking aka will be wrong speed for a bit
icon_state = "bloodbrood"
icon_living = "bloodbrood"
icon_aggro = "bloodbrood"
attack_verb_continuous = "pierces"
attack_verb_simple = "pierce"
color = "#C80000"
density = FALSE
faction = list("mining", "boss")
weather_immunities = list("lava","ash")
has_field_of_vision = FALSE
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter/CanPass(atom/movable/mover, turf/target) /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_pools(turf/T, range)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) . = list()
for(var/obj/effect/decal/cleanable/nearby in view(T, range))
if(nearby.can_bloodcrawl_in())
. += nearby
/obj/effect/decal/cleanable/blood/bubblegum
bloodiness = 0
/obj/effect/decal/cleanable/blood/bubblegum/can_bloodcrawl_in()
return TRUE
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hallucination_charge_around(var/times = 4, var/delay = 6, var/chargepast = 0, var/useoriginal = 1)
var/startingangle = rand(1, 360)
if(!target)
return
var/turf/chargeat = get_turf(target)
var/srcplaced = 0
for(var/i = 1 to times)
var/ang = (startingangle + 360/times * i)
if(!chargeat)
return
var/turf/place = locate(chargeat.x + cos(ang) * times, chargeat.y + sin(ang) * times, chargeat.z)
if(!place)
continue
if(!srcplaced && useoriginal)
forceMove(place)
srcplaced = 1
continue
var/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/B = new /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination(src.loc)
B.forceMove(place)
INVOKE_ASYNC(B, .proc/charge, chargeat, delay, chargepast)
if(useoriginal)
charge(chargeat, delay, chargepast)
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination
name = "bubblegum's hallucination"
desc = "Is that really just a hallucination?"
health = 1
maxHealth = 1
alpha = 127.5
crusher_loot = null
loot = null
deathmessage = "Explodes into a pool of blood!"
deathsound = 'sound/effects/splat.ogg'
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Initialize()
..()
toggle_ai(AI_OFF)
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/charge(atom/chargeat = target, delay = 3, chargepast = 2)
..()
qdel(src)
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Destroy()
new /obj/effect/decal/cleanable/blood(get_turf(src))
. = ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum)) // hallucinations should not be stopping bubblegum or eachother
return 1 return 1
return 0 return ..()
#undef MEDAL_PREFIX /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/Life()
return
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, only_robotic = FALSE, only_organic = TRUE)
return
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/OpenFire()
return
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/AttackingTarget()
return
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/try_bloodattack()
return
/mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination/grant_achievement(medaltype,scoretype)
return
@@ -60,10 +60,10 @@ Difficulty: Medium
pixel_x = -16 pixel_x = -16
crusher_loot = list(/obj/structure/closet/crate/necropolis/dragon/crusher) crusher_loot = list(/obj/structure/closet/crate/necropolis/dragon/crusher)
loot = list(/obj/structure/closet/crate/necropolis/dragon) loot = list(/obj/structure/closet/crate/necropolis/dragon)
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30, /obj/item/reagent_containers/food/snacks/meat/slab/dragon = 5)
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10) guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10)
var/swooping = NONE var/swooping = NONE
var/swoop_cooldown = 0 var/player_cooldown = 0
achievement_type = /datum/award/achievement/boss/drake_kill achievement_type = /datum/award/achievement/boss/drake_kill
crusher_achievement_type = /datum/award/achievement/boss/drake_crusher crusher_achievement_type = /datum/award/achievement/boss/drake_crusher
score_achievement_type = /datum/award/score/drake_score score_achievement_type = /datum/award/score/drake_score
@@ -116,66 +116,167 @@ Difficulty: Medium
ranged_cooldown = world.time + ranged_cooldown_time ranged_cooldown = world.time + ranged_cooldown_time
if(prob(15 + anger_modifier) && !client) if(prob(15 + anger_modifier) && !client)
if(health < maxHealth/2) if(health < maxHealth*0.5)
INVOKE_ASYNC(src, .proc/swoop_attack, TRUE, null, 50) swoop_attack(lava_arena = TRUE)
else else
fire_rain() lava_swoop()
else if(prob(10+anger_modifier) && !client) else if(prob(10+anger_modifier) && !client)
if(health > maxHealth/2) if(health < maxHealth*0.5)
INVOKE_ASYNC(src, .proc/swoop_attack) mass_fire()
else else
INVOKE_ASYNC(src, .proc/triple_swoop) fire_cone()
else else
fire_walls() if(prob(50) && !client)
INVOKE_ASYNC(src, .proc/lava_pools, 10, 2)
fire_cone()
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_rain() /mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_pools(var/amount, var/delay = 0.8)
if(!target) if(!target)
return return
target.visible_message("<span class='boldwarning'>Fire rains from the sky!</span>") target.visible_message("<span class='boldwarning'>Lava starts to pool up around you!</span>")
for(var/turf/turf in range(9,get_turf(target))) while(amount > 0)
if(prob(11)) if(!target)
new /obj/effect/temp_visual/target(turf)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_walls()
playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1)
for(var/d in GLOB.cardinals)
INVOKE_ASYNC(src, .proc/fire_wall, d)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_wall(dir)
var/list/hit_things = list(src)
var/turf/E = get_edge_target_turf(src, dir)
var/range = 10
var/turf/previousturf = get_turf(src)
for(var/turf/J in getline(src,E))
if(!range || (J != previousturf && (!previousturf.atmos_adjacent_turfs || !previousturf.atmos_adjacent_turfs[J])))
break break
range-- var/turf/T = pick(RANGE_TURFS(1, target))
new /obj/effect/hotspot(J) new /obj/effect/temp_visual/lava_warning(T, 60) // longer reset time for the lava
J.hotspot_expose(DRAKE_FIRE_TEMP, DRAKE_FIRE_EXPOSURE, 1) amount--
for(var/mob/living/L in J.contents - hit_things) sleep(delay)
if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon))
/mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_swoop(var/amount = 30)
INVOKE_ASYNC(src, .proc/lava_pools, amount)
swoop_attack(FALSE, target, 1000) // longer cooldown until it gets reset below
fire_cone()
if(health < maxHealth*0.5)
sleep(10)
fire_cone()
sleep(10)
fire_cone()
SetRecoveryTime(40)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/mass_fire(var/spiral_count = 12, var/range = 15, var/times = 3)
for(var/i = 1 to times)
SetRecoveryTime(50)
playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1)
var/increment = 360 / spiral_count
for(var/j = 1 to spiral_count)
var/list/turfs = line_target(j * increment + i * increment / 2, range, src)
INVOKE_ASYNC(src, .proc/fire_line, turfs)
sleep(25)
SetRecoveryTime(30)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/lava_arena()
if(!target)
return
target.visible_message("<span class='boldwarning'>[src] encases you in an arena of fire!</span>")
var/amount = 3
var/turf/center = get_turf(target)
var/list/walled = RANGE_TURFS(3, center) - RANGE_TURFS(2, center)
var/list/drakewalls = list()
for(var/turf/T in walled)
drakewalls += new /obj/effect/temp_visual/drakewall(T) // no people with lava immunity can just run away from the attack for free
var/list/indestructible_turfs = list()
for(var/turf/T in RANGE_TURFS(2, center))
if(istype(T, /turf/open/indestructible))
continue
if(!istype(T, /turf/closed/indestructible))
T.ChangeTurf(/turf/open/floor/plating/asteroid/basalt/lava_land_surface)
else
indestructible_turfs += T
sleep(10) // give them a bit of time to realize what attack is actually happening
var/list/turfs = RANGE_TURFS(2, center)
while(amount > 0)
var/list/empty = indestructible_turfs.Copy() // can't place safe turfs on turfs that weren't changed to be open
var/any_attack = 0
for(var/turf/T in turfs)
for(var/mob/living/L in T.contents)
if(L.client)
empty += pick(((RANGE_TURFS(2, L) - RANGE_TURFS(1, L)) & turfs) - empty) // picks a turf within 2 of the creature not outside or in the shield
any_attack = 1
for(var/obj/mecha/M in T.contents)
empty += pick(((RANGE_TURFS(2, M) - RANGE_TURFS(1, M)) & turfs) - empty)
any_attack = 1
if(!any_attack)
for(var/obj/effect/temp_visual/drakewall/D in drakewalls)
qdel(D)
return 0 // nothing to attack in the arena time for enraged attack if we still have a target
for(var/turf/T in turfs)
if(!(T in empty))
new /obj/effect/temp_visual/lava_warning(T)
else if(!istype(T, /turf/closed/indestructible))
new /obj/effect/temp_visual/lava_safe(T)
amount--
sleep(24)
return 1 // attack finished completely
/mob/living/simple_animal/hostile/megafauna/dragon/proc/arena_escape_enrage() // you ran somehow / teleported away from my arena attack now i'm mad fucker
SetRecoveryTime(80)
visible_message("<span class='boldwarning'>[src] starts to glow vibrantly as its wounds close up!</span>")
adjustBruteLoss(-250) // yeah you're gonna pay for that, don't run nerd
add_atom_colour(rgb(255, 255, 0), TEMPORARY_COLOUR_PRIORITY)
move_to_delay = move_to_delay / 2
light_range = 10
sleep(10) // run.
mass_fire(20, 15, 3)
remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
move_to_delay = initial(move_to_delay)
light_range = initial(light_range)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_cone(var/atom/at = target)
playsound(get_turf(src),'sound/magic/fireball.ogg', 200, 1)
if(QDELETED(src) || stat == DEAD) // we dead no fire
return
var/range = 15
var/list/turfs = list()
turfs = line_target(-40, range, at)
INVOKE_ASYNC(src, .proc/fire_line, turfs)
turfs = line_target(0, range, at)
INVOKE_ASYNC(src, .proc/fire_line, turfs)
turfs = line_target(40, range, at)
INVOKE_ASYNC(src, .proc/fire_line, turfs)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/line_target(var/offset, var/range, var/atom/at = target)
if(!at)
return
var/angle = ATAN2(at.x - src.x, at.y - src.y) + offset
var/turf/T = get_turf(src)
for(var/i in 1 to range)
var/turf/check = locate(src.x + cos(angle) * i, src.y + sin(angle) * i, src.z)
if(!check)
break
T = check
return (getline(src, T) - get_turf(src))
/mob/living/simple_animal/hostile/megafauna/dragon/proc/fire_line(var/list/turfs)
var/list/hit_list = list()
for(var/turf/T in turfs)
if(istype(T, /turf/closed))
break
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
for(var/mob/living/L in T.contents)
if(L in hit_list || L == src)
continue continue
hit_list += L
L.adjustFireLoss(20) L.adjustFireLoss(20)
to_chat(L, "<span class='userdanger'>You're hit by the drake's fire breath!</span>") to_chat(L, "<span class='userdanger'>You're hit by [src]'s fire breath!</span>")
hit_things += L
previousturf = J
sleep(1)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/triple_swoop() // deals damage to mechs
swoop_attack(swoop_duration = 30) for(var/obj/mecha/M in T.contents)
swoop_attack(swoop_duration = 30) if(M in hit_list)
swoop_attack(swoop_duration = 30) continue
hit_list += M
M.take_damage(45, BRUTE, "melee", 1)
sleep(1.5)
/mob/living/simple_animal/hostile/megafauna/dragon/proc/swoop_attack(fire_rain, atom/movable/manual_target, swoop_duration = 40) /mob/living/simple_animal/hostile/megafauna/dragon/proc/swoop_attack(lava_arena = FALSE, atom/movable/manual_target, var/swoop_cooldown = 30)
if(stat || swooping) if(stat || swooping)
return return
if(manual_target) if(manual_target)
target = manual_target target = manual_target
if(!target) if(!target)
return return
swoop_cooldown = world.time + 200
stop_automated_movement = TRUE stop_automated_movement = TRUE
swooping |= SWOOP_DAMAGEABLE swooping |= SWOOP_DAMAGEABLE
density = FALSE density = FALSE
@@ -209,32 +310,16 @@ Difficulty: Medium
swooping |= SWOOP_INVULNERABLE swooping |= SWOOP_INVULNERABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT mouse_opacity = MOUSE_OPACITY_TRANSPARENT
sleep(7) sleep(7)
var/list/flame_hit = list()
while(swoop_duration > 0) while(target && loc != get_turf(target))
if(!target && !FindTarget())
break //we lost our target while chasing it down and couldn't get a new one
if(swoop_duration < 7)
fire_rain = FALSE //stop raining fire near the end of the swoop
if(loc == get_turf(target))
if(!fire_rain)
break //we're not spewing fire at our target, slam they
if(isliving(target))
var/mob/living/L = target
if(L.stat == DEAD)
break //target is dead and we're on em, slam they
if(fire_rain)
new /obj/effect/temp_visual/target(loc, flame_hit)
forceMove(get_step(src, get_dir(src, target))) forceMove(get_step(src, get_dir(src, target)))
if(loc == get_turf(target)) sleep(0.5)
if(!fire_rain)
break // Ash drake flies onto its target and rains fire down upon them
if(isliving(target)) var/descentTime = 10;
var/mob/living/L = target var/lava_success = 1
if(L.stat == DEAD) if(lava_arena)
break lava_success = lava_arena()
var/swoop_speed = 1.5
swoop_duration -= swoop_speed
sleep(swoop_speed)
//ensure swoop direction continuity. //ensure swoop direction continuity.
if(negative) if(negative)
@@ -245,8 +330,8 @@ Difficulty: Medium
negative = TRUE negative = TRUE
new /obj/effect/temp_visual/dragon_flight/end(loc, negative) new /obj/effect/temp_visual/dragon_flight/end(loc, negative)
new /obj/effect/temp_visual/dragon_swoop(loc) new /obj/effect/temp_visual/dragon_swoop(loc)
animate(src, alpha = 255, transform = oldtransform, time = 5) animate(src, alpha = 255, transform = oldtransform, descentTime)
sleep(5) sleep(descentTime)
swooping &= ~SWOOP_INVULNERABLE swooping &= ~SWOOP_INVULNERABLE
mouse_opacity = initial(mouse_opacity) mouse_opacity = initial(mouse_opacity)
icon_state = "dragon" icon_state = "dragon"
@@ -264,6 +349,8 @@ Difficulty: Medium
var/throwtarget = get_edge_target_turf(src, throw_dir) var/throwtarget = get_edge_target_turf(src, throw_dir)
L.throw_at(throwtarget, 3) L.throw_at(throwtarget, 3)
visible_message("<span class='warning'>[L] is thrown clear of [src]!</span>") visible_message("<span class='warning'>[L] is thrown clear of [src]!</span>")
for(var/obj/mecha/M in orange(1, src))
M.take_damage(75, BRUTE, "melee", 1)
for(var/mob/M in range(7, src)) for(var/mob/M in range(7, src))
shake_camera(M, 15, 1) shake_camera(M, 15, 1)
@@ -271,16 +358,20 @@ Difficulty: Medium
density = TRUE density = TRUE
sleep(1) sleep(1)
swooping &= ~SWOOP_DAMAGEABLE swooping &= ~SWOOP_DAMAGEABLE
SetRecoveryTime(MEGAFAUNA_DEFAULT_RECOVERY_TIME) SetRecoveryTime(swoop_cooldown)
if(!lava_success)
arena_escape_enrage()
/mob/living/simple_animal/hostile/megafauna/dragon/AltClickOn(atom/movable/A) /mob/living/simple_animal/hostile/megafauna/dragon/AltClickOn(atom/movable/A)
if(!istype(A)) if(!istype(A))
AltClickNoInteract(src, A) AltClickNoInteract(src, A)
return return
if(swoop_cooldown >= world.time) if(player_cooldown >= world.time)
to_chat(src, "<span class='warning'>You need to wait 20 seconds between swoop attacks!</span>") to_chat(src, "<span class='warning'>You need to wait [(player_cooldown - world.time) / 10] seconds before swooping again!</span>")
return return
swoop_attack(TRUE, A, 25) swoop_attack(FALSE, A)
lava_pools(10, 2) // less pools but longer delay before spawns
player_cooldown = world.time + 200 // needs seperate cooldown or cant use fire attacks
/obj/item/gps/internal/dragon /obj/item/gps/internal/dragon
icon_state = null icon_state = null
@@ -289,54 +380,63 @@ Difficulty: Medium
invisibility = 100 invisibility = 100
/obj/effect/temp_visual/fireball /obj/effect/temp_visual/lava_warning
icon = 'icons/obj/wizard.dmi' icon_state = "lavastaff_warn"
icon_state = "fireball"
name = "fireball"
desc = "Get out of the way!"
layer = FLY_LAYER
randomdir = FALSE
duration = 9
pixel_z = DRAKE_SWOOP_HEIGHT
/obj/effect/temp_visual/fireball/Initialize()
. = ..()
animate(src, pixel_z = 0, time = duration)
/obj/effect/temp_visual/target
icon = 'icons/mob/actions/actions_items.dmi'
icon_state = "sniper_zoom"
layer = BELOW_MOB_LAYER layer = BELOW_MOB_LAYER
light_range = 2 light_range = 2
duration = 9 duration = 13
/obj/effect/temp_visual/target/ex_act() /obj/effect/temp_visual/lava_warning/ex_act()
return return
/obj/effect/temp_visual/target/Initialize(mapload, list/flame_hit) /obj/effect/temp_visual/lava_warning/Initialize(mapload, var/reset_time = 10)
. = ..() . = ..()
INVOKE_ASYNC(src, .proc/fall, flame_hit) INVOKE_ASYNC(src, .proc/fall, reset_time)
src.alpha = 63.75
animate(src, alpha = 255, time = duration)
/obj/effect/temp_visual/target/proc/fall(list/flame_hit) /obj/effect/temp_visual/lava_warning/proc/fall(var/reset_time)
var/turf/T = get_turf(src) var/turf/T = get_turf(src)
playsound(T,'sound/magic/fleshtostone.ogg', 80, 1) playsound(T,'sound/magic/fleshtostone.ogg', 80, 1)
new /obj/effect/temp_visual/fireball(T)
sleep(duration) sleep(duration)
if(ismineralturf(T)) playsound(T,'sound/magic/fireball.ogg', 200, 1)
var/turf/closed/mineral/M = T
M.gets_drilled()
playsound(T, "explosion", 80, 1)
new /obj/effect/hotspot(T)
T.hotspot_expose(700, 50, 1)
for(var/mob/living/L in T.contents) for(var/mob/living/L in T.contents)
if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon)) if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon))
continue continue
if(islist(flame_hit) && !flame_hit[L]) L.adjustFireLoss(10)
L.adjustFireLoss(40) to_chat(L, "<span class='userdanger'>You fall directly into the pool of lava!</span>")
to_chat(L, "<span class='userdanger'>You're hit by the drake's fire breath!</span>")
flame_hit[L] = TRUE // deals damage to mechs
else for(var/obj/mecha/M in T.contents)
L.adjustFireLoss(10) //if we've already hit them, do way less damage M.take_damage(45, BRUTE, "melee", 1)
// changes turf to lava temporarily
if(!istype(T, /turf/closed) && !istype(T, /turf/open/lava))
var/lava_turf = /turf/open/lava/smooth
var/reset_turf = T.type
T.ChangeTurf(lava_turf)
sleep(reset_time)
T.ChangeTurf(reset_turf)
/obj/effect/temp_visual/drakewall
desc = "An ash drakes true flame."
name = "Fire Barrier"
icon = 'icons/effects/fire.dmi'
icon_state = "1"
anchored = TRUE
opacity = 0
density = TRUE
CanAtmosPass = ATMOS_PASS_DENSITY
duration = 82
color = COLOR_DARK_ORANGE
/obj/effect/temp_visual/lava_safe
icon = 'icons/obj/hand_of_god_structures.dmi'
icon_state = "trap-earth"
layer = BELOW_MOB_LAYER
light_range = 2
duration = 13
/obj/effect/temp_visual/dragon_swoop /obj/effect/temp_visual/dragon_swoop
name = "certain death" name = "certain death"
@@ -347,7 +447,7 @@ Difficulty: Medium
pixel_x = -32 pixel_x = -32
pixel_y = -32 pixel_y = -32
color = "#FF0000" color = "#FF0000"
duration = 5 duration = 10
/obj/effect/temp_visual/dragon_flight /obj/effect/temp_visual/dragon_flight
icon = 'icons/mob/lavaland/64x64megafauna.dmi' icon = 'icons/mob/lavaland/64x64megafauna.dmi'
@@ -376,7 +476,7 @@ Difficulty: Medium
/obj/effect/temp_visual/dragon_flight/end /obj/effect/temp_visual/dragon_flight/end
pixel_x = DRAKE_SWOOP_HEIGHT pixel_x = DRAKE_SWOOP_HEIGHT
pixel_z = DRAKE_SWOOP_HEIGHT pixel_z = DRAKE_SWOOP_HEIGHT
duration = 5 duration = 10
/obj/effect/temp_visual/dragon_flight/end/flight(negative) /obj/effect/temp_visual/dragon_flight/end/flight(negative)
if(negative) if(negative)
@@ -399,6 +499,16 @@ Difficulty: Medium
crusher_loot = list() crusher_loot = list()
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30) butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30)
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/AltClickOn(atom/movable/A)
if(!istype(A))
return
if(player_cooldown >= world.time)
to_chat(src, "<span class='warning'>You need to wait [(player_cooldown - world.time) / 10] seconds before swooping again!</span>")
return
swoop_attack(FALSE, A)
lava_pools(10, 2) // less pools but longer delay before spawns
player_cooldown = world.time + 200 // needs seperate cooldown or cant use fire attacks
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed //ash drake balanced around player control /mob/living/simple_animal/hostile/megafauna/dragon/lesser/transformed //ash drake balanced around player control
name = "transformed ash drake" name = "transformed ash drake"
desc = "A sentient being transformed into an ash drake" desc = "A sentient being transformed into an ash drake"
@@ -64,7 +64,6 @@
friendly_verb_simple = "groom" friendly_verb_simple = "groom"
mob_size = MOB_SIZE_SMALL mob_size = MOB_SIZE_SMALL
movement_type = FLYING movement_type = FLYING
gold_core_spawnable = FRIENDLY_SPAWN
var/parrot_damage_upper = 10 var/parrot_damage_upper = 10
var/parrot_state = PARROT_WANDER //Hunt for a perch when created var/parrot_state = PARROT_WANDER //Hunt for a perch when created
+1 -2
View File
@@ -267,8 +267,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
apply_effect(EFFECT_STUTTER, G.stunforce) apply_effect(EFFECT_STUTTER, G.stunforce)
SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK)
lastattacker = H.real_name set_last_attacker(H)
lastattackerckey = H.ckey
log_combat(H, src, "stunned") log_combat(H, src, "stunned")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
@@ -390,7 +390,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
speaking = "[DisplayTimeText(i, TRUE)] remain before causality stabilization." speaking = "[DisplayTimeText(i, TRUE)] remain before causality stabilization."
else else
speaking = "[i*0.1]..." speaking = "[i*0.1]..."
radio.talk_into(src, speaking, common_channel) radio.talk_into(src, speaking, common_channel, list(SPAN_COMMAND)) // IT GOT WORSE, LOUD TIME
sleep(10) sleep(10)
explode() explode()
@@ -715,7 +715,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
//Oh shit it's bad, time to freak out //Oh shit it's bad, time to freak out
if(damage > emergency_point) if(damage > emergency_point)
radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel) // it's bad, LETS YELL
radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel, list(SPAN_YELL))
lastwarning = REALTIMEOFDAY lastwarning = REALTIMEOFDAY
if(!has_reached_emergency) if(!has_reached_emergency)
investigate_log("has reached the emergency point for the first time.", INVESTIGATE_SUPERMATTER) investigate_log("has reached the emergency point for the first time.", INVESTIGATE_SUPERMATTER)
@@ -73,3 +73,11 @@
name = ".38 DumDum bullet casing" name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing." desc = "A .38 DumDum bullet casing."
projectile_type = /obj/item/projectile/bullet/c38/dumdum projectile_type = /obj/item/projectile/bullet/c38/dumdum
//.45-70 GOVT (Gunslinger's Derringer)
/obj/item/ammo_casing/g4570
name= ".45-70 Govt bullet casing"
desc = "An exceedingly rare .45-70 Govt bullet casing."
caliber = "45-70g"
projectile_type = /obj/item/projectile/bullet/g4570
@@ -0,0 +1,30 @@
/obj/item/ammo_box/magazine/internal/derringer
name = "derringer muzzle"
ammo_type = /obj/item/ammo_casing/c38
caliber = "38"
max_ammo = 2
multiload = FALSE
/obj/item/ammo_box/magazine/internal/derringer/ammo_count(countempties = 1)
if (!countempties)
var/boolets = 0
for(var/obj/item/ammo_casing/bullet in stored_ammo)
if(bullet.BB)
boolets++
return boolets
else
return ..()
/obj/item/ammo_box/magazine/internal/derringer/a357
name = "\improper derringer muzzle"
ammo_type = /obj/item/ammo_casing/a357
caliber = "357"
max_ammo = 2
multiload = FALSE
/obj/item/ammo_box/magazine/internal/derringer/g4570
name = "\improper derringer muzzle"
ammo_type = /obj/item/ammo_casing/g4570
caliber = "45-70g"
max_ammo = 2
multiload = FALSE

Some files were not shown because too many files have changed in this diff Show More