diff --git a/_maps/RandomZLevels/Cabin.dmm b/_maps/RandomZLevels/Cabin.dmm index f4a18fd87c..4d07c02565 100644 --- a/_maps/RandomZLevels/Cabin.dmm +++ b/_maps/RandomZLevels/Cabin.dmm @@ -967,10 +967,6 @@ }, /turf/open/floor/plating, /area/awaymission/cabin) -"dw" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/space) (1,1,1) = {" aa @@ -1227,7 +1223,7 @@ aa aa aa aa -dw +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/VR/snowdin_VR.dmm b/_maps/RandomZLevels/VR/snowdin_VR.dmm index 8f6eda450c..bae0b05e3d 100644 --- a/_maps/RandomZLevels/VR/snowdin_VR.dmm +++ b/_maps/RandomZLevels/VR/snowdin_VR.dmm @@ -4,9 +4,15 @@ /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) "ab" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/awaymission/snowdin/cave/mountain) +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/mob/living/simple_animal/hostile/netherworld/migo, +/turf/open/floor/plasteel, +/area/awaymission/snowdin/post/mining_dock) "ac" = ( /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) @@ -10267,16 +10273,6 @@ /obj/item/shard, /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_dock) -"xA" = ( -/mob/living/simple_animal/hostile/netherworld/migo, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/snowdin/post/mining_dock) "xB" = ( /obj/effect/turf_decal/stripes/line{ dir = 4 @@ -16013,7 +16009,7 @@ ac ac "} (2,1,1) = {" -ab +ac ac ac ac @@ -66518,7 +66514,7 @@ wD wT xe xs -xA +ab xI xN wL diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm index d50480c381..96c1a08b1f 100644 --- a/_maps/RandomZLevels/caves.dmm +++ b/_maps/RandomZLevels/caves.dmm @@ -2251,10 +2251,6 @@ initial_gas_mix = "n2=23;o2=14" }, /area/awaymission/caves/BMP_asteroid) -"gW" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock, -/area/space/nearstation) "gX" = ( /obj/effect/baseturf_helper/lava, /turf/closed/mineral/volcanic, @@ -2536,7 +2532,7 @@ aa aa aa aa -gW +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm index ddc8778ac1..8c33326703 100644 --- a/_maps/RandomZLevels/moonoutpost19.dmm +++ b/_maps/RandomZLevels/moonoutpost19.dmm @@ -7215,10 +7215,6 @@ heat_capacity = 1e+006 }, /area/awaymission/moonoutpost19/research) -"oV" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/space, -/area/space) "vV" = ( /obj/machinery/door/airlock/external, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ @@ -7512,7 +7508,7 @@ aa aa aa aa -oV +aa "} (2,1,1) = {" aa diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm index 525c8fcf7b..57e03061dd 100644 --- a/_maps/RandomZLevels/snowdin.dmm +++ b/_maps/RandomZLevels/snowdin.dmm @@ -4,9 +4,15 @@ /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) "ab" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/indestructible/rock/snow, -/area/awaymission/snowdin/cave/mountain) +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/mob/living/simple_animal/hostile/netherworld/migo, +/turf/open/floor/plasteel, +/area/awaymission/snowdin/post/mining_dock) "ac" = ( /turf/closed/indestructible/rock/snow, /area/awaymission/snowdin/cave/mountain) @@ -10331,16 +10337,6 @@ /obj/item/shard, /turf/open/floor/plating, /area/awaymission/snowdin/post/mining_dock) -"xA" = ( -/mob/living/simple_animal/hostile/netherworld/migo, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/snowdin/post/mining_dock) "xB" = ( /obj/effect/turf_decal/stripes/line{ dir = 4 @@ -16108,7 +16104,7 @@ ac ac "} (2,1,1) = {" -ab +ac ac ac ac @@ -66613,7 +66609,7 @@ wD wT xe xs -xA +ab xI xN wL diff --git a/_maps/RandomZLevels/spacebattle.dmm b/_maps/RandomZLevels/spacebattle.dmm index 21cd37f78f..66d7f556b4 100644 --- a/_maps/RandomZLevels/spacebattle.dmm +++ b/_maps/RandomZLevels/spacebattle.dmm @@ -5,6 +5,16 @@ "ab" = ( /turf/open/space, /area/space) +"ac" = ( +/obj/effect/turf_decal/tile/blue{ + dir = 1 + }, +/obj/effect/turf_decal/tile/blue{ + dir = 8 + }, +/mob/living/simple_animal/hostile/syndicate/melee/sword, +/turf/open/floor/plasteel, +/area/awaymission/spacebattle/cruiser) "ad" = ( /obj/structure/shuttle/engine/propulsion/right{ dir = 1 @@ -2597,16 +2607,6 @@ /obj/effect/spawner/lootdrop/armory_contraband, /turf/open/floor/plating, /area/awaymission/spacebattle/cruiser) -"jK" = ( -/mob/living/simple_animal/hostile/syndicate/melee/sword, -/obj/effect/turf_decal/tile/blue{ - dir = 1 - }, -/obj/effect/turf_decal/tile/blue{ - dir = 8 - }, -/turf/open/floor/plasteel, -/area/awaymission/spacebattle/cruiser) "jL" = ( /obj/machinery/door/poddoor{ id = "spacebattlearmory"; @@ -2894,10 +2894,6 @@ /obj/item/mecha_parts/mecha_equipment/weapon/energy/ion, /turf/open/floor/plating, /area/awaymission/spacebattle/cruiser) -"kM" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/closed/mineral/random, -/area/space/nearstation) "vw" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, /area/awaymission/spacebattle/syndicate5) @@ -3175,7 +3171,7 @@ aa aa aa aa -kM +aa "} (2,1,1) = {" aa @@ -35686,10 +35682,10 @@ eC cn fL cp -jK +ac fs fO -jK +ac eM eM eM diff --git a/_maps/RandomZLevels/undergroundoutpost45.dmm b/_maps/RandomZLevels/undergroundoutpost45.dmm index f6d46ef97d..56018b943a 100644 --- a/_maps/RandomZLevels/undergroundoutpost45.dmm +++ b/_maps/RandomZLevels/undergroundoutpost45.dmm @@ -25,6 +25,9 @@ "ag" = ( /turf/closed/wall/mineral/titanium, /area/awaymission/undergroundoutpost45/central) +"ah" = ( +/turf/open/space, +/area/space/nearstation) "aj" = ( /obj/effect/decal/cleanable/dirt, /turf/open/floor/plasteel{ @@ -13945,10 +13948,6 @@ temperature = 363.9 }, /area/awaymission/undergroundoutpost45/caves) -"zi" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/space, -/area/space/nearstation) "KE" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 @@ -14211,7 +14210,7 @@ aa aa aa aa -zi +ah "} (2,1,1) = {" aa diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm index de42e1ac63..99c536bd3c 100644 --- a/_maps/map_files/Mining/Lavaland.dmm +++ b/_maps/map_files/Mining/Lavaland.dmm @@ -3571,14 +3571,6 @@ }, /turf/open/floor/plating/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) -"Wt" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/laborcamp/security) -"Wz" = ( -/obj/effect/mapping_helpers/planet_z, -/turf/open/lava/smooth/lava_land_surface, -/area/lavaland/surface/outdoors) "WA" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 8 @@ -3630,26 +3622,6 @@ }, /turf/open/floor/plasteel, /area/mine/production) -"WF" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/laborcamp) -"WH" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/eva) -"WI" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/production) -"WJ" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall/r_wall, -/area/mine/maintenance) -"WK" = ( -/obj/effect/baseturf_helper/lava_land/surface, -/turf/closed/wall, -/area/mine/living_quarters) (1,1,1) = {" aa @@ -3906,7 +3878,7 @@ aj aj aj aj -Wz +aj "} (2,1,1) = {" aa @@ -8751,7 +8723,7 @@ aq WB aq bi -WF +aq WC aq bZ @@ -9012,7 +8984,7 @@ az az aq ca -Wt +ca ca aj aj @@ -12102,7 +12074,7 @@ ai cQ dk dA -WJ +cQ ed er eM @@ -13134,7 +13106,7 @@ dR ef es dZ -WK +cM fg cM cM @@ -20320,9 +20292,9 @@ bt bH bV cq -WH +bf +bq bq -WI db bP bP diff --git a/code/__DEFINES/citadel_defines.dm b/code/__DEFINES/citadel_defines.dm index 24f6c393b3..e29be375e6 100644 --- a/code/__DEFINES/citadel_defines.dm +++ b/code/__DEFINES/citadel_defines.dm @@ -116,7 +116,7 @@ #define HYPNO (1<<7) #define NEVER_HYPNO (1<<8) #define NO_APHRO (1<<9) -#define NO_ASS_SLAP (1<<9) +#define NO_ASS_SLAP (1<<10) #define TOGGLES_CITADEL (EATING_NOISES|DIGESTION_NOISES|BREAST_ENLARGEMENT|PENIS_ENLARGEMENT) diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm new file mode 100644 index 0000000000..45c1ba9cb2 --- /dev/null +++ b/code/__DEFINES/dynamic.dm @@ -0,0 +1,14 @@ +#define CURRENT_LIVING_PLAYERS 1 +#define CURRENT_LIVING_ANTAGS 2 +#define CURRENT_DEAD_PLAYERS 3 +#define CURRENT_OBSERVERS 4 + +#define NO_ASSASSIN (1<<0) +#define WAROPS_ALWAYS_ALLOWED (1<<1) + +#define ONLY_RULESET (1<<0) +#define HIGHLANDER_RULESET (1<<1) +#define TRAITOR_RULESET (1<<2) +#define MINOR_RULESET (1<<3) + +#define RULESET_STOP_PROCESSING 1 diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index 1b42217fe1..73e900226e 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -38,8 +38,6 @@ require only minor tweaks. #define ZTRAIT_SPACE_RUINS "Space Ruins" #define ZTRAIT_LAVA_RUINS "Lava Ruins" #define ZTRAIT_ISOLATED_RUINS "Isolated Ruins" //Placing ruins on z levels with this trait will use turf reservation instead of usual placement. -// prevents certain turfs from being stripped by a singularity -#define ZTRAIT_PLANET "Planet" // number - bombcap is multiplied by this before being applied to bombs #define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier" @@ -60,11 +58,18 @@ require only minor tweaks. // CROSSLINKED - mixed in with the cross-linked space pool #define CROSSLINKED "Cross" +// string - type path of the z-level's baseturf (defaults to space) +#define ZTRAIT_BASETURF "Baseturf" + // default trait definitions, used by SSmapping #define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE) #define ZTRAITS_STATION list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_STATION = TRUE) #define ZTRAITS_SPACE list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_SPACE_RUINS = TRUE) -#define ZTRAITS_LAVALAND list(ZTRAIT_MINING = TRUE, ZTRAIT_LAVA_RUINS = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 5) +#define ZTRAITS_LAVALAND list(\ + ZTRAIT_MINING = TRUE, \ + ZTRAIT_LAVA_RUINS = TRUE, \ + ZTRAIT_BOMBCAP_MULTIPLIER = 5, \ + ZTRAIT_BASETURF = /turf/open/lava/smooth/lava_land_surface) #define ZTRAITS_REEBE list(ZTRAIT_REEBE = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 0.5) #define DL_NAME "name" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index b3c2b241f3..4f66482f51 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -169,6 +169,7 @@ #define TRAIT_EMPATH "empath" #define TRAIT_FRIENDLY "friendly" #define TRAIT_CULT_EYES "cult_eyes" +#define TRAIT_AUTO_CATCH_ITEM "auto_catch_item" #define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman. #define TRAIT_FREESPRINT "free_sprinting" diff --git a/code/__DEFINES/vote.dm b/code/__DEFINES/vote.dm new file mode 100644 index 0000000000..8fb2e6deab --- /dev/null +++ b/code/__DEFINES/vote.dm @@ -0,0 +1,6 @@ +#define PLURALITY_VOTING 0 +#define APPROVAL_VOTING 1 +#define RANKED_CHOICE_VOTING 2 +#define SCORE_VOTING 3 + +GLOBAL_LIST_INIT(vote_score_options,list("Bad","Poor","Acceptable","Good","Great")) diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index c259831f94..3ee77d3edc 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -157,6 +157,8 @@ WRITE_LOG(GLOB.config_error_log, text) SEND_TEXT(world.log, text) +/proc/log_mapping(text) + WRITE_LOG(GLOB.world_map_error_log, text) /* For logging round startup. */ /proc/start_log(log) diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm index 55ee069321..3e6e88c8fa 100644 --- a/code/__HELPERS/level_traits.dm +++ b/code/__HELPERS/level_traits.dm @@ -12,6 +12,3 @@ #define is_reserved_level(z) SSmapping.level_trait(z, ZTRAIT_RESERVED) #define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY) - -// If true, the singularity cannot strip away asteroid turf on this Z -#define is_planet_level(z) SSmapping.level_trait(z, ZTRAIT_PLANET) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index ce8b2e4cf0..cdf0f604b8 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -20,8 +20,6 @@ else return "000" -#define UNDIE_COLORABLE(U) (U?.has_color) - /proc/random_underwear(gender) if(!GLOB.underwear_list.len) init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 29f5331fd3..74c32dd52f 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -758,16 +758,6 @@ GLOBAL_LIST_INIT(can_embed_types, typecacheof(list( /obj/item/stack/rods, /obj/item/pipe))) -/proc/can_embed(obj/item/W) - if(W.get_sharpness()) - return 1 - if(is_pointed(W)) - return 1 - - if(is_type_in_typecache(W, GLOB.can_embed_types)) - return 1 - - /* Checks if that loc and dir has an item on the wall */ diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index c25ebf5b0a..914e45add5 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -115,5 +115,6 @@ GLOBAL_LIST_INIT(maintenance_loot, list( /obj/item/clothing/shoes/kindleKicks = 1, /obj/item/autosurgeon/penis = 1, /obj/item/autosurgeon/testicles = 1, + /obj/item/storage/box/marshmallow = 2, "" = 3 )) diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 8e5da083e3..5ca3513e66 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -28,6 +28,8 @@ GLOBAL_VAR(world_job_debug_log) GLOBAL_PROTECT(world_job_debug_log) GLOBAL_VAR(world_virus_log) GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) GLOBAL_LIST_EMPTY(bombers) GLOBAL_PROTECT(bombers) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index f082f2ad16..e6b2a63673 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -98,17 +98,19 @@ take_damage(I.force, I.damtype, "melee", 1) /mob/living/attacked_by(obj/item/I, mob/living/user) + //CIT CHANGES START HERE - combatmode and resting checks + var/totitemdamage = I.force + if(iscarbon(user)) + var/mob/living/carbon/tempcarb = user + if(!tempcarb.combatmode) + totitemdamage *= 0.5 + if(user.resting) + totitemdamage *= 0.5 + //CIT CHANGES END HERE + if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + return FALSE send_item_attack_message(I, user) if(I.force) - //CIT CHANGES START HERE - combatmode and resting checks - var/totitemdamage = I.force - if(iscarbon(user)) - var/mob/living/carbon/tempcarb = user - if(!tempcarb.combatmode) - totitemdamage *= 0.5 - if(user.resting) - totitemdamage *= 0.5 - //CIT CHANGES END HERE apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage if(I.damtype == BRUTE && !HAS_TRAIT(src, TRAIT_NOMARROW)) if(prob(33)) diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 30e6883d24..6ee7d51091 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -91,6 +91,8 @@ var/obj/item/clothing/gloves/G = gloves if(istype(G) && G.Touch(A,0)) // for magic gloves return + if (istype(glasses) && glasses.ranged_attack(src,A,mouseparams)) + return for(var/datum/mutation/human/HM in dna.mutations) HM.on_ranged_attack(src, A, mouseparams) diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index a5d27819b8..14954524fa 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -14,6 +14,7 @@ var/list/modes // allowed modes var/list/gamemode_cache var/list/votable_modes // votable modes + var/list/storyteller_cache var/list/mode_names var/list/mode_reports var/list/mode_false_report_weight @@ -37,6 +38,7 @@ CRASH("/datum/controller/configuration/Load() called more than once!") InitEntries() LoadModes() + storyteller_cache = typecacheof(/datum/dynamic_storyteller, TRUE) if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1) var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt") for(var/I in legacy_configs) @@ -227,6 +229,7 @@ for(var/T in gamemode_cache) // I wish I didn't have to instance the game modes in order to look up // their information, but it is the only way (at least that I know of). + // for future reference: just use initial() lol var/datum/game_mode/M = new T() if(M.config_tag) @@ -317,6 +320,14 @@ return new T return new /datum/game_mode/extended() +/datum/controller/configuration/proc/pick_storyteller(storyteller_name) + for(var/T in storyteller_cache) + var/datum/dynamic_storyteller/S = T + var/name = initial(S.name) + if(name && name == storyteller_name) + return T + return /datum/dynamic_storyteller/classic + /datum/controller/configuration/proc/get_runnable_modes() var/list/datum/game_mode/runnable_modes = new var/list/probabilities = Get(/datum/config_entry/keyed_list/probability) diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 1bfdc79d27..2a59558bcd 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -315,7 +315,7 @@ SUBSYSTEM_DEF(air) var/starting_ats = active_turfs.len sleep(world.tick_lag) var/timer = world.timeofday - warning("There are [starting_ats] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)") + log_mapping("There are [starting_ats] active turfs at roundstart caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required).") for(var/turf/T in active_turfs) GLOB.active_turfs_startlist += T diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm index 8329493818..e43c8f21a2 100644 --- a/code/controllers/subsystem/persistence.dm +++ b/code/controllers/subsystem/persistence.dm @@ -13,13 +13,14 @@ SUBSYSTEM_DEF(persistence) var/list/saved_messages = list() var/list/saved_modes = list(1,2,3) var/list/saved_dynamic_rules = list(list(),list(),list()) - var/list/saved_threat_levels = list(1,1,1) + var/list/saved_storytellers = list("foo","bar","baz","foo again","bar again") var/list/saved_maps var/list/saved_trophies = list() var/list/spawned_objects = list() var/list/antag_rep = list() var/list/antag_rep_change = list() var/list/picture_logging_information = list() + var/list/saved_votes = list() var/list/obj/structure/sign/picture_frame/photo_frames var/list/obj/item/storage/photo_album/photo_albums @@ -29,9 +30,12 @@ SUBSYSTEM_DEF(persistence) LoadChiselMessages() LoadTrophies() LoadRecentModes() - LoadRecentThreats() + LoadRecentStorytellers() + LoadRecentRulesets() LoadRecentMaps() LoadPhotoPersistence() + for(var/client/C in GLOB.clients) + LoadSavedVote(C.ckey) if(CONFIG_GET(flag/use_antag_rep)) LoadAntagReputation() LoadRandomizedRecipes() @@ -169,14 +173,23 @@ SUBSYSTEM_DEF(persistence) return saved_modes = json["data"] -/datum/controller/subsystem/persistence/proc/LoadRecentThreats() - var/json_file = file("data/RecentThreatLevels.json") +/datum/controller/subsystem/persistence/proc/LoadRecentRulesets() + var/json_file = file("data/RecentRulesets.json") if(!fexists(json_file)) return var/list/json = json_decode(file2text(json_file)) if(!json) return - saved_threat_levels = json["data"] + saved_dynamic_rules = json["data"] + +/datum/controller/subsystem/persistence/proc/LoadRecentStorytellers() + var/json_file = file("data/RecentStorytellers.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_storytellers = json["data"] /datum/controller/subsystem/persistence/proc/LoadRecentMaps() var/json_file = file("data/RecentMaps.json") @@ -197,6 +210,15 @@ SUBSYSTEM_DEF(persistence) return antag_rep = json_decode(json) +/datum/controller/subsystem/persistence/proc/LoadSavedVote(var/ckey) + var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + if(!json) + return + saved_votes[ckey] = json["data"] + /datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items) for(var/A in GLOB.trophy_cases) var/obj/structure/displaycase/trophy/T = A @@ -230,7 +252,7 @@ SUBSYSTEM_DEF(persistence) CollectRoundtype() if(istype(SSticker.mode, /datum/game_mode/dynamic)) var/datum/game_mode/dynamic/mode = SSticker.mode - CollectThreatLevel(mode) + CollectStoryteller(mode) CollectRulesets(mode) RecordMaps() SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. @@ -388,13 +410,16 @@ SUBSYSTEM_DEF(persistence) fdel(json_file) WRITE_FILE(json_file, json_encode(file_data)) -/datum/controller/subsystem/persistence/proc/CollectThreatLevel(var/datum/game_mode/dynamic/mode) - saved_threat_levels[3] = saved_threat_levels[2] - saved_threat_levels[2] = saved_threat_levels [1] - saved_threat_levels[1] = mode.threat_level - var/json_file = file("data/RecentThreatLevels.json") +/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode) + saved_storytellers.len = 5 + saved_storytellers[5] = saved_storytellers[4] + saved_storytellers[4] = saved_storytellers[3] + saved_storytellers[3] = saved_storytellers[2] + saved_storytellers[2] = saved_storytellers[1] + saved_storytellers[1] = mode.storyteller.name + var/json_file = file("data/RecentStorytellers.json") var/list/file_data = list() - file_data["data"] = saved_threat_levels + file_data["data"] = saved_storytellers fdel(json_file) WRITE_FILE(json_file, json_encode(file_data)) @@ -402,8 +427,9 @@ SUBSYSTEM_DEF(persistence) saved_dynamic_rules[3] = saved_dynamic_rules[2] saved_dynamic_rules[2] = saved_dynamic_rules[1] saved_dynamic_rules[1] = list() - for(var/datum/dynamic_ruleset/ruleset in mode.executed_rules) - saved_dynamic_rules[1] += ruleset.config_tag + for(var/r in mode.executed_rules) + var/datum/dynamic_ruleset/rule = r + saved_dynamic_rules[1] += rule.config_tag var/json_file = file("data/RecentRulesets.json") var/list/file_data = list() file_data["data"] = saved_dynamic_rules @@ -473,3 +499,11 @@ SUBSYSTEM_DEF(persistence) fdel(json_file) WRITE_FILE(json_file, json_encode(file_data)) + +/datum/controller/subsystem/persistence/proc/SaveSavedVotes() + for(var/ckey in saved_votes) + var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json") + var/list/file_data = list() + file_data["data"] = saved_votes[ckey] + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) diff --git a/code/controllers/subsystem/processing/nanites.dm b/code/controllers/subsystem/processing/nanites.dm index 69bd5781de..5b53f9f884 100644 --- a/code/controllers/subsystem/processing/nanites.dm +++ b/code/controllers/subsystem/processing/nanites.dm @@ -6,6 +6,7 @@ PROCESSING_SUBSYSTEM_DEF(nanites) var/list/datum/nanite_cloud_backup/cloud_backups = list() var/list/mob/living/nanite_monitored_mobs = list() var/list/datum/nanite_program/relay/nanite_relays = list() + var/neural_network_count = 0 /datum/controller/subsystem/processing/nanites/proc/check_hardware(datum/nanite_cloud_backup/backup) if(QDELETED(backup.storage) || (backup.storage.stat & (NOPOWER|BROKEN))) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 974d870a8a..45c8e3c8fe 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -483,7 +483,10 @@ SUBSYSTEM_DEF(ticker) SSticker.timeLeft = 900 SSticker.modevoted = TRUE var/dynamic = CONFIG_GET(flag/dynamic_voting) - SSvote.initiate_vote(dynamic ? "dynamic" : "roundtype","server",TRUE) + if(dynamic) + SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 2 MINUTES) + else + SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=PLURALITY_VOTING,forced=TRUE, vote_time = 2 MINUTES) /datum/controller/subsystem/ticker/Recover() current_state = SSticker.current_state diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index dee10e4998..573c89a5af 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -1,3 +1,5 @@ +#define VOTE_COOLDOWN 10 + SUBSYSTEM_DEF(vote) name = "Vote" wait = 10 @@ -8,13 +10,17 @@ SUBSYSTEM_DEF(vote) var/initiator = null var/started_time = null - var/time_remaining = 0 + var/end_time = 0 var/mode = null + var/vote_system = PLURALITY_VOTING var/question = null var/list/choices = list() + var/list/choice_descs = list() // optional descriptions var/list/voted = list() var/list/voting = list() + var/list/saved = list() var/list/generated_actions = list() + var/next_pop = 0 var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes @@ -22,28 +28,30 @@ SUBSYSTEM_DEF(vote) /datum/controller/subsystem/vote/fire() //called by master_controller if(mode) - time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10) - - if(time_remaining < 0) + if(end_time < world.time) result() + SSpersistence.SaveSavedVotes() for(var/client/C in voting) C << browse(null, "window=vote;can_close=0") reset() - else + else if(next_pop < world.time) var/datum/browser/client_popup for(var/client/C in voting) - client_popup = new(C, "vote", "Voting Panel") + client_popup = new(C, "vote", "Voting Panel", nwidth=600,nheight=700) client_popup.set_window_options("can_close=0") client_popup.set_content(interface(C)) client_popup.open(0) + next_pop = world.time+VOTE_COOLDOWN + /datum/controller/subsystem/vote/proc/reset() initiator = null - time_remaining = 0 + end_time = 0 mode = null question = null choices.Cut() + choice_descs.Cut() voted.Cut() voting.Cut() obfuscated = FALSE //CIT CHANGE - obfuscated votes @@ -84,17 +92,114 @@ SUBSYSTEM_DEF(vote) . += option return . +/datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text) + // https://en.wikipedia.org/wiki/Schulze_method#Implementation + var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + for(var/a in 1 to choices.len) + for(var/b in a+1 to choices.len) + var/a_rank = this_vote.Find(a) + var/b_rank = this_vote.Find(b) + a_rank = a_rank ? a_rank : choices.len+1 + b_rank = b_rank ? b_rank : choices.len+1 + if(a_rankopposite_pref) + p[i][j] = d[i][j] + p[j][i] = 0 + else + p[i][j] = 0 + p[j][i] = d[i][j] + for(var/i in 1 to choices.len) + for(var/j in 1 to choices.len) + if(i != j) + for(var/k in 1 to choices.len) // YEAH O(n^3) !! + if(i != k && j != k) + p[j][k] = max(p[j][k],min(p[j][i], p[i][k])) + //one last pass, now that we've done the math + for(var/i in 1 to choices.len) + for(var/j in 1 to choices.len) + if(i != j) + SSblackbox.record_feedback("nested tally","voting",p[i][j],list(blackbox_text,"Shortest Paths",choices[i],choices[j])) + if(p[i][j] >= p[j][i]) + choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here + // choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers + +/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text) + // https://en.wikipedia.org/wiki/Majority_judgment + var/list/scores_by_choice = list() + for(var/choice in choices) + scores_by_choice[choice] = list() + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + var/list/pretty_vote = list() + for(var/choice in this_vote) + sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) + // START BALLOT GATHERING + pretty_vote += choice + pretty_vote[choice] = GLOB.vote_score_options[this_vote[choice]] + SSblackbox.record_feedback("associative","voting_ballots",1,pretty_vote) + // END BALLOT GATHERING + for(var/score_name in scores_by_choice) + var/list/score = scores_by_choice[score_name] + for(var/indiv_score in score) + SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score])) + if(score.len == 0) + scores_by_choice -= score_name + while(scores_by_choice.len > 1) + var/highest_median = 0 + for(var/score_name in scores_by_choice) // first get highest median + var/list/score = scores_by_choice[score_name] + if(!score.len) + scores_by_choice -= score_name + continue + var/median = score[max(1,round(score.len/2))] + if(median >= highest_median) + highest_median = median + for(var/score_name in scores_by_choice) // then, remove + var/list/score = scores_by_choice[score_name] + var/median = score[max(1,round(score.len/2))] + if(median < highest_median) + scores_by_choice -= score_name + for(var/score_name in scores_by_choice) // after removals + var/list/score = scores_by_choice[score_name] + if(score.len == 0) + choices[score_name] += 100 // we're in a tie situation--just go with the first one + return + var/median_pos = max(1,round(score.len/2)) + score.Cut(median_pos,median_pos+1) + choices[score_name]++ + /datum/controller/subsystem/vote/proc/announce_result() - var/list/winners = get_result() + var/vote_title_text var/text + if(question) + text += "[question]" + vote_title_text = "[question]" + else + text += "[capitalize(mode)] Vote" + vote_title_text = "[capitalize(mode)] Vote" + if(vote_system == RANKED_CHOICE_VOTING) + calculate_condorcet_votes(vote_title_text) + if(vote_system == SCORE_VOTING) + calculate_majority_judgement_vote(vote_title_text) + var/list/winners = get_result() var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" if(winners.len > 0) - if(question) - text += "[question]" - else - text += "[capitalize(mode)] Vote" if(was_roundtype_vote) stored_gamemode_votes = list() + if(!obfuscated && vote_system == RANKED_CHOICE_VOTING) + text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" for(var/i=1,i<=choices.len,i++) var/votes = choices[choices[i]] if(!votes) @@ -116,17 +221,25 @@ SUBSYSTEM_DEF(vote) log_vote(text) remove_action_buttons() to_chat(world, "\n[text]") + switch(vote_system) + if(APPROVAL_VOTING,PLURALITY_VOTING) + for(var/i=1,i<=choices.len,i++) + SSblackbox.record_feedback("nested tally","voting",choices[choices[i]],list(vote_title_text,choices[i])) + if(RANKED_CHOICE_VOTING) + for(var/i=1,i<=voted.len,i++) + var/list/myvote = voted[voted[i]] + for(var/j=1,j<=myvote.len,j++) + SSblackbox.record_feedback("nested tally","voting",1,list(vote_title_text,"[j]\th",choices[myvote[j]])) if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results var/admintext = "Obfuscated results" + if(vote_system == RANKED_CHOICE_VOTING) + admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" for(var/i=1,i<=choices.len,i++) var/votes = choices[choices[i]] admintext += "\n[choices[i]]: [votes]" message_admins(admintext) return . -#define PEACE "calm" -#define CHAOS "chaotic" - /datum/controller/subsystem/vote/proc/result() . = announce_result() var/restart = 0 @@ -152,33 +265,15 @@ SUBSYSTEM_DEF(vote) if("dynamic") if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") - GLOB.master_mode = "dynamic" - if("extended" in choices) - if(. == "extended") - GLOB.dynamic_forced_extended = TRUE // we still do the rest of the stuff - choices[PEACE] += choices["extended"] - var/mean = 0 - var/voters = 0 - for(var/client/c in GLOB.clients) - var/vote = c.prefs.preferred_chaos - if(vote) - voters += 1 - switch(vote) - if(CHAOS_NONE) - mean -= 0.1 - if(CHAOS_LOW) - mean -= 0.05 - if(CHAOS_HIGH) - mean += 0.05 - if(CHAOS_MAX) - mean += 0.1 - mean/=voters - if(voted.len != 0) - mean += (choices[PEACE]*-1+choices[CHAOS])/voted.len - GLOB.dynamic_curve_centre = mean*20 - GLOB.dynamic_curve_width = CLAMP(2-abs(mean*5),0.5,4) - to_chat(world,"Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width].") - log_admin("Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width]") + if(. == "Secret") + GLOB.master_mode = "secret" + SSticker.save_mode(.) + message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") + log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") + else + GLOB.master_mode = "dynamic" + var/datum/dynamic_storyteller/S = config.pick_storyteller(.) + GLOB.dynamic_storyteller_type = S if("map") var/datum/map_config/VM = config.maplist[.] message_admins("The map has been voted for and will change to: [VM.map_name]") @@ -196,27 +291,58 @@ SUBSYSTEM_DEF(vote) else to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.") message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.") - + return . -/datum/controller/subsystem/vote/proc/submit_vote(vote) +/datum/controller/subsystem/vote/proc/submit_vote(vote, score = 0) if(mode) if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) return 0 - if(!(usr.ckey in voted)) - if(vote && 1<=vote && vote<=choices.len) - voted += usr.ckey - voted[usr.ckey] = vote - choices[choices[vote]]++ //check this - return vote - else if(vote && 1<=vote && vote<=choices.len) - choices[choices[voted[usr.ckey]]]-- - voted[usr.ckey] = vote - choices[choices[vote]]++ - return vote + if(vote && ISINRANGE(vote, 1, choices.len)) + switch(vote_system) + if(PLURALITY_VOTING) + if(usr.ckey in voted) + choices[choices[voted[usr.ckey]]]-- + voted[usr.ckey] = vote + choices[choices[vote]]++ + return vote + else + voted += usr.ckey + voted[usr.ckey] = vote + choices[choices[vote]]++ //check this + return vote + if(APPROVAL_VOTING) + if(usr.ckey in voted) + if(vote in voted[usr.ckey]) + voted[usr.ckey] -= vote + choices[choices[vote]]-- + else + voted[usr.ckey] += vote + choices[choices[vote]]++ + else + voted += usr.ckey + voted[usr.ckey] = list(vote) + choices[choices[vote]]++ + return vote + if(RANKED_CHOICE_VOTING) + if(usr.ckey in voted) + if(vote in voted[usr.ckey]) + voted[usr.ckey] -= vote + else + voted += usr.ckey + voted[usr.ckey] = list() + voted[usr.ckey] += vote + saved -= usr.ckey + if(SCORE_VOTING) + if(!(usr.ckey in voted)) + voted += usr.ckey + voted[usr.ckey] = list() + voted[usr.ckey][choices[vote]] = score + saved -= usr.ckey return 0 -/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes +/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults, votesystem = PLURALITY_VOTING, forced = FALSE,vote_time = -1)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes + vote_system = votesystem if(!mode) if(started_time) var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) @@ -257,11 +383,17 @@ SUBSYSTEM_DEF(vote) if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote choices.Add("secret", "extended") if("dynamic") - var/saved_threats = SSpersistence.saved_threat_levels - if((saved_threats[1]+saved_threats[2]+saved_threats[3])>150) - choices.Add("extended",PEACE,CHAOS) - else - choices.Add(PEACE,CHAOS) + for(var/T in config.storyteller_cache) + var/datum/dynamic_storyteller/S = T + var/recent_rounds = 0 + for(var/i in 1 to SSpersistence.saved_storytellers.len) + if(SSpersistence.saved_storytellers[i] == initial(S.name)) + recent_rounds++ + if(recent_rounds < initial(S.weight)) + choices.Add(initial(S.name)) + choice_descs.Add(initial(S.desc)) + choices.Add("Secret") + choice_descs.Add("Standard secret. Switches mode if it wins.") if("custom") question = stripped_input(usr,"What is the vote for?") if(!question) @@ -280,9 +412,11 @@ SUBSYSTEM_DEF(vote) if(mode == "custom") text += "\n[question]" log_vote(text) - var/vp = CONFIG_GET(number/vote_period) + var/vp = vote_time + if(vp == -1) + vp = CONFIG_GET(number/vote_period) to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.") - time_remaining = round(vp/10) + end_time = started_time+vp for(var/c in GLOB.clients) SEND_SOUND(c, sound('sound/misc/server-ready.ogg')) var/client/C = c @@ -292,6 +426,11 @@ SUBSYSTEM_DEF(vote) C.player_details.player_actions += V V.Grant(C.mob) generated_actions += V + if(forced) + var/datum/browser/popup = new(C, "vote", "Voting Panel",nwidth=600,nheight=700) + popup.set_window_options("can_close=0") + popup.set_content(SSvote.interface(C)) + popup.open(0) return 1 return 0 @@ -311,14 +450,71 @@ SUBSYSTEM_DEF(vote) . += "

Vote: '[question]'

" else . += "

Vote: [capitalize(mode)]

" - . += "Time Left: [time_remaining] s

" + switch(vote_system) + if(PLURALITY_VOTING) + . += "

Vote one.

" + if(APPROVAL_VOTING) + . += "

Vote any number of choices.

" + if(RANKED_CHOICE_VOTING) + . += "

Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.

" + if(SCORE_VOTING) + . += "

Grade the candidates by how much you like them.

" + . += "

No-votes have no power--your opinion is only heard if you vote!

" + . += "Time Left: [DisplayTimeText(end_time-world.time)]

" + if(RANKED_CHOICE_VOTING) + var/list/myvote = voted[C.ckey] + for(var/i=1,i<=choices.len,i++) + var/vote = (myvote ? (myvote.Find(i)) : 0) + if(vote) + . += "
  • [choices[i]] ([vote])
  • " + else + . += "
  • [choices[i]]
  • " + if(choice_descs.len >= i) + . += "
  • [choice_descs[i]]
  • " + . += "
    " + if(!(C.ckey in saved)) + . += "(Save vote)" + else + . += "(Saved!)" + . += "(Load vote from save)" + . += "(Reset votes)" + if(SCORE_VOTING) + var/list/myvote = voted[C.ckey] + for(var/i=1,i<=choices.len,i++) + . += "
  • [choices[i]]" + for(var/r in 1 to GLOB.vote_score_options.len) + . += " " + if((choices[i] in myvote) && myvote[choices[i]] == r) + . +="([GLOB.vote_score_options[r]])" + else + . +="[GLOB.vote_score_options[r]]" + . += "" + . += "
  • " + if(choice_descs.len >= i) + . += "
  • [choice_descs[i]]
  • " + . += "
    " + if(!(C.ckey in saved)) + . += "(Save vote)" + else + . += "(Saved!)" + . += "(Load vote from save)" + . += "(Reset votes)" if(admin) . += "(Cancel Vote) " else @@ -376,8 +572,31 @@ SUBSYSTEM_DEF(vote) if("custom") if(usr.client.holder) initiate_vote("custom",usr.key) + if("reset") + if(usr.ckey in voted) + voted -= usr.ckey + if("save") + if(usr.ckey in voted) + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.saved_votes[usr.ckey] = list() + SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] + saved += usr.ckey + if("load") + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.LoadSavedVote(usr.ckey) + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.saved_votes[usr.ckey] = list() + if(usr.ckey in voted) + SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] + else + SSpersistence.saved_votes[usr.ckey][mode] = list() + voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode] + saved += usr.ckey else - submit_vote(round(text2num(href_list["vote"]))) + if(vote_system == SCORE_VOTING) + submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"]))) + else + submit_vote(round(text2num(href_list["vote"]))) usr.vote() /datum/controller/subsystem/vote/proc/remove_action_buttons() @@ -392,7 +611,7 @@ SUBSYSTEM_DEF(vote) set category = "OOC" set name = "Vote" - var/datum/browser/popup = new(src, "vote", "Voting Panel") + var/datum/browser/popup = new(src, "vote", "Voting Panel",nwidth=600,nheight=700) popup.set_window_options("can_close=0") popup.set_content(SSvote.interface(client)) popup.open(0) @@ -419,6 +638,3 @@ SUBSYSTEM_DEF(vote) var/datum/player_details/P = GLOB.player_details[owner.ckey] if(P) P.player_actions -= src - -#undef PEACE -#undef CHAOS diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index 0ef13b514b..89c4deb2e9 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -11,8 +11,9 @@ var/list/datum/nanite_program/programs = list() var/max_programs = NANITE_PROGRAM_LIMIT - var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans, and does not display the program list on nanite scans - + var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans + var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners + /datum/component/nanites/Initialize(amount = 100, cloud = 0) if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup)) return COMPONENT_INCOMPATIBLE @@ -252,8 +253,8 @@ to_chat(user, "Cloud ID: [cloud_id ? cloud_id : "Disabled"]") to_chat(user, "================") to_chat(user, "Program List:") - if(stealth) - to_chat(user, "%#$ENCRYPTED&^@") + if(!diagnostics) + to_chat(user, "Diagnostics Disabled") else for(var/X in programs) var/datum/nanite_program/NP = X diff --git a/code/datums/martial/rising_bass.dm b/code/datums/martial/rising_bass.dm index a3b71c6784..9e00c90a92 100644 --- a/code/datums/martial/rising_bass.dm +++ b/code/datums/martial/rising_bass.dm @@ -150,7 +150,9 @@ if(!.) return ADD_TRAIT(H, TRAIT_NOGUNS, RISING_BASS_TRAIT) + ADD_TRAIT(H, TRAIT_AUTO_CATCH_ITEM, RISING_BASS_TRAIT) /datum/martial_art/the_rising_bass/on_remove(mob/living/carbon/human/H) . = ..() - REMOVE_TRAIT(H, TRAIT_NOGUNS, RISING_BASS_TRAIT) \ No newline at end of file + REMOVE_TRAIT(H, TRAIT_NOGUNS, RISING_BASS_TRAIT) + REMOVE_TRAIT(H, TRAIT_AUTO_CATCH_ITEM, RISING_BASS_TRAIT) \ No newline at end of file diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 5c4fc61827..5dab98d5ca 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -292,6 +292,7 @@ var/mob/living/carbon/C = owner for(var/X in C.bodyparts) var/obj/item/bodypart/BP = X + BP.max_damage *= 10 BP.brute_dam *= 10 BP.burn_dam *= 10 owner.toxloss *= 10 @@ -377,6 +378,7 @@ var/obj/item/bodypart/BP = X BP.brute_dam *= 0.1 BP.burn_dam *= 0.1 + BP.max_damage /= 10 owner.toxloss *= 0.1 owner.oxyloss *= 0.1 owner.cloneloss *= 0.1 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 66570205e3..36b701aae1 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -47,7 +47,7 @@ /atom/New(loc, ...) //atom creation method that preloads variables at creation if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New() - GLOB._preloader.load(src) + world.preloader_load(src) if(datum_flags & DF_USE_TAG) GenerateTag() @@ -166,7 +166,7 @@ return FALSE -/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) if(does_attack_animation) user.changeNext_move(CLICK_CD_MELEE) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index ef20b6e35e..7c57de74a4 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -515,10 +515,10 @@ step(src, AM.dir) ..() -/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback) - return throw_at(target, range, speed, thrower, spin, diagonals_first, callback) +/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) + return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, messy_throw) -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback) //If this returns FALSE then callback will not be called. +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) //If this returns FALSE then callback will not be called. . = FALSE if (!target || speed <= 0) return diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index 0890d81dfb..821c819664 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -1,14 +1,3 @@ -#define CURRENT_LIVING_PLAYERS 1 -#define CURRENT_LIVING_ANTAGS 2 -#define CURRENT_DEAD_PLAYERS 3 -#define CURRENT_OBSERVERS 4 - -#define ONLY_RULESET 1 -#define HIGHLANDER_RULESET 2 -#define TRAITOR_RULESET 4 -#define MINOR_RULESET 8 - -#define RULESET_STOP_PROCESSING 1 // -- Injection delays GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (10 MINUTES)) @@ -52,6 +41,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset) // Forced threat level, setting this to zero or higher forces the roundstart threat to the value. GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) +GLOBAL_VAR_INIT(dynamic_storyteller_type, null) + /datum/game_mode/dynamic name = "dynamic mode" config_tag = "dynamic" @@ -60,7 +51,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) announce_text = "Dynamic mode!" // This needs to be changed maybe reroll_friendly = FALSE; - + // Current storyteller + var/datum/dynamic_storyteller/storyteller = null // Threat logging vars /// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations var/threat_level = 0 @@ -164,6 +156,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
    " dat += "
    " + dat += "Storyteller: [storyteller.name]
    " dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].
    " dat += "On average, [peaceful_percentage]% of the rounds are more peaceful.
    " dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
    " @@ -178,7 +171,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) dat += "[DR.ruletype] - [DR.name]
    " else dat += "none.
    " - dat += "
    Injection Timers: ([get_injection_chance(TRUE)]% chance)
    " + dat += "
    Injection Timers: ([storyteller.get_injection_chance(TRUE)]% chance)
    " dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
    " dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
    " dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)] seconds"] \[Now!\]
    " @@ -336,6 +329,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) SSblackbox.record_feedback("tally","dynamic_threat",peaceful_percentage,"Percent of same-vote rounds that are more peaceful") /datum/game_mode/dynamic/can_start() + storyteller = new GLOB.dynamic_storyteller_type // this is where all the initialization happens + storyteller.on_start() + SSblackbox.record_feedback("text","dynamic_storyteller",1,storyteller.name) message_admins("Dynamic mode parameters for the round:") message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].") message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].") @@ -345,19 +341,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if(GLOB.dynamic_forced_threat_level >= 0) threat_level = round(GLOB.dynamic_forced_threat_level, 0.1) threat = threat_level - SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced by admins)") + SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced)") else generate_threat() - var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min) - latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time + storyteller.start_injection_cooldowns() - var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max) - midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time - - var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min) - event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time) - log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!") initial_threat_level = threat_level return TRUE @@ -391,7 +380,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) log_game("DYNAMIC: [roundstart_rules.len] rules.") return TRUE SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Roundstart rules considered") - SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Players readied up") + SSblackbox.record_feedback("tally","dynamic",roundstart_pop_ready,"Players readied up") if(GLOB.dynamic_forced_roundstart_ruleset.len > 0) rigged_roundstart() else @@ -429,13 +418,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (GLOB.dynamic_forced_extended) log_game("DYNAMIC: Starting a round of forced extended.") return TRUE - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules) - if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required - rule.candidates = candidates.Copy() - rule.trim_candidates() - if (rule.ready() && rule.candidates.len > 0) - drafted_rules[rule] = rule.weight + var/list/drafted_rules = storyteller.roundstart_draft() if(!drafted_rules.len) message_admins("Not enough threat level for roundstart antags!") log_game("DYNAMIC: Not enough threat level for roundstart antags!") @@ -653,7 +636,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) if (rule.persistent) current_rules += rule return TRUE - rule.clean_up() stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.") return FALSE @@ -667,62 +649,41 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) current_rules -= rule SSblackbox.record_feedback("tally","dynamic",1,"Rulesets finished") SSblackbox.record_feedback("associative","dynamic_rulesets_finished",1,rule.get_blackbox_info()) - + + storyteller.do_process() + if (midround_injection_cooldown < world.time) if (GLOB.dynamic_forced_extended) return // Somehow it managed to trigger midround multiple times so this was moved here. // There is no way this should be able to trigger an injection twice now. - var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) - midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time) + midround_injection_cooldown = storyteller.get_midround_cooldown() + world.time // Time to inject some threat into the round if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone return + if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called + return message_admins("DYNAMIC: Checking for midround injection.") log_game("DYNAMIC: Checking for midround injection.") update_playercounts() - if (get_injection_chance()) + if (prob(storyteller.get_injection_chance())) SSblackbox.record_feedback("tally","dynamic",1,"Attempted midround injections") - var/cur_threat_frac = threat/threat_level - var/list/drafted_rules = list() - var/antag_num = current_players[CURRENT_LIVING_ANTAGS].len - for (var/datum/dynamic_ruleset/midround/rule in midround_rules) - // if there are antags OR the rule is an antag rule, antag_acceptable will be true. - if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) - // Classic secret : only autotraitor/minor roles - if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) - continue - rule.trim_candidates() - if (rule.ready()) - if(!antag_num) - drafted_rules[rule] = round(rule.get_weight() + (rule.cost * cur_threat_frac)) - else - drafted_rules[rule] = rule.get_weight() - else if(threat < rule.cost) - SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend") + var/list/drafted_rules = storyteller.midround_draft() if (drafted_rules.len > 0) SSblackbox.record_feedback("tally","dynamic",1,"Successful midround injections") picking_midround_latejoin_rule(drafted_rules) - else - midround_injection_cooldown = (midround_injection_cooldown + world.time)/2 + // get_injection_chance can do things on fail if(event_injection_cooldown < world.time) SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections") - var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min) - event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time) + event_injection_cooldown = storyteller.get_event_cooldown() + world.time message_admins("DYNAMIC: Doing event injection.") log_game("DYNAMIC: Doing event injection.") update_playercounts() - var/list/drafted_rules = list() - for(var/datum/dynamic_ruleset/event/rule in events) - if(rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) - if(rule.ready()) - drafted_rules[rule] = rule.get_weight() - else if(threat < rule.cost) - SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend") + var/list/drafted_rules = storyteller.event_draft() if(drafted_rules.len > 0) SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections") picking_midround_latejoin_rule(drafted_rules) @@ -748,31 +709,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) continue current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow) -/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection. -/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE) - if(forced_injection) - forced_injection = !dry_run - return 100 - var/chance = 0 - // If the high pop override is in effect, we reduce the impact of population on the antag injection chance - var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit) - var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5))) - if (!current_players[CURRENT_LIVING_ANTAGS].len) - chance += 80 // No antags at all? let's boost those odds! - else - var/current_pop_per_antag = current_players[CURRENT_LIVING_PLAYERS].len / current_players[CURRENT_LIVING_ANTAGS].len - if (current_pop_per_antag > max_pop_per_antag) - chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag)) - else - chance += 25-10*(max_pop_per_antag-current_pop_per_antag) - if (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len) - chance -= 30 // More than half the crew died? ew, let's calm down on antags - if (threat > 70) - chance += 15 - if (threat < 30) - chance -= 15 - return round(max(0,chance)) - /// Removes type from the list /datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type) for(var/I in type_list) @@ -803,7 +739,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) return if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left return - + if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called + return update_playercounts() if (forced_latejoin_rule) @@ -814,28 +751,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE) forced_latejoin_rule = null - else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance())) + else if (latejoin_injection_cooldown < world.time && prob(storyteller.get_injection_chance())) SSblackbox.record_feedback("tally","dynamic",1,"Attempted latejoin injections") - var/list/drafted_rules = list() - for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules) - if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost) - // Classic secret : only autotraitor/minor roles - if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) - continue - // No stacking : only one round-ender, unless threat level > stacking_limit. - if (threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) - if(rule.flags & HIGHLANDER_RULESET && highlander_executed) - continue - - rule.candidates = list(newPlayer) - rule.trim_candidates() - if (rule.ready()) - drafted_rules[rule] = rule.get_weight() - + var/list/drafted_rules = storyteller.latejoin_draft(newPlayer) if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules)) SSblackbox.record_feedback("tally","dynamic",1,"Successful latejoin injections") - var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min) - latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time + latejoin_injection_cooldown = storyteller.get_latejoin_cooldown() + world.time /// Refund threat, but no more than threat_level. /datum/game_mode/dynamic/proc/refund_threat(regain) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index 7f6ea088a1..2f143f7233 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -80,9 +80,13 @@ /// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin). /// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure. var/delay = 0 + /// List of tags for use in storytellers. + var/list/property_weights = list() /// Whether or not recent-round weight values are taken into account for this ruleset. /// Weight reduction uses the same values as secret's recent-round mode weight reduction. var/always_max_weight = FALSE + /// Weight reduction by recent-rounds. Saved on new. + var/weight_mult = 1 /datum/dynamic_ruleset/New() ..() @@ -96,13 +100,11 @@ var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement) var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust) if(config_tag in weights) - var/weight_mult = 1 if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3) var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules for(var/i in 1 to 3) if(config_tag in saved_dynamic_rules[i]) weight_mult -= (repeated_mode_adjust[i]/100) - weight = weights[config_tag] * weight_mult if(config_tag in costs) cost = costs[config_tag] if(config_tag in requirementses) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm index b6cd313bfc..23e94c4065 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm @@ -58,6 +58,7 @@ cost = 10 blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear) requirements = list(70,60,50,50,40,40,40,30,20,15) + property_weights = list("story_potential" = 1, "trust" = 1, "chaos" = 1) high_population_requirement = 15 /datum/dynamic_ruleset/event/pirates/ready(forced = FALSE) @@ -81,6 +82,7 @@ cost = 10 requirements = list(70,60,50,50,40,40,40,30,20,15) high_population_requirement = 15 + property_weights = list("chaos" = 1, "valid" = 1) ////////////////////////////////////////////// // // @@ -100,6 +102,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended high_population_requirement = 5 repeatable = TRUE + property_weights = list("chaos" = 1, "extended" = 2) /datum/dynamic_ruleset/event/ventclog/ready() if(mode.threat_level > 30 && mode.threat >= 5 && prob(20)) @@ -133,10 +136,11 @@ required_enemies = list(1,1,0,0,0,0,0,0,0,0) weight = 4 // no repeatable weight decrease. too variable to be unfun multiple times in one round - cost = 3 + cost = 1 requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("story_potential" = 1, "extended" = 1) always_max_weight = TRUE ////////////////////////////////////////////// @@ -156,6 +160,7 @@ repeatable_weight_decrease = 2 requirements = list(60,50,40,30,30,30,30,30,30,30) high_population_requirement = 30 + property_weights = list("extended" = -2) /datum/dynamic_ruleset/event/meteor_wave/ready() if(mode.threat_level > 40 && mode.threat >= 25 && prob(20)) @@ -190,6 +195,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/anomaly_flux name = "Anomaly: Hyper-Energetic Flux" @@ -203,6 +209,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/anomaly_gravitational name = "Anomaly: Gravitational" @@ -214,6 +221,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/anomaly_pyroclastic name = "Anomaly: Pyroclastic" @@ -227,6 +235,7 @@ requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/anomaly_vortex name = "Anomaly: Vortex" @@ -240,6 +249,7 @@ requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = 1) ////////////////////////////////////////////// // // @@ -259,6 +269,7 @@ requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = -1, "chaos" = 1) /datum/dynamic_ruleset/event/carp_migration name = "Carp Migration" @@ -270,6 +281,7 @@ requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/communications_blackout name = "Communications Blackout" @@ -283,6 +295,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1, "chaos" = 1) /datum/dynamic_ruleset/event/processor_overload name = "Processor Overload" @@ -296,6 +309,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1, "chaos" = 1) always_max_weight = TRUE /datum/dynamic_ruleset/event/space_dust @@ -310,6 +324,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1) always_max_weight = TRUE /datum/dynamic_ruleset/event/major_dust @@ -324,6 +339,7 @@ requirements = list(10,10,10,10,10,10,10,10,10,10) high_population_requirement = 10 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/electrical_storm name = "Electrical Storm" @@ -337,6 +353,7 @@ requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1) /datum/dynamic_ruleset/event/heart_attack name = "Random Heart Attack" @@ -350,6 +367,7 @@ requirements = list(101,101,101,5,5,5,5,5,5,5) high_population_requirement = 5 repeatable = TRUE + property_weights = list("extended" = 1) always_max_weight = TRUE /datum/dynamic_ruleset/event/radiation_storm @@ -362,3 +380,4 @@ required_enemies = list(1,1,1,1,1,1,1,1,1,1) requirements = list(5,5,5,5,5,5,5,5,5,5) high_population_requirement = 5 + property_weights = list("extended" = 1,"chaos" = 1) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index fe9347d9d5..f4e9971a05 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -69,8 +69,15 @@ high_population_requirement = 15 repeatable = TRUE flags = TRAITOR_RULESET + property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1) always_max_weight = TRUE +/datum/dynamic_ruleset/latejoin/infiltrator/execute() + . = ..() + for(var/datum/mind/M in assigned) + log_admin("[M.name] was made into a traitor by dynamic.") + message_admins("[M.name] was made into a traitor by dynamic.") + ////////////////////////////////////////////// // // // REVOLUTIONARY PROVOCATEUR // @@ -94,6 +101,7 @@ requirements = list(101,101,70,40,40,40,40,40,40,40) high_population_requirement = 40 flags = HIGHLANDER_RULESET + property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1) var/required_heads_of_staff = 3 var/finished = FALSE var/datum/team/revolution/revolution @@ -123,6 +131,8 @@ revolution.update_objectives() revolution.update_heads() SSshuttle.registerHostileEnvironment(src) + log_admin("[M.name] was made into a revolutionary by dynamic.") + message_admins("[M.name] was made into a revolutionary by dynamic.") return TRUE else log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.") @@ -187,30 +197,31 @@ ////////////////////////////////////////////// // // -// VAMPIRE // +// BLOODSUCKERS // // // ////////////////////////////////////////////// -/* -/datum/dynamic_ruleset/latejoin/vampire - name = "vampire" - config_tag = "vampire_latejoin" - antag_flag = ROLE_VAMPIRE - antag_datum = ANTAG_DATUM_VAMPIRE - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain") +/datum/dynamic_ruleset/latejoin/bloodsucker + name = "Bloodsucker Infiltrator" + config_tag = "latejoin_bloodsucker" + antag_datum = ANTAG_DATUM_BLOODSUCKER + antag_flag = ROLE_TRAITOR restricted_roles = list("AI", "Cyborg") + protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") required_candidates = 1 - weight = 5 - cost = 15 - requirements = list(80,70,60,50,40,20,20,15,15,15) + weight = 3 + cost = 10 + property_weights = list("story_potential" = 2, "extended" = 2, "trust" = -2, "valid" = 1) + requirements = list(70,65,60,55,50,45,40,35,30,30) + high_population_requirement = 30 repeatable = TRUE - high_population_requirement = 15 -/datum/dynamic_ruleset/latejoin/vampire/pre_execute() +/datum/dynamic_ruleset/latejoin/bloodsucker/execute() var/mob/M = pick(candidates) - candidates -= M assigned += M.mind - M.mind.restricted_roles = restricted_roles - M.mind.special_role = ROLE_VAMPIRE + M.mind.special_role = antag_flag + if(mode.make_bloodsucker(M.mind)) + mode.bloodsuckers += M + log_admin("[M.name] was made into a bloodsucker by dynamic.") + message_admins("[M.name] was made into a bloodsucker by dynamic.") return TRUE -*/ diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 32c076b69a..92a02f5920 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -133,8 +133,12 @@ candidates = pollGhostCandidates("The mode is looking for volunteers to become a [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300) - if(!candidates || candidates.len <= required_candidates) + if(!candidates || candidates.len < required_candidates) message_admins("The ruleset [name] did not receive enough applications.") + if(candidates) + message_admins("Only received [candidates.len], needed [required_candidates].") + else + message_admins("There were no candidates.") log_game("DYNAMIC: The ruleset [name] did not receive enough applications.") return FALSE @@ -205,6 +209,7 @@ repeatable = TRUE high_population_requirement = 15 flags = TRAITOR_RULESET + property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1) always_max_weight = TRUE /datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0) @@ -239,6 +244,8 @@ living_players -= M var/datum/antagonist/traitor/newTraitor = new M.mind.add_antag_datum(newTraitor) + log_admin("[M] was made into a traitor by dynamic.") + message_admins("[M] was made into a traitor by dynamic.") return TRUE @@ -262,6 +269,7 @@ requirements = list(101,101,70,50,50,50,40,30,30,30) high_population_requirement = 30 required_type = /mob/living/silicon/ai + property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2) var/ion_announce = 33 var/removeDontImproveChance = 10 @@ -286,6 +294,8 @@ var/datum/antagonist/traitor/AI = new M.mind.special_role = antag_flag M.mind.add_antag_datum(AI) + log_admin("[M] was made into a malf AI by dynamic.") + message_admins("[M] was made into a malf AI by dynamic.") if(prob(ion_announce)) priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm") if(prob(removeDontImproveChance)) @@ -314,6 +324,7 @@ requirements = list(90,90,70,50,50,50,50,40,30,30) high_population_requirement = 30 repeatable = TRUE + property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2) var/datum/mind/wizard /datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE) @@ -359,6 +370,7 @@ cost = 35 requirements = list(90,90,90,80,70,60,50,40,40,40) high_population_requirement = 40 + property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2) var/operative_cap = list(2,2,3,3,4,5,5,5,5,5) var/datum/team/nuclear/nuke_team flags = HIGHLANDER_RULESET @@ -406,6 +418,7 @@ requirements = list(101,101,101,80,60,50,50,50,50,50) high_population_requirement = 50 repeatable = TRUE + property_weights = list("story_potential" = -1, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2) /datum/dynamic_ruleset/midround/from_ghosts/blob/generate_ruleset_body(mob/applicant) var/body = applicant.become_overmind() @@ -431,6 +444,7 @@ high_population_requirement = 50 repeatable_weight_decrease = 2 repeatable = TRUE + property_weights = list("story_potential" = -1, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2) var/list/vents = list() /datum/dynamic_ruleset/midround/from_ghosts/xenomorph/execute() @@ -480,6 +494,7 @@ high_population_requirement = 50 repeatable_weight_decrease = 2 repeatable = TRUE + property_weights = list("story_potential" = 1, "trust" = 1, "extended" = 1, "valid" = 2, "integrity" = 2) var/list/spawn_locs = list() /datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute() @@ -525,6 +540,7 @@ weight = 4 cost = 5 requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"! + property_weights = list("story_potential" = 1, "extended" = 1, "valid" = -2) high_population_requirement = 5 /datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/generate_ruleset_body(mob/applicant) @@ -553,14 +569,18 @@ cost = 5 requirements = list(30,30,30,30,20,15,15,15,15,15) high_population_requirement = 15 + property_weights = list("story_potential" = -2, "extended" = -1) var/list/spawn_locs = list() -/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE) +/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population = 0,threat = 0) var/deadMobs = 0 for(var/mob/M in GLOB.dead_mob_list) deadMobs++ if(deadMobs < REVENANT_SPAWN_THRESHOLD) return FALSE + return ..() + +/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE) for(var/mob/living/L in GLOB.dead_mob_list) //look for any dead bodies var/turf/T = get_turf(L) if(T && is_station_level(T.z)) @@ -602,6 +622,7 @@ weight = 4 cost = 15 requirements = list(101,101,101,90,80,70,60,50,40,30) + property_weights = list("story_potential" = -2, "extended" = -2, "integrity" = 2, "valid" = 2, "trust" = 2) high_population_requirement = 30 var/list/spawn_locs = list() @@ -651,6 +672,7 @@ blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear) high_population_requirement = 15 var/datum/team/abductor_team/team + property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 1, "trust" = -1, "chaos" = 2) repeatable_weight_decrease = 4 repeatable = TRUE @@ -688,6 +710,7 @@ cost = 15 requirements = list(101,101,101,90,80,70,60,50,40,30) high_population_requirement = 30 + property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 2) var/list/spawn_locs = list() var/spawn_loc @@ -731,31 +754,3 @@ #undef ABDUCTOR_MAX_TEAMS #undef REVENANT_SPAWN_THRESHOLD - -////////////////////////////////////////////// -// // -// BLOODSUCKERS // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/latejoin/bloodsucker - name = "Bloodsucker Infiltrator" - config_tag = "latejoin_bloodsucker" - antag_datum = ANTAG_DATUM_BLOODSUCKER - antag_flag = ROLE_TRAITOR - restricted_roles = list("AI", "Cyborg") - protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") - required_candidates = 1 - weight = 3 - cost = 10 - requirements = list(90,80,70,60,55,50,45,40,35,30) - high_population_requirement = 30 - repeatable = TRUE - -/datum/dynamic_ruleset/latejoin/bloodsucker/execute() - var/mob/M = pick(candidates) - assigned += M.mind - M.mind.special_role = antag_flag - if(mode.make_bloodsucker(M.mind)) - mode.bloodsuckers += M - return TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index e5bb0af464..abd40a1756 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -21,6 +21,7 @@ requirements = list(50,50,50,50,50,50,50,50,50,50) high_population_requirement = 40 antag_cap = list(1,1,1,1,2,2,2,2,3,3) + property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1, "valid" = 1) always_max_weight = TRUE var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec) @@ -61,6 +62,7 @@ requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter. + property_weights = list("story_potential" = 1, "trust" = -1, "extended" = 1, "valid" = 1) var/list/datum/team/brother_team/pre_brother_teams = list() var/const/min_team_size = 2 @@ -108,6 +110,7 @@ cost = 15 scaling_cost = 15 requirements = list(101,101,101,101,101,101,101,101,101,101) + property_weights = list("trust" = -2, "valid" = 2) high_population_requirement = 10 antag_cap = list(1,1,1,1,1,2,2,2,2,3) var/team_mode_probability = 30 @@ -160,6 +163,7 @@ cost = 30 requirements = list(101,101,101,60,50,50,50,50,50,50) high_population_requirement = 50 + property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2) var/list/roundstart_wizards = list() /datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0) @@ -222,6 +226,7 @@ weight = 3 cost = 30 requirements = list(101,101,101,80,70,60,50,50,50,50) + property_weights = list("story_potential" = -1, "trust" = -1, "chaos" = 1, "conversion" = 1, "extended" = -2, "valid" = 2) high_population_requirement = 50 flags = HIGHLANDER_RULESET antag_cap = list(2,2,2,3,3,4,4,4,4,4) @@ -283,6 +288,7 @@ high_population_requirement = 50 flags = HIGHLANDER_RULESET antag_cap = list(1,1,2,3,4,5,5,5,5,5) + property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2) var/datum/team/nuclear/nuke_team /datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE) @@ -373,6 +379,7 @@ flags = HIGHLANDER_RULESET // I give up, just there should be enough heads with 35 players... minimum_players = 35 + property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1) var/datum/team/revolution/revolution var/finished = FALSE @@ -490,6 +497,7 @@ weight = 3 cost = 0 requirements = list(101,101,101,101,101,101,101,101,101,101) + property_weights = list("extended" = 2) high_population_requirement = 101 /datum/dynamic_ruleset/roundstart/extended/pre_execute() @@ -517,6 +525,7 @@ high_population_requirement = 50 flags = HIGHLANDER_RULESET antag_cap = list(2,3,3,4,4,4,4,4,4,4) + property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "conversion" = 1, "valid" = 2) var/ark_time /datum/dynamic_ruleset/roundstart/clockcult/pre_execute() @@ -616,6 +625,8 @@ antag_leader_datum = /datum/antagonist/nukeop/leader/clownop requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 + property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "story_potential" = 2, "valid" = 2) + /datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute() . = ..() @@ -647,6 +658,7 @@ requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 antag_cap = list(1,1,1,2,2,2,3,3,3,4) + property_weights = list("extended" = 1) /datum/dynamic_ruleset/roundstart/devil/pre_execute() var/num_devils = antag_cap[indice_pop] @@ -698,6 +710,7 @@ cost = 0 requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 + property_weights = list("extended" = -2, "chaos" = 2, "conversion" = 1, "valid" = 2) var/players_per_carrier = 30 var/monkeys_to_win = 1 var/escaped_monkeys = 0 @@ -759,6 +772,7 @@ cost = 0 requirements = list(101,101,101,101,101,101,101,101,101,101) high_population_requirement = 101 + property_weights = list("extended" = -2, "chaos" = 2, "trust" = 2) var/meteordelay = 2000 var/nometeors = 0 var/rampupdelta = 5 @@ -799,7 +813,8 @@ weight = 2 cost = 15 scaling_cost = 10 - requirements = list(90,80,70,60,50,50,50,50,50,50) + property_weights = list("story_potential" = 1, "extended" = 1, "trust" = -2, "valid" = 1) + requirements = list(70,65,60,55,50,50,50,50,50,50) high_population_requirement = 50 antag_cap = list(1,1,1,1,1,2,2,2,2,2) diff --git a/code/game/gamemodes/dynamic/dynamic_storytellers.dm b/code/game/gamemodes/dynamic/dynamic_storytellers.dm new file mode 100644 index 0000000000..afa6ed8ba7 --- /dev/null +++ b/code/game/gamemodes/dynamic/dynamic_storytellers.dm @@ -0,0 +1,235 @@ +/datum/dynamic_storyteller + var/name = "none" + var/desc = "A coder's idiocy." + var/list/property_weights = list() + var/curve_centre = 0 + var/curve_width = 1.8 + var/forced_threat_level = -1 + var/flags = 0 + var/weight = 3 // how many rounds need to have been recently played for this storyteller to be left out of the vote + var/datum/game_mode/dynamic/mode = null + +/** +Property weights are: +"story_potential" -- essentially how many different ways the antag can be played. +"trust" -- How much it makes the crew trust each other. Negative values means they're suspicious. Team antags are like this. +"chaos" -- How chaotic it makes the round. Has some overlap with "valid" and somewhat contradicts "extended". +"valid" -- How likely the non-antag-enemy crew are to get involved, e.g. nukies encouraging the warden to + let everyone into the armory, wizard moving around and being a nuisance, nightmare busting lights. +"extended" -- How much the antag is conducive to a long round. Nukies and cults are bad for this; Wizard is less bad; and so on. +"conversion" -- Basically a bool. Conversion antags, well, convert. It's its own class for a good reason. +*/ + +/datum/dynamic_storyteller/New() + ..() + if (istype(SSticker.mode, /datum/game_mode/dynamic)) + mode = SSticker.mode + GLOB.dynamic_curve_centre = curve_centre + GLOB.dynamic_curve_width = curve_width + GLOB.dynamic_forced_threat_level = forced_threat_level + +/datum/dynamic_storyteller/proc/start_injection_cooldowns() + var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min) + mode.latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time + + var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max) + mode.midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time + + var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min) + mode.event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time) + +/datum/dynamic_storyteller/proc/do_process() + return + +/datum/dynamic_storyteller/proc/on_start() + return + +/datum/dynamic_storyteller/proc/get_midround_cooldown() + var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min) + return round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + +/datum/dynamic_storyteller/proc/get_event_cooldown() + var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min) + return round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + +/datum/dynamic_storyteller/proc/get_latejoin_cooldown() + var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min) + return round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + +/datum/dynamic_storyteller/proc/get_injection_chance(dry_run = FALSE) + if(mode.forced_injection) + mode.forced_injection = !dry_run + return 100 + var/chance = 0 + // If the high pop override is in effect, we reduce the impact of population on the antag injection chance + var/high_pop_factor = (mode.current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit) + var/max_pop_per_antag = max(5,15 - round(mode.threat_level/10) - round(mode.current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5))) + if (!mode.current_players[CURRENT_LIVING_ANTAGS].len) + chance += 80 // No antags at all? let's boost those odds! + else + var/current_pop_per_antag = mode.current_players[CURRENT_LIVING_PLAYERS].len / mode.current_players[CURRENT_LIVING_ANTAGS].len + if (current_pop_per_antag > max_pop_per_antag) + chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag)) + else + chance += 25-10*(max_pop_per_antag-current_pop_per_antag) + if (mode.current_players[CURRENT_DEAD_PLAYERS].len > mode.current_players[CURRENT_LIVING_PLAYERS].len) + chance -= 30 // More than half the crew died? ew, let's calm down on antags + if (mode.threat > 70) + chance += 15 + if (mode.threat < 30) + chance -= 15 + return round(max(0,chance)) + +/datum/dynamic_storyteller/proc/roundstart_draft() + var/list/drafted_rules = list() + for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules) + if (rule.acceptable(mode.roundstart_pop_ready, mode.threat_level) && mode.threat >= rule.cost) // If we got the population and threat required + rule.candidates = mode.candidates.Copy() + rule.trim_candidates() + if (rule.ready() && rule.candidates.len > 0) + var/property_weight = 0 + for(var/property in property_weights) + if(property in rule.property_weights) // just treat it as 0 if it's not in there + property_weight += rule.property_weights[property] * property_weights[property] + drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult + return drafted_rules + +/datum/dynamic_storyteller/proc/midround_draft() + var/list/drafted_rules = list() + for (var/datum/dynamic_ruleset/midround/rule in mode.midround_rules) + // if there are antags OR the rule is an antag rule, antag_acceptable will be true. + if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost) + // Classic secret : only autotraitor/minor roles + if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) + continue + rule.trim_candidates() + if (rule.ready()) + var/property_weight = 0 + for(var/property in property_weights) + if(property in rule.property_weights) + property_weight += rule.property_weights[property] * property_weights[property] + drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult + else if(mode.threat < rule.cost) + SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend") + return drafted_rules + +/datum/dynamic_storyteller/proc/latejoin_draft(mob/living/carbon/human/newPlayer) + var/list/drafted_rules = list() + for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules) + if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost) + // Classic secret : only autotraitor/minor roles + if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))) + continue + // No stacking : only one round-ender, unless threat level > stacking_limit. + if (mode.threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking) + if(rule.flags & HIGHLANDER_RULESET && mode.highlander_executed) + continue + + rule.candidates = list(newPlayer) + rule.trim_candidates() + if (rule.ready()) + var/property_weight = 0 + for(var/property in property_weights) + if(property in rule.property_weights) + property_weight += rule.property_weights[property] * property_weights[property] + drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult + else if(mode.threat < rule.cost) + SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend") + return drafted_rules + +/datum/dynamic_storyteller/proc/event_draft() + var/list/drafted_rules = list() + for(var/datum/dynamic_ruleset/event/rule in mode.events) + if(rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost) + if(rule.ready()) + var/property_weight = 0 + for(var/property in property_weights) + if(property in rule.property_weights) + property_weight += rule.property_weights[property] * property_weights[property] + drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult + else if(mode.threat < rule.cost) + SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend") + return drafted_rules + + +/datum/dynamic_storyteller/cowabunga + name = "Chaotic" + curve_centre = 10 + desc = "Chaos: high. Variation: high. Likely antags: clock cult, revs, wizard." + property_weights = list("extended" = -1, "chaos" = 10) + weight = 2 + flags = WAROPS_ALWAYS_ALLOWED + var/refund_cooldown + +/datum/dynamic_storyteller/cowabunga/get_midround_cooldown() + return ..() / 4 + +/datum/dynamic_storyteller/cowabunga/get_latejoin_cooldown() + return ..() / 4 + +/datum/dynamic_storyteller/cowabunga/do_process() + if(refund_cooldown < world.time) + mode.refund_threat(10) + mode.log_threat("Cowabunga it is. Refunded 10 threat. Threat is now [mode.threat].") + refund_cooldown = world.time + 300 SECONDS + +/datum/dynamic_storyteller/team + name = "Teamwork" + desc = "Chaos: high. Variation: low. Likely antags: nukies, clockwork cult, wizard, blob, xenomorph." + curve_centre = 2 + curve_width = 1.5 + weight = 2 + flags = WAROPS_ALWAYS_ALLOWED + property_weights = list("valid" = 3, "trust" = 5) + +/datum/dynamic_storyteller/team/get_injection_chance(dry_run = FALSE) + return (mode.current_players[CURRENT_LIVING_ANTAGS].len ? 0 : ..()) + +/datum/dynamic_storyteller/conversion + name = "Conversion" + desc = "Chaos: high. Variation: medium. Likely antags: cults, bloodsuckers, revs." + curve_centre = 3 + curve_width = 1 + weight = 2 + flags = WAROPS_ALWAYS_ALLOWED + property_weights = list("valid" = 1, "conversion" = 20) + +/datum/dynamic_storyteller/classic + name = "Random" + desc = "Chaos: varies. Variation: highest. No special weights attached." + weight = 6 + curve_width = 4 + +/datum/dynamic_storyteller/memes + name = "Story" + desc = "Chaos: varies. Variation: high. Likely antags: abductors, nukies, wizard, traitor." + curve_width = 4 + property_weights = list("story_potential" = 10) + +/datum/dynamic_storyteller/suspicion + name = "Intrigue" + desc = "Chaos: low. Variation: high. Likely antags: traitor, bloodsucker. Rare: revs, blood cult." + curve_width = 4 + property_weights = list("trust" = -5) + +/datum/dynamic_storyteller/liteextended + name = "Calm" + desc = "Chaos: low. Variation: medium. Likely antags: bloodsuckers, traitors, sentient disease, revenant." + curve_centre = -5 + curve_width = 0.5 + flags = NO_ASSASSIN + weight = 2 + property_weights = list("extended" = 1, "chaos" = -1, "valid" = -1, "story_potential" = 1, "conversion" = -10) + +/datum/dynamic_storyteller/liteextended/get_injection_chance(dry_run = FALSE) + return ..()/2 + +/datum/dynamic_storyteller/extended + name = "Extended" + desc = "Chaos: none. Variation: none. Likely antags: none." + curve_centre = -20 + weight = 2 + curve_width = 0.5 + +/datum/dynamic_storyteller/extended/on_start() + GLOB.dynamic_forced_extended = TRUE diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 52b52eb288..03c1bb3bc8 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -180,7 +180,7 @@ limit-- while(!FoundDoor && limit) if (!FoundDoor) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!") + log_mapping("[src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!") return FoundDoor.cyclelinkedairlock = src cyclelinkedairlock = FoundDoor diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm index 36708fcf90..ca8e8cecd9 100644 --- a/code/game/mecha/equipment/tools/work_tools.dm +++ b/code/game/mecha/equipment/tools/work_tools.dm @@ -262,14 +262,14 @@ occupant_message("Deconstructing [W]...") if(do_after_cooldown(W)) chassis.spark_system.start() - W.ScrapeAway() + W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) playsound(W, 'sound/items/deconstruct.ogg', 50, 1) else if(isfloorturf(target)) var/turf/open/floor/F = target occupant_message("Deconstructing [F]...") if(do_after_cooldown(target)) chassis.spark_system.start() - F.ScrapeAway() + F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) playsound(F, 'sound/items/deconstruct.ogg', 50, 1) else if (istype(target, /obj/machinery/door/airlock)) occupant_message("Deconstructing [target]...") @@ -282,7 +282,7 @@ var/turf/open/space/S = target occupant_message("Building Floor...") if(do_after_cooldown(S)) - S.PlaceOnTop(/turf/open/floor/plating) + S.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) playsound(S, 'sound/items/deconstruct.ogg', 50, 1) chassis.spark_system.start() else if(isfloorturf(target)) diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index 10ee73915c..b6deaa1d06 100644 --- a/code/game/objects/effects/effect_system/effects_foam.dm +++ b/code/game/objects/effects/effect_system/effects_foam.dm @@ -123,7 +123,7 @@ if(metal) var/turf/T = get_turf(src) if(isspaceturf(T)) //Block up any exposed space - T.PlaceOnTop(/turf/open/floor/plating/foam) + T.PlaceOnTop(/turf/open/floor/plating/foam, flags = CHANGETURF_INHERIT_AIR) for(var/direction in GLOB.cardinals) var/turf/cardinal_turf = get_step(T, direction) if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf! diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index c5d198c05f..a410827347 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -578,21 +578,22 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) itempush = 0 //too light to push anything return A.hitby(src, 0, itempush) -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, messy_throw = TRUE) thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback) //replace their callback with our own + callback = CALLBACK(src, .proc/after_throw, callback, (spin && messy_throw)) //replace their callback with our own . = ..(target, range, speed, thrower, spin, diagonals_first, callback) -/obj/item/proc/after_throw(datum/callback/callback) +/obj/item/proc/after_throw(datum/callback/callback, messy_throw) if (callback) //call the original callback . = callback.Invoke() throw_speed = initial(throw_speed) //explosions change this. item_flags &= ~IN_INVENTORY - var/matrix/M = matrix(transform) - M.Turn(rand(-170, 170)) - transform = M - pixel_x = rand(-8, 8) - pixel_y = rand(-8, 8) + if(messy_throw) + var/matrix/M = matrix(transform) + M.Turn(rand(-170, 170)) + transform = M + pixel_x = rand(-8, 8) + pixel_y = rand(-8, 8) /obj/item/proc/remove_item_from_storage(atom/newLoc) //please use this if you're going to snowflake an item out of a obj/item/storage if(!newLoc) diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index cf8d38d9c9..884f5c4400 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -863,3 +863,205 @@ CIGARETTE PACKETS ARE IN FANCY.DM if(reagents && reagents.total_volume) hand_reagents() + +/////////////// +/////BONGS///// +/////////////// + +/obj/item/bong + name = "bong" + desc = "A water bong used for smoking dried plants." + icon = 'icons/obj/bongs.dmi' + icon_state = null + item_state = null + w_class = WEIGHT_CLASS_NORMAL + light_color = "#FFCC66" + var/icon_off = "bong" + var/icon_on = "bong_lit" + var/chem_volume = 100 + var/last_used_time //for cooldown + var/firecharges = 0 //used for counting how many hits can be taken before the flame goes out + var/list/list_reagents = list() //For the base reagents bongs could get + + +/obj/item/bong/Initialize() + . = ..() + create_reagents(chem_volume, NO_REACT) // so it doesn't react until you light it + reagents.add_reagent_list(list_reagents) + icon_state = icon_off + +/obj/item/bong/attackby(obj/item/O, mob/user, params) + . = ..() + //If we're using a dried plant.. + if(istype(O,/obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/DP = O + if (DP.dry) + //Nothing if our bong is full + if (reagents.holder_full()) + user.show_message("The bowl is full!", MSG_VISUAL) + return + + //Transfer reagents and remove the plant + user.show_message("You stuff the [DP] into the [src]'s bowl.", MSG_VISUAL) + DP.reagents.trans_to(src, 100) + qdel(DP) + return + else + user.show_message("[DP] must be dried first!", MSG_VISUAL) + return + + if (O.get_temperature() <= 500) + return + if (reagents && reagents.total_volume) //if there's stuff in the bong + var/lighting_text = O.ignition_effect(src, user) + if(lighting_text) + //Logic regarding igniting it on + if (firecharges == 0) + user.show_message("You light the [src] with the [O]!", MSG_VISUAL) + bongturnon() + else + user.show_message("You rekindle [src]'s flame with the [O]!", MSG_VISUAL) + + firecharges = 1 + return + else + user.show_message("There's nothing to light up in the bowl.", MSG_VISUAL) + return + +/obj/item/bong/CtrlShiftClick(mob/user) //empty reagents on alt click + ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if (reagents && reagents.total_volume) + user.show_message("You empty the [src].", MSG_VISUAL) + reagents.clear_reagents() + if(firecharges) + firecharges = 0 + bongturnoff() + else + user.show_message("The [src] is already empty.", MSG_VISUAL) + +/obj/item/bong/AltClick(mob/user) + ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(firecharges) + firecharges = 0 + bongturnoff() + user.show_message("You quench the flame.", MSG_VISUAL) + return TRUE + +/obj/item/bong/examine(mob/user) + . = ..() + if(!reagents.total_volume) + . += "The bowl is empty." + else if (reagents.total_volume > 80) + . += "The bowl is filled to the brim." + else if (reagents.total_volume > 40) + . += "The bowl has plenty weed in it." + else + . += "The bowl has some weed in it." + + . += "Ctrl+Shift-click to empty." + . += "Alt-click to extinguish." + +/obj/item/bong/ignition_effect(atom/A, mob/user) + if(firecharges) + . = "[user] lights [A] off of the [src]." + else + . = "" + +/obj/item/bong/attack(mob/living/carbon/M, mob/living/carbon/user, obj/target) + //if it's lit up, some stuff in the bowl and the user is a target, and we're not on cooldown + + if (M != user) + return ..() + + if(user.is_mouth_covered(head_only = 1)) + to_chat(user, "Remove your headgear first.") + return ..() + + if(user.is_mouth_covered(mask_only = 1)) + to_chat(user, "Remove your mask first.") + return ..() + + if (!reagents.total_volume) + to_chat(user, "There's nothing in the bowl.") + return ..() + + if (!firecharges) + to_chat(user, "You have to light it up first.") + return ..() + + if (last_used_time + 30 >= world.time) + return ..() + var/hit_strength + var/noise + var/hittext = "" + //if the intent is help then you take a small hit, else a big one + if (user.a_intent == INTENT_HARM) + hit_strength = 2 + noise = 100 + hittext = "big hit" + else + hit_strength = 1 + noise = 70 + hittext = "hit" + //bubbling sound + playsound(user.loc,'sound/effects/bonghit.ogg', noise, 1) + + last_used_time = world.time + + //message + user.visible_message("[user] begins to take a [hittext] from the [src]!", \ + "You begin to take a [hittext] from [src].") + + //we take a hit here, after an uninterrupted delay + if(!do_after(user, 25, target = user)) + return + if (!(reagents && reagents.total_volume)) + return + + var/fraction = 12 * hit_strength + + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, hit_strength, 18, user.loc) + s.start() + + reagents.reaction(user, INGEST, fraction) + if(!reagents.trans_to(user, fraction)) + reagents.remove_any(fraction) + + if (hit_strength == 2 && prob(15)) + user.emote("cough") + user.adjustOxyLoss(15) + + user.visible_message("[user] takes a [hittext] from the [src]!", \ + "You take a [hittext] from [src].") + + firecharges = firecharges - 1 + if (!firecharges) + bongturnoff() + if (!reagents.total_volume) + firecharges = 0 + bongturnoff() + + + +/obj/item/bong/proc/bongturnon() + icon_state = icon_on + set_light(3, 0.8) + +/obj/item/bong/proc/bongturnoff() + icon_state = icon_off + set_light(0, 0.0) + + + +/obj/item/bong/coconut + name = "coconut bong" + icon_off = "coconut_bong" + icon_on = "coconut_bong_lit" + desc = "A water bong used for smoking dried plants. This one's made out of a coconut and some bamboo." \ No newline at end of file diff --git a/code/game/objects/items/devices/forcefieldprojector.dm b/code/game/objects/items/devices/forcefieldprojector.dm index 0c73d829ff..bf9c5daccd 100644 --- a/code/game/objects/items/devices/forcefieldprojector.dm +++ b/code/game/objects/items/devices/forcefieldprojector.dm @@ -2,7 +2,7 @@ name = "forcefield projector" desc = "An experimental device that can create several forcefields at a distance." icon = 'icons/obj/device.dmi' - icon_state = "signmaker_engi" + icon_state = "signmaker_forcefield" slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL item_flags = NOBLUDGEON diff --git a/code/game/objects/items/holosign_creator.dm b/code/game/objects/items/holosign_creator.dm index 78881d901e..eb58089293 100644 --- a/code/game/objects/items/holosign_creator.dm +++ b/code/game/objects/items/holosign_creator.dm @@ -79,7 +79,7 @@ /obj/item/holosign_creator/atmos name = "ATMOS holofan projector" desc = "A holographic projector that creates holographic barriers that prevent changes in atmosphere conditions." - icon_state = "signmaker_engi" + icon_state = "signmaker_atmos" holosign_type = /obj/structure/holosign/barrier/atmos creation_time = 0 max_signs = 3 diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 16262d61c3..4549dc73b6 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -374,7 +374,7 @@ It appears to have a wooden grip and a shaved down guard." icon_state = "cxsword_hilt_traitor" force_on = 30 - armour_penetration = 50 + armour_penetration = 35 embedding = list("embedded_pain_multiplier" = 10, "embed_chance" = 75, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 10) block_chance = 50 hitsound_on = 'sound/weapons/blade1.ogg' diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index d8abff70a6..4242fb6c4b 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -216,10 +216,11 @@ return else if(last_hit < world.time) + if(target.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + playsound(target, 'sound/weapons/genhit.ogg', 50, 1) + return if(ishuman(target)) var/mob/living/carbon/human/H = target - if (H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) - return if(check_martial_counter(H, user)) return playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1) diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 77bc7ed810..c09f5b1b65 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -11,11 +11,9 @@ var/charge_cost = 30 /obj/item/borg/stun/attack(mob/living/M, mob/living/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, 1) - return FALSE + if(M.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, 1) + return FALSE if(iscyborg(user)) var/mob/living/silicon/robot/R = user if(!R.cell.use(charge_cost)) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index e8bd50f19b..d2ddd493b2 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -192,9 +192,14 @@ var/obj/O if(R.max_res_amount > 1) //Is it a stack? O = new R.result_type(usr.drop_location(), R.res_amount * multiplier) - else + else if(ispath(R.result_type, /turf)) + var/turf/T = usr.drop_location() + if(!isturf(T)) + return + T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR) O = new R.result_type(usr.drop_location()) - O.setDir(usr.dir) + if(O) + O.setDir(usr.dir) use(R.req_amount * multiplier) //START: oh fuck i'm so sorry diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 3a4579d3f8..6c250029a6 100755 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -61,6 +61,7 @@ /obj/item/radio, /obj/item/clothing/gloves, /obj/item/holosign_creator, + /obj/item/forcefield_projector, /obj/item/assembly/signaler )) STR.can_hold = can_hold @@ -458,8 +459,7 @@ /obj/item/extinguisher/mini, /obj/item/radio, /obj/item/clothing/gloves, - /obj/item/holosign_creator/atmos, - /obj/item/holosign_creator/engineering, + /obj/item/holosign_creator, /obj/item/forcefield_projector, /obj/item/assembly/signaler, /obj/item/lightreplacer, @@ -571,6 +571,7 @@ /obj/item/reagent_containers/spray, /obj/item/soap, /obj/item/holosign_creator, + /obj/item/forcefield_projector, /obj/item/key/janitor, /obj/item/clothing/gloves, /obj/item/melee/flyswatter, @@ -582,7 +583,7 @@ /obj/item/storage/belt/bandolier name = "bandolier" - desc = "A bandolier for holding shotgun ammunition." + desc = "A bandolier for holding ammunition." icon_state = "bandolier" item_state = "bandolier" @@ -592,7 +593,7 @@ STR.max_items = 18 STR.display_numerical_stacking = TRUE STR.can_hold = typecacheof(list( - /obj/item/ammo_casing/shotgun + /obj/item/ammo_casing )) /obj/item/storage/belt/bandolier/durathread diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index e214db3c71..4b2b3e2203 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1259,3 +1259,12 @@ var/obj/item/stack/sheet/cardboard/I = new(user.drop_location()) qdel(src) user.put_in_hands(I) + +/obj/item/storage/box/marshmallow + name = "box of marshmallows" + desc = "A box of marshmallows." + illustration = "marshmallow" + +/obj/item/storage/box/marshmallow/PopulateContents() + for (var/i in 1 to 5) + new /obj/item/reagent_containers/food/snacks/marshmallow(src) \ No newline at end of file diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 484b9862ff..6972587263 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -36,7 +36,7 @@ . = ..() if(preload_cell_type) if(!ispath(preload_cell_type,/obj/item/stock_parts/cell)) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") + log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].") else cell = new preload_cell_type(src) update_icon() @@ -168,11 +168,9 @@ /obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user) - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(L, 'sound/weapons/genhit.ogg', 50, 1) - return FALSE + if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(L, 'sound/weapons/genhit.ogg', 50, 1) + return FALSE var/stunpwr = stunforce var/obj/item/stock_parts/cell/our_cell = get_cell() if(!our_cell) diff --git a/code/game/objects/items/telescopic_iv.dm b/code/game/objects/items/telescopic_iv.dm index e1ec33569f..9b0b714ea5 100644 --- a/code/game/objects/items/telescopic_iv.dm +++ b/code/game/objects/items/telescopic_iv.dm @@ -9,7 +9,7 @@ /obj/item/tele_iv/afterattack(atom/target, mob/user, proximity) . = ..() - if(proximity|| isopenturf(target)) + if(proximity && isopenturf(target) && user.CanReach(target)) deploy_iv(user, target) /obj/item/tele_iv/proc/deploy_iv(mob/user, atom/location) diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm index dd7d46d0d7..d7891feaa0 100644 --- a/code/game/objects/items/twohanded.dm +++ b/code/game/objects/items/twohanded.dm @@ -872,7 +872,7 @@ user.visible_message("[user] blasts \the [target] with \the [src]!") playsound(target, 'sound/magic/disintegrate.ogg', 100, 1) W.break_wall() - W.ScrapeAway() + W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return //HF blade diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index f5dfa690db..14ac2f037e 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -80,13 +80,12 @@ SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) anchored = anchorvalue -/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) - ..() +/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, messy_throw) + . = ..() if(obj_flags & FROZEN) visible_message("[src] shatters into a million pieces!") qdel(src) - /obj/assume_air(datum/gas_mixture/giver) if(loc) return loc.assume_air(giver) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm index 8da50d488f..579b1444a5 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -12,18 +12,12 @@ new /obj/item/clothing/head/hardhat/white(src) new /obj/item/clothing/head/hardhat/weldhat/white(src) new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/shoes/sneakers/brown(src) new /obj/item/tank/jetpack/suit(src) new /obj/item/cartridge/ce(src) new /obj/item/radio/headset/heads/ce(src) - new /obj/item/storage/toolbox/mechanical(src) - new /obj/item/clothing/suit/hazardvest(src) new /obj/item/megaphone/command(src) new /obj/item/areaeditor/blueprints(src) - new /obj/item/airlock_painter(src) new /obj/item/holosign_creator/engineering(src) - new /obj/item/clothing/mask/gas(src) - new /obj/item/multitool(src) new /obj/item/assembly/flash/handheld(src) new /obj/item/clothing/glasses/meson/engine(src) new /obj/item/door_remote/chief_engineer(src) @@ -67,7 +61,7 @@ for(var/i in 1 to 3) new /obj/item/clothing/head/welding(src) for(var/i in 1 to 3) - new /obj/item/weldingtool(src) + new /obj/item/weldingtool/largetank(src) /obj/structure/closet/secure_closet/engineering_personal name = "engineer's locker" @@ -84,7 +78,6 @@ new /obj/item/clothing/glasses/meson/engine(src) new /obj/item/storage/box/emptysandbags(src) - /obj/structure/closet/secure_closet/atmospherics name = "\proper atmospheric technician's locker" req_access = list(ACCESS_ATMOSPHERICS) @@ -103,3 +96,33 @@ new /obj/item/clothing/head/hardhat/atmos(src) new /obj/item/clothing/glasses/meson/engine/tray(src) new /obj/item/extinguisher/advanced(src) + +/* + * Empty lockers + * Some of the lockers are filled with junk, and sometimes its nice to just fill it with your own set-up for your own map gimmicks. + */ + +/obj/structure/closet/secure_closet/engineering_chief/empty + +/obj/structure/closet/secure_closet/engineering_chief/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_electrical/empty + +/obj/structure/closet/secure_closet/engineering_electrical/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_welding/empty + +/obj/structure/closet/secure_closet/engineering_welding/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_personal/empty + +/obj/structure/closet/secure_closet/engineering_personal/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/atmospherics/empty + +/obj/structure/closet/secure_closet/atmospherics/empty/PopulateContents() + return diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index 05e62c196f..7f0c3c6eb8 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -30,11 +30,11 @@ return var/list/undergarment_choices = list("Underwear", "Underwear Color", "Undershirt", "Undershirt Color", "Socks", "Socks Color") - if(!UNDIE_COLORABLE(GLOB.underwear_list[H.underwear])) + if(!(GLOB.underwear_list[H.underwear]?.has_color)) undergarment_choices -= "Underwear Color" - if(!UNDIE_COLORABLE(GLOB.undershirt_list[H.undershirt])) + if(!(GLOB.undershirt_list[H.undershirt]?.has_color)) undergarment_choices -= "Undershirt Color" - if(!UNDIE_COLORABLE(GLOB.socks_list[H.socks])) + if(!(GLOB.socks_list[H.socks]?.has_color)) undergarment_choices -= "Socks Color" var/choice = input(H, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in undergarment_choices diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm index 4656a9348e..705ca9a884 100644 --- a/code/game/objects/structures/ghost_role_spawners.dm +++ b/code/game/objects/structures/ghost_role_spawners.dm @@ -605,7 +605,7 @@ rank = "Gunner" /obj/effect/mob_spawn/human/ghostcafe - name = "ghost cafe sleeper" + name = "Ghost Cafe Sleeper" uses = -1 icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper" @@ -639,6 +639,7 @@ uniform = /obj/item/clothing/under/color/random shoes = /obj/item/clothing/shoes/sneakers/black id = /obj/item/card/id + r_hand = /obj/item/storage/box/syndie_kit/chameleon/ghostcafe /datum/outfit/ghostcafe/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE, client/preference_source) @@ -655,3 +656,17 @@ else uniform = /obj/item/clothing/under/skirt/color/random +/obj/item/storage/box/syndie_kit/chameleon/ghostcafe + name = "ghost cafe costuming kit" + desc = "Look just the way you did in life - or better!" + +/obj/item/storage/box/syndie_kit/chameleon/ghostcafe/PopulateContents() // Doesn't contain a PDA, for isolation reasons. + new /obj/item/clothing/under/chameleon(src) + new /obj/item/clothing/suit/chameleon(src) + new /obj/item/clothing/gloves/chameleon(src) + new /obj/item/clothing/shoes/chameleon(src) + new /obj/item/clothing/glasses/chameleon(src) + new /obj/item/clothing/head/chameleon(src) + new /obj/item/clothing/mask/chameleon(src) + new /obj/item/storage/backpack/chameleon(src) + new /obj/item/clothing/neck/cloak/chameleon(src) diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index b946a06432..a7eaea0d16 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -60,7 +60,7 @@ to_chat(user, "You build a floor.") var/turf/T = src.loc if(isspaceturf(T)) - T.PlaceOnTop(/turf/open/floor/plating) + T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) qdel(src) return TRUE return FALSE diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index bd657fe1e8..f2c4e628e0 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -102,13 +102,13 @@ /obj/structure/stairs/proc/force_open_above() var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP) if(T && !istype(T)) - T.ChangeTurf(/turf/open/openspace) + T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR) /obj/structure/stairs/proc/on_multiz_new(turf/source, dir) if(dir == UP) var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP) if(T && !istype(T)) - T.ChangeTurf(/turf/open/openspace) + T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR) /obj/structure/stairs/intercept_zImpact(atom/movable/AM, levels = 1) . = ..() diff --git a/code/game/turfs/baseturf_skipover.dm b/code/game/turfs/baseturf_skipover.dm index 644714f8ed..4df8c86e62 100644 --- a/code/game/turfs/baseturf_skipover.dm +++ b/code/game/turfs/baseturf_skipover.dm @@ -10,4 +10,9 @@ /turf/baseturf_skipover/shuttle name = "Shuttle baseturf skipover" - desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through." \ No newline at end of file + desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through." + +/turf/baseturf_bottom + name = "Z-level baseturf placeholder" + desc = "Marker for z-level baseturf, usually resolves to space." + baseturfs = /turf/baseturf_bottom diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index 6a055bbd35..bc4cd8df33 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -1,6 +1,7 @@ // This is a list of turf types we dont want to assign to baseturfs unless through initialization or explicitly GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( /turf/open/space, + /turf/baseturf_bottom ))) /turf/proc/empty(turf_type=/turf/open/space, baseturf_type, list/ignore_typecache, flags) @@ -56,12 +57,20 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( // Creates a new turf // new_baseturfs can be either a single type or list of types, formated the same as baseturfs. see turf.dm /turf/proc/ChangeTurf(path, list/new_baseturfs, flags) - if(!path) - return - if(path == /turf/open/space/basic) - // basic doesn't initialize and this will cause issues - // no warning though because this can happen naturaly as a result of it being built on top of - path = /turf/open/space + switch(path) + if(null) + return + if(/turf/baseturf_bottom) + path = SSmapping.level_trait(z, ZTRAIT_BASETURF) || /turf/open/space + if (!ispath(path)) + path = text2path(path) + if (!ispath(path)) + warning("Z-level [z] has invalid baseturf '[SSmapping.level_trait(z, ZTRAIT_BASETURF)]'") + path = /turf/open/space + if(/turf/open/space/basic) + // basic doesn't initialize and this will cause issues + // no warning though because this can happen naturaly as a result of it being built on top of + path = /turf/open/space if(!GLOB.use_preloader && path == type && !(flags & CHANGETURF_FORCEOP)) // Don't no-op if the map loader requires it to be reconstructed return src if(flags & CHANGETURF_SKIP) @@ -128,16 +137,15 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( /turf/open/ChangeTurf(path, list/new_baseturfs, flags) if ((flags & CHANGETURF_INHERIT_AIR) && ispath(path, /turf/open)) SSair.remove_from_active(src) - var/stashed_air = air - air = null // so that it doesn't get deleted + var/datum/gas_mixture/stashed_air = new() + stashed_air.copy_from(air) . = ..() - if (!. || . == src) // changeturf failed or didn't do anything - air = stashed_air + if (!.) // changeturf failed or didn't do anything + QDEL_NULL(stashed_air) return var/turf/open/newTurf = . - if (!istype(newTurf.air, /datum/gas_mixture/immutable/space)) - QDEL_NULL(newTurf.air) - newTurf.air = stashed_air + newTurf.air.copy_from(stashed_air) + QDEL_NULL(stashed_air) SSair.add_to_active(newTurf) else if(ispath(path,/turf/closed)) @@ -215,7 +223,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( newT.assemble_baseturfs(initial(fake_turf_type.baseturfs)) // The baseturfs list is created like roundstart if(!length(newT.baseturfs)) newT.baseturfs = list(baseturfs) - newT.baseturfs -= newT.baseturfs & GLOB.blacklisted_automated_baseturfs + newT.baseturfs -= GLOB.blacklisted_automated_baseturfs newT.baseturfs.Insert(1, old_baseturfs) // The old baseturfs are put underneath return newT if(!length(baseturfs)) @@ -315,5 +323,5 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( SSair.add_to_active(src) /turf/proc/ReplaceWithLattice() - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) new /obj/structure/lattice(locate(x, y, z)) diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm index 6297cadc63..9e593cafce 100644 --- a/code/game/turfs/closed.dm +++ b/code/game/turfs/closed.dm @@ -23,7 +23,7 @@ icon = 'icons/turf/walls.dmi' explosion_block = 50 -/turf/closed/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE) +/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE) return /turf/closed/indestructible/acid_act(acidpwr, acid_volume, acid_id) diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 4bdb13effc..577c45a7f0 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -58,7 +58,7 @@ /turf/open/indestructible/singularity_act() return -/turf/open/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE) +/turf/open/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE) return /turf/open/indestructible/sound diff --git a/code/game/turfs/openspace/openspace.dm b/code/game/turfs/openspace/openspace.dm index e68aabcd08..66507e7053 100644 --- a/code/game/turfs/openspace/openspace.dm +++ b/code/game/turfs/openspace/openspace.dm @@ -106,7 +106,7 @@ qdel(L) playsound(src, 'sound/weapons/genhit.ogg', 50, 1) to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else to_chat(user, "You need one floor tile to build a floor!") else diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm index 0a7b507488..dab0c1c376 100644 --- a/code/game/turfs/simulated/chasm.dm +++ b/code/game/turfs/simulated/chasm.dm @@ -38,7 +38,7 @@ switch(passed_mode) if(RCD_FLOORWALL) to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE return FALSE @@ -70,7 +70,7 @@ playsound(src, 'sound/weapons/genhit.ogg', 50, 1) to_chat(user, "You build a floor.") // Create a floor, which has this chasm underneath it - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else to_chat(user, "You need one floor tile to build a floor!") else diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index d14a4c7385..3f46994da9 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -62,29 +62,29 @@ if(severity != 1 && shielded && target != src) return if(target == src) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return if(target != null) severity = 3 switch(severity) if(1) - ScrapeAway(2) + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) if(2) switch(pick(1,2;75,3)) if(1) if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor)) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) ReplaceWithLattice() else - ScrapeAway(2) + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) if(prob(33)) new /obj/item/stack/sheet/metal(src) if(2) - ScrapeAway(2) + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) if(3) if(prob(80)) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) else break_tile() hotspot_expose(1000,CELL_VOLUME) @@ -135,7 +135,7 @@ burnt = 1 /turf/open/floor/proc/make_plating() - return ScrapeAway() + return ScrapeAway(flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/ChangeTurf(path, new_baseturf, flags) if(!isfloorturf(src)) @@ -213,15 +213,15 @@ /turf/open/floor/narsie_act(force, ignore_mobs, probability = 20) . = ..() if(.) - ChangeTurf(/turf/open/floor/engine/cult) + ChangeTurf(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/ratvar_act(force, ignore_mobs) . = ..() if(.) - ChangeTurf(/turf/open/floor/clockwork) + ChangeTurf(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/acid_melt() - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) switch(the_rcd.mode) @@ -267,7 +267,7 @@ A.autoclose = TRUE return TRUE if(RCD_DECONSTRUCT) - if(ScrapeAway() == src) + if(ScrapeAway(flags = CHANGETURF_INHERIT_AIR) == src) return FALSE to_chat(user, "You deconstruct [src].") return TRUE diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm index ae67edf073..8e0eace9cd 100644 --- a/code/game/turfs/simulated/floor/plating.dm +++ b/code/game/turfs/simulated/floor/plating.dm @@ -11,7 +11,7 @@ name = "plating" icon_state = "plating" intact = FALSE - baseturfs = /turf/open/space + baseturfs = /turf/baseturf_bottom footstep = FOOTSTEP_PLATING barefootstep = FOOTSTEP_HARD_BAREFOOT clawfootstep = FOOTSTEP_HARD_CLAW @@ -61,7 +61,7 @@ to_chat(user, "You begin reinforcing the floor...") if(do_after(user, 30, target = src)) if (R.get_amount() >= 2 && !istype(src, /turf/open/floor/engine)) - PlaceOnTop(/turf/open/floor/engine) + PlaceOnTop(/turf/open/floor/engine, flags = CHANGETURF_INHERIT_AIR) playsound(src, 'sound/items/deconstruct.ogg', 80, 1) R.use(2) to_chat(user, "You reinforce the floor.") @@ -76,7 +76,7 @@ var/obj/item/stack/tile/W = C if(!W.use(1)) return - var/turf/open/floor/T = PlaceOnTop(W.turf_type) + var/turf/open/floor/T = PlaceOnTop(W.turf_type, flags = CHANGETURF_INHERIT_AIR) if(istype(W, /obj/item/stack/tile/light)) //TODO: get rid of this ugly check somehow var/obj/item/stack/tile/light/L = W var/turf/open/floor/light/F = T @@ -117,7 +117,7 @@ qdel(L) to_chat(user, "You reinforce the foamed plating with tiling.") playsound(src, 'sound/weapons/Genhit.ogg', 50, TRUE) - ChangeTurf(/turf/open/floor/plating) + ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else playsound(src, 'sound/weapons/tap.ogg', 100, TRUE) //The attack sound is muffled by the foam itself user.changeNext_move(CLICK_CD_MELEE) @@ -125,7 +125,7 @@ if(prob(I.force * 20 - 25)) user.visible_message("[user] smashes through [src]!", \ "You smash through [src] with [I]!") - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) else to_chat(user, "You hit [src], to no effect!") @@ -136,13 +136,13 @@ /turf/open/floor/plating/foam/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode) if(passed_mode == RCD_FLOORWALL) to_chat(user, "You build a floor.") - ChangeTurf(/turf/open/floor/plating) + ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE return FALSE /turf/open/floor/plating/foam/ex_act() ..() - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type) return diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/simulated/floor/plating/asteroid.dm index 9f2da57312..ecc8ee5e8d 100644 --- a/code/game/turfs/simulated/floor/plating/asteroid.dm +++ b/code/game/turfs/simulated/floor/plating/asteroid.dm @@ -76,11 +76,6 @@ for(var/obj/item/stack/ore/O in src) SEND_SIGNAL(W, COMSIG_PARENT_ATTACKBY, O) -/turf/open/floor/plating/asteroid/singularity_act() - if(is_planet_level(z)) - return ..() - ScrapeAway() - /turf/open/floor/plating/asteroid/ex_act(severity, target) . = SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) contents_explosion(severity, target) @@ -132,6 +127,7 @@ /turf/open/floor/plating/asteroid/airless initial_gas_mix = AIRLESS_ATMOS + baseturfs = /turf/open/floor/plating/asteroid/airless turf_type = /turf/open/floor/plating/asteroid/airless diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/simulated/floor/reinf_floor.dm index b04f89f8be..28ffbbd1ff 100644 --- a/code/game/turfs/simulated/floor/reinf_floor.dm +++ b/code/game/turfs/simulated/floor/reinf_floor.dm @@ -43,7 +43,7 @@ return TRUE if(floor_tile) new floor_tile(src, 2) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return TRUE /turf/open/floor/engine/acid_act(acidpwr, acid_volume) @@ -56,23 +56,23 @@ if(severity != 1 && shielded && target != src) return if(target == src) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return switch(severity) if(1) if(prob(80)) if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor)) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) ReplaceWithLattice() else - ScrapeAway(2) + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) else if(prob(50)) - ScrapeAway(2) + ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR) else - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) if(2) if(prob(50)) - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) /turf/open/floor/engine/singularity_pull(S, current_size) ..() diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm index 362b410cbb..2f8ae0cb93 100644 --- a/code/game/turfs/simulated/lava.dm +++ b/code/game/turfs/simulated/lava.dm @@ -54,7 +54,7 @@ switch(passed_mode) if(RCD_FLOORWALL) to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE return FALSE diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index f38f13e657..5278374abd 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -125,7 +125,7 @@ qdel(L) playsound(src, 'sound/weapons/genhit.ogg', 50, 1) to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else to_chat(user, "You need one floor tile to build a floor!") else @@ -212,7 +212,7 @@ switch(passed_mode) if(RCD_FLOORWALL) to_chat(user, "You build a floor.") - PlaceOnTop(/turf/open/floor/plating) + PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) return TRUE return FALSE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index d618e457b0..d280ba408b 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -9,7 +9,7 @@ // A list will be created in initialization that figures out the baseturf's baseturf etc. // In the case of a list it is sorted from bottom layer to top. // This shouldn't be modified directly, use the helper procs. - var/list/baseturfs = /turf/open/space + var/list/baseturfs = /turf/baseturf_bottom var/temperature = T20C var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed @@ -391,7 +391,7 @@ continue if(O.invisibility == INVISIBILITY_MAXIMUM) O.singularity_act() - ScrapeAway() + ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return(2) /turf/proc/can_have_cabling() @@ -564,4 +564,4 @@ //Whatever happens after high temperature fire dies out or thermite reaction works. //Should return new turf /turf/proc/Melt() - return ScrapeAway() + return ScrapeAway(flags = CHANGETURF_INHERIT_AIR) diff --git a/code/game/world.dm b/code/game/world.dm index 25b8c4d9f3..4043f15f6f 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -108,6 +108,7 @@ GLOBAL_VAR(restart_counter) GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log" GLOB.sql_error_log = "[GLOB.log_directory]/sql.log" GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log" + GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log" GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log" GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log" diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 895f8c9f88..1cecbbd0e5 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -693,7 +693,7 @@ var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting) CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting) if (!prev_dynamic_voting) - to_chat(world, "Vote is now between extended and dynamic chaos.") + to_chat(world, "Vote is now a ranked choice of dynamic storytellers.") else to_chat(world, "Vote is now between extended and secret.") log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.") diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 2b8d365e15..acd320856a 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -35,6 +35,9 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list( /client/proc/cmd_admin_grantfullaccess, /client/proc/cmd_admin_areatest_all, /client/proc/cmd_admin_areatest_station, + #ifdef TESTING + /client/proc/see_dirty_varedits, + #endif /client/proc/cmd_admin_test_atmos_controllers, /client/proc/cmd_admin_rejuvenate, /datum/admins/proc/show_traitor_panel, @@ -84,8 +87,24 @@ GLOBAL_PROTECT(admin_verbs_debug_mapping) SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") +#ifdef TESTING +GLOBAL_LIST_EMPTY(dirty_vars) +/client/proc/see_dirty_varedits() + set category = "Mapping" + set name = "Dirty Varedits" + + var/list/dat = list() + dat += "

    Abandon all hope ye who enter here



    " + for(var/thing in GLOB.dirty_vars) + dat += "[thing]
    " + CHECK_TICK + var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750) + popup.set_content(dat.Join()) + popup.open() +#endif + /client/proc/sec_camera_report() set category = "Mapping" set name = "Camera Report" diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm index 433f52306b..fce45810d1 100644 --- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm +++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm @@ -484,11 +484,9 @@ user.do_attack_animation(L) - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) - playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE) - return FALSE + if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + playsound(L, 'sound/weapons/genhit.ogg', 50, TRUE) + return FALSE switch (mode) if(BATON_STUN) diff --git a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm index f73912d0f5..3493622945 100644 --- a/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm +++ b/code/modules/antagonists/bloodsucker/objects/bloodsucker_crypt.dm @@ -275,7 +275,7 @@ // All done! if(convert_progress <= 0) // FAIL: Can't be Vassal - if(!SSticker.mode.can_make_vassal(target, user, display_warning=FALSE) && HAS_TRAIT(target, TRAIT_MINDSHIELD)) // If I'm an unconvertable Antag ONLY + if(!SSticker.mode.can_make_vassal(target, user, display_warning=FALSE) || HAS_TRAIT(target, TRAIT_MINDSHIELD)) // If I'm an unconvertable Antag ONLY to_chat(user, "[target] doesn't respond to your persuasion. It doesn't appear they can be converted to follow you, they either have a mindshield or their external loyalties are too difficult for you to break.\[ALT+click to release\]") convert_progress ++ // Pop it back up some. Avoids wasting Blood on a lost cause. // SUCCESS: All done! diff --git a/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm b/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm index 0365ae63af..501bcdc1c4 100644 --- a/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm +++ b/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm @@ -139,7 +139,7 @@ var/new_thing_type = fabrication_values["new_obj_type"] if(isturf(target)) //if our target is a turf, we're just going to ChangeTurf it and assume it'll work out. var/turf/T = target - T.ChangeTurf(new_thing_type) + T.ChangeTurf(new_thing_type, flags = CHANGETURF_INHERIT_AIR) else if(new_thing_type) if(fabrication_values["dir_in_new"]) diff --git a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm index 32b1b61dd1..d823f19d4a 100644 --- a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm +++ b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm @@ -59,7 +59,7 @@ if(anchored) T.PlaceOnTop(/turf/closed/wall/clockwork) else - T.PlaceOnTop(/turf/open/floor/clockwork) + T.PlaceOnTop(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR) new /obj/structure/falsewall/brass(T) qdel(src) else diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index f38f379abe..6f340b9271 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -239,9 +239,9 @@ var/turf/T = safepick(validturfs) if(T) if(istype(T, /turf/open/floor/plating)) - T.PlaceOnTop(/turf/open/floor/engine/cult) + T.PlaceOnTop(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR) else - T.ChangeTurf(/turf/open/floor/engine/cult) + T.ChangeTurf(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR) else var/turf/open/floor/engine/cult/F = safepick(cultturfs) if(F) diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm index 0a223f8b10..b5ce5538e9 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm @@ -76,9 +76,10 @@ GLOBAL_VAR_INIT(war_declared, FALSE) CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY)) if(istype(SSticker.mode, /datum/game_mode/dynamic)) var/datum/game_mode/dynamic/mode = SSticker.mode - var/threat_spent = CONFIG_GET(number/dynamic_warops_cost) - mode.spend_threat(threat_spent) - mode.log_threat("Nuke ops spent [threat_spent] on war ops.") + if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED)) + var/threat_spent = CONFIG_GET(number/dynamic_warops_cost) + mode.spend_threat(threat_spent) + mode.log_threat("Nuke ops spent [threat_spent] on war ops.") SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1) qdel(src) @@ -101,12 +102,13 @@ GLOBAL_VAR_INIT(war_declared, FALSE) return FALSE if(istype(SSticker.mode, /datum/game_mode/dynamic)) var/datum/game_mode/dynamic/mode = SSticker.mode - if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement)) - to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.") - return FALSE - else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost)) - to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.") - return FALSE + if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED)) + if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement)) + to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.") + return FALSE + else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost)) + to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.") + return FALSE return TRUE /obj/item/nuclear_challenge/clownops diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index ac19df3631..40d1712bc3 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -80,6 +80,8 @@ if(istype(SSticker.mode,/datum/game_mode/dynamic)) mode = SSticker.mode is_dynamic = TRUE + if(mode.storyteller.flags & NO_ASSASSIN) + is_hijacker = FALSE if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit) is_hijacker = (prob(10) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement)) else @@ -180,7 +182,7 @@ destroy_objective.owner = owner destroy_objective.find_target() add_objective(destroy_objective) - else if(prob(30)) + else if(prob(30) || (mode.storyteller.flags & NO_ASSASSIN)) var/datum/objective/maroon/maroon_objective = new maroon_objective.owner = owner maroon_objective.find_target() diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 38178a4339..db8a8e90ad 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -57,10 +57,10 @@ if(item.parent) var/static/pipenetwarnings = 10 if(pipenetwarnings > 0) - warning("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z])") + log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z]).") pipenetwarnings -= 1 if(pipenetwarnings == 0) - warning("build_pipeline(): further messages about pipenets will be suppressed") + log_mapping("build_pipeline(): further messages about pipenets will be suppressed") members += item possible_expansions += item diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index d0c78d21b3..329c77ba14 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -56,7 +56,7 @@ pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y /obj/machinery/meter/process_atmos() - if(!target) + if(!(target?.flags_1 & INITIALIZED_1)) icon_state = "meterX" return 0 diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm index 4f7b176691..fe3d831d81 100644 --- a/code/modules/buildmode/submodes/basic.dm +++ b/code/modules/buildmode/submodes/basic.dm @@ -22,9 +22,9 @@ if(istype(object,/turf) && left_click && !alt_click && !ctrl_click) var/turf/T = object if(isspaceturf(object)) - T.PlaceOnTop(/turf/open/floor/plating) + T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else if(isplatingturf(object)) - T.PlaceOnTop(/turf/open/floor/plasteel) + T.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR) else if(isfloorturf(object)) T.PlaceOnTop(/turf/closed/wall) else if(iswallturf(object)) @@ -35,7 +35,7 @@ log_admin("Build Mode: [key_name(c)] deleted [object] at [AREACOORD(object)]") if(isturf(object)) var/turf/T = object - T.ScrapeAway() + T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) else if(isobj(object)) qdel(object) return diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm index aa680e1b1e..29133b78d2 100644 --- a/code/modules/cargo/packs/misc.dm +++ b/code/modules/cargo/packs/misc.dm @@ -13,17 +13,16 @@ //////////////////// Paperwork and Writing Supplies ////////////////////////// ////////////////////////////////////////////////////////////////////////////// -/* I did it Kevin /datum/supply_pack/misc/abandonedcrate - name = "Abandoned Crate" - desc = "Someone keeps finding these locked crates out in the boonies. How about you take a crack at it, we've had our fill. WARNING: EXPLOSIVE" + name = "Loot Box" + desc = "Try your luck with these highly secure loot boxes! Solve the lock, win great prizes! WARNING: EXPLOSIVE FAILURE." contraband = TRUE - cost = 12800 + cost = 15000 contains = list(/obj/structure/closet/crate/secure/loot) crate_name = "abandoned crate" crate_type = /obj/structure/closet/crate/large dangerous = TRUE -*/ + /datum/supply_pack/misc/artsupply name = "Art Supplies" desc = "Make some happy little accidents with six canvasses, two easels, two boxes of crayons, and a rainbow crayon!" diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 06511f6fa9..96a32d50e6 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -195,6 +195,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/auto_fit_viewport = TRUE var/uplink_spawn_loc = UPLINK_PDA + + var/sprint_spacebar = FALSE + var/sprint_toggle = FALSE var/list/exp = list() var/list/menuoptions @@ -722,13 +725,13 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "
    " dat += "

    Clothing & Equipment

    " dat += "Underwear:[underwear]" - if(UNDIE_COLORABLE(GLOB.underwear_list[underwear])) + if(GLOB.underwear_list[underwear]?.has_color) dat += "Underwear Color:     Change
    " dat += "Undershirt:[undershirt]" - if(UNDIE_COLORABLE(GLOB.undershirt_list[undershirt])) + if(GLOB.undershirt_list[undershirt]?.has_color) dat += "Undershirt Color:     Change
    " dat += "Socks:[socks]" - if(UNDIE_COLORABLE(GLOB.socks_list[socks])) + if(GLOB.socks_list[socks]?.has_color) dat += "Socks Color:     Change
    " dat += "Backpack:[backbag]" dat += "Jumpsuit:
    [jumpsuit_style]
    " @@ -889,6 +892,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "
    " dat += "Ambient Occlusion: [ambientocclusion ? "Enabled" : "Disabled"]
    " dat += "Fit Viewport: [auto_fit_viewport ? "Auto" : "Manual"]
    " + dat += "Sprint Key: [sprint_spacebar ? "Space" : "Shift"]
    " + dat += "Toggle Sprint: [sprint_toggle ? "Enabled" : "Disabled"]
    " if (CONFIG_GET(flag/maprotation) && CONFIG_GET(flag/tgstyle_maprotation)) var/p_map = preferred_map @@ -2239,6 +2244,12 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(auto_fit_viewport && parent) parent.fit_viewport() + if("sprint_key") + sprint_spacebar = !sprint_spacebar + + if("sprint_toggle") + sprint_toggle = !sprint_toggle + if("save") save_preferences() save_character() diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 5fdcbf0484..d78b9fe6fd 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -167,6 +167,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["parallax"] >> parallax S["ambientocclusion"] >> ambientocclusion S["auto_fit_viewport"] >> auto_fit_viewport + S["sprint_spacebar"] >> sprint_spacebar + S["sprint_toggle"] >> sprint_toggle S["menuoptions"] >> menuoptions S["enable_tips"] >> enable_tips S["tip_delay"] >> tip_delay @@ -204,6 +206,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car parallax = sanitize_integer(parallax, PARALLAX_INSANE, PARALLAX_DISABLE, null) ambientocclusion = sanitize_integer(ambientocclusion, 0, 1, initial(ambientocclusion)) auto_fit_viewport = sanitize_integer(auto_fit_viewport, 0, 1, initial(auto_fit_viewport)) + sprint_spacebar = sanitize_integer(sprint_spacebar, 0, 1, initial(sprint_spacebar)) + sprint_toggle = sanitize_integer(sprint_toggle, 0, 1, initial(sprint_toggle)) ghost_form = sanitize_inlist(ghost_form, GLOB.ghost_forms, initial(ghost_form)) ghost_orbit = sanitize_inlist(ghost_orbit, GLOB.ghost_orbits, initial(ghost_orbit)) ghost_accs = sanitize_inlist(ghost_accs, GLOB.ghost_accs_options, GHOST_ACCS_DEFAULT_OPTION) @@ -264,6 +268,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["parallax"], parallax) WRITE_FILE(S["ambientocclusion"], ambientocclusion) WRITE_FILE(S["auto_fit_viewport"], auto_fit_viewport) + WRITE_FILE(S["sprint_spacebar"], sprint_spacebar) + WRITE_FILE(S["sprint_toggle"], sprint_toggle) WRITE_FILE(S["menuoptions"], menuoptions) WRITE_FILE(S["enable_tips"], enable_tips) WRITE_FILE(S["tip_delay"], tip_delay) diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 1a178158d8..a80cf6fba3 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -54,6 +54,9 @@ H.blur_eyes(5) eyes.applyOrganDamage(5) +/obj/item/clothing/glasses/proc/ranged_attack(mob/living/carbon/human/user,atom/A, params) + return FALSE + /obj/item/clothing/glasses/meson name = "optical meson scanner" desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." diff --git a/code/modules/clothing/glasses/disablerglasses.dm b/code/modules/clothing/glasses/disablerglasses.dm new file mode 100644 index 0000000000..a46e4c8339 --- /dev/null +++ b/code/modules/clothing/glasses/disablerglasses.dm @@ -0,0 +1,15 @@ +/obj/item/clothing/glasses/hud/security/sunglasses/disablers + name = "true stunglasses" + desc = "Made for only the best of shitsec. Wear 'em like you're gonna robust all of those fuckers." + var/beamtype = /obj/item/projectile/beam/disabler //change for adminbus + +/obj/item/clothing/glasses/hud/security/sunglasses/disablers/ranged_attack(mob/living/carbon/human/user,atom/A, params) + user.changeNext_move(CLICK_CD_RANGE) + var/obj/item/projectile/beam/disabler/LE = new beamtype( loc ) + playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) + LE.firer = src + LE.def_zone = user.get_organ_target() + LE.preparePixelProjectile(A, src, params) + LE.fire() + return TRUE + //shamelessly copied \ No newline at end of file diff --git a/code/modules/crafting/recipes/recipes_misc.dm b/code/modules/crafting/recipes/recipes_misc.dm index 70dd4d1370..38ae3560e5 100644 --- a/code/modules/crafting/recipes/recipes_misc.dm +++ b/code/modules/crafting/recipes/recipes_misc.dm @@ -358,3 +358,11 @@ time = 100 category = CAT_MISC always_availible = FALSE // Disabled til learned + +/datum/crafting_recipe/coconut_bong + name = "Coconut Bong" + result = /obj/item/bong/coconut + reqs = list(/obj/item/stack/sheet/mineral/bamboo = 2, + /obj/item/reagent_containers/food/snacks/grown/coconut = 1) + time = 70 + category = CAT_MISC \ No newline at end of file diff --git a/code/modules/food_and_drinks/food/snacks_frozen.dm b/code/modules/food_and_drinks/food/snacks_frozen.dm index 4a4c5ddb88..fb7bcf33b4 100644 --- a/code/modules/food_and_drinks/food/snacks_frozen.dm +++ b/code/modules/food_and_drinks/food/snacks_frozen.dm @@ -219,7 +219,7 @@ name = "space cola snowcone" desc = "Space Cola drizzled over a snowball in a paper cup." icon_state = "soda_sc" - list_reagents = list("nutriment" = 1, "space_cola" = 5) + list_reagents = list("nutriment" = 1, "cola" = 5) tastes = list("ice" = 1, "water" = 1, "cola" = 5) /obj/item/reagent_containers/food/snacks/snowcones/spacemountainwind @@ -248,4 +248,4 @@ desc = "A very colorful snowball in a paper cup." icon_state = "rainbow_sc" list_reagents = list("nutriment" = 5, "laughter" = 25) - tastes = list("ice" = 1, "water" = 1, "sunlight" = 5, "light" = 5, "slime" = 5, "paint" = 3, "clouds" = 3) \ No newline at end of file + tastes = list("ice" = 1, "water" = 1, "sunlight" = 5, "light" = 5, "slime" = 5, "paint" = 3, "clouds" = 3) diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm index 58a0ab4382..de22537063 100644 --- a/code/modules/food_and_drinks/food/snacks_other.dm +++ b/code/modules/food_and_drinks/food/snacks_other.dm @@ -609,4 +609,68 @@ filling_color = "#ECA735" tastes = list("fried corn" = 1) foodtype = JUNKFOOD | FRIED - dunkable = TRUE \ No newline at end of file + dunkable = TRUE + +/obj/item/reagent_containers/food/snacks/marshmallow + name = "marshmallow" + desc = "A marshmallow filled with fluffy marshmallow fluff." + icon_state = "marshmallow" + list_reagents = list("sugar" = 5, "nutriment" = 2) + filling_color = "#fafafa" + w_class = WEIGHT_CLASS_TINY + tastes = list("marshmallow" = 2) + foodtype = SUGAR | JUNKFOOD + var/burned = 0 + +/obj/item/reagent_containers/food/snacks/marshmallow/attackby(obj/item/I, mob/user) + switch (I.get_temperature()) + if (355 to 1500) + if (prob(30)) + burnmallow() + if (1500 to 2000) + if (prob(50)) + burnmallow() + else + burnmallow(TRUE) + if (2000 to 3000) + if (prob(10)) + burnmallow() + else + burnmallow(TRUE) + if (3000 to INFINITY) + burnmallow(TRUE) + return ..() + +/obj/item/reagent_containers/food/snacks/marshmallow/proc/burnmallow(reallyburned = FALSE) + if (reallyburned && burned == 1) + icon_state = "marshmallowrburned" + else if (burned == 0) + icon_state = "marshmallowburned" + +/obj/item/reagent_containers/food/snacks/marshmallow/examine(mob/user) + . = ..() + if (burned == 2) + . += "It looks very burned." + if (burned == 1) + . += "It looks just right for eating!" + +/obj/item/reagent_containers/food/snacks/marshmallow/fire_act(temp,volume) + switch (temp) + if (355 to 1500) + if (prob(30)) + burnmallow() + if (1500 to 2000) + if (prob(50)) + burnmallow() + else + burnmallow(TRUE) + if (2000 to 3000) + if (prob(10)) + burnmallow() + else + burnmallow(TRUE) + if (3000 to 7000) + burnmallow(TRUE) + if (7000 to INFINITY) + burn() + ..() diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index c273690571..6f1e19d5e0 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -8,6 +8,8 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico //On client, created on login /datum/chatOutput var/client/owner //client ref + var/total_checks = 0 + var/last_check = 0 var/loaded = FALSE // Has the client loaded the browser output area? var/list/messageQueue //If they haven't loaded chat, this is where messages will go until they do var/cookieSent = FALSE // Has the client sent a cookie for analysis @@ -150,6 +152,18 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico //Called by client, sent data to investigate (cookie history so far) /datum/chatOutput/proc/analyzeClientData(cookie = "") + //Spam check + if(world.time > last_check + (3 SECONDS)) + last_check = world.time + total_checks = 0 + + total_checks += 1 + + if(total_checks > SPAM_TRIGGER_AUTOMUTE) + message_admins("[key_name(owner)] kicked for goonchat topic spam") + qdel(owner) + return + if(!cookie) return @@ -158,13 +172,22 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico if (connData && islist(connData) && connData.len > 0 && connData["connData"]) connectionHistory = connData["connData"] //lol fuck var/list/found = new() - for(var/i in connectionHistory.len to 1 step -1) + if(connectionHistory.len > 5) + message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)") + qdel(owner) + return + + for(var/i in min(connectionHistory.len, 5) to 1 step -1) + if(QDELETED(owner)) + //he got cleaned up before we were done + return var/list/row = src.connectionHistory[i] if (!row || row.len < 3 || (!row["ckey"] || !row["compid"] || !row["ip"])) //Passed malformed history object return if (world.IsBanned(row["ckey"], row["ip"], row["compid"], real_bans_only=TRUE)) found = row break + CHECK_TICK //Uh oh this fucker has a history of playing on a banned account!! if (found.len > 0) diff --git a/code/modules/goonchat/browserassets/js/browserOutput.js b/code/modules/goonchat/browserassets/js/browserOutput.js index abd05d29ed..823ad107d2 100644 --- a/code/modules/goonchat/browserassets/js/browserOutput.js +++ b/code/modules/goonchat/browserassets/js/browserOutput.js @@ -421,8 +421,8 @@ function handleClientData(ckey, ip, compid) { return; //Record already exists } } - - if (opts.clientData.length >= opts.clientDataLimit) { + //Lets make sure we obey our limit (can connect from server with higher limit) + while (opts.clientData.length >= opts.clientDataLimit) { opts.clientData.shift(); } } else { diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 889706744a..d551222836 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -59,9 +59,7 @@ return var/area/AS = get_area(src) if(istype(AS, /area/holodeck)) - log_world("### MAPPING ERROR") - log_world("Holodeck computer cannot be in a holodeck.") - log_world("This would cause circular power dependency.") + log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.") qdel(src) return else diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm index 3376bac1cc..77199b3496 100644 --- a/code/modules/hydroponics/gene_modder.dm +++ b/code/modules/hydroponics/gene_modder.dm @@ -240,7 +240,7 @@ dat += "
    " else dat += "No trait-related genes detected in sample.
    " - if(can_insert && istype(disk.gene, /datum/plant_gene/trait)) + if(can_insert && istype(disk.gene, /datum/plant_gene/trait) && !seed.is_gene_forbidden(disk.gene.type)) dat += "Insert: [disk.gene.get_name()]" dat += "" else diff --git a/code/modules/hydroponics/grown/misc.dm b/code/modules/hydroponics/grown/misc.dm index b37e494233..3af284974e 100644 --- a/code/modules/hydroponics/grown/misc.dm +++ b/code/modules/hydroponics/grown/misc.dm @@ -232,7 +232,7 @@ /obj/item/reagent_containers/food/snacks/grown/cherry_bomb/proc/detonate() reagents.chem_temp = 1000 //Sets off the black powder reagents.handle_reactions() - + // Lavaland cactus /obj/item/seeds/lavaland/cactus @@ -244,3 +244,279 @@ product = /obj/item/reagent_containers/food/snacks/grown/ash_flora/cactus_fruit growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi' growthstages = 2 + + +// Coconut +/obj/item/seeds/coconut + name = "pack of coconut seeds" + desc = "They're seeds that grow into coconut palm trees." + icon_state = "seed-coconut" + species = "coconut" + plantname = "Coconut Palm Tree" + product = /obj/item/reagent_containers/food/snacks/grown/coconut + lifespan = 50 + endurance = 30 + potency = 35 + growing_icon = 'icons/obj/hydroponics/growing.dmi' + icon_dead = "coconut-dead" + genes = list(/datum/plant_gene/trait/repeated_harvest) + forbiddengenes = list(/datum/plant_gene/trait/squash, /datum/plant_gene/trait/stinging) + reagents_add = list("coconutmilk" = 0.3) + +/obj/item/reagent_containers/food/snacks/grown/coconut + seed = /obj/item/seeds/coconut + name = "coconut" + desc = "Hard shell of a nut containing delicious milk inside. Perhaps try using something sharp?" + icon_state = "coconut" + item_state = "coconut" + possible_transfer_amounts = list(5, 10, 15, 20, 25, 30, 50) + spillable = FALSE + resistance_flags = ACID_PROOF + volume = 150 //so it won't cut reagents despite having the capacity for them + w_class = WEIGHT_CLASS_SMALL + force = 5 + throwforce = 5 + hitsound = 'sound/weapons/klonk.ogg' + attack_verb = list("klonked", "donked", "bonked") + var/opened = FALSE + var/carved = FALSE + var/chopped = FALSE + var/straw = FALSE + var/fused = FALSE + var/fusedactive = FALSE + var/defused = FALSE + +/obj/item/reagent_containers/food/snacks/grown/coconut/Initialize(mapload, obj/item/seeds/new_seed) + . = ..() + var/newvolume = 50 + round(seed.potency,10) + if (seed.get_gene(/datum/plant_gene/trait/maxchem)) + newvolume = newvolume + 50 + volume = newvolume + reagents.maximum_volume = newvolume + reagents.update_total() + + transform *= TRANSFORM_USING_VARIABLE(40, 100) + 0.5 //temporary fix for size? + +/obj/item/reagent_containers/food/snacks/grown/coconut/attack_self(mob/user) + if (!opened) + return + + if(!possible_transfer_amounts.len) + return + var/i=0 + for(var/A in possible_transfer_amounts) + i++ + if(A != amount_per_transfer_from_this) + continue + if(i[src]'s transfer amount is now [amount_per_transfer_from_this] units.") + return + +/obj/item/reagent_containers/food/snacks/grown/coconut/attackby(obj/item/W, mob/user, params) + //DEFUSING NADE LOGIC + if (W.tool_behaviour == TOOL_WIRECUTTER && fused) + user.show_message("You cut the fuse!", MSG_VISUAL) + playsound(user, W.hitsound, 50, 1, -1) + icon_state = "coconut_carved" + desc = "A coconut. This one's got a hole in it." + name = "coconut" + defused = TRUE + fused = FALSE + fusedactive = FALSE + if(!seed.get_gene(/datum/plant_gene/trait/glow)) + set_light(0, 0.0) + return + //IGNITING NADE LOGIC + if(!fusedactive && fused) + var/lighting_text = W.ignition_effect(src, user) + if(lighting_text) + user.visible_message("[user] ignites [src]'s fuse!", "You ignite the [src]'s fuse!") + fusedactive = TRUE + defused = FALSE + playsound(src, 'sound/effects/fuse.ogg', 100, 0) + message_admins("[ADMIN_LOOKUPFLW(user)] ignited a coconut bomb for detonation at [ADMIN_VERBOSEJMP(user)] [pretty_string_from_reagent_list(reagents.reagent_list)]") + log_game("[key_name(user)] primed a coconut grenade for detonation at [AREACOORD(user)].") + addtimer(CALLBACK(src, .proc/prime), 5 SECONDS) + icon_state = "coconut_grenade_active" + desc = "RUN!" + if(!seed.get_gene(/datum/plant_gene/trait/glow)) + light_color = "#FFCC66" //for the fuse + set_light(3, 0.8) + return + + //ADDING A FUSE, NADE LOGIC + if (istype(W,/obj/item/stack/sheet/cloth) || istype(W,/obj/item/stack/sheet/durathread)) + if (carved && !straw && !fused) + user.show_message("You add a fuse to the coconut!", 1) + W.use(1) + fused = TRUE + icon_state = "coconut_grenade" + desc = "A makeshift bomb made out of a coconut. You estimate the fuse is long enough for 5 seconds." + name = "coconut bomb" + return + //ADDING STRAW LOGIC + if (istype(W,/obj/item/stack/sheet/mineral/bamboo) && opened && !straw && fused) + user.show_message("You add a bamboo straw to the coconut!", 1) + straw = TRUE + W.use(1) + icon_state += "_straw" + desc = "You can already feel like you're on a tropical vacation." + return + //OPENING THE NUT LOGIC + if (!carved && !chopped) + var/screwdrivered = W.tool_behaviour == TOOL_SCREWDRIVER + if(screwdrivered || W.sharpness) + user.show_message("You [screwdrivered ? "make a hole in the coconut" : "slice the coconut open"]!", 1) + carved = TRUE + opened = TRUE + spillable = !screwdrivered + reagent_flags = OPENCONTAINER + ENABLE_BITFIELD(reagents.reagents_holder_flags, OPENCONTAINER) + icon_state = screwdrivered ? "coconut_carved" : "coconut_chopped" + desc = "A coconut. [screwdrivered ? "This one's got a hole in it" : "This one's sliced open, with all its delicious contents for your eyes to savour"]." + playsound(user, W.hitsound, 50, 1, -1) + return + return ..() + +/obj/item/reagent_containers/food/snacks/grown/coconut/attack(mob/living/M, mob/user, obj/target) + if(M && user.a_intent == INTENT_HARM && !spillable) + var/obj/item/bodypart/affecting = user.zone_selected //Find what the player is aiming at + if (affecting == BODY_ZONE_HEAD && prob(15)) + //smash the nut open + var/armor_block = min(90, M.run_armor_check(affecting, "melee", null, null,armour_penetration)) // For normal attack damage + M.apply_damage(force, BRUTE, affecting, armor_block) + + //Sound + playsound(user, hitsound, 100, 1, -1) + + //Attack logs + log_combat(user, M, "attacked", src) + + //Display an attack message. + if(M != user) + M.visible_message("[user] has cracked open a [name] on [M]'s head!", \ + "[user] has cracked open a [name] on [M]'s head!") + else + user.visible_message("[M] cracks open a [name] on their [M.p_them()] head!", \ + "[M] cracks open a [name] on [M.p_their()] head!") + + //The coconut breaks open so splash its reagents + spillable = TRUE + SplashReagents(M) + + //Lastly we remove the nut + qdel(src) + else + . = ..() + return + + if(fusedactive) + return + + if(!opened) + return + + if(!canconsume(M, user)) + return + + if(!reagents || !reagents.total_volume) + to_chat(user, "[src] is empty!") + return + + if(user.a_intent == INTENT_HARM && spillable) + var/R + M.visible_message("[user] splashes the contents of [src] onto [M]!", \ + "[user] splashes the contents of [src] onto [M]!") + if(reagents) + for(var/datum/reagent/A in reagents.reagent_list) + R += A.id + " (" + R += num2text(A.volume) + ")," + if(isturf(target) && reagents.reagent_list.len && thrownby) + log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]") + message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] at [ADMIN_VERBOSEJMP(target)].") + reagents.reaction(M, TOUCH) + log_combat(user, M, "splashed", R) + reagents.clear_reagents() + else + if(M != user) + M.visible_message("[user] attempts to feed something to [M].", \ + "[user] attempts to feed something to you.") + if(!do_mob(user, M)) + return + if(!reagents || !reagents.total_volume) + return // The drink might be empty after the delay, such as by spam-feeding + M.visible_message("[user] feeds something to [M].", "[user] feeds something to you.") + log_combat(user, M, "fed", reagents.log_list()) + else + to_chat(user, "You swallow a gulp of [src].") + var/fraction = min(5/reagents.total_volume, 1) + reagents.reaction(M, INGEST, fraction) + addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5), 5) + playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1) + +/obj/item/reagent_containers/food/snacks/grown/coconut/afterattack(obj/target, mob/user, proximity) + . = ..() + if(fusedactive) + return + + if((!proximity) || !check_allowed_items(target,target_self=1)) + return + + if(target.is_refillable()) //Something like a glass. Player probably wants to transfer TO it. + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + + if(target.reagents.holder_full()) + to_chat(user, "[target] is full.") + return + + var/trans = reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [trans] unit\s of the solution to [target].") + + else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty and can't be refilled!") + return + + if(reagents.holder_full()) + to_chat(user, "[src] is full.") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) + to_chat(user, "You fill [src] with [trans] unit\s of the contents of [target].") + + else if(reagents.total_volume) + if(user.a_intent == INTENT_HARM && spillable == TRUE) + user.visible_message("[user] splashes the contents of [src] onto [target]!", \ + "You splash the contents of [src] onto [target].") + reagents.reaction(target, TOUCH) + reagents.clear_reagents() + +/obj/item/reagent_containers/food/snacks/grown/coconut/dropped(mob/user) + . = ..() + transform *= TRANSFORM_USING_VARIABLE(40, 100) + 0.5 //temporary fix for size? + +/obj/item/reagent_containers/food/snacks/grown/coconut/proc/prime() + if (defused) + return + var/turf/T = get_turf(src) + reagents.chem_temp = 1000 + //Disable seperated contents when the grenade primes + if (seed.get_gene(/datum/plant_gene/trait/noreact)) + DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) + reagents.handle_reactions() + log_game("Coconut bomb detonation at [AREACOORD(T)], location [loc]") + qdel(src) + +/obj/item/reagent_containers/food/snacks/grown/coconut/ex_act(severity) + qdel(src) + +/obj/item/reagent_containers/food/snacks/grown/coconut/deconstruct(disassembled = TRUE) + if(!disassembled && fused) + prime() + if(!QDELETED(src)) + qdel(src) diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 4132719389..39e97e3801 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -27,6 +27,7 @@ var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. var/list/mutatelist = list() // The type of plants that this plant can mutate into. var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. + var/list/forbiddengenes = list() var/list/reagents_add = list() // A list of reagents to add to product. // Format: "reagent_id" = potency multiplier @@ -96,6 +97,10 @@ S.reagents_add = reagents_add.Copy() // Faster than grabbing the list from genes. return S +obj/item/seeds/proc/is_gene_forbidden(typepath) + return (typepath in forbiddengenes) + + /obj/item/seeds/proc/get_gene(typepath) return (locate(typepath) in genes) @@ -448,7 +453,7 @@ for(var/i in 1 to amount_random_traits) var/random_trait = pick((subtypesof(/datum/plant_gene/trait)-typesof(/datum/plant_gene/trait/plant_type))) var/datum/plant_gene/trait/T = new random_trait - if(T.can_add(src)) + if(T.can_add(src) && !is_gene_forbidden(random_trait)) genes += T else qdel(T) diff --git a/code/modules/keybindings/bindings_human.dm b/code/modules/keybindings/bindings_human.dm index a9eafacef6..ed033935b2 100644 --- a/code/modules/keybindings/bindings_human.dm +++ b/code/modules/keybindings/bindings_human.dm @@ -58,13 +58,23 @@ return switch(_key) if("Shift") - sprint_hotkey(TRUE) + if(!user.prefs.sprint_spacebar) + user.prefs.sprint_toggle ? togglesprint() : sprint_hotkey(TRUE) //Yes, this looks hacky. Yes, this works. + return + if("Space") + if(user.prefs.sprint_spacebar) + user.prefs.sprint_toggle ? togglesprint() : sprint_hotkey(TRUE) return return ..() /mob/living/carbon/human/key_up(_key, client/user) switch(_key) if("Shift") - sprint_hotkey(FALSE) + if(!user.prefs.sprint_spacebar && !user.prefs.sprint_toggle) + sprint_hotkey(FALSE) + return + if("Space") + if(user.prefs.sprint_spacebar && !user.prefs.sprint_toggle) + sprint_hotkey(FALSE) return return ..() diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 1f55f18823..3e361179de 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -70,7 +70,7 @@ //initialize things that are normally initialized after map load parsed.initTemplateBounds() smooth_zlevel(world.maxz) - log_game("Z-level [name] loaded at at [x],[y],[world.maxz]") + log_game("Z-level [name] loaded at [x],[y],[world.maxz]") return level @@ -84,6 +84,13 @@ if(T.y+height > world.maxy) return + var/list/border = block(locate(max(T.x-1, 1), max(T.y-1, 1), T.z), + locate(min(T.x+width+1, world.maxx), min(T.y+height+1, world.maxy), T.z)) + for(var/L in border) + var/turf/turf_to_disable = L + SSair.remove_from_active(turf_to_disable) //stop processing turfs along the border to prevent runtimes, we return it in initTemplateBounds() + turf_to_disable.atmos_adjacent_turfs?.Cut() + // Accept cached maps, but don't save them automatically - we don't want // ruins clogging up memory for the whole round. var/datum/parsed_map/parsed = cached_map || new(file(mappath)) @@ -100,7 +107,7 @@ //initialize things that are normally initialized after map load parsed.initTemplateBounds() - log_game("[name] loaded at at [T.x],[T.y],[T.z]") + log_game("[name] loaded at [T.x],[T.y],[T.z]") return bounds /datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE) diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index e2459d780a..a7f84fd71f 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -18,7 +18,7 @@ /obj/effect/baseturf_helper/LateInitialize() if(!baseturf_to_replace) - baseturf_to_replace = typecacheof(/turf/open/space) + baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom)) else if(!length(baseturf_to_replace)) baseturf_to_replace = list(baseturf_to_replace = TRUE) else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative @@ -45,7 +45,6 @@ thing.PlaceOnBottom(null, baseturf) else if(baseturf_to_replace[thing.baseturfs]) thing.assemble_baseturfs(baseturf) - return else thing.PlaceOnBottom(null, baseturf) @@ -107,16 +106,16 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) if(airlock.cyclelinkeddir) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") + log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") else airlock.cyclelinkeddir = dir else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") /obj/effect/mapping_helpers/airlock/locked @@ -126,16 +125,16 @@ /obj/effect/mapping_helpers/airlock/locked/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) if(airlock.locked) - log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") + log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") else airlock.locked = TRUE else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") /obj/effect/mapping_helpers/airlock/unres name = "airlock unresctricted side helper" @@ -144,13 +143,13 @@ /obj/effect/mapping_helpers/airlock/unres/Initialize(mapload) . = ..() if(!mapload) - log_world("### MAP WARNING, [src] spawned outside of mapload!") + log_mapping("[src] spawned outside of mapload!") return var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc if(airlock) airlock.unres_sides ^= dir else - log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]") + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") //needs to do its thing before spawn_rivers() is called @@ -164,17 +163,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) var/turf/T = get_turf(src) T.flags_1 |= NO_LAVA_GEN_1 -/// Adds the map it is on to the z_is_planet list -/obj/effect/mapping_helpers/planet_z - name = "planet z helper" - layer = POINT_LAYER - -/obj/effect/mapping_helpers/planet_z/Initialize() - . = ..() - var/datum/space_level/S = SSmapping.get_level(z) - S.traits[ZTRAIT_PLANET] = TRUE - - //This helper applies components to things on the map directly. /obj/effect/mapping_helpers/component_injector name = "Component Injector" diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm index e6fa2421a0..4b61663f66 100644 --- a/code/modules/mapping/preloader.dm +++ b/code/modules/mapping/preloader.dm @@ -8,18 +8,26 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) var/list/attributes var/target_path -/datum/map_preloader/proc/setup(list/the_attributes, path) +/world/proc/preloader_setup(list/the_attributes, path) if(the_attributes.len) GLOB.use_preloader = TRUE - attributes = the_attributes - target_path = path + var/datum/map_preloader/preloader_local = GLOB._preloader + preloader_local.attributes = the_attributes + preloader_local.target_path = path -/datum/map_preloader/proc/load(atom/what) +/world/proc/preloader_load(atom/what) GLOB.use_preloader = FALSE - for(var/attribute in attributes) - var/value = attributes[attribute] + var/datum/map_preloader/preloader_local = GLOB._preloader + for(var/attribute in preloader_local.attributes) + var/value = preloader_local.attributes[attribute] if(islist(value)) value = deepCopyList(value) + #ifdef TESTING + if(what.vars[attribute] == value) + var/message = "[what.type] at [AREACOORD(what)] - VAR: [attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]" + log_mapping("DIRTY VAR: [message]") + GLOB.dirty_vars += message + #endif what.vars[attribute] = value /area/template_noop diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 22be4aa246..9b27691dd3 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -306,8 +306,8 @@ //first instance the /area and remove it from the members list index = members.len if(members[index] != /area/template_noop) - GLOB._preloader.setup(members_attributes[index])//preloader for assigning set variables on atom creation var/atype = members[index] + world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation var/atom/instance = areaCache[atype] if (!instance) instance = GLOB.areas_by_type[atype] @@ -318,7 +318,7 @@ instance.contents.Add(crds) if(GLOB.use_preloader && instance) - GLOB._preloader.load(instance) + world.preloader_load(instance) //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect @@ -354,7 +354,7 @@ //Instance an atom at (x,y,z) and gives it the variables in attributes /datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) - GLOB._preloader.setup(attributes, path) + world.preloader_setup(attributes, path) if(crds) if(ispath(path, /turf)) @@ -368,7 +368,7 @@ . = create_atom(path, crds)//first preloader pass if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New() - GLOB._preloader.load(.) + world.preloader_load(.) //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize if(TICK_CHECK) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index ab3b20ac9f..7bef9b652a 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -926,7 +926,7 @@ timer = world.time + create_delay + 1 if(do_after(user, create_delay, target = T)) var/old_name = T.name - if(T.TerraformTurf(turf_type)) + if(T.TerraformTurf(turf_type, flags = CHANGETURF_INHERIT_AIR)) user.visible_message("[user] turns \the [old_name] into [transform_string]!") message_admins("[ADMIN_LOOKUPFLW(user)] fired the lava staff at [ADMIN_VERBOSEJMP(T)]") log_game("[key_name(user)] fired the lava staff at [AREACOORD(T)].") @@ -937,7 +937,7 @@ qdel(L) else var/old_name = T.name - if(T.TerraformTurf(reset_turf_type)) + if(T.TerraformTurf(reset_turf_type, flags = CHANGETURF_INHERIT_AIR)) user.visible_message("[user] turns \the [old_name] into [reset_string]!") timer = world.time + reset_cooldown playsound(T,'sound/magic/fireball.ogg', 200, 1) diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 79da476719..b5f7bbf8ab 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -50,7 +50,7 @@ /obj/machinery/mineral/ore_redemption/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore.
    Ore pickup speed at [ore_pickup_rate].
    " + . += "The status display reads: Smelting [sheet_per_ore] sheet(s) per piece of ore.
    Reward point generation at [point_upgrade*100]%.
    Ore pickup speed at [ore_pickup_rate].
    " /obj/machinery/mineral/ore_redemption/proc/smelt_ore(obj/item/stack/ore/O) var/datum/component/material_container/mat_container = materials.mat_container @@ -63,7 +63,7 @@ ore_buffer -= O if(O && O.refined_type) - points += O.points * O.amount + points += O.points * point_upgrade * O.amount var/material_amount = mat_container.get_item_material_amount(O) diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm index dd66f68e5d..241ec8118e 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm @@ -74,4 +74,5 @@ /datum/sprite_accessory/underwear icon = 'icons/mob/underwear.dmi' - var/has_color = FALSE \ No newline at end of file + var/has_color = FALSE + var/has_digitigrade = FALSE \ No newline at end of file diff --git a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm index 0a35f0cd26..19ec677a72 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm @@ -2,6 +2,9 @@ // Socks Definitions // /////////////////////// +/datum/sprite_accessory/underwear/socks + has_digitigrade = TRUE + /datum/sprite_accessory/underwear/socks/nude name = "Nude" icon_state = null diff --git a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm index 3356804cb3..9441b5120a 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm @@ -28,41 +28,49 @@ name = "Boxers" icon_state = "boxers" has_color = TRUE + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_bee name = "Boxers - Bee" icon_state = "bee_shorts" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_hearts name = "Boxers - Heart" icon_state = "boxers_heart" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_stripe name = "Boxers - Striped" icon_state = "boxers_striped" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_commie name = "Boxers - Striped Communist" icon_state = "boxers_commie" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_usastripe name = "Boxers - Striped Freedom" icon_state = "boxers_assblastusa" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/male_uk name = "Boxers - Striped UK" icon_state = "boxers_uk" + has_digitigrade = TRUE gender = MALE /datum/sprite_accessory/underwear/bottom/boxer_briefs name = "Boxer Briefs" icon_state = "boxer_briefs" + has_digitigrade = TRUE has_color = TRUE /datum/sprite_accessory/underwear/bottom/panties @@ -140,6 +148,7 @@ /datum/sprite_accessory/underwear/bottom/longjon name = "Long John Bottoms" icon_state = "ljonb" + has_digitigrade = TRUE has_color = TRUE /datum/sprite_accessory/underwear/bottom/swimsuit_red diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index b9e27c0637..bdc691ce49 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -6,21 +6,21 @@ return 2 //no ears /mob/living/carbon/alien/hitby(atom/movable/AM, skipcatch, hitpush) - ..(AM, skipcatch = TRUE, hitpush = FALSE) + return ..(AM, skipcatch = TRUE, hitpush = FALSE) +/mob/living/carbon/alien/can_embed(obj/item/I) + return FALSE /*Code for aliens attacking aliens. Because aliens act on a hivemind, I don't see them as very aggressive with each other. As such, they can either help or harm other aliens. Help works like the human help command while harm is a simple nibble. In all, this is a lot like the monkey code. /N */ /mob/living/carbon/alien/attack_alien(mob/living/carbon/alien/M) - if(isturf(loc) && istype(loc.loc, /area/start)) - to_chat(M, "No attacking people at spawn, you jackass.") + . = ..() + if(!.) // the attack was blocked or was help/grab intent return - switch(M.a_intent) - - if ("help") + if (INTENT_HELP) if(!recoveringstam) resting = 0 AdjustStun(-60) @@ -28,11 +28,7 @@ In all, this is a lot like the monkey code. /N AdjustUnconscious(-60) AdjustSleeping(-100) visible_message("[M.name] nuzzles [src] trying to wake [p_them()] up!") - - if ("grab") - grabbedby(M) - - else + if(INTENT_DISARM, INTENT_HARM) if(health > 0) M.do_attack_animation(src, ATTACK_EFFECT_BITE) playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) @@ -50,28 +46,31 @@ In all, this is a lot like the monkey code. /N /mob/living/carbon/alien/attack_hand(mob/living/carbon/human/M) - if(..()) //to allow surgery to return properly. - return 0 - + . = ..() + if(.) //To allow surgery to return properly. + return switch(M.a_intent) - if("help") + if(INTENT_HELP) help_shake_act(M) - if("grab") + if(INTENT_GRAB) grabbedby(M) - if ("harm") + if (INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt [src]!") + return TRUE M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - return 1 - if("disarm") + if(INTENT_DISARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt [src]!") + return TRUE M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return 1 - return 0 /mob/living/carbon/alien/attack_paw(mob/living/carbon/monkey/M) - if(..()) - if (stat != DEAD) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - apply_damage(rand(1, 3), BRUTE, affecting) + . = ..() + if(.) //successful monkey bite. + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + apply_damage(rand(1, 3), BRUTE, affecting) /mob/living/carbon/alien/attack_animal(mob/living/simple_animal/M) @@ -93,13 +92,15 @@ In all, this is a lot like the monkey code. /N adjustStaminaLoss(damage) /mob/living/carbon/alien/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - var/damage = rand(5, 35) - if(M.is_adult) - damage = rand(10, 40) - adjustBruteLoss(damage) - log_combat(M, src, "attacked") - updatehealth() + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 35) + if(M.is_adult) + damage = rand(10, 40) + adjustBruteLoss(damage) + log_combat(M, src, "attacked") + updatehealth() /mob/living/carbon/alien/ex_act(severity, target, origin) if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm index fe682b5c99..d1ed09665b 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm @@ -63,12 +63,7 @@ if(A) if(isliving(A)) var/mob/living/L = A - var/blocked = FALSE - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) - blocked = TRUE - if(!blocked) + if(!L.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") L.Knockdown(100) sleep(2)//Runtime prevention (infinite bump() calls on hulks) diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm index b3839a6033..1d613db07a 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid_defense.dm @@ -5,9 +5,11 @@ else ..() -/mob/living/carbon/alien/humanoid/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/mob/living/carbon/alien/humanoid/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) - ..(user, 1) + . = ..(user, TRUE) + if(.) + return adjustBruteLoss(15) var/hitverb = "punched" if(mob_size < MOB_SIZE_LARGE) @@ -21,46 +23,46 @@ return 1 /mob/living/carbon/alien/humanoid/attack_hand(mob/living/carbon/human/M) - if(..()) - switch(M.a_intent) - if ("harm") - var/damage = rand(1, 9) - if (prob(90)) - playsound(loc, "punch", 25, 1, -1) - visible_message("[M] has punched [src]!", \ - "[M] has punched [src]!", null, COMBAT_MESSAGE_RANGE) - if ((stat != DEAD) && (damage > 9 || prob(5)))//Regular humans have a very small chance of knocking an alien down. - Unconscious(40) - visible_message("[M] has knocked [src] down!", \ - "[M] has knocked [src] down!") - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - apply_damage(damage, BRUTE, affecting) - log_combat(M, src, "attacked") + . = ..() + if(.) //To allow surgery to return properly. + return + switch(M.a_intent) + if (INTENT_HARM) + var/damage = rand(1, 9) + if (prob(90)) + playsound(loc, "punch", 25, 1, -1) + visible_message("[M] has punched [src]!", \ + "[M] has punched [src]!", null, COMBAT_MESSAGE_RANGE) + if ((stat != DEAD) && (damage > 9 || prob(5)))//Regular humans have a very small chance of knocking an alien down. + Unconscious(40) + visible_message("[M] has knocked [src] down!", \ + "[M] has knocked [src] down!") + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + apply_damage(damage, BRUTE, affecting) + log_combat(M, src, "attacked") + else + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message("[M] has attempted to punch [src]!", \ + "[M] has attempted to punch [src]!", null, COMBAT_MESSAGE_RANGE) + + if (INTENT_DISARM) + if (!lying) + if (prob(5)) + Unconscious(40) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + log_combat(M, src, "pushed") + visible_message("[M] has pushed down [src]!", \ + "[M] has pushed down [src]!") else - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - visible_message("[M] has attempted to punch [src]!", \ - "[M] has attempted to punch [src]!", null, COMBAT_MESSAGE_RANGE) - - if ("disarm") - if (!lying) - if (prob(5)) - Unconscious(40) + if (prob(50)) + dropItemToGround(get_active_held_item()) playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - log_combat(M, src, "pushed") - visible_message("[M] has pushed down [src]!", \ - "[M] has pushed down [src]!") + visible_message("[M] has disarmed [src]!", \ + "[M] has disarmed [src]!", null, COMBAT_MESSAGE_RANGE) else - if (prob(50)) - dropItemToGround(get_active_held_item()) - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - visible_message("[M] has disarmed [src]!", \ - "[M] has disarmed [src]!", null, COMBAT_MESSAGE_RANGE) - else - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - visible_message("[M] has attempted to disarm [src]!",\ - "[M] has attempted to disarm [src]!", null, COMBAT_MESSAGE_RANGE) - - + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message("[M] has attempted to disarm [src]!",\ + "[M] has attempted to disarm [src]!", null, COMBAT_MESSAGE_RANGE) /mob/living/carbon/alien/humanoid/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) if(!no_effect && !visual_effect_icon) diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm index 69c1be707d..7dabcf5abf 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm @@ -1,26 +1,33 @@ /mob/living/carbon/alien/larva/attack_hand(mob/living/carbon/human/M) - if(..()) - var/damage = rand(1, 9) - if (prob(90)) - playsound(loc, "punch", 25, 1, -1) - log_combat(M, src, "attacked") - visible_message("[M] has kicked [src]!", \ - "[M] has kicked [src]!", null, COMBAT_MESSAGE_RANGE) - if ((stat != DEAD) && (damage > 4.9)) - Unconscious(rand(100,200)) + . = ..() + if(. || M.a_intent == INTENT_HELP || M.a_intent == INTENT_GRAB) + return + var/damage = rand(1, 9) + if (prob(90)) + playsound(loc, "punch", 25, 1, -1) + log_combat(M, src, "attacked") + visible_message("[M] has kicked [src]!", \ + "[M] has kicked [src]!", null, COMBAT_MESSAGE_RANGE) + if ((stat != DEAD) && (damage > 4.9)) + Unconscious(rand(100,200)) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - apply_damage(damage, BRUTE, affecting) - else - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - visible_message("[M] has attempted to kick [src]!", \ - "[M] has attempted to kick [src]!", null, COMBAT_MESSAGE_RANGE) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + apply_damage(damage, BRUTE, affecting) + else + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + visible_message("[M] has attempted to kick [src]!", \ + "[M] has attempted to kick [src]!", null, COMBAT_MESSAGE_RANGE) -/mob/living/carbon/alien/larva/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/mob/living/carbon/alien/larva/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) - ..(user, 1) + . = ..(user, TRUE) + if(.) + return + playsound(loc, "punch", 25, 1, -1) + visible_message("[user] has pummeled [src]!", \ + "[user] has pummeled [src]!", null, COMBAT_MESSAGE_RANGE) adjustBruteLoss(5 + rand(1,9)) new /datum/forced_movement(src, get_step_away(user,src, 30), 1) return 1 diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index 0bfd23cbff..c08fe4f582 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -103,15 +103,17 @@ new_xeno.notransform = 0 new_xeno.invisibility = 0 + var/mob/living/carbon/old_owner = owner if(kill_on_sucess) //ITS TOO LATE new_xeno.visible_message("[new_xeno] bursts out of [owner]!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!") owner.apply_damage(rand(100,300),BRUTE,zone,FALSE) //Random high damage to torso so health sensors don't metagame. - owner.spill_organs(TRUE,FALSE,TRUE) //Lets still make the death gruesome and impossible to just simply defib someone. + var/obj/item/bodypart/B = owner.get_bodypart(zone) + B.drop_organs(owner) //Lets still make the death gruesome and impossible to just simply defib someone. owner.death(FALSE) //Just in case some freak occurance occurs where you somehow survive all your organs being removed from you and the 100-300 brute damage. else //When it is removed via surgery at a late stage, rather than forced. new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.") owner.adjustBruteLoss(40) - owner.cut_overlay(overlay) + old_owner.cut_overlay(overlay) qdel(src) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 52cf82a06a..102bc6fc3d 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1,990 +1,991 @@ -/mob/living/carbon - blood_volume = BLOOD_VOLUME_NORMAL - -/mob/living/carbon/Initialize() - . = ..() - create_reagents(1000) - update_body_parts() //to update the carbon's new bodyparts appearance - GLOB.carbon_list += src - blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio) - -/mob/living/carbon/Destroy() - //This must be done first, so the mob ghosts correctly before DNA etc is nulled - . = ..() - - QDEL_LIST(internal_organs) - QDEL_LIST(stomach_contents) - QDEL_LIST(bodyparts) - QDEL_LIST(implants) - remove_from_all_data_huds() - QDEL_NULL(dna) - GLOB.carbon_list -= src - -/mob/living/carbon/initialize_footstep() - AddComponent(/datum/component/footstep, 0.6, 2) - -/mob/living/carbon/relaymove(mob/user, direction) - if(user in src.stomach_contents) - if(prob(40)) - if(prob(25)) - audible_message("You hear something rumbling inside [src]'s stomach...", \ - "You hear something rumbling.", 4,\ - "Something is rumbling inside your stomach!") - var/obj/item/I = user.get_active_held_item() - if(I && I.force) - var/d = rand(round(I.force / 4), I.force) - var/obj/item/bodypart/BP = get_bodypart(BODY_ZONE_CHEST) - if(BP.receive_damage(d, 0)) - update_damage_overlays() - visible_message("[user] attacks [src]'s stomach wall with the [I.name]!", \ - "[user] attacks your stomach wall with the [I.name]!") - playsound(user.loc, 'sound/effects/attackblob.ogg', 50, 1) - - if(prob(src.getBruteLoss() - 50)) - for(var/atom/movable/A in stomach_contents) - A.forceMove(drop_location()) - stomach_contents.Remove(A) - src.gib() - - -/mob/living/carbon/swap_hand(held_index) - if(!held_index) - held_index = (active_hand_index % held_items.len)+1 - - var/obj/item/item_in_hand = src.get_active_held_item() - if(item_in_hand) //this segment checks if the item in your hand is twohanded. - var/obj/item/twohanded/TH = item_in_hand - if(istype(TH)) - if(TH.wielded == 1) - to_chat(usr, "Your other hand is too busy holding [TH]") - return - var/oindex = active_hand_index - active_hand_index = held_index - if(hud_used) - var/obj/screen/inventory/hand/H - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_icon() - H = hud_used.hand_slots["[held_index]"] - if(H) - H.update_icon() - - -/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() // Activate held item - -/mob/living/carbon/attackby(obj/item/I, mob/user, params) - if(lying && surgeries.len) - if(user != src && (user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)) - for(var/datum/surgery/S in surgeries) - if(S.next_step(user,user.a_intent)) - return 1 - return ..() - -/mob/living/carbon/throw_impact(atom/hit_atom, throwingdatum) - . = ..() - var/hurt = TRUE - if(istype(throwingdatum, /datum/thrownthing)) - var/datum/thrownthing/D = throwingdatum - if(iscyborg(D.thrower)) - var/mob/living/silicon/robot/R = D.thrower - if(!R.emagged) - hurt = FALSE - if(hit_atom.density && isturf(hit_atom)) - if(hurt) - Knockdown(20) - take_bodypart_damage(10) - if(iscarbon(hit_atom) && hit_atom != src) - var/mob/living/carbon/victim = hit_atom - if(victim.movement_type & FLYING) - return - if(hurt) - victim.take_bodypart_damage(10) - take_bodypart_damage(10) - victim.Knockdown(20) - Knockdown(20) - visible_message("[src] crashes into [victim], knocking them both over!",\ - "You violently crash into [victim]!") - playsound(src,'sound/weapons/punch1.ogg',50,1) - - -//Throwing stuff -/mob/living/carbon/proc/toggle_throw_mode() - if(stat) - return - if(in_throw_mode) - throw_mode_off() - else - throw_mode_on() - - -/mob/living/carbon/proc/throw_mode_off() - in_throw_mode = 0 - if(client && hud_used) - hud_used.throw_icon.icon_state = "act_throw_off" - - -/mob/living/carbon/proc/throw_mode_on() - in_throw_mode = 1 - if(client && hud_used) - hud_used.throw_icon.icon_state = "act_throw_on" - -/mob/proc/throw_item(atom/target) - SEND_SIGNAL(src, COMSIG_MOB_THROW, target) - return - -/mob/living/carbon/throw_item(atom/target) - throw_mode_off() - if(!target || !isturf(loc)) - return - if(istype(target, /obj/screen)) - return - - //CIT CHANGES - makes it impossible to throw while in stamina softcrit - if(getStaminaLoss() >= STAMINA_SOFTCRIT) - to_chat(src, "You're too exhausted.") - return - //END OF CIT CHANGES - - var/obj/item/I = src.get_active_held_item() - - var/atom/movable/thrown_thing - var/mob/living/throwable_mob - - if(istype(I, /obj/item/clothing/head/mob_holder)) - var/obj/item/clothing/head/mob_holder/holder = I - if(holder.held_mob) - throwable_mob = holder.held_mob - holder.release() - - if(!I || throwable_mob) - if(!throwable_mob && pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) - throwable_mob = pulling - - if(throwable_mob && !throwable_mob.buckled) - thrown_thing = throwable_mob - if(pulling) - stop_pulling() - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(src, "You gently let go of [throwable_mob].") - return - - adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring - var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors - var/turf/end_T = get_turf(target) - if(start_T && end_T) - log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") - - else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP)) - thrown_thing = I - dropItemToGround(I) - - if(HAS_TRAIT(src, TRAIT_PACIFISM) && I.throwforce) - to_chat(src, "You set [I] down gently on the ground.") - return - - adjustStaminaLossBuffered(I.getweight()*2)//CIT CHANGE - throwing items shall be more tiring than swinging em. Doubly so. - - if(thrown_thing) - visible_message("[src] has thrown [thrown_thing].") - src.log_message("has thrown [thrown_thing]", LOG_ATTACK) - do_attack_animation(target, no_effect = 1) - playsound(loc, 'sound/weapons/punchmiss.ogg', 50, 1, -1) - newtonian_move(get_dir(target, src)) - thrown_thing.throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src) - - - -/mob/living/carbon/restrained(ignore_grab) - . = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)) - -/mob/living/carbon/proc/canBeHandcuffed() - return 0 - - -/mob/living/carbon/show_inv(mob/user) - user.set_machine(src) - var/dat = {" -
    - [name] -
    -
    Head: [(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"] -
    Mask: [(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"] -
    Neck: [(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Nothing"]"} - - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "
    [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]" - - dat += "
    Back: [back ? back : "Nothing"]" - - if(istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank)) - dat += "
    [internal ? "Disable Internals" : "Set Internals"]" - - if(handcuffed) - dat += "
    Handcuffed" - if(legcuffed) - dat += "
    Legcuffed" - - dat += {" -
    -
    Close - "} - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") - -/mob/living/carbon/Topic(href, href_list) - ..() - //strip panel - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - if(href_list["internal"]) - var/slot = text2num(href_list["internal"]) - var/obj/item/ITEM = get_item_by_slot(slot) - if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) - visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \ - "[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].") - if(do_mob(usr, src, POCKET_STRIP_DELAY)) - if(internal) - internal = null - update_internals_hud_icon(0) - else if(ITEM && istype(ITEM, /obj/item/tank)) - if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - internal = ITEM - update_internals_hud_icon(1) - - visible_message("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].", \ - "[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].") - - -/mob/living/carbon/fall(forced) - loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing - -/mob/living/carbon/is_muzzled() - return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) - -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - -/mob/living/carbon/resist_buckle() - if(restrained()) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - var/buckle_cd = 600 - if(handcuffed) - var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED) - buckle_cd = O.breakouttime - visible_message("[src] attempts to unbuckle [p_them()]self!", \ - "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)") - if(do_after(src, buckle_cd, 0, target = src)) - if(!buckled) - return - buckled.user_unbuckle_mob(src,src) - else - if(src && buckled) - to_chat(src, "You fail to unbuckle yourself!") - else - buckled.user_unbuckle_mob(src,src) - -/mob/living/carbon/resist_fire() - fire_stacks -= 5 - Knockdown(60, TRUE, TRUE) - spin(32,2) - visible_message("[src] rolls on the floor, trying to put [p_them()]self out!", \ - "You stop, drop, and roll!") - sleep(30) - if(fire_stacks <= 0) - visible_message("[src] has successfully extinguished [p_them()]self!", \ - "You extinguish yourself.") - ExtinguishMob() - return - -/mob/living/carbon/resist_restraints() - var/obj/item/I = null - var/type = 0 - if(handcuffed) - I = handcuffed - type = 1 - else if(legcuffed) - I = legcuffed - type = 2 - if(I) - if(type == 1) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - if(type == 2) - changeNext_move(CLICK_CD_RANGE) - last_special = world.time + CLICK_CD_RANGE - cuff_resist(I) - - -/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) - if(I.item_flags & BEING_REMOVED) - to_chat(src, "You're already attempting to remove [I]!") - return - I.item_flags |= BEING_REMOVED - breakouttime = I.breakouttime - if(!cuff_break) - visible_message("[src] attempts to remove [I]!") - to_chat(src, "You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)") - if(do_after(src, breakouttime, 0, target = src)) - clear_cuffs(I, cuff_break) - else - to_chat(src, "You fail to remove [I]!") - - else if(cuff_break == FAST_CUFFBREAK) - breakouttime = 50 - visible_message("[src] is trying to break [I]!") - to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)") - if(do_after(src, breakouttime, 0, target = src)) - clear_cuffs(I, cuff_break) - else - to_chat(src, "You fail to break [I]!") - - else if(cuff_break == INSTANT_CUFFBREAK) - clear_cuffs(I, cuff_break) - I.item_flags &= ~BEING_REMOVED - -/mob/living/carbon/proc/uncuff() - if (handcuffed) - var/obj/item/W = handcuffed - handcuffed = null - if (buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - if (legcuffed) - var/obj/item/W = legcuffed - legcuffed = null - update_inv_legcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - -/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) - if(!I.loc || buckled) - return - visible_message("[src] manages to [cuff_break ? "break" : "remove"] [I]!") - to_chat(src, "You successfully [cuff_break ? "break" : "remove"] [I].") - - if(cuff_break) - . = !((I == handcuffed) || (I == legcuffed)) - qdel(I) - return - - else - if(I == handcuffed) - handcuffed.forceMove(drop_location()) - handcuffed.dropped(src) - handcuffed = null - if(buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - return - if(I == legcuffed) - legcuffed.forceMove(drop_location()) - legcuffed.dropped() - legcuffed = null - update_inv_legcuffed() - return - else - dropItemToGround(I) - return - return TRUE - -/mob/living/carbon/get_standard_pixel_y_offset(lying = 0) - if(lying) - return -6 - else - return initial(pixel_y) - -/mob/living/carbon/proc/accident(obj/item/I) - if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP)) - return - - //dropItemToGround(I) CIT CHANGE - makes it so the item doesn't drop if the modifier rolls above 100 - - var/modifier = 0 - - if(HAS_TRAIT(src, TRAIT_CLUMSY)) - modifier -= 40 //Clumsy people are more likely to hit themselves -Honk! - - //CIT CHANGES START HERE - else if(combatmode) - modifier += 50 - - if(modifier < 100) - dropItemToGround(I) - //END OF CIT CHANGES - - switch(rand(1,100)+modifier) //91-100=Nothing special happens - if(-INFINITY to 0) //attack yourself - I.attack(src,src) - if(1 to 30) //throw it at yourself - I.throw_impact(src) - if(31 to 60) //Throw object in facing direction - var/turf/target = get_turf(loc) - var/range = rand(2,I.throw_range) - for(var/i = 1; i < range; i++) - var/turf/new_turf = get_step(target, dir) - target = new_turf - if(new_turf.density) - break - I.throw_at(target,I.throw_range,I.throw_speed,src) - if(61 to 90) //throw it down to the floor - var/turf/target = get_turf(loc) - I.throw_at(target,I.throw_range,I.throw_speed,src) - -/mob/living/carbon/Stat() - ..() - if(statpanel("Status")) - var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) - if(vessel) - stat(null, "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]") - if(locate(/obj/item/assembly/health) in src) - stat(null, "Health: [health]") - - add_abilities_to_panel() - -/mob/living/carbon/attack_ui(slot) - if(!has_hand_for_held_index(active_hand_index)) - return 0 - return ..() - -/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, toxic = FALSE) - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return 1 - - if(nutrition < 100 && !blood) - if(message) - visible_message("[src] dry heaves!", \ - "You try to throw up, but there's nothing in your stomach!") - if(stun) - Knockdown(200) - return 1 - - if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious - if(message) - visible_message("[src] throws up all over [p_them()]self!", \ - "You throw up all over yourself!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself) - distance = 0 - else - if(message) - visible_message("[src] throws up!", "You throw up!") - if(!isflyperson(src)) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit) - if(stun) - Stun(80) - - playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1) - var/turf/T = get_turf(src) - if(!blood) - nutrition -= lost_nutrition - adjustToxLoss(-3) - for(var/i=0 to distance) - if(blood) - if(T) - add_splatter_floor(T) - if(stun) - adjustBruteLoss(3) - else if(src.reagents.has_reagent("blazaam")) - if(T) - T.add_vomit_floor(src, VOMIT_PURPLE) - else - if(T) - T.add_vomit_floor(src, VOMIT_TOXIC)//toxic barf looks different - T = get_step(T, dir) - if (is_blocked_turf(T)) - break - return 1 - -/mob/living/carbon/proc/spew_organ(power = 5, amt = 1) - for(var/i in 1 to amt) - if(!internal_organs.len) - break //Guess we're out of organs! - var/obj/item/organ/guts = pick(internal_organs) - var/turf/T = get_turf(src) - guts.Remove(src) - guts.forceMove(T) - var/atom/throw_target = get_edge_target_turf(guts, dir) - guts.throw_at(throw_target, power, 4, src) - - -/mob/living/carbon/fully_replace_character_name(oldname,newname) - ..() - if(dna) - dna.real_name = real_name - -//Updates the mob's health from bodyparts and mob damage variables -/mob/living/carbon/updatehealth() - if(status_flags & GODMODE) - return - var/total_burn = 0 - var/total_brute = 0 - var/total_stamina = 0 - for(var/X in bodyparts) //hardcoded to streamline things a bit - var/obj/item/bodypart/BP = X - total_brute += (BP.brute_dam * BP.body_damage_coeff) - total_burn += (BP.burn_dam * BP.body_damage_coeff) - total_stamina += (BP.stamina_dam * BP.stam_damage_coeff) - health = round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION) - staminaloss = round(total_stamina, DAMAGE_PRECISION) - update_stat() - if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD ) - become_husk("burn") - med_hud_set_health() - if(stat == SOFT_CRIT) - add_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE, multiplicative_slowdown = SOFTCRIT_ADD_SLOWDOWN) - else - remove_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE) - -/mob/living/carbon/update_stamina() - var/stam = getStaminaLoss() - if(stam > DAMAGE_PRECISION) - var/total_health = (health - stam) - if(total_health <= crit_threshold && !stat) - if(!IsKnockdown()) - to_chat(src, "You're too exhausted to keep going...") - Knockdown(100) - update_health_hud() - -/mob/living/carbon/update_sight() - if(!client) - return - if(stat == DEAD) - sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_OBSERVER - return - - sight = initial(sight) - lighting_alpha = initial(lighting_alpha) - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(!E) - update_tint() - else - see_invisible = E.see_invisible - see_in_dark = E.see_in_dark - sight |= E.sight_flags - if(!isnull(E.lighting_alpha)) - lighting_alpha = E.lighting_alpha - if(HAS_TRAIT(src, TRAIT_NIGHT_VISION)) - lighting_alpha = min(LIGHTING_PLANE_ALPHA_NV_TRAIT, lighting_alpha) - see_in_dark = max(NIGHT_VISION_DARKSIGHT_RANGE, see_in_dark) - - if(client.eye && client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - - if(glasses) - var/obj/item/clothing/glasses/G = glasses - sight |= G.vision_flags - see_in_dark = max(G.darkness_view, see_in_dark) - if(G.invis_override) - see_invisible = G.invis_override - else - see_invisible = min(G.invis_view, see_invisible) - if(!isnull(G.lighting_alpha)) - lighting_alpha = min(lighting_alpha, G.lighting_alpha) - if(dna) - for(var/X in dna.mutations) - var/datum/mutation/M = X - if(M.name == XRAY) - sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = max(see_in_dark, 8) - - if(see_override) - see_invisible = see_override - . = ..() - - -//to recalculate and update the mob's total tint from tinted equipment it's wearing. -/mob/living/carbon/proc/update_tint() - if(!GLOB.tinted_weldhelh) - return - tinttotal = get_total_tint() - if(tinttotal >= TINT_BLIND) - become_blind(EYES_COVERED) - else if(tinttotal >= TINT_DARKENED) - cure_blind(EYES_COVERED) - overlay_fullscreen("tint", /obj/screen/fullscreen/impaired, 2) - else - cure_blind(EYES_COVERED) - clear_fullscreen("tint", 0) - -/mob/living/carbon/proc/get_total_tint() - . = 0 - if(istype(head, /obj/item/clothing/head)) - var/obj/item/clothing/head/HT = head - . += HT.tint - if(wear_mask) - . += wear_mask.tint - - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(E) - . += E.tint - - else - . += INFINITY - -/mob/living/carbon/get_permeability_protection(list/target_zones = list(HANDS,CHEST,GROIN,LEGS,FEET,ARMS,HEAD)) - var/list/tally = list() - for(var/obj/item/I in get_equipped_items()) - for(var/zone in target_zones) - if(I.body_parts_covered & zone) - tally["[zone]"] = max(1 - I.permeability_coefficient, target_zones["[zone]"]) - var/protection = 0 - for(var/key in tally) - protection += tally[key] - protection *= INVERSE(target_zones.len) - return protection - -//this handles hud updates -/mob/living/carbon/update_damage_hud() - - if(!client) - return - - if(health <= crit_threshold) - var/severity = 0 - switch(health) - if(-20 to -10) - severity = 1 - if(-30 to -20) - severity = 2 - if(-40 to -30) - severity = 3 - if(-50 to -40) - severity = 4 - if(-50 to -40) - severity = 5 - if(-60 to -50) - severity = 6 - if(-70 to -60) - severity = 7 - if(-90 to -70) - severity = 8 - if(-95 to -90) - severity = 9 - if(-INFINITY to -95) - severity = 10 - if(!InFullCritical()) - var/visionseverity = 4 - switch(health) - if(-8 to -4) - visionseverity = 5 - if(-12 to -8) - visionseverity = 6 - if(-16 to -12) - visionseverity = 7 - if(-20 to -16) - visionseverity = 8 - if(-24 to -20) - visionseverity = 9 - if(-INFINITY to -24) - visionseverity = 10 - overlay_fullscreen("critvision", /obj/screen/fullscreen/crit/vision, visionseverity) - else - clear_fullscreen("critvision") - overlay_fullscreen("crit", /obj/screen/fullscreen/crit, severity) - else - clear_fullscreen("crit") - clear_fullscreen("critvision") - - //Oxygen damage overlay - var/windedup = getOxyLoss() + getStaminaLoss() * 0.2 - if(windedup) - var/severity = 0 - switch(windedup) - if(10 to 20) - severity = 1 - if(20 to 25) - severity = 2 - if(25 to 30) - severity = 3 - if(30 to 35) - severity = 4 - if(35 to 40) - severity = 5 - if(40 to 45) - severity = 6 - if(45 to INFINITY) - severity = 7 - overlay_fullscreen("oxy", /obj/screen/fullscreen/oxy, severity) - else - clear_fullscreen("oxy") - - //Fire and Brute damage overlay (BSSR) - var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp - if(hurtdamage) - var/severity = 0 - switch(hurtdamage) - if(5 to 15) - severity = 1 - if(15 to 30) - severity = 2 - if(30 to 45) - severity = 3 - if(45 to 70) - severity = 4 - if(70 to 85) - severity = 5 - if(85 to INFINITY) - severity = 6 - overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity) - else - clear_fullscreen("brute") - -/mob/living/carbon/update_health_hud(shown_health_amount) - if(!client || !hud_used) - return - if(hud_used.healths) - if(stat != DEAD) - . = 1 - if(!shown_health_amount) - shown_health_amount = health - if(shown_health_amount >= maxHealth) - hud_used.healths.icon_state = "health0" - else if(shown_health_amount > maxHealth*0.8) - hud_used.healths.icon_state = "health1" - else if(shown_health_amount > maxHealth*0.6) - hud_used.healths.icon_state = "health2" - else if(shown_health_amount > maxHealth*0.4) - hud_used.healths.icon_state = "health3" - else if(shown_health_amount > maxHealth*0.2) - hud_used.healths.icon_state = "health4" - else if(shown_health_amount > 0) - hud_used.healths.icon_state = "health5" - else - hud_used.healths.icon_state = "health6" - else - hud_used.healths.icon_state = "health7" - -/mob/living/carbon/proc/update_internals_hud_icon(internal_state = 0) - if(hud_used && hud_used.internals) - hud_used.internals.icon_state = "internal[internal_state]" - -/mob/living/carbon/update_stat() - if(status_flags & GODMODE) - return - if(stat != DEAD) - if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH)) - death() - return - if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))) - stat = UNCONSCIOUS - blind_eyes(1) - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - else - if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) - stat = SOFT_CRIT - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - else - stat = CONSCIOUS - adjust_blindness(-1) - update_canmove() - update_damage_hud() - update_health_hud() - med_hud_set_status() - -//called when we get cuffed/uncuffed -/mob/living/carbon/proc/update_handcuffed() - if(handcuffed) - drop_all_held_items() - stop_pulling() - throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed) - if(handcuffed.demoralize_criminals) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed) - else - clear_alert("handcuffed") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed") - update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. - update_inv_handcuffed() - update_hud_handcuffed() - -/mob/living/carbon/fully_heal(admin_revive = FALSE) - if(reagents) - reagents.clear_reagents() - var/obj/item/organ/brain/B = getorgan(/obj/item/organ/brain) - if(B) - B.brain_death = FALSE - for(var/thing in diseases) - var/datum/disease/D = thing - if(D.severity != DISEASE_SEVERITY_POSITIVE) - D.cure(FALSE) - if(admin_revive) - regenerate_limbs() - regenerate_organs() - handcuffed = initial(handcuffed) - for(var/obj/item/restraints/R in contents) //actually remove cuffs from inventory - qdel(R) - update_handcuffed() - if(reagents) - reagents.addiction_list = list() - cure_all_traumas(TRAUMA_RESILIENCE_MAGIC) - ..() - // heal ears after healing traits, since ears check TRAIT_DEAF trait - // when healing. - restoreEars() - -/mob/living/carbon/can_be_revived() - . = ..() - if(!getorgan(/obj/item/organ/brain) && (!mind || !mind.has_antag_datum(/datum/antagonist/changeling))) - return 0 - -/mob/living/carbon/harvest(mob/living/user) - if(QDELETED(src)) - return - var/organs_amt = 0 - for(var/X in internal_organs) - var/obj/item/organ/O = X - if(prob(50)) - organs_amt++ - O.Remove(src) - O.forceMove(drop_location()) - if(organs_amt) - to_chat(user, "You retrieve some of [src]\'s internal organs!") - -/mob/living/carbon/ExtinguishMob() - for(var/X in get_equipped_items()) - var/obj/item/I = X - I.acid_level = 0 //washes off the acid on our clothes - I.extinguish() //extinguishes our clothes - ..() - -/mob/living/carbon/fakefire(var/fire_icon = "Generic_mob_burning") - var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) - new_fire_overlay.appearance_flags = RESET_COLOR - overlays_standing[FIRE_LAYER] = new_fire_overlay - apply_overlay(FIRE_LAYER) - -/mob/living/carbon/fakefireextinguish() - remove_overlay(FIRE_LAYER) - - -/mob/living/carbon/proc/devour_mob(mob/living/carbon/C, devour_time = 130) - C.visible_message("[src] is attempting to devour [C]!", \ - "[src] is attempting to devour you!") - if(!do_mob(src, C, devour_time)) - return - if(pulling && pulling == C && grab_state >= GRAB_AGGRESSIVE && a_intent == INTENT_GRAB) - C.visible_message("[src] devours [C]!", \ - "[src] devours you!") - C.forceMove(src) - stomach_contents.Add(C) - log_combat(src, C, "devoured") - -/mob/living/carbon/proc/create_bodyparts() - var/l_arm_index_next = -1 - var/r_arm_index_next = 0 - for(var/X in bodyparts) - var/obj/item/bodypart/O = new X() - O.owner = src - bodyparts.Remove(X) - bodyparts.Add(O) - if(O.body_part == ARM_LEFT) - l_arm_index_next += 2 - O.held_index = l_arm_index_next //1, 3, 5, 7... - hand_bodyparts += O - else if(O.body_part == ARM_RIGHT) - r_arm_index_next += 2 - O.held_index = r_arm_index_next //2, 4, 6, 8... - hand_bodyparts += O - -/mob/living/carbon/do_after_coefficent() - . = ..() - var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) //Currently, only carbons or higher use mood, move this once that changes. - if(mood) - switch(mood.sanity) //Alters do_after delay based on how sane you are - if(SANITY_INSANE to SANITY_DISTURBED) - . *= 1.25 - if(SANITY_NEUTRAL to SANITY_GREAT) - . *= 0.90 - - -/mob/living/carbon/proc/create_internal_organs() - for(var/X in internal_organs) - var/obj/item/organ/I = X - I.Insert(src) - -/mob/living/carbon/proc/update_disabled_bodyparts() - for(var/B in bodyparts) - var/obj/item/bodypart/BP = B - BP.update_disabled() - -/mob/living/carbon/vv_get_dropdown() - . = ..() - . += "---" - .["Make AI"] = "?_src_=vars;[HrefToken()];makeai=[REF(src)]" - .["Modify bodypart"] = "?_src_=vars;[HrefToken()];editbodypart=[REF(src)]" - .["Modify organs"] = "?_src_=vars;[HrefToken()];editorgans=[REF(src)]" - .["Hallucinate"] = "?_src_=vars;[HrefToken()];hallucinate=[REF(src)]" - .["Give martial arts"] = "?_src_=vars;[HrefToken()];givemartialart=[REF(src)]" - .["Give brain trauma"] = "?_src_=vars;[HrefToken()];givetrauma=[REF(src)]" - .["Cure brain traumas"] = "?_src_=vars;[HrefToken()];curetraumas=[REF(src)]" - -/mob/living/carbon/can_resist() - return bodyparts.len > 2 && ..() - -/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - return FALSE - if(hallucinating()) - return TRUE - if(IsSleeping()) - return TRUE - if(HAS_TRAIT(src, TRAIT_DUMB)) - return TRUE - var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) - if(mood) - if(mood.sanity < SANITY_UNSTABLE) - return TRUE - -/mob/living/carbon/transfer_ckey(mob/new_mob, send_signal = TRUE) - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - return ..() - -/mob/living/carbon/can_see_reagents() - . = ..() - if(.) //No need to run through all of this if it's already true. - return - if(isclothing(head)) - var/obj/item/clothing/H = head - if(H.clothing_flags & SCAN_REAGENTS) - return TRUE - if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) - return TRUE +/mob/living/carbon + blood_volume = BLOOD_VOLUME_NORMAL + +/mob/living/carbon/Initialize() + . = ..() + create_reagents(1000) + update_body_parts() //to update the carbon's new bodyparts appearance + GLOB.carbon_list += src + blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio) + +/mob/living/carbon/Destroy() + //This must be done first, so the mob ghosts correctly before DNA etc is nulled + . = ..() + + QDEL_LIST(internal_organs) + QDEL_LIST(stomach_contents) + QDEL_LIST(bodyparts) + QDEL_LIST(implants) + remove_from_all_data_huds() + QDEL_NULL(dna) + GLOB.carbon_list -= src + +/mob/living/carbon/initialize_footstep() + AddComponent(/datum/component/footstep, 0.6, 2) + +/mob/living/carbon/relaymove(mob/user, direction) + if(user in src.stomach_contents) + if(prob(40)) + if(prob(25)) + audible_message("You hear something rumbling inside [src]'s stomach...", \ + "You hear something rumbling.", 4,\ + "Something is rumbling inside your stomach!") + var/obj/item/I = user.get_active_held_item() + if(I && I.force) + var/d = rand(round(I.force / 4), I.force) + var/obj/item/bodypart/BP = get_bodypart(BODY_ZONE_CHEST) + if(BP.receive_damage(d, 0)) + update_damage_overlays() + visible_message("[user] attacks [src]'s stomach wall with the [I.name]!", \ + "[user] attacks your stomach wall with the [I.name]!") + playsound(user.loc, 'sound/effects/attackblob.ogg', 50, 1) + + if(prob(src.getBruteLoss() - 50)) + for(var/atom/movable/A in stomach_contents) + A.forceMove(drop_location()) + stomach_contents.Remove(A) + src.gib() + + +/mob/living/carbon/swap_hand(held_index) + if(!held_index) + held_index = (active_hand_index % held_items.len)+1 + + var/obj/item/item_in_hand = src.get_active_held_item() + if(item_in_hand) //this segment checks if the item in your hand is twohanded. + var/obj/item/twohanded/TH = item_in_hand + if(istype(TH)) + if(TH.wielded == 1) + to_chat(usr, "Your other hand is too busy holding [TH]") + return + var/oindex = active_hand_index + active_hand_index = held_index + if(hud_used) + var/obj/screen/inventory/hand/H + H = hud_used.hand_slots["[oindex]"] + if(H) + H.update_icon() + H = hud_used.hand_slots["[held_index]"] + if(H) + H.update_icon() + + +/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len + if(!selhand) + selhand = (active_hand_index % held_items.len)+1 + + if(istext(selhand)) + selhand = lowertext(selhand) + if(selhand == "right" || selhand == "r") + selhand = 2 + if(selhand == "left" || selhand == "l") + selhand = 1 + + if(selhand != active_hand_index) + swap_hand(selhand) + else + mode() // Activate held item + +/mob/living/carbon/attackby(obj/item/I, mob/user, params) + if(lying && surgeries.len) + if(user != src && (user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)) + for(var/datum/surgery/S in surgeries) + if(S.next_step(user,user.a_intent)) + return 1 + return ..() + +/mob/living/carbon/throw_impact(atom/hit_atom, throwingdatum) + . = ..() + var/hurt = TRUE + if(istype(throwingdatum, /datum/thrownthing)) + var/datum/thrownthing/D = throwingdatum + if(iscyborg(D.thrower)) + var/mob/living/silicon/robot/R = D.thrower + if(!R.emagged) + hurt = FALSE + if(hit_atom.density && isturf(hit_atom)) + if(hurt) + Knockdown(20) + take_bodypart_damage(10) + if(iscarbon(hit_atom) && hit_atom != src) + var/mob/living/carbon/victim = hit_atom + if(victim.movement_type & FLYING) + return + if(hurt) + victim.take_bodypart_damage(10) + take_bodypart_damage(10) + victim.Knockdown(20) + Knockdown(20) + visible_message("[src] crashes into [victim], knocking them both over!",\ + "You violently crash into [victim]!") + playsound(src,'sound/weapons/punch1.ogg',50,1) + + +//Throwing stuff +/mob/living/carbon/proc/toggle_throw_mode() + if(stat) + return + if(in_throw_mode) + throw_mode_off() + else + throw_mode_on() + + +/mob/living/carbon/proc/throw_mode_off() + in_throw_mode = 0 + if(client && hud_used) + hud_used.throw_icon.icon_state = "act_throw_off" + + +/mob/living/carbon/proc/throw_mode_on() + in_throw_mode = 1 + if(client && hud_used) + hud_used.throw_icon.icon_state = "act_throw_on" + +/mob/proc/throw_item(atom/target) + SEND_SIGNAL(src, COMSIG_MOB_THROW, target) + return + +/mob/living/carbon/throw_item(atom/target) + throw_mode_off() + if(!target || !isturf(loc)) + return + if(istype(target, /obj/screen)) + return + + //CIT CHANGES - makes it impossible to throw while in stamina softcrit + if(getStaminaLoss() >= STAMINA_SOFTCRIT) + to_chat(src, "You're too exhausted.") + return + var/random_turn = a_intent == INTENT_HARM + //END OF CIT CHANGES + + var/obj/item/I = src.get_active_held_item() + + var/atom/movable/thrown_thing + var/mob/living/throwable_mob + + if(istype(I, /obj/item/clothing/head/mob_holder)) + var/obj/item/clothing/head/mob_holder/holder = I + if(holder.held_mob) + throwable_mob = holder.held_mob + holder.release() + + if(!I || throwable_mob) + if(!throwable_mob && pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) + throwable_mob = pulling + + if(throwable_mob && !throwable_mob.buckled) + thrown_thing = throwable_mob + if(pulling) + stop_pulling() + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(src, "You gently let go of [throwable_mob].") + return + + adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring + var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors + var/turf/end_T = get_turf(target) + if(start_T && end_T) + log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") + + else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP)) + thrown_thing = I + dropItemToGround(I) + + if(HAS_TRAIT(src, TRAIT_PACIFISM) && I.throwforce) + to_chat(src, "You set [I] down gently on the ground.") + return + + adjustStaminaLossBuffered(I.getweight()*2)//CIT CHANGE - throwing items shall be more tiring than swinging em. Doubly so. + + if(thrown_thing) + visible_message("[src] has thrown [thrown_thing].") + src.log_message("has thrown [thrown_thing]", LOG_ATTACK) + do_attack_animation(target, no_effect = 1) + playsound(loc, 'sound/weapons/punchmiss.ogg', 50, 1, -1) + newtonian_move(get_dir(target, src)) + thrown_thing.throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src, null, null, null, random_turn) + + + +/mob/living/carbon/restrained(ignore_grab) + . = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)) + +/mob/living/carbon/proc/canBeHandcuffed() + return 0 + + +/mob/living/carbon/show_inv(mob/user) + user.set_machine(src) + var/dat = {" +
    + [name] +
    +
    Head: [(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"] +
    Mask: [(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"] +
    Neck: [(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Nothing"]"} + + for(var/i in 1 to held_items.len) + var/obj/item/I = get_item_for_held_index(i) + dat += "
    [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]" + + dat += "
    Back: [back ? back : "Nothing"]" + + if(istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank)) + dat += "
    [internal ? "Disable Internals" : "Set Internals"]" + + if(handcuffed) + dat += "
    Handcuffed" + if(legcuffed) + dat += "
    Legcuffed" + + dat += {" +
    +
    Close + "} + user << browse(dat, "window=mob[REF(src)];size=325x500") + onclose(user, "mob[REF(src)]") + +/mob/living/carbon/Topic(href, href_list) + ..() + //strip panel + if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + if(href_list["internal"]) + var/slot = text2num(href_list["internal"]) + var/obj/item/ITEM = get_item_by_slot(slot) + if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) + visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \ + "[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].") + if(do_mob(usr, src, POCKET_STRIP_DELAY)) + if(internal) + internal = null + update_internals_hud_icon(0) + else if(ITEM && istype(ITEM, /obj/item/tank)) + if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + internal = ITEM + update_internals_hud_icon(1) + + visible_message("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].", \ + "[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].") + + +/mob/living/carbon/fall(forced) + loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing + +/mob/living/carbon/is_muzzled() + return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) + +/mob/living/carbon/hallucinating() + if(hallucination) + return TRUE + else + return FALSE + +/mob/living/carbon/resist_buckle() + if(restrained()) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + var/buckle_cd = 600 + if(handcuffed) + var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED) + buckle_cd = O.breakouttime + visible_message("[src] attempts to unbuckle [p_them()]self!", \ + "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)") + if(do_after(src, buckle_cd, 0, target = src)) + if(!buckled) + return + buckled.user_unbuckle_mob(src,src) + else + if(src && buckled) + to_chat(src, "You fail to unbuckle yourself!") + else + buckled.user_unbuckle_mob(src,src) + +/mob/living/carbon/resist_fire() + fire_stacks -= 5 + Knockdown(60, TRUE, TRUE) + spin(32,2) + visible_message("[src] rolls on the floor, trying to put [p_them()]self out!", \ + "You stop, drop, and roll!") + sleep(30) + if(fire_stacks <= 0) + visible_message("[src] has successfully extinguished [p_them()]self!", \ + "You extinguish yourself.") + ExtinguishMob() + return + +/mob/living/carbon/resist_restraints() + var/obj/item/I = null + var/type = 0 + if(handcuffed) + I = handcuffed + type = 1 + else if(legcuffed) + I = legcuffed + type = 2 + if(I) + if(type == 1) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + if(type == 2) + changeNext_move(CLICK_CD_RANGE) + last_special = world.time + CLICK_CD_RANGE + cuff_resist(I) + + +/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) + if(I.item_flags & BEING_REMOVED) + to_chat(src, "You're already attempting to remove [I]!") + return + I.item_flags |= BEING_REMOVED + breakouttime = I.breakouttime + if(!cuff_break) + visible_message("[src] attempts to remove [I]!") + to_chat(src, "You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)") + if(do_after(src, breakouttime, 0, target = src)) + clear_cuffs(I, cuff_break) + else + to_chat(src, "You fail to remove [I]!") + + else if(cuff_break == FAST_CUFFBREAK) + breakouttime = 50 + visible_message("[src] is trying to break [I]!") + to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)") + if(do_after(src, breakouttime, 0, target = src)) + clear_cuffs(I, cuff_break) + else + to_chat(src, "You fail to break [I]!") + + else if(cuff_break == INSTANT_CUFFBREAK) + clear_cuffs(I, cuff_break) + I.item_flags &= ~BEING_REMOVED + +/mob/living/carbon/proc/uncuff() + if (handcuffed) + var/obj/item/W = handcuffed + handcuffed = null + if (buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + if (client) + client.screen -= W + if (W) + W.forceMove(drop_location()) + W.dropped(src) + if (W) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + changeNext_move(0) + if (legcuffed) + var/obj/item/W = legcuffed + legcuffed = null + update_inv_legcuffed() + if (client) + client.screen -= W + if (W) + W.forceMove(drop_location()) + W.dropped(src) + if (W) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + changeNext_move(0) + +/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) + if(!I.loc || buckled) + return + visible_message("[src] manages to [cuff_break ? "break" : "remove"] [I]!") + to_chat(src, "You successfully [cuff_break ? "break" : "remove"] [I].") + + if(cuff_break) + . = !((I == handcuffed) || (I == legcuffed)) + qdel(I) + return + + else + if(I == handcuffed) + handcuffed.forceMove(drop_location()) + handcuffed.dropped(src) + handcuffed = null + if(buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + return + if(I == legcuffed) + legcuffed.forceMove(drop_location()) + legcuffed.dropped() + legcuffed = null + update_inv_legcuffed() + return + else + dropItemToGround(I) + return + return TRUE + +/mob/living/carbon/get_standard_pixel_y_offset(lying = 0) + if(lying) + return -6 + else + return initial(pixel_y) + +/mob/living/carbon/proc/accident(obj/item/I) + if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP)) + return + + //dropItemToGround(I) CIT CHANGE - makes it so the item doesn't drop if the modifier rolls above 100 + + var/modifier = 0 + + if(HAS_TRAIT(src, TRAIT_CLUMSY)) + modifier -= 40 //Clumsy people are more likely to hit themselves -Honk! + + //CIT CHANGES START HERE + else if(combatmode) + modifier += 50 + + if(modifier < 100) + dropItemToGround(I) + //END OF CIT CHANGES + + switch(rand(1,100)+modifier) //91-100=Nothing special happens + if(-INFINITY to 0) //attack yourself + I.attack(src,src) + if(1 to 30) //throw it at yourself + I.throw_impact(src) + if(31 to 60) //Throw object in facing direction + var/turf/target = get_turf(loc) + var/range = rand(2,I.throw_range) + for(var/i = 1; i < range; i++) + var/turf/new_turf = get_step(target, dir) + target = new_turf + if(new_turf.density) + break + I.throw_at(target,I.throw_range,I.throw_speed,src) + if(61 to 90) //throw it down to the floor + var/turf/target = get_turf(loc) + I.throw_at(target,I.throw_range,I.throw_speed,src) + +/mob/living/carbon/Stat() + ..() + if(statpanel("Status")) + var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) + if(vessel) + stat(null, "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]") + if(locate(/obj/item/assembly/health) in src) + stat(null, "Health: [health]") + + add_abilities_to_panel() + +/mob/living/carbon/attack_ui(slot) + if(!has_hand_for_held_index(active_hand_index)) + return 0 + return ..() + +/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, toxic = FALSE) + if(HAS_TRAIT(src, TRAIT_NOHUNGER)) + return 1 + + if(nutrition < 100 && !blood) + if(message) + visible_message("[src] dry heaves!", \ + "You try to throw up, but there's nothing in your stomach!") + if(stun) + Knockdown(200) + return 1 + + if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious + if(message) + visible_message("[src] throws up all over [p_them()]self!", \ + "You throw up all over yourself!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself) + distance = 0 + else + if(message) + visible_message("[src] throws up!", "You throw up!") + if(!isflyperson(src)) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit) + if(stun) + Stun(80) + + playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1) + var/turf/T = get_turf(src) + if(!blood) + nutrition -= lost_nutrition + adjustToxLoss(-3) + for(var/i=0 to distance) + if(blood) + if(T) + add_splatter_floor(T) + if(stun) + adjustBruteLoss(3) + else if(src.reagents.has_reagent("blazaam")) + if(T) + T.add_vomit_floor(src, VOMIT_PURPLE) + else + if(T) + T.add_vomit_floor(src, VOMIT_TOXIC)//toxic barf looks different + T = get_step(T, dir) + if (is_blocked_turf(T)) + break + return 1 + +/mob/living/carbon/proc/spew_organ(power = 5, amt = 1) + for(var/i in 1 to amt) + if(!internal_organs.len) + break //Guess we're out of organs! + var/obj/item/organ/guts = pick(internal_organs) + var/turf/T = get_turf(src) + guts.Remove(src) + guts.forceMove(T) + var/atom/throw_target = get_edge_target_turf(guts, dir) + guts.throw_at(throw_target, power, 4, src) + + +/mob/living/carbon/fully_replace_character_name(oldname,newname) + ..() + if(dna) + dna.real_name = real_name + +//Updates the mob's health from bodyparts and mob damage variables +/mob/living/carbon/updatehealth() + if(status_flags & GODMODE) + return + var/total_burn = 0 + var/total_brute = 0 + var/total_stamina = 0 + for(var/X in bodyparts) //hardcoded to streamline things a bit + var/obj/item/bodypart/BP = X + total_brute += (BP.brute_dam * BP.body_damage_coeff) + total_burn += (BP.burn_dam * BP.body_damage_coeff) + total_stamina += (BP.stamina_dam * BP.stam_damage_coeff) + health = round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION) + staminaloss = round(total_stamina, DAMAGE_PRECISION) + update_stat() + if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD ) + become_husk("burn") + med_hud_set_health() + if(stat == SOFT_CRIT) + add_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE, multiplicative_slowdown = SOFTCRIT_ADD_SLOWDOWN) + else + remove_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE) + +/mob/living/carbon/update_stamina() + var/stam = getStaminaLoss() + if(stam > DAMAGE_PRECISION) + var/total_health = (health - stam) + if(total_health <= crit_threshold && !stat) + if(!IsKnockdown()) + to_chat(src, "You're too exhausted to keep going...") + Knockdown(100) + update_health_hud() + +/mob/living/carbon/update_sight() + if(!client) + return + if(stat == DEAD) + sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_OBSERVER + return + + sight = initial(sight) + lighting_alpha = initial(lighting_alpha) + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(!E) + update_tint() + else + see_invisible = E.see_invisible + see_in_dark = E.see_in_dark + sight |= E.sight_flags + if(!isnull(E.lighting_alpha)) + lighting_alpha = E.lighting_alpha + if(HAS_TRAIT(src, TRAIT_NIGHT_VISION)) + lighting_alpha = min(LIGHTING_PLANE_ALPHA_NV_TRAIT, lighting_alpha) + see_in_dark = max(NIGHT_VISION_DARKSIGHT_RANGE, see_in_dark) + + if(client.eye && client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + + if(glasses) + var/obj/item/clothing/glasses/G = glasses + sight |= G.vision_flags + see_in_dark = max(G.darkness_view, see_in_dark) + if(G.invis_override) + see_invisible = G.invis_override + else + see_invisible = min(G.invis_view, see_invisible) + if(!isnull(G.lighting_alpha)) + lighting_alpha = min(lighting_alpha, G.lighting_alpha) + if(dna) + for(var/X in dna.mutations) + var/datum/mutation/M = X + if(M.name == XRAY) + sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = max(see_in_dark, 8) + + if(see_override) + see_invisible = see_override + . = ..() + + +//to recalculate and update the mob's total tint from tinted equipment it's wearing. +/mob/living/carbon/proc/update_tint() + if(!GLOB.tinted_weldhelh) + return + tinttotal = get_total_tint() + if(tinttotal >= TINT_BLIND) + become_blind(EYES_COVERED) + else if(tinttotal >= TINT_DARKENED) + cure_blind(EYES_COVERED) + overlay_fullscreen("tint", /obj/screen/fullscreen/impaired, 2) + else + cure_blind(EYES_COVERED) + clear_fullscreen("tint", 0) + +/mob/living/carbon/proc/get_total_tint() + . = 0 + if(istype(head, /obj/item/clothing/head)) + var/obj/item/clothing/head/HT = head + . += HT.tint + if(wear_mask) + . += wear_mask.tint + + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(E) + . += E.tint + + else + . += INFINITY + +/mob/living/carbon/get_permeability_protection(list/target_zones = list(HANDS,CHEST,GROIN,LEGS,FEET,ARMS,HEAD)) + var/list/tally = list() + for(var/obj/item/I in get_equipped_items()) + for(var/zone in target_zones) + if(I.body_parts_covered & zone) + tally["[zone]"] = max(1 - I.permeability_coefficient, target_zones["[zone]"]) + var/protection = 0 + for(var/key in tally) + protection += tally[key] + protection *= INVERSE(target_zones.len) + return protection + +//this handles hud updates +/mob/living/carbon/update_damage_hud() + + if(!client) + return + + if(health <= crit_threshold) + var/severity = 0 + switch(health) + if(-20 to -10) + severity = 1 + if(-30 to -20) + severity = 2 + if(-40 to -30) + severity = 3 + if(-50 to -40) + severity = 4 + if(-50 to -40) + severity = 5 + if(-60 to -50) + severity = 6 + if(-70 to -60) + severity = 7 + if(-90 to -70) + severity = 8 + if(-95 to -90) + severity = 9 + if(-INFINITY to -95) + severity = 10 + if(!InFullCritical()) + var/visionseverity = 4 + switch(health) + if(-8 to -4) + visionseverity = 5 + if(-12 to -8) + visionseverity = 6 + if(-16 to -12) + visionseverity = 7 + if(-20 to -16) + visionseverity = 8 + if(-24 to -20) + visionseverity = 9 + if(-INFINITY to -24) + visionseverity = 10 + overlay_fullscreen("critvision", /obj/screen/fullscreen/crit/vision, visionseverity) + else + clear_fullscreen("critvision") + overlay_fullscreen("crit", /obj/screen/fullscreen/crit, severity) + else + clear_fullscreen("crit") + clear_fullscreen("critvision") + + //Oxygen damage overlay + var/windedup = getOxyLoss() + getStaminaLoss() * 0.2 + if(windedup) + var/severity = 0 + switch(windedup) + if(10 to 20) + severity = 1 + if(20 to 25) + severity = 2 + if(25 to 30) + severity = 3 + if(30 to 35) + severity = 4 + if(35 to 40) + severity = 5 + if(40 to 45) + severity = 6 + if(45 to INFINITY) + severity = 7 + overlay_fullscreen("oxy", /obj/screen/fullscreen/oxy, severity) + else + clear_fullscreen("oxy") + + //Fire and Brute damage overlay (BSSR) + var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp + if(hurtdamage) + var/severity = 0 + switch(hurtdamage) + if(5 to 15) + severity = 1 + if(15 to 30) + severity = 2 + if(30 to 45) + severity = 3 + if(45 to 70) + severity = 4 + if(70 to 85) + severity = 5 + if(85 to INFINITY) + severity = 6 + overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity) + else + clear_fullscreen("brute") + +/mob/living/carbon/update_health_hud(shown_health_amount) + if(!client || !hud_used) + return + if(hud_used.healths) + if(stat != DEAD) + . = 1 + if(!shown_health_amount) + shown_health_amount = health + if(shown_health_amount >= maxHealth) + hud_used.healths.icon_state = "health0" + else if(shown_health_amount > maxHealth*0.8) + hud_used.healths.icon_state = "health1" + else if(shown_health_amount > maxHealth*0.6) + hud_used.healths.icon_state = "health2" + else if(shown_health_amount > maxHealth*0.4) + hud_used.healths.icon_state = "health3" + else if(shown_health_amount > maxHealth*0.2) + hud_used.healths.icon_state = "health4" + else if(shown_health_amount > 0) + hud_used.healths.icon_state = "health5" + else + hud_used.healths.icon_state = "health6" + else + hud_used.healths.icon_state = "health7" + +/mob/living/carbon/proc/update_internals_hud_icon(internal_state = 0) + if(hud_used && hud_used.internals) + hud_used.internals.icon_state = "internal[internal_state]" + +/mob/living/carbon/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH)) + death() + return + if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))) + stat = UNCONSCIOUS + blind_eyes(1) + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + else + if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) + stat = SOFT_CRIT + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + else + stat = CONSCIOUS + adjust_blindness(-1) + update_canmove() + update_damage_hud() + update_health_hud() + med_hud_set_status() + +//called when we get cuffed/uncuffed +/mob/living/carbon/proc/update_handcuffed() + if(handcuffed) + drop_all_held_items() + stop_pulling() + throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed) + if(handcuffed.demoralize_criminals) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed) + else + clear_alert("handcuffed") + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed") + update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. + update_inv_handcuffed() + update_hud_handcuffed() + +/mob/living/carbon/fully_heal(admin_revive = FALSE) + if(reagents) + reagents.clear_reagents() + var/obj/item/organ/brain/B = getorgan(/obj/item/organ/brain) + if(B) + B.brain_death = FALSE + for(var/thing in diseases) + var/datum/disease/D = thing + if(D.severity != DISEASE_SEVERITY_POSITIVE) + D.cure(FALSE) + if(admin_revive) + regenerate_limbs() + regenerate_organs() + handcuffed = initial(handcuffed) + for(var/obj/item/restraints/R in contents) //actually remove cuffs from inventory + qdel(R) + update_handcuffed() + if(reagents) + reagents.addiction_list = list() + cure_all_traumas(TRAUMA_RESILIENCE_MAGIC) + ..() + // heal ears after healing traits, since ears check TRAIT_DEAF trait + // when healing. + restoreEars() + +/mob/living/carbon/can_be_revived() + . = ..() + if(!getorgan(/obj/item/organ/brain) && (!mind || !mind.has_antag_datum(/datum/antagonist/changeling))) + return 0 + +/mob/living/carbon/harvest(mob/living/user) + if(QDELETED(src)) + return + var/organs_amt = 0 + for(var/X in internal_organs) + var/obj/item/organ/O = X + if(prob(50)) + organs_amt++ + O.Remove(src) + O.forceMove(drop_location()) + if(organs_amt) + to_chat(user, "You retrieve some of [src]\'s internal organs!") + +/mob/living/carbon/ExtinguishMob() + for(var/X in get_equipped_items()) + var/obj/item/I = X + I.acid_level = 0 //washes off the acid on our clothes + I.extinguish() //extinguishes our clothes + ..() + +/mob/living/carbon/fakefire(var/fire_icon = "Generic_mob_burning") + var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) + new_fire_overlay.appearance_flags = RESET_COLOR + overlays_standing[FIRE_LAYER] = new_fire_overlay + apply_overlay(FIRE_LAYER) + +/mob/living/carbon/fakefireextinguish() + remove_overlay(FIRE_LAYER) + + +/mob/living/carbon/proc/devour_mob(mob/living/carbon/C, devour_time = 130) + C.visible_message("[src] is attempting to devour [C]!", \ + "[src] is attempting to devour you!") + if(!do_mob(src, C, devour_time)) + return + if(pulling && pulling == C && grab_state >= GRAB_AGGRESSIVE && a_intent == INTENT_GRAB) + C.visible_message("[src] devours [C]!", \ + "[src] devours you!") + C.forceMove(src) + stomach_contents.Add(C) + log_combat(src, C, "devoured") + +/mob/living/carbon/proc/create_bodyparts() + var/l_arm_index_next = -1 + var/r_arm_index_next = 0 + for(var/X in bodyparts) + var/obj/item/bodypart/O = new X() + O.owner = src + bodyparts.Remove(X) + bodyparts.Add(O) + if(O.body_part == ARM_LEFT) + l_arm_index_next += 2 + O.held_index = l_arm_index_next //1, 3, 5, 7... + hand_bodyparts += O + else if(O.body_part == ARM_RIGHT) + r_arm_index_next += 2 + O.held_index = r_arm_index_next //2, 4, 6, 8... + hand_bodyparts += O + +/mob/living/carbon/do_after_coefficent() + . = ..() + var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) //Currently, only carbons or higher use mood, move this once that changes. + if(mood) + switch(mood.sanity) //Alters do_after delay based on how sane you are + if(SANITY_INSANE to SANITY_DISTURBED) + . *= 1.25 + if(SANITY_NEUTRAL to SANITY_GREAT) + . *= 0.90 + + +/mob/living/carbon/proc/create_internal_organs() + for(var/X in internal_organs) + var/obj/item/organ/I = X + I.Insert(src) + +/mob/living/carbon/proc/update_disabled_bodyparts() + for(var/B in bodyparts) + var/obj/item/bodypart/BP = B + BP.update_disabled() + +/mob/living/carbon/vv_get_dropdown() + . = ..() + . += "---" + .["Make AI"] = "?_src_=vars;[HrefToken()];makeai=[REF(src)]" + .["Modify bodypart"] = "?_src_=vars;[HrefToken()];editbodypart=[REF(src)]" + .["Modify organs"] = "?_src_=vars;[HrefToken()];editorgans=[REF(src)]" + .["Hallucinate"] = "?_src_=vars;[HrefToken()];hallucinate=[REF(src)]" + .["Give martial arts"] = "?_src_=vars;[HrefToken()];givemartialart=[REF(src)]" + .["Give brain trauma"] = "?_src_=vars;[HrefToken()];givetrauma=[REF(src)]" + .["Cure brain traumas"] = "?_src_=vars;[HrefToken()];curetraumas=[REF(src)]" + +/mob/living/carbon/can_resist() + return bodyparts.len > 2 && ..() + +/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) + return FALSE + if(hallucinating()) + return TRUE + if(IsSleeping()) + return TRUE + if(HAS_TRAIT(src, TRAIT_DUMB)) + return TRUE + var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) + if(mood) + if(mood.sanity < SANITY_UNSTABLE) + return TRUE + +/mob/living/carbon/transfer_ckey(mob/new_mob, send_signal = TRUE) + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + return ..() + +/mob/living/carbon/can_see_reagents() + . = ..() + if(.) //No need to run through all of this if it's already true. + return + if(isclothing(head)) + var/obj/item/clothing/H = head + if(H.clothing_flags & SCAN_REAGENTS) + return TRUE + if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) + return TRUE diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 06575aa802..ba40ae8e5f 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -48,41 +48,42 @@ if(affecting && affecting.dismemberable && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) affecting.dismember(P.damtype) -/mob/living/carbon/proc/can_catch_item(skip_throw_mode_check) - . = FALSE - if(mind) - if(mind.martial_art && mind.martial_art.dodge_chance == 100) - return TRUE - if(!skip_throw_mode_check && !in_throw_mode) +/mob/living/carbon/catch_item(obj/item/I, skip_throw_mode_check = FALSE) + . = ..() + if(!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) && !skip_throw_mode_check && !in_throw_mode) return - if(get_active_held_item()) + if(get_active_held_item() || restrained()) return - if(restrained()) - return - return TRUE - -/mob/living/carbon/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE) - if(!skipcatch) //ugly, but easy - if(can_catch_item()) - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if (mind) - if (mind.martial_art && mind.martial_art.dodge_chance == 100) //autocatch for rising bass - if (get_active_held_item()) - visible_message("[I] falls to the ground as [src] chops it out of the air!") - return 1 - if(!in_throw_mode) - throw_mode_on() - if(isturf(I.loc)) - I.attack_hand(src) - if(get_active_held_item() == I) //if our attack_hand() picks up the item... - visible_message("[src] catches [I]!") //catch that sucker! - throw_mode_off() - return 1 - ..() + I.attack_hand(src) + if(get_active_held_item() == I) //if our attack_hand() picks up the item... + visible_message("[src] catches [I]!") //catch that sucker! + throw_mode_off() + return TRUE +/mob/living/carbon/embed_item(obj/item/I) + throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) + var/obj/item/bodypart/L = pick(bodyparts) + L.embedded_objects |= I + I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! + I.forceMove(src) + L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier) + visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) /mob/living/carbon/attacked_by(obj/item/I, mob/living/user) + //CIT CHANGES START HERE - combatmode and resting checks + var/totitemdamage = I.force + if(iscarbon(user)) + var/mob/living/carbon/tempcarb = user + if(!tempcarb.combatmode) + totitemdamage *= 0.5 + if(user.resting) + totitemdamage *= 0.5 + if(!combatmode) + totitemdamage *= 1.5 + //CIT CHANGES END HERE + if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + return FALSE var/obj/item/bodypart/affecting if(user == src) affecting = get_bodypart(check_zone(user.zone_selected)) //we're self-mutilating! yay! @@ -93,17 +94,6 @@ SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) send_item_attack_message(I, user, affecting.name) if(I.force) - //CIT CHANGES START HERE - combatmode and resting checks - var/totitemdamage = I.force - if(iscarbon(user)) - var/mob/living/carbon/tempcarb = user - if(!tempcarb.combatmode) - totitemdamage *= 0.5 - if(user.resting) - totitemdamage *= 0.5 - if(!combatmode) - totitemdamage *= 1.5 - //CIT CHANGES END HERE apply_damage(totitemdamage, I.damtype, affecting) //CIT CHANGE - replaces I.force with totitemdamage if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) var/basebloodychance = affecting.brute_dam + totitemdamage @@ -137,7 +127,9 @@ //ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/carbon/attack_hand(mob/living/carbon/human/user) - + . = ..() + if(.) //was the attack blocked? + return for(var/thing in diseases) var/datum/disease/D = thing if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) @@ -152,8 +144,7 @@ if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM) for(var/datum/surgery/S in surgeries) if(S.next_step(user, user.a_intent)) - return 1 - return 0 + return TRUE /mob/living/carbon/attack_paw(mob/living/carbon/monkey/M) @@ -173,7 +164,8 @@ help_shake_act(M) return 0 - if(..()) //successful monkey bite. + . = ..() + if(.) //successful monkey bite. for(var/thing in M.diseases) var/datum/disease/D = thing ForceContractDisease(D) @@ -181,26 +173,27 @@ /mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - if(M.powerlevel > 0) - var/stunprob = M.powerlevel * 7 + 10 // 17 at level 1, 80 at level 10 - if(prob(stunprob)) - M.powerlevel -= 3 - if(M.powerlevel < 0) - M.powerlevel = 0 + . = ..() + if(!.) + return + if(M.powerlevel > 0) + var/stunprob = M.powerlevel * 7 + 10 // 17 at level 1, 80 at level 10 + if(prob(stunprob)) + M.powerlevel -= 3 + if(M.powerlevel < 0) + M.powerlevel = 0 - visible_message("The [M.name] has shocked [src]!", \ - "The [M.name] has shocked [src]!") + visible_message("The [M.name] has shocked [src]!", \ + "The [M.name] has shocked [src]!") - do_sparks(5, TRUE, src) - var/power = M.powerlevel + rand(0,3) - Knockdown(power*20) - if(stuttering < power) - stuttering = power - if (prob(stunprob) && M.powerlevel >= 8) - adjustFireLoss(M.powerlevel * rand(6,10)) - updatehealth() - return 1 + do_sparks(5, TRUE, src) + var/power = M.powerlevel + rand(0,3) + Knockdown(power*20) + if(stuttering < power) + stuttering = power + if (prob(stunprob) && M.powerlevel >= 8) + adjustFireLoss(M.powerlevel * rand(6,10)) + updatehealth() /mob/living/carbon/proc/dismembering_strike(mob/living/attacker, dam_zone) if(!attacker.limb_destroyer) @@ -332,12 +325,12 @@ else return - + else if(check_zone(M.zone_selected) == "r_arm" || check_zone(M.zone_selected) == "l_arm") M.visible_message( \ "[M] shakes [src]'s hand.", \ "You shake [src]'s hand.", ) - + else M.visible_message("[M] hugs [src] to make [p_them()] feel better!", \ "You hug [src] to make [p_them()] feel better!") diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 165e1fdd31..2b1d6afc4f 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -67,66 +67,36 @@ P.setAngle(rand(0, 360))//SHING return FALSE - if(!(P.original == src && P.firer == src)) //can't block or reflect when shooting yourself - if(P.is_reflectable) - if(check_reflect(def_zone)) // Checks if you've passed a reflection% check - visible_message("The [P.name] gets reflected by [src]!", \ - "The [P.name] gets reflected by [src]!") - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(src) + return ..() - // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x - var/new_angle_s = P.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - P.setAngle(new_angle_s) +/mob/living/carbon/human/check_reflect(def_zone) + if(wear_suit?.IsReflect(def_zone)) + return TRUE + return ..() - return -1 // complete projectile permutation - - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) - P.on_hit(src, 100, def_zone) - return 2 - - return (..(P , def_zone)) - -/mob/living/carbon/human/proc/check_reflect(def_zone) //Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance of the object - if(wear_suit) - if(wear_suit.IsReflect(def_zone) == 1) - return 1 - for(var/obj/item/I in held_items) - if(I.IsReflect(def_zone) == 1) - return 1 - return 0 - -/mob/living/carbon/human/proc/check_shields(atom/AM, var/damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) +/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + . = ..() + if(.) + return var/block_chance_modifier = round(damage / -3) - - for(var/obj/item/I in held_items) - if(!istype(I, /obj/item/clothing)) - var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example - if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 if(wear_suit) var/final_block_chance = wear_suit.block_chance - (CLAMP((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 + return TRUE if(w_uniform) var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 + return TRUE if(wear_neck) var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return 1 - return 0 + return TRUE + return FALSE + +/mob/living/carbon/human/can_embed(obj/item/I) + if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) + return TRUE + return FALSE /mob/living/carbon/human/proc/check_block() if(mind) @@ -135,39 +105,7 @@ return FALSE /mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE) - if(dna && dna.species) - var/spec_return = dna.species.spec_hitby(AM, src) - if(spec_return) - return spec_return - var/obj/item/I - var/throwpower = 30 - if(istype(AM, /obj/item)) - I = AM - throwpower = I.throwforce - if(I.thrownby == src) //No throwing stuff at yourself to trigger hit reactions - return ..() - if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) - hitpush = FALSE - skipcatch = TRUE - blocked = TRUE - else if(I) - if(I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && !(mind.martial_art && mind.martial_art.dodge_chance == 100)) - if(can_embed(I)) - if(prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE)) - throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) - var/obj/item/bodypart/L = pick(bodyparts) - L.embedded_objects |= I - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - I.forceMove(src) - L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier) - visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item - if (mind) - if (mind.martial_art && mind.martial_art.dodge_chance == 100) - skipcatch = FALSE - return ..() + return dna?.species?.spec_hitby(AM, src) || ..() /mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (HAS_TRAIT(src, TRAIT_FAT)) && ismonkey(pulling)) @@ -201,12 +139,12 @@ return dna.species.spec_attacked_by(I, user, affecting, a_intent, src) -/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) - var/hulk_verb = pick("smash","pummel") - if(check_shields(user, 15, "the [hulk_verb]ing")) + . = ..(user, TRUE) + if(.) return - ..(user, 1) + var/hulk_verb = pick("smash","pummel") playsound(loc, user.dna.species.attack_sound, 25, 1, -1) var/message = "[user] has [hulk_verb]ed [src]!" visible_message("[message]", \ @@ -215,7 +153,8 @@ return 1 /mob/living/carbon/human/attack_hand(mob/user) - if(..()) //to allow surgery to return properly. + . = ..() + if(.) //To allow surgery to return properly. return if(ishuman(user)) var/mob/living/carbon/human/H = user @@ -227,8 +166,7 @@ if(!affecting) affecting = get_bodypart(BODY_ZONE_CHEST) if(M.a_intent == INTENT_HELP) - ..() //shaking - return 0 + return ..() //shaking if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stunned instead. var/obj/item/I = get_active_held_item() @@ -249,78 +187,69 @@ if(can_inject(M, 1, affecting))//Thick suits can stop monkey bites. if(..()) //successful monkey bite, this handles disease contraction. var/damage = rand(1, 3) - if(check_shields(M, damage, "the [M.name]")) - return 0 - if(stat != DEAD) - apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) + apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) return 1 /mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) - if(check_shields(M, 0, "the M.name")) - visible_message("[M] attempted to touch [src]!") - return 0 + . = ..() + if(!.) + return + if(M.a_intent == INTENT_HARM) + if (w_uniform) + w_uniform.add_fingerprint(M) + var/damage = prob(90) ? 20 : 0 + if(!damage) + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) + visible_message("[M] has lunged at [src]!", \ + "[M] has lunged at [src]!") + return 0 + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee", null, null,10) - if(..()) - if(M.a_intent == INTENT_HARM) - if (w_uniform) - w_uniform.add_fingerprint(M) - var/damage = prob(90) ? 20 : 0 - if(!damage) - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) - visible_message("[M] has lunged at [src]!", \ - "[M] has lunged at [src]!") - return 0 - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee", null, null,10) + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + visible_message("[M] has slashed at [src]!", \ + "[M] has slashed at [src]!") + log_combat(M, src, "attacked") + if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful + return 1 + apply_damage(damage, BRUTE, affecting, armor_block) - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - visible_message("[M] has slashed at [src]!", \ - "[M] has slashed at [src]!") - log_combat(M, src, "attacked") - if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful - return 1 - apply_damage(damage, BRUTE, affecting, armor_block) - - if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. - var/obj/item/I = get_active_held_item() - if(I && dropItemToGround(I)) - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - visible_message("[M] disarmed [src]!", \ - "[M] disarmed [src]!") + if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. + var/obj/item/I = get_active_held_item() + if(I && dropItemToGround(I)) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + visible_message("[M] disarmed [src]!", \ + "[M] disarmed [src]!") + else + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + if(!lying) //CITADEL EDIT + Knockdown(100, TRUE, FALSE, 30, 25) else - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - if(!lying) //CITADEL EDIT - Knockdown(100, TRUE, FALSE, 30, 25) - else - Knockdown(100) - log_combat(M, src, "tackled") - visible_message("[M] has tackled down [src]!", \ - "[M] has tackled down [src]!") - + Knockdown(100) + log_combat(M, src, "tackled") + visible_message("[M] has tackled down [src]!", \ + "[M] has tackled down [src]!") /mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) - - if(..()) //successful larva bite. - var/damage = rand(1, 3) - if(check_shields(L, damage, "the [L.name]")) - return 0 - if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) + . = ..() + if(!.) //unsuccessful larva bite. + return + var/damage = rand(1, 3) + if(stat != DEAD) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) /mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) . = ..() if(.) var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - if(check_shields(M, damage, "the [M.name]", MELEE_ATTACK, M.armour_penetration)) - return FALSE var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) if(!dam_zone) //Dismemberment successful return TRUE @@ -332,23 +261,22 @@ /mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - var/damage = rand(5, 25) - if(M.is_adult) - damage = rand(10, 35) + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 25) + if(M.is_adult) + damage = rand(10, 35) - if(check_shields(M, damage, "the [M.name]")) - return 0 + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return 1 - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return 1 - - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) /mob/living/carbon/human/mech_melee_attack(obj/mecha/M) if(M.occupant.a_intent == INTENT_HARM) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 6f574bdc12..2e069297b3 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -545,6 +545,20 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) //Underwear, Undershirts & Socks if(!(NO_UNDERWEAR in species_traits)) + + if(H.socks && H.get_num_legs(FALSE) >= 2) + if(H.hidden_socks) + H.socks = "Nude" + else + H.socks = H.saved_socks + var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[H.socks] + if(S) + var/digilegs = ((DIGITIGRADE in species_traits) && S.has_digitigrade) ? "_d" : "" + var/mutable_appearance/MA = mutable_appearance(S.icon, "[S.icon_state][digilegs]", -BODY_LAYER) + if(S.has_color) + MA.color = "#[H.socks_color]" + standing += MA + if(H.underwear) if(H.hidden_underwear) H.underwear = "Nude" @@ -552,8 +566,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) H.underwear = H.saved_underwear var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[H.underwear] if(B) - var/mutable_appearance/MA = mutable_appearance(B.icon, B.icon_state, -BODY_LAYER) - if(UNDIE_COLORABLE(B)) + var/digilegs = ((DIGITIGRADE in species_traits) && B.has_digitigrade) ? "_d" : "" + var/mutable_appearance/MA = mutable_appearance(B.icon, "[B.icon_state][digilegs]", -BODY_LAYER) + if(B.has_color) MA.color = "#[H.undie_color]" standing += MA @@ -564,28 +579,16 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) H.undershirt = H.saved_undershirt var/datum/sprite_accessory/underwear/top/T = GLOB.undershirt_list[H.undershirt] if(T) + var/state = "[T.icon_state][((DIGITIGRADE in species_traits) && T.has_digitigrade) ? "_d" : ""]" var/mutable_appearance/MA if(H.dna.species.sexes && H.gender == FEMALE) - MA = wear_female_version(T.icon_state, T.icon, BODY_LAYER) + MA = wear_female_version(state, T.icon, BODY_LAYER) else - MA = mutable_appearance(T.icon, T.icon_state, -BODY_LAYER) - if(UNDIE_COLORABLE(T)) + MA = mutable_appearance(T.icon, state, -BODY_LAYER) + if(T.has_color) MA.color = "#[H.shirt_color]" standing += MA - if(H.socks && H.get_num_legs(FALSE) >= 2) - if(H.hidden_socks) - H.socks = "Nude" - else - H.socks = H.saved_socks - var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[H.socks] - if(S) - var/digilegs = (DIGITIGRADE in species_traits) ? "_d" : "" - var/mutable_appearance/MA = mutable_appearance(S.icon, "[S.icon_state][digilegs]", -BODY_LAYER) - if(UNDIE_COLORABLE(S)) - MA.color = "#[H.socks_color]" - standing += MA - if(standing.len) H.overlays_standing[BODY_LAYER] = standing @@ -1682,11 +1685,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) attacker_style = M.mind.martial_art if(attacker_style?.pacifism_check && HAS_TRAIT(M, TRAIT_PACIFISM)) // most martial arts are quite harmful, alas. attacker_style = null - if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK)) - log_combat(M, H, "attempted to touch") - H.visible_message("[M] attempted to touch [H]!") - return 0 - SEND_SIGNAL(M, COMSIG_MOB_ATTACK_HAND, M, H, attacker_style) switch(M.a_intent) if("help") help(M, H, attacker_style) diff --git a/code/modules/mob/living/carbon/monkey/combat.dm b/code/modules/mob/living/carbon/monkey/combat.dm index 92ad87f6b5..56ef4fe24a 100644 --- a/code/modules/mob/living/carbon/monkey/combat.dm +++ b/code/modules/mob/living/carbon/monkey/combat.dm @@ -369,6 +369,23 @@ retaliate(L) return ..() +/mob/living/carbon/monkey/attack_alien(mob/living/carbon/alien/humanoid/M) + if(M.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB)) + retaliate(M) + else if(M.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB)) + retaliate(M) + return ..() + +/mob/living/carbon/monkey/attack_larva(mob/living/carbon/alien/larva/L) + if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB)) + retaliate(L) + return ..() + +/mob/living/carbon/monkey/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB)) + retaliate(user) + return ..() + /mob/living/carbon/monkey/attack_paw(mob/living/L) if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB)) retaliate(L) diff --git a/code/modules/mob/living/carbon/monkey/monkey_defense.dm b/code/modules/mob/living/carbon/monkey/monkey_defense.dm index df90dd56fd..32e3d21ee2 100644 --- a/code/modules/mob/living/carbon/monkey/monkey_defense.dm +++ b/code/modules/mob/living/carbon/monkey/monkey_defense.dm @@ -6,37 +6,55 @@ ..() /mob/living/carbon/monkey/attack_paw(mob/living/M) - if(..()) //successful monkey bite. - var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - if(M.limb_destroyer) - dismembering_strike(M, affecting.body_zone) - if(stat != DEAD) - var/dmg = rand(1, 5) - apply_damage(dmg, BRUTE, affecting) + . = ..() + if(!.) //unsuccessful monkey bite. + return + var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + if(M.limb_destroyer) + dismembering_strike(M, affecting.body_zone) + var/dmg = rand(1, 5) + apply_damage(dmg, BRUTE, affecting) /mob/living/carbon/monkey/attack_larva(mob/living/carbon/alien/larva/L) - if(..()) //successful larva bite. - var/damage = rand(1, 3) - if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - apply_damage(damage, BRUTE, affecting) + . = ..() + if(!.) //unsuccessful larva bite + return + var/damage = rand(1, 3) + if(stat != DEAD) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + apply_damage(damage, BRUTE, affecting) + +/mob/living/carbon/monkey/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + . = ..(user, TRUE) + if(.) + return + var/hulk_verb = pick("smash","pummel") + playsound(loc, user.dna.species.attack_sound, 25, 1, -1) + var/message = "[user] has [hulk_verb]ed [src]!" + visible_message("[message]", \ + "[message]") + adjustBruteLoss(15) + return TRUE /mob/living/carbon/monkey/attack_hand(mob/living/carbon/human/M) - if(..()) //To allow surgery to return properly. + . = ..() + if(.) //To allow surgery to return properly. return - switch(M.a_intent) - if("help") + if(INTENT_HELP) help_shake_act(M) - if("grab") + if(INTENT_GRAB) grabbedby(M) - if("harm") + if(INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt [src]!") + return M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) if (prob(75)) visible_message("[M] has punched [name]!", \ @@ -60,7 +78,7 @@ playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) visible_message("[M] has attempted to punch [name]!", \ "[M] has attempted to punch [name]!", null, COMBAT_MESSAGE_RANGE) - if("disarm") + if(INTENT_DISARM) if(!IsUnconscious()) M.do_attack_animation(src, ATTACK_EFFECT_DISARM) if (prob(25)) @@ -74,50 +92,51 @@ visible_message("[M] has disarmed [src]!", "[M] has disarmed [src]!", null, COMBAT_MESSAGE_RANGE) /mob/living/carbon/monkey/attack_alien(mob/living/carbon/alien/humanoid/M) - if(..()) //if harm or disarm intent. - if (M.a_intent == INTENT_HARM) - if ((prob(95) && health > 0)) - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - var/damage = rand(15, 30) - if (damage >= 25) - damage = rand(20, 40) - if(AmountUnconscious() < 300) - Unconscious(rand(200, 300)) - visible_message("[M] has wounded [name]!", \ - "[M] has wounded [name]!", null, COMBAT_MESSAGE_RANGE) - else - visible_message("[M] has slashed [name]!", \ - "[M] has slashed [name]!", null, COMBAT_MESSAGE_RANGE) - - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - log_combat(M, src, "attacked") - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - if(!dismembering_strike(M, affecting.body_zone)) //Dismemberment successful - return 1 - apply_damage(damage, BRUTE, affecting) - + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return + if (M.a_intent == INTENT_HARM) + if ((prob(95) && health > 0)) + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + var/damage = rand(15, 30) + if (damage >= 25) + damage = rand(20, 40) + if(AmountUnconscious() < 300) + Unconscious(rand(200, 300)) + visible_message("[M] has wounded [name]!", \ + "[M] has wounded [name]!", null, COMBAT_MESSAGE_RANGE) else - playsound(loc, 'sound/weapons/slashmiss.ogg', 25, 1, -1) - visible_message("[M] has attempted to lunge at [name]!", \ - "[M] has attempted to lunge at [name]!", null, COMBAT_MESSAGE_RANGE) + visible_message("[M] has slashed [name]!", \ + "[M] has slashed [name]!", null, COMBAT_MESSAGE_RANGE) - if (M.a_intent == INTENT_DISARM) - var/obj/item/I = null - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - if(prob(95)) - Knockdown(20) - visible_message("[M] has tackled down [name]!", \ - "[M] has tackled down [name]!", null, COMBAT_MESSAGE_RANGE) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + log_combat(M, src, "attacked") + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + if(!dismembering_strike(M, affecting.body_zone)) //Dismemberment successful + return 1 + apply_damage(damage, BRUTE, affecting) + + else + playsound(loc, 'sound/weapons/slashmiss.ogg', 25, 1, -1) + visible_message("[M] has attempted to lunge at [name]!", \ + "[M] has attempted to lunge at [name]!", null, COMBAT_MESSAGE_RANGE) + + else + var/obj/item/I = null + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + if(prob(95)) + Knockdown(20) + visible_message("[M] has tackled down [name]!", \ + "[M] has tackled down [name]!", null, COMBAT_MESSAGE_RANGE) + else + I = get_active_held_item() + if(dropItemToGround(I)) + visible_message("[M] has disarmed [name]!", "[M] has disarmed [name]!", null, COMBAT_MESSAGE_RANGE) else - I = get_active_held_item() - if(dropItemToGround(I)) - visible_message("[M] has disarmed [name]!", "[M] has disarmed [name]!", null, COMBAT_MESSAGE_RANGE) - else - I = null - log_combat(M, src, "disarmed", "[I ? " removing \the [I]" : ""]") - updatehealth() - + I = null + log_combat(M, src, "disarmed", "[I ? " removing \the [I]" : ""]") + updatehealth() /mob/living/carbon/monkey/attack_animal(mob/living/simple_animal/M) . = ..() @@ -132,17 +151,19 @@ apply_damage(damage, M.melee_damage_type, affecting) /mob/living/carbon/monkey/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - var/damage = rand(5, 35) - if(M.is_adult) - damage = rand(20, 40) - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return 1 - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - apply_damage(damage, BRUTE, affecting) + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 35) + if(M.is_adult) + damage = rand(20, 40) + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return 1 + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + apply_damage(damage, BRUTE, affecting) /mob/living/carbon/monkey/acid_act(acidpwr, acid_volume, bodyzone_hit) . = 1 diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 722c984309..3c5c5e3518 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -36,7 +36,50 @@ /mob/living/proc/on_hit(obj/item/projectile/P) return +/mob/living/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + var/block_chance_modifier = round(damage / -3) + for(var/obj/item/I in held_items) + if(!istype(I, /obj/item/clothing)) + var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example + if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + return FALSE + +/mob/living/proc/check_reflect(def_zone) //Reflection checks for anything in your hands, based on the reflection chance of the object(s) + for(var/obj/item/I in held_items) + if(I.IsReflect(def_zone)) + return TRUE + return FALSE + +/mob/living/proc/reflect_bullet_check(obj/item/projectile/P, def_zone) + if(P.is_reflectable && check_reflect(def_zone)) // Checks if you've passed a reflection% check + visible_message("The [P.name] gets reflected by [src]!", \ + "The [P.name] gets reflected by [src]!") + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(src) + // redirect the projectile + P.original = locate(new_x, new_y, P.z) + P.starting = curloc + P.firer = src + P.yo = new_y - curloc.y + P.xo = new_x - curloc.x + var/new_angle_s = P.Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + P.setAngle(new_angle_s) + return TRUE + return FALSE + /mob/living/bullet_act(obj/item/projectile/P, def_zone) + if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself + if(reflect_bullet_check(P, def_zone)) + return -1 // complete projectile permutation + if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + P.on_hit(src, 100, def_zone) + return 2 var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null) if(!P.nodamage) apply_damage(P.damage, P.damage_type, def_zone, armor) @@ -55,9 +98,32 @@ else return 0 +/mob/living/proc/catch_item(obj/item/I, skip_throw_mode_check = FALSE) + return FALSE + +/mob/living/proc/embed_item(obj/item/I) + return + +/mob/living/proc/can_embed(obj/item/I) + return FALSE + /mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE) - if(istype(AM, /obj/item)) - var/obj/item/I = AM + var/obj/item/I + var/throwpower = 30 + if(isitem(AM)) + I = AM + throwpower = I.throwforce + if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) + hitpush = FALSE + skipcatch = TRUE + blocked = TRUE + else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item())) + embed_item(I) + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item + if(I) + if(!skipcatch && isturf(I.loc) && catch_item(I)) + return TRUE var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest var/dtype = BRUTE var/volume = I.get_volume_by_throwforce_and_or_w_class() @@ -214,6 +280,24 @@ Move(user.loc) return 1 +/mob/living/attack_hand(mob/user) + ..() //Ignoring parent return value here. + SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user) + if((user != src) && user.a_intent != INTENT_HELP && check_shields(user, 0, user.name, attack_type = UNARMED_ATTACK)) + log_combat(user, src, "attempted to touch") + visible_message("[user] attempted to touch [src]!") + return TRUE + +/mob/living/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to hurt [src]!") + return TRUE + var/hulk_verb = pick("smash","pummel") + if(user != src && check_shields(user, 15, "the [hulk_verb]ing")) + return TRUE + ..() + return FALSE /mob/living/attack_slime(mob/living/simple_animal/slime/M) if(!SSticker.HasRoundStarted()) @@ -229,6 +313,12 @@ to_chat(M, "You don't want to hurt anyone!") return FALSE + var/damage = rand(5, 35) + if(M.is_adult) + damage = rand(20, 40) + if(check_shields(M, damage, "the [M.name]")) + return FALSE + if (stat != DEAD) log_combat(M, src, "attacked") M.do_attack_animation(src) @@ -245,7 +335,8 @@ if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") return FALSE - + if(check_shields(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", MELEE_ATTACK, M.armour_penetration)) + return FALSE if(M.attack_sound) playsound(loc, M.attack_sound, 50, 1, 1) M.do_attack_animation(src) @@ -256,10 +347,6 @@ /mob/living/attack_paw(mob/living/carbon/monkey/M) - if(isturf(loc) && istype(loc.loc, /area/start)) - to_chat(M, "No attacking people at spawn, you jackass.") - return FALSE - if (M.a_intent == INTENT_HARM) if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") @@ -268,6 +355,8 @@ if(M.is_muzzled() || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH)) to_chat(M, "You can't bite with your mouth covered!") return FALSE + if(check_shields(M, 0, "the [M.name]")) + return FALSE M.do_attack_animation(src, ATTACK_EFFECT_BITE) if (prob(75)) log_combat(M, src, "attacked") @@ -282,15 +371,16 @@ /mob/living/attack_larva(mob/living/carbon/alien/larva/L) switch(L.a_intent) - if("help") + if(INTENT_HELP) visible_message("[L.name] rubs its head against [src].") return FALSE else if(HAS_TRAIT(L, TRAIT_PACIFISM)) to_chat(L, "You don't want to hurt anyone!") - return - + return FALSE + if(L != src && check_shields(L, rand(1, 3), "the [L.name]")) + return FALSE L.do_attack_animation(src) if(prob(90)) log_combat(L, src, "attacked") @@ -301,24 +391,29 @@ else visible_message("[L.name] has attempted to bite [src]!", \ "[L.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) - return FALSE /mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) + if((M != src) && M.a_intent != INTENT_HELP && check_shields(M, 0, "the [M.name]")) + visible_message("[M] attempted to touch [src]!") + return FALSE switch(M.a_intent) - if ("help") - visible_message("[M] caresses [src] with its scythe like arm.") + if (INTENT_HELP) + if(!isalien(src)) //I know it's ugly, but the alien vs alien attack_alien behaviour is a bit different. + visible_message("[M] caresses [src] with its scythe like arm.") return FALSE - if ("grab") + if (INTENT_GRAB) grabbedby(M) return FALSE - if("harm") + if(INTENT_HARM) if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") return FALSE - M.do_attack_animation(src) + if(!isalien(src)) + M.do_attack_animation(src) return TRUE - if("disarm") - M.do_attack_animation(src, ATTACK_EFFECT_DISARM) + if(INTENT_DISARM) + if(!isalien(src)) + M.do_attack_animation(src, ATTACK_EFFECT_DISARM) return TRUE /mob/living/ex_act(severity, target, origin) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 0444458841..ac67d16d35 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -260,15 +260,6 @@ viewalerts = 1 src << browse(dat, "window=aialerts&can_close=0") -/mob/living/silicon/ai/proc/ai_roster() - var/dat = "Crew RosterCrew Roster:

    " - - dat += GLOB.data_core.get_manifest() - dat += "" - - src << browse(dat, "window=airoster") - onclose(src, "airoster") - /mob/living/silicon/ai/proc/ai_call_shuttle() if(control_disabled) to_chat(usr, "Wireless control is disabled!") diff --git a/code/modules/mob/living/silicon/ai/ai_defense.dm b/code/modules/mob/living/silicon/ai/ai_defense.dm index 7c59c2b791..97d26f672a 100644 --- a/code/modules/mob/living/silicon/ai/ai_defense.dm +++ b/code/modules/mob/living/silicon/ai/ai_defense.dm @@ -1,15 +1,9 @@ - /mob/living/silicon/ai/attacked_by(obj/item/I, mob/living/user, def_zone) + . = ..() + if(!.) + return FALSE if(I.force && I.damtype != STAMINA && stat != DEAD) //only sparks if real damage is dealt. spark_system.start() - return ..() - - -/mob/living/silicon/ai/attack_alien(mob/living/carbon/alien/humanoid/M) - if(!SSticker.HasRoundStarted()) - to_chat(M, "You cannot attack people before the game has started.") - return - ..() /mob/living/silicon/ai/attack_slime(mob/living/simple_animal/slime/user) return //immune to slimes diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm index dfc718f1ab..5ac841326b 100644 --- a/code/modules/mob/living/silicon/pai/pai_defense.dm +++ b/code/modules/mob/living/silicon/pai/pai_defense.dm @@ -26,13 +26,14 @@ fold_in(force = 1) Knockdown(200) +//ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user) switch(user.a_intent) - if("help") + if(INTENT_HELP) visible_message("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field.") - if("disarm") + if(INTENT_DISARM) visible_message("[user] boops [src] on the head!") - if("harm") + if(INTENT_HARM) user.do_attack_animation(src) if (user.name == master) visible_message("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence.") @@ -41,14 +42,19 @@ if(user.put_in_hands(card)) user.visible_message("[user] promptly scoops up [user.p_their()] pAI's card.") else + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to hurt [src]!") + return visible_message("[user] stomps on [src]!.") take_holo_damage(2) + else + grabbedby(user) -/mob/living/silicon/pai/bullet_act(obj/item/projectile/Proj) - if(Proj.stun) +/mob/living/silicon/pai/bullet_act(obj/item/projectile/P, def_zone) + if(P.stun) fold_in(force = TRUE) - src.visible_message("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!") - . = ..(Proj) + visible_message("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!") + . = ..() /mob/living/silicon/pai/stripPanelUnequip(obj/item/what, mob/who, where) //prevents stripping to_chat(src, "Your holochassis stutters and warps intensely as you attempt to interact with the object, forcing you to cease lest the field fail.") diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 5df24406c5..5f7d3ca243 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1298,4 +1298,12 @@ /mob/living/silicon/robot/adjustStaminaLossBuffered(amount, updating_health = 1) if(istype(cell)) - cell.charge -= amount*5 \ No newline at end of file + cell.charge -= amount*5 + +/mob/living/silicon/robot/verb/viewmanifest() + set category = "Robot Commands" + set name = "View Crew Manifest" + + if(usr.stat == DEAD) + return //won't work if dead + ai_roster() \ No newline at end of file diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 0f09b6f62a..7e06c66eff 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -13,7 +13,19 @@ spark_system.start() return ..() +/mob/living/silicon/robot/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + . = ..() + if(.) + spark_system.start() + spawn(0) + step_away(src,user,15) + sleep(3) + step_away(src,user,15) + /mob/living/silicon/robot/attack_alien(mob/living/carbon/alien/humanoid/M) + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return if (M.a_intent == INTENT_DISARM) if(!(lying)) M.do_attack_animation(src, ATTACK_EFFECT_DISARM) @@ -30,24 +42,19 @@ visible_message("[M] has forced back [src]!", \ "[M] has forced back [src]!", null, COMBAT_MESSAGE_RANGE) playsound(loc, 'sound/weapons/pierce.ogg', 50, 1, -1) - else - ..() - return /mob/living/silicon/robot/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime shock - flash_act() - var/stunprob = M.powerlevel * 7 + 10 - if(prob(stunprob) && M.powerlevel >= 8) - adjustBruteLoss(M.powerlevel * rand(6,10)) - - var/damage = rand(1, 3) - + . = ..() + if(!.) //unsuccessful slime shock + return + var/stunprob = M.powerlevel * 7 + 10 + var/damage = M.powerlevel * rand(6,10) + if(prob(stunprob) && M.powerlevel >= 8) + flash_act(affect_silicon = TRUE) //my borg eyes! if(M.is_adult) - damage = rand(20, 40) + damage += rand(10, 20) else - damage = rand(5, 35) - damage = round(damage / 2) // borgs receive half damage + damage += rand(2, 17) adjustBruteLoss(damage) updatehealth() @@ -56,23 +63,17 @@ //ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/silicon/robot/attack_hand(mob/living/carbon/human/user) add_fingerprint(user) - if(opened && !wiresexposed && !issilicon(user)) - if(cell) - cell.update_icon() - cell.add_fingerprint(user) - user.put_in_active_hand(cell) - to_chat(user, "You remove \the [cell].") - cell = null - update_icons() - diag_hud_set_borgcell() + if(opened && !wiresexposed && cell && !issilicon(user)) + cell.update_icon() + cell.add_fingerprint(user) + user.put_in_active_hand(cell) + to_chat(user, "You remove \the [cell].") + cell = null + update_icons() + diag_hud_set_borgcell() if(!opened) - if(..()) // hulk attack - spark_system.start() - spawn(0) - step_away(src,user,15) - sleep(3) - step_away(src,user,15) + return ..() /mob/living/silicon/robot/fire_act() if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them @@ -182,9 +183,9 @@ if (stat != DEAD) adjustBruteLoss(30) -/mob/living/silicon/robot/bullet_act(var/obj/item/projectile/Proj) - ..(Proj) +/mob/living/silicon/robot/bullet_act(obj/item/projectile/P, def_zone) + ..() updatehealth() - if(prob(75) && Proj.damage > 0) + if(prob(75) && P.damage > 0) spark_system.start() return 2 diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index b9cee43138..699105ac4c 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -389,6 +389,15 @@ if (aicamera) return aicamera.selectpicture(user) +/mob/living/silicon/proc/ai_roster() + var/dat = "Crew RosterCrew Roster:

    " + + dat += GLOB.data_core.get_manifest() + dat += "" + + src << browse(dat, "window=airoster") + onclose(src, "airoster") + /mob/living/silicon/update_transform() var/matrix/ntransform = matrix(transform) //aka transform.Copy() var/changed = 0 diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index 073a2eec2b..ca8ad25713 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -6,7 +6,10 @@ return 2 /mob/living/silicon/attack_alien(mob/living/carbon/alien/humanoid/M) - if(..()) //if harm or disarm intent + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return + if(M.a_intent == INTENT_HARM) var/damage = 20 if (prob(90)) log_combat(M, src, "attacked") @@ -49,34 +52,33 @@ /mob/living/silicon/attack_paw(mob/living/user) return attack_hand(user) -/mob/living/silicon/attack_larva(mob/living/carbon/alien/larva/L) - if(L.a_intent == INTENT_HELP) - visible_message("[L.name] rubs its head against [src].") - -/mob/living/silicon/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/mob/living/silicon/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) - ..(user, 1) + . = ..(user, TRUE) + if(.) + return adjustBruteLoss(rand(10, 15)) playsound(loc, "punch", 25, 1, -1) visible_message("[user] has punched [src]!", \ "[user] has punched [src]!") - return 1 - return 0 + return TRUE + return FALSE -//ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/silicon/attack_hand(mob/living/carbon/human/M) + . = ..() + if(.) //the attack was blocked + return switch(M.a_intent) - if ("help") + if (INTENT_HELP) M.visible_message("[M] pets [src].", \ "You pet [src].") - if("grab") + if(INTENT_GRAB) grabbedby(M) else M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) playsound(src.loc, 'sound/effects/bang.ogg', 10, 1) visible_message("[M] punches [src], but doesn't leave a dent.", \ "[M] punches [src], but doesn't leave a dent.", null, COMBAT_MESSAGE_RANGE) - return 0 /mob/living/silicon/attack_drone(mob/living/simple_animal/drone/M) if(M.a_intent == INTENT_HARM) @@ -108,19 +110,25 @@ M.visible_message("[M] is thrown off of [src]!") flash_act(affect_silicon = 1) -/mob/living/silicon/bullet_act(obj/item/projectile/Proj) - if((Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - adjustBruteLoss(Proj.damage) - if(prob(Proj.damage*1.5)) +/mob/living/silicon/bullet_act(obj/item/projectile/P, def_zone) + if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself + if(reflect_bullet_check(P, def_zone)) + return -1 // complete projectile permutation + if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + P.on_hit(src, 100, def_zone) + return 2 + if((P.damage_type == BRUTE || P.damage_type == BURN)) + adjustBruteLoss(P.damage) + if(prob(P.damage*1.5)) for(var/mob/living/M in buckled_mobs) M.visible_message("[M] is knocked off of [src]!") unbuckle_mob(M) M.Knockdown(40) - if(Proj.stun || Proj.knockdown) + if(P.stun || P.knockdown) for(var/mob/living/M in buckled_mobs) unbuckle_mob(M) - M.visible_message("[M] is knocked off of [src] by the [Proj]!") - Proj.on_hit(src) + M.visible_message("[M] is knocked off of [src] by the [P]!") + P.on_hit(src) return 2 /mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash/static) diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 793df63c87..0dfa126e79 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -1,20 +1,22 @@ /mob/living/simple_animal/attack_hand(mob/living/carbon/human/M) - ..() + . = ..() + if(.) //the attack was blocked + return switch(M.a_intent) - if("help") + if(INTENT_HELP) if (health > 0) visible_message("[M] [response_help] [src].") playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - if("grab") + if(INTENT_GRAB) if(grab_state >= GRAB_AGGRESSIVE && isliving(pulling)) vore_attack(M, pulling) else grabbedby(M) - if("harm", "disarm") + if(INTENT_HARM, INTENT_DISARM) if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt [src]!") return @@ -27,12 +29,11 @@ updatehealth() return TRUE -/mob/living/simple_animal/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) +/mob/living/simple_animal/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) if(user.a_intent == INTENT_HARM) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to hurt [src]!") - return FALSE - ..(user, 1) + . = ..(user, TRUE) + if(.) + return playsound(loc, "punch", 25, 1, -1) visible_message("[user] has punched [src]!", \ "[user] has punched [src]!", null, COMBAT_MESSAGE_RANGE) @@ -40,32 +41,32 @@ return TRUE /mob/living/simple_animal/attack_paw(mob/living/carbon/monkey/M) - if(..()) //successful monkey bite. - if(stat != DEAD) - var/damage = rand(1, 3) - attack_threshold_check(damage) - return 1 + . = ..() + if(.) //successful larva bite + var/damage = rand(1, 3) + attack_threshold_check(damage) + return 1 if (M.a_intent == INTENT_HELP) if (health > 0) visible_message("[M.name] [response_help] [src].") playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - /mob/living/simple_animal/attack_alien(mob/living/carbon/alien/humanoid/M) - if(..()) //if harm or disarm intent. - if(M.a_intent == INTENT_DISARM) - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - visible_message("[M] [response_disarm] [name]!", \ - "[M] [response_disarm] [name]!", null, COMBAT_MESSAGE_RANGE) - log_combat(M, src, "disarmed") - else - var/damage = rand(15, 30) - visible_message("[M] has slashed at [src]!", \ - "[M] has slashed at [src]!", null, COMBAT_MESSAGE_RANGE) - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - attack_threshold_check(damage) - log_combat(M, src, "attacked") - return 1 + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return + if(M.a_intent == INTENT_DISARM) + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + visible_message("[M] [response_disarm] [name]!", \ + "[M] [response_disarm] [name]!", null, COMBAT_MESSAGE_RANGE) + log_combat(M, src, "disarmed") + else + var/damage = rand(15, 30) + visible_message("[M] has slashed at [src]!", \ + "[M] has slashed at [src]!", null, COMBAT_MESSAGE_RANGE) + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + attack_threshold_check(damage) + log_combat(M, src, "attacked") /mob/living/simple_animal/attack_larva(mob/living/carbon/alien/larva/L) . = ..() @@ -82,7 +83,8 @@ return attack_threshold_check(damage, M.melee_damage_type) /mob/living/simple_animal/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack + . = ..() + if(.) //successful slime shock var/damage = rand(15, 25) if(M.is_adult) damage = rand(20, 35) diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm index 396c6de166..cae707ce46 100644 --- a/code/modules/mob/living/simple_animal/bot/floorbot.dm +++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm @@ -318,9 +318,9 @@ sleep(50) if(mode == BOT_REPAIRING && src.loc == target_turf) if(autotile) //Build the floor and include a tile. - target_turf.PlaceOnTop(/turf/open/floor/plasteel) + target_turf.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR) else //Build a hull plating without a floor tile. - target_turf.PlaceOnTop(/turf/open/floor/plating) + target_turf.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) else var/turf/open/floor/F = target_turf @@ -334,7 +334,7 @@ if(mode == BOT_REPAIRING && F && src.loc == F) F.broken = 0 F.burnt = 0 - F.PlaceOnTop(/turf/open/floor/plasteel) + F.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR) if(replacetiles && F.type != initial(tiletype.turf_type) && specialtiles && !isplatingturf(F)) anchored = TRUE @@ -345,7 +345,7 @@ if(mode == BOT_REPAIRING && F && src.loc == F) F.broken = 0 F.burnt = 0 - F.PlaceOnTop(initial(tiletype.turf_type)) + F.PlaceOnTop(initial(tiletype.turf_type), flags = CHANGETURF_INHERIT_AIR) specialtiles -= 1 if(specialtiles == 0) speak("Requesting refill of custom floortiles to continue replacing.") diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 2de4ca39fd..109c7b4636 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -113,7 +113,7 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, mode = BOT_HUNT /mob/living/simple_animal/bot/honkbot/attack_hand(mob/living/carbon/human/H) - if(H.a_intent == "harm") + if(H.a_intent == INTENT_HARM) retaliate(H) addtimer(CALLBACK(src, .proc/react_buzz), 5) return ..() diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 02e171e4c4..bda309f7c7 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -232,9 +232,9 @@ /mob/living/simple_animal/pet/cat/attack_hand(mob/living/carbon/human/M) . = ..() switch(M.a_intent) - if("help") + if(INTENT_HELP) wuv(1, M) - if("harm") + if(INTENT_HARM) wuv(-1, M) /mob/living/simple_animal/pet/cat/proc/wuv(change, mob/M) @@ -290,7 +290,9 @@ D.decorate_donut() /mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) - ..() + . = ..() + if(.) //the attack was blocked + return if(L.a_intent == INTENT_HARM && L.reagents && !stat) L.reagents.add_reagent("nutriment", 0.4) L.reagents.add_reagent("vitamin", 0.4) diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index 0d933d171b..986e5c9b4d 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -643,9 +643,9 @@ /mob/living/simple_animal/pet/dog/attack_hand(mob/living/carbon/human/M) . = ..() switch(M.a_intent) - if("help") + if(INTENT_HELP) wuv(1,M) - if("harm") + if(INTENT_HARM) wuv(-1,M) /mob/living/simple_animal/pet/dog/proc/wuv(change, mob/M) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm index e40eb585e8..c9207fcf89 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/interaction.dm @@ -31,9 +31,9 @@ //picky up the drone c: /mob/living/simple_animal/drone/attack_hand(mob/user) - ..() - if(user.a_intent == INTENT_HELP) - mob_try_pickup(user) + if(user.a_intent != INTENT_HELP) + return ..() // TODO: convert picking up mobs into an element or component. + mob_try_pickup(user) /mob/living/simple_animal/drone/proc/try_reactivate(mob/living/user) var/mob/dead/observer/G = get_ghost() diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm index 49514d0d12..914d38022f 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/charger.dm @@ -54,10 +54,8 @@ var/blocked = FALSE if(hasmatchingsummoner(A)) //if the summoner matches don't hurt them blocked = TRUE - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.check_shields(src, 90, "[name]", attack_type = THROWN_PROJECTILE_ATTACK)) - blocked = TRUE + if(L.check_shields(src, 90, "[name]", attack_type = THROWN_PROJECTILE_ATTACK)) + blocked = TRUE if(!blocked) L.drop_all_held_items() L.visible_message("[src] slams into [L]!", "[src] slams into you!") diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 51a9d8d62b..c2c9f5a71f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -486,10 +486,7 @@ Difficulty: Very Hard if(isturf(Stuff)) var/turf/T = Stuff if((isspaceturf(T) || isfloorturf(T)) && NewTerrainFloors) - var/turf/open/O = T.ChangeTurf(NewTerrainFloors) - if(O.air) - var/datum/gas_mixture/G = O.air - G.copy_from_turf(O) + var/turf/open/O = T.ChangeTurf(NewTerrainFloors, flags = CHANGETURF_INHERIT_AIR) if(prob(florachance) && NewFlora.len && !is_blocked_turf(O, TRUE)) var/atom/Picked = pick(NewFlora) new Picked(O) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 8c2fbef15e..c6386540f7 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -25,7 +25,7 @@ mob_size = MOB_SIZE_LARGE layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise mouse_opacity = MOUSE_OPACITY_OPAQUE // Easier to click on in melee, they're giant targets anyway - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1 var/list/crusher_loot var/medal_type var/score_type = BOSS_SCORE diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm index 05dec578bd..03d2365016 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm @@ -20,7 +20,7 @@ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE mob_size = MOB_SIZE_LARGE var/icon_aggro = null - var/crusher_drop_mod = 5 + var/crusher_drop_mod = 25 /mob/living/simple_animal/hostile/asteroid/Initialize(mapload) . = ..() @@ -58,7 +58,7 @@ /mob/living/simple_animal/hostile/asteroid/death(gibbed) SSblackbox.record_feedback("tally", "mobs_killed_mining", 1, type) var/datum/status_effect/crusher_damage/C = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING) - if(C && crusher_loot && prob((C.total_damage/maxHealth) * crusher_drop_mod)) //on average, you'll need to kill 20 creatures before getting the item + if(C && crusher_loot && prob((C.total_damage/maxHealth) * crusher_drop_mod)) //on average, you'll need to kill 4 creatures before getting the item spawn_crusher_loot() ..(gibbed) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm index e3c81b2639..27ebb71c0e 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm @@ -91,5 +91,5 @@ visible_message("The tendril falls inward, the ground around it widening into a yawning chasm!") for(var/turf/T in range(2,src)) if(!T.density) - T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland) + T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR) qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/mushroom.dm b/code/modules/mob/living/simple_animal/hostile/mushroom.dm index 514edf896b..a5e3cd8120 100644 --- a/code/modules/mob/living/simple_animal/hostile/mushroom.dm +++ b/code/modules/mob/living/simple_animal/hostile/mushroom.dm @@ -166,7 +166,9 @@ ..() /mob/living/simple_animal/hostile/mushroom/attack_hand(mob/living/carbon/human/M) - ..() + . = ..() + if(.) // the attack was blocked + return if(M.a_intent == INTENT_HARM) Bruise() diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index d1e10ea693..439ea5b2bf 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -253,33 +253,34 @@ return /mob/living/simple_animal/slime/attack_slime(mob/living/simple_animal/slime/M) - if(..()) //successful slime attack - if(M == src) - return - if(buckled) - Feedstop(silent = TRUE) - visible_message("[M] pulls [src] off!") - return - attacked += 5 - if(nutrition >= 100) //steal some nutrition. negval handled in life() - nutrition -= (50 + (40 * M.is_adult)) - M.add_nutrition(50 + (40 * M.is_adult)) - if(health > 0) - M.adjustBruteLoss(-10 + (-10 * M.is_adult)) - M.updatehealth() + . = ..() + if(!. || M == src) //unsuccessful slime shock + return + if(buckled) + Feedstop(silent = TRUE) + visible_message("[M] pulls [src] off!") + return + attacked += 5 + if(nutrition >= 100) //steal some nutrition. negval handled in life() + nutrition -= (50 + (40 * M.is_adult)) + M.add_nutrition(50 + (40 * M.is_adult)) + if(health > 0) + M.adjustBruteLoss(-10 + (-10 * M.is_adult)) + M.updatehealth() /mob/living/simple_animal/slime/attack_animal(mob/living/simple_animal/M) . = ..() if(.) attacked += 10 - /mob/living/simple_animal/slime/attack_paw(mob/living/carbon/monkey/M) - if(..()) //successful monkey bite. + . = ..() + if(.)//successful monkey bite. attacked += 10 /mob/living/simple_animal/slime/attack_larva(mob/living/carbon/alien/larva/L) - if(..()) //successful larva bite. + . = ..() + if(.) //successful larva bite. attacked += 10 /mob/living/simple_animal/slime/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) @@ -321,9 +322,11 @@ attacked += 10 /mob/living/simple_animal/slime/attack_alien(mob/living/carbon/alien/humanoid/M) - if(..()) //if harm or disarm intent. - attacked += 10 - discipline_slime(M) + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return + attacked += 10 + discipline_slime(M) /mob/living/simple_animal/slime/attackby(obj/item/W, mob/living/user, params) diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm index 8c304d8a2b..56c50078c1 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stealth.dm @@ -26,10 +26,9 @@ Contents: /obj/item/clothing/suit/space/space_ninja/proc/enable_signals() if(!affecting) return - RegisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE), .proc/reduce_stealth) + RegisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE), .proc/reduce_stealth) RegisterSignal(affecting, COMSIG_MOVABLE_BUMP, .proc/bumping_stealth) - /obj/item/clothing/suit/space/space_ninja/proc/reduce_stealth(datum/source) affecting.alpha = min(affecting.alpha + 40, 100) @@ -42,7 +41,7 @@ Contents: return FALSE stealth = !stealth stealth_cooldown = world.time + 5 SECONDS - UnregisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY, COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE)) + UnregisterSignal(affecting, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_MOB_ATTACK_RANGED, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_THROW, COMSIG_PARENT_ATTACKBY, COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_TELEPORTED, COMSIG_LIVING_GUN_PROCESS_FIRE)) animate(affecting, alpha = 255, time = 3 SECONDS) affecting.visible_message("[affecting.name] appears from thin air!", \ "You are now visible.") diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 2ca6ce2611..f3f7e45378 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -102,7 +102,7 @@ /obj/item/paperplane/throw_impact(atom/hit_atom) if(iscarbon(hit_atom)) var/mob/living/carbon/C = hit_atom - if(C.can_catch_item(TRUE)) + if(!C.get_active_held_item() && !C.restrained()) var/datum/action/innate/origami/origami_action = locate() in C.actions if(origami_action?.active) //if they're a master of origami and have the ability turned on, force throwmode on so they'll automatically catch the plane. C.throw_mode_on() diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 531c6082b0..19fdfd2b7e 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -37,6 +37,7 @@ var/burst_spread = 0 //Spread induced by the gun itself during burst fire per iteration. Only checked if spread is 0. var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once. var/inaccuracy_modifier = 1 + var/pb_knockback = 0 lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' @@ -125,6 +126,10 @@ if(message) if(pointblank) user.visible_message("[user] fires [src] point blank at [pbtarget]!", null, null, COMBAT_MESSAGE_RANGE) + if(pb_knockback > 0) + var/atom/throw_target = get_edge_target_turf(pbtarget, user.dir) + pbtarget.throw_at(throw_target, pb_knockback, 2) + else user.visible_message("[user] fires [src]!", null, null, COMBAT_MESSAGE_RANGE) diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index bcb212a031..7fb4a8232e 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -267,6 +267,7 @@ fire_delay = 0 pin = /obj/item/firing_pin/implant/pindicate actions_types = list() + pb_knockback = 2 /obj/item/gun/ballistic/automatic/shotgun/bulldog/unrestricted pin = /obj/item/firing_pin diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index c2206fcea8..31a5131804 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -273,6 +273,7 @@ "Maple" = "dshotgun-l", "Rosewood" = "dshotgun-p" ) + pb_knockback = 3 // it's a super shotgun! /obj/item/gun/ballistic/revolver/doublebarrel/attackby(obj/item/A, mob/user, params) ..() diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 571525d8f0..deec187f88 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -12,6 +12,8 @@ var/recentpump = 0 // to prevent spammage weapon_weight = WEAPON_MEDIUM + pb_knockback = 2 + /obj/item/gun/ballistic/shotgun/attackby(obj/item/A, mob/user, params) . = ..() if(.) diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm index 8b358832b0..8f9bc13583 100644 --- a/code/modules/projectiles/guns/ballistic/toy.dm +++ b/code/modules/projectiles/guns/ballistic/toy.dm @@ -56,6 +56,7 @@ item_flags = NONE casing_ejector = FALSE can_suppress = FALSE + pb_knockback = 0 /obj/item/gun/ballistic/shotgun/toy/process_chamber(empty_chamber = 0) ..() diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 173d9721a9..791db320a2 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -93,7 +93,7 @@ /obj/item/projectile/magic/door/proc/CreateDoor(turf/T) var/door_type = pick(door_types) var/obj/structure/mineral_door/D = new door_type(T) - T.ChangeTurf(/turf/open/floor/plating) + T.ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) D.Open() /obj/item/projectile/magic/door/proc/OpenDoor(var/obj/machinery/door/D) diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 1ac62ba651..513626465a 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -260,7 +260,7 @@ if(!amount) return vol_each = min(reagents.total_volume / amount, 50) - var/name = stripped_input(usr,"Name:","Name your pill!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr,"Name:","Name your pill!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return var/obj/item/reagent_containers/pill/P @@ -287,7 +287,7 @@ adjust_item_drop_location(P) reagents.trans_to(P,vol_each) else - var/name = stripped_input(usr, "Name:", "Name your pack!", reagents.get_master_reagent_name(), MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr, "Name:", "Name your pack!", reagents.get_master_reagent_name(), MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return var/obj/item/reagent_containers/food/condiment/pack/P = new/obj/item/reagent_containers/food/condiment/pack(drop_location()) @@ -313,7 +313,7 @@ if(!amount) return vol_each = min(reagents.total_volume / amount, 40) - var/name = stripped_input(usr,"Name:","Name your patch!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr,"Name:","Name your patch!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return var/obj/item/reagent_containers/pill/P @@ -331,7 +331,7 @@ return if(condi) - var/name = stripped_input(usr, "Name:","Name your bottle!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr, "Name:","Name your bottle!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return var/obj/item/reagent_containers/food/condiment/P = new(drop_location()) @@ -344,7 +344,7 @@ if(text2num(many)) amount_full = round(reagents.total_volume / 30) vol_part = ((reagents.total_volume*1000) % 30000) / 1000 //% operator doesn't support decimals. - var/name = stripped_input(usr, "Name:","Name your bottle!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr, "Name:","Name your bottle!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return @@ -372,7 +372,7 @@ if(text2num(many)) amount_full = round(reagents.total_volume / 60) vol_part = reagents.total_volume % 60 - var/name = stripped_input(usr, "Name:","Name your hypovial!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr, "Name:","Name your hypovial!", (reagents.total_volume ? reagents.get_master_reagent_name() : " "), MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return @@ -407,7 +407,7 @@ return vol_each = min(reagents.total_volume / amount, 20) - var/name = stripped_input(usr,"Name:","Name your SmartDart!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN) + var/name = html_decode(stripped_input(usr,"Name:","Name your SmartDart!", "[reagents.get_master_reagent_name()] ([vol_each]u)", MAX_NAME_LEN)) if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr))) return diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 5d31bfae9b..e8551074e7 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -247,6 +247,23 @@ . = 1 ..() +/datum/reagent/consumable/coconutmilk + name = "Coconut Milk" + id = "coconutmilk" + description = "A transparent white liquid extracted from coconuts. Rich in taste." + color = "#DFDFDF" // rgb: 223, 223, 223 + taste_description = "sweet milk" + quality = DRINK_GOOD + glass_icon_state = "glass_white" + glass_name = "glass of coconut milk" + glass_desc = "White and nutritious goodness!" + +/datum/reagent/consumable/coconutmilk/on_mob_life(mob/living/carbon/M) + if(M.getBruteLoss() && prob(20)) + M.heal_bodypart_damage(2,0, 0) + . = 1 + ..() + /datum/reagent/consumable/cream name = "Cream" id = "cream" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 512b8a21a1..bd6eb4244c 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1870,133 +1870,82 @@ reagent_state = LIQUID color = "#b51d05" taste_description = "carpet" // Your tounge feels furry. + var/carpet_type = /turf/open/floor/carpet /datum/reagent/carpet/reaction_turf(turf/T, reac_volume) if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet) + F.PlaceOnTop(carpet_type, flags = CHANGETURF_INHERIT_AIR) ..() /datum/reagent/carpet/black name = "Liquid Black Carpet" id = "blackcarpet" color = "#363636" - -/datum/reagent/carpet/black/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/black) - ..() + carpet_type = /turf/open/floor/carpet/black /datum/reagent/carpet/blackred name = "Liquid Red Black Carpet" id = "blackredcarpet" color = "#342125" - -/datum/reagent/carpet/blackred/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/blackred) - ..() + carpet_type = /turf/open/floor/carpet/blackred /datum/reagent/carpet/monochrome name = "Liquid Monochrome Carpet" id = "monochromecarpet" color = "#b4b4b4" - -/datum/reagent/carpet/monochrome/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/monochrome) - ..() + carpet_type = /turf/open/floor/carpet/monochrome /datum/reagent/carpet/blue name = "Liquid Blue Carpet" id = "bluecarpet" color = "#1256ff" - -/datum/reagent/carpet/blue/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/blue) - ..() + carpet_type = /turf/open/floor/carpet/blue /datum/reagent/carpet/cyan name = "Liquid Cyan Carpet" id = "cyancarpet" color = "#3acfb9" - -/datum/reagent/carpet/cyan/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/cyan) - ..() + carpet_type = /turf/open/floor/carpet/cyan /datum/reagent/carpet/green name = "Liquid Green Carpet" id = "greencarpet" color = "#619b62" - -/datum/reagent/carpet/green/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/green) - ..() + carpet_type = /turf/open/floor/carpet/green /datum/reagent/carpet/orange name = "Liquid Orange Carpet" id = "orangecarpet" color = "#cc7900" - -/datum/reagent/carpet/orange/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/orange) - ..() + carpet_type = /turf/open/floor/carpet/orange /datum/reagent/carpet/purple name = "Liquid Purple Carpet" id = "purplecarpet" color = "#6d3392" + carpet_type = /turf/open/floor/carpet/purple -/datum/reagent/carpet/purple/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/purple) - ..() /datum/reagent/carpet/red name = "Liquid Red Carpet" id = "redcarpet" color = "#871515" + carpet_type = /turf/open/floor/carpet/red -/datum/reagent/carpet/red/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/red) - ..() /datum/reagent/carpet/royalblack name = "Liquid Royal Black Carpet" id = "royalblackcarpet" color = "#483d05" + carpet_type = /turf/open/floor/carpet/royalblack -/datum/reagent/carpet/royalblack/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/royalblack) - ..() /datum/reagent/carpet/royalblue name = "Liquid Royal Blue Carpet" id = "royalbluecarpet" color = "#24227e" - -/datum/reagent/carpet/royalblue/reaction_turf(turf/T, reac_volume) - if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) - var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/carpet/royalblue) - ..() + carpet_type = /turf/open/floor/carpet/royalblue // Virology virus food chems. diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index c170858d98..36621aa662 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -53,7 +53,7 @@ if(isplatingturf(T)) var/turf/open/floor/plating/F = T if(prob(10 + F.burnt + 5*F.broken)) //broken or burnt plating is more susceptible to being destroyed - F.ScrapeAway() + F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) if(isfloorturf(T)) var/turf/open/floor/F = T if(prob(reac_volume)) diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm index 59394e6e4b..08d07b8a52 100644 --- a/code/modules/reagents/chemistry/recipes/special.dm +++ b/code/modules/reagents/chemistry/recipes/special.dm @@ -123,24 +123,11 @@ GLOBAL_LIST_INIT(food_reagents, build_reagents_to_food()) //reagentid = related /datum/chemical_reaction/randomized/proc/LoadOldRecipe(recipe_data) created = text2num(recipe_data["timestamp"]) - - var/req_reag = unwrap_reagent_list(recipe_data["required_reagents"]) - if(!req_reag) - return FALSE - required_reagents = req_reag - - var/req_catalysts = unwrap_reagent_list(recipe_data["required_catalysts"]) - if(!req_catalysts) - return FALSE - required_catalysts = req_catalysts - + required_reagents = SANITIZE_LIST(recipe_data["required_reagents"]) + required_catalysts = SANITIZE_LIST(recipe_data["required_catalysts"]) required_temp = recipe_data["required_temp"] is_cold_recipe = recipe_data["is_cold_recipe"] - - var/temp_results = unwrap_reagent_list(recipe_data["results"]) - if(!temp_results) - return FALSE - results = temp_results + results = SANITIZE_LIST(recipe_data["results"]) var/containerpath = text2path(recipe_data["required_container"]) if(!containerpath) return FALSE diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 3ed505c360..85248250a2 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -109,11 +109,13 @@ /obj/item/reagent_containers/glass/beaker name = "beaker" - desc = "A beaker. It can hold up to 50 units. Unable to withstand extreme pHes" + desc = "A beaker. It can hold up to 60 units. Unable to withstand extreme pHes." icon = 'icons/obj/chemical.dmi' + volume = 60 icon_state = "beaker" item_state = "beaker" materials = list(MAT_GLASS=500) + possible_transfer_amounts = list(5,10,15,20,25,30,60) beaker_weakness_bitflag = PH_WEAK /obj/item/reagent_containers/glass/beaker/Initialize() @@ -156,28 +158,28 @@ /obj/item/reagent_containers/glass/beaker/jar name = "honey jar" - desc = "A jar for honey. It can hold up to 50 units of sweet delight. Unable to withstand reagents of an extreme pH." + desc = "A jar for honey. It can hold up to 60 units of sweet delight. Unable to withstand reagents of an extreme pH." icon = 'icons/obj/chemical.dmi' icon_state = "vapour" /obj/item/reagent_containers/glass/beaker/large name = "large beaker" - desc = "A large beaker. Can hold up to 100 units. Unable to withstand reagents of an extreme pH." + desc = "A large beaker. Can hold up to 120 units. Unable to withstand reagents of an extreme pH." icon_state = "beakerlarge" materials = list(MAT_GLASS=2500) - volume = 100 + volume = 120 amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100) + possible_transfer_amounts = list(5,10,15,20,25,30,40,60,120) container_HP = 3 /obj/item/reagent_containers/glass/beaker/plastic name = "x-large beaker" - desc = "An extra-large beaker. Can hold up to 150 units. Is able to resist acid and alkaline solutions, but melts at 444K" + desc = "An extra-large beaker. Can hold up to 180 units. Is able to resist acid and alkaline solutions, but melts at 444 K." icon_state = "beakerwhite" materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000) - volume = 150 + volume = 180 amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100,150) + possible_transfer_amounts = list(5,10,15,20,25,30,40,60,120,180) /obj/item/reagent_containers/glass/beaker/plastic/Initialize() beaker_weakness_bitflag &= ~PH_WEAK @@ -191,14 +193,14 @@ /obj/item/reagent_containers/glass/beaker/meta name = "metamaterial beaker" - desc = "A large beaker. Can hold up to 200 units. Is able to withstand all chemical situations." + desc = "A large beaker. Can hold up to 240 units, and is able to withstand all chemical situations." icon_state = "beakergold" materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000) - volume = 200 + volume = 240 amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100,200) + possible_transfer_amounts = list(5,10,15,20,25,30,40,60,120,200,240) -/obj/item/reagent_containers/glass/beaker/meta/Initialize() +/obj/item/reagent_containers/glass/beaker/meta/Initialize() // why the fuck can't you just set the beaker weakness bitflags to nothing? fuck you beaker_weakness_bitflag &= ~PH_WEAK . = ..() @@ -228,7 +230,7 @@ volume = 300 amount_per_transfer_from_this = 10 possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300) - container_HP = 4 + container_HP = 5 /obj/item/reagent_containers/glass/beaker/cryoxadone list_reagents = list("cryoxadone" = 30) diff --git a/code/modules/reagents/reagent_containers/hypovial.dm b/code/modules/reagents/reagent_containers/hypovial.dm index ba5ce48a4a..482877afcd 100644 --- a/code/modules/reagents/reagent_containers/hypovial.dm +++ b/code/modules/reagents/reagent_containers/hypovial.dm @@ -1,6 +1,6 @@ //hypovials used with the MkII hypospray. See hypospray.dm. -/obj/item/reagent_containers/glass/bottle/vial +/obj/item/reagent_containers/glass/bottle/vial // these have literally no fucking right to just be better beakers that you can shit out of a chemmaster name = "broken hypovial" desc = "A hypovial compatible with most hyposprays." icon_state = "hypovial" @@ -27,6 +27,8 @@ for(var/R in comes_with) reagents.add_reagent(R,comes_with[R]) update_icon() +// beaker_weakness_bitflag |= PH_WEAK // fuck you if you're using these like beakers +// beaker_weakness_bitflag |= TEMP_WEAK /obj/item/reagent_containers/glass/bottle/vial/on_reagent_change() @@ -60,11 +62,11 @@ /obj/item/reagent_containers/glass/bottle/vial/small name = "hypovial" volume = 60 - possible_transfer_amounts = list(5,10) + possible_transfer_amounts = list(1,2,5,10,20,30) /obj/item/reagent_containers/glass/bottle/vial/small/bluespace volume = 120 - possible_transfer_amounts = list(5,10) + possible_transfer_amounts = list(1,2,5,10,20,30,40) name = "bluespace hypovial" icon_state = "hypovialbs" unique_reskin = null @@ -74,7 +76,7 @@ desc = "A large hypovial, for deluxe hypospray models." icon_state = "hypoviallarge" volume = 120 - possible_transfer_amounts = list(5,10,15,20) + possible_transfer_amounts = list(1,2,5,10,20,30,40,60) unique_reskin = list("large hypovial" = "hypoviallarge", "large red hypovial" = "hypoviallarge-b", "large blue hypovial" = "hypoviallarge-d", @@ -106,7 +108,7 @@ add_overlay(filling) /obj/item/reagent_containers/glass/bottle/vial/large/bluespace - possible_transfer_amounts = list(5,10,15,20) + possible_transfer_amounts = list(1,2,5,10,20,30,40,60) name = "bluespace large hypovial" volume = 240 icon_state = "hypoviallargebs" diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index 7701e58616..46ddb1e2ed 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /obj/machinery/conveyor/inverted/Initialize(mapload) . = ..() if(mapload && !(dir in GLOB.diagonals)) - log_game("### MAPPING ERROR: [src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.") + log_mapping("[src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.") // Auto conveyour is always on unless unpowered diff --git a/code/modules/research/designs/nanite_designs.dm b/code/modules/research/designs/nanite_designs.dm index 177d7073a1..09fe1d9c9b 100644 --- a/code/modules/research/designs/nanite_designs.dm +++ b/code/modules/research/designs/nanite_designs.dm @@ -25,6 +25,20 @@ program_type = /datum/nanite_program/viral category = list("Utility Nanites") +/datum/design/nanites/research + name = "Distributed Computing" + desc = "The nanites aid the research servers by performing a portion of its calculations, increasing research point generation." + id = "research_nanites" + program_type = /datum/nanite_program/research + category = list("Utility Nanites") + +/datum/design/nanites/researchplus + name = "Neural Network" + desc = "The nanites link the host's brains together forming a neural research network, that becomes more efficient with the amount of total hosts. Can be overloaded to increase research output." + id = "researchplus_nanites" + program_type = /datum/nanite_program/researchplus + category = list("Utility Nanites") + /datum/design/nanites/monitoring name = "Monitoring" desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network." @@ -39,6 +53,13 @@ program_type = /datum/nanite_program/triggered/self_scan category = list("Utility Nanites") +/datum/design/nanites/dermal_button + name = "Dermal Button" + desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites." + id = "dermal_button_nanites" + program_type = /datum/nanite_program/dermal_button + category = list("Utility Nanites") + /datum/design/nanites/stealth name = "Stealth" desc = "The nanites hide their activity and programming from superficial scans." @@ -46,6 +67,15 @@ program_type = /datum/nanite_program/stealth category = list("Utility Nanites") + +/datum/design/nanites/reduced_diagnostics + name = "Reduced Diagnostics" + desc = "Disables some high-cost diagnostics in the nanites, making them unable to communicate their program list to portable scanners. \ + Doing so saves some power, slightly increasing their replication speed." + id = "red_diag_nanites" + program_type = /datum/nanite_program/reduced_diagnostics + category = list("Utility Nanites") + /datum/design/nanites/access name = "Subdermal ID" desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access." diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm index a269d01ac0..3db482d989 100644 --- a/code/modules/research/nanites/nanite_programs/utility.dm +++ b/code/modules/research/nanites/nanite_programs/utility.dm @@ -130,7 +130,7 @@ /datum/nanite_program/stealth name = "Stealth" - desc = "The nanites hide their activity and programming from superficial scans." + desc = "The nanites mask their activity from superficial scans, becoming undetectable by HUDs and non-specialized scanners." rogue_types = list(/datum/nanite_program/toxic) use_rate = 0.2 @@ -142,6 +142,22 @@ . = ..() nanites.stealth = FALSE +/datum/nanite_program/reduced_diagnostics + name = "Reduced Diagnostics" + desc = "Disables some high-cost diagnostics in the nanites, making them unable to communicate their program list to portable scanners. \ + Doing so saves some power, slightly increasing their replication speed." + rogue_types = list(/datum/nanite_program/toxic) + use_rate = -0.1 + +/datum/nanite_program/reduced_diagnostics/enable_passive_effect() + . = ..() + nanites.diagnostics = FALSE + +/datum/nanite_program/reduced_diagnostics/disable_passive_effect() + . = ..() + nanites.diagnostics = TRUE + + /datum/nanite_program/relay name = "Relay" desc = "The nanites receive and relay long-range nanite signals." @@ -271,3 +287,138 @@ if(fault == src) return fault.software_error() + +/datum/nanite_program/dermal_button + name = "Dermal Button" + desc = "Displays a button on the host's skin, which can be used to send a signal to the nanites." + extra_settings = list("Sent Code","Button Name","Icon","Color") + unique = FALSE + var/datum/action/innate/nanite_button/button + var/button_name = "Button" + var/icon = "power" + var/color = "green" + var/sent_code = 0 + +/datum/nanite_program/dermal_button/set_extra_setting(user, setting) + if(setting == "Sent Code") + var/new_code = input(user, "Set the sent code (1-9999):", name, null) as null|num + if(isnull(new_code)) + return + sent_code = CLAMP(round(new_code, 1), 1, 9999) + if(setting == "Button Name") + var/new_button_name = stripped_input(user, "Choose the name for the button.", "Button Name", button_name, MAX_NAME_LEN) + if(!new_button_name) + return + button_name = new_button_name + if(setting == "Icon") + var/new_icon = input("Select the icon to display on the button:", name) as null|anything in list("one","two","three","four","five","plus","minus","power") + if(!new_icon) + return + icon = new_icon + if(setting == "Color") + var/new_color = input("Select the color of the button's icon:", name) as null|anything in list("green","red","yellow","blue") + if(!new_color) + return + color = new_color + +/datum/nanite_program/dermal_button/get_extra_setting(setting) + if(setting == "Sent Code") + return sent_code + if(setting == "Button Name") + return button_name + if(setting == "Icon") + return capitalize(icon) + if(setting == "Color") + return capitalize(color) + +/datum/nanite_program/dermal_button/copy_extra_settings_to(datum/nanite_program/dermal_button/target) + target.sent_code = sent_code + target.button_name = button_name + target.icon = icon + target.color = color + +/datum/nanite_program/dermal_button/enable_passive_effect() + . = ..() + if(!button) + button = new(src, button_name, icon, color) + button.target = host_mob + button.Grant(host_mob) + +/datum/nanite_program/dermal_button/disable_passive_effect() + . = ..() + if(button) + button.Remove(host_mob) + +/datum/nanite_program/dermal_button/on_mob_remove() + . = ..() + qdel(button) + +/datum/nanite_program/dermal_button/proc/press() + if(activated) + host_mob.visible_message("[host_mob] presses a button on [host_mob.p_their()] forearm.", + "You press the nanite button on your forearm.", null, 2) + SEND_SIGNAL(host_mob, COMSIG_NANITE_SIGNAL, sent_code, "a [name] program") + +/datum/action/innate/nanite_button + name = "Button" + icon_icon = 'icons/mob/actions/actions_items.dmi' + check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + button_icon_state = "power_green" + var/datum/nanite_program/dermal_button/program + +/datum/action/innate/nanite_button/New(datum/nanite_program/dermal_button/_program, _name, _icon, _color) + ..() + program = _program + name = _name + button_icon_state = "[_icon]_[_color]" + +/datum/action/innate/nanite_button/Activate() + program.press() + +/datum/nanite_program/research + name = "Distributed Computing" + desc = "The nanites aid the research servers by performing a portion of its calculations, increasing research point generation." + use_rate = 0.2 + rogue_types = list(/datum/nanite_program/toxic) + +/datum/nanite_program/research/active_effect() + if(!iscarbon(host_mob)) + return + var/points = 1 + if(!host_mob.client) //less brainpower + points *= 0.25 + SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = points)) + +/datum/nanite_program/researchplus + name = "Neural Network" + desc = "The nanites link the host's brains together forming a neural research network, that becomes more efficient with the amount of total hosts." + use_rate = 0.3 + rogue_types = list(/datum/nanite_program/brain_decay) + +/datum/nanite_program/researchplus/enable_passive_effect() + . = ..() + if(!iscarbon(host_mob)) + return + if(host_mob.client) + SSnanites.neural_network_count++ + else + SSnanites.neural_network_count += 0.25 + +/datum/nanite_program/researchplus/disable_passive_effect() + . = ..() + if(!iscarbon(host_mob)) + return + if(host_mob.client) + SSnanites.neural_network_count-- + else + SSnanites.neural_network_count -= 0.25 + +/datum/nanite_program/researchplus/active_effect() + if(!iscarbon(host_mob)) + return + var/mob/living/carbon/C = host_mob + var/points = round(SSnanites.neural_network_count / 12, 0.1) + if(!C.client) //less brainpower + points *= 0.25 + SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = points)) + diff --git a/code/modules/research/nanites/program_disks.dm b/code/modules/research/nanites/program_disks.dm index 86b7803fc9..f780f40932 100644 --- a/code/modules/research/nanites/program_disks.dm +++ b/code/modules/research/nanites/program_disks.dm @@ -130,4 +130,16 @@ program_type = /datum/nanite_program/pacifying /obj/item/disk/nanite_program/stun - program_type = /datum/nanite_program/triggered/stun \ No newline at end of file + program_type = /datum/nanite_program/triggered/stun + +/obj/item/disk/nanite_program/dermal_button + program_type = /datum/nanite_program/dermal_button + +/obj/item/disk/nanite_program/research + program_type = /datum/nanite_program/research + +/obj/item/disk/nanite_program/researchplus + program_type = /datum/nanite_program/researchplus + +/obj/item/disk/nanite_program/reduced_diagnostics + program_type = /datum/nanite_program/reduced_diagnostics \ No newline at end of file diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 5f3fc0e85e..af4148d194 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -960,7 +960,7 @@ prereq_ids = list("datatheory","robotics") design_ids = list("nanite_disk","nanite_remote","nanite_scanner",\ "nanite_chamber","public_nanite_chamber","nanite_chamber_control","nanite_programmer","nanite_program_hub","nanite_cloud_control",\ - "relay_nanites", "monitoring_nanites", "access_nanites", "repairing_nanites","sensor_nanite_volume", "repeater_nanites", "relay_repeater_nanites") + "relay_nanites", "monitoring_nanites", "access_nanites", "repairing_nanites","sensor_nanite_volume", "repeater_nanites", "relay_repeater_nanites","red_diag_nanites") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) export_price = 5000 @@ -969,7 +969,7 @@ display_name = "Smart Nanite Programming" description = "Nanite programs that require nanites to perform complex actions, act independently, roam or seek targets." prereq_ids = list("nanite_base","adv_robotics") - design_ids = list("purging_nanites", "metabolic_nanites", "stealth_nanites", "memleak_nanites","sensor_voice_nanites", "voice_nanites") + design_ids = list("purging_nanites", "research_nanites", "metabolic_nanites", "stealth_nanites", "memleak_nanites","sensor_voice_nanites", "voice_nanites") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000) export_price = 4000 @@ -978,7 +978,7 @@ display_name = "Mesh Nanite Programming" description = "Nanite programs that require static structures and membranes." prereq_ids = list("nanite_base","engineering") - design_ids = list("hardening_nanites", "refractive_nanites", "cryo_nanites", "conductive_nanites", "shock_nanites", "emp_nanites", "temperature_nanites") + design_ids = list("hardening_nanites", "dermal_button_nanites", "refractive_nanites", "cryo_nanites", "conductive_nanites", "shock_nanites", "emp_nanites", "temperature_nanites") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) export_price = 5000 @@ -1015,7 +1015,7 @@ display_name = "Harmonic Nanite Programming" description = "Nanite programs that require seamless integration between nanites and biology." prereq_ids = list("nanite_bio","nanite_smart","nanite_mesh") - design_ids = list("fakedeath_nanites","aggressive_nanites","defib_nanites","regenerative_plus_nanites","brainheal_plus_nanites","purging_plus_nanites","adrenaline_nanites") + design_ids = list("fakedeath_nanites","researchplus_nanites","aggressive_nanites","defib_nanites","regenerative_plus_nanites","brainheal_plus_nanites","purging_plus_nanites","adrenaline_nanites") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 4000) export_price = 8000 diff --git a/code/modules/ruins/objects_and_mobs/sin_ruins.dm b/code/modules/ruins/objects_and_mobs/sin_ruins.dm index caa2ca7adf..76897b5276 100644 --- a/code/modules/ruins/objects_and_mobs/sin_ruins.dm +++ b/code/modules/ruins/objects_and_mobs/sin_ruins.dm @@ -108,7 +108,7 @@ if (levels.len) dest = locate(T.x, T.y, pick(levels)) - T.ChangeTurf(/turf/open/chasm) + T.ChangeTurf(/turf/open/chasm, flags = CHANGETURF_INHERIT_AIR) var/turf/open/chasm/C = T C.set_target(dest) C.drop(user) diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm index 3ebded7487..38a2d5bddd 100644 --- a/code/modules/spells/spell_types/conjure.dm +++ b/code/modules/spells/spell_types/conjure.dm @@ -31,7 +31,7 @@ if(ispath(summoned_object_type, /turf)) var/turf/O = spawn_place var/N = summoned_object_type - O.ChangeTurf(N) + O.ChangeTurf(N, flags = CHANGETURF_INHERIT_AIR) else var/atom/summoned_object = new summoned_object_type(spawn_place) diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm index 8b8328abbe..d97d466e4d 100644 --- a/code/modules/spells/spell_types/devil.dm +++ b/code/modules/spells/spell_types/devil.dm @@ -234,7 +234,7 @@ dancefloor_exists = FALSE for(var/i in 1 to dancefloor_turfs.len) var/turf/T = dancefloor_turfs[i] - T.ChangeTurf(dancefloor_turfs_types[i]) + T.ChangeTurf(dancefloor_turfs_types[i], flags = CHANGETURF_INHERIT_AIR) else var/list/funky_turfs = RANGE_TURFS(1, user) for(var/turf/closed/solid in funky_turfs) @@ -248,7 +248,7 @@ var/turf/T = t dancefloor_turfs[i] = T dancefloor_turfs_types[i] = T.type - T.ChangeTurf((i % 2 == 0) ? /turf/open/floor/light/colour_cycle/dancefloor_a : /turf/open/floor/light/colour_cycle/dancefloor_b) + T.ChangeTurf((i % 2 == 0) ? /turf/open/floor/light/colour_cycle/dancefloor_a : /turf/open/floor/light/colour_cycle/dancefloor_b, flags = CHANGETURF_INHERIT_AIR) i++ /datum/effect_system/smoke_spread/transparent/dancefloor_devil diff --git a/code/modules/vending/megaseed.dm b/code/modules/vending/megaseed.dm index 5c092c3659..03241c4a86 100644 --- a/code/modules/vending/megaseed.dm +++ b/code/modules/vending/megaseed.dm @@ -14,6 +14,7 @@ /obj/item/seeds/chanter = 3, /obj/item/seeds/chili = 3, /obj/item/seeds/cocoapod = 3, + /obj/item/seeds/coconut = 3, /obj/item/seeds/coffee = 3, /obj/item/seeds/cotton = 3, /obj/item/seeds/corn = 3, diff --git a/html/changelogs/AutoChangeLog-pr-10123.yml b/html/changelogs/AutoChangeLog-pr-10123.yml new file mode 100644 index 0000000000..208b17b930 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10123.yml @@ -0,0 +1,4 @@ +author: "kevinz000" +delete-after: True +changes: + - rscadd: "throwing things no longer makes them randomly turned as long as you aren't on harm intent" diff --git a/html/changelogs/AutoChangeLog-pr-10165.yml b/html/changelogs/AutoChangeLog-pr-10165.yml new file mode 100644 index 0000000000..be6f9dedf7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10165.yml @@ -0,0 +1,5 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed missing digi versions fishnet sprites and wrong digitigrade left dir purple stockings sprite." + - imageadd: "Add digitigrade versions for boxers and the long johns." diff --git a/html/changelogs/AutoChangeLog-pr-10189.yml b/html/changelogs/AutoChangeLog-pr-10189.yml new file mode 100644 index 0000000000..5889e127d2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10189.yml @@ -0,0 +1,5 @@ +author: "Putnam3145" +delete-after: True +changes: + - rscadd: "Dynamic storytellers, a new voting paradigm for dynamic" + - rscadd: "Support for approval voting and condorcet (ranked choice) voting in server votes" diff --git a/html/changelogs/AutoChangeLog-pr-10238.yml b/html/changelogs/AutoChangeLog-pr-10238.yml new file mode 100644 index 0000000000..dda40c86c9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10238.yml @@ -0,0 +1,7 @@ +author: "SpaceManiac, bobbahbrown, ShizCalev, SpaceManiac (ported by Ghommie)" +delete-after: True +changes: + - code_imp: "It is now possible to set a different most-base-turf per z-level." + - spellcheck: "Removed unlawful reference to Disney's Star Wars franchise in map logging." + - tweak: "Moved mapping related errors to their own log file." + - bugfix: "Destruction on Lavaland will no longer reveal space in rare situations." diff --git a/html/changelogs/AutoChangeLog-pr-10270.yml b/html/changelogs/AutoChangeLog-pr-10270.yml new file mode 100644 index 0000000000..b47a249f9a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10270.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed the secret sauce recipe being randomized every round." diff --git a/html/changelogs/AutoChangeLog-pr-10309.yml b/html/changelogs/AutoChangeLog-pr-10309.yml new file mode 100644 index 0000000000..a59ebff156 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10309.yml @@ -0,0 +1,4 @@ +author: "Seris02" +delete-after: True +changes: + - rscadd: "disabler sechuds" diff --git a/html/changelogs/AutoChangeLog-pr-10319.yml b/html/changelogs/AutoChangeLog-pr-10319.yml new file mode 100644 index 0000000000..88235aab8e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10319.yml @@ -0,0 +1,5 @@ +author: "Seris02" +delete-after: True +changes: + - rscadd: "adds coconut" + - rscadd: "adds a coconut bong" diff --git a/html/changelogs/AutoChangeLog-pr-10330.yml b/html/changelogs/AutoChangeLog-pr-10330.yml new file mode 100644 index 0000000000..b2a46ef605 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10330.yml @@ -0,0 +1,4 @@ +author: "keronshb" +delete-after: True +changes: + - rscadd: "Adds new features for nanites" diff --git a/html/changelogs/AutoChangeLog-pr-10338.yml b/html/changelogs/AutoChangeLog-pr-10338.yml new file mode 100644 index 0000000000..c4816a3fbe --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10338.yml @@ -0,0 +1,4 @@ +author: "Seris02" +delete-after: True +changes: + - rscadd: "marshmallow" diff --git a/html/changelogs/AutoChangeLog-pr-10344.yml b/html/changelogs/AutoChangeLog-pr-10344.yml new file mode 100644 index 0000000000..e0381c6a43 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10344.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Chestbursters won't delete the host's brain somewhat anymore." diff --git a/html/changelogs/AutoChangeLog-pr-10345.yml b/html/changelogs/AutoChangeLog-pr-10345.yml new file mode 100644 index 0000000000..a95653cdda --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10345.yml @@ -0,0 +1,4 @@ +author: "Trilbyspaceclone" +delete-after: True +changes: + - tweak: "allows bandoliers to hold any ammo type as long as it has a casing" diff --git a/html/changelogs/AutoChangeLog-pr-10349.yml b/html/changelogs/AutoChangeLog-pr-10349.yml new file mode 100644 index 0000000000..fad3179c6e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10349.yml @@ -0,0 +1,6 @@ +author: "KathrinBailey" +delete-after: True +changes: + - rscadd: "Empty engineering lockers for mappers." + - rscadd: "Industrial welding tools to the engineer welding locker." + - rscdel: "Removed the multitool, airlock painter, mechanical toolbox, brown sneakers, hazard vest and airlock painter from the CE's locker." diff --git a/html/changelogs/AutoChangeLog-pr-10356.yml b/html/changelogs/AutoChangeLog-pr-10356.yml new file mode 100644 index 0000000000..1016b559ee --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10356.yml @@ -0,0 +1,5 @@ +author: "Bhijn" +delete-after: True +changes: + - rscadd: "Added a preference to make the sprint hotkey be a toggle instead of a hold bind" + - rscadd: "Added a preference to bind the sprint hotkey to space instead of shift." diff --git a/html/changelogs/AutoChangeLog-pr-10361.yml b/html/changelogs/AutoChangeLog-pr-10361.yml new file mode 100644 index 0000000000..0224bdcb49 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10361.yml @@ -0,0 +1,4 @@ +author: "CameronWoof" +delete-after: True +changes: + - rscadd: "Ghost Cafe patrons now spawn with chameleon kits. Dress up! Be fancy!" diff --git a/html/changelogs/AutoChangeLog-pr-10362.yml b/html/changelogs/AutoChangeLog-pr-10362.yml new file mode 100644 index 0000000000..f92e16302a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10362.yml @@ -0,0 +1,4 @@ +author: "Trilbyspaceclone" +delete-after: True +changes: + - bugfix: "rouge cases of #$39; in bottle/pill/patch/condiments" diff --git a/html/changelogs/AutoChangeLog-pr-10364.yml b/html/changelogs/AutoChangeLog-pr-10364.yml new file mode 100644 index 0000000000..8834c494ad --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10364.yml @@ -0,0 +1,4 @@ +author: "kevinz000" +delete-after: True +changes: + - bugfix: "telescopic iv drips now have the proper sanity checks for deployment." diff --git a/html/changelogs/AutoChangeLog-pr-10365.yml b/html/changelogs/AutoChangeLog-pr-10365.yml new file mode 100644 index 0000000000..f7ba047fc7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10365.yml @@ -0,0 +1,4 @@ +author: "Putnam3145" +delete-after: True +changes: + - bugfix: "no ass slap is no longer the same thing as no aphro" diff --git a/html/changelogs/AutoChangeLog-pr-10366.yml b/html/changelogs/AutoChangeLog-pr-10366.yml new file mode 100644 index 0000000000..ec8d234edd --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10366.yml @@ -0,0 +1,5 @@ +author: "Hatterhat" +delete-after: True +changes: + - balance: "Beakers are generally more useful now, with slight capacity increases." + - tweak: "Transfer amounts are different now. Adjust your muscle memory to compensate." diff --git a/html/changelogs/AutoChangeLog-pr-10368.yml b/html/changelogs/AutoChangeLog-pr-10368.yml new file mode 100644 index 0000000000..e6486f2815 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10368.yml @@ -0,0 +1,4 @@ +author: "Hatterhat" +delete-after: True +changes: + - balance: "ore redemption machines actually get affected by lasers again kthx" diff --git a/html/changelogs/AutoChangeLog-pr-10369.yml b/html/changelogs/AutoChangeLog-pr-10369.yml new file mode 100644 index 0000000000..c292d948fb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10369.yml @@ -0,0 +1,4 @@ +author: "Hatterhat" +delete-after: True +changes: + - tweak: "crusher trophy drop chance on mining mobs increased to 1 in 4 (from 1 in 20)" diff --git a/html/changelogs/AutoChangeLog-pr-10370.yml b/html/changelogs/AutoChangeLog-pr-10370.yml new file mode 100644 index 0000000000..6674fd2e17 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10370.yml @@ -0,0 +1,4 @@ +author: "Hatterhat" +delete-after: True +changes: + - bugfix: "Blood-drunk buff from blood-drunk eye crusher trophy is less likely to cripple its user." diff --git a/html/changelogs/AutoChangeLog-pr-10371.yml b/html/changelogs/AutoChangeLog-pr-10371.yml new file mode 100644 index 0000000000..e4322d70f1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10371.yml @@ -0,0 +1,4 @@ +author: "kappa-sama" +delete-after: True +changes: + - rscadd: "loot crates in cargo contraband" diff --git a/html/changelogs/AutoChangeLog-pr-10372.yml b/html/changelogs/AutoChangeLog-pr-10372.yml new file mode 100644 index 0000000000..6bb60c3d8d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10372.yml @@ -0,0 +1,5 @@ +author: "Hatterhat" +delete-after: True +changes: + - tweak: "Forcefield projectors now fit on toolbelts." + - imageadd: "New sprites for ATMOS holofan and forcefield projectors!" diff --git a/html/changelogs/AutoChangeLog-pr-10374.yml b/html/changelogs/AutoChangeLog-pr-10374.yml new file mode 100644 index 0000000000..376f49a4d4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10374.yml @@ -0,0 +1,4 @@ +author: "Bhijn helped" +delete-after: True +changes: + - bugfix: "Fixes Dragon's Tooth Sword 50% armor penetration by making it 35%" diff --git a/html/changelogs/AutoChangeLog-pr-10379.yml b/html/changelogs/AutoChangeLog-pr-10379.yml new file mode 100644 index 0000000000..9623f815d6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10379.yml @@ -0,0 +1,4 @@ +author: "Ryll/Shaps, ported by Hatterhat" +delete-after: True +changes: + - rscadd: "Point-blanking people with shotguns actually throws them backwards!" diff --git a/html/changelogs/AutoChangeLog-pr-10383.yml b/html/changelogs/AutoChangeLog-pr-10383.yml new file mode 100644 index 0000000000..c6787bbafe --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10383.yml @@ -0,0 +1,4 @@ +author: "kevinz000" +delete-after: True +changes: + - bugfix: "megafauna can hear again" diff --git a/html/changelogs/AutoChangeLog-pr-10388.yml b/html/changelogs/AutoChangeLog-pr-10388.yml new file mode 100644 index 0000000000..fd06a5db3f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10388.yml @@ -0,0 +1,4 @@ +author: "CameronWoof" +delete-after: True +changes: + - rscadd: "Robots can now check the crew manifest from anywhere with the \"View Crew Manifest\" verb." diff --git a/html/changelogs/AutoChangeLog-pr-9856.yml b/html/changelogs/AutoChangeLog-pr-9856.yml new file mode 100644 index 0000000000..697c1c78f5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-9856.yml @@ -0,0 +1,5 @@ +author: "Ghommie" +delete-after: True +changes: + - rscadd: "Refactored code to allow all living mobs to use shields and not only humans." + - tweak: "Monkys will now retaliate against aliens attacking them (as if they even posed a threat to start with)." diff --git a/icons/mob/underwear.dmi b/icons/mob/underwear.dmi index bf0df371eb..e0789a1eeb 100644 Binary files a/icons/mob/underwear.dmi and b/icons/mob/underwear.dmi differ diff --git a/icons/mob/underwear_old.dmi b/icons/mob/underwear_old.dmi deleted file mode 100644 index 5e16a473ba..0000000000 Binary files a/icons/mob/underwear_old.dmi and /dev/null differ diff --git a/icons/obj/bongs.dmi b/icons/obj/bongs.dmi new file mode 100644 index 0000000000..406cce3817 Binary files /dev/null and b/icons/obj/bongs.dmi differ diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index f467da6fbf..73eb12fd6a 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index 6b29d599cc..a719150f11 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi index 469b1e1aff..712ea11a3b 100644 Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ diff --git a/icons/obj/hydroponics/harvest.dmi b/icons/obj/hydroponics/harvest.dmi index a57719fb3a..e746d8f43d 100644 Binary files a/icons/obj/hydroponics/harvest.dmi and b/icons/obj/hydroponics/harvest.dmi differ diff --git a/icons/obj/hydroponics/seeds.dmi b/icons/obj/hydroponics/seeds.dmi index 8695d03b3c..43e231cbab 100644 Binary files a/icons/obj/hydroponics/seeds.dmi and b/icons/obj/hydroponics/seeds.dmi differ diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi index 8ede8c66ab..8fee8fcf9f 100644 Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm index a1466f58d4..98de5eed14 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm @@ -424,12 +424,7 @@ SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! if(A) if(isliving(A)) var/mob/living/L = A - var/blocked = 0 - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) - blocked = 1 - if(!blocked) + if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") L.Knockdown(iscarbon(L) ? 60 : 45, override_stamdmg = CLAMP(pounce_stamloss, 0, pounce_stamloss_cap-L.getStaminaLoss())) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm index d7ff42a9eb..7e094eddda 100644 --- a/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm +++ b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm @@ -54,7 +54,7 @@ I'd like to point out from my calculations it'll take about 60-80 minutes to die /datum/reagent/fermi/astral/reaction_turf(turf/T, reac_volume) if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel)) var/turf/open/floor/F = T - F.PlaceOnTop(/turf/open/floor/fakespace) + F.PlaceOnTop(/turf/open/floor/fakespace, flags = CHANGETURF_INHERIT_AIR) ..() /datum/reagent/fermi/astral/reaction_obj(obj/O, reac_volume) diff --git a/sound/effects/bonghit.ogg b/sound/effects/bonghit.ogg new file mode 100644 index 0000000000..45a0dec1b6 Binary files /dev/null and b/sound/effects/bonghit.ogg differ diff --git a/sound/weapons/klonk.ogg b/sound/weapons/klonk.ogg new file mode 100644 index 0000000000..471f3ad8b7 Binary files /dev/null and b/sound/weapons/klonk.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 4825a75fc5..a11e5e9360 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -43,6 +43,7 @@ #include "code\__DEFINES\diseases.dm" #include "code\__DEFINES\DNA.dm" #include "code\__DEFINES\donator_groupings.dm" +#include "code\__DEFINES\dynamic.dm" #include "code\__DEFINES\events.dm" #include "code\__DEFINES\exports.dm" #include "code\__DEFINES\fantasy_affixes.dm" @@ -108,6 +109,7 @@ #include "code\__DEFINES\typeids.dm" #include "code\__DEFINES\vehicles.dm" #include "code\__DEFINES\voreconstants.dm" +#include "code\__DEFINES\vote.dm" #include "code\__DEFINES\vv.dm" #include "code\__DEFINES\wall_dents.dm" #include "code\__DEFINES\wires.dm" @@ -580,6 +582,7 @@ #include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm" #include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm" +#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm" #include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\gangs\dominator.dm" #include "code\game\gamemodes\gangs\dominator_countdown.dm" @@ -1600,6 +1603,7 @@ #include "code\modules\clothing\clothing.dm" #include "code\modules\clothing\ears\_ears.dm" #include "code\modules\clothing\glasses\_glasses.dm" +#include "code\modules\clothing\glasses\disablerglasses.dm" #include "code\modules\clothing\glasses\engine_goggles.dm" #include "code\modules\clothing\glasses\hud.dm" #include "code\modules\clothing\glasses\phantomthief.dm"