Merge branch 'master' into WanderingFox95-TheUnrollingPin

This commit is contained in:
WanderingFox95
2021-04-29 10:54:00 +02:00
committed by GitHub
155 changed files with 4299 additions and 1203 deletions
+1 -1
View File
@@ -10444,7 +10444,7 @@
/area/maintenance/port/fore)
"avV" = (
/obj/structure/table/wood,
/obj/item/reagent_containers/food/snacks/burger/ghost,
/obj/item/ectoplasm,
/turf/open/floor/wood,
/area/maintenance/port/fore)
"avW" = (
+1
View File
@@ -51,3 +51,4 @@
#define ORGAN_NO_SPOIL (1<<5) //Do not spoil under any circumstances
#define ORGAN_NO_DISMEMBERMENT (1<<6) //Immune to disembowelment.
#define ORGAN_EDIBLE (1<<7) //is a snack? :D
#define ORGAN_SYNTHETIC_EMP (1<<6) //Synthetic organ affected by an EMP. Deteriorates over time.
+2 -3
View File
@@ -60,6 +60,5 @@ GLOBAL_LIST_INIT(podstyles, list(\
))
//cit
#define PACK_GOODY_NONE 0
#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo
#define PACK_GOODY_PRIVATE 2 //can be bought only by privates
#define PACK_GOODY_NONE 0 // can be bought by cargo and privates
#define PACK_GOODY_PRIVATE 1 // can be bought only by privates
-3
View File
@@ -159,9 +159,6 @@
#define SHOVE_STAGGER_DURATION 35
/// how long they're off balance for
#define SHOVE_OFFBALANCE_DURATION 30
//Shove disarming item list
GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
/obj/item/gun)))
//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 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_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_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 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!
#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)
@@ -357,7 +362,6 @@
// /obj/item signals
#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 COMPONENT_NO_INTERACT 1
#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
@@ -455,6 +459,10 @@
// /datum/mutation signals
#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*******/
//Janitor
#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_LIZARD "#db004D"
#define BLOOD_COLOR_UNIVERSAL "#db3300"
#define BLOOD_COLOR_BUG "#a37c0f"
#define BLOOD_COLOR_BUG "#ffc933"
#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_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_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
#define TRAIT_LIVING_NO_DENSITY "living_no_density"
/// forces us to not render our overlays
+3
View File
@@ -136,3 +136,6 @@
// paintings
#define VV_HK_REMOVE_PAINTING "remove_painting"
//outfits
#define VV_HK_TO_OUTFIT_EDITOR "outfit_editor"
+1
View File
@@ -68,6 +68,7 @@
for(var/spath in subtypesof(/datum/species))
var/datum/species/S = new spath()
GLOB.species_list[S.id] = spath
GLOB.species_datums[S.id] = S
//Surgeries
for(var/path in subtypesof(/datum/surgery))
+3 -4
View File
@@ -1051,7 +1051,7 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
return 0
//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()
if(!icon_id || !humanoid_icon_cache[icon_id])
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")
COMPILE_OVERLAYS(body)
for(var/D in showDirs)
body.setDir(D)
COMPILE_OVERLAYS(body)
var/icon/partial = getFlatIcon(body)
var/icon/partial = getFlatIcon(body, defdir = D, no_anim = no_anim)
out_icon.Insert(partial,dir=D)
humanoid_icon_cache[icon_id] = out_icon
+2
View File
@@ -320,6 +320,8 @@ GLOBAL_LIST_INIT(nonstandard_skin_tones, list("orange"))
GLOBAL_LIST_EMPTY(species_list)
GLOBAL_LIST_EMPTY(species_datums)
/proc/age2agedescription(age)
switch(age)
if(0 to 1)
+6
View File
@@ -95,3 +95,9 @@
if("I'm feeling lucky")
chosen_id = pick(subtypesof(/datum/reagent))
return chosen_id
/proc/find_reagent_object_from_type(input)
if(GLOB.chemical_reagents_list[input]) //prefer IDs!
return GLOB.chemical_reagents_list[input]
else
return null
+23 -1
View File
@@ -11,6 +11,23 @@ GLOBAL_LIST_INIT(bitfields, list(
"TILE_BOUND" = TILE_BOUND,
"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(
"SEE_INFRA" = SEE_INFRA,
"SEE_SELF" = SEE_SELF,
@@ -32,7 +49,12 @@ GLOBAL_LIST_INIT(bitfields, list(
"UNIQUE_RENAME" = UNIQUE_RENAME,
"USES_TGUI" = USES_TGUI,
"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(
"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_EMPATH" = TRAIT_EMPATH,
"TRAIT_FRIENDLY" = TRAIT_FRIENDLY,
"TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT
"TRAIT_IWASBATONED" = TRAIT_IWASBATONED
),
/obj/item/bodypart = list(
"TRAIT_PARALYSIS" = TRAIT_PARALYSIS
+1 -2
View File
@@ -94,8 +94,7 @@
else if(hitsound)
playsound(loc, hitsound, get_clamped_volume(), 1, -1)
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
M.set_last_attacker(user)
if(force && M == user && user.client)
user.client.give_award(/datum/award/achievement/misc/selfouch, user)
+2 -2
View File
@@ -61,14 +61,14 @@ SUBSYSTEM_DEF(activity)
for(var/threat in threat_history)
. = max(threat_history[threat], .)
/datum/controller/subsystem/activity/proc/on_explosion(atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
/datum/controller/subsystem/activity/proc/on_explosion(datum/source, atom/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
if(!("explosions" in deferred_threats))
deferred_threats["explosions"] = 0
var/area/A = get_area(epicenter)
if(is_station_level(epicenter.z) && (A.area_flags & BLOBS_ALLOWED) && !istype(A, /area/asteroid))
deferred_threats["explosions"] += devastation_range**2 + heavy_impact_range**2 / 4 + light_impact_range**2 / 8 // 75 for a maxcap
/datum/controller/subsystem/activity/proc/on_death(mob/M, gibbed)
/datum/controller/subsystem/activity/proc/on_death(datum/source, mob/M, gibbed)
if(!("crew_deaths" in deferred_threats))
deferred_threats["crew_deaths"] = 0
if(M?.mind && SSjob.GetJob(M.mind.assigned_role))
+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()
/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)
UnregisterSignal(user, COMSIG_MOB_APPLY_DAMGE)
UnregisterSignal(user, COMSIG_MOB_APPLY_DAMAGE)
/// Checks if we're hitting the zone this component is covering
/datum/component/explodable/proc/is_hitting_zone(def_zone)
+1 -1
View File
@@ -52,7 +52,7 @@
return
strength -= strength / hl3_release_date
if(strength <= RAD_BACKGROUND_RADIATION)
return PROCESS_KILL
qdel(src)
/datum/component/radioactive/proc/glow_loop(atom/movable/master)
+3 -2
View File
@@ -329,12 +329,13 @@
uni_identity = generate_uni_identity()
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)
blood_type = newblood_type
unique_enzymes = generate_unique_enzymes()
uni_identity = generate_uni_identity()
generate_dna_blocks()
if(!skip_index) //I hate this
generate_dna_blocks()
features = random_features(species?.id, holder?.gender)
+2 -2
View File
@@ -34,13 +34,13 @@
. = ..()
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
if(L.stat == DEAD)
return
//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)
addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
if(INTENT_HELP)
+1 -1
View File
@@ -11,7 +11,7 @@
/datum/mutation/human/space_adaptation/New(class_ = MUT_OTHER, timer, datum/mutation/human/copymut)
..()
if(!(type in visual_indicators))
visual_indicators[type] = list(mutable_appearance('icons/effects/genetics.dmi', "fire", -MUTATIONS_LAYER))
visual_indicators[type] = list(mutable_appearance('icons/effects/genetics.dmi', "space_adapt", -MUTATIONS_LAYER))
/datum/mutation/human/space_adaptation/get_visual_indicator()
return visual_indicators[type][1]
+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
///Name of the outfit (shows up in the equip admin verb)
var/name = "Naked"
var/uniform = null
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
/// Type path of item to go in the idcard slot
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/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/internals_slot = null //ID of slot containing a gas tank
var/list/backpack_contents = null // In the list(path=count,otherpath=count) format
var/box // Internals box. Will be inserted at the start of backpack_contents
var/list/implants = null
//Type path of item to go in left hand
var/r_hand = null
/// Any clothing accessory item
var/accessory = null
var/can_be_admin_equipped = TRUE // Set to FALSE if your outfit requires runtime parameters
var/list/chameleon_extras //extra types for chameleon outfit changes, mostly guns
/// Internals box. Will be inserted at the start of backpack_contents
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)
//to be overridden for customization depending on client prefs,species etc
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)
//to be overridden for toggling internals, id binding, access etc
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)
pre_equip(H, visualsOnly, preference_source)
//Start with uniform,suit,backpack for additional slots
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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(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)
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(!backpack_contents)
@@ -97,7 +211,7 @@
if(!isnum(number))//Default to 1
number = 1
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))
var/obj/item/clothing/suit/space/hardsuit/HS = H.wear_suit
@@ -112,55 +226,178 @@
H.update_action_buttons_icon()
if(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)
H.update_body()
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)
if(!istype(H))
return
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)
I.add_fingerprint(H,1)
I.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_id)
H.wear_id.add_fingerprint(H,1)
H.wear_id.add_fingerprint(H, ignoregloves = TRUE)
if(H.w_uniform)
H.w_uniform.add_fingerprint(H,1)
H.w_uniform.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_suit)
H.wear_suit.add_fingerprint(H,1)
H.wear_suit.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_mask)
H.wear_mask.add_fingerprint(H,1)
H.wear_mask.add_fingerprint(H, ignoregloves = TRUE)
if(H.wear_neck)
H.wear_neck.add_fingerprint(H,1)
H.wear_neck.add_fingerprint(H, ignoregloves = TRUE)
if(H.head)
H.head.add_fingerprint(H,1)
H.head.add_fingerprint(H, ignoregloves = TRUE)
if(H.shoes)
H.shoes.add_fingerprint(H,1)
H.shoes.add_fingerprint(H, ignoregloves = TRUE)
if(H.gloves)
H.gloves.add_fingerprint(H,1)
H.gloves.add_fingerprint(H, ignoregloves = TRUE)
if(H.ears)
H.ears.add_fingerprint(H,1)
H.ears.add_fingerprint(H, ignoregloves = TRUE)
if(H.glasses)
H.glasses.add_fingerprint(H,1)
H.glasses.add_fingerprint(H, ignoregloves = TRUE)
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)
I.add_fingerprint(H,1)
I.add_fingerprint(H, ignoregloves = TRUE)
if(H.s_store)
H.s_store.add_fingerprint(H,1)
H.s_store.add_fingerprint(H, ignoregloves = TRUE)
if(H.l_store)
H.l_store.add_fingerprint(H,1)
H.l_store.add_fingerprint(H, ignoregloves = TRUE)
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)
I.add_fingerprint(H,1)
return 1
I.add_fingerprint(H, ignoregloves = TRUE)
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()
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
listclearnulls(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()
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)
return ..()
+18
View File
@@ -412,6 +412,24 @@ If not set, defaults to check_completion instead. Set it. It's used by cryo.
counter++
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
name = "escape"
explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody."
+234 -195
View File
@@ -1,9 +1,5 @@
#define LIMBGROWER_MAIN_MENU 1
#define LIMBGROWER_CATEGORY_MENU 2
#define LIMBGROWER_CHEMICAL_MENU 3
//use these for the menu system
/// The limbgrower. Makes organd and limbs with synthflesh and chems.
/// See [limbgrower_designs.dm] for everything we can make.
/obj/machinery/limbgrower
name = "limb grower"
desc = "It grows new limbs using Synthflesh."
@@ -15,161 +11,235 @@
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/limbgrower
var/operating = FALSE
var/disabled = FALSE
/// The category of limbs we're browing in our UI.
var/selected_category = "human"
/// If we're currently printing something.
var/busy = FALSE
var/prod_coeff = 1
/// How efficient our machine is. Better parts = less chemicals used and less power used. Range of 1 to 0.25.
var/production_coefficient = 1
/// How long it takes for us to print a limb. Affected by production_coefficient.
var/production_speed = 3 SECONDS
/// The design we're printing currently.
var/datum/design/being_built
/// Our internal techweb for limbgrower designs.
var/datum/techweb/stored_research
var/selected_category
var/screen = 1
/// All the categories of organs we can print.
var/list/categories = list(
"human" = /datum/species/human,
"lizard" = /datum/species/lizard,
"mammal" = /datum/species/mammal,
"insect" = /datum/species/insect,
"fly" = /datum/species/fly,
"plasmaman" = /datum/species/plasmaman,
"xeno" = /datum/species/xeno,
"other" = /datum/species,
)
var/list/stored_species = list()
"human",
"lizard",
"mammal",
"insect",
"fly",
"plasmaman",
"xeno",
"other",
)
var/obj/item/disk/data/dna_disk
/obj/machinery/limbgrower/Initialize()
create_reagents(100, OPENCONTAINER)
stored_research = new /datum/techweb/specialized/autounlocking/limbgrower
for(var/i in categories)
var/species = categories[i]
stored_species[i] = new species()
. = ..()
AddComponent(/datum/component/plumbing/simple_demand)
AddComponent(/datum/component/simple_rotation, ROTATION_WRENCH | ROTATION_CLOCKWISE, null, CALLBACK(src, .proc/can_be_rotated))
/obj/machinery/limbgrower/ui_interact(mob/user)
/obj/machinery/limbgrower/ui_interact(mob/user, datum/tgui/ui)
. = ..()
if(!is_operational())
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Limbgrower", src)
ui.open()
var/dat = main_win(user)
/obj/machinery/limbgrower/ui_data(mob/user)
var/list/data = list()
switch(screen)
if(LIMBGROWER_MAIN_MENU)
dat = main_win(user)
if(LIMBGROWER_CATEGORY_MENU)
dat = category_win(user,selected_category)
if(LIMBGROWER_CHEMICAL_MENU)
dat = chemical_win(user)
for(var/datum/reagent/reagent_id in reagents.reagent_list)
var/list/reagent_data = list(
reagent_name = reagent_id.name,
reagent_amount = reagent_id.volume,
reagent_type = reagent_id.type
)
data["reagents"] += list(reagent_data)
var/datum/browser/popup = new(user, "Limb Grower", name, 400, 500)
popup.set_content(dat)
popup.open()
data["total_reagents"] = reagents.total_volume
data["max_reagents"] = reagents.maximum_volume
data["busy"] = busy
var/list/disk_data = list()
disk_data["disk"] = dna_disk //Do i, the machine, have a disk?
disk_data["name"] = dna_disk?.fields["name"] //Name for the human saved if there is one
data["disk"] = disk_data
return data
/obj/machinery/limbgrower/ui_static_data(mob/user)
var/list/data = list()
data["categories"] = list()
var/species_categories = categories.Copy()
for(var/species in species_categories)
species_categories[species] = list()
for(var/design_id in stored_research.researched_designs)
var/datum/design/limb_design = SSresearch.techweb_design_by_id(design_id)
for(var/found_category in species_categories)
if(found_category in limb_design.category)
species_categories[found_category] += limb_design
for(var/category in species_categories)
var/list/category_data = list(
name = category,
designs = list(),
)
for(var/datum/design/found_design in species_categories[category])
var/list/all_reagents = list()
for(var/reagent_typepath in found_design.reagents_list)
var/datum/reagent/reagent_id = find_reagent_object_from_type(reagent_typepath)
var/list/reagent_data = list(
name = reagent_id.name,
amount = (found_design.reagents_list[reagent_typepath] * production_coefficient),
)
all_reagents += list(reagent_data)
category_data["designs"] += list(list(
parent_category = category,
name = found_design.name,
id = found_design.id,
needed_reagents = all_reagents,
))
data["categories"] += list(category_data)
return data
/obj/machinery/limbgrower/on_deconstruction()
for(var/obj/item/reagent_containers/glass/G in component_parts)
reagents.trans_to(G, G.reagents.maximum_volume)
for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
reagents.trans_to(our_beaker, our_beaker.reagents.maximum_volume)
..()
/obj/machinery/limbgrower/attackby(obj/item/O, mob/user, params)
if(busy)
/obj/machinery/limbgrower/attackby(obj/item/user_item, mob/living/user, params)
if (busy)
to_chat(user, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
return
if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", O))
updateUsrDialog()
if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", user_item))
ui_close(user)
return
if(panel_open && default_deconstruction_crowbar(O))
return
if(user.a_intent == INTENT_HARM) //so we can hit the machine
if(user_item.tool_behaviour == TOOL_WRENCH && panel_open)
return ..()
if(istype(O, /obj/item/disk))
if(panel_open && default_deconstruction_crowbar(user_item))
return
if(istype(user_item, /obj/item/disk))
if(dna_disk)
to_chat(user, "<span class='warning'>\The [src] already has a dna disk, take it out first!</span>")
return
else
O.forceMove(src)
dna_disk = O
to_chat(user, "<span class='notice'>You insert \the [O] into \the [src].</span>")
user_item.forceMove(src)
dna_disk = user_item
to_chat(user, "<span class='notice'>You insert \the [user_item] into \the [src].</span>")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
return
/obj/machinery/limbgrower/Topic(href, href_list)
if(..())
if(user.a_intent != INTENT_HELP)
return ..()
/obj/machinery/limbgrower/proc/can_be_rotated()
if(panel_open)
return TRUE
return FALSE
/obj/machinery/limbgrower/ui_act(action, list/params)
. = ..()
if(.)
return
if (!busy)
if(href_list["menu"])
screen = text2num(href_list["menu"])
if(href_list["category"])
selected_category = href_list["category"]
if (busy)
to_chat(usr, "<span class='danger'>\The [src] is busy. Please wait for completion of previous operation.</span>")
return
if(href_list["disposeI"]) //Get rid of a reagent incase you add the wrong one by mistake
reagents.del_reagent(text2path(href_list["disposeI"]))
switch(action)
if(href_list["make"])
if("empty_reagent")
reagents.del_reagent(text2path(params["reagent_type"]))
. = TRUE
/////////////////
//href protection
being_built = stored_research.isDesignResearchedID(href_list["make"]) //check if it's a valid design
if("eject_disk")
eject_disk(usr)
if("make_limb")
being_built = stored_research.isDesignResearchedID(params["design_id"])
if(!being_built)
return
CRASH("[src] was passed an invalid design id!")
/// All the reagents we're using to make our organ.
var/list/consumed_reagents_list = being_built.reagents_list.Copy()
/// The amount of power we're going to use, based on how much reagent we use.
var/power = 0
var/synth_cost = being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff
var/power = max(2000, synth_cost/5)
for(var/reagent_id in consumed_reagents_list)
consumed_reagents_list[reagent_id] *= production_coefficient
if(!reagents.has_reagent(reagent_id, consumed_reagents_list[reagent_id]))
audible_message("<span class='notice'>\The [src] buzzes.</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
return
if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff))
busy = TRUE
use_power(power)
flick("limbgrower_fill",src)
icon_state = "limbgrower_idleon"
addtimer(CALLBACK(src, .proc/build_item),32*prod_coeff)
power = max(2000, (power + consumed_reagents_list[reagent_id]))
if(href_list["dna_disk"])
var/mob/living/carbon/user = usr
if(istype(user))
if(!dna_disk)
var/obj/item/disk/diskette = user.get_active_held_item()
if(istype(diskette))
diskette.forceMove(src)
dna_disk = diskette
to_chat(user, "<span class='notice'>You insert \the [diskette] into \the [src].</span>")
else
dna_disk.forceMove(src.loc)
user.put_in_active_hand(dna_disk)
to_chat(user, "<span class='notice'>You remove \the [dna_disk] from \the [src].</span>")
dna_disk = null
else
to_chat(user, "<span class='warning'>You are unable to grasp \the [dna_disk] disk from \the [src].</span>")
else
to_chat(usr, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
busy = TRUE
use_power(power)
flick("limbgrower_fill",src)
icon_state = "limbgrower_idleon"
selected_category = params["active_tab"]
addtimer(CALLBACK(src, .proc/build_item, consumed_reagents_list), production_speed * production_coefficient)
. = TRUE
updateUsrDialog()
return
/obj/machinery/limbgrower/proc/build_item()
if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //sanity check, if this happens we are in big trouble
reagents.remove_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)
var/buildpath = being_built.build_path
if(ispath(buildpath, /obj/item/bodypart)) //This feels like spaghetti code, but i need to initiliaze a limb somehow
build_limb(buildpath)
else if(ispath(buildpath, /obj/item/organ/genital)) //genitals are uhh... customizable
build_genital(buildpath)
else
//Just build whatever it is
new buildpath(loc)
else
src.visible_message("<span class=\"error\"> Something went very wrong and there isnt enough synthflesh anymore!</span>")
busy = FALSE
flick("limbgrower_unfill",src)
icon_state = "limbgrower_idleoff"
updateUsrDialog()
/*
* The process of beginning to build a limb or organ.
* Goes through and sanity checks that we actually have enough reagent to build our item.
* Then, remove those reagents from our reagents datum.
*
* After the reagents are handled, we can proceede with making the limb or organ. (Limbs are handled in a separate proc)
*
* modified_consumed_reagents_list - the list of reagents we will consume on build, modified by the production coefficient.
*/
/obj/machinery/limbgrower/proc/build_item(list/modified_consumed_reagents_list)
for(var/reagent_id in modified_consumed_reagents_list)
if(!reagents.has_reagent(reagent_id, modified_consumed_reagents_list[reagent_id]))
audible_message("<span class='notice'>\The [src] buzzes.</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
break
/obj/machinery/limbgrower/proc/build_limb(buildpath)
reagents.remove_reagent(reagent_id, modified_consumed_reagents_list[reagent_id])
var/built_typepath = being_built.build_path
// If we have a bodypart, we need to initialize the limb on its own. Otherwise we can build it here.
if(ispath(built_typepath, /obj/item/bodypart))
build_limb(built_typepath)
else if(ispath(built_typepath, /obj/item/organ/genital)) //genitals are uhh... customizable
build_genital(built_typepath)
else
new built_typepath(loc)
busy = FALSE
flick("limbgrower_unfill", src)
icon_state = "limbgrower_idleoff"
/*
* The process of putting together a limb.
* This is called from after we remove the reagents, so this proc is just initializing the limb type.
*
* This proc handles skin / mutant color, greyscaling, names and descriptions, and various other limb creation steps.
*
* built_typepath - the path of the bodypart we're building.
*/
/obj/machinery/limbgrower/proc/build_limb(built_typepath)
//i need to create a body part manually using a set icon (otherwise it doesnt appear)
var/obj/item/bodypart/limb
var/datum/species/selected = stored_species[selected_category]
limb = new buildpath(loc)
var/datum/species/selected = GLOB.species_datums[selected_category]
limb = new built_typepath(loc)
limb.base_bp_icon = selected.icon_limbs || DEFAULT_BODYPART_ICON_ORGANIC
limb.species_id = selected.limbs_id
limb.color_src = (MUTCOLORS in selected.species_traits ? MUTCOLORS : (selected.use_skintones ? SKINTONE : FALSE))
@@ -189,135 +259,103 @@
BP.name = "\improper synthetic [lowertext(selected.name)] [limb.name]"
BP.desc = "A synthetic [selected_category] limb that will morph on its first use in surgery. This one is for the [parse_zone(limb.body_zone)]."
/obj/machinery/limbgrower/proc/build_genital(buildpath)
/*
* Builds genitals, modifies to be the same
* as the person's cloning data on the data disk
*/
/obj/machinery/limbgrower/proc/build_genital(built_typepath)
//i needed to create a way to customize gene tools using dna
var/list/features = dna_disk?.fields["features"]
if(length(features))
switch(buildpath)
switch(built_typepath)
if(/obj/item/organ/genital/penis)
var/obj/item/organ/genital/penis/penis = new(loc)
if(features["has_cock"])
penis.shape = features["cock_shape"]
penis.length = features["cock_shape"]
penis.diameter_ratio = features["cock_diameter_ratio"]
penis.color = sanitize_hexcolor(features["cock_color"], 6)
penis.update_icon()
penis.color = sanitize_hexcolor(features["cock_color"], 6, TRUE)
penis.update()
if(/obj/item/organ/genital/testicles)
var/obj/item/organ/genital/testicles/balls = new(loc)
if(features["has_balls"])
balls.color = sanitize_hexcolor(features["balls_color"], 6)
balls.color = sanitize_hexcolor(features["balls_color"], 6, TRUE)
balls.shape = features["balls_shape"]
balls.size = features["balls_size"]
balls.fluid_rate = features["balls_cum_rate"]
balls.fluid_mult = features["balls_cum_mult"]
balls.fluid_efficiency = features["balls_efficiency"]
balls.update()
if(/obj/item/organ/genital/vagina)
var/obj/item/organ/genital/vagina/vegana = new(loc)
if(features["has_vagina"])
vegana.color = sanitize_hexcolor(features["vag_color"], 6)
if(features["has_vag"])
vegana.color = sanitize_hexcolor(features["vag_color"], 6, TRUE)
vegana.shape = features["vag_shape"]
vegana.update()
if(/obj/item/organ/genital/breasts)
var/obj/item/organ/genital/breasts/boobs = new(loc)
if(features["has_breasts"])
boobs.color = sanitize_hexcolor(features["breasts_color"], 6)
boobs.color = sanitize_hexcolor(features["breasts_color"], 6, TRUE)
boobs.size = features["breasts_size"]
boobs.shape = features["breasts_shape"]
if(!features["breasts_producing"])
boobs.genital_flags &= ~(GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH)
boobs.update()
else
new buildpath(loc)
new built_typepath(loc)
else
new buildpath(loc)
new built_typepath(loc)
/obj/machinery/limbgrower/RefreshParts()
reagents.maximum_volume = 0
for(var/obj/item/reagent_containers/glass/G in component_parts)
reagents.maximum_volume += G.volume
G.reagents.trans_to(src, G.reagents.total_volume)
var/T=1.2
for(var/obj/item/stock_parts/manipulator/M in component_parts)
T -= M.rating*0.2
prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4
for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
reagents.maximum_volume += our_beaker.volume
our_beaker.reagents.trans_to(src, our_beaker.reagents.total_volume)
production_coefficient = 1.2
for(var/obj/item/stock_parts/manipulator/our_manipulator in component_parts)
production_coefficient -= our_manipulator.rating * 0.2
production_coefficient = clamp(production_coefficient, 0, 1) // coefficient goes from 1 -> 0.8 -> 0.6 -> 0.4
/obj/machinery/limbgrower/examine(mob/user)
. = ..()
if(!panel_open)
. += "<span class='notice'>It looks like as if the panel were open you could rotate it with a <b>wrench</b>.</span>"
else
. += "<span class='notice'>The panel is open.</span>"
if(in_range(user, src) || isobserver(user))
. += "<span class='notice'>The status display reads: Storing up to <b>[reagents.maximum_volume]u</b> of synthflesh.<br>Synthflesh consumption at <b>[prod_coeff*100]%</b>.<span>"
. += "<span class='notice'>The status display reads: Storing up to <b>[reagents.maximum_volume]u</b> of reagents.<br>Reagent consumption rate at <b>[production_coefficient * 100]%</b>.</span>"
/obj/machinery/limbgrower/proc/main_win(mob/user)
var/dat = "<div class='statusDisplay'><h3>[src] Menu:</h3><br>"
dat += "<A href='?src=[REF(src)];dna_disk=1'>[dna_disk ? "Remove" : "Insert"] cloning data disk</A>"
dat += "<hr>"
dat += "<A href='?src=[REF(src)];menu=[LIMBGROWER_CHEMICAL_MENU]'>Chemical Storage</A>"
dat += materials_printout()
dat += "<table style='width:100%' align='center'><tr>"
for(var/C in categories)
dat += "<td><A href='?src=[REF(src)];category=[C];menu=[LIMBGROWER_CATEGORY_MENU]'>[C]</A></td>"
dat += "</tr><tr>"
//one category per line
dat += "</tr></table></div>"
return dat
/obj/machinery/limbgrower/proc/category_win(mob/user,selected_category)
var/dat = "<A href='?src=[REF(src)];menu=[LIMBGROWER_MAIN_MENU]'>Return to main menu</A>"
dat += "<div class='statusDisplay'><h3>Browsing [selected_category]:</h3><br>"
dat += materials_printout()
for(var/v in stored_research.researched_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(v)
if(!(selected_category in D.category))
continue
if(disabled || !can_build(D))
dat += "<span class='linkOff'>[D.name]</span>"
else
dat += "<a href='?src=[REF(src)];make=[D.id];multiplier=1'>[D.name]</a>"
dat += "[get_design_cost(D)]<br>"
dat += "</div>"
return dat
/obj/machinery/limbgrower/proc/chemical_win(mob/user)
var/dat = "<A href='?src=[REF(src)];menu=[LIMBGROWER_MAIN_MENU]'>Return to main menu</A>"
dat += "<div class='statusDisplay'><h3>Browsing Chemical Storage:</h3><br>"
dat += materials_printout()
for(var/datum/reagent/R in reagents.reagent_list)
dat += "[R.name]: [R.volume]"
dat += "<A href='?src=[REF(src)];disposeI=[R]'>Purge</A><BR>"
dat += "</div>"
return dat
/obj/machinery/limbgrower/proc/materials_printout()
var/dat = "<b>Total amount:></b> [reagents.total_volume] / [reagents.maximum_volume] cm<sup>3</sup><br>"
return dat
/obj/machinery/limbgrower/proc/can_build(datum/design/D)
return (reagents.has_reagent(/datum/reagent/medicine/synthflesh, D.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //Return whether the machine has enough synthflesh to produce the design
/obj/machinery/limbgrower/proc/get_design_cost(datum/design/D)
var/dat
if(D.reagents_list[/datum/reagent/medicine/synthflesh])
dat += "[D.reagents_list[/datum/reagent/medicine/synthflesh] * prod_coeff] Synthetic flesh "
return dat
/*
* Checks our reagent list to see if a design can be built.
*
* limb_design - the design we're checking for buildability.
*
* returns TRUE if we have enough reagent to build it. Returns FALSE if we do not.
*/
/obj/machinery/limbgrower/proc/can_build(datum/design/limb_design)
for(var/datum/reagent/reagent_id in limb_design.reagents_list)
if(!reagents.has_reagent(reagent_id, limb_design.reagents_list[reagent_id] * production_coefficient))
return FALSE
return TRUE
/// Emagging a limbgrower allows you to build synthetic armblades.
/obj/machinery/limbgrower/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
for(var/id in SSresearch.techweb_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
if((D.build_type & LIMBGROWER) && ("emagged" in D.category))
stored_research.add_design(D)
for(var/design_id in SSresearch.techweb_designs)
var/datum/design/found_design = SSresearch.techweb_design_by_id(design_id)
if((found_design.build_type & LIMBGROWER) && ("emagged" in found_design.category))
stored_research.add_design(found_design)
to_chat(user, "<span class='warning'>A warning flashes onto the screen, stating that safety overrides have been deactivated!</span>")
obj_flags |= EMAGGED
return TRUE
update_static_data(user)
/obj/machinery/limbgrower/AltClick(mob/living/user)
. = ..()
eject_disk(user)
/obj/machinery/limbgrower/proc/eject_disk(mob/user)
if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
if(busy)
to_chat(user, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
@@ -326,6 +364,7 @@
dna_disk.forceMove(src.loc)
user.put_in_active_hand(dna_disk)
to_chat(user, "<span class='notice'>You remove \the [dna_disk] from \the [src].</span>")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
dna_disk = null
else
to_chat(user, "<span class='warning'>\The [src] has doesn't have a disk on it!")
+20
View File
@@ -21,6 +21,7 @@ Buildable meters
level = 2
var/piping_layer = PIPING_LAYER_DEFAULT
var/RPD_type
var/disposable = TRUE
/obj/item/pipe/directional
RPD_type = PIPE_UNARY
@@ -236,3 +237,22 @@ Buildable meters
/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT)
piping_layer = new_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 ..()
+8 -6
View File
@@ -375,12 +375,14 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
. = 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))
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
if(do_after(user, destroy_speed, target = A))
activate()
qdel(A)
return
var/obj/item/pipe/P = A
if(!istype(P) || P.disposable)
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
if(do_after(user, destroy_speed, target = A))
activate()
qdel(A)
return
if((mode & PAINT_MODE))
if(istype(A, /obj/machinery/atmospherics/pipe) && !istype(A, /obj/machinery/atmospherics/pipe/layer_manifold))
+7 -1
View File
@@ -157,7 +157,7 @@
to_chat(user, "<span class='notice'>You need to get closer!</span>")
return
if(use_paint(user) && isturf(F))
F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, turn(stored_dir, -dir2angle(F.dir)), CLEAN_STRONG, color, null, null, alpha)
F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha)
/obj/item/airlock_painter/decal/attack_self(mob/user)
if((ink) && (ink.charges >= 1))
@@ -180,6 +180,11 @@
stored_decal_total = "[stored_decal][yellow_fix][stored_color]"
return
/obj/item/airlock_painter/decal/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/decals)
)
/obj/item/airlock_painter/decal/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -189,6 +194,7 @@
/obj/item/airlock_painter/decal/ui_data(mob/user)
var/list/data = list()
data["decal_direction"] = stored_dir
data["decal_dir_text"] = dir2text(stored_dir)
data["decal_color"] = stored_color
data["decal_style"] = stored_decal
data["decal_list"] = list()
-15
View File
@@ -75,18 +75,3 @@
desc = "A chromosome that reduces action based mutation cooldowns by by 50%."
icon_state = "energy"
energy_coeff = 0.5
/obj/item/chromosome/reinforcer
name = "reinforcement chromosome"
desc = "A chromosome that renders mutations immune to mutadone."
icon_state = "reinforcer"
weight = 3
/obj/item/chromosome/reinforcer/can_apply(datum/mutation/human/HM)
if(!HM || !(HM.can_chromosome == CHROMOSOME_NONE))
return FALSE
return !HM.mutadone_proof
/obj/item/chromosome/reinforcer/apply(datum/mutation/human/HM)
HM.mutadone_proof = TRUE
..()
+2 -4
View File
@@ -210,8 +210,7 @@
target.apply_effect(EFFECT_STUTTER, stunforce)
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
if(user)
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
target.set_last_attacker(user)
target.visible_message("<span class='danger'>[user] has shocked [target] with [src]!</span>", \
"<span class='userdanger'>[user] has shocked you with [src]!</span>")
log_combat(user, target, "stunned with an electrostaff")
@@ -237,8 +236,7 @@
target.adjustFireLoss(lethal_force) //good against ointment spam
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
if(user)
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
target.set_last_attacker(user)
target.visible_message("<span class='danger'>[user] has seared [target] with [src]!</span>", \
"<span class='userdanger'>[user] has seared you with [src]!</span>")
log_combat(user, target, "burned with an electrostaff")
+1 -1
View File
@@ -312,7 +312,7 @@
trap_damage = 0
item_flags = DROPDEL
flags_1 = NONE
breakouttime = 50
breakouttime = 25
/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
+2 -2
View File
@@ -135,8 +135,8 @@
/obj/item/organ/cyberimp/arm/toolset,
/obj/item/organ/cyberimp/arm/surgery,
/obj/item/organ/cyberimp/chest/thrusters,
/obj/item/organ/lungs/cybernetic,
/obj/item/organ/liver/cybernetic) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof
/obj/item/organ/lungs/cybernetic/tier3,
/obj/item/organ/liver/cybernetic/tier3) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof
for(var/V in templist)
var/atom/A = V
augment_list[initial(A.name)] = A
@@ -272,7 +272,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
. = ..()
. += 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.efficiency *= 2
+69
View File
@@ -351,6 +351,75 @@
if(!contents.len)
. += "[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//
/////////////
@@ -546,3 +546,21 @@
. = ..()
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/mining_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)
. = ..()
+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)
attack_speed = CLICK_CD_MELEE
var/stamforce = 35
var/stamina_loss_amount = 35
var/turned_on = FALSE
var/knockdown = TRUE
var/obj/item/stock_parts/cell/cell
var/hitcost = 750
var/throw_hit_chance = 35
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)
. = ..()
. += "<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()
. = cell
@@ -53,8 +57,8 @@
/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
..()
//Only mob/living types have stun handling
if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom))
baton_stun(hit_atom)
if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom) && thrownby)
baton_stun(hit_atom, thrownby, shoving = TRUE)
/obj/item/melee/baton/loaded //this one starts with a cell pre-installed.
preload_cell_type = /obj/item/stock_parts/cell/high/plus
@@ -74,6 +78,16 @@
//we're below minimum, turn off
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)
if(turned_on != new_status)
turned_on = new_status
@@ -101,6 +115,7 @@
var/obj/item/stock_parts/cell/copper_top = get_cell()
if(copper_top)
. += "<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
. += "<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)
if(!user.CheckActionCooldown(CLICK_CD_MELEE))
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.
/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
return FALSE
if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
@@ -167,21 +182,26 @@
if(check_martial_counter(L, user))
return TRUE
if(turned_on)
if(baton_stun(M, user, disarming))
if(baton_stun(M, user, shoving))
user.do_attack_animation(M)
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>", \
"<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()
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)
return FALSE
var/stunpwr = stamforce
stunpwr = block_calculate_resultant_damage(stunpwr, return_list)
var/final_stamina_loss_amount = stamina_loss_amount //Our stunning power for the baton
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()
if(!our_cell)
switch_status(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>", \
"<span class='warning'>[user] has prodded you with [src]. Luckily it was out of charge.</span>")
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))
return FALSE
if(!disarming)
if(knockdown)
L.DefaultCombatKnockdown(50, override_stamdmg = 0) //knockdown
L.adjustStaminaLoss(stunpwr)
else
L.drop_all_held_items() //no knockdown/stamina damage, instead disarm.
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.
L.DefaultCombatKnockdown(50, override_stamdmg = 0)
L.apply_status_effect(STATUS_EFFECT_TASED_WEAK, status_duration) //Even if they shove themselves up, they're still slowed.
L.apply_status_effect(STATUS_EFFECT_OFF_BALANCE, status_duration) //They're very likely to drop items if shoved briefly after a knockdown.
shoved = TRUE
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)
if(user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
L.visible_message("<span class='danger'>[user] has [disarming? "disarmed" : "stunned"] [L] with [src]!</span>", \
"<span class='userdanger'>[user] has [disarming? "disarmed" : "stunned"] you with [src]!</span>")
log_combat(user, L, disarming? "disarmed" : "stunned")
L.set_last_attacker(user)
L.visible_message("<span class='danger'>[user] has [shoved ? "brutally stunned" : "stunned"] [L] with [src]!</span>", \
"<span class='userdanger'>[user] has [shoved ? "brutally stunnned" : "stunned"] you with [src]!</span>")
log_combat(user, L, shoved ? "stunned and attempted knockdown" : "stunned")
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>", \
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
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)
deductcharge(hitcost)
@@ -306,18 +331,20 @@
w_class = WEIGHT_CLASS_BULKY
force = 3
throwforce = 5
stamforce = 25
stamina_loss_amount = 25
hitcost = 1000
throw_hit_chance = 10
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
/obj/item/melee/baton/cattleprod/Initialize()
. = ..()
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()
. = ..()
@@ -344,8 +371,8 @@
/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(turned_on)
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
baton_stun(hit_atom)
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, thrownby, shoving = TRUE)
if(thrownby && !caught)
sleep(1)
if(!QDELETED(src))
+2 -2
View File
@@ -6,7 +6,7 @@
item_state = "teleprod"
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)
return
@@ -16,7 +16,7 @@
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>")
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)
if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE))
deductcharge(hitcost)
@@ -19,3 +19,50 @@
desc = "A sturdier card-locked storage unit used for bulky shipments."
max_integrity = 500 // Same as crates.
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
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"
random = TRUE
icon = 'icons/obj/machines/sleeper.dmi'
@@ -608,6 +608,54 @@
assignedrole = "Space Pirate"
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)
new_spawn.fully_replace_character_name(new_spawn.real_name,generate_pirate_name())
new_spawn.mind.add_antag_datum(/datum/antagonist/pirate)
@@ -199,8 +199,7 @@
return TRUE
/obj/item/gun_control/attack(mob/living/M, mob/living/user)
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
M.set_last_attacker(user)
M.attacked_by(src, user)
add_fingerprint(user)
+1 -1
View File
@@ -554,7 +554,7 @@
if(B.cell)
if(B.cell.charge > 0 && B.turned_on)
flick("baton_active", src)
var/stunforce = B.stamforce
var/stunforce = B.stamina_loss_amount
user.DefaultCombatKnockdown(stunforce * 2)
user.stuttering = stunforce/20
B.deductcharge(B.hitcost)
+1 -1
View File
@@ -428,7 +428,7 @@
output += "<option value='[j]'>[j]</option>"
for(var/j in GLOB.nonhuman_positions)
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 += "</select></td></tr></table>"
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_PROTECT(admin_verbs_sounds)
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/drop_bomb,
/client/proc/set_dynex_scale,
@@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/play_local_sound,
/client/proc/play_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/drop_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 += "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>"
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()
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
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
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>"
+22 -44
View File
@@ -484,74 +484,52 @@
set name = "Test Areas (ALL)"
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()
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)
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))
return
if (outfits[dresscode])
dresscode = outfits[dresscode]
if(dresscode == "Cancel")
return
if (dresscode == "As Job...")
var/list/job_paths = subtypesof(/datum/outfit/job)
var/list/job_outfits = list()
for(var/path in job_paths)
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]
if(isnull(dresscode))
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")
var/list/custom_names = list()
for(var/datum/outfit/D in GLOB.custom_outfits)
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]
if(isnull(dresscode))
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)].")
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()
set category = "Debug"
set name = "Create Custom Outfit"
@@ -1547,6 +1545,55 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
msg += "</UL></BODY></HTML>"
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)
if(!check_rights(R_ADMIN))
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)]
if(istype(M))
M.name = "[name] Training"
owner.AddComponent(/datum/component/activity)
SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src)
/datum/antagonist/proc/is_banned(mob/M)
@@ -141,6 +142,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/datum/team/team = get_team()
if(team)
team.remove_member(owner)
// we don't remove the activity component on purpose--no real point to it
qdel(src)
/datum/antagonist/proc/greet()
@@ -511,8 +511,7 @@
/obj/item/abductor/baton/proc/StunAttack(mob/living/L,mob/living/user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
L.set_last_attacker(user)
L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement
L.DefaultCombatKnockdown(140)
+1 -2
View File
@@ -385,8 +385,7 @@
qdel(src)
return
log_combat(user, M, "used a cult spell on", source.name, "")
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
M.set_last_attacker(user)
/obj/item/melee/blood_magic/afterattack(atom/target, mob/living/carbon/user, proximity)
. = ..()
@@ -1,6 +1,6 @@
//Revenants: based off of wraiths from Goon
//"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
#define INVISIBILITY_REVENANT 50
@@ -77,6 +77,7 @@
/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/telepathy/revenant(null))
AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null))
@@ -20,7 +20,7 @@
continue
if(!SSpersistence.IsValidDebrisLocation(C.loc, allowed_turf_typecache, allowed_z_cache, C.type, FALSE))
continue
weight += 0.05
weight += 0.03
return ..()
/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"
employer = "Donk Corporation"
weight = 0
weight = 6
chaos = 1
threat = 2
/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)
assassin_prob = 70
@@ -1,12 +1,12 @@
/datum/traitor_class/human/freeform
name = "Waffle Co Agent"
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
/datum/traitor_class/human/freeform/forge_objectives(datum/antagonist/traitor/T)
var/datum/objective/escape/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!"
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! Don't get captured or killed, but if you've done nothing, you'll be in trouble!"
O.owner = T.owner
T.add_objective(O)
return
@@ -13,8 +13,12 @@
T.assign_exchange_role(SSticker.mode.exchange_blue)
objective_count += 1 //Exchange counts towards number of objectives
var/toa = CONFIG_GET(number/traitor_objectives_amount)
var/attempts = 0
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))
var/datum/objective/escape/escape_objective = new
escape_objective.owner = T.owner
@@ -1,12 +1,11 @@
/datum/traitor_class/human/subterfuge
name = "MI13 Operative"
employer = "MI13"
weight = 36
weight = 25
chaos = -5
var/assassin_prob = 25
/datum/traitor_class/human/subterfuge/forge_single_objective(datum/antagonist/traitor/T)
.=1
var/assassin_prob = 30
var/datum/game_mode/dynamic/mode
if(istype(SSticker.mode,/datum/game_mode/dynamic))
mode = SSticker.mode
@@ -16,24 +15,31 @@
kill_objective.owner = T.owner
kill_objective.find_target()
T.add_objective(kill_objective)
return TRUE
else
var/list/weights = list()
weights["sabo"] = length(subtypesof(/datum/sabotage_objective))
weights["steal"] = length(subtypesof(/datum/objective_item/steal))
var/datum/objective/sabotage/sabotage_objective = new
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")))
switch(pickweight(weights))
if("sabo")
var/datum/objective/sabotage/sabotage_objective = new
sabotage_objective.owner = T.owner
sabotage_objective.find_target()
T.add_objective(sabotage_objective)
qdel(steal_objective)
return TRUE
if("steal")
var/datum/objective/steal/steal_objective = new
steal_objective.owner = T.owner
steal_objective.find_target()
T.add_objective(steal_objective)
qdel(sabotage_objective)
return TRUE
if("download")
var/datum/objective/download/download_objective = new
download_objective.owner = T.owner
download_objective.gen_amount_goal()
T.add_objective(download_objective)
return TRUE
return FALSE
@@ -7,7 +7,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
var/chaos = 0
var/threat = 0
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/list/uplink_filters
@@ -43,4 +44,6 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/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.
/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)
traitor_kind.remove_innate_effects(owner.current)
traitor_kind.clean_up_traitor(src)
if(traitor_kind.processing)
STOP_PROCESSING(SSprocessing, src)
swap_from_old = TRUE
traitor_kind = GLOB.traitor_classes[kind]
traitor_kind.apply_innate_effects(owner.current)
@@ -33,11 +35,16 @@
for(var/O in objectives)
qdel(O)
traitor_kind.forge_objectives(src)
if(traitor_kind.processing)
START_PROCESSING(SSprocessing, src)
if(swap_from_old)
traitor_kind.finalize_traitor(src)
traitor_kind.greet(src)
owner.announce_objectives()
/datum/antagonist/traitor/process()
traitor_kind.on_process(src)
/proc/get_random_traitor_kind(var/list/blacklist = list())
var/chaos_weight = 0
if(istype(SSticker.mode,/datum/game_mode/dynamic))
@@ -345,6 +345,14 @@
InsertAll("", each, GLOB.alldirs)
..()
/datum/asset/spritesheet/decals
name = "decals"
/datum/asset/spritesheet/decals/register()
for(var/each in list('icons/turf/decals.dmi'))
InsertAll("", each, GLOB.alldirs)
..()
/datum/asset/spritesheet/supplypods
name = "supplypods"
@@ -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
stock_max = 3
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
@@ -75,7 +75,7 @@
cost = 250
unit_name = "heart"
export_types = list(/obj/item/organ/heart)
exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic)
exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic/tier2, /obj/item/organ/heart/cybernetic/tier3)
/datum/export/organs/tongue
cost = 75
@@ -92,29 +92,30 @@
cost = 50 //can be replaced
unit_name = "stomach"
export_types = list(/obj/item/organ/stomach)
exclude_types = list(/obj/item/organ/stomach/cybernetic/tier2, /obj/item/organ/stomach/cybernetic/tier3)
/datum/export/organs/lungs
cost = 150
unit_name = "lungs"
export_types = list(/obj/item/organ/lungs)
exclude_types = list(/obj/item/organ/lungs/cybernetic, /obj/item/organ/lungs/cybernetic/upgraded)
export_types = list(/obj/item/organ/lungs,)
exclude_types = list(/obj/item/organ/lungs/cybernetic/tier2, /obj/item/organ/lungs/cybernetic/tier3)
/datum/export/organs/liver
cost = 175
unit_name = "liver"
export_types = list(/obj/item/organ/liver)
exclude_types = list(/obj/item/organ/liver/cybernetic, /obj/item/organ/liver/cybernetic/upgraded)
exclude_types = list(/obj/item/organ/liver/cybernetic/tier2, /obj/item/organ/liver/cybernetic/tier3)
/datum/export/organs/cybernetic
cost = 225
unit_name = "cybernetic organ"
export_types = list(/obj/item/organ/liver/cybernetic, /obj/item/organ/lungs/cybernetic, /obj/item/organ/eyes/robotic, /obj/item/organ/heart/cybernetic)
exclude_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded)
export_types = list(/obj/item/organ/liver/cybernetic/tier2, /obj/item/organ/lungs/cybernetic/tier2, /obj/item/organ/eyes/robotic/shield, /obj/item/organ/eyes/robotic/glow, /obj/item/organ/stomach/cybernetic/tier2, /obj/item/organ/heart/cybernetic/tier2)
exclude_types = list(/obj/item/organ/liver/cybernetic/tier3, /obj/item/organ/lungs/cybernetic/tier3, /obj/item/organ/eyes/robotic/xray, /obj/item/organ/eyes/robotic/thermals, /obj/item/organ/stomach/cybernetic/tier3, /obj/item/organ/heart/cybernetic/tier3)
/datum/export/organs/upgraded
cost = 275
unit_name = "upgraded cybernetic organ"
export_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded)
export_types = list(/obj/item/organ/liver/cybernetic/tier3, /obj/item/organ/lungs/cybernetic/tier3, /obj/item/organ/eyes/robotic/xray, /obj/item/organ/eyes/robotic/thermals, /obj/item/organ/stomach/cybernetic/tier3, /obj/item/organ/heart/cybernetic/tier3)
/datum/export/organs/tail // yeah have fun pulling this off someone without catching a bwoink
cost = 500
+5 -1
View File
@@ -15,13 +15,17 @@
var/special_enabled = FALSE
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?
// 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/can_private_buy = TRUE //Can it be purchased privately by each crewmember?
/datum/supply_pack/proc/generate(atom/A, datum/bank_account/paying_account)
var/obj/structure/closet/crate/C
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]"
else
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."
cost = 1500
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."
cost = 500
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
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Cardboard Sheets"
desc = "Create a bunch of boxes."
cost = 300 //thrice their export value
contains = list(/obj/item/stack/sheet/cardboard/fifty)
/datum/supply_pack/materials/glass50
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Glass Sheets"
desc = "Let some nice light in with fifty glass sheets!"
cost = 300 //double their export value
contains = list(/obj/item/stack/sheet/glass/fifty)
/datum/supply_pack/materials/metal50
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Metal Sheets"
desc = "Any construction project begins with a good stack of fifty metal sheets!"
cost = 300 //double their export value
contains = list(/obj/item/stack/sheet/metal/fifty)
/datum/supply_pack/materials/plasteel20
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "20 Plasteel Sheets"
desc = "Reinforce the station's integrity with twenty plasteel sheets!"
cost = 4000
contains = list(/obj/item/stack/sheet/plasteel/twenty)
/datum/supply_pack/materials/plastic50
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "50 Plastic Sheets"
desc = "Build a limitless amount of toys with fifty plastic sheets!"
cost = 200 // double their export
contains = list(/obj/item/stack/sheet/plastic/twenty)
/datum/supply_pack/materials/sandstone30
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "30 Sandstone Blocks"
desc = "Neither sandy nor stoney, these thirty blocks will still get the job done."
cost = 150 // five times their export
contains = list(/obj/item/stack/sheet/mineral/sandstone/thirty)
/datum/supply_pack/materials/wood20
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
name = "20 Wood 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
contains = list(/obj/item/stack/sheet/mineral/wood/twenty)
/datum/supply_pack/materials/rcdammo
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
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."
cost = 600
+1 -1
View File
@@ -370,7 +370,7 @@
//////////////////////////////////////////////////////////////////////////////
/datum/supply_pack/misc/carpet
goody = PACK_GOODY_PUBLIC
crate_type = /obj/structure/closet/secure_closet/goodies
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."
cost = 200
+8 -10
View File
@@ -80,15 +80,15 @@
/datum/supply_pack/security/russianclothing
name = "Russian Surplus Clothing"
desc = "An old russian crate full of surplus armor that they used to use! Has two sets of bulletproff armor, a few union suits and some warm hats!"
desc = "An old russian crate full of surplus armor that they used to use! Has two sets of bulletproof armor, a few union suits and some warm hats!"
contraband = TRUE
cost = 5750 // Its basicly sec suits, good boots/gloves
contains = list(/obj/item/clothing/suit/armor/navyblue/russian,
/obj/item/clothing/suit/armor/navyblue/russian,
contains = list(/obj/item/clothing/under/syndicate/rus_army,
/obj/item/clothing/under/syndicate/rus_army,
/obj/item/clothing/shoes/combat,
/obj/item/clothing/shoes/combat,
/obj/item/clothing/head/ushanka,
/obj/item/clothing/head/ushanka,
/obj/item/clothing/head/helmet/rus_helmet,
/obj/item/clothing/head/helmet/rus_helmet,
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/head/helmet/alt,
@@ -98,23 +98,21 @@
/obj/item/clothing/mask/gas,
/obj/item/clothing/mask/gas)
crate_name = "surplus russian clothing"
crate_type = /obj/structure/closet/crate/internals
/datum/supply_pack/security/russian_partisan
name = "Russian Partisan Gear"
desc = "An old russian partisan equipment crate, comes with a full russian outfit, a loaded surplus rifle and a second magazine."
contraband = TRUE
access = FALSE
cost = 6500
contains = list(/obj/item/clothing/suit/armor/navyblue/russian,
/obj/item/clothing/shoes/combat,
/obj/item/clothing/head/ushanka,
/obj/item/clothing/head/helmet/rus_helmet,
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/head/helmet/alt,
/obj/item/clothing/gloves/tackler/combat/insulated,
/obj/item/clothing/under/syndicate/rus_army,
/obj/item/clothing/mask/gas)
crate_name = "surplus russian gear"
crate_type = /obj/structure/closet/crate/internals
/datum/supply_pack/security/russian_partisan/fill(obj/structure/closet/crate/C)
..()
@@ -241,7 +239,7 @@
access = FALSE
access_any = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS)
contains = list(/obj/item/ammo_box/c38/dumdum)
crate_name = ".38 match crate"
crate_name = ".38 dumdum crate"
/datum/supply_pack/security/match
name = ".38 Match Grade Speedloader"
+2
View File
@@ -227,6 +227,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/persistent_scars = TRUE
///If we want to broadcast deadchat connect/disconnect messages
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
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)]
@@ -385,6 +385,15 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["auto_ooc"] >> auto_ooc
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
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))
key_bindings = sanitize_islist(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
@@ -535,6 +545,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["pda_skin"], pda_skin)
WRITE_FILE(S["key_bindings"], key_bindings)
WRITE_FILE(S["modless_key_bindings"], modless_key_bindings)
WRITE_FILE(S["favorite_outfits"], favorite_outfits)
//citadel code
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 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)
to_chat(src, "<span class='danger'>Function on cooldown, try again in 5 seconds.</span>")
return
+1 -2
View File
@@ -83,8 +83,7 @@
standard_outfit_options = list()
for(var/path in subtypesof(/datum/outfit/job))
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)
outfit_options = standard_outfit_options
@@ -358,6 +358,12 @@
..()
user.cure_blind("blindfold_[REF(src)]")
/obj/item/clothing/glasses/fakeblindfold
name = "thin blindfold"
desc = "Covers the eyes, but not thick enough to obscure vision. Mostly for aesthetic."
icon_state = "blindfoldwhite"
item_state = "blindfoldwhite"
/obj/item/clothing/glasses/sunglasses/blindfold/white
name = "blind personnel blindfold"
desc = "Indicates that the wearer suffers from blindness."
@@ -278,6 +278,19 @@
visible_message("[src] finishes cooking!")
new /obj/item/reagent_containers/food/snacks/meat/steak/goliath(loc)
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
name = "meatwheat clump"
@@ -395,6 +408,16 @@
trash = null
tastes = list("meat" = 1, "rock" = 1)
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
name = "gondola steak"
@@ -36,7 +36,7 @@
desc = "It's watching you suspiciously."
/obj/structure/closet/crate/necropolis/tendril/magic/PopulateContents()
var/loot = rand(1,9)
var/loot = rand(1,10)
switch(loot)
if(1)
new /obj/item/soulstone/anybody(src)
@@ -57,6 +57,8 @@
new /obj/item/immortality_talisman(src)
if(9)
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()
var/loot = rand(1,11)
@@ -128,7 +130,7 @@
new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
/obj/structure/closet/crate/necropolis/tendril/all/PopulateContents()
var/loot = rand(1,28)
var/loot = rand(1,29)
switch(loot)
if(1)
new /obj/item/shared_storage/red(src)
@@ -192,6 +194,8 @@
new /obj/item/immortality_talisman(src)
if(28)
new /obj/item/gun/magic/wand/book/healing(src)
if(29)
new /obj/item/guardiancreator(src)
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disc
@@ -1105,14 +1109,12 @@
/obj/structure/closet/crate/necropolis/bubblegum/PopulateContents()
new /obj/item/clothing/suit/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)
if(1)
new /obj/item/mayhem(src)
if(2)
new /obj/item/book/granter/spell/asura(src)
if(3)
new /obj/item/guardiancreator(src)
/obj/structure/closet/crate/necropolis/bubblegum/crusher
name = "bloody bubblegum chest"
@@ -297,6 +297,11 @@
icon = 'icons/obj/surgery.dmi'
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////////////////////////////////////////
@@ -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)
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
if(!forced && hit_percent <= 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()
delete_equipment()
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.
GLOBAL_LIST_EMPTY(human_dummy_list)
@@ -42,13 +47,48 @@ GLOBAL_LIST_EMPTY(dummy_mob_list)
D = new
GLOB.human_dummy_list[slotkey] = D
GLOB.dummy_mob_list += D
else
D.regenerate_icons() //they were cut in wipe_state()
D.in_use = TRUE
return D
/proc/unset_busy_human_dummy(slotnumber)
if(!slotnumber)
/proc/generate_dummy_lookalike(slotkey, mob/target)
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
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))
D.wipe_state()
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
create_bodyparts()
//initialize dna. for spawned humans; overwritten by other code
create_dna(src)
randomize_human(src)
dna.initialize_dna()
setup_human_dna()
if(dna.species)
set_species(dna.species.type)
@@ -36,6 +33,11 @@
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood)
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()
. = ..()
@@ -176,3 +176,9 @@
/mob/living/carbon/human/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, \
user, "<span class='danger'>You [atk_verb]ed [target]!</span>")
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
target.set_last_attacker(user)
user.dna.species.spec_unarmedattacked(user, target)
if(user.limb_destroyer)
@@ -2039,8 +2038,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
user, "<span class='danger'>You shove [target.name]!</span>")
target.Stagger(SHOVE_STAGGER_DURATION)
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_held_item)
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)
/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
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
if(!forced && hit_percent <= 0)
@@ -7,6 +7,9 @@
species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR,HAS_FLESH)
mutantlungs = /obj/item/organ/lungs/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")
inherent_traits = list(TRAIT_TOXINLOVER)
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
@@ -22,18 +25,12 @@
heatmod = 0.5 // = 1/4x heat damage
burnmod = 0.5 // = 1/2x generic burn damage
species_language_holder = /datum/language_holder/jelly
mutant_brain = /obj/item/organ/brain/jelly
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
species_category = SPECIES_CATEGORY_JELLY
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)
C.faction -= "slime"
if(ishuman(C))
+1 -1
View File
@@ -2,7 +2,7 @@
/**
* Applies damage to this mob
*
* Sends [COMSIG_MOB_APPLY_DAMGE]
* Sends [COMSIG_MOB_APPLY_DAMAGE]
*
* Arguuments:
* * damage - amount of damage
+2
View File
@@ -55,6 +55,8 @@
/mob/living/death(gibbed)
SEND_SIGNAL(src, COMSIG_LIVING_PREDEATH, gibbed)
stat = DEAD
unset_machine()
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)
..() //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))
log_combat(user, src, "attempted to touch")
visible_message("<span class='warning'>[user] attempted to touch [src]!</span>",
@@ -561,3 +561,9 @@
/mob/living/proc/getFireLoss_nonProsthetic()
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
@@ -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'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.
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.
To make this possible, it sprays streams of blood at random.
From these blood pools Bubblegum may summon slaughterlings - weak, low-damage minions designed to impede the target's progress.
It leaves blood trails behind wherever it goes, its clones do as well.
It tries to strike at its target through any bloodpools under them; if it fails to do that.
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.
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:
1. A spellblade that can slice off limbs at range
2. A bottle that, when activated, drives everyone nearby into a frenzy
3. A super double-barrel shotgun that shoots both shells at the same time.
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 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.
Difficulty: Hard
*/
/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."
health = 2500
maxHealth = 2500
@@ -42,15 +41,20 @@ Difficulty: Hard
melee_damage_lower = 40
melee_damage_upper = 40
speed = 1
move_to_delay = 10
ranged_cooldown_time = 10
move_to_delay = 5
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
pixel_x = -32
gender = MALE
del_on_death = 1
crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher)
loot = list(/obj/structure/closet/crate/necropolis/bubblegum)
blood_volume = BLOOD_VOLUME_MAXIMUM //BLEED FOR ME
var/charging = 0
var/enrage_till = null
achievement_type = /datum/award/achievement/boss/bubblegum_kill
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."
invisibility = 100
/mob/living/simple_animal/hostile/megafauna/bubblegum/BiologicalLife(seconds, times_fired)
if(!(. = ..()))
return
move_to_delay = clamp(round((health/maxHealth) * 10), 3, 10)
/mob/living/simple_animal/hostile/megafauna/bubblegum/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, only_robotic = FALSE, only_organic = TRUE)
. = ..()
if(. > 0 && prob(25))
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()
anger_modifier = clamp(((maxHealth - health)/50),0,20)
anger_modifier = clamp(((maxHealth - health)/60),0,20)
if(charging)
return
ranged_cooldown = world.time + ranged_cooldown_time
ranged_cooldown = world.time + 50
if(!try_bloodattack())
blood_warp()
blood_warp()
if(prob(25))
INVOKE_ASYNC(src, .proc/blood_spray)
else if(prob(5+anger_modifier/2))
slaughterlings()
else
if(health > maxHealth/2 && !client)
INVOKE_ASYNC(src, .proc/charge)
if(health > maxHealth * 0.5)
if(prob(50 + anger_modifier))
charge(delay = 6)
charge(delay = 4) // The FitnessGram Pacer Test is a multistage aerobic capacity test that progressively gets more difficult as it continues.
charge(delay = 2)
SetRecoveryTime(15)
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()
. = ..()
for(var/mob/living/simple_animal/hostile/megafauna/bubblegum/B in GLOB.mob_list)
if(B != src)
return INITIALIZE_HINT_QDEL //There can be only one
if(istype(src, /mob/living/simple_animal/hostile/megafauna/bubblegum/hallucination))
return
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
AddSpell(bloodspell)
if(istype(loc, /obj/effect/dummy/phased_mob/slaughter))
@@ -109,52 +137,78 @@ Difficulty: Hard
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)
if(charging)
return
..()
if(!charging)
..()
/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
..()
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)
if(charging)
return
..()
if(!charging)
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/MoveToTarget(list/possible_targets)
if(!charging)
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/Move()
if(!stat)
playsound(src.loc, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1)
if(charging)
new/obj/effect/temp_visual/decoy/fading(loc,src)
new /obj/effect/temp_visual/decoy/fading(loc,src)
DestroySurroundings()
. = ..()
..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved()
new /obj/effect/decal/cleanable/blood(src.loc)
if(charging)
DestroySurroundings()
playsound(src, 'sound/effects/meteorimpact.ogg', 200, 1, 2, 1)
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/triple_charge()
charge()
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)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/charge(atom/chargeat = target, delay = 3, chargepast = 2)
if(!chargeat)
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
DestroySurroundings()
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)
animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 5)
sleep(5)
throw_at(T, get_dist(src, T), 1, src, 0)
animate(D, alpha = 0, color = "#FF0000", transform = matrix()*2, time = 3)
sleep(delay)
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
Goto(target, move_to_delay, minimum_distance)
/**
* Attack by override for bubblegum
@@ -176,36 +230,147 @@ Difficulty: Hard
if(isturf(A) || isobj(A) && A.density)
A.ex_act(EXPLODE_HEAVY)
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)
if(!charging)
return ..()
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_mobs_on_blood()
var/list/targets = ListTargets()
. = 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))
var/mob/living/L = hit_atom
L.visible_message("<span class='danger'>[src] slams into [L]!</span>", "<span class='userdanger'>[src] slams into you!</span>")
L.apply_damage(40, BRUTE)
playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, 1)
shake_camera(L, 4, 3)
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)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/try_bloodattack()
var/list/targets = get_mobs_on_blood()
if(targets.len)
INVOKE_ASYNC(src, .proc/bloodattack, targets, prob(50))
return TRUE
return FALSE
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()
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/list/pools = list()
var/can_jaunt = FALSE
for(var/obj/effect/decal/cleanable/blood/nearby in view(src,2))
can_jaunt = TRUE
break
if(!can_jaunt)
return
for(var/obj/effect/decal/cleanable/blood/nearby in view(get_turf(target),2))
pools += nearby
pools = get_pools(get_turf(target), 2)
pools_to_remove = get_pools(get_turf(target), 1)
pools -= pools_to_remove
if(pools.len)
shuffle_inplace(pools)
found_bloodpool = pick(pools)
@@ -215,48 +380,110 @@ Difficulty: Hard
forceMove(get_turf(found_bloodpool))
playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1)
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()
visible_message("<span class='danger'>[src] sprays a stream of gore!</span>")
var/turf/E = get_edge_target_turf(src, src.dir)
var/range = 10
var/turf/previousturf = get_turf(src)
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/blood_enrage_end(var/newcolor = rgb(149, 10, 10))
retreat_distance = initial(retreat_distance)
minimum_distance = initial(minimum_distance)
change_move_delay()
remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, newcolor)
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/slaughterlings()
visible_message("<span class='danger'>[src] summons a shoal of slaughterlings!</span>")
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/megafauna/bubblegum/proc/is_enraged()
return (enrage_till > world.time)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter
name = "slaughterling"
desc = "Though not yet strong enough to create a true physical form, it's nonetheless determined to murder you."
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/megafauna/bubblegum/proc/change_move_delay(var/newmove = initial(move_to_delay))
move_to_delay = newmove
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
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/slaughter/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum))
/mob/living/simple_animal/hostile/megafauna/bubblegum/proc/get_pools(turf/T, range)
. = 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 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
crusher_loot = list(/obj/structure/closet/crate/necropolis/dragon/crusher)
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)
var/swooping = NONE
var/swoop_cooldown = 0
var/player_cooldown = 0
achievement_type = /datum/award/achievement/boss/drake_kill
crusher_achievement_type = /datum/award/achievement/boss/drake_crusher
score_achievement_type = /datum/award/score/drake_score
@@ -116,66 +116,167 @@ Difficulty: Medium
ranged_cooldown = world.time + ranged_cooldown_time
if(prob(15 + anger_modifier) && !client)
if(health < maxHealth/2)
INVOKE_ASYNC(src, .proc/swoop_attack, TRUE, null, 50)
if(health < maxHealth*0.5)
swoop_attack(lava_arena = TRUE)
else
fire_rain()
lava_swoop()
else if(prob(10+anger_modifier) && !client)
if(health > maxHealth/2)
INVOKE_ASYNC(src, .proc/swoop_attack)
if(health < maxHealth*0.5)
mass_fire()
else
INVOKE_ASYNC(src, .proc/triple_swoop)
fire_cone()
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)
return
target.visible_message("<span class='boldwarning'>Fire rains from the sky!</span>")
for(var/turf/turf in range(9,get_turf(target)))
if(prob(11))
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])))
target.visible_message("<span class='boldwarning'>Lava starts to pool up around you!</span>")
while(amount > 0)
if(!target)
break
range--
new /obj/effect/hotspot(J)
J.hotspot_expose(DRAKE_FIRE_TEMP, DRAKE_FIRE_EXPOSURE, 1)
for(var/mob/living/L in J.contents - hit_things)
if(istype(L, /mob/living/simple_animal/hostile/megafauna/dragon))
var/turf/T = pick(RANGE_TURFS(1, target))
new /obj/effect/temp_visual/lava_warning(T, 60) // longer reset time for the lava
amount--
sleep(delay)
/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
hit_list += L
L.adjustFireLoss(20)
to_chat(L, "<span class='userdanger'>You're hit by the drake's fire breath!</span>")
hit_things += L
previousturf = J
sleep(1)
to_chat(L, "<span class='userdanger'>You're hit by [src]'s fire breath!</span>")
/mob/living/simple_animal/hostile/megafauna/dragon/proc/triple_swoop()
swoop_attack(swoop_duration = 30)
swoop_attack(swoop_duration = 30)
swoop_attack(swoop_duration = 30)
// deals damage to mechs
for(var/obj/mecha/M in T.contents)
if(M in hit_list)
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)
return
if(manual_target)
target = manual_target
if(!target)
return
swoop_cooldown = world.time + 200
stop_automated_movement = TRUE
swooping |= SWOOP_DAMAGEABLE
density = FALSE
@@ -209,32 +310,16 @@ Difficulty: Medium
swooping |= SWOOP_INVULNERABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
sleep(7)
var/list/flame_hit = list()
while(swoop_duration > 0)
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)
while(target && loc != get_turf(target))
forceMove(get_step(src, get_dir(src, target)))
if(loc == get_turf(target))
if(!fire_rain)
break
if(isliving(target))
var/mob/living/L = target
if(L.stat == DEAD)
break
var/swoop_speed = 1.5
swoop_duration -= swoop_speed
sleep(swoop_speed)
sleep(0.5)
// Ash drake flies onto its target and rains fire down upon them
var/descentTime = 10;
var/lava_success = 1
if(lava_arena)
lava_success = lava_arena()
//ensure swoop direction continuity.
if(negative)
@@ -245,8 +330,8 @@ Difficulty: Medium
negative = TRUE
new /obj/effect/temp_visual/dragon_flight/end(loc, negative)
new /obj/effect/temp_visual/dragon_swoop(loc)
animate(src, alpha = 255, transform = oldtransform, time = 5)
sleep(5)
animate(src, alpha = 255, transform = oldtransform, descentTime)
sleep(descentTime)
swooping &= ~SWOOP_INVULNERABLE
mouse_opacity = initial(mouse_opacity)
icon_state = "dragon"
@@ -264,6 +349,8 @@ Difficulty: Medium
var/throwtarget = get_edge_target_turf(src, throw_dir)
L.throw_at(throwtarget, 3)
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))
shake_camera(M, 15, 1)
@@ -271,16 +358,20 @@ Difficulty: Medium
density = TRUE
sleep(1)
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)
if(!istype(A))
AltClickNoInteract(src, A)
return
if(swoop_cooldown >= world.time)
to_chat(src, "<span class='warning'>You need to wait 20 seconds between swoop attacks!</span>")
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(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
icon_state = null
@@ -289,54 +380,63 @@ Difficulty: Medium
invisibility = 100
/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 = 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"
/obj/effect/temp_visual/lava_warning
icon_state = "lavastaff_warn"
layer = BELOW_MOB_LAYER
light_range = 2
duration = 9
duration = 13
/obj/effect/temp_visual/target/ex_act()
/obj/effect/temp_visual/lava_warning/ex_act()
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)
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)
playsound(T,'sound/magic/fireball.ogg', 200, 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
L.adjustFireLoss(10)
to_chat(L, "<span class='userdanger'>You fall directly into the pool of lava!</span>")
// deals damage to mechs
for(var/obj/mecha/M in T.contents)
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
name = "certain death"
@@ -347,7 +447,7 @@ Difficulty: Medium
pixel_x = -32
pixel_y = -32
color = "#FF0000"
duration = 5
duration = 10
/obj/effect/temp_visual/dragon_flight
icon = 'icons/mob/lavaland/64x64megafauna.dmi'
@@ -376,7 +476,7 @@ Difficulty: Medium
/obj/effect/temp_visual/dragon_flight/end
pixel_x = DRAKE_SWOOP_HEIGHT
pixel_z = DRAKE_SWOOP_HEIGHT
duration = 5
duration = 10
/obj/effect/temp_visual/dragon_flight/end/flight(negative)
if(negative)
@@ -399,6 +499,16 @@ Difficulty: Medium
crusher_loot = list()
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
name = "transformed ash drake"
desc = "A sentient being transformed into an ash drake"
@@ -64,7 +64,6 @@
friendly_verb_simple = "groom"
mob_size = MOB_SIZE_SMALL
movement_type = FLYING
gold_core_spawnable = FRIENDLY_SPAWN
var/parrot_damage_upper = 10
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)
SEND_SIGNAL(src, COMSIG_LIVING_MINOR_SHOCK)
lastattacker = H.real_name
lastattackerckey = H.ckey
set_last_attacker(H)
log_combat(H, src, "stunned")
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."
else
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)
explode()
@@ -715,7 +715,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
//Oh shit it's bad, time to freak out
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
if(!has_reached_emergency)
investigate_log("has reached the emergency point for the first time.", INVESTIGATE_SUPERMATTER)
@@ -73,3 +73,11 @@
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
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
@@ -25,7 +25,7 @@
/obj/item/ammo_casing/chemgun
name = "dart synthesiser"
desc = "A high-power spring, linked to an energy-based dart synthesiser."
projectile_type = /obj/item/projectile/bullet/dart
projectile_type = /obj/item/projectile/bullet/dart/piercing
firing_effect_type = null
/obj/item/ammo_casing/chemgun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
@@ -35,7 +35,7 @@
var/obj/item/gun/chem/CG = loc
if(CG.syringes_left <= 0)
return
CG.reagents.trans_to(BB, 15)
CG.reagents.trans_to(BB, 10)
BB.name = "chemical dart"
CG.syringes_left--
..()

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