diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm index f4e7bbd43cc51..15bcb272d73f0 100644 --- a/_maps/map_files/NebulaStation/NebulaStation.dmm +++ b/_maps/map_files/NebulaStation/NebulaStation.dmm @@ -23929,6 +23929,13 @@ /obj/effect/turf_decal/delivery, /turf/open/floor/iron/dark, /area/station/command/teleporter) +"dAP" = ( +/obj/effect/turf_decal/delivery, +/obj/machinery/mecha_part_fabricator{ + drop_direction = 1 + }, +/turf/open/floor/iron/dark, +/area/station/science/robotics/lab) "dAT" = ( /obj/effect/spawner/random/entertainment/toy_figure{ pixel_y = 37; @@ -141153,7 +141160,7 @@ /area/station/hallway/primary/central) "uYT" = ( /obj/machinery/mecha_part_fabricator{ - dir = 1 + drop_direction = 1 }, /obj/effect/turf_decal/delivery, /turf/open/floor/iron/dark, @@ -263701,7 +263708,7 @@ juD iZk vqW jyY -uYT +dAP xGM mVH xGM diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 84cfbc8baa918..ba43900425bde 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -141,6 +141,8 @@ #define COMSIG_LIVING_SLAM_TABLE "living_slam_table" ///from /obj/item/hand_item/slapper/attack(): (source=mob/living/slapper, mob/living/slapped) #define COMSIG_LIVING_SLAP_MOB "living_slap_mob" +///from /obj/item/hand_item/slapper/attack(): (source=mob/living/slapper, mob/living/slapped) +#define COMSIG_LIVING_SLAPPED "living_slapped" /// from /mob/living/*/UnarmedAttack(), before sending [COMSIG_LIVING_UNARMED_ATTACK]: (mob/living/source, atom/target, proximity, modifiers) /// The only reason this exists is so hulk can fire before Fists of the North Star. /// Note that this is called before [/mob/living/proc/can_unarmed_attack] is called, so be wary of that. @@ -288,6 +290,8 @@ ///From mob/living/carbon/proc/throw_mode_on and throw_mode_off #define COMSIG_LIVING_THROW_MODE_TOGGLE "living_throw_mode_toggle" +/// From mob/living/proc/on_fall +#define COMSIG_LIVING_THUD "living_thud" ///From /datum/component/happiness() #define COMSIG_MOB_HAPPINESS_CHANGE "happiness_change" /// From /obj/item/melee/baton/baton_effect(): (datum/source, mob/living/user, /obj/item/melee/baton) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 69ccaa5fa641b..7e7a5f17837ac 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -393,6 +393,7 @@ ///sent to targets during the process_hit proc of projectiles #define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" #define PROJECTILE_INTERRUPT_HIT (1<<0) + #define PROJECTILE_INTERRUPT_HIT_PHASE (1<<1) ///from /obj/projectile/process_movement(): () #define COMSIG_PROJECTILE_MOVE_PROCESS_STEP "projectile_move_process_step" ///sent to self during the process_hit proc of projectiles diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index c64a50cd077df..efa29f98dc2a0 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -166,7 +166,7 @@ ///The breeding timeout for newly instantiated fish is multiplied by this. #define NEW_FISH_BREEDING_TIMEOUT_MULT 2 ///The last feeding timestamp of newly instantiated fish is multiplied by this: ergo, they spawn 50% hungry. -#define NEW_FISH_LAST_FEEDING_MULT 0.5 +#define NEW_FISH_LAST_FEEDING_MULT 0.33 //IF YOU ADD ANY NEW FLAG, ADD IT TO THE RESPECTIVE BITFIELD in _globalvars/bitfields.dm TOO! @@ -187,7 +187,6 @@ ///Flag added when in an aquarium with the right fluid type. #define FISH_FLAG_SAFE_FLUID (1<<7) - #define MIN_AQUARIUM_TEMP T0C #define MAX_AQUARIUM_TEMP (T0C + 100) #define DEFAULT_AQUARIUM_TEMP (T0C + 24) @@ -260,11 +259,8 @@ #define FISH_SOURCE_FLAG_EXPLOSIVE_MALUS (1<<0) /// The fish source is not elegible for random rewards from bluespace fishing rods #define FISH_SOURCE_FLAG_NO_BLUESPACE_ROD (1<<1) -/** - * If present, fish that can be caught from this source won't be included in the 'fish caught' score, unless - * present in other sources without this flag as well. - */ -#define FISH_SOURCE_FLAG_SKIP_CATCHABLES (1<<2) +/// When examined by someone with enough fishing skill, this will also display fish that doesn't have FISH_FLAG_SHOW_IN_CATALOG +#define FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG (1<<2) /** * A macro to ensure the wikimedia filenames of fish icons are unique, especially since there're a couple fish that have diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index 87f7ba50f7576..3e9b0bcc33c6f 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -163,7 +163,6 @@ DEFINE_BITFIELD(no_equip_flags, list( /// The sprite works fine for digitigrade legs as-is. #define CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON (1<<2) /// Auto-generates the leg portion of the sprite with GAGS -/// Suggested that you set [/obj/item/var/digitigrade_greyscale_config_worn] when using this flag #define CLOTHING_DIGITIGRADE_MASK (1<<3) /// All variation flags which render "correctly" on a digitigrade leg setup diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index a1496d2e13876..11182e1ea2f6e 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -73,6 +73,8 @@ #define MOB_PLANT (1 << 10) ///The mob is a goopy creature, probably coming from xenobiology. #define MOB_SLIME (1 << 11) +/// Mob is fish or water-related. +#define MOB_AQUATIC (1 << 12) //Lung respiration type flags #define RESPIRATION_OXYGEN (1 << 0) diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 756d78c172353..3642973721049 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -12,6 +12,11 @@ /// Use in status effect "tick_interval" to prevent it from calling tick() #define STATUS_EFFECT_NO_TICK -1 +/// Indicates this status effect is an abstract type, ie not instantiated +/// Doesn't actually do anything in practice, primarily just a marker / used in unit tests, +/// so don't worry if your abstract status effect doesn't actually set this +#define STATUS_EFFECT_ID_ABSTRACT "abstract" + ///Processing flags - used to define the speed at which the status will work ///This is fast - 0.2s between ticks (I believe!) #define STATUS_EFFECT_FAST_PROCESS 0 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index eb623ed708ec9..21c239c59b914 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -51,6 +51,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_EMOTEMUTE "emotemute" #define TRAIT_DEAF "deaf" #define TRAIT_FAT "fat" +/// Always hungry. They can eat as much as they want without eating slowdown. +#define TRAIT_GLUTTON "glutton" #define TRAIT_HUSK "husk" ///Blacklisted from being revived via defibrillator #define TRAIT_DEFIB_BLACKLISTED "defib_blacklisted" @@ -507,6 +509,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_USER_SCOPED "user_scoped" /// Mob is unable to feel pain #define TRAIT_ANALGESIA "analgesia" +/// Mob does not get a damage overlay from brute/burn +#define TRAIT_NO_DAMAGE_OVERLAY "no_damage_overlay" /// Mob has a scar on their left/right eye #define TRAIT_RIGHT_EYE_SCAR "right_eye_scar" #define TRAIT_LEFT_EYE_SCAR "left_eye_scar" diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 76651964e24e0..efee782aaeb8d 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -30,6 +30,9 @@ /proc/cmp_name_dsc(atom/a, atom/b) return sorttext(a.name, b.name) +/proc/cmp_init_name_asc(atom/a, atom/b) + return sorttext(initial(b.name), initial(a.name)) + /proc/cmp_records_asc(datum/record/a, datum/record/b) return sorttext(b.name, a.name) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index c54d78421a304..f5d79447f56a2 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -249,6 +249,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_FAST_CUFFING" = TRAIT_FAST_CUFFING, "TRAIT_FAST_TYING" = TRAIT_FAST_TYING, "TRAIT_FAT" = TRAIT_FAT, + "TRAIT_GLUTTON" = TRAIT_GLUTTON, "TRAIT_FEARLESS" = TRAIT_FEARLESS, "TRAIT_FENCE_CLIMBER" = TRAIT_FENCE_CLIMBER, "TRAIT_FINGERPRINT_PASSTHROUGH" = TRAIT_FINGERPRINT_PASSTHROUGH, @@ -380,6 +381,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NOSOFTCRIT" = TRAIT_NOSOFTCRIT, "TRAIT_NO_AUGMENTS" = TRAIT_NO_AUGMENTS, "TRAIT_NO_BLOOD_OVERLAY" = TRAIT_NO_BLOOD_OVERLAY, + "TRAIT_NO_DAMAGE_OVERLAY" = TRAIT_NO_DAMAGE_OVERLAY, "TRAIT_NO_DEBRAIN_OVERLAY" = TRAIT_NO_DEBRAIN_OVERLAY, "TRAIT_NO_DNA_COPY" = TRAIT_NO_DNA_COPY, "TRAIT_NO_DNA_SCRAMBLE" = TRAIT_NO_DNA_SCRAMBLE, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 7b7ca780a5409..5a3512f1d8f20 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -106,6 +106,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_FAST_CUFFING" = TRAIT_FAST_CUFFING, "TRAIT_FAST_TYING" = TRAIT_FAST_TYING, "TRAIT_FAT" = TRAIT_FAT, + "TRAIT_GLUTTON" = TRAIT_GLUTTON, "TRAIT_FEARLESS" = TRAIT_FEARLESS, "TRAIT_FENCE_CLIMBER" = TRAIT_FENCE_CLIMBER, "TRAIT_FISH_EATER" = TRAIT_FISH_EATER, diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index ec7070237d333..cbe26c0046be3 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -957,6 +957,10 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen/splash) state = HUNGER_STATE_FAT return + if(HAS_TRAIT(hungry, TRAIT_GLUTTON)) + state = HUNGER_STATE_HUNGRY // Can't get enough + return + switch(hungry.nutrition) if(NUTRITION_LEVEL_FULL to INFINITY) state = HUNGER_STATE_FULL diff --git a/code/controllers/subsystem/processing/fishing.dm b/code/controllers/subsystem/processing/fishing.dm index 477dbf35e51d2..3f0433b21b9e8 100644 --- a/code/controllers/subsystem/processing/fishing.dm +++ b/code/controllers/subsystem/processing/fishing.dm @@ -19,10 +19,13 @@ PROCESSING_SUBSYSTEM_DEF(fishing) cached_fish_icons = list() cached_unknown_fish_icons = list() fish_properties = list() + catchable_fish = list() var/icon/questionmark = icon('icons/effects/random_spawners.dmi', "questionmark") var/list/mark_dimension = get_icon_dimensions(questionmark) - for(var/obj/item/fish/fish_type as anything in subtypesof(/obj/item/fish)) + var/list/spawned_fish = list() + var/list/fish_subtypes = sortTim(subtypesof(/obj/item/fish), GLOBAL_PROC_REF(cmp_init_name_asc)) + for(var/obj/item/fish/fish_type as anything in fish_subtypes) var/list/fish_dimensions = get_icon_dimensions(fish_type::icon) var/icon/fish_icon = icon(fish_type::icon, fish_type::icon_state, frame = 1, moving = FALSE) cached_fish_icons[fish_type] = icon2base64(fish_icon) @@ -35,6 +38,7 @@ PROCESSING_SUBSYSTEM_DEF(fishing) cached_unknown_fish_icons[fish_type] = icon2base64(unknown_icon) var/obj/item/fish/fish = new fish_type(null, FALSE) + spawned_fish += fish var/list/properties = list() fish_properties[fish_type] = properties properties[FISH_PROPERTIES_FAV_BAIT] = fish.favorite_bait.Copy() @@ -67,36 +71,35 @@ PROCESSING_SUBSYSTEM_DEF(fishing) properties[FISH_PROPERTIES_BEAUTY_SCORE] = beauty_score - qdel(fish) - - catchable_fish = list() - var/list/all_catchables = list() - for(var/source_type as anything in GLOB.preset_fish_sources) - var/datum/fish_source/source = GLOB.preset_fish_sources[source_type] - if(!(source.fish_source_flags & FISH_SOURCE_FLAG_SKIP_CATCHABLES)) - all_catchables |= source.fish_table - for(var/thing in all_catchables) - if(!ispath(thing, /obj/item/fish)) - continue - var/obj/item/fish/fishie = thing - var/fish_id = initial(fishie.fish_id) + var/fish_id + if(fish.fish_id_redirect_path) + var/obj/item/fish/other_path = fish.fish_id_redirect_path + if(!ispath(other_path, /obj/item/fish)) + stack_trace("[fish.type] has a set 'fish_id_redirect_path' variable but it isn't a fish path but [other_path]") + continue + fish_id = initial(other_path.fish_id) + else + fish_id = fish.fish_id if(!fish_id) - stack_trace("[fishie] doesn't have a set 'fish_id' variable despite being a catchable fish") + stack_trace("[fish.type] doesn't have a set 'fish_id' variable despite being a catchable fish") + continue + if(fish.fish_id_redirect_path) continue if(catchable_fish[fish_id]) - stack_trace("[fishie] has a 'fish_id' value already assigned to [catchable_fish[fish_id]]. fish_id: [fish_id]") + stack_trace("[fish.type] has a 'fish_id' value already assigned to [catchable_fish[fish_id]]. fish_id: [fish_id]") continue - catchable_fish[fish_id] = fishie + catchable_fish[fish_id] = fish.type ///init the list of things lures can catch lure_catchables = list() - var/list/fish_types = subtypesof(/obj/item/fish) for(var/lure_type in typesof(/obj/item/fishing_lure)) var/obj/item/fishing_lure/lure = new lure_type lure_catchables[lure_type] = list() - for(var/obj/item/fish/fish_type as anything in fish_types) - if(lure.is_catchable_fish(fish_type, fish_properties[fish_type])) - lure_catchables[lure_type] += fish_type + for(var/obj/item/fish/fish as anything in spawned_fish) + if(lure.is_catchable_fish(fish, fish_properties[fish.type])) + lure_catchables[lure_type] += fish.type qdel(lure) + QDEL_LIST(spawned_fish) + return SS_INIT_SUCCESS diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 0ad0a78589221..4f573ee2f2224 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -356,6 +356,13 @@ SUBSYSTEM_DEF(shuttle) return TRUE +/** + * Calls the emergency shuttle. + * + * Arguments: + * * user - The mob that called the shuttle. + * * call_reason - The reason the shuttle was called, which should be non-html-encoded text. + */ /datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason) if (!check_backup_emergency_shuttle()) return diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 85def333f4c26..2887fc55f8dc4 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -470,13 +470,13 @@ for(var/component_key in dc) var/component_or_list = dc[component_key] if(islist(component_or_list)) - for(var/datum/component/I in component_or_list) - if(I.can_transfer) - target.TakeComponent(I) + for(var/datum/component/component in component_or_list) + if(component.can_transfer) + target.TakeComponent(component) else - var/datum/component/C = component_or_list - if(C.can_transfer) - target.TakeComponent(C) + var/datum/component/component = component_or_list + if(!QDELETED(component) && component.can_transfer) + target.TakeComponent(component) /** * Return the object that is the host of any UI's that this component has diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm index e0ef6d8c04aa9..cf86c6b56b470 100644 --- a/code/datums/components/aquarium.dm +++ b/code/datums/components/aquarium.dm @@ -118,6 +118,11 @@ RegisterSignal(movable, COMSIG_ATOM_UI_INTERACT, PROC_REF(interact)) movable.AddElement(/datum/element/relay_attackers) + movable.AddComponent(/datum/component/fishing_spot, /datum/fish_source/aquarium) + + + movable.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1 + RegisterSignal(movable, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) for(var/atom/movable/content as anything in movable.contents) if(content.flags_1 & INITIALIZED_1) @@ -143,6 +148,7 @@ COMSIG_ATOM_ATTACK_ROBOT_SECONDARY, COMSIG_ATOM_ATTACK_HAND_SECONDARY, COMSIG_ATOM_UI_INTERACT, + COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, )) if(movable.reagents) UnregisterSignal(movable, COMSIG_REAGENTS_NEW_REAGENT) @@ -150,6 +156,7 @@ beauty_by_content = null tracked_fish_by_type = null movable.remove_traits(list(TRAIT_IS_AQUARIUM, TRAIT_AQUARIUM_PANEL_OPEN, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH), AQUARIUM_TRAIT) + qdel(movable.GetComponent(/datum/component/fishing_spot)) REMOVE_KEEP_TOGETHER(movable, AQUARIUM_TRAIT) /datum/component/aquarium/PreTransfer(atom/movable/new_parent) @@ -214,7 +221,7 @@ source.balloon_alert(user, "fed the fish") return ITEM_INTERACT_SUCCESS - if(!HAS_TRAIT(item, TRAIT_AQUARIUM_CONTENT)) + if(!HAS_TRAIT(item, TRAIT_AQUARIUM_CONTENT) || (!isitem(parent) && user.combat_mode)) return //proceed with normal interactions var/broken = source.get_integrity_percentage() <= source.integrity_failure @@ -238,6 +245,10 @@ ///Feed the fish at defined intervals until the feed storage is empty. /datum/component/aquarium/process(seconds_per_tick) + //safe mode, no need to feed the fishes + if(HAS_TRAIT_FROM(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)) + last_feeding += seconds_per_tick SECONDS + return var/atom/movable/movable = parent if(!movable.reagents?.total_volume) if(movable.reagents) @@ -253,6 +264,7 @@ /datum/component/aquarium/proc/on_plunger_act(atom/movable/source, obj/item/plunger/plunger, mob/living/user, reinforced) SIGNAL_HANDLER if(!HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN)) + source.balloon_alert(user, "open panel first!") return INVOKE_ASYNC(src, PROC_REF(do_plunging), source, user) return COMPONENT_NO_AFTERATTACK @@ -461,7 +473,7 @@ var/atom/movable/aquarium = parent .["fluidType"] = fluid_type .["temperature"] = fluid_temp - .["allowBreeding"] = HAS_TRAIT_FROM(aquarium, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) + .["safe_mode"] = !HAS_TRAIT_FROM(aquarium, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) .["fishData"] = list() .["feedingInterval"] = feeding_interval / (1 MINUTES) .["propData"] = list() @@ -510,8 +522,8 @@ if(params["fluid"] != fluid_type && (params["fluid"] in fluid_types)) set_fluid_type(params["fluid"]) . = TRUE - if("allow_breeding") - if(HAS_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) + if("safe_mode") + if(HAS_TRAIT_FROM(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)) REMOVE_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) else ADD_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) @@ -561,5 +573,26 @@ else if(dead_fish > 0) user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_good : /datum/mood_event/aquarium_negative) +/datum/component/aquarium/proc/on_requesting_context_from_item(atom/source, list/context, obj/item/held_item, mob/user) + SIGNAL_HANDLER + var/open_panel = HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN) + if(!held_item) + var/isitem = isitem(source) + if(!isitem || open_panel) + context[SCREENTIP_CONTEXT_LMB] = open_panel ? "Adjust settings" : "Admire" + if(isitem) + context[SCREENTIP_CONTEXT_RMB] = "Admire" + context[SCREENTIP_CONTEXT_ALT_LMB] = "[open_panel ? "Open" : "Close"] settings panel" + return CONTEXTUAL_SCREENTIP_SET + if(istype(held_item, /obj/item/plunger)) + context[SCREENTIP_CONTEXT_LMB] = "Empty feed storage" + return CONTEXTUAL_SCREENTIP_SET + if(istype(held_item, /obj/item/reagent_containers/cup/fish_feed) && (!source.reagents || !open_panel)) + context[SCREENTIP_CONTEXT_LMB] = "Feed fishes" + return CONTEXTUAL_SCREENTIP_SET + if(HAS_TRAIT(held_item, TRAIT_AQUARIUM_CONTENT)) + context[SCREENTIP_CONTEXT_LMB] = "Insert in aquarium" + return CONTEXTUAL_SCREENTIP_SET + #undef MIN_AQUARIUM_BEAUTY #undef MAX_AQUARIUM_BEAUTY diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm index 461cfcff3aae4..32ec8177f467a 100644 --- a/code/datums/components/bloodysoles.dm +++ b/code/datums/components/bloodysoles.dm @@ -108,7 +108,7 @@ set_bloody_shoes(pool.blood_state, new_our_bloodiness) pool.bloodiness = total_bloodiness - new_our_bloodiness // Give the pool the remaining blood incase we were limited - if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP)) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor + if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP) || (wielder && HAS_TRAIT(wielder, TRAIT_LIGHT_STEP))) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor return TRUE parent_atom.add_blood_DNA(GET_ATOM_BLOOD_DNA(pool)) diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 3dc99cd8067de..3ce68dce33959 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -41,7 +41,7 @@ if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - if(!fish_source.has_known_fishes()) + if(!fish_source.has_known_fishes(source)) return examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 7cf5027b80953..f9d29680ec211 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -369,12 +369,11 @@ Behavior that's still missing from this component that original food items had t var/fullness = eater.get_fullness() + 10 //The theoretical fullness of the person eating if they were to eat this var/time_to_eat = (eater == feeder) ? eat_time : EAT_TIME_FORCE_FEED - if(HAS_TRAIT(eater, TRAIT_VORACIOUS)) + if(HAS_TRAIT(eater, TRAIT_VORACIOUS) && !HAS_TRAIT(eater, TRAIT_GLUTTON)) //with TRAIT_GLUTTON you consume food without delay if(fullness < NUTRITION_LEVEL_FAT || (eater != feeder)) // No extra delay when being forcefed time_to_eat *= EAT_TIME_VORACIOUS_MULT else time_to_eat *= (fullness / NUTRITION_LEVEL_FAT) * EAT_TIME_VORACIOUS_FULL_MULT // takes longer to eat the more well fed you are - if(eater == feeder)//If you're eating it yourself. if(eat_time > 0 && !do_after(feeder, time_to_eat, eater, timed_action_flags = food_flags & FOOD_FINGER_FOOD ? IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE : NONE)) //Gotta pass the minimal eat time return @@ -386,12 +385,12 @@ Behavior that's still missing from this component that original food items had t var/message_to_consumer = "" var/message_to_blind_consumer = "" - if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS)) + if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS) && !HAS_TRAIT(eater, TRAIT_GLUTTON)) to_chat(eater, span_warning("You don't feel like eating any more junk food at the moment!")) return else if(fullness > (600 * (1 + eater.overeatduration / (4000 SECONDS)))) // The more you eat - the more you can eat - if(HAS_TRAIT(eater, TRAIT_VORACIOUS)) - message_to_nearby_audience = span_notice("[eater] voraciously forces \the [parent] down [eater.p_their()] throat..") + if(HAS_TRAIT(eater, TRAIT_VORACIOUS) || HAS_TRAIT(eater, TRAIT_GLUTTON)) + message_to_nearby_audience = span_notice("[eater] voraciously forces \the [parent] down [eater.p_their()] throat.") message_to_consumer = span_notice("You voraciously force \the [parent] down your throat.") else message_to_nearby_audience = span_warning("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!") diff --git a/code/datums/components/hat_stabilizer.dm b/code/datums/components/hat_stabilizer.dm index 40cae0633f48b..9a79895153023 100644 --- a/code/datums/components/hat_stabilizer.dm +++ b/code/datums/components/hat_stabilizer.dm @@ -1,7 +1,10 @@ /// Allows players to place hats on the atom this is attached to /datum/component/hat_stabilizer + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS /// Currently "stored" hat. No armor or function will be inherited, only the icon and cover flags. var/obj/item/clothing/head/attached_hat + /// If TRUE, the hat will fall to the ground when the owner does so. It can also be shot off. + var/loose_hat = FALSE /// Original cover flags for the helmet, before a hat is placed var/former_flags var/former_visor_flags @@ -10,7 +13,7 @@ /// Pixel_y offset for the hat var/pixel_y_offset -/datum/component/hat_stabilizer/Initialize(add_overlay = FALSE, use_worn_icon = TRUE, pixel_y_offset = 0) +/datum/component/hat_stabilizer/Initialize(use_worn_icon = FALSE, pixel_y_offset = 0, loose_hat = FALSE) if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE @@ -19,14 +22,37 @@ src.use_worn_icon = use_worn_icon src.pixel_y_offset = pixel_y_offset + src.loose_hat = loose_hat + // Examine signals RegisterSignal(source, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(source, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) + + // Equip signals, used to drop loose hats + RegisterSignal(source, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) + RegisterSignal(source, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) + + // Wear & Remove RegisterSignal(source, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) - RegisterSignal(source, COMSIG_QDELETING, PROC_REF(on_qdel)) RegisterSignal(source, COMSIG_ATOM_ATTACK_HAND_SECONDARY, PROC_REF(on_secondary_attack_hand)) + + // Overlays RegisterSignals(source, list(COMSIG_MODULE_GENERATE_WORN_OVERLAY, COMSIG_ITEM_GET_WORN_OVERLAYS), PROC_REF(get_worn_overlays)) - RegisterSignal(source, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) - if (add_overlay) - RegisterSignal(source, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + + RegisterSignal(source, COMSIG_QDELETING, PROC_REF(on_qdel)) + +// Inherit the new values passed to the component +/datum/component/hat_stabilizer/InheritComponent(datum/component/hat_stabilizer/new_comp, original, use_worn_icon, pixel_y_offset, loose_hat) + if(!original) + return + + if(!isnull(use_worn_icon)) + src.use_worn_icon = use_worn_icon + if(!isnull(use_worn_icon)) + src.use_worn_icon = use_worn_icon + if(!isnull(pixel_y_offset)) + src.pixel_y_offset = pixel_y_offset + if(!isnull(loose_hat)) + src.loose_hat = loose_hat /datum/component/hat_stabilizer/UnregisterFromParent() if (attached_hat) @@ -34,12 +60,44 @@ UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_ATTACK_HAND_SECONDARY, COMSIG_MODULE_GENERATE_WORN_OVERLAY, COMSIG_ITEM_GET_WORN_OVERLAYS, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_QDELETING, - COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)) + COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) + +/datum/component/hat_stabilizer/proc/on_equip(datum/source, mob/equipper, slot) + SIGNAL_HANDLER + + if(!loose_hat) + return + + var/obj/item/our_item = parent + if(!(slot & our_item.slot_flags)) + return + RegisterSignals(equipper, list(COMSIG_MOB_SLIPPED, COMSIG_LIVING_SLAPPED, COMSIG_MOVABLE_POST_THROW), PROC_REF(throw_hat)) + RegisterSignal(equipper, COMSIG_LIVING_THUD, PROC_REF(drop_hat)) + +/datum/component/hat_stabilizer/proc/on_drop(datum/source, mob/dropper) + SIGNAL_HANDLER + UnregisterSignal(dropper, list(COMSIG_MOB_SLIPPED, COMSIG_LIVING_SLAPPED, COMSIG_MOVABLE_POST_THROW, COMSIG_LIVING_THUD)) + +/datum/component/hat_stabilizer/proc/throw_hat(mob/hatless) + SIGNAL_HANDLER + if(!loose_hat) + return + var/obj/item/hat = remove_hat() + if(!hat) + return + hat.visible_message(span_danger("[hat] goes flying off [hatless]'s head!")) + hat.throw_at(get_edge_target_turf(get_turf(hat), pick(GLOB.alldirs)), 2, 1, spin = TRUE) + +/datum/component/hat_stabilizer/proc/drop_hat(mob/hatless) + SIGNAL_HANDLER + if(!loose_hat) + return + remove_hat() /datum/component/hat_stabilizer/proc/on_examine(datum/source, mob/user, list/base_examine) SIGNAL_HANDLER if(attached_hat) - base_examine += span_notice("There's \a [attached_hat] placed on [parent].") + base_examine += span_notice("There's \a [attached_hat] [loose_hat ? "loosely" : ""] placed on [parent].") else base_examine += span_notice("There's nothing placed on [parent]. Yet.") @@ -49,17 +107,15 @@ return if(attached_hat) var/mutable_appearance/worn_overlay = attached_hat.build_worn_icon(default_layer = ABOVE_BODY_FRONT_HEAD_LAYER-0.1, default_icon_file = 'icons/mob/clothing/head/default.dmi') + // loose hats are slightly angled + if(loose_hat) + var/matrix/tilt_trix = matrix(worn_overlay.transform) + var/angle = 5 + tilt_trix.Turn(angle * pick(1, -1)) + worn_overlay.transform = tilt_trix worn_overlay.pixel_y = pixel_y_offset + attached_hat.worn_y_offset overlays += worn_overlay -/datum/component/hat_stabilizer/proc/on_update_overlays(atom/movable/source, list/overlays) - SIGNAL_HANDLER - if (isnull(attached_hat)) - return - var/mutable_appearance/worn_overlay = use_worn_icon ? attached_hat.build_worn_icon(default_layer = ABOVE_OBJ_LAYER, default_icon_file = 'icons/mob/clothing/head/default.dmi') : mutable_appearance(attached_hat, layer = ABOVE_OBJ_LAYER) - worn_overlay.pixel_y = pixel_y_offset - overlays += worn_overlay - /datum/component/hat_stabilizer/proc/on_qdel(atom/movable/source) SIGNAL_HANDLER @@ -90,7 +146,7 @@ /datum/component/hat_stabilizer/proc/attach_hat(obj/item/clothing/hat, mob/user) var/atom/movable/movable_parent = parent attached_hat = hat - RegisterSignal(hat, COMSIG_MOVABLE_MOVED, PROC_REF(remove_hat)) + RegisterSignal(hat, COMSIG_MOVABLE_MOVED, PROC_REF(on_hat_movement)) if (!isnull(user)) movable_parent.balloon_alert(user, "hat attached") @@ -111,6 +167,10 @@ var/mob/wearer = apparel.loc wearer.update_clothing(wearer.get_slot_by_item(apparel)) +/datum/component/hat_stabilizer/proc/on_hat_movement(obj/hat, mob/user) + SIGNAL_HANDLER + remove_hat(user) + /datum/component/hat_stabilizer/proc/on_secondary_attack_hand(datum/source, mob/user) SIGNAL_HANDLER . = COMPONENT_CANCEL_ATTACK_CHAIN @@ -122,9 +182,9 @@ else movable_parent.balloon_alert_to_viewers("the hat falls to the floor!") -/datum/component/hat_stabilizer/proc/remove_hat(mob/user) - SIGNAL_HANDLER +/datum/component/hat_stabilizer/proc/on_retraction() +/datum/component/hat_stabilizer/proc/remove_hat(mob/user) if(QDELETED(attached_hat)) return @@ -144,6 +204,7 @@ movable_parent.update_appearance() return + var/former_hat = attached_hat var/obj/item/clothing/apparel = parent apparel.detach_clothing_traits(attached_hat) apparel.flags_cover = former_flags @@ -154,6 +215,8 @@ var/mob/wearer = apparel.loc wearer.update_clothing(wearer.get_slot_by_item(apparel)) + return former_hat + /datum/component/hat_stabilizer/proc/on_requesting_context_from_item(atom/source, list/context, obj/item/held_item, mob/user) SIGNAL_HANDLER diff --git a/code/datums/components/leanable.dm b/code/datums/components/leanable.dm index 3d2a4e0b73e5b..b95fd734ad043 100644 --- a/code/datums/components/leanable.dm +++ b/code/datums/components/leanable.dm @@ -86,19 +86,29 @@ COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_GET_PULLED, - COMSIG_MOVABLE_TELEPORTING, ), PROC_REF(stop_leaning)) + + RegisterSignal(src, COMSIG_MOVABLE_TELEPORTED, PROC_REF(teleport_away_while_leaning)) RegisterSignal(src, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(lean_dir_changed)) update_fov() +/// You fall on your face if you get teleported while leaning +/mob/living/proc/teleport_away_while_leaning() + SIGNAL_HANDLER + // Make sure we unregister signal handlers and reset animation + stop_leaning() + // -1000 aura + visible_message(span_notice("[src] falls flat on [p_their()] face from losing [p_their()] balance!"), span_warning("You fall suddenly as the object you were leaning on vanishes from contact with you!")) + Knockdown(3 SECONDS) + /mob/living/proc/stop_leaning() SIGNAL_HANDLER UnregisterSignal(src, list( COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_GET_PULLED, - COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_POST_DIR_CHANGE, + COMSIG_MOVABLE_TELEPORTED, )) animate(src, 0.2 SECONDS, pixel_x = base_pixel_x + body_position_pixel_x_offset, pixel_y = base_pixel_y + body_position_pixel_y_offset) remove_traits(list(TRAIT_UNDENSE, TRAIT_EXPANDED_FOV), LEANING_TRAIT) diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm index e24bb4d2480df..a478fcbd27802 100644 --- a/code/datums/components/parry.dm +++ b/code/datums/components/parry.dm @@ -91,15 +91,16 @@ if (!istype(user) || !parriers[user] || parried) return + parriers -= user - attempt_parry(source, user) + return attempt_parry(source, user) /datum/component/parriable_projectile/proc/attempt_parry(obj/projectile/source, mob/user) if (QDELETED(source) || source.deletion_queued) - return + return NONE if (SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRIED, source) & INTERCEPT_PARRY_EFFECTS) - return + return NONE parried = TRUE if (source.firer != user) @@ -120,4 +121,4 @@ user.playsound_local(source.loc, 'sound/effects/parry.ogg', 50, TRUE) user.overlay_fullscreen("projectile_parry", /atom/movable/screen/fullscreen/crit/projectile_parry, 2) addtimer(CALLBACK(user, TYPE_PROC_REF(/mob, clear_fullscreen), "projectile_parry"), 0.25 SECONDS) - return PROJECTILE_INTERRUPT_HIT + return PROJECTILE_INTERRUPT_HIT_PHASE diff --git a/code/datums/components/profound_fisher.dm b/code/datums/components/profound_fisher.dm index cc7e87aeb40af..62019ee3f6c67 100644 --- a/code/datums/components/profound_fisher.dm +++ b/code/datums/components/profound_fisher.dm @@ -17,6 +17,7 @@ if(!isgloves) RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(parent, COMSIG_MOB_COMPLETE_FISHING, PROC_REF(stop_fishing)) else var/obj/item/clothing/gloves = parent RegisterSignal(gloves, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) @@ -68,6 +69,7 @@ if(slot != ITEM_SLOT_GLOVES) return RegisterSignal(equipper, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack)) + RegisterSignal(equipper, COMSIG_MOB_COMPLETE_FISHING, PROC_REF(stop_fishing)) /datum/component/profound_fisher/proc/open_rod_menu(datum/source, mob/user, list/modifiers) SIGNAL_HANDLER @@ -76,7 +78,7 @@ /datum/component/profound_fisher/proc/on_drop(datum/source, atom/dropper) SIGNAL_HANDLER - UnregisterSignal(dropper, COMSIG_LIVING_UNARMED_ATTACK) + UnregisterSignal(dropper, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_MOB_COMPLETE_FISHING)) REMOVE_TRAIT(dropper, TRAIT_PROFOUND_FISHER, TRAIT_GENERIC) //this will cancel the current minigame if the fishing rod was internal. /datum/component/profound_fisher/proc/on_unarmed_attack(mob/living/source, atom/attack_target, proximity_flag, list/modifiers) @@ -108,19 +110,12 @@ return TRUE /datum/component/profound_fisher/proc/begin_fishing(mob/living/user, atom/target) - RegisterSignal(user, COMSIG_MOB_BEGIN_FISHING, PROC_REF(actually_fishing_with_internal_rod)) our_rod.melee_attack_chain(user, target) - UnregisterSignal(user, COMSIG_MOB_BEGIN_FISHING) + ADD_TRAIT(user, TRAIT_PROFOUND_FISHER, TRAIT_GENERIC) -/datum/component/profound_fisher/proc/actually_fishing_with_internal_rod(datum/source) - SIGNAL_HANDLER - ADD_TRAIT(source, TRAIT_PROFOUND_FISHER, REF(parent)) - RegisterSignal(source, COMSIG_MOB_COMPLETE_FISHING, PROC_REF(remove_profound_fisher)) - -/datum/component/profound_fisher/proc/remove_profound_fisher(datum/source) +/datum/component/profound_fisher/proc/stop_fishing(datum/source) SIGNAL_HANDLER REMOVE_TRAIT(source, TRAIT_PROFOUND_FISHER, TRAIT_GENERIC) - UnregisterSignal(source, COMSIG_MOB_COMPLETE_FISHING) /datum/component/profound_fisher/proc/pretend_fish(mob/living/source, atom/target) if(DOING_INTERACTION_WITH_TARGET(source, target)) diff --git a/code/datums/elements/fish_safe_storage.dm b/code/datums/elements/fish_safe_storage.dm index bb7864ced0e6a..ec5c59848646d 100644 --- a/code/datums/elements/fish_safe_storage.dm +++ b/code/datums/elements/fish_safe_storage.dm @@ -22,7 +22,7 @@ /datum/element/fish_safe_storage/Detach(atom/source) for(var/obj/item/fish/fish in source) tracked_fish -= fish - fish.exit_stasis() + REMOVE_TRAIT(fish, TRAIT_FISH_STASIS, REF(src)) UnregisterSignal(source, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON)) return ..() @@ -30,19 +30,19 @@ SIGNAL_HANDLER if(isfish(arrived)) tracked_fish |= arrived - arrived.enter_stasis() + ADD_TRAIT(arrived, TRAIT_FISH_STASIS, REF(src)) /datum/element/fish_safe_storage/proc/on_init_on(datum/source, obj/item/fish/created) SIGNAL_HANDLER if(isfish(created) && !QDELETED(created)) tracked_fish |= created - created.enter_stasis() + ADD_TRAIT(created, TRAIT_FISH_STASIS, REF(src)) /datum/element/fish_safe_storage/proc/on_exit(datum/source, obj/item/fish/gone) SIGNAL_HANDLER if(isfish(gone)) tracked_fish -= gone - gone.exit_stasis() + REMOVE_TRAIT(gone, TRAIT_FISH_STASIS, REF(src)) /datum/element/fish_safe_storage/process(seconds_per_tick) for(var/obj/item/fish/fish as anything in tracked_fish) diff --git a/code/datums/elements/organ_set_bonus.dm b/code/datums/elements/organ_set_bonus.dm index 118c64fbeafa4..a8d1d3c7ad736 100644 --- a/code/datums/elements/organ_set_bonus.dm +++ b/code/datums/elements/organ_set_bonus.dm @@ -57,6 +57,10 @@ var/required_biotype = MOB_ORGANIC /// A list of traits added to the mob upon bonus activation, can be of any length. var/list/bonus_traits = list() + /// Bonus biotype to add on bonus activation. + var/bonus_biotype + /// If the biotype was added - used to check if we should remove the biotype or not, on organ set loss. + var/biotype_added = FALSE /// Limb overlay to apply upon activation var/limb_overlay /// Color priority for limb overlay @@ -80,10 +84,20 @@ if((required_biotype == MOB_ORGANIC) && !owner.can_mutate()) return FALSE bonus_active = TRUE + // Add traits if(length(bonus_traits)) owner.add_traits(bonus_traits, REF(src)) + + // Add biotype + if(owner.mob_biotypes & bonus_biotype) + biotype_added = FALSE + owner.mob_biotypes |= bonus_biotype + biotype_added = TRUE + if(bonus_activate_text) to_chat(owner, bonus_activate_text) + + // Add limb overlay if(!iscarbon(owner) || !limb_overlay) return TRUE var/mob/living/carbon/carbon_owner = owner @@ -96,10 +110,18 @@ /datum/status_effect/organ_set_bonus/proc/disable_bonus() SHOULD_CALL_PARENT(TRUE) bonus_active = FALSE + + // Remove traits if(length(bonus_traits)) owner.remove_traits(bonus_traits, REF(src)) + // Remove biotype (if added) + if(biotype_added) + owner.mob_biotypes &= ~bonus_biotype + if(bonus_deactivate_text) to_chat(owner, bonus_deactivate_text) + + // Remove limb overlay if(!iscarbon(owner) || QDELETED(owner) || !limb_overlay) return var/mob/living/carbon/carbon_owner = owner diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_clothes.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_clothes.dm index bdc2a7d2928c5..54ea9f4185756 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_clothes.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_clothes.dm @@ -276,11 +276,6 @@ icon_file = 'icons/mob/inhands/clothing/suits_righthand.dmi' json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json' -/datum/greyscale_config/jumpsuit/worn_digi - name = "Jumpsuit Worn (Digitigrate)" - icon_file = 'icons/mob/clothing/under/digi_template.dmi' - json_config = 'code/datums/greyscale/json_configs/jumpsuit_worn_digilegs.json' - /datum/greyscale_config/eth_tunic name = "Ethereal Tunic" icon_file = 'icons/obj/clothing/under/ethereal.dmi' @@ -627,6 +622,14 @@ name = "Waistcoat (Worn)" icon_file = 'icons/mob/clothing/accessories.dmi' +// Digi Stuff + +/datum/greyscale_config/digitigrade + name = "Digitigrade Clothes" + icon_file = 'icons/mob/clothing/digi_template.dmi' + json_config = 'code/datums/greyscale/json_configs/digitigrade.json' + + // // SUIT + HEAD // (Specifically for toggleable suits with hats, i.e. winter coats) diff --git a/code/datums/greyscale/json_configs/digitigrade.json b/code/datums/greyscale/json_configs/digitigrade.json new file mode 100644 index 0000000000000..899dff334e423 --- /dev/null +++ b/code/datums/greyscale/json_configs/digitigrade.json @@ -0,0 +1,39 @@ +{ + "jumpsuit_worn": [ + { + "type": "icon_state", + "icon_state": "jumpsuit", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ], + "oversuit_worn": [ + { + "type": "icon_state", + "icon_state": "oversuit", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ], + "sneakers_worn": [ + { + "type": "icon_state", + "icon_state": "shoes_colored", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "shoes_uncolored", + "blend_mode": "overlay" + } + ], + "boots_worn": [ + { + "type": "icon_state", + "icon_state": "boots", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/jumpsuit_worn_digilegs.json b/code/datums/greyscale/json_configs/jumpsuit_worn_digilegs.json deleted file mode 100644 index 9aa201cece3c1..0000000000000 --- a/code/datums/greyscale/json_configs/jumpsuit_worn_digilegs.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "": [ - { - "type": "icon_state", - "icon_state": "jumpsuit", - "blend_mode": "overlay", - "color_ids": [ 1 ] - } - ] -} diff --git a/code/datums/mood.dm b/code/datums/mood.dm index fe91e1db2c66c..d3f2ac70861e0 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -124,6 +124,10 @@ clear_mood_event(MOOD_CATEGORY_NUTRITION) return FALSE + if(HAS_TRAIT(mob_parent, TRAIT_GLUTTON)) + add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/hungry) //you'll never get enough + return TRUE + if(HAS_TRAIT(mob_parent, TRAIT_FAT) && !HAS_TRAIT(mob_parent, TRAIT_VORACIOUS)) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fat) return TRUE diff --git a/code/datums/quirks/negative_quirks/numb.dm b/code/datums/quirks/negative_quirks/numb.dm index ee8b86d342679..cb982cf0641b2 100644 --- a/code/datums/quirks/negative_quirks/numb.dm +++ b/code/datums/quirks/negative_quirks/numb.dm @@ -10,8 +10,8 @@ /datum/quirk/numb/add(client/client_source) quirk_holder.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) - quirk_holder.add_traits(list(TRAIT_ANALGESIA, QUIRK_TRAIT)) + quirk_holder.add_traits(list(TRAIT_ANALGESIA, TRAIT_NO_DAMAGE_OVERLAY), QUIRK_TRAIT) /datum/quirk/numb/remove(client/client_source) quirk_holder.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) - quirk_holder.remove_traits(list(TRAIT_ANALGESIA, QUIRK_TRAIT)) + quirk_holder.remove_traits(list(TRAIT_ANALGESIA, TRAIT_NO_DAMAGE_OVERLAY), QUIRK_TRAIT) diff --git a/code/datums/quirks/positive_quirks/chipped.dm b/code/datums/quirks/positive_quirks/chipped.dm index 474e8a4adcdc4..26b4883f9b339 100644 --- a/code/datums/quirks/positive_quirks/chipped.dm +++ b/code/datums/quirks/positive_quirks/chipped.dm @@ -68,6 +68,7 @@ id = "itchy skillchip" tick_interval_lowerbound = 5 SECONDS tick_interval_upperbound = 10 MINUTES + alert_type = null ///lower damage we apply to our itchy owner var/minimum_damage = 1 ///upper damage we apply to our itchy owner diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm index 7d83439b6d494..f3bb4fb4a9c75 100644 --- a/code/datums/station_traits/neutral_traits.dm +++ b/code/datums/station_traits/neutral_traits.dm @@ -291,38 +291,35 @@ greyscale_config = /datum/greyscale_config/festive_hat greyscale_config_worn = /datum/greyscale_config/festive_hat/worn -/datum/station_trait/scarves - name = "Scarves" +/datum/station_trait/scryers + name = "Scryers" trait_type = STATION_TRAIT_NEUTRAL - weight = 5 - cost = STATION_TRAIT_COST_MINIMAL + weight = 2 + cost = STATION_TRAIT_COST_LOW show_in_report = TRUE - var/list/scarves + report_message = "Nanotrasen has chosen your station for an experiment - everyone has free scryers! Use these to talk to other people easily and privately." -/datum/station_trait/scarves/New() +/datum/station_trait/scryers/New() . = ..() - report_message = pick( - "Nanotrasen is experimenting with seeing if neck warmth improves employee morale.", - "After Space Fashion Week, scarves are the hot new accessory.", - "Everyone was simultaneously a little bit cold when they packed to go to the station.", - "The station is definitely not under attack by neck grappling aliens masquerading as wool. Definitely not.", - "You all get free scarves. Don't ask why.", - "A shipment of scarves was delivered to the station.", - ) - scarves = typesof(/obj/item/clothing/neck/scarf) + list( - /obj/item/clothing/neck/large_scarf/red, - /obj/item/clothing/neck/large_scarf/green, - /obj/item/clothing/neck/large_scarf/blue, - ) - RegisterSignal(SSdcs, COMSIG_GLOB_JOB_AFTER_SPAWN, PROC_REF(on_job_after_spawn)) - -/datum/station_trait/scarves/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client) +/datum/station_trait/scryers/proc/on_job_after_spawn(datum/source, datum/job/job, mob/living/spawned, client/player_client) SIGNAL_HANDLER - var/scarf_type = pick(scarves) - - spawned.equip_to_slot_or_del(new scarf_type(spawned), ITEM_SLOT_NECK, initial = FALSE) + if(!ishuman(spawned)) + return + var/mob/living/carbon/human/humanspawned = spawned + // Put their silly little scarf or necktie somewhere else + var/obj/item/silly_little_scarf = humanspawned.wear_neck + if(silly_little_scarf) + humanspawned.temporarilyRemoveItemFromInventory(silly_little_scarf) + silly_little_scarf.forceMove(get_turf(humanspawned)) + humanspawned.equip_in_one_of_slots(silly_little_scarf, ITEM_SLOT_BACKPACK, ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET, qdel_on_fail = FALSE, indirect_action = TRUE) + + var/obj/item/clothing/neck/link_scryer/loaded/new_scryer = new(spawned) + new_scryer.label = spawned.name + new_scryer.update_name() + + spawned.equip_to_slot_or_del(new_scryer, ITEM_SLOT_NECK, initial = FALSE) /datum/station_trait/wallets name = "Wallets!" diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index a24e8d4ccdac9..e4471f2f4dbd8 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -406,6 +406,7 @@ /datum/status_effect/mayhem id = "Mayhem" duration = 2 MINUTES + alert_type = null /// The chainsaw spawned by the status effect var/obj/item/chainsaw/doomslayer/chainsaw @@ -450,6 +451,7 @@ duration = 2 SECONDS status_type = STATUS_EFFECT_REPLACE show_duration = TRUE + alert_type = null /datum/status_effect/speed_boost/on_creation(mob/living/new_owner, set_duration) if(isnum(set_duration)) @@ -595,6 +597,7 @@ id = "radiation_immunity" duration = 1 MINUTES show_duration = TRUE + alert_type = null /datum/status_effect/radiation_immunity/on_apply() ADD_TRAIT(owner, TRAIT_RADIMMUNE, type) diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 02fe573ef36e9..b9b56161e84a7 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -8,6 +8,7 @@ //Largely negative status effects go here, even if they have small beneficial effects //STUN EFFECTS /datum/status_effect/incapacitating + id = STATUS_EFFECT_ID_ABSTRACT tick_interval = STATUS_EFFECT_NO_TICK status_type = STATUS_EFFECT_REPLACE alert_type = null @@ -967,6 +968,7 @@ duration = 10 SECONDS status_type = STATUS_EFFECT_REPLACE tick_interval = 0.2 SECONDS + alert_type = null /datum/status_effect/teleport_madness/tick(seconds_between_ticks) dump_in_space(owner) diff --git a/code/datums/status_effects/debuffs/drunk.dm b/code/datums/status_effects/debuffs/drunk.dm index 706ab36fcff2c..37ab11f130c4c 100644 --- a/code/datums/status_effects/debuffs/drunk.dm +++ b/code/datums/status_effects/debuffs/drunk.dm @@ -15,6 +15,7 @@ tick_interval = 2 SECONDS status_type = STATUS_EFFECT_REPLACE remove_on_fullheal = TRUE + alert_type = null /// The level of drunkness we are currently at. var/drunk_value = 0 diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm index 648f09f31e95f..4b071d87e21e6 100644 --- a/code/datums/status_effects/debuffs/fire_stacks.dm +++ b/code/datums/status_effects/debuffs/fire_stacks.dm @@ -1,5 +1,6 @@ /datum/status_effect/fire_handler duration = STATUS_EFFECT_PERMANENT + id = STATUS_EFFECT_ID_ABSTRACT alert_type = null status_type = STATUS_EFFECT_REFRESH //Custom code on_remove_on_mob_delete = TRUE diff --git a/code/datums/status_effects/debuffs/screwy_hud.dm b/code/datums/status_effects/debuffs/screwy_hud.dm index 2e8fc566cf460..10c20112160c7 100644 --- a/code/datums/status_effects/debuffs/screwy_hud.dm +++ b/code/datums/status_effects/debuffs/screwy_hud.dm @@ -8,6 +8,7 @@ * accidentally removing another source's hud. */ /datum/status_effect/grouped/screwy_hud + id = STATUS_EFFECT_ID_ABSTRACT alert_type = null /// The priority of this screwyhud over other screwyhuds. var/priority = -1 diff --git a/code/datums/status_effects/debuffs/spacer.dm b/code/datums/status_effects/debuffs/spacer.dm index 1add806c02f0e..ed14fd285968e 100644 --- a/code/datums/status_effects/debuffs/spacer.dm +++ b/code/datums/status_effects/debuffs/spacer.dm @@ -3,6 +3,7 @@ /datum/status_effect/spacer id = "spacer_gravity_effects" status_type = STATUS_EFFECT_REPLACE + alert_type = null /// Essentially, tracks whether this is a planetary map. /// It'd be pretty miserable if you're playing a planetary map and getting the worse of all effects, so we handwave it a bit. VAR_FINAL/nerfed_effects_because_planetary = FALSE diff --git a/code/datums/status_effects/debuffs/speech_debuffs.dm b/code/datums/status_effects/debuffs/speech_debuffs.dm index a557f7ddd4dd4..1ba85ebe72f72 100644 --- a/code/datums/status_effects/debuffs/speech_debuffs.dm +++ b/code/datums/status_effects/debuffs/speech_debuffs.dm @@ -1,5 +1,5 @@ /datum/status_effect/speech - id = null + id = STATUS_EFFECT_ID_ABSTRACT alert_type = null remove_on_fullheal = TRUE tick_interval = STATUS_EFFECT_NO_TICK diff --git a/code/datums/status_effects/debuffs/stamcrit.dm b/code/datums/status_effects/debuffs/stamcrit.dm index 74c3fde12f55f..0d4a844a61e0d 100644 --- a/code/datums/status_effects/debuffs/stamcrit.dm +++ b/code/datums/status_effects/debuffs/stamcrit.dm @@ -1,4 +1,5 @@ /datum/status_effect/incapacitating/stamcrit + id = "stamcrit" status_type = STATUS_EFFECT_UNIQUE // Lasts until we go back to 0 stamina, which is handled by the mob duration = STATUS_EFFECT_PERMANENT diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm index 601945b83aae4..27b37af1e3567 100644 --- a/code/datums/status_effects/grouped_effect.dm +++ b/code/datums/status_effects/grouped_effect.dm @@ -1,5 +1,7 @@ /// Status effect from multiple sources, when all sources are removed, so is the effect /datum/status_effect/grouped + id = STATUS_EFFECT_ID_ABSTRACT + alert_type = null // Grouped effects adds itself to [var/sources] and destroys itself if one exists already, there are never actually multiple status_type = STATUS_EFFECT_MULTIPLE /// A list of all sources applying this status effect. Sources are a list of keys diff --git a/code/datums/status_effects/limited_effect.dm b/code/datums/status_effects/limited_effect.dm index b577248d35eee..047a7a5ae07c8 100644 --- a/code/datums/status_effects/limited_effect.dm +++ b/code/datums/status_effects/limited_effect.dm @@ -3,6 +3,7 @@ id = "limited_buff" duration = STATUS_EFFECT_PERMANENT status_type = STATUS_EFFECT_REFRESH + alert_type = null ///How many stacks we currently have var/stacks = 1 ///How many stacks we can have maximum diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 43c4ad7a3f09a..23db827daadec 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -110,6 +110,7 @@ /datum/status_effect/bounty id = "bounty" status_type = STATUS_EFFECT_UNIQUE + alert_type = null var/mob/living/rewarded /datum/status_effect/bounty/on_creation(mob/living/new_owner, mob/living/caster) @@ -581,6 +582,7 @@ id = "tinea_luxor_light" processing_speed = STATUS_EFFECT_NORMAL_PROCESS remove_on_fullheal = TRUE + alert_type = null var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj /datum/status_effect/tinlux_light/on_creation(mob/living/new_owner, duration) diff --git a/code/datums/status_effects/stacking_effect.dm b/code/datums/status_effects/stacking_effect.dm index 3ed7846f6ff23..acdefd8290c88 100644 --- a/code/datums/status_effects/stacking_effect.dm +++ b/code/datums/status_effects/stacking_effect.dm @@ -1,7 +1,7 @@ /// Status effects that can stack. /datum/status_effect/stacking - id = "stacking_base" + id = STATUS_EFFECT_ID_ABSTRACT duration = STATUS_EFFECT_PERMANENT // Only removed under specific conditions. tick_interval = 1 SECONDS // Deciseconds between decays, once decay starts alert_type = null diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 6b1e16543d7ac..830d128cddce5 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -347,7 +347,7 @@ user.visible_message(span_danger("[user] begins [scanned ? "expertly" : ""] resetting [victim]'s [limb.plaintext_zone] with [I]."), span_notice("You begin resetting [victim]'s [limb.plaintext_zone] with [I][scanned ? ", keeping the holo-image's indications in mind" : ""]...")) if(!do_after(user, treatment_delay, target = victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) - return + return TRUE if(victim == user) limb.receive_damage(brute=15, wound_bonus=CANT_WOUND) @@ -359,6 +359,7 @@ victim.emote("scream") qdel(src) + return TRUE /* Severe (Hairline Fracture) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 0b46daebb01fb..46e568d5dd281 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1320,6 +1320,7 @@ step(src, hitting_atom.dir) return ..() +// Calls throw_at after checking that the move strength is greater than the thrown atom's move resist. Identical args. /atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) return diff --git a/code/game/machinery/civilian_bounties.dm b/code/game/machinery/civilian_bounties.dm index d8c8a98caef77..dcd967e082e5f 100644 --- a/code/game/machinery/civilian_bounties.dm +++ b/code/game/machinery/civilian_bounties.dm @@ -84,10 +84,16 @@ return FALSE status_report = "Civilian Bounty: " var/obj/machinery/piratepad/civilian/pad = pad_ref?.resolve() - for(var/atom/movable/AM in get_turf(pad)) - if(AM == pad) + for(var/atom/movable/possible_shippable in get_turf(pad)) + if(possible_shippable == pad) continue - if(inserted_scan_id.registered_account.civilian_bounty.applies_to(AM)) + if(possible_shippable.flags_1 & HOLOGRAM_1) + continue + if(isitem(possible_shippable)) + var/obj/item/possible_shippable_item = possible_shippable + if(possible_shippable_item.item_flags & ABSTRACT) + continue + if(inserted_scan_id.registered_account.civilian_bounty.applies_to(possible_shippable)) status_report += "Target Applicable." playsound(loc, 'sound/machines/synth/synth_yes.ogg', 30 , TRUE) return @@ -110,13 +116,19 @@ var/datum/bounty/curr_bounty = inserted_scan_id.registered_account.civilian_bounty var/active_stack = 0 var/obj/machinery/piratepad/civilian/pad = pad_ref?.resolve() - for(var/atom/movable/AM in get_turf(pad)) - if(AM == pad) + for(var/atom/movable/possible_shippable in get_turf(pad)) + if(possible_shippable == pad) + continue + if(possible_shippable.flags_1 & HOLOGRAM_1) continue - if(curr_bounty.applies_to(AM)) + if(isitem(possible_shippable)) + var/obj/item/possible_shippable_item = possible_shippable + if(possible_shippable_item.item_flags & ABSTRACT) + continue + if(curr_bounty.applies_to(possible_shippable)) active_stack ++ - curr_bounty.ship(AM) - qdel(AM) + curr_bounty.ship(possible_shippable) + qdel(possible_shippable) if(active_stack >= 1) status_report += "Bounty Target Found x[active_stack]. " else diff --git a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm index 1e4bc41ae415b..9a873b5e373f6 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -10,6 +10,7 @@ bonus_activate_text = span_notice("Carp DNA is deeply infused with you! You've learned how to propel yourself through space!") bonus_deactivate_text = span_notice("Your DNA is once again mostly yours, and so fades your ability to space-swim...") bonus_traits = list(TRAIT_SPACEWALK) + bonus_biotype = MOB_AQUATIC limb_overlay = /datum/bodypart_overlay/texture/carpskin color_overlay_priority = LIMB_COLOR_CARP_INFUSION diff --git a/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm index 7dda9ba15639d..69f5a23798ff5 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fish_organs.dm @@ -23,6 +23,7 @@ TRAIT_EXPANDED_FOV, //fish vision TRAIT_WATER_ADAPTATION, ) + bonus_biotype = MOB_AQUATIC /datum/status_effect/organ_set_bonus/fish/enable_bonus() . = ..() diff --git a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm index 03955f06ffe82..2c07038fd1709 100644 --- a/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/roach_organs.dm @@ -16,10 +16,9 @@ // - Immunity to nuke gibs // - Nukes come with radiation (not actually but yknow) bonus_traits = list(TRAIT_NUKEIMMUNE, TRAIT_RADIMMUNE, TRAIT_VIRUS_RESISTANCE) + bonus_biotype = MOB_BUG /// Armor type attached to the owner's physiology var/datum/armor/given_armor = /datum/armor/roach_internal_armor - /// Storing biotypes pre-organ bonus applied so we don't remove bug from mobs which should have it. - var/old_biotypes = NONE /datum/status_effect/organ_set_bonus/roach/enable_bonus() . = ..() @@ -29,9 +28,6 @@ var/mob/living/carbon/human/human_owner = owner human_owner.physiology.armor = human_owner.physiology.armor.add_other_armor(given_armor) - old_biotypes = human_owner.mob_biotypes - human_owner.mob_biotypes |= MOB_BUG - /datum/status_effect/organ_set_bonus/roach/disable_bonus() . = ..() if(!ishuman(owner) || QDELETED(owner)) @@ -40,9 +36,6 @@ var/mob/living/carbon/human/human_owner = owner human_owner.physiology.armor = human_owner.physiology.armor.subtract_other_armor(given_armor) - if(!(old_biotypes & MOB_BUG)) // only remove bug if it wasn't there before - human_owner.mob_biotypes &= ~MOB_BUG - /// Roach heart: /// Reduces damage taken from brute attacks from behind, /// but increases duration of knockdowns diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index f32690f334cad..cd2ec6c6476f7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -39,12 +39,6 @@ ///The config type to use for greyscaled belt overlays. Both this and greyscale_colors must be assigned to work. var/greyscale_config_belt - /// Greyscale config used when generating digitigrade versions of the sprite. - var/digitigrade_greyscale_config_worn - /// Greyscale colors used when generating digitigrade versions of the sprite. - /// Optional - If not set it will default to normal greyscale colors, or approximate them if those are unset as well - var/digitigrade_greyscale_colors - /* !!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!! IF YOU ADD MORE ICON CRAP TO THIS @@ -1889,11 +1883,12 @@ return src /// Checks if the bait is liked by the fish type or not. Returns a multiplier that affects the chance of catching it. -/obj/item/proc/check_bait(obj/item/fish/fish_type) +/obj/item/proc/check_bait(obj/item/fish/fish) if(HAS_TRAIT(src, TRAIT_OMNI_BAIT)) return 1 var/catch_multiplier = 1 - var/list/properties = SSfishing.fish_properties[fish_type] + + var/list/properties = SSfishing.fish_properties[isfish(fish) ? fish.type : fish] //Bait matching likes doubles the chance var/list/fav_bait = properties[FISH_PROPERTIES_FAV_BAIT] for(var/bait_identifer in fav_bait) diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 589e916be21f7..9bfd33b8b1b63 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -218,6 +218,7 @@ /obj/item/hand_item/slapper/attack(mob/living/slapped, mob/living/carbon/human/user) SEND_SIGNAL(user, COMSIG_LIVING_SLAP_MOB, slapped) + SEND_SIGNAL(slapped, COMSIG_LIVING_SLAPPED, user) if(iscarbon(slapped)) var/mob/living/carbon/potential_tailed = slapped diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 9e5ae3ba74b1c..3f813e50b45db 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -570,6 +570,11 @@ new /obj/item/book/granter/action/spell/mime/mimery_blockade(src) new /obj/item/book/granter/action/spell/mime/mimery_guns(src) +/obj/item/storage/box/syndie_kit/moltobeso/PopulateContents() + new /obj/item/reagent_containers/cup/bottle/moltobeso(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/dropper(src) + /obj/item/storage/box/syndie_kit/combat_baking/PopulateContents() new /obj/item/food/baguette/combat(src) for(var/i in 1 to 2) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index aab0f609d0e3f..bda90858cae4e 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -836,7 +836,7 @@ GLOBAL_LIST_EMPTY(station_turfs) . = ..() if(!fish_source || !HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - if(!GLOB.preset_fish_sources[fish_source].has_known_fishes()) + if(!GLOB.preset_fish_sources[fish_source].has_known_fishes(src)) return . += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index d66e7f777c309..5e68dcedbfaea 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -42,9 +42,7 @@ ADMIN_VERB(play_local_sound, R_SOUND, "Play Local Sound", "Plays a sound only yo log_admin("[key_name(user)] played a local sound [sound]") message_admins("[key_name_admin(user)] played a local sound [sound]") var/volume = tgui_input_number(user, "What volume would you like the sound to play at?", max_value = 100) - var/sound/admin_sound = sound(sound) - admin_sound.volume = volume || 50 - playsound(get_turf(user.mob), sound, FALSE, FALSE) + playsound(get_turf(user.mob), sound, volume || 50, FALSE) BLACKBOX_LOG_ADMIN_VERB("Play Local Sound") ADMIN_VERB(play_direct_mob_sound, R_SOUND, "Play Direct Mob Sound", "Play a sound directly to a mob.", ADMIN_CATEGORY_FUN, sound as sound, mob/target in world) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index c6bfa9cca425a..32d1ca218769b 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -128,6 +128,7 @@ Striking a noncultist, however, will tear their flesh."} free_use = TRUE light_color = COLOR_HERETIC_GREEN light_range = 3 + demolition_mod = 1.5 /// holder for the actual action when created. var/list/datum/action/cooldown/spell/path_sword_actions /// holder for the actual action when created. diff --git a/code/modules/antagonists/heretic/magic/void_conduit.dm b/code/modules/antagonists/heretic/magic/void_conduit.dm index 036415269c975..16faf3b1a3844 100644 --- a/code/modules/antagonists/heretic/magic/void_conduit.dm +++ b/code/modules/antagonists/heretic/magic/void_conduit.dm @@ -94,7 +94,7 @@ var/mob/living/affected_mob = thing_to_affect if(affected_mob.can_block_magic(MAGIC_RESISTANCE)) continue - if(IS_HERETIC(affected_mob)) + if(IS_HERETIC_OR_MONSTER(affected_mob) || HAS_TRAIT(affected_mob, TRAIT_MANSUS_TOUCHED)) affected_mob.apply_status_effect(/datum/status_effect/void_conduit) else affected_mob.apply_status_effect(/datum/status_effect/void_chill, 1) @@ -116,13 +116,14 @@ falloff_exponent = 20 /datum/status_effect/void_conduit + id = "void_conduit" duration = 15 SECONDS status_type = STATUS_EFFECT_REPLACE alert_type = null /datum/status_effect/void_conduit/on_apply() - ADD_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "void_conduit") + ADD_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, type) return TRUE /datum/status_effect/void_conduit/on_remove() - REMOVE_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "void_conduit") + REMOVE_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, type) diff --git a/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm b/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm index 6b7f733265cae..6fbe461543a90 100644 --- a/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm +++ b/code/modules/antagonists/voidwalker/voidwalker_status_effects.dm @@ -32,15 +32,15 @@ icon_state = "paralysis" /datum/status_effect/void_eatered + id = "void_eatered" duration = 10 SECONDS remove_on_fullheal = TRUE + alert_type = null /datum/status_effect/void_eatered/on_apply() . = ..() - - ADD_TRAIT(owner, TRAIT_NODEATH, REF(src)) + ADD_TRAIT(owner, TRAIT_NODEATH, type) /datum/status_effect/void_eatered/on_remove() . = ..() - - REMOVE_TRAIT(owner, TRAIT_NODEATH, REF(src)) + REMOVE_TRAIT(owner, TRAIT_NODEATH, type) diff --git a/code/modules/cargo/markets/market_items/misc.dm b/code/modules/cargo/markets/market_items/misc.dm index bb8d3da04d825..a35d85fa7ccca 100644 --- a/code/modules/cargo/markets/market_items/misc.dm +++ b/code/modules/cargo/markets/market_items/misc.dm @@ -51,6 +51,33 @@ stock_max = 2 availability_prob = 30 +/datum/market_item/misc/atrocinator + name = "MOD Anti-Gravity Module" + desc = "We found this module in a maintenance tunnel, behind several warning cones and hazard signs, unlabeled. It's probably safe." + item = /obj/item/mod/module/atrocinator + price_min = CARGO_CRATE_VALUE * 4 + price_max = CARGO_CRATE_VALUE * 7 + stock_max = 1 + availability_prob = 22 + +/datum/market_item/misc/tanner + name = "MOD Tanning Module" + desc = "Ever wanted to be at the beach AND at work? Now you can with this snazzy tanning module!" + item = /obj/item/mod/module/tanner + price_min = CARGO_CRATE_VALUE * 2 + price_max = CARGO_CRATE_VALUE * 3 + stock_max = 2 + availability_prob = 30 + +/datum/market_item/misc/hat_stabilizer + name = "MOD Hat Stabilizer Module" + desc = "Don't sacrifice style for substance with this module! Hats not included." + item = /obj/item/mod/module/tanner + price_min = CARGO_CRATE_VALUE * 2 + price_max = CARGO_CRATE_VALUE * 3 + stock_max = 2 + availability_prob = 35 + /datum/market_item/misc/shove_blocker name = "MOD Bulwark Module" desc = "You have no idea how much effort it took us to extract this module from that damn safeguard MODsuit last shift." diff --git a/code/modules/cargo/packs/service.dm b/code/modules/cargo/packs/service.dm index 0e704a62df49d..89ce9a5c3716c 100644 --- a/code/modules/cargo/packs/service.dm +++ b/code/modules/cargo/packs/service.dm @@ -285,21 +285,6 @@ crate_type = /obj/structure/closet/crate/large discountable = SUPPLY_PACK_UNCOMMON_DISCOUNTABLE -/datum/supply_pack/service/aquarium_kit - name = "Aquarium Kit" - desc = "Everything you need to start your own aquarium. Contains aquarium construction kit, \ - fish catalog, fish food and three freshwater fish from our collection." - cost = CARGO_CRATE_VALUE * 5 - contains = list(/obj/item/book/manual/fish_catalog, - /obj/item/storage/fish_case/random/freshwater = 3, - /obj/item/reagent_containers/cup/fish_feed, - /obj/item/storage/box/aquarium_props, - /obj/item/aquarium_kit, - ) - crate_name = "aquarium kit crate" - crate_type = /obj/structure/closet/crate/wooden - discountable = SUPPLY_PACK_UNCOMMON_DISCOUNTABLE - /// Spare bar sign wallmount /datum/supply_pack/service/bar_sign name = "Bar Sign Replacement Kit" diff --git a/code/modules/clothing/head/perceptomatrix.dm b/code/modules/clothing/head/perceptomatrix.dm index 4250a1e399663..e8b75bbdef5bc 100644 --- a/code/modules/clothing/head/perceptomatrix.dm +++ b/code/modules/clothing/head/perceptomatrix.dm @@ -66,6 +66,8 @@ update_appearance(UPDATE_ICON_STATE) update_anomaly_state() + AddComponent(/datum/component/adjust_fishing_difficulty, -7) // PSYCHIC FISHING + AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) /obj/item/clothing/head/helmet/perceptomatrix/equipped(mob/living/user, slot) . = ..() diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index 3ecec813a95ce..1793a212ae330 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -110,6 +110,9 @@ var/mob/M = loc M.update_worn_shoes() +/obj/item/clothing/shoes/generate_digitigrade_icons(icon/base_icon, greyscale_colors) + return icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "boots_worn") + /** * adjust_laces adjusts whether our shoes (assuming they can_be_tied) and tied, untied, or knotted * diff --git a/code/modules/clothing/shoes/boots.dm b/code/modules/clothing/shoes/boots.dm index 77e7b2ff369e2..2dba6ed24fce7 100644 --- a/code/modules/clothing/shoes/boots.dm +++ b/code/modules/clothing/shoes/boots.dm @@ -3,6 +3,7 @@ desc = "High speed, low drag combat boots." icon_state = "jackboots" inhand_icon_state = "jackboots" + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK body_parts_covered = FEET|LEGS armor_type = /datum/armor/shoes_combat strip_delay = 40 @@ -45,6 +46,7 @@ desc = "Nanotrasen-issue Security combat boots for combat scenarios or combat situations. All combat, all the time." icon_state = "jackboots" inhand_icon_state = "jackboots" + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK strip_delay = 30 equip_delay_other = 50 resistance_flags = NONE @@ -71,6 +73,7 @@ desc = "Is it just me or is there a pair of jackboots on the floor?" icon_state = "ftc_boots" inhand_icon_state = null + supports_variations_flags = NONE /obj/item/clothing/shoes/jackboots/floortile/Initialize(mapload) . = ..() @@ -81,6 +84,7 @@ desc = "Boots lined with 'synthetic' animal fur." icon_state = "winterboots" inhand_icon_state = null + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK armor_type = /datum/armor/shoes_winterboots cold_protection = FEET|LEGS min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT @@ -131,6 +135,7 @@ icon_state = "workboots" inhand_icon_state = "jackboots" armor_type = /datum/armor/shoes_workboots + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK strip_delay = 20 equip_delay_other = 40 lace_time = 8 SECONDS @@ -155,6 +160,7 @@ icon_state = "rus_shoes" inhand_icon_state = null lace_time = 8 SECONDS + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK /obj/item/clothing/shoes/russian/Initialize(mapload) . = ..() diff --git a/code/modules/clothing/shoes/sneakers.dm b/code/modules/clothing/shoes/sneakers.dm index 0ae1e6e9caad9..1fc47accd3534 100644 --- a/code/modules/clothing/shoes/sneakers.dm +++ b/code/modules/clothing/shoes/sneakers.dm @@ -9,9 +9,17 @@ greyscale_config_worn = /datum/greyscale_config/sneakers/worn greyscale_config_inhand_left = /datum/greyscale_config/sneakers/inhand_left greyscale_config_inhand_right = /datum/greyscale_config/sneakers/inhand_right + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK flags_1 = IS_PLAYER_COLORABLE_1 interaction_flags_mouse_drop = NEED_HANDS +/obj/item/clothing/shoes/sneakers/get_general_color(icon/base_icon) + var/colors = SSgreyscale.ParseColorString(greyscale_colors) + return colors ? colors[1] : ..() + +/obj/item/clothing/shoes/sneakers/generate_digitigrade_icons(icon/base_icon, greyscale_colors) + return icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "sneakers_worn") + /obj/item/clothing/shoes/sneakers/random/Initialize(mapload) . = ..() greyscale_colors = "#" + random_color() + "#" + random_color() diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index eb8cb2a11e524..81a9a4809f521 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -36,6 +36,10 @@ . = ..() if(fishing_modifier) AddComponent(/datum/component/adjust_fishing_difficulty, fishing_modifier) + add_stabilizer() + +/obj/item/clothing/head/helmet/space/proc/add_stabilizer(loose_hat = TRUE) + AddComponent(/datum/component/hat_stabilizer, loose_hat = loose_hat) /datum/armor/helmet_space bio = 100 diff --git a/code/modules/clothing/spacesuits/pirate.dm b/code/modules/clothing/spacesuits/pirate.dm index 8ead0aeaa6619..f9625467ccd19 100644 --- a/code/modules/clothing/spacesuits/pirate.dm +++ b/code/modules/clothing/spacesuits/pirate.dm @@ -39,6 +39,9 @@ desc = "A modified EVA helmet with a five-thousand credit Lizzy Vuitton hat affixed to the top, proving that working in deep space is no excuse for being poor." icon_state = "spacetophat" +/obj/item/clothing/head/helmet/space/pirate/tophat/add_stabilizer(loose_hat = FALSE) + return + /obj/item/clothing/suit/space/pirate/silverscale name = "designer pirate suit" desc = "A specially-made Cybersun branded space suit; the fine plastisilk exterior is woven from the cocoons of black-market Lümlan mothroaches \ diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index 903142fdaa203..d2c31498d184b 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -109,10 +109,12 @@ /obj/item/clothing/head/helmet/space/plasmaman/Initialize(mapload) . = ..() visor_toggling() - AddComponent(/datum/component/hat_stabilizer) update_appearance() register_context() +/obj/item/clothing/head/helmet/space/plasmaman/add_stabilizer(loose_hat = FALSE) + ..() + /obj/item/clothing/head/helmet/space/plasmaman/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) context[SCREENTIP_CONTEXT_ALT_LMB] = "Toggle Welding Screen" if(istype(held_item, /obj/item/toy/crayon)) diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index ecae343f68506..78ea1d2f3b7a1 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -43,3 +43,7 @@ if(ismob(loc)) var/mob/M = loc M.update_worn_oversuit() + +/obj/item/clothing/suit/generate_digitigrade_icons(icon/base_icon, greyscale_colors) + var/icon/legs = icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "oversuit_worn") + return replace_icon_legs(base_icon, legs) diff --git a/code/modules/clothing/suits/bio.dm b/code/modules/clothing/suits/bio.dm index 25b28c74d1a7a..919d1da261ecb 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -17,6 +17,7 @@ if(flags_inv & HIDEFACE) AddComponent(/datum/component/clothing_fov_visor, FOV_90_DEGREES) AddComponent(/datum/component/adjust_fishing_difficulty, 6) + AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) /datum/armor/head_bio_hood bio = 100 diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index bb42fb1ed3975..2608c41eb4ad5 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -109,6 +109,7 @@ if(flags_inv & HIDEFACE) AddComponent(/datum/component/clothing_fov_visor, FOV_90_DEGREES) AddComponent(/datum/component/adjust_fishing_difficulty, 8) + AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) /datum/armor/utility_bomb_hood melee = 20 @@ -189,6 +190,7 @@ if(flags_inv & HIDEFACE) AddComponent(/datum/component/clothing_fov_visor, FOV_90_DEGREES) AddComponent(/datum/component/adjust_fishing_difficulty, 7) + AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) /datum/armor/utility_radiation bio = 60 diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 85fff72052b23..3fa11d2882c8b 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -9,7 +9,6 @@ interaction_flags_click = NEED_DEXTERITY armor_type = /datum/armor/clothing_under supports_variations_flags = CLOTHING_DIGITIGRADE_MASK - digitigrade_greyscale_config_worn = /datum/greyscale_config/jumpsuit/worn_digi equip_sound = 'sound/items/equip/jumpsuit_equip.ogg' drop_sound = 'sound/items/handling/cloth_drop.ogg' pickup_sound = 'sound/items/handling/cloth_pickup.ogg' @@ -142,6 +141,10 @@ adjusted = DIGITIGRADE_STYLE update_appearance() +/obj/item/clothing/under/generate_digitigrade_icons(icon/base_icon, greyscale_colors) + var/icon/legs = icon(SSgreyscale.GetColoredIconByType(/datum/greyscale_config/digitigrade, greyscale_colors), "jumpsuit_worn") + return replace_icon_legs(base_icon, legs) + /obj/item/clothing/under/equipped(mob/living/user, slot) ..() if((slot & ITEM_SLOT_ICLOTHING) && freshly_laundered) diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 9ae1c7d63e366..af64ca4e999e3 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -224,10 +224,12 @@ greyscale_config_inhand_left = null greyscale_config_inhand_right = null greyscale_config_worn = null - digitigrade_greyscale_colors = "#3f3f3f" can_adjust = FALSE flags_1 = NONE +/obj/item/clothing/under/color/rainbow/get_general_color(icon/base_icon) + return "#3f3f3f" + /obj/item/clothing/under/color/jumpskirt/rainbow name = "rainbow jumpskirt" desc = "A multi-colored jumpskirt!" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 70d2a5eeadb94..2c428f1c35c1a 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -33,7 +33,9 @@ desc = "Groovy!" icon_state = "psyche" inhand_icon_state = "p_suit" - digitigrade_greyscale_colors = "#3f3f3f" + +/obj/item/clothing/under/misc/psyche/get_general_color(icon/base_icon) + return "#3f3f3f" /obj/item/clothing/under/misc/vice_officer name = "vice officer's jumpsuit" diff --git a/code/modules/deathmatch/deathmatch_modifier.dm b/code/modules/deathmatch/deathmatch_modifier.dm index 9671f19c92a33..edddbe7e267bc 100644 --- a/code/modules/deathmatch/deathmatch_modifier.dm +++ b/code/modules/deathmatch/deathmatch_modifier.dm @@ -82,7 +82,7 @@ description = "Unaffected by critical condition and pain" /datum/deathmatch_modifier/tenacity/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby) - player.add_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_ANALGESIA), DEATHMATCH_TRAIT) + player.add_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_ANALGESIA, TRAIT_NO_DAMAGE_OVERLAY), DEATHMATCH_TRAIT) /datum/deathmatch_modifier/no_wounds name = "No Wounds" diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm index 15c7d22693255..d83865497805c 100644 --- a/code/modules/fishing/aquarium/aquarium.dm +++ b/code/modules/fishing/aquarium/aquarium.dm @@ -104,6 +104,8 @@ /obj/structure/aquarium/prefilled/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) //start with safe mode on + new /obj/item/aquarium_prop/sand(src) new /obj/item/aquarium_prop/seaweed(src) @@ -246,6 +248,8 @@ /obj/item/fish_tank/lawyer/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) //start with safe mode on + new /obj/item/aquarium_prop/sand(src) new /obj/item/aquarium_prop/seaweed(src) diff --git a/code/modules/fishing/bait.dm b/code/modules/fishing/bait.dm index 275f387cbdae7..50cb6ef09b163 100644 --- a/code/modules/fishing/bait.dm +++ b/code/modules/fishing/bait.dm @@ -88,13 +88,11 @@ rod.spin_frequency = null ///Called for every fish subtype by the fishing subsystem when initializing, to populate the list of fish that can be catched with this lure. -/obj/item/fishing_lure/proc/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) +/obj/item/fishing_lure/proc/is_catchable_fish(obj/item/fish/fish, list/fish_properties) var/intermediate_size = FISH_SIZE_SMALL_MAX + (FISH_SIZE_NORMAL_MAX - FISH_SIZE_SMALL_MAX) - if(!ISINRANGE(avg_size, FISH_SIZE_TINY_MAX * 0.5, intermediate_size)) + if(!ISINRANGE(fish.size, FISH_SIZE_TINY_MAX * 0.5, intermediate_size)) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE return TRUE @@ -120,11 +118,16 @@ . += span_info("You can catch the following fish with this lure: [english_list(known_fishes)].") ///Check if the fish is in the list of catchable fish for this fishing lure. Return value is a multiplier. -/obj/item/fishing_lure/check_bait(obj/item/fish/fish_type) +/obj/item/fishing_lure/check_bait(obj/item/fish/fish) var/multiplier = 0 - if(is_type_in_list(/obj/item/fishing_lure, SSfishing.fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT])) + var/is_instance = istype(fish) + var/list/fish_properties = SSfishing.fish_properties[is_instance ? fish.type : fish] + if(is_type_in_list(/obj/item/fishing_lure, fish_properties[FISH_PROPERTIES_FAV_BAIT])) multiplier += 2 - if(fish_type in SSfishing.lure_catchables[type]) + if(is_instance) + if(is_catchable_fish(fish, fish_properties)) + multiplier += 10 + else if(fish in SSfishing.lure_catchables[type]) multiplier += 10 return multiplier @@ -133,12 +136,10 @@ desc = "A bigger fishing lure that may attract larger fish. Tiny or picky prey will remain uninterested." icon_state = "plug" -/obj/item/fishing_lure/plug/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) - if(avg_size <= FISH_SIZE_SMALL_MAX) +/obj/item/fishing_lure/plug/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(fish.size <= FISH_SIZE_SMALL_MAX) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE return TRUE @@ -148,17 +149,17 @@ icon_state = "dropping" spin_frequency = list(1.5 SECONDS, 2.8 SECONDS) -/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) +/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish, list/fish_properties) var/list/sources = list(/datum/fish_source/toilet, /datum/fish_source/moisture_trap) for(var/datum/fish_source/source as anything in sources) var/datum/fish_source/instance = GLOB.preset_fish_sources[/datum/fish_source/toilet] - if(fish_type in instance.fish_table) + if(fish.type in instance.fish_table) return TRUE var/list/fav_baits = fish_properties[FISH_PROPERTIES_FAV_BAIT] for(var/list/identifier in fav_baits) if(identifier[FISH_BAIT_TYPE] == FISH_BAIT_FOODTYPE && (identifier[FISH_BAIT_VALUE] & (JUNKFOOD|GROSS|TOXIC))) return TRUE - if(initial(fish_type.beauty) <= FISH_BEAUTY_DISGUSTING) + if(fish.beauty <= FISH_BEAUTY_DISGUSTING) return TRUE return FALSE @@ -168,17 +169,15 @@ icon_state = "spoon" spin_frequency = list(1.25 SECONDS, 2.25 SECONDS) -/obj/item/fishing_lure/spoon/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) - if(!ISINRANGE(avg_size, FISH_SIZE_TINY_MAX + 1, FISH_SIZE_NORMAL_MAX)) +/obj/item/fishing_lure/spoon/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!ISINRANGE(fish.size, FISH_SIZE_TINY_MAX + 1, FISH_SIZE_NORMAL_MAX)) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE - var/fluid_type = initial(fish_type.required_fluid_type) + var/fluid_type = fish.required_fluid_type if(fluid_type == AQUARIUM_FLUID_FRESHWATER || fluid_type == AQUARIUM_FLUID_ANADROMOUS || fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE - if((/datum/fish_trait/amphibious in fish_traits) && fluid_type == AQUARIUM_FLUID_AIR) + if((/datum/fish_trait/amphibious in fish.fish_traits) && fluid_type == AQUARIUM_FLUID_AIR) return TRUE return FALSE @@ -188,9 +187,8 @@ icon_state = "artificial_fly" spin_frequency = list(1.1 SECONDS, 2 SECONDS) -/obj/item/fishing_lure/artificial_fly/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/picky_eater in fish_traits) +/obj/item/fishing_lure/artificial_fly/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/picky_eater in fish.fish_traits) return TRUE return FALSE @@ -216,9 +214,8 @@ . = ..() REMOVE_TRAIT(rod, TRAIT_ROD_IGNORE_ENVIRONMENT, type) -/obj/item/fishing_lure/led/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) +/obj/item/fishing_lure/led/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(length(list(/datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return TRUE return FALSE @@ -236,9 +233,8 @@ . = ..() REMOVE_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS, REF(src)) -/obj/item/fishing_lure/lucky_coin/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/shiny_lover in fish_traits) +/obj/item/fishing_lure/lucky_coin/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/shiny_lover in fish.fish_traits) return TRUE return FALSE @@ -248,9 +244,8 @@ icon_state = "algae" spin_frequency = list(3 SECONDS, 5 SECONDS) -/obj/item/fishing_lure/algae/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/vegan in fish_traits) +/obj/item/fishing_lure/algae/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/vegan in fish.fish_traits) return TRUE return FALSE @@ -260,11 +255,10 @@ icon_state = "grub" spin_frequency = list(1 SECONDS, 2.7 SECONDS) -/obj/item/fishing_lure/grub/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - if(initial(fish_type.average_size) >= FISH_SIZE_SMALL_MAX) +/obj/item/fishing_lure/grub/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(fish.size >= FISH_SIZE_SMALL_MAX) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater) & fish.fish_traits)) return FALSE return TRUE @@ -274,9 +268,8 @@ icon_state = "buzzbait" spin_frequency = list(0.8 SECONDS, 1.7 SECONDS) -/obj/item/fishing_lure/buzzbait/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/electrogenesis in fish_traits) +/obj/item/fishing_lure/buzzbait/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(HAS_TRAIT(fish, TRAIT_FISH_ELECTROGENESIS)) return TRUE return FALSE @@ -286,14 +279,13 @@ icon_state = "spinnerbait" spin_frequency = list(2 SECONDS, 4 SECONDS) -/obj/item/fishing_lure/spinnerbait/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(!(/datum/fish_trait/predator in fish_traits)) +/obj/item/fishing_lure/spinnerbait/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!(/datum/fish_trait/predator in fish.fish_traits)) return FALSE - var/init_fluid_type = initial(fish_type.required_fluid_type) + var/init_fluid_type = fish.required_fluid_type if(init_fluid_type == AQUARIUM_FLUID_FRESHWATER || init_fluid_type == AQUARIUM_FLUID_ANADROMOUS || init_fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE - if((/datum/fish_trait/amphibious in fish_traits) && init_fluid_type == AQUARIUM_FLUID_AIR) //fluid type is changed to freshwater on init + if((/datum/fish_trait/amphibious in fish.fish_traits) && init_fluid_type == AQUARIUM_FLUID_AIR) //fluid type is changed to freshwater on init return TRUE return FALSE @@ -303,11 +295,10 @@ icon_state = "daisy_chain" spin_frequency = list(2 SECONDS, 4 SECONDS) -/obj/item/fishing_lure/daisy_chain/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(!(/datum/fish_trait/predator in fish_traits)) +/obj/item/fishing_lure/daisy_chain/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!(/datum/fish_trait/predator in fish.fish_traits)) return FALSE - var/init_fluid_type = initial(fish_type.required_fluid_type) + var/init_fluid_type = fish.required_fluid_type if(init_fluid_type == AQUARIUM_FLUID_SALTWATER || init_fluid_type == AQUARIUM_FLUID_ANADROMOUS || init_fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE return FALSE diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index cee77ae003d88..3d91e1173fc2d 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -170,6 +170,8 @@ * Once set, the value shouldn't be changed, so don't make typos. */ var/fish_id + ///Used to redirect to another fish path so that catching this fish unlocks its entry instead. + var/obj/item/fish/fish_id_redirect_path /obj/item/fish/Initialize(mapload, apply_qualities = TRUE) . = ..() @@ -189,6 +191,9 @@ ADD_TRAIT(src, TRAIT_UNCOMPOSTABLE, REF(src)) //Composting a food that is not real food wouldn't work anyway. START_PROCESSING(SSobj, src) + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_FISH_STASIS), PROC_REF(enter_stasis)) + RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_FISH_STASIS), PROC_REF(exit_stasis)) + //Adding this because not all fish have the gore foodtype that makes them automatically eligible for dna infusion. ADD_TRAIT(src, TRAIT_VALID_DNA_INFUSION, INNATE_TRAIT) @@ -219,8 +224,26 @@ if(istype(held_item, /obj/item/fish_analyzer)) context[SCREENTIP_CONTEXT_LMB] = "Scan" return CONTEXTUAL_SCREENTIP_SET + if(istype(held_item, /obj/item/clothing/neck/stethoscope)) + context[SCREENTIP_CONTEXT_LMB] = "Check Pulse" + return CONTEXTUAL_SCREENTIP_SET return NONE +/obj/item/fish/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/clothing/neck/stethoscope)) + return NONE + user.balloon_alert_to_viewers("checking pulse") + if(!do_after(user, 2.5 SECONDS, src)) + return ITEM_INTERACT_FAILURE + // Sir... I'm afraid your fish is dying. + user.visible_message(span_notice("[user] checks the pulse of [src] with [tool]."), span_notice("You check the pulse of [src] with [tool].")) + var/warns = get_health_warnings(user, always_deep = TRUE) + if(!warns) + to_chat(user, span_notice("[src] appears to be perfectly healthy!")) + return ITEM_INTERACT_SUCCESS + to_chat(user, warns) + return ITEM_INTERACT_SUCCESS + /obj/item/fish/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) if(!HAS_TRAIT(interacting_with, TRAIT_CATCH_AND_RELEASE)) return NONE @@ -434,27 +457,46 @@ /obj/item/fish/examine(mob/user) . = ..() - if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH)) - if(status == FISH_DEAD) - . += span_deadsay("It's [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "taking the big snooze" : "dead"].") - else - var/list/warnings = list() - if(is_starving()) - warnings += "starving" - if(!HAS_TRAIT(src, TRAIT_FISH_STASIS) && !proper_environment()) - warnings += "drowning" - if(health < initial(health) * 0.6) - warnings += "sick" - if(length(warnings)) - . += span_warning("It's [english_list(warnings)].") if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH)) . += span_notice("It's [size] cm long.") . += span_notice("It weighs [weight] g.") - if(HAS_TRAIT(src, TRAIT_FISHING_BAIT)) - . += span_smallnoticeital("It can be used as a fishing bait.") + + . += get_health_warnings(user, always_deep = FALSE) + + if(HAS_TRAIT(src, TRAIT_FISHING_BAIT)) + . += span_smallnoticeital("It can be used as a fishing bait.") + if(bites_amount) . += span_warning("It's been bitten by someone.") +/obj/item/fish/proc/get_health_warnings(mob/user, always_deep = FALSE) + if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH) && !always_deep) + return + if(status == FISH_DEAD) + return span_deadsay("It's [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "taking the big snooze" : "dead"].") + + var/list/warnings = list() + if(get_starvation_mult()) + warnings += "starving" + if(!HAS_TRAIT(src, TRAIT_FISH_STASIS) && !proper_environment()) + warnings += "drowning" + + var/health_ratio = health / initial(health) + switch(health_ratio) + if(0 to 0.25) + warnings += "dying" + if(0.25 to 0.5) + warnings += "very unhealthy" + if(0.5 to 0.75) + warnings += "unhealthy" + if(0.75 to 0.9) + warnings += "mostly healthy" + + if(length(warnings)) + . += span_warning("It's [english_list(warnings)].") + + return . + /** * This proc takes a base size, base weight and deviation arguments to generate new size and weight through a gaussian distribution (bell curve) * Mainly used to determinate the size and weight of caught fish. @@ -758,25 +800,29 @@ . = ..() check_flopping() -/obj/item/fish/proc/enter_stasis() - ADD_TRAIT(src, TRAIT_FISH_STASIS, INNATE_TRAIT) - // Stop processing until inserted into aquarium again. +/// Stop processing once the stasis trait is added +/obj/item/fish/proc/enter_stasis(datum/source) + SIGNAL_HANDLER stop_flopping() STOP_PROCESSING(SSobj, src) -/obj/item/fish/proc/exit_stasis() - REMOVE_TRAIT(src, TRAIT_FISH_STASIS, INNATE_TRAIT) - if(status != FISH_DEAD) - START_PROCESSING(SSobj, src) +/// Start processing again when the stasis trait is removed +/obj/item/fish/proc/exit_stasis(datum/source) + SIGNAL_HANDLER + if(status == FISH_DEAD) + return + START_PROCESSING(SSobj, src) + check_flopping() -///Returns the 0-1 value for hunger -/obj/item/fish/proc/get_hunger() - . = CLAMP01((world.time - last_feeding) / feeding_frequency) +///Returns the value for hunger ranging from 0 to the cap (by default 1) +/obj/item/fish/proc/get_hunger(cap = 1) + . = clamp((world.time - last_feeding) / feeding_frequency, 0, cap) if(HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER)) return min(., 0.2) -/obj/item/fish/proc/is_starving() - return get_hunger() >= 1 +/obj/item/fish/proc/get_starvation_mult() + var/hunger = get_hunger(cap = 2) + return hunger >= 1 ? hunger : 0 ///Feed the fishes with the contents of the fish feed /obj/item/fish/proc/feed(datum/reagents/fed_reagents) @@ -873,12 +919,17 @@ if(HAS_TRAIT(src, TRAIT_FISH_STASIS) || status != FISH_ALIVE) return - process_health(seconds_per_tick) - if(ready_to_reproduce()) - try_to_reproduce() + //safe mode, don't do much except a few things that don't involve growing or reproducing. + if(loc && HAS_TRAIT_FROM(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)) + last_feeding += seconds_per_tick SECONDS + breeding_wait += seconds_per_tick SECONDS + else + process_health(seconds_per_tick) + if(ready_to_reproduce()) + try_to_reproduce() - if(HAS_TRAIT(src, TRAIT_FISH_ELECTROGENESIS) && COOLDOWN_FINISHED(src, electrogenesis_cooldown)) - try_electrogenesis() + if(HAS_TRAIT(src, TRAIT_FISH_ELECTROGENESIS) && COOLDOWN_FINISHED(src, electrogenesis_cooldown)) + try_electrogenesis() SEND_SIGNAL(src, COMSIG_FISH_LIFE, seconds_per_tick) @@ -961,7 +1012,7 @@ var/datum/reagent/medicine/strange_reagent/revival = locate() in reagents if(!revival) return - if(reagents[revival] >= 2 * w_class) + if(reagents[revival] >= 2 * w_class && revival.pre_rez_check(src)) set_status(FISH_ALIVE) else balloon_alert_to_viewers("twitches for a moment!") @@ -1098,13 +1149,14 @@ /obj/item/fish/proc/process_health(seconds_per_tick) var/health_change_per_second = 0 if(!proper_environment()) - health_change_per_second -= 3 //Dying here - if(is_starving()) - health_change_per_second -= 0.5 //Starving + health_change_per_second -= 2.5 //Dying here + var/starvation_mult = get_starvation_mult() + if(starvation_mult) + health_change_per_second -= 0.25 * starvation_mult //Starving else health_change_per_second += 0.5 //Slowly healing if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM)) - health_change_per_second -= 0.65 //This becomes - 0.15 if safe and not starving. + health_change_per_second -= 0.65 adjust_health(health + health_change_per_second * seconds_per_tick) @@ -1323,7 +1375,7 @@ flop_animation() /obj/item/fish/proc/try_electrogenesis() - if(status == FISH_DEAD || is_starving()) + if(status == FISH_DEAD || get_starvation_mult()) return COOLDOWN_START(src, electrogenesis_cooldown, ELECTROGENESIS_DURATION + ELECTROGENESIS_VARIANCE) var/fish_zap_range = 1 @@ -1407,7 +1459,7 @@ user.electrocute_act(5, src) //was it all worth it? fish_flags |= FISH_FLAG_PETTED new /obj/effect/temp_visual/heart(get_turf(src)) - if((/datum/fish_trait/aggressive in fish_traits) && prob(50)) + if((/datum/fish_trait/predator in fish_traits) && prob(50)) if(!in_aquarium) user.visible_message( span_warning("[src] dances around before biting [user]!"), diff --git a/code/modules/fishing/fish/chasm_detritus.dm b/code/modules/fishing/fish/chasm_detritus.dm index 50b35f7d07447..05db957100470 100644 --- a/code/modules/fishing/fish/chasm_detritus.dm +++ b/code/modules/fishing/fish/chasm_detritus.dm @@ -61,7 +61,9 @@ GLOBAL_LIST_INIT_TYPED(chasm_detritus_types, /datum/chasm_detritus, init_chasm_d var/default_spawn = pick(default_contents[default_contents_key]) return new default_spawn(fisher_turf) - return determine_detritus(chasm_contents) + var/atom/movable/detritus = determine_detritus(chasm_contents) + detritus.forceMove(fisher_turf) + return detritus /datum/chasm_detritus/proc/get_chasm_contents(turf/fishing_spot) . = list() diff --git a/code/modules/fishing/fish/fish_evolution.dm b/code/modules/fishing/fish/fish_evolution.dm index 8dd9bb0ffcd0d..1e4135b6ec12b 100644 --- a/code/modules/fishing/fish/fish_evolution.dm +++ b/code/modules/fishing/fish/fish_evolution.dm @@ -69,8 +69,15 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) /datum/fish_evolution/proc/get_evolution_tooltip() . = "" if(required_temperature_min > 0 || required_temperature_max < INFINITY) - var/max_temp = required_temperature_max < INFINITY ? " to [required_temperature_max]" : "" - . = "An aquarium temperature of [required_temperature_min][max_temp] is required." + var/temp_reqs = "" + if(required_temperature_min == 0) + temp_reqs = "below [required_temperature_max]" + else if(required_temperature_max == INFINITY) + temp_reqs = "above [required_temperature_min]" + else + temp_reqs = "of [required_temperature_min] to [required_temperature_max]" + . = "An aquarium temperature [temp_reqs]K is required." + if(conditions_note) . += " [conditions_note]" return . @@ -96,7 +103,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) name = "???" //The resulting fish is not shown on the catalog. probability = 40 new_fish_type = /obj/item/fish/mastodon - new_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/predator, /datum/fish_trait/aggressive) + new_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/predator, /datum/fish_trait/territorial) conditions_note = "The fish (and its mate) needs to be unusually big both in size and weight." show_result_on_wiki = FALSE @@ -125,13 +132,13 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) /datum/fish_evolution/chainsawfish probability = 30 new_fish_type = /obj/item/fish/chainsawfish - new_traits = list(/datum/fish_trait/predator, /datum/fish_trait/aggressive) - conditions_note = "The fish needs to be unusually big and aggressive" + new_traits = list(/datum/fish_trait/predator, /datum/fish_trait/territorial) + conditions_note = "The fish needs to be unusually big and territorial" /datum/fish_evolution/chainsawfish/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) var/double_avg_size = /obj/item/fish/goldfish::average_size * 2 var/double_avg_weight = /obj/item/fish/goldfish::average_weight * 2 - if(source.size >= double_avg_size && source.weight >= double_avg_weight && (/datum/fish_trait/aggressive in source.fish_traits)) + if(source.size >= double_avg_size && source.weight >= double_avg_weight && (/datum/fish_trait/territorial in source.fish_traits)) return ..() return FALSE @@ -168,10 +175,10 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) /datum/fish_evolution/moonfish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) if(source.size < (/obj/item/fish/moonfish/dwarf::average_size * 1.5) && source.size < (/obj/item/fish/moonfish/dwarf::average_weight * 1.5)) - return ..() + return FALSE if(mate && (mate.size < (/obj/item/fish/moonfish::average_size * 1.3) && mate.size < (/obj/item/fish/moonfish::average_weight * 1.3))) return FALSE - return FALSE + return ..() /datum/fish_evolution/dwarf_moonfish probability = 200 //guaranteed if the conditions are met @@ -185,3 +192,12 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) return FALSE return ..() +/datum/fish_evolution/lavaloop + probability = 85 + new_fish_type = /obj/item/fish/lavaloop + required_temperature_min = MIN_AQUARIUM_TEMP + 60 + +/datum/fish_evolution/plasmaloop + probability = 85 + new_fish_type = /obj/item/fish/lavaloop/plasma_river + required_temperature_max = MIN_AQUARIUM_TEMP + 60 diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index aa045c6308bf5..7211041915418 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -184,11 +184,12 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/nocturnal/proc/check_light(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(source.loc && (HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) || isturf(source.loc))) - var/turf/turf = get_turf(source) - var/light_amount = turf.get_lumcount() - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) - source.adjust_health(source.health - 0.5 * seconds_per_tick) + if(!source.loc || (!HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) && !isturf(source.loc))) + return + var/turf/turf = get_turf(source) + var/light_amount = turf.get_lumcount() + if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) + source.adjust_health(source.health - 0.5 * seconds_per_tick) /datum/fish_trait/nocturnal/apply_to_mob(mob/living/basic/mob) . = ..() @@ -518,20 +519,23 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) . = ..() ADD_TRAIT(fish, TRAIT_FISH_CROSSBREEDER, FISH_TRAIT_DATUM) -/datum/fish_trait/aggressive - name = "Aggressive" +/datum/fish_trait/territorial + name = "Territorial" inheritability = 80 diff_traits_inheritability = 40 - catalog_description = "This fish is aggressively territorial, and may attack fish that come close to it." + catalog_description = "This fish will start attacking other fish if the aquarium has five or more." -/datum/fish_trait/aggressive/apply_to_fish(obj/item/fish/fish) +/datum/fish_trait/territorial/apply_to_fish(obj/item/fish/fish) . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(try_attack_fish)) -/datum/fish_trait/aggressive/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick) +/datum/fish_trait/territorial/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) || !SPT_PROB(1, seconds_per_tick)) return + var/list/fishes = source.get_aquarium_fishes(TRUE, source) + if(length(fishes) < 5) + return for(var/obj/item/fish/victim as anything in source.get_aquarium_fishes(TRUE, source)) if(victim.status != FISH_ALIVE) continue @@ -596,6 +600,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) diff_traits_inheritability = 25 catalog_description = "This fish will invert the gravity of the bait at random. May fall upward outside after being caught." added_difficulty = 20 + reagents_to_add = list(/datum/reagent/gravitum = 2.3) /datum/fish_trait/antigrav/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame) minigame.special_effects |= FISHING_MINIGAME_RULE_ANTIGRAV diff --git a/code/modules/fishing/fish/types/air_space.dm b/code/modules/fishing/fish/types/air_space.dm index 3b053dc4c25eb..95418f5248a3b 100644 --- a/code/modules/fishing/fish/types/air_space.dm +++ b/code/modules/fishing/fish/types/air_space.dm @@ -122,7 +122,7 @@ fillet_type = /obj/item/food/fishmeat/carp/no_tox fish_traits = list( /datum/fish_trait/carnivore, - /datum/fish_trait/aggressive, + /datum/fish_trait/territorial, /datum/fish_trait/predator, /datum/fish_trait/necrophage, /datum/fish_trait/no_mating, diff --git a/code/modules/fishing/fish/types/anadromous.dm b/code/modules/fishing/fish/types/anadromous.dm index 7f9e6b4d2e26c..02d126bb30183 100644 --- a/code/modules/fishing/fish/types/anadromous.dm +++ b/code/modules/fishing/fish/types/anadromous.dm @@ -55,7 +55,7 @@ fishing_difficulty_modifier = 10 required_temperature_min = MIN_AQUARIUM_TEMP+12 required_temperature_max = MIN_AQUARIUM_TEMP+27 - fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive) + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/territorial) evolution_types = list(/datum/fish_evolution/armored_pike) compatible_types = list(/obj/item/fish/pike/armored) favorite_bait = list( diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm index 16687bc649790..cb4546e79616e 100644 --- a/code/modules/fishing/fish/types/freshwater.dm +++ b/code/modules/fishing/fish/types/freshwater.dm @@ -25,6 +25,7 @@ /obj/item/fish/goldfish/gill name = "McGill" desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case." + fish_id_redirect_path = /obj/item/fish/goldfish stable_population = 1 random_case_rarity = FISH_RARITY_NOPE fish_flags = parent_type::fish_flags & ~FISH_FLAG_SHOW_IN_CATALOG @@ -61,6 +62,7 @@ /obj/item/fish/goldfish/three_eyes/gill name = "McGill" desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case. It looks kinda different today..." + fish_id_redirect_path = /obj/item/fish/goldfish/three_eyes compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) beauty = FISH_BEAUTY_GREAT fish_flags = parent_type::fish_flags & ~FISH_FLAG_SHOW_IN_CATALOG @@ -80,7 +82,7 @@ average_size = 30 average_weight = 500 stable_population = 3 - fish_traits = list(/datum/fish_trait/aggressive) + fish_traits = list(/datum/fish_trait/territorial) required_temperature_min = MIN_AQUARIUM_TEMP+22 required_temperature_max = MIN_AQUARIUM_TEMP+30 @@ -163,6 +165,7 @@ /obj/item/fish/tadpole name = "tadpole" + fish_id = "tadpole" desc = "The larval spawn of an amphibian. A very minuscle, round creature with a long tail it uses to swim around." icon_state = "tadpole" average_size = 3 diff --git a/code/modules/fishing/fish/types/mining.dm b/code/modules/fishing/fish/types/mining.dm index 3a1d6ff83107f..73a9b8dc1752d 100644 --- a/code/modules/fishing/fish/types/mining.dm +++ b/code/modules/fishing/fish/types/mining.dm @@ -143,8 +143,11 @@ /obj/item/fish/boned/make_edible(weight_val) return //it's all bones and no meat. +/obj/item/fish/boned/get_health_warnings(mob/user, always_deep = FALSE) + return list(span_deadsay("It's bones.")) + /obj/item/fish/lavaloop - name = "lavaloop fish" + name = "lavaloop" fish_id = "lavaloop" desc = "Due to its curvature, it can be used as make-shift boomerang." icon_state = "lava_loop" @@ -156,13 +159,14 @@ required_fluid_type = AQUARIUM_FLUID_ANY_WATER //if we can survive hot lava and freezing plasrivers, we can survive anything fish_movement_type = /datum/fish_movement/zippy min_pressure = HAZARD_LOW_PRESSURE - required_temperature_min = MIN_AQUARIUM_TEMP+30 - required_temperature_max = MIN_AQUARIUM_TEMP+35 + required_temperature_min = MIN_AQUARIUM_TEMP+40 + required_temperature_max = MAX_AQUARIUM_TEMP+900 fish_traits = list( /datum/fish_trait/carnivore, /datum/fish_trait/heavy, ) compatible_types = list(/obj/item/fish/lavaloop/plasma_river) + evolution_types = list(/datum/fish_evolution/plasmaloop) hitsound = null throwforce = 5 beauty = FISH_BEAUTY_GOOD @@ -188,7 +192,7 @@ return list("chewy fish" = 2) /obj/item/fish/lavaloop/get_food_types() - return SEAFOOD|MEAT|GORE //Well-cooked in lava + return SEAFOOD|MEAT|GORE //Well-cooked in lava/plasma /obj/item/fish/lavaloop/proc/explode_on_user(mob/living/user) var/obj/item/bodypart/arm/active_arm = user.get_active_hand() @@ -204,8 +208,15 @@ return (target.mob_size >= MOB_SIZE_LARGE) /obj/item/fish/lavaloop/plasma_river + name = "plasmaloop" + desc = "A lavaloop that has evolved to survive in cold liquid plasma. Can be used as make-shift boomerang." fish_id = "plasma_lavaloop" + icon_state = "plasma_loop" + dedicated_in_aquarium_icon_state = /obj/item/fish/lavaloop::icon_state + "_small" + required_temperature_min = MIN_AQUARIUM_TEMP - 100 + required_temperature_max = MIN_AQUARIUM_TEMP+80 compatible_types = list(/obj/item/fish/lavaloop) + evolution_types = list(/datum/fish_evolution/lavaloop) maximum_bonus = 30 /obj/item/fish/lavaloop/plasma_river/explode_on_user(mob/living/user) diff --git a/code/modules/fishing/fish/types/ruins.dm b/code/modules/fishing/fish/types/ruins.dm index a9a7148986775..da2d2ef77293b 100644 --- a/code/modules/fishing/fish/types/ruins.dm +++ b/code/modules/fishing/fish/types/ruins.dm @@ -24,7 +24,7 @@ average_size = 180 average_weight = 5000 death_text = "%SRC stops moving." - fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/revival, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive) + fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/revival, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/territorial) beauty = FISH_BEAUTY_BAD /obj/item/fish/mastodon/Initialize(mapload, apply_qualities = TRUE) @@ -37,6 +37,9 @@ /obj/item/fish/mastodon/get_export_price(price, elasticity_percent) return ..() * 1.2 //This should push its soft-capped (it's pretty big) price a bit above the rest +/obj/item/fish/mastodon/get_health_warnings(mob/user, always_deep = FALSE) + return list(span_deadsay("It's bones.")) + ///From the cursed spring /obj/item/fish/soul name = "soulfish" diff --git a/code/modules/fishing/fish/types/station.dm b/code/modules/fishing/fish/types/station.dm index 8d958303bf319..353201a1afc78 100644 --- a/code/modules/fishing/fish/types/station.dm +++ b/code/modules/fishing/fish/types/station.dm @@ -64,6 +64,7 @@ /obj/item/fish/sludgefish/purple name = "purple sludgefish" + fish_id = "purple_sludgefish" desc = "A misshapen, fragile, loosely fish-like living goop. This one has developed sexual reproduction mechanisms, and a purple tint to boot." icon_state = "sludgefish_purple" random_case_rarity = FISH_RARITY_NOPE diff --git a/code/modules/fishing/fish/types/syndicate.dm b/code/modules/fishing/fish/types/syndicate.dm index 81366c2ba8f75..b36efd4a0c2f2 100644 --- a/code/modules/fishing/fish/types/syndicate.dm +++ b/code/modules/fishing/fish/types/syndicate.dm @@ -97,7 +97,7 @@ FISH_BAIT_VALUE = GORE, ), ) - fish_traits = list(/datum/fish_trait/aggressive, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) + fish_traits = list(/datum/fish_trait/territorial, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) required_temperature_min = MIN_AQUARIUM_TEMP+18 required_temperature_max = MIN_AQUARIUM_TEMP+26 @@ -207,7 +207,7 @@ random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS beauty = FISH_BEAUTY_GREAT fishing_difficulty_modifier = 20 - fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive, /datum/fish_trait/picky_eater, /datum/fish_trait/stinger) + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/territorial, /datum/fish_trait/picky_eater, /datum/fish_trait/stinger) evolution_types = null compatible_types = list(/obj/item/fish/pike) favorite_bait = list( diff --git a/code/modules/fishing/fish/types/tiziran.dm b/code/modules/fishing/fish/types/tiziran.dm index 7cc3ea94e6862..fdfbd578083c0 100644 --- a/code/modules/fishing/fish/types/tiziran.dm +++ b/code/modules/fishing/fish/types/tiziran.dm @@ -37,7 +37,7 @@ /obj/item/fish/moonfish/proc/egg_checks(datum/source, seconds_per_tick, growth, result_path) if(result_path != /obj/item/food/moonfish_eggs) //Don't stop the growth of the dwarf subtype. return - if(!proper_environment() || is_starving()) + if(!proper_environment() || get_starvation_mult()) return COMPONENT_DONT_GROW var/count = 0 for(var/obj/item/food/moonfish_eggs/egg in loc) diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index ab72b0b0fb26e..37aea262426bb 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -174,6 +174,9 @@ ///Check if tha target can be caught by the hook /obj/item/fishing_hook/proc/can_be_hooked(atom/target) + if(isliving(target)) + var/mob/living/mob = target + return (mob.mob_biotypes & MOB_AQUATIC) return isitem(target) ///Any special effect when hooking a target that's not managed by the fishing rod. @@ -212,8 +215,8 @@ SIGNAL_HANDLER REMOVE_TRAIT(rod, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) -/obj/item/fishing_hook/magnet/get_hook_bonus_multiplicative(fish_type, datum/fish_source/source) - if(fish_type == FISHING_DUD || ispath(fish_type, /obj/item/fish)) +/obj/item/fishing_hook/magnet/get_hook_bonus_multiplicative(fish_type) + if(fish_type == FISHING_DUD || ispath(fish_type, /obj/item/fish) || isfish(fish_type)) return ..() // We multiply the odds by five for everything that's not a fish nor a dud @@ -272,9 +275,9 @@ return "The hook on your fishing rod wasn't meant for traditional fishing, rendering it useless at doing so!" -/obj/item/fishing_hook/rescue/get_hook_bonus_multiplicative(fish_type, datum/fish_source/source) +/obj/item/fishing_hook/rescue/get_hook_bonus_multiplicative(fish_type) // Sorry, you won't catch fish with this. - if(ispath(fish_type, /obj/item/fish)) + if(ispath(fish_type, /obj/item/fish) || isfish(fish_type)) return RESCUE_HOOK_FISH_MULTIPLIER return ..() @@ -430,12 +433,12 @@ ///From the fishing mystery box. It's basically a lazarus and a few bottles of strange reagents. /obj/item/storage/box/fish_revival_kit name = "fish revival kit" - desc = "Become a fish doctor today." + desc = "Become a fish doctor today. A label on the side indicates that fish require two to ten reagent units to be splashed onto them for revival, depending on size." illustration = "fish" /obj/item/storage/box/fish_revival_kit/PopulateContents() new /obj/item/lazarus_injector(src) - new /obj/item/reagent_containers/cup/bottle/strange_reagent(src) + new /obj/item/reagent_containers/cup/bottle/fishy_reagent(src) new /obj/item/reagent_containers/cup(src) //to splash the reagents on the fish. new /obj/item/storage/fish_case(src) new /obj/item/storage/fish_case(src) diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index a8fa4d26ad8da..75cd0043d2274 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -357,11 +357,12 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) if(win) if(reward_path != FISHING_DUD) playsound(location, 'sound/effects/bigsplash.ogg', 100) - if(ispath(reward_path, /obj/item/fish)) + if(ispath(reward_path, /obj/item/fish) || isfish(reward_path)) var/obj/item/fish/fish_reward = reward_path - var/fish_id = initial(fish_reward.fish_id) + var/obj/item/fish/redirect_path = initial(fish_reward.fish_id_redirect_path) + var/fish_id = ispath(redirect_path, /obj/item/fish) ? initial(redirect_path.fish_id) : initial(fish_reward.fish_id) if(fish_id) - user.client?.give_award(/datum/award/score/progress/fish, user, initial(fish_reward.fish_id)) + user.client?.give_award(/datum/award/score/progress/fish, user, fish_id) SEND_SIGNAL(user, COMSIG_MOB_COMPLETE_FISHING, src, win) if(!QDELETED(src)) qdel(src) @@ -397,7 +398,13 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) playsound(location, 'sound/effects/fish_splash.ogg', 100) if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) - fish_icon = GLOB.specific_fish_icons[reward_path] || FISH_ICON_DEF + var/possible_icon + if(isatom(reward_path)) + var/atom/reward = reward_path + possible_icon = GLOB.specific_fish_icons[reward.type] + else + possible_icon = GLOB.specific_fish_icons[reward_path] + fish_icon = possible_icon || FISH_ICON_DEF switch(fish_icon) if(FISH_ICON_DEF) send_alert("fish!!!") @@ -455,11 +462,22 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) SIGNAL_HANDLER interrupt() +/datum/fishing_challenge/proc/on_reward_removed(datum/source) + SIGNAL_HANDLER + send_alert("reward gone!") + interrupt() + +/datum/fishing_challenge/proc/on_fish_death(obj/item/fish/source) + SIGNAL_HANDLER + if(source.status == FISH_DEAD) + win_anyway() + /datum/fishing_challenge/proc/win_anyway() - if(!completed) - //winning by timeout or idling around shouldn't give as much experience. - experience_multiplier *= 0.5 - complete(TRUE) + if(completed) + return + //winning by timeout / fish death shouldn't give as much experience. + experience_multiplier *= 0.5 + complete(TRUE) /datum/fishing_challenge/proc/hurt_fish(datum/source, obj/item/fish/reward) SIGNAL_HANDLER @@ -506,13 +524,15 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) if(difficulty > FISHING_DEFAULT_DIFFICULTY) completion -= MAX_FISH_COMPLETION_MALUS * (difficulty * 0.01) + var/is_fish_instance = isfish(reward_path) + /// Fish minigame properties - if(ispath(reward_path,/obj/item/fish)) + if(ispath(reward_path,/obj/item/fish) || is_fish_instance) var/obj/item/fish/fish = reward_path var/movement_path = initial(fish.fish_movement_type) mover = new movement_path(src) // Apply fish trait modifiers - var/list/fish_traits = SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] + var/list/fish_traits = is_fish_instance ? fish.fish_traits : SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] trait.minigame_mod(used_rod, user, src) @@ -552,6 +572,11 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) var/obj/item/fish/fish = reward_path var/wait_time = (initial(fish.health) / FISH_DAMAGE_PER_SECOND) SECONDS addtimer(CALLBACK(src, PROC_REF(win_anyway)), wait_time, TIMER_DELETE_ME) + else if(ismovable(reward_path)) + var/atom/movable/reward = reward_path + RegisterSignal(reward, COMSIG_MOVABLE_MOVED, PROC_REF(on_reward_removed)) + if(is_fish_instance) + RegisterSignal(reward, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_fish_death)) start_time = world.time ///Throws a stack with prefixed text. diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 64c9977fbb924..abf3a298462cb 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -88,8 +88,8 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) /// Background image name from /datum/asset/simple/fishing_minigame var/background = "background_default" var/fish_source_flags = NONE - /// If FISH_SOURCE_FLAG_EXPLOSIVE_MALUS is set, this will be used to keep track of the turfs where an explosion happened for when we'll spawn the loot. - var/list/exploded_turfs + /// If FISH_SOURCE_FLAG_EXPLOSIVE_MALUS is set, this will track of how much we're "exhausting" the system by bombing it repeatedly. + var/explosive_fishing_score = 0 ///When linked to a fishing portal, this will be the icon_state of this option in the radial menu var/radial_state = "default" ///When selected by the fishing portal, this will be the icon_state of the overlay shown on the machine. @@ -126,7 +126,8 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) stack_trace("wait_time_range for [type] is set but has length different than two") /datum/fish_source/Destroy() - exploded_turfs = null + if(explosive_fishing_score) + STOP_PROCESSING(SSprocessing, src) return ..() ///Called when src is set as the fish source of a fishing spot component @@ -179,16 +180,20 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) // Difficulty modifier added by the rod . += rod.difficulty_modifier - if(!ispath(result,/obj/item/fish)) + var/is_fish_instance = isfish(result) + if(!ispath(result,/obj/item/fish) && !is_fish_instance) // In the future non-fish rewards can have variable difficulty calculated here return var/obj/item/fish/caught_fish = result - var/list/fish_properties = SSfishing.fish_properties[caught_fish] + + //Just to clarify when we should use the path instead of the fish, which can be both a path and an instance. + var/result_path = is_fish_instance ? caught_fish.type : result + // Baseline fish difficulty . += initial(caught_fish.fishing_difficulty_modifier) - + var/list/fish_properties = SSfishing.fish_properties[result_path] if(rod.bait) var/obj/item/bait = rod.bait //Fav bait makes it easier @@ -203,7 +208,11 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) . += DISLIKED_BAIT_DIFFICULTY_MOD // Matching/not matching fish traits and equipment - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] + var/list/fish_traits + if(is_fish_instance) + fish_traits = caught_fish.fish_traits + else + fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] var/additive_mod = 0 var/multiplicative_mod = 1 @@ -216,6 +225,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) . += additive_mod . *= multiplicative_mod + ///Comsig proc from the fishing minigame for 'roll_reward' /datum/fish_source/proc/roll_reward_minigame(datum/source, obj/item/fishing_rod/rod, mob/fisherman, atom/location, list/rewards) SIGNAL_HANDLER @@ -264,8 +274,6 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) else if(istype(reward, /obj/effect/spawner)) // Do not attempt to forceMove() a spawner. It will break things, and the spawned item should already be at the mob's turf by now. fisherman.balloon_alert(fisherman, "caught something!") return - else // for fishing things like corpses, move them to the turf of the fisherman - INVOKE_ASYNC(reward, TYPE_PROC_REF(/atom/movable, forceMove), get_turf(fisherman)) fisherman.balloon_alert(fisherman, "caught [reward]!") return reward @@ -305,6 +313,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) /datum/fish_source/proc/spawn_reward(reward_path, atom/spawn_location, atom/fishing_spot) if(reward_path == FISHING_DUD) return + if(ismovable(reward_path)) + var/atom/movable/reward = reward_path + reward.forceMove(spawn_location) + return reward if(ispath(reward_path, /datum/chasm_detritus)) return GLOB.chasm_detritus_types[reward_path].dispense_detritus(spawn_location, fishing_spot) if(!ispath(reward_path, /atom/movable)) @@ -316,7 +328,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) return reward /// Returns the fish table, with with the unavailable items from fish_counts removed. -/datum/fish_source/proc/get_fish_table(from_explosion = FALSE) +/datum/fish_source/proc/get_fish_table(atom/location, from_explosion = FALSE) var/list/table = fish_table.Copy() //message bottles cannot spawn from explosions. They're meant to be one-time messages (rarely) and photos from past rounds //and it would suck if the pool of bottle messages were constantly being emptied by explosive fishing. @@ -335,8 +347,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) ///Multiplier used to make fishes more common compared to everything else. var/result_multiplier = 1 - - var/list/final_table = get_fish_table() + var/list/final_table = get_fish_table(location) if(bait) for(var/trait in weight_result_multiplier) @@ -356,7 +367,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) final_table[result] *= rod.hook.get_hook_bonus_multiplicative(result) final_table[result] += rod.hook.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative - if(ispath(result, /obj/item/fish)) + if(ispath(result, /obj/item/fish) || isfish(result)) if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) var/mult = bait.check_bait(result) @@ -383,7 +394,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/highest_fish_weight var/list/collected_fish_weights = list() for(var/fishable in table) - if(ispath(fishable, /obj/item/fish)) + if(ispath(fishable, /obj/item/fish) || isfish(fishable)) var/fish_weight = table[fishable] collected_fish_weights[fishable] = fish_weight if(fish_weight > highest_fish_weight) @@ -396,30 +407,38 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) table[fish] += round(difference**exponent, 1) /datum/fish_source/proc/get_fish_trait_catch_mods(weight, obj/item/fish/fish, obj/item/fishing_rod/rod, mob/user, atom/location) - if(!ispath(fish, /obj/item/fish)) + var/is_fish_instance = isfish(fish) + if(!ispath(fish, /obj/item/fish) && !is_fish_instance) return weight var/multiplier = 1 - for(var/fish_trait in SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS]) + var/list/fish_traits + if(is_fish_instance) + fish_traits = fish.fish_traits + else + fish_traits = SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] + for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] - var/list/mod = trait.catch_weight_mod(rod, user, location, fish) + var/list/mod = trait.catch_weight_mod(rod, user, location, is_fish_instance ? fish.type : fish) weight += mod[ADDITIVE_FISHING_MOD] multiplier *= mod[MULTIPLICATIVE_FISHING_MOD] return round(weight * multiplier, 1) ///returns true if this fishing spot has fish that are shown in the catalog. -/datum/fish_source/proc/has_known_fishes() - for(var/reward in fish_table) - if(!ispath(reward, /obj/item/fish)) +/datum/fish_source/proc/has_known_fishes(atom/location) + var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG + for(var/reward in get_fish_table(location)) + if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward - if(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) + if(!show_anyway && initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) return TRUE return FALSE ///Add a string with the names of catchable fishes to the examine text. /datum/fish_source/proc/get_catchable_fish_names(mob/user, atom/location, list/examine_text) var/list/known_fishes = list() + var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG var/obj/item/fishing_rod/rod = user.get_active_held_item() var/list/final_table @@ -432,17 +451,18 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/list/rodless_weights = list() var/total_rod_weight = 0 var/list/rod_weights = list() - for(var/reward in fish_table) - var/weight = fish_table[reward] + var/list/table = get_fish_table(location) + for(var/reward in table) + var/weight = table[reward] var/final_weight if(rod) total_weight += weight final_weight = final_table[reward] total_rod_weight += final_weight - if(!ispath(reward, /obj/item/fish)) + if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward - if(!(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG)) + if(!show_anyway && !(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG)) continue if(rod) rodless_weights[reward] = weight @@ -471,32 +491,30 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/info = "You can catch the following fish here" if(rod) - info = span_tooltip("boldened are the fish you're more likely to catch with your current setup. The opposite is true for smaller names", info) + info = span_tooltip("In bold are fish you're more likely to catch with the current setup. The opposite is true for the smaller font", info) examine_text += span_info("[info]: [english_list(known_fishes)].") +///How much the explosive_fishing_score impacts explosive fishing. The higher the value, the stronger the malus for repeated calls +#define EXPLOSIVE_FISHING_MALUS_EXPONENT 0.55 +///How much the explosive_fishing_score is reduced each second. +#define EXPLOSIVE_FISHING_RECOVERY_RATE 0.18 + /datum/fish_source/proc/spawn_reward_from_explosion(atom/location, severity) - if(!(fish_source_flags & FISH_SOURCE_FLAG_EXPLOSIVE_MALUS)) - explosive_spawn(isturf(location) ? location : location.drop_location(), severity) - return - if(isnull(exploded_turfs)) - exploded_turfs = list() - addtimer(CALLBACK(src, PROC_REF(post_explosion_spawn)), 1) //run this the next tick. - var/turf/turf = get_turf(location) - var/peak_severity = max(exploded_turfs[turf], severity) - exploded_turfs[turf] = peak_severity - -/datum/fish_source/proc/post_explosion_spawn() - var/multiplier = 1/(length(exploded_turfs)**0.5) - for(var/turf/turf as anything in exploded_turfs) - explosive_spawn(turf, exploded_turfs[turf], multiplier) - exploded_turfs = null - -/datum/fish_source/proc/explosive_spawn(atom/location, severity, multiplier = 1) + SIGNAL_HANDLER + var/multiplier = 1 + if(fish_source_flags & FISH_SOURCE_FLAG_EXPLOSIVE_MALUS) + if(explosive_fishing_score <= 0) + explosive_fishing_score = 1 + START_PROCESSING(SSprocessing, src) + else + explosive_fishing_score++ + multiplier = explosive_fishing_score**-EXPLOSIVE_FISHING_MALUS_EXPONENT for(var/i in 1 to (severity + 2)) if(!prob((100 + 100 * severity)/i * multiplier)) continue - var/reward_loot = pick_weight(get_fish_table(from_explosion = TRUE)) - var/atom/movable/reward = simple_dispense_reward(reward_loot, location, location) + var/reward_loot = pick_weight(get_fish_table(location, from_explosion = TRUE)) + var/atom/spawn_location = isturf(location) ? location : location.drop_location() + var/atom/movable/reward = simple_dispense_reward(reward_loot, spawn_location, location) if(isnull(reward)) continue if(isfish(reward)) @@ -508,6 +526,15 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) if(severity >= EXPLODE_DEVASTATE) reward.ex_act(EXPLODE_LIGHT) +/datum/fish_source/process(seconds_per_tick) + explosive_fishing_score -= EXPLOSIVE_FISHING_RECOVERY_RATE * seconds_per_tick + if(explosive_fishing_score <= 0) + STOP_PROCESSING(SSprocessing, src) + explosive_fishing_score = 0 + +#undef EXPLOSIVE_FISHING_MALUS_EXPONENT +#undef EXPLOSIVE_FISHING_RECOVERY_RATE + ///Called when releasing a fish in a fishing spot with the TRAIT_CATCH_AND_RELEASE trait. /datum/fish_source/proc/readd_fish(obj/item/fish/fish, mob/living/releaser) var/is_morbid = HAS_MIND_TRAIT(releaser, TRAIT_MORBID) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 1828091f0616e..b14ec608558d1 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -601,7 +601,7 @@ seeds_to_draw_from -= seed_path var/picked_path = pick(seeds_to_draw_from) - return new picked_path(get_turf(fishing_spot)) + return new picked_path(spawn_location) /datum/fish_source/carp_rift catalog_description = "Space Dragon Rifts" @@ -698,6 +698,46 @@ return data +#define RANDOM_AQUARIUM_FISH "random_aquarium_fish" + +/datum/fish_source/aquarium + radial_state = "fish_tank" + fish_table = list( + FISHING_DUD = 10, + ) + fish_source_flags = FISH_SOURCE_FLAG_NO_BLUESPACE_ROD|FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG + fishing_difficulty = FISHING_EASY_DIFFICULTY - 5 + +#undef RANDOM_AQUARIUM_FISH + +/datum/fish_source/aquarium/get_fish_table(atom/location, from_explosion = FALSE) + if(istype(location, /obj/machinery/fishing_portal_generator)) + var/obj/machinery/fishing_portal_generator/portal = location + location = portal.current_linked_atom + var/list/table = list() + for(var/obj/item/fish/fish in location) + if(fish.status == FISH_DEAD) //dead fish cannot be caught + continue + table[fish] = 10 + if(!length(table)) + return fish_table + return table + +/datum/fish_source/aquarium/spawn_reward_from_explosion(atom/location, severity) + return //If the aquarium breaks, all fish are released anyway. + +/datum/fish_source/aquarium/generate_wiki_contents(datum/autowiki/fish_sources/wiki) + var/list/data = list() + + data += LIST_VALUE_WRAP_LISTS(list( + FISH_SOURCE_AUTOWIKI_NAME = "Fish", + FISH_SOURCE_AUTOWIKI_DUD = "", + FISH_SOURCE_AUTOWIKI_WEIGHT = 100, + FISH_SOURCE_AUTOWIKI_NOTES = "Any fish currently inside the aquarium, be they alive or dead.", + )) + + return data + /datum/fish_source/hot_spring catalog_description = "Hot Springs" radial_state = "onsen" diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm b/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm index ac4f257dc2a52..30cb60cc243ee 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm @@ -88,12 +88,11 @@ /obj/structure/aquarium/donkfish/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) new /obj/item/aquarium_prop/rocks(src) new /obj/item/aquarium_prop/seaweed(src) new /obj/item/fish/donkfish(src) new /obj/item/fish/donkfish(src) - create_reagents(20, SEALED_CONTAINER) - reagents.add_reagent(/datum/reagent/consumable/nutriment, 20) //gimmick ketchup bottle for healing minor injuries /obj/item/reagent_containers/condiment/donksauce diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index 216275a239076..a6b2f075b8f1e 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -6,6 +6,7 @@ icon = 'icons/obj/clothing/suits/utility.dmi' worn_icon = 'icons/mob/clothing/suits/utility.dmi' inhand_icon_state = null + supports_variations_flags = CLOTHING_DIGITIGRADE_MASK body_parts_covered = CHEST|GROIN|LEGS|ARMS cold_protection = CHEST|GROIN|LEGS|ARMS min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT @@ -15,6 +16,9 @@ armor_type = /datum/armor/hooded_explorer resistance_flags = FIRE_PROOF +/obj/item/clothing/suit/hooded/explorer/get_general_color(icon/base_icon) + return "#796755" + /datum/armor/hooded_explorer melee = 30 bullet = 10 diff --git a/code/modules/mob/living/basic/cult/constructs/harvester.dm b/code/modules/mob/living/basic/cult/constructs/harvester.dm index 95a5956825421..971c341a73abf 100644 --- a/code/modules/mob/living/basic/cult/constructs/harvester.dm +++ b/code/modules/mob/living/basic/cult/constructs/harvester.dm @@ -53,7 +53,8 @@ carbon_target.Paralyze(6 SECONDS) visible_message(span_danger("[src] knocks [carbon_target] down!")) - to_chat(src, span_cult_large("\"Bring [carbon_target.p_them()] to me.\"")) + if(theme == THEME_CULT) + to_chat(src, span_cult_large("\"Bring [carbon_target.p_them()] to me.\"")) /datum/action/innate/seek_master name = "Seek your Master" @@ -142,8 +143,8 @@ can_repair = FALSE slowed_by_drag = FALSE faction = list(FACTION_HERETIC) - maxHealth = 35 - health = 35 + maxHealth = 45 + health = 45 melee_damage_lower = 20 melee_damage_upper = 25 // Dim green diff --git a/code/modules/mob/living/basic/heretic/fire_shark.dm b/code/modules/mob/living/basic/heretic/fire_shark.dm index 1ac4ccb7b237a..0dfb9a3a1578c 100644 --- a/code/modules/mob/living/basic/heretic/fire_shark.dm +++ b/code/modules/mob/living/basic/heretic/fire_shark.dm @@ -5,7 +5,7 @@ icon_state = "fire_shark" icon_living = "fire_shark" pass_flags = PASSTABLE | PASSMOB - mob_biotypes = MOB_ORGANIC | MOB_BEAST + mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_AQUATIC speed = -0.5 health = 16 maxHealth = 16 diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm index c0b49fbdc6199..71e938c5164e6 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk_overheat.dm @@ -2,6 +2,7 @@ /datum/status_effect/basilisk_overheat id = "basilisk_overheat" duration = 3 MINUTES + alert_type = null /// Things which will chill us out if we get hit by them var/static/list/chilling_reagents = list( /datum/reagent/medicine/cryoxadone, diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index d8288360bd1a2..ebca48f2fa201 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -12,6 +12,7 @@ maxHealth = 150 health = 150 obj_damage = 15 + mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_AQUATIC melee_damage_lower = 15 melee_damage_upper = 19 attack_verb_continuous = "snips" diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.dm b/code/modules/mob/living/basic/pets/penguin/penguin.dm index 0df0dfedcb29c..376234f3a3e52 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin.dm @@ -11,6 +11,7 @@ response_harm_simple = "kick" faction = list(FACTION_NEUTRAL) + mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_AQUATIC ai_controller = /datum/ai_controller/basic_controller/penguin ///it can lay an egg? var/can_lay_eggs = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index b16a00a6906b1..acd72af2b3889 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -19,7 +19,7 @@ icon_dead = "base_dead" icon_gib = "carp_gib" gold_core_spawnable = HOSTILE_SPAWN - mob_biotypes = MOB_ORGANIC | MOB_BEAST + mob_biotypes = MOB_ORGANIC | MOB_BEAST | MOB_AQUATIC health = 25 maxHealth = 25 pressure_resistance = 200 diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm index b7bc6e34dcf7e..6b5f63bf00f0e 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_effects.dm @@ -1,5 +1,7 @@ /// Parent type for all unique revenant status effects /datum/status_effect/revenant + id = STATUS_EFFECT_ID_ABSTRACT + alert_type = null /datum/status_effect/revenant/on_creation(mob/living/new_owner, duration) if(isnum(duration)) diff --git a/code/modules/mob/living/basic/vermin/axolotl.dm b/code/modules/mob/living/basic/vermin/axolotl.dm index 3b1f630df40f1..2ee244a7f62e4 100644 --- a/code/modules/mob/living/basic/vermin/axolotl.dm +++ b/code/modules/mob/living/basic/vermin/axolotl.dm @@ -12,7 +12,7 @@ density = FALSE pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY - mob_biotypes = MOB_ORGANIC | MOB_BEAST + mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_AQUATIC gold_core_spawnable = FRIENDLY_SPAWN response_help_continuous = "pets" diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm index d2a634b7e9edd..169fa4e833563 100644 --- a/code/modules/mob/living/basic/vermin/frog.dm +++ b/code/modules/mob/living/basic/vermin/frog.dm @@ -4,7 +4,7 @@ icon_state = "frog" icon_living = "frog" icon_dead = "frog_dead" - mob_biotypes = MOB_ORGANIC|MOB_BEAST + mob_biotypes = MOB_ORGANIC|MOB_BEAST|MOB_AQUATIC verb_say = "ribbits" verb_ask = "ribbits inquisitively" verb_exclaim = "croaks" diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 7ff695e4a744f..408a3c30925fd 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -244,10 +244,6 @@ paper_note.show_through_camera(usr) -/mob/living/carbon/on_fall() - . = ..() - loc?.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing - /mob/living/carbon/resist_buckle() if(!HAS_TRAIT(src, TRAIT_RESTRAINED)) buckled.user_unbuckle_mob(src, src) @@ -749,7 +745,7 @@ //Fire and Brute damage overlay (BSSR) var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp - if(hurtdamage && !HAS_TRAIT(src, TRAIT_ANALGESIA)) + if(hurtdamage && !HAS_TRAIT(src, TRAIT_NO_DAMAGE_OVERLAY)) var/severity = 0 switch(hurtdamage) if(5 to 15) diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 73ce6808339b8..6227e9558db8a 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -573,20 +573,70 @@ There are several things that need to be remembered: return icon(female_clothing_icon) -// These coordonates point to roughly somewhere in the middle of the left leg -// Used in approximating what color the pants of clothing should be +/// Modifies a sprite to conform to digitigrade body shapes +/proc/wear_digi_version(icon/base_icon, obj/item/item, key, greyscale_colors) + ASSERT(istype(item), "wear_digi_version: no item passed") + ASSERT(istext(key), "wear_digi_version: no key passed") + if(isnull(greyscale_colors) || length(SSgreyscale.ParseColorString(greyscale_colors)) > 1) + greyscale_colors = item.get_general_color(base_icon) + + var/index = "[key]-[item.type]-[greyscale_colors]" + var/static/list/digitigrade_clothing_cache = list() + var/icon/resulting_icon = digitigrade_clothing_cache[index] + if(!resulting_icon) + resulting_icon = item.generate_digitigrade_icons(base_icon, greyscale_colors) + if(!resulting_icon) + stack_trace("[item.type] is set to generate a masked digitigrade icon, but generate_digitigrade_icons was not implemented (or error'd).") + return base_icon + digitigrade_clothing_cache[index] = fcopy_rsc(resulting_icon) + + return icon(resulting_icon) + +/// Modifies a sprite to replace the legs with a new version +/proc/replace_icon_legs(icon/base_icon, icon/new_legs) + var/static/icon/leg_mask + if(!leg_mask) + leg_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_leg_mask") + + // cuts the legs off + base_icon.Blend(leg_mask, ICON_SUBTRACT) + // staples the new legs on + base_icon.Blend(new_legs, ICON_OVERLAY) + return base_icon + +/** + * Generates a digitigrade version of this item's worn icon + * + * Arguments: + * * base_icon: The icon to generate the digitigrade icon from + * * greyscale_colors: The greyscale colors to use for the digitigrade icon + * + * Returns an icon that is the digitigrade version of the item's worn icon + * Returns null if the item has no support for digitigrade variations via this method + */ +/obj/item/proc/generate_digitigrade_icons(icon/base_icon, greyscale_colors) + return null + +/** + * Get what color the item is on "average" + * Can be used to approximate what color this item is/should be + * + * Arguments: + * * base_icon: The icon to get the color from + */ +/obj/item/proc/get_general_color(icon/base_icon) + if(greyscale_colors && length(SSgreyscale.ParseColorString(greyscale_colors)) == 1) + return greyscale_colors + return color + +// These coordinates point to the middle of the left leg #define LEG_SAMPLE_X_LOWER 13 #define LEG_SAMPLE_X_UPPER 14 - #define LEG_SAMPLE_Y_LOWER 8 #define LEG_SAMPLE_Y_UPPER 9 -/// Modifies a sprite to conform to digitigrade body shapes -/proc/wear_digi_version(icon/base_icon, key, greyscale_config = /datum/greyscale_config/jumpsuit/worn_digi, greyscale_colors) - ASSERT(key, "wear_digi_version: no key passed") - ASSERT(ispath(greyscale_config, /datum/greyscale_config), "wear_digi_version: greyscale_config is not a valid path (got: [greyscale_config])") - // items with greyscale colors containing multiple colors are invalid - if(isnull(greyscale_colors) || length(SSgreyscale.ParseColorString(greyscale_colors)) > 1) +/obj/item/clothing/get_general_color(icon/base_icon) + if(slot_flags & (ITEM_SLOT_ICLOTHING|ITEM_SLOT_OCLOTHING)) var/pant_color // approximates the color of the pants by sampling a few pixels in the middle of the left leg for(var/x in LEG_SAMPLE_X_LOWER to LEG_SAMPLE_X_UPPER) @@ -594,37 +644,26 @@ There are several things that need to be remembered: var/xy_color = base_icon.GetPixel(x, y) pant_color = pant_color ? BlendRGB(pant_color, xy_color, 0.5) : xy_color - greyscale_colors = pant_color || "#1d1d1d" // black pants always look good - - var/index = "[key]-[greyscale_config]-[greyscale_colors]" - var/static/list/digitigrade_clothing_icons = list() - var/icon/digitigrade_clothing_icon = digitigrade_clothing_icons[index] - if(!digitigrade_clothing_icon) - var/static/icon/torso_mask - if(!torso_mask) - torso_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_torso_mask") - var/static/icon/leg_mask - if(!leg_mask) - leg_mask = icon('icons/mob/clothing/under/masking_helpers.dmi', "digi_leg_mask") - - base_icon.Blend(leg_mask, ICON_SUBTRACT) // cuts the legs off - - var/icon/leg_icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors) - leg_icon.Blend(torso_mask, ICON_SUBTRACT) // cuts the torso off - - base_icon.Blend(leg_icon, ICON_OVERLAY) // puts the new legs on - - digitigrade_clothing_icon = fcopy_rsc(base_icon) - digitigrade_clothing_icons[index] = digitigrade_clothing_icon + return pant_color || "#1d1d1d" // black pants always look good - return icon(digitigrade_clothing_icon) + return ..() #undef LEG_SAMPLE_X_LOWER #undef LEG_SAMPLE_X_UPPER - #undef LEG_SAMPLE_Y_LOWER #undef LEG_SAMPLE_Y_UPPER +// Points to the tip of the left foot +#define SHOE_SAMPLE_X 11 +#define SHOE_SAMPLE_Y 2 + +/obj/item/clothing/shoes/get_general_color(icon/base_icon) + // just grabs the color of the middle of the left foot + return base_icon.GetPixel(SHOE_SAMPLE_X, SHOE_SAMPLE_Y) || "#1d1d1d" + +#undef SHOE_SAMPLE_X +#undef SHOE_SAMPLE_Y + /mob/living/carbon/human/proc/get_overlays_copy(list/unwantedLayers) var/list/out = new for(var/i in 1 to TOTAL_LAYERS) @@ -774,9 +813,9 @@ generate/load female uniform sprites matching all previously decided variables if(!isinhands && is_digi && (supports_variations_flags & CLOTHING_DIGITIGRADE_MASK)) building_icon = wear_digi_version( base_icon = building_icon || icon(file2use, t_state), + item = src, key = "[t_state]-[file2use]-[female_uniform]", - greyscale_config = digitigrade_greyscale_config_worn || greyscale_config_worn, - greyscale_colors = digitigrade_greyscale_colors || greyscale_colors || color, + greyscale_colors = greyscale_colors, ) if(building_icon) standing = mutable_appearance(building_icon, layer = -layer2use) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 476fe9baa92df..e2021864bcaef 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1923,6 +1923,9 @@ GLOBAL_LIST_EMPTY(fire_appearances) /// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment. /mob/living/proc/on_fall() + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_LIVING_THUD) + loc?.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing return /mob/living/forceMove(atom/destination) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 46cfb5980a20b..e2a898b352642 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -350,6 +350,7 @@ "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call", max_length = MAX_MESSAGE_LEN, + encode = FALSE, ) if(incapacitated) diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 8e222dd54c765..a451166bcada8 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -119,7 +119,7 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real if(opened) to_chat(user, span_warning("You must close the cover to swipe an ID card!")) else - if(allowed(usr)) + if(allowed(user)) locked = !locked to_chat(user, span_notice("You [ locked ? "lock" : "unlock"] [src]'s cover.")) update_icons() @@ -273,7 +273,7 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real . &= ~(SHOVE_CAN_MOVE|SHOVE_CAN_HIT_SOMETHING) /mob/living/silicon/robot/welder_act(mob/living/user, obj/item/tool) - if(user.combat_mode && usr != src) + if(user.combat_mode && user != src) return FALSE . = TRUE user.changeNext_move(CLICK_CD_MELEE) diff --git a/code/modules/mod/mod_clothes.dm b/code/modules/mod/mod_clothes.dm index 8c5e7a2c4904f..7c63655dee86c 100644 --- a/code/modules/mod/mod_clothes.dm +++ b/code/modules/mod/mod_clothes.dm @@ -11,6 +11,11 @@ cold_protection = HEAD item_flags = IMMUTABLE_SLOW +// Even without a hat stabilizer, hats can be worn - however, they'll fall off very easily +/obj/item/clothing/head/mod/Initialize(mapload) + . = ..() + AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) + /obj/item/clothing/suit/mod name = "MOD chestplate" desc = "A chestplate for a MODsuit." diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index d1817e5170ba3..ad81fa828350a 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -699,7 +699,8 @@ var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) if(!istype(helmet)) return - helmet.AddComponent(/datum/component/hat_stabilizer) + // Override pre-existing component + helmet.AddComponent(/datum/component/hat_stabilizer, loose_hat = FALSE) /obj/item/mod/module/hat_stabilizer/on_part_deactivation(deleting = FALSE) if(deleting) @@ -707,7 +708,8 @@ var/obj/item/clothing/helmet = mod.get_part_from_slot(ITEM_SLOT_HEAD) if(!istype(helmet)) return - qdel(helmet.GetComponent(/datum/component/hat_stabilizer)) + // Override again! + helmet.AddComponent(/datum/component/hat_stabilizer, loose_hat = TRUE) /obj/item/mod/module/hat_stabilizer/syndicate name = "MOD elite hat stabilizer module" diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index c4c5d42164ce0..43ef0eee70fe5 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -523,10 +523,16 @@ target = select_target(target_turf, target) continue - if (SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, src) & PROJECTILE_INTERRUPT_HIT) + var/target_signal = SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, src) + if (target_signal & PROJECTILE_INTERRUPT_HIT_PHASE) + return PROJECTILE_IMPACT_PASSED + if (target_signal & PROJECTILE_INTERRUPT_HIT) return PROJECTILE_IMPACT_INTERRUPTED - if (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, target) & PROJECTILE_INTERRUPT_HIT) + var/self_signal = SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, target) + if (self_signal & PROJECTILE_INTERRUPT_HIT_PHASE) + return PROJECTILE_IMPACT_PASSED + if (self_signal & PROJECTILE_INTERRUPT_HIT) return PROJECTILE_IMPACT_INTERRUPTED if(mode == PROJECTILE_PIERCE_HIT) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 90937471bf1c4..e43512426dccf 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -13,7 +13,7 @@ inverse_chem_val = 0.1 inverse_chem = null creation_purity = CONSUMABLE_STANDARD_PURITY - /// How much nutrition this reagent supplies + /// How much nutrition this reagent supplies. Look at get_nutriment_factor() for an understanding. var/nutriment_factor = 1 /// affects mood, typically higher for mixed drinks with more complex recipes' var/quality = 0 @@ -54,7 +54,7 @@ if(isitem(the_real_food) && !is_reagent_container(the_real_food)) exposed_mob.add_mob_memory(/datum/memory/good_food, food = the_real_food) -/// Gets just how much nutrition this reagent is worth for the passed mob +/// Gets just how much nutrition this reagent supplies per server tick to the eater /datum/reagent/consumable/proc/get_nutriment_factor(mob/living/carbon/eater) return nutriment_factor * REAGENTS_METABOLISM * purity * 2 @@ -861,6 +861,27 @@ taste_description = "rancid fungus" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/consumable/moltobeso + name = "Molt'Obeso" //pardon my Italian + description = "Concentrated gluttony." + color = "#f8fc36" + taste_description = "gluttony" + taste_mult = 0.3 + nutriment_factor = 0 //the essence of this sauce is to stimulate hunger and improve the absorption of calories from food eaten + metabolization_rate = 0.025 * REAGENTS_METABOLISM + metabolized_traits = list(TRAIT_GLUTTON) + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + +/datum/reagent/consumable/moltobeso/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + for(var/datum/reagent/consumable/food in affected_mob.reagents.reagent_list) + if(food == src) + continue + var/food_factor = food.get_nutriment_factor(affected_mob) + if(food_factor <= 0) + continue + affected_mob.adjust_nutrition(food_factor * REM * seconds_per_tick) + /datum/reagent/consumable/eggrot name = "Rotten Eggyolk" description = "It smells absolutely dreadful." diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index da743f9656352..9a169257e1c0c 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -921,6 +921,10 @@ /// The maximum amount of damage we can revive from, as a ratio of max health var/max_revive_damage_ratio = 2 +// To override for subtypes. +/datum/reagent/medicine/strange_reagent/proc/pre_rez_check(atom/thing_to_rez) + return TRUE + /datum/reagent/medicine/strange_reagent/instant name = "Stranger Reagent" instant = TRUE @@ -980,6 +984,11 @@ exposed_mob.do_jitter_animation(10) return + if(!pre_rez_check(exposed_mob)) + exposed_mob.visible_message(span_warning("[exposed_mob]'s body twitches slightly.")) + exposed_mob.do_jitter_animation(1) + return + exposed_mob.visible_message(span_warning("[exposed_mob]'s body starts convulsing!")) exposed_mob.notify_revival("Your body is being revived with Strange Reagent!") exposed_mob.do_jitter_animation(10) @@ -1010,6 +1019,27 @@ if(need_mob_update) return UPDATE_MOB_HEALTH +/datum/reagent/medicine/strange_reagent/fishy_reagent + name = "Fishy Reagent" + description = "This reagent has a chemical composition very similar to that of Strange Reagent, however, it seems to work purely and only on... fish. Or at least, aquatic creatures." + reagent_state = LIQUID + color = "#5ee8b3" + metabolization_rate = 1.25 * REAGENTS_METABOLISM + taste_description = "magnetic scales" + ph = 0.5 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + +// only revives fish. +/datum/reagent/medicine/strange_reagent/fishy_reagent/pre_rez_check(atom/thing_to_rez) + if(ismob(thing_to_rez)) + var/mob/living/mob_to_rez = thing_to_rez + if(mob_to_rez.mob_biotypes & MOB_AQUATIC) + return TRUE + return FALSE + if(isfish(thing_to_rez)) + return TRUE + return FALSE + /datum/reagent/medicine/mannitol name = "Mannitol" description = "Efficiently restores brain damage." diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 868917893c90c..84f251b759fad 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -208,6 +208,16 @@ required_reagents = list(/datum/reagent/medicine/omnizine/protozine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1) reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_PLANT | REACTION_TAG_OTHER +/datum/chemical_reaction/medicine/fishy_reagent + results = list(/datum/reagent/medicine/strange_reagent/fishy_reagent = 3) + required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/salt = 1, /datum/reagent/toxin/carpotoxin = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OTHER + +/datum/chemical_reaction/medicine/fishy_reagent/alt + results = list(/datum/reagent/medicine/strange_reagent/fishy_reagent = 6) + required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/salt = 1, /datum/reagent/toxin/tetrodotoxin = 1) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OTHER + /datum/chemical_reaction/medicine/mannitol results = list(/datum/reagent/medicine/mannitol = 3) required_reagents = list(/datum/reagent/consumable/sugar = 1, /datum/reagent/hydrogen = 1, /datum/reagent/water = 1) diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index 542850ac9beae..6219fc964575b 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -575,6 +575,29 @@ new /mob/living/basic/pet/dog/corgi(location) ..() +/datum/chemical_reaction/lifish + required_reagents = list(/datum/reagent/medicine/strange_reagent/fishy_reagent = 1, /datum/reagent/medicine/c2/synthflesh = 1, /datum/reagent/blood = 1) + required_temp = 374 + reaction_flags = REACTION_INSTANT + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE + +/datum/chemical_reaction/lifish/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) + var/location = get_turf(holder.my_atom) + + // create fish possibles + var/list/fish_types = list() + for(var/path in subtypesof(/obj/item/fish)) + var/obj/item/fish/fake_fish = path + if(initial(fake_fish.random_case_rarity) == FISH_RARITY_NOPE) // means they aren't mean to be randomly available + continue + fish_types |= path + + // spawn from popssible fishes + for(var/i in 1 to rand(1, created_volume)) // More flop. + var/obj/item/fish/spawned_fish = pick(fish_types) + new spawned_fish(location) + return ..() + //monkey powder heehoo /datum/chemical_reaction/monkey_powder results = list(/datum/reagent/monkey_powder = 5) diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm index f733a4dac8851..4c757e0e14691 100644 --- a/code/modules/reagents/reagent_containers/cups/bottle.dm +++ b/code/modules/reagents/reagent_containers/cups/bottle.dm @@ -134,6 +134,11 @@ desc = "A small bottle. May be used to revive people." list_reagents = list(/datum/reagent/medicine/strange_reagent = 30) +/obj/item/reagent_containers/cup/bottle/fishy_reagent + name = "Fishy Reagent Bottle" + desc = "A small bottle. May be used to revive fish." + list_reagents = list(/datum/reagent/medicine/strange_reagent/fishy_reagent = 30) + /obj/item/reagent_containers/cup/bottle/traitor name = "syndicate bottle" desc = "A small bottle. Contains a random nasty chemical." @@ -255,6 +260,12 @@ desc = "A small bottle of Romerol. The REAL zombie powder." list_reagents = list(/datum/reagent/romerol = 30) +/obj/item/reagent_containers/cup/bottle/moltobeso + name = "Molt'Obeso bottle" + desc = "The revolutionary new sauce from Syndicate's culinary experts, designed to instantly reshape your figure! \ + The key to the effectiveness of this product lies in its unique formulation, which combines carefully selected ingredients to stimulate appetite and enhance the absorption of calories." + list_reagents = list(/datum/reagent/consumable/moltobeso = 50) + /obj/item/reagent_containers/cup/bottle/random_virus name = "Experimental disease culture bottle" desc = "A small bottle. Contains an untested viral culture in synthblood medium." diff --git a/code/modules/research/designs/autolathe/service_designs.dm b/code/modules/research/designs/autolathe/service_designs.dm index 21fda49fc1bdc..38e36d269149d 100644 --- a/code/modules/research/designs/autolathe/service_designs.dm +++ b/code/modules/research/designs/autolathe/service_designs.dm @@ -562,6 +562,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE +/datum/design/aquarium_kit + name = "Aquarium Kit" + id = "aquarium_kit" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/aquarium_kit + category = list( + RND_CATEGORY_INITIAL, + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE + /datum/design/ticket_machine name = "Ticket Machine Frame" id = "ticket_machine" diff --git a/code/modules/research/techweb/nodes/service_nodes.dm b/code/modules/research/techweb/nodes/service_nodes.dm index 824cd611b837d..6786c2f9e1ec1 100644 --- a/code/modules/research/techweb/nodes/service_nodes.dm +++ b/code/modules/research/techweb/nodes/service_nodes.dm @@ -159,6 +159,7 @@ "fishing_portal_generator", "fishing_rod", "fish_case", + "aquarium_kit", ) /datum/techweb_node/fishing_equip_adv diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 53e0cf08eb782..4d16d24e9e93a 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -83,6 +83,8 @@ #endif /// A trait source when adding traits through unit tests #define TRAIT_SOURCE_UNIT_TESTS "unit_tests" +/// Helper to allocate a new object with the implied type (the type of the variable it's assigned to) in the corner of the test room +#define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments) // BEGIN_INCLUDE #include "abductor_baton_spell.dm" @@ -122,6 +124,12 @@ #include "clothing_drops_items.dm" #include "clothing_under_armor_subtype_check.dm" #include "combat.dm" +#include "combat_blocking.dm" +#include "combat_cuffs.dm" +#include "combat_eyestab.dm" +#include "combat_flash.dm" +#include "combat_help.dm" +#include "combat_pistol_whip.dm" #include "combat_stamina.dm" #include "combat_welder.dm" #include "component_tests.dm" @@ -130,6 +138,7 @@ #include "container_sanity.dm" #include "crayons.dm" #include "create_and_destroy.dm" +#include "damp_rag.dm" #include "dcs_check_list_arguments.dm" #include "dcs_get_id_from_elements.dm" #include "designs.dm" @@ -138,11 +147,13 @@ #include "door_access.dm" #include "dragon_expiration.dm" #include "drink_icons.dm" +#include "dropper.dm" #include "dummy_spawn.dm" #include "dynamic_ruleset_sanity.dm" #include "egg_glands.dm" #include "embedding.dm" #include "emoting.dm" +#include "emp_flashlight.dm" #include "ensure_subtree_operational_datum.dm" #include "explosion_action.dm" #include "fish_unit_tests.dm" @@ -161,6 +172,7 @@ #include "high_five.dm" #include "holder_loving.dm" #include "holidays.dm" +#include "holofan_placement.dm" #include "hulk.dm" #include "human_through_recycler.dm" #include "hunger_curse.dm" @@ -169,8 +181,12 @@ #include "hydroponics_self_mutations.dm" #include "hydroponics_validate_genes.dm" #include "inhands.dm" +#include "interaction_door.dm" +#include "interaction_silicon.dm" +#include "interaction_structures.dm" #include "json_savefile_importing.dm" #include "keybinding_init.dm" +#include "kinetic_crusher.dm" #include "knockoff_component.dm" #include "language_transfer.dm" #include "leash.dm" @@ -268,10 +284,11 @@ #include "spell_shapeshift.dm" #include "spell_timestop.dm" #include "spies.dm" +#include "spraycan.dm" #include "spritesheets.dm" #include "stack_singular_name.dm" #include "station_trait_tests.dm" -#include "status_effect_ticks.dm" +#include "status_effect_validity.dm" #include "stomach.dm" #include "storage.dm" #include "strange_reagent.dm" @@ -280,6 +297,7 @@ #include "subsystem_init.dm" #include "suit_storage_icons.dm" #include "surgeries.dm" +#include "syringe_gun.dm" #include "tail_wag.dm" #include "teleporters.dm" #include "tgui_create_message.dm" diff --git a/code/modules/unit_tests/combat_blocking.dm b/code/modules/unit_tests/combat_blocking.dm new file mode 100644 index 0000000000000..45c1450708509 --- /dev/null +++ b/code/modules/unit_tests/combat_blocking.dm @@ -0,0 +1,35 @@ +/// Test that items can block unarmed attacks +/datum/unit_test/unarmed_blocking + +/datum/unit_test/unarmed_blocking/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/chair/chair = EASY_ALLOCATE() + chair.hit_reaction_chance = 100 + victim.put_in_active_hand(chair, forced = TRUE) + attacker.set_combat_mode(TRUE) + ADD_TRAIT(attacker, TRAIT_PERFECT_ATTACKER, TRAIT_SOURCE_UNIT_TESTS) + + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim.getBruteLoss(), 0, "Victim took damage from being punched despite having a 100% block chance chair in their hands.") + +/// Test that items can block weapon attacks +/datum/unit_test/armed_blocking + +/datum/unit_test/armed_blocking/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/shield/riot/shield = EASY_ALLOCATE() + shield.block_chance = INFINITY + victim.put_in_active_hand(shield, forced = TRUE) + attacker.set_combat_mode(TRUE) + ADD_TRAIT(attacker, TRAIT_PERFECT_ATTACKER, TRAIT_SOURCE_UNIT_TESTS) + + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim.getBruteLoss(), 0, "Victim took damage from being punched despite having a 100% block chance shield in their hands.") + + var/obj/item/storage/toolbox/weapon = EASY_ALLOCATE() + attacker.put_in_active_hand(weapon, forced = TRUE) + + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim.getBruteLoss(), 0, "Victim took damage from being hit with a weapon despite having a 100% block chance shield in their hands.") diff --git a/code/modules/unit_tests/combat_cuffs.dm b/code/modules/unit_tests/combat_cuffs.dm new file mode 100644 index 0000000000000..18505966a4486 --- /dev/null +++ b/code/modules/unit_tests/combat_cuffs.dm @@ -0,0 +1,18 @@ +/// Tests that handcuffs can be applied. +/datum/unit_test/apply_cuffs + priority = TEST_LONGER + +/datum/unit_test/apply_cuffs/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/restraints/handcuffs/cuffs = EASY_ALLOCATE() + cuffs.handcuff_time = 0.2 SECONDS + attacker.put_in_active_hand(cuffs, forced = TRUE) + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim.handcuffed, cuffs, "Handcuff attempt (non-combat-mode) failed in an otherwise valid setup.") + + victim.clear_cuffs(cuffs) + attacker.put_in_active_hand(cuffs, forced = TRUE) + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim.handcuffed, cuffs, "Handcuff attempt (combat-mode) failed in an otherwise valid setup.") diff --git a/code/modules/unit_tests/combat_eyestab.dm b/code/modules/unit_tests/combat_eyestab.dm new file mode 100644 index 0000000000000..bb1126ae40dbf --- /dev/null +++ b/code/modules/unit_tests/combat_eyestab.dm @@ -0,0 +1,17 @@ +/// Tests that eyestabbing with combat mode on does damage to the eyes. +/datum/unit_test/eyestab + +/datum/unit_test/eyestab/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/screwdriver/stabber = EASY_ALLOCATE() + + attacker.zone_selected = BODY_ZONE_PRECISE_EYES + attacker.put_in_active_hand(stabber, forced = TRUE) + + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, victim) + TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim should have taken some brute damage from an eyestab with combat mode on") + + var/obj/item/organ/eyes/eyes = victim.get_organ_slot(ORGAN_SLOT_EYES) + TEST_ASSERT_NOTEQUAL(eyes.damage, 0, "Victim's eyes should have taken some damage from an eyestab with combat mode on") diff --git a/code/modules/unit_tests/combat_flash.dm b/code/modules/unit_tests/combat_flash.dm new file mode 100644 index 0000000000000..815be27f486d0 --- /dev/null +++ b/code/modules/unit_tests/combat_flash.dm @@ -0,0 +1,41 @@ +/// Tests that flashes, well, flash. +/datum/unit_test/flash_click + var/apply_verb = "while Attacker was not on combat mode" + +/datum/unit_test/flash_click/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/assembly/flash/handheld/flash = EASY_ALLOCATE() + + attacker.put_in_active_hand(flash, forced = TRUE) + ready_subjects(attacker, victim) + click_wrapper(attacker, victim) + check_results(attacker, victim) + +/datum/unit_test/flash_click/proc/ready_subjects(mob/living/carbon/human/attacker, mob/living/carbon/human/victim) + victim.forceMove(locate(attacker.x + 1, attacker.y, attacker.z)) + attacker.face_atom(victim) + victim.face_atom(attacker) + +/datum/unit_test/flash_click/proc/check_results(mob/living/carbon/human/attacker, mob/living/carbon/human/victim) + TEST_ASSERT_NOTEQUAL(victim.getStaminaLoss(), 0, "Victim should have sustained stamina loss from being flashed head-on [apply_verb].") + +/// Tests that flashes flash on combat mode. +/datum/unit_test/flash_click/combat_mode + apply_verb = "while Attacker was on combat mode" + +/datum/unit_test/flash_click/combat_mode/ready_subjects(mob/living/carbon/human/attacker, mob/living/carbon/human/victim) + . = ..() + attacker.set_combat_mode(TRUE) + +/// Tests that flashes do not flash if wearing protection. +/datum/unit_test/flash_click/flash_protection + apply_verb = "while wearing flash protection" + +/datum/unit_test/flash_click/flash_protection/ready_subjects(mob/living/carbon/human/attacker, mob/living/carbon/human/victim) + . = ..() + var/obj/item/clothing/glasses/sunglasses/glasses = EASY_ALLOCATE() + victim.equip_to_appropriate_slot(glasses) + +/datum/unit_test/flash_click/flash_protection/check_results(mob/living/carbon/human/attacker, mob/living/carbon/human/victim) + TEST_ASSERT_EQUAL(victim.getStaminaLoss(), 0, "Victim should not have sustained stamina loss from being flashed head-on [apply_verb].") diff --git a/code/modules/unit_tests/combat_help.dm b/code/modules/unit_tests/combat_help.dm new file mode 100644 index 0000000000000..cfa32609e249b --- /dev/null +++ b/code/modules/unit_tests/combat_help.dm @@ -0,0 +1,33 @@ +/// Tests help intent clicking on people, particularly ensuring it results in a help_shake (check self or hug) +/datum/unit_test/help_click + var/helper_times_helped = 0 + var/helpee_times_helped = 0 + +/datum/unit_test/help_click/Run() + var/mob/living/carbon/human/consistent/helps_the_guy = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/gets_the_help = EASY_ALLOCATE() + + gets_the_help.forceMove(locate(helps_the_guy.x + 1, helps_the_guy.y, helps_the_guy.z)) + + RegisterSignal(helps_the_guy, COMSIG_CARBON_PRE_MISC_HELP, PROC_REF(helper_help_received)) + RegisterSignal(gets_the_help, COMSIG_CARBON_PRE_MISC_HELP, PROC_REF(helpee_help_received)) + + // Click on self + click_wrapper(helps_the_guy, helps_the_guy) + + TEST_ASSERT_EQUAL(helper_times_helped, 1, "Helper should have been helped once - clicking on themselves should check self.") + TEST_ASSERT_EQUAL(helpee_times_helped, 0, "Helpee should not have been helped - helper clicked on themselves.") + + // Click on the other guy + click_wrapper(helps_the_guy, gets_the_help) + + TEST_ASSERT_EQUAL(helper_times_helped, 1, "Helper should not have been helped - helper clicked on helpee.") + TEST_ASSERT_EQUAL(helpee_times_helped, 1, "Helpee should have been helped once - helper clicked on helpee.") + +/datum/unit_test/help_click/proc/helper_help_received() + SIGNAL_HANDLER + helper_times_helped += 1 + +/datum/unit_test/help_click/proc/helpee_help_received() + SIGNAL_HANDLER + helpee_times_helped += 1 diff --git a/code/modules/unit_tests/combat_pistol_whip.dm b/code/modules/unit_tests/combat_pistol_whip.dm new file mode 100644 index 0000000000000..4a3a45b9e3f9b --- /dev/null +++ b/code/modules/unit_tests/combat_pistol_whip.dm @@ -0,0 +1,60 @@ +/// Tests that guns (bayonetted or otherwise) are able to be used as melee weapons in close range +/datum/unit_test/pistol_whip + +/datum/unit_test/pistol_whip/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/gun/ballistic/automatic/pistol/gun = EASY_ALLOCATE() + + attacker.put_in_active_hand(gun, forced = TRUE) + victim.forceMove(locate(attacker.x + 1, attacker.y, attacker.z)) + + var/expected_ammo = gun.magazine.max_ammo + 1 + // These assertions are just here because I don't understand gun code + TEST_ASSERT(gun.chambered, "Gun spawned without a chambered round.") + TEST_ASSERT_EQUAL(gun.get_ammo(countchambered = TRUE), expected_ammo, "Gun spawned without a full magazine, \ + when it should spawn with mag size + 1 (chambered) rounds.") + + // Combat mode in melee range -> pistol whip + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, victim) + TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim did not take brute damage from being pistol-whipped.") + TEST_ASSERT_EQUAL(gun.get_ammo(countchambered = TRUE), expected_ammo, "The gun fired a shot when it was used for a pistol whip.") + victim.fully_heal() + + // No combat mode -> point blank shot + attacker.set_combat_mode(FALSE) + click_wrapper(attacker, victim) + TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim did not take brute damage from being fired upon point-blank.") + TEST_ASSERT(locate(/obj/item/ammo_casing/c9mm) in attacker.loc, "The gun did not eject a casing when it was used for a point-blank shot.") + TEST_ASSERT_EQUAL(gun.get_ammo(countchambered = TRUE), expected_ammo - 1, "The gun did not fire a shot when it was used for a point-blank shot.") + victim.fully_heal() + + // Combat mode in melee range with bayonet -> bayonet stab + var/obj/item/knife/combat/knife = EASY_ALLOCATE() + gun.AddComponent(/datum/component/bayonet_attachable, starting_bayonet = knife) + + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, victim) + TEST_ASSERT_NOTEQUAL(victim.getBruteLoss(), 0, "Victim did not take brute damage from being bayonet stabbed.") + victim.fully_heal() + +/// Test that bayonetted weapons can be used to butcher +/datum/unit_test/bayonet_butchering + +/datum/unit_test/bayonet_butchering/Run() + var/mob/living/carbon/human/species/monkey/meat = EASY_ALLOCATE() + meat.death() + + var/mob/living/carbon/human/consistent/butcher = EASY_ALLOCATE() + butcher.set_combat_mode(TRUE) + var/obj/item/gun/energy/recharge/kinetic_accelerator/gun = EASY_ALLOCATE() + var/obj/item/knife/combat/knife = EASY_ALLOCATE() + var/datum/component/bayonet_attachable/bayonet = gun.GetComponent(/datum/component/bayonet_attachable) + bayonet.add_bayonet(knife) + var/datum/component/butchering/butcher_comp = knife.GetComponent(/datum/component/butchering) + butcher_comp.speed = 1 SECONDS + + butcher.put_in_active_hand(gun, forced = TRUE) + click_wrapper(butcher, meat) + TEST_ASSERT(DOING_INTERACTION(butcher, meat), "The butcher did not start butchering the monkey when using a bayonetted weapon.") diff --git a/code/modules/unit_tests/combat_welder.dm b/code/modules/unit_tests/combat_welder.dm index 2fa9052d6fba0..b44022fe04993 100644 --- a/code/modules/unit_tests/combat_welder.dm +++ b/code/modules/unit_tests/combat_welder.dm @@ -1,9 +1,9 @@ /datum/unit_test/welder_combat /datum/unit_test/welder_combat/Run() - var/mob/living/carbon/human/tider = allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left) - var/mob/living/carbon/human/victim = allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left) - var/obj/item/weldingtool/weapon = allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left) + var/mob/living/carbon/human/consistent/tider = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/weldingtool/weapon = EASY_ALLOCATE() tider.put_in_active_hand(weapon, forced = TRUE) tider.set_combat_mode(TRUE) @@ -13,7 +13,7 @@ TEST_ASSERT_NOTEQUAL(victim.getFireLoss(), 0, "Victim did not get burned by welder.") TEST_ASSERT_EQUAL(weapon.get_fuel(), weapon.max_fuel - 1, "Welder did not consume fuel on attacking a mob") - var/obj/structure/blob/blobby = allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left) + var/obj/structure/blob/blobby = EASY_ALLOCATE() weapon.melee_attack_chain(tider, blobby) TEST_ASSERT_NOTEQUAL(blobby.get_integrity(), blobby.max_integrity, "Blob did not get burned by welder.") diff --git a/code/modules/unit_tests/damp_rag.dm b/code/modules/unit_tests/damp_rag.dm new file mode 100644 index 0000000000000..6a3948036af04 --- /dev/null +++ b/code/modules/unit_tests/damp_rag.dm @@ -0,0 +1,17 @@ +/// Tests that damp rags can smother people. +/// When smothing reagents are ingested (go to the stomach). +/datum/unit_test/damp_rag_smother + +/datum/unit_test/damp_rag_smother/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/organ/stomach/victim_stomach = victim.get_organ_slot(ORGAN_SLOT_STOMACH) + var/obj/item/reagent_containers/cup/rag/rag = EASY_ALLOCATE() + + attacker.put_in_active_hand(rag, forced = TRUE) + attacker.zone_selected = BODY_ZONE_PRECISE_MOUTH + attacker.set_combat_mode(TRUE) + rag.reagents.add_reagent(/datum/reagent/water, rag.reagents.maximum_volume) + click_wrapper(attacker, victim) + TEST_ASSERT_EQUAL(victim_stomach.reagents.get_reagent_amount(/datum/reagent/water), rag.reagents.maximum_volume, \ + "The victim should have been smothered by the rag, gaining water reagent.") diff --git a/code/modules/unit_tests/dropper.dm b/code/modules/unit_tests/dropper.dm new file mode 100644 index 0000000000000..7ada84513a8db --- /dev/null +++ b/code/modules/unit_tests/dropper.dm @@ -0,0 +1,21 @@ +/// Tests the droppper picks up and dispenses reagents correctly. +/datum/unit_test/dropper_use + +/datum/unit_test/dropper_use/Run() + var/mob/living/carbon/human/consistent/chemist = EASY_ALLOCATE() + var/obj/item/reagent_containers/dropper/dropper = EASY_ALLOCATE() + var/obj/item/reagent_containers/cup/beaker/large/beaker = EASY_ALLOCATE() + + var/starting_volume = 50 + beaker.reagents.add_reagent(/datum/reagent/water, starting_volume) + + chemist.put_in_active_hand(dropper, forced = TRUE) + click_wrapper(chemist, beaker) + + TEST_ASSERT_EQUAL(dropper.reagents.total_volume, 5, "Dropper should have taken 5 units of reagents from the beaker.") + TEST_ASSERT_EQUAL(beaker.reagents.total_volume, starting_volume - 5, "Beaker should have transferred reagents to the dropper.") + + click_wrapper(chemist, beaker) + + TEST_ASSERT_EQUAL(dropper.reagents.total_volume, 0, "Dropper should have emptied itself into the beaker.") + TEST_ASSERT_EQUAL(beaker.reagents.total_volume, starting_volume, "Beaker should have received reagents from the dropper.") diff --git a/code/modules/unit_tests/emp_flashlight.dm b/code/modules/unit_tests/emp_flashlight.dm new file mode 100644 index 0000000000000..e6e78009fb36a --- /dev/null +++ b/code/modules/unit_tests/emp_flashlight.dm @@ -0,0 +1,18 @@ +/// Test EMP flashlight EMPs people you point it at +/datum/unit_test/emp_flashlight + var/sig_caught = 0 + +/datum/unit_test/emp_flashlight/Run() + var/mob/living/carbon/human/consistent/flashlighter = EASY_ALLOCATE() + var/mob/living/carbon/human/consistent/victim = EASY_ALLOCATE() + var/obj/item/flashlight/emp/debug/flashlight = EASY_ALLOCATE() + + flashlighter.put_in_active_hand(flashlight, forced = TRUE) + RegisterSignal(victim, COMSIG_ATOM_EMP_ACT, PROC_REF(sig_caught)) + + click_wrapper(flashlighter, victim) + TEST_ASSERT_NOTEQUAL(sig_caught, 0, "EMP flashlight did not EMP the target on click.") + +/datum/unit_test/emp_flashlight/proc/sig_caught() + SIGNAL_HANDLER + sig_caught++ diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index b399dcb94e12e..767c1ceeaebfa 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -101,6 +101,7 @@ stable_population = INFINITY breeding_timeout = 0 fish_flags = parent_type::fish_flags & ~(FISH_FLAG_SHOW_IN_CATALOG|FISH_FLAG_EXPERIMENT_SCANNABLE) + fish_id_redirect_path = /obj/item/fish/goldfish //Stops SSfishing from complaining var/expected_num_fillets = 0 //used to know how many fillets should be gotten out of this fish /obj/item/fish/testdummy/add_fillet_type() @@ -368,6 +369,7 @@ /obj/item/fish/chasm_crab/instant_growth fish_traits = list() //We don't want to end up applying traits twice on the resulting lobstrosity + fish_id_redirect_path = /obj/item/fish/chasm_crab /datum/unit_test/fish_sources @@ -419,10 +421,10 @@ /datum/fish_source/unit_test_profound_fisher fish_table = list(/obj/item/fish/testdummy = 1) fish_counts = list(/obj/item/fish/testdummy = 2) - fish_source_flags = parent_type::fish_source_flags | FISH_SOURCE_FLAG_SKIP_CATCHABLES + fish_source_flags = parent_type::fish_source_flags /datum/fish_source/unit_test_all_fish - fish_source_flags = parent_type::fish_source_flags | FISH_SOURCE_FLAG_SKIP_CATCHABLES + fish_source_flags = parent_type::fish_source_flags /datum/fish_source/unit_test_all_fish/New() for(var/fish_type as anything in subtypesof(/obj/item/fish)) diff --git a/code/modules/unit_tests/holofan_placement.dm b/code/modules/unit_tests/holofan_placement.dm new file mode 100644 index 0000000000000..532d0c30e939f --- /dev/null +++ b/code/modules/unit_tests/holofan_placement.dm @@ -0,0 +1,14 @@ +/// Tests the ability to place holosigns from a holosign creator. +/datum/unit_test/place_holosign + +/datum/unit_test/place_holosign/Run() + var/mob/living/carbon/human/consistent/jannie = EASY_ALLOCATE() + var/obj/item/holosign_creator/janibarrier/jannie_holosign_creator = EASY_ALLOCATE() + + jannie.put_in_active_hand(jannie_holosign_creator, forced = TRUE) + var/turf/open/next_to_the_jannie = locate(jannie.x + 1, jannie.y, jannie.z) + + click_wrapper(jannie, next_to_the_jannie) + + var/obj/structure/holosign/barrier/wetsign/placed_sign = locate() in next_to_the_jannie + TEST_ASSERT_NOTNULL(placed_sign, "Holosign creator failed to place a holosign in an adjacent tile.") diff --git a/code/modules/unit_tests/interaction_door.dm b/code/modules/unit_tests/interaction_door.dm new file mode 100644 index 0000000000000..418f02213fec2 --- /dev/null +++ b/code/modules/unit_tests/interaction_door.dm @@ -0,0 +1,13 @@ +/// Tests that airlocks can be closed by clicking on the floor, as [/datum/component/redirect_attack_hand_from_turf ] dictates +/datum/unit_test/door_click + +/datum/unit_test/door_click/Run() + var/mob/living/carbon/human/consistent/tider = EASY_ALLOCATE() + var/obj/machinery/door/airlock/public/glass/door = EASY_ALLOCATE() + + tider.forceMove(locate(door.x + 1, door.y, door.z)) + door.open() // this sleeps we just have to cope + TEST_ASSERT(!door.operating, "Airlock was operating after being opened.") + TEST_ASSERT(!door.density, "Airlock was not open after being opened.") + click_wrapper(tider, get_turf(door)) + TEST_ASSERT(door.operating, "Airlock was not closing after clicking the turf below, as per /datum/component/redirect_attack_hand_from_turf.") diff --git a/code/modules/unit_tests/interaction_silicon.dm b/code/modules/unit_tests/interaction_silicon.dm new file mode 100644 index 0000000000000..4b4efe85ee466 --- /dev/null +++ b/code/modules/unit_tests/interaction_silicon.dm @@ -0,0 +1,38 @@ +/// Tests the ability to unlock and crowbar open a silicon +/datum/unit_test/silicon_interacting + +/datum/unit_test/silicon_interacting/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/silicon/robot/borgo = EASY_ALLOCATE() + var/obj/item/card/id/advanced/gold/captains_spare/id = EASY_ALLOCATE() + var/obj/item/crowbar/crowbar = EASY_ALLOCATE() + // unlock + attacker.put_in_active_hand(id, forced = TRUE) + click_wrapper(attacker, borgo) + TEST_ASSERT(!borgo.locked, "Robot was not unlocked when swiped with ID") + // open + id.forceMove(attacker.drop_location()) + attacker.put_in_active_hand(crowbar, forced = TRUE) + click_wrapper(attacker, borgo) + TEST_ASSERT(borgo.opened, "Robot was not opened when crowbarred") + // close + attacker.put_in_active_hand(crowbar, forced = TRUE) + click_wrapper(attacker, borgo) + TEST_ASSERT(!borgo.opened, "Robot was not closed when crowbarred") + // lock + crowbar.forceMove(attacker.drop_location()) + attacker.put_in_active_hand(id, forced = TRUE) + click_wrapper(attacker, borgo) + TEST_ASSERT(borgo.locked, "Robot was not re-locked when swiped with ID") + +/// Tests unarmed clicking a cyborg doesn't cause damage +/datum/unit_test/silicon_punch + +/datum/unit_test/silicon_punch/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/mob/living/silicon/robot/borgo = EASY_ALLOCATE() + borgo.forceMove(locate(attacker.x + 1, attacker.y, attacker.z)) + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, borgo) + TEST_ASSERT_EQUAL(borgo.getBruteLoss(), 0, "Cyborg took damage from an unarmed punched - \ + their unarmed damage threshold should be too high for this to happen.") diff --git a/code/modules/unit_tests/interaction_structures.dm b/code/modules/unit_tests/interaction_structures.dm new file mode 100644 index 0000000000000..d5be4b6d863fe --- /dev/null +++ b/code/modules/unit_tests/interaction_structures.dm @@ -0,0 +1,40 @@ +/// Tests that mobs are able to bash down tables by clicking on them. +/datum/unit_test/structure_table_bash + +/datum/unit_test/structure_table_bash/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/obj/item/storage/toolbox/toolbox = EASY_ALLOCATE() + var/obj/structure/table/to_smack = EASY_ALLOCATE() + attacker.put_in_active_hand(toolbox, forced = TRUE) + click_wrapper(attacker, to_smack) + TEST_ASSERT_EQUAL(toolbox.loc, to_smack.loc, "The toolbox should have been placed on the table. Instead, its loc is [toolbox.loc].") + TEST_ASSERT_EQUAL(to_smack.get_integrity(), to_smack.max_integrity, "Table took damage despite not being smacked.") + + attacker.put_in_active_hand(toolbox, forced = TRUE) + attacker.set_combat_mode(TRUE) + click_wrapper(attacker, to_smack) + TEST_ASSERT_NOTEQUAL(toolbox.loc, to_smack.loc, "The toolbox should not have been placed on the table.") + TEST_ASSERT_NOTEQUAL(to_smack.get_integrity(), to_smack.max_integrity, "Table failed to take damage from being smacked.") + +/// Tests that mobs are able to bash down barricades / structures by clicking on them. +/datum/unit_test/structure_generic_bash + +/datum/unit_test/structure_generic_bash/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/obj/item/storage/toolbox/toolbox = EASY_ALLOCATE() + var/obj/structure/barricade/to_smack = EASY_ALLOCATE() + attacker.put_in_active_hand(toolbox, forced = TRUE) + click_wrapper(attacker, to_smack) + TEST_ASSERT_NOTEQUAL(to_smack.get_integrity(), to_smack.max_integrity, "The barricade should have taken damage a from a non-combat-mode click.") + +/// Tests that common tool interactions are possible still, by attempting to open the panel of an air alarm. +/datum/unit_test/machinery_tool_interaction + +/datum/unit_test/machinery_tool_interaction/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/obj/item/screwdriver/screwdriver = EASY_ALLOCATE() + var/obj/machinery/airalarm/to_smack = EASY_ALLOCATE() + attacker.put_in_active_hand(screwdriver, forced = TRUE) + click_wrapper(attacker, to_smack) + TEST_ASSERT_EQUAL(to_smack.get_integrity(), to_smack.max_integrity, "The air alarm took damage when interacted with a screwdriver.") + TEST_ASSERT(to_smack.panel_open, "The air alarm should have opened its panel after being interacted with a screwdriver.") diff --git a/code/modules/unit_tests/kinetic_crusher.dm b/code/modules/unit_tests/kinetic_crusher.dm new file mode 100644 index 0000000000000..cb15d70e62c6d --- /dev/null +++ b/code/modules/unit_tests/kinetic_crusher.dm @@ -0,0 +1,13 @@ +/// Tests that the Kinetic Crusher fires a projectile on RMB +/datum/unit_test/crusher_projectile + +/datum/unit_test/crusher_projectile/Run() + var/mob/living/carbon/human/consistent/attacker = EASY_ALLOCATE() + var/obj/item/kinetic_crusher/crusher = EASY_ALLOCATE() + + attacker.put_in_active_hand(crusher, forced = TRUE) + crusher.attack_self(attacker) // wields the crusher + + click_wrapper(attacker, run_loc_floor_top_right, list(RIGHT_CLICK = TRUE, BUTTON = RIGHT_CLICK)) + + TEST_ASSERT(!crusher.charged, "Attacker failed to fire the kinetic crusher on right clicking a distant target") diff --git a/code/modules/unit_tests/screenshots/screenshot_digi_leg_test.png b/code/modules/unit_tests/screenshots/screenshot_digi_leg_test.png index 1ca452de08946..3907b46b8709b 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_digi_leg_test.png and b/code/modules/unit_tests/screenshots/screenshot_digi_leg_test.png differ diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard_ashwalker.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard_ashwalker.png index 3437280e851ba..9c05ab07e5ffe 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard_ashwalker.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard_ashwalker.png differ diff --git a/code/modules/unit_tests/spraycan.dm b/code/modules/unit_tests/spraycan.dm new file mode 100644 index 0000000000000..2ee4ab14454b1 --- /dev/null +++ b/code/modules/unit_tests/spraycan.dm @@ -0,0 +1,22 @@ +/// Tests spray painting the ground to create graffiti. +/datum/unit_test/spraypainting + +/datum/unit_test/spraypainting/Run() + var/mob/living/carbon/human/consistent/artist = EASY_ALLOCATE() + var/obj/item/toy/crayon/spraycan/can = EASY_ALLOCATE() + var/turf/spray_turf = get_turf(artist) + artist.put_in_active_hand(can, forced = TRUE) + + // Try to spray with a capped spraycan. + click_wrapper(artist, spray_turf) + TEST_ASSERT_EQUAL(can.charges, can.charges_left, "Spraypaint sprayed paint while capped.") + // Uncap it + click_wrapper(artist, can, list(ALT_CLICK = TRUE, BUTTON = ALT_CLICK)) + TEST_ASSERT(!can.is_capped, "Spraypaint did not uncap when alt-clicked.") + // Try to spray with an uncapped spraycan. + click_wrapper(artist, spray_turf) + TEST_ASSERT_NOTEQUAL(can.charges, can.charges_left, "Spraypaint did not spray any paint when clicking on a turf with it.") + + // Cleanup + for(var/obj/effect/decal/cleanable/crayon/made_art in spray_turf) + qdel(made_art) diff --git a/code/modules/unit_tests/status_effect_ticks.dm b/code/modules/unit_tests/status_effect_ticks.dm deleted file mode 100644 index d60ba187abc42..0000000000000 --- a/code/modules/unit_tests/status_effect_ticks.dm +++ /dev/null @@ -1,23 +0,0 @@ -/// Validates status effect tick interval setup -/datum/unit_test/status_effect_ticks - -/datum/unit_test/status_effect_ticks/Run() - for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect)) - var/tick_speed = initial(checking.tick_interval) - if(tick_speed == STATUS_EFFECT_NO_TICK) - continue - if(tick_speed == INFINITY) - TEST_FAIL("Status effect [checking] has tick_interval set to INFINITY, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.") - continue - if(tick_speed == 0) - TEST_FAIL("Status effect [checking] has tick_interval set to 0, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.") - continue - switch(initial(checking.processing_speed)) - if(STATUS_EFFECT_FAST_PROCESS) - if(tick_speed < SSfastprocess.wait) - TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSfastprocess can tick ([SSfastprocess.wait]).") - if(STATUS_EFFECT_NORMAL_PROCESS) - if(tick_speed < SSprocessing.wait) - TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSprocessing can tick ([SSprocessing.wait]).") - else - TEST_FAIL("Invalid processing speed for status effect [checking] : [initial(checking.processing_speed)]") diff --git a/code/modules/unit_tests/status_effect_validity.dm b/code/modules/unit_tests/status_effect_validity.dm new file mode 100644 index 0000000000000..76a367233fd11 --- /dev/null +++ b/code/modules/unit_tests/status_effect_validity.dm @@ -0,0 +1,61 @@ +/// Validates status effect tick interval setup +/datum/unit_test/status_effect_ticks + +/datum/unit_test/status_effect_ticks/Run() + for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect)) + if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT) + continue + var/tick_speed = initial(checking.tick_interval) + if(tick_speed == STATUS_EFFECT_NO_TICK) + continue + if(tick_speed == INFINITY) + TEST_FAIL("Status effect [checking] has tick_interval set to INFINITY, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.") + continue + if(tick_speed == 0) + TEST_FAIL("Status effect [checking] has tick_interval set to 0, this is not how you prevent ticks - use tick_interval = STATUS_EFFECT_NO_TICK instead.") + continue + switch(initial(checking.processing_speed)) + if(STATUS_EFFECT_FAST_PROCESS) + if(tick_speed < SSfastprocess.wait) + TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSfastprocess can tick ([SSfastprocess.wait]).") + if(STATUS_EFFECT_NORMAL_PROCESS) + if(tick_speed < SSprocessing.wait) + TEST_FAIL("Status effect [checking] has tick_interval set to [tick_speed], which is faster than SSprocessing can tick ([SSprocessing.wait]).") + else + TEST_FAIL("Invalid processing speed for status effect [checking] : [initial(checking.processing_speed)]") + +/// Validates status effect alert type setup +/datum/unit_test/status_effect_alert + +/datum/unit_test/status_effect_alert/Run() + // The base typepath is used to indicate "I didn't set an alert type" + var/bad_alert_type = /datum/status_effect::alert_type + TEST_ASSERT_NOTNULL(bad_alert_type, "No alert type defined in /datum/status_effect - This test may be redundant now.") + + for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect)) + if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT) + continue + if(initial(checking.alert_type) != bad_alert_type) + continue + TEST_FAIL("[checking] has not set alert_type. If you don't want an alert, set alert_type = null - \ + Otherwise, give it an alert subtype.") + +/// Validates status effect id setup +/datum/unit_test/status_effect_ids + +/datum/unit_test/status_effect_ids/Run() + // The base id is used to indicate "I didn't set an id" + var/bad_id = /datum/status_effect::id + TEST_ASSERT_NOTNULL(bad_id, "No id defined in /datum/status_effect - This test may be redundant now.") + + for(var/datum/status_effect/checking as anything in subtypesof(/datum/status_effect)) + if(initial(checking.id) == STATUS_EFFECT_ID_ABSTRACT) + // we are just assuming that a child of an abstract should not be abstract. + // of course in practice, this may not always be the case - but if you're + // structuring a status effect like this, you can just change the parent id to anything else + var/datum/status_effect/checking_parent = initial(checking.parent_type) + if(initial(checking_parent.id) != STATUS_EFFECT_ID_ABSTRACT) + continue + if(initial(checking.id) != bad_id) + continue + TEST_FAIL("[checking] has not set an id. This is required for status effects.") diff --git a/code/modules/unit_tests/syringe_gun.dm b/code/modules/unit_tests/syringe_gun.dm new file mode 100644 index 0000000000000..2d3e56a58089a --- /dev/null +++ b/code/modules/unit_tests/syringe_gun.dm @@ -0,0 +1,14 @@ +/// Tests the ability to load syringe into a syringe gun +/datum/unit_test/load_syringe + +/datum/unit_test/load_syringe/Run() + var/mob/living/carbon/human/consistent/chemist = EASY_ALLOCATE() + var/obj/item/gun/syringe/syringe_gun = EASY_ALLOCATE() + var/obj/item/reagent_containers/syringe/syringe = EASY_ALLOCATE() + + chemist.put_in_active_hand(syringe, forced = TRUE) + chemist.put_in_inactive_hand(syringe_gun, forced = TRUE) + + click_wrapper(chemist, syringe_gun) + + TEST_ASSERT_EQUAL(syringe.loc, syringe_gun, "Syringe was not added to syringe gun when clicking on it to load it.") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index f5dee3e11ee3d..6a2bda4ee25af 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -166,6 +166,18 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]") +/** + * Helper to perform a click + * + * * clicker: The mob that will be clicking + * * clicked_on: The atom that will be clicked + * * passed_params: A list of parameters to pass to the click + */ +/datum/unit_test/proc/click_wrapper(mob/living/clicker, atom/clicked_on, list/passed_params = list(LEFT_CLICK = 1, BUTTON = LEFT_CLICK)) + clicker.next_click = -1 + clicker.next_move = -1 + clicker.ClickOn(clicked_on, list2params(passed_params)) + /proc/RunUnitTest(datum/unit_test/test_path, list/test_results) if(ispath(test_path, /datum/unit_test/focus_only)) return diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm index 9c0c92edcbfe7..953a6cf7ace5d 100644 --- a/code/modules/uplink/uplink_items/job.dm +++ b/code/modules/uplink/uplink_items/job.dm @@ -142,6 +142,14 @@ cost = 11 restricted_roles = list(JOB_COOK) +/datum/uplink_item/role_restricted/moltobeso + name = "Molt'Obeso Sauce Bottle" + desc = "A branded bottle of Molt'Obeso sauce. This sauce can stimulate hunger in people, leading them to eat more than they intended. \ + It also enhances the absorption of calories from the food consumed." + item = /obj/item/storage/box/syndie_kit/moltobeso + cost = 2 + restricted_roles = list(JOB_COOK) + /datum/uplink_item/role_restricted/turretbox name = "Disposable Sentry Gun" desc = "A disposable sentry gun deployment system cleverly disguised as a toolbox, apply wrench for functionality." diff --git a/code/modules/wiremod/components/math/arithmetic.dm b/code/modules/wiremod/components/math/arithmetic.dm index 083616f1e0e54..20474ec949295 100644 --- a/code/modules/wiremod/components/math/arithmetic.dm +++ b/code/modules/wiremod/components/math/arithmetic.dm @@ -2,6 +2,7 @@ #define COMP_ARITHMETIC_SUBTRACT "Subtract" #define COMP_ARITHMETIC_MULTIPLY "Multiply" #define COMP_ARITHMETIC_DIVIDE "Divide" +#define COMP_ARITHMETIC_MODULO "Modulo" #define COMP_ARITHMETIC_MIN "Minimum" #define COMP_ARITHMETIC_MAX "Maximum" @@ -34,6 +35,7 @@ COMP_ARITHMETIC_SUBTRACT, COMP_ARITHMETIC_MULTIPLY, COMP_ARITHMETIC_DIVIDE, + COMP_ARITHMETIC_MODULO, COMP_ARITHMETIC_MIN, COMP_ARITHMETIC_MAX, ) @@ -75,6 +77,8 @@ result = null break result /= value + if(COMP_ARITHMETIC_MODULO) + result %= value if(COMP_ARITHMETIC_MAX) result = max(result, value) if(COMP_ARITHMETIC_MIN) @@ -86,5 +90,6 @@ #undef COMP_ARITHMETIC_SUBTRACT #undef COMP_ARITHMETIC_MULTIPLY #undef COMP_ARITHMETIC_DIVIDE +#undef COMP_ARITHMETIC_MODULO #undef COMP_ARITHMETIC_MIN #undef COMP_ARITHMETIC_MAX diff --git a/html/changelogs/AutoChangeLog-pr-88068.yml b/html/changelogs/AutoChangeLog-pr-88068.yml new file mode 100644 index 0000000000000..02e8b9eda3921 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88068.yml @@ -0,0 +1,7 @@ +author: "Ghommie" +delete-after: True +changes: + - balance: "Aquarium kits can now be printed from cargo, service, science protolathes as well as the autolathe. They no longer have to be ordered from cargo." + - balance: "Revamped the \"Growth/Reproduction\" setting for aquarium to \"Safe Mode\", which also disables the food, temperature and water requiremenets of aquariums, making it useful for purely decorative aquariums." + - balance: "Replaced the \"Aggressive\" fish trait with \"Territorial\". No more angelfish shanking the goldfish and guppy in prefilled aquariums with less than 5 fishes." + - qol: "Added screentips to aquariums." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88144.yml b/html/changelogs/AutoChangeLog-pr-88144.yml deleted file mode 100644 index 541f128a1d81f..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88144.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "Ben10Omintrix" -delete-after: True -changes: - - bugfix: "repairbots no longer get flashed by their own welder" - - bugfix: "repairbots no longer break glass tables they step on" - - bugfix: "repairbots can no longer flush their own welders" - - bugfix: "fixes some runtimes when emagged repairbots try to deconstruct things" - - bugfix: "fixes sentient repairbots not being able to see or remove their material counts" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88207.yml b/html/changelogs/AutoChangeLog-pr-88207.yml deleted file mode 100644 index 627db590cf4dd..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88207.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - bugfix: "ACTUALLY allow dot radio prefixes to also work with the tgui-say radio prefix display." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88212.yml b/html/changelogs/AutoChangeLog-pr-88212.yml deleted file mode 100644 index d8eeded5b519f..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88212.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "ValuedEmployee" -delete-after: True -changes: - - rscadd: "New purr and meow emotes for players with felinid tongues" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88214.yml b/html/changelogs/AutoChangeLog-pr-88214.yml new file mode 100644 index 0000000000000..4bb15d4c80e4c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88214.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "Fixed play local sound for admins" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88217.yml b/html/changelogs/AutoChangeLog-pr-88217.yml deleted file mode 100644 index 54f0d00580506..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88217.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Mothblocks" -delete-after: True -changes: - - qol: "IV drips now create a beam from their spout to your body, and will visually pull you closer." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88218.yml b/html/changelogs/AutoChangeLog-pr-88218.yml deleted file mode 100644 index aa138e0ae745f..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88218.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Maybe fixes some organ manipulation bugs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88219.yml b/html/changelogs/AutoChangeLog-pr-88219.yml deleted file mode 100644 index 563f6d175141a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88219.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Frozen slimes can't latch on to you (but they can still attack you technically)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88220.yml b/html/changelogs/AutoChangeLog-pr-88220.yml deleted file mode 100644 index 9e84257f5f611..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88220.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Mulebot wire hacking is less fourth wall breaking" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88221.yml b/html/changelogs/AutoChangeLog-pr-88221.yml deleted file mode 100644 index 6c78766a75c52..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88221.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Stuff like the SM exploding will no longer output to your OOC tab" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88223.yml b/html/changelogs/AutoChangeLog-pr-88223.yml deleted file mode 100644 index 9d9c5dcb84022..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88223.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed colossus committing suicide on a regular basis" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88225.yml b/html/changelogs/AutoChangeLog-pr-88225.yml deleted file mode 100644 index f1b2dddd24e1c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88225.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "chainsaw is not invisible when turned off in hand" - - bugfix: "chainsaw won't play ping sound when turned on" - - bugfix: "mounted chainsaw has a new animated sprite when turned on & won't go invisible in hand" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88235.yml b/html/changelogs/AutoChangeLog-pr-88235.yml new file mode 100644 index 0000000000000..92fdb13d0ecf7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88235.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - rscadd: "Icemoon lavaloops (now named plasmaloops) fish now look different." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88236.yml b/html/changelogs/AutoChangeLog-pr-88236.yml new file mode 100644 index 0000000000000..71f8df6358def --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88236.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed Nebula's robotics fabricators being obstructed roundstart" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88240.yml b/html/changelogs/AutoChangeLog-pr-88240.yml new file mode 100644 index 0000000000000..e634bc152bda3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88240.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "You should be afflicted by the \"Curse of Mundanity\" far, far less" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88243.yml b/html/changelogs/AutoChangeLog-pr-88243.yml new file mode 100644 index 0000000000000..e8f1bd2db22eb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88243.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Aquariums are now potential fishing spots." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88245.yml b/html/changelogs/AutoChangeLog-pr-88245.yml new file mode 100644 index 0000000000000..ea65524072acf --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88245.yml @@ -0,0 +1,4 @@ +author: "Absolucy" +delete-after: True +changes: + - bugfix: "Fixed double-encoded messages with AI shuttle call reasons." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88247.yml b/html/changelogs/AutoChangeLog-pr-88247.yml new file mode 100644 index 0000000000000..217f594f040f5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88247.yml @@ -0,0 +1,4 @@ +author: "Absolucy" +delete-after: True +changes: + - bugfix: "Light Step now properly prevents your hands and clothes from getting covered in blood when stepping in it." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88253.yml b/html/changelogs/AutoChangeLog-pr-88253.yml new file mode 100644 index 0000000000000..eb837aa04dba1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88253.yml @@ -0,0 +1,4 @@ +author: "CRITAWAKETS" +delete-after: True +changes: + - rscadd: "Added in the modulo operator to the circuit arithmetic component." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88269.yml b/html/changelogs/AutoChangeLog-pr-88269.yml new file mode 100644 index 0000000000000..a8cc4960e11be --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88269.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed projectile parrying" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88281.yml b/html/changelogs/AutoChangeLog-pr-88281.yml new file mode 100644 index 0000000000000..4f86cfe64f0ac --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88281.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed damage overlays hiding themselves or flickering when you get wounded." \ No newline at end of file diff --git a/html/changelogs/archive/2024-11.yml b/html/changelogs/archive/2024-11.yml index f01bf01ffa3c6..7559696167874 100644 --- a/html/changelogs/archive/2024-11.yml +++ b/html/changelogs/archive/2024-11.yml @@ -980,3 +980,81 @@ - bugfix: Brain transplants work again carlarctg: - spellcheck: Fixes a typo in legionnaire abilities +2024-11-28: + Absolucy: + - bugfix: ACTUALLY allow dot radio prefixes to also work with the tgui-say radio + prefix display. + Ben10Omintrix: + - bugfix: repairbots no longer get flashed by their own welder + - bugfix: repairbots no longer break glass tables they step on + - bugfix: repairbots can no longer flush their own welders + - bugfix: fixes some runtimes when emagged repairbots try to deconstruct things + - bugfix: fixes sentient repairbots not being able to see or remove their material + counts + Melbert: + - bugfix: Frozen slimes can't latch on to you (but they can still attack you technically) + - bugfix: Maybe fixes some organ manipulation bugs + - bugfix: Mulebot wire hacking is less fourth wall breaking + - bugfix: Stuff like the SM exploding will no longer output to your OOC tab + Mothblocks: + - qol: IV drips now create a beam from their spout to your body, and will visually + pull you closer. + SmArtKar: + - bugfix: Fixed colossus committing suicide on a regular basis + SyncIt21: + - bugfix: chainsaw is not invisible when turned off in hand + - bugfix: chainsaw won't play ping sound when turned on + - bugfix: mounted chainsaw has a new animated sprite when turned on & won't go invisible + in hand + ValuedEmployee: + - rscadd: New purr and meow emotes for players with felinid tongues +2024-11-29: + Ghommie: + - bugfix: Player-controlled lobstrosities, space dragons, space carps and penguins + can fish again. + - bugfix: Bombing someone during organ manipulation no longer summons new organs. + Runi-c: + - rscadd: digitigrade lizards can wear certain shoes and suits + - image: added digitigrade shoes & oversuit templates + - refactor: improved digi clothing generator code + TealSeer: + - bugfix: Using a bonesetter to correct a dislocated limb should no longer cause + you to hit the patient with it too. + carlarctg: + - qol: Added some tips of the round. + - balance: Increased Rusted Harvester health from 35 to 45. + - balance: Increased haunted longsword's demolition mod by 20%. + - bugfix: Rusted harvesters won't get narsie telling them to bring her their victims. + - bugfix: Heretic summons aren't affected by void chill anymore. + - rscadd: Centcom's janitorial department has gotten tired of scooping up discarded + scarves from the floor and has instead assigned MODlink scryers to some stations' + crew in an attempt to spice up the disposal team's jobs. + - rscadd: Adds a new biotype, MOB_AQUATIC, indicating the mob is water-themed somehow. + Given to carp, lobstrosities, frogs, axolotls, penguins, fire sharks. + - rscadd: Aquatic mobs can be hooked by fishing rods, even without a jawed fishing + hook installed. + - rscadd: The carp and fish infusion sets now give the infusee the aquatic biotype. + Added support for infusions adding a biotype. + - rscadd: You can check for a fish's pulse with a stethoscope, which will tell you + its status even without fishing skill. + - qol: Refined fish health status checks to be more precise. + - rscadd: Added 'Fishy' Reagent, a version of strange reagent that only works for + fish or aquatic biotype mobs. It's made with omnizine, saltwater, and carpotoxin + or tetrodotoxin. + - rscadd: Added a lifish chemical reaction that creates fish. + - rscadd: You can now wear a hat on any modsuit, even w/o the stabilizing module. + But it may easily fall off... + - rscadd: Added the Atrocinator, Hat Stabilizer, and Tanning modules to the black + market. + - rscadd: Added the loose hat component to bio/bomb/rad hoods and space helmets. + necromanceranne: + - bugfix: Civilian bounty pads refuse to send holographic items. Nanotrasen have + received far too many holographic fish for their liking. Officer Mathews is + still weeping in a corner after all the clownfish in his aquarium turned to + dust before his very eyes... + - code_imp: Cleans up the bounty pad code just a smidge. + oranges: + - rscadd: Teleporting a leaning person will now make them fall over + paganiy: + - rscadd: 'Add a new item to the chef traitor''s uplink: Molt''Obeso sauce. A sauce + that makes people want to eat too much.' diff --git a/icons/mob/clothing/digi_template.dmi b/icons/mob/clothing/digi_template.dmi new file mode 100644 index 0000000000000..319bd875041c7 Binary files /dev/null and b/icons/mob/clothing/digi_template.dmi differ diff --git a/icons/mob/clothing/under/digi_template.dmi b/icons/mob/clothing/under/digi_template.dmi deleted file mode 100644 index 0c9db80eb1c88..0000000000000 Binary files a/icons/mob/clothing/under/digi_template.dmi and /dev/null differ diff --git a/icons/mob/clothing/under/masking_helpers.dmi b/icons/mob/clothing/under/masking_helpers.dmi index dfbec7d1cb8ec..26e1cc446685f 100644 Binary files a/icons/mob/clothing/under/masking_helpers.dmi and b/icons/mob/clothing/under/masking_helpers.dmi differ diff --git a/icons/obj/aquarium/fish.dmi b/icons/obj/aquarium/fish.dmi index 53a218bc780a1..49856cc649fe9 100644 Binary files a/icons/obj/aquarium/fish.dmi and b/icons/obj/aquarium/fish.dmi differ diff --git a/sound/music/antag/attribution.txt b/sound/music/antag/attribution.txt index 6ae7cecc51970..d7b444fc909c9 100644 --- a/sound/music/antag/attribution.txt +++ b/sound/music/antag/attribution.txt @@ -1,5 +1,5 @@ sound/instrumental/antag/abductee.ogg is from "Warp SFX" https://freesound.org/people/Breviceps/sounds/453391 (CC0) -sound/instrumental/antag/brainwash.ogg is from "nog.wav" https://freesound.org/people/_NOMINAL_/sounds/124602 (CC-BY 3.0) +sound/instrumental/antag/brainwash.ogg is made by FeiH from https://github.com/OracleStation/OracleStation/pull/1122/commits/b28fbbad715b96db029a8e8df38b1357a58daec1 sound/instrumental/antag/hypnosis.ogg is from "Flashback.wav" https://freesound.org/people/Sclolex/sounds/342103 (CC0) { diff --git a/strings/fishing_tips.txt b/strings/fishing_tips.txt index 836cab85da53f..fd2e0dccc8100 100644 --- a/strings/fishing_tips.txt +++ b/strings/fishing_tips.txt @@ -54,4 +54,6 @@ Fish can grow in size and weight if you fed them somewhat frequently. Giving the Feeding a fish mutagen can triple the probability of generating evolved offsprings, provided it has an evolution. You can print fishing rods of different materials from an autolathe, which can inrease or decrease fishing difficulty, casting range, experience gained and can have other, special effects. Albeit scarcely, it's possible to catch fish made of the same materials of a custom material fishing rod. Equipping a shiny fishing hook and the quality of the bait can improve your odds. -You can use a fishing rod to snatch random organs during the "manipulate organs" step of the "organ manipulation" surgery. \ No newline at end of file +You can use a fishing rod to snatch random organs during the "manipulate organs" step of the "organ manipulation" surgery. +By opening the aquarium panel and turning "Safe Mode" on, you can easily set up a purely decorative aquarium without having to worry about food, temperature and type of water. +Aquariums are also potential fishing spots. Only useful for catching fish you couldn't find in the wild, as a personal achievement and nothing more. \ No newline at end of file diff --git a/strings/tips.txt b/strings/tips.txt index 2e80296af2258..1dc5626606b18 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -290,3 +290,26 @@ You don't need to destroy a Spacecoin machine to make your funds stop draining. As a Bitrunner, upgrading your quantum server will increase rewards and reduce downtime. As a Bitrunner, your avatar has a domain info ability which will give you clues to help complete virtual domains. Bitrunning is a crime. +You can right click someone with wire cutters, jaws of life, and box cutters to instantly snap cablecuffs or zipties. The jaws can even instantly break handcuffs! +You can alt-click tank transfer valves to remove a tank from them. +You can use a multitool in your hand to track the area's local APC. +Some items, when examined, are labeled as 'crafting components', which means you can smack them with another item to directly construct a recipe. Try using an igniter on a rod! +You can further the cycle of life by having two adult plushies play with eachother, creating a smaller junior child plushie. +Most species have only 32 teeth for use in dental implants. Moths have none. Lizards have seventy five. +As a Changeling, Repurposed Glands will break bolas, disable stuns, and give you a hefty speed boost at the cost of the use of your arms - which includes the ability to open restricted airlocks. +As a Geneticist, you should usually save all mutations you unlock. Negative mutations will increase your genetic stability, allowing you to keep more positive mutations. +You can craft peg limbs and crutches with wood for use in dire circumstances. The latter are also available in medical vendors. +As a Heretic, you can also sacrifice cultists, rewarding you with a knowledge point and one of three unique, powerful rewards. +As a Cultist, if you manage to sacrifice a Heretic, you will unlock one of three powerful and unique items to be created in every one of your cult's forges, archives, or altars. +As a Cultist, when you sacrifice a Heretic, they will be bound inside a powerful haunted longsword. Anyone can then unbind the blade, unlocking its powers and abilities, but also allowing the blade to act of its own free will. +As a Syndicate Monkey, you're explosively allergic to species transformations and should probably avoid them. +Biological armor will protect your limb from a zombie's infective attack, unless the limb's more damaged than the armor value. Armor with thick material, such as firesuits and EVA suits, also partially protects, preventing at least the first attack from infection. +In a pinch, you can reduce bleeding or burn infection with several commonplace reagents - flour, salt, saltwater can all be splashed onto the wound to stall for time, and tea can be drank for a boost to your body's defences. +First-aid analyzers double the speed of wound treatment on injuries, alongside giving out normal and improvised instructions for treatment. +Syndicate Duffelbags are a lot quicker to zip and unzip, have significantly less slowdown, and can carry up to two of various bulky, objective-related items - such as fire axes, guns, or gibtonite. Examine them closely to see all the possibilities. +The Coroner's surgical tools are considered 'cruel implements', which speeds up surgery on corpses but slows it on not-yet-corpses. A few other items also have it. +As a Coroner, remember that your autopsy scanner also works as an advanced health analyzer on right-click, but only for corpses. +As the Captain, your sabre deals extra damage to Assistants (as long as they have their original liver). +You can automatically extract and retract arm implants by 'activating' the empty hand they're on. This includes integrated toolsets, cursed katanas, and vorpal scythes. +You can combine the Carpet reagent with various different reagents, such as Oil and Cyanide, to create unique carpet types. +You can bake a birthday cake and then microwave it to create a legendary cake hat. You can then combine it with an energy sword to create an energy cake. diff --git a/tgui/packages/tgui/interfaces/Aquarium.tsx b/tgui/packages/tgui/interfaces/Aquarium.tsx index bbdc753263417..c5eb08e564419 100644 --- a/tgui/packages/tgui/interfaces/Aquarium.tsx +++ b/tgui/packages/tgui/interfaces/Aquarium.tsx @@ -24,7 +24,7 @@ type Data = { fluidTypes: string[]; fishData: FishData[]; propData: PropData[]; - allowBreeding: BooleanLike; + safe_mode: BooleanLike; feedingInterval: number; heartIcon: string; heartIconState: string; @@ -270,7 +270,7 @@ const Settings = (props) => { maxTemperature, fluidTypes, fluidType, - allowBreeding, + safe_mode, feedingInterval, } = data; @@ -319,13 +319,14 @@ const Settings = (props) => {
- +