diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index 622cd98514ac9..414f5d59ea050 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -287,7 +287,7 @@ jobs: completion_gate: # Serves as a non-moving target for branch rulesets if: always() && !cancelled() name: Completion Gate - needs: [ test_windows, compare_screenshots, compile_all_maps, run_linters ] + needs: [ test_windows, compare_screenshots, compile_all_maps, run_all_tests, run_alternate_tests, run_linters ] runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm index d5c344e9cd31a..3553672b1080c 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm @@ -993,9 +993,6 @@ /area/ruin/comms_agent) "UI" = ( /obj/structure/table/reinforced, -/obj/machinery/computer/records/security/laptop/syndie{ - dir = 1 - }, /obj/item/paper/monitorkey{ pixel_x = -15; pixel_y = 7 diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index de9e1338f2939..125324bb07ea4 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -9394,10 +9394,9 @@ /obj/machinery/airalarm/directional/north, /obj/structure/table/wood, /obj/machinery/chem_dispenser/drinks, -/obj/effect/turf_decal/siding/wood{ - dir = 5 +/obj/effect/turf_decal/siding/wood/end{ + dir = 4 }, -/obj/effect/turf_decal/siding/wood, /turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "dxZ" = ( @@ -10072,6 +10071,7 @@ }, /obj/structure/disposalpipe/segment, /obj/structure/cable, +/obj/structure/extinguisher_cabinet/directional/west, /turf/open/floor/stone, /area/station/service/bar) "dMm" = ( @@ -33247,11 +33247,10 @@ pixel_x = -7; pixel_y = 15 }, -/obj/effect/turf_decal/siding/wood{ - dir = 9 - }, -/obj/effect/turf_decal/siding/wood, /obj/structure/sign/warning/no_smoking/circle/directional/north, +/obj/effect/turf_decal/siding/wood/end{ + dir = 8 + }, /turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "lnI" = ( @@ -50833,12 +50832,6 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/iron/dark, /area/station/service/lawoffice) -"rrX" = ( -/obj/effect/turf_decal/siding/wood/corner{ - dir = 4 - }, -/turf/open/floor/stone, -/area/station/service/bar) "rrZ" = ( /obj/structure/closet/crate/trashcart, /obj/effect/spawner/random/trash/garbage, @@ -56445,11 +56438,14 @@ /turf/open/floor/eighties/red, /area/station/hallway/primary/central/fore) "tjT" = ( -/obj/structure/extinguisher_cabinet/directional/north, +/obj/machinery/chem_master/condimaster, +/obj/effect/turf_decal/siding/wood/end{ + dir = 8 + }, /obj/effect/turf_decal/siding/wood{ - dir = 5 + dir = 4 }, -/turf/open/floor/stone, +/turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "tjY" = ( /obj/machinery/atmospherics/components/binary/pump/on{ @@ -100086,7 +100082,7 @@ iXW dRb sON tjT -rrX +xkV lAV fYJ eGU diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index a690aede2a2f6..e26e6bf08ce2d 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -84092,10 +84092,10 @@ /obj/structure/disposalpipe/trunk{ dir = 4 }, -/obj/machinery/disposal/delivery_chute{ +/obj/effect/turf_decal/box/red, +/obj/structure/disposaloutlet{ dir = 8 }, -/obj/effect/turf_decal/box/red, /turf/open/floor/engine/xenobio, /area/station/science/xenobiology) "uYg" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index aa3df4dfd8a93..6ee4db7790ea9 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -46005,6 +46005,13 @@ dir = 4 }, /area/station/hallway/secondary/entry) +"nqI" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/landmark/start/hangover, +/turf/open/floor/iron, +/area/station/commons/fitness) "nqP" = ( /obj/machinery/camera/directional/north{ c_tag = "Research Division West"; @@ -53846,8 +53853,8 @@ /obj/effect/turf_decal/tile/neutral{ dir = 1 }, -/obj/item/kirbyplants/random, /obj/structure/sign/flag/terragov/directional/north, +/obj/structure/weightmachine/weightlifter, /turf/open/floor/iron, /area/station/commons/fitness) "pyn" = ( @@ -81087,6 +81094,10 @@ /obj/structure/cable, /turf/open/floor/plating, /area/station/security/detectives_office) +"xyg" = ( +/obj/structure/weightmachine, +/turf/open/floor/iron, +/area/station/commons/fitness) "xyl" = ( /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 1 @@ -249315,7 +249326,7 @@ cGB cGB gsI tLL -spy +nqI kKL kKL kKL @@ -250338,8 +250349,8 @@ dyA skl gaC vfW -eOl vfW +xyg vfW lvk crv diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 6b4adfe0d4896..e6aabf579de2a 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -67095,10 +67095,6 @@ }, /turf/open/floor/iron, /area/station/ai_monitored/command/storage/eva) -"xBX" = ( -/obj/machinery/firealarm/directional/east, -/turf/closed/wall, -/area/station/security/checkpoint/customs) "xCf" = ( /obj/item/clothing/suit/jacket/straight_jacket, /obj/item/electropack, @@ -83046,7 +83042,7 @@ wdr fcq xfI fcq -xBX +xjh qhW xjh yme diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm index 74c3327a4dfd4..047b54e5fd4bb 100644 --- a/_maps/map_files/NebulaStation/NebulaStation.dmm +++ b/_maps/map_files/NebulaStation/NebulaStation.dmm @@ -12770,6 +12770,7 @@ /turf/open/floor/iron/dark/textured, /area/station/engineering/atmos) "bUo" = ( +/obj/structure/cable, /turf/open/floor/iron/solarpanel/airless, /area/station/solars/port/aft) "bUs" = ( @@ -92159,7 +92160,8 @@ /area/station/security/checkpoint/science) "nKF" = ( /obj/item/toy/katana{ - icon_state = "supermatter_sword" + icon_state = "supermatter_sword"; + icon_angle = -45 }, /obj/structure/ladder, /turf/open/floor/plating, diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm index 4aab72d4be630..8f24d290d1750 100644 --- a/_maps/map_files/generic/CentCom.dmm +++ b/_maps/map_files/generic/CentCom.dmm @@ -62229,7 +62229,7 @@ xt xt xt xt -xt +PG xt Vd zm @@ -62486,7 +62486,7 @@ xt xt xt xt -rq +xt xt xt zl diff --git a/_maps/shuttles/emergency_lance.dmm b/_maps/shuttles/emergency_lance.dmm index d83294d00aca0..2f62bc945bdd9 100644 --- a/_maps/shuttles/emergency_lance.dmm +++ b/_maps/shuttles/emergency_lance.dmm @@ -62,7 +62,7 @@ /area/shuttle/escape) "bV" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/turf_decal/trimline/dark_blue/arrow_ccw{ dir = 8 @@ -125,7 +125,7 @@ /area/shuttle/escape) "dW" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 8 @@ -287,7 +287,7 @@ /area/shuttle/escape) "jo" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/turf_decal/trimline/dark_blue/arrow_ccw{ dir = 8 @@ -533,7 +533,7 @@ /area/shuttle/escape) "pu" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 4 @@ -573,7 +573,7 @@ /area/shuttle/escape) "qe" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/access/any/engineering/general, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ @@ -609,7 +609,7 @@ /area/shuttle/escape) "rw" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/access/any/engineering/general, /obj/effect/mapping_helpers/airlock/cyclelink_helper, @@ -682,7 +682,7 @@ /area/shuttle/escape) "uK" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 4 @@ -868,7 +868,7 @@ /area/shuttle/escape) "Cx" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 8 @@ -887,7 +887,7 @@ /area/shuttle/escape) "CR" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 4 @@ -1199,7 +1199,7 @@ /area/shuttle/escape) "LD" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 8 diff --git a/_maps/shuttles/emergency_raven.dmm b/_maps/shuttles/emergency_raven.dmm index 7e3937568001f..7eb255fc2d42d 100644 --- a/_maps/shuttles/emergency_raven.dmm +++ b/_maps/shuttles/emergency_raven.dmm @@ -742,7 +742,7 @@ /area/shuttle/escape) "cd" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /turf/open/floor/plating, /area/shuttle/escape) @@ -1456,7 +1456,7 @@ /area/shuttle/escape) "eo" = ( /obj/machinery/door/airlock/external/ruin{ - name = "Emegency Shuttle External Airlock" + name = "Emergency Shuttle External Airlock" }, /obj/docking_port/mobile/emergency{ name = "CentCom Raven Cruiser" diff --git a/_maps/templates/lazy_templates/nukie_base.dmm b/_maps/templates/lazy_templates/nukie_base.dmm index bb1d8bd412650..5178b0f935630 100644 --- a/_maps/templates/lazy_templates/nukie_base.dmm +++ b/_maps/templates/lazy_templates/nukie_base.dmm @@ -68,6 +68,17 @@ /obj/effect/turf_decal/siding/thinplating_new/light{ dir = 8 }, +/obj/structure/closet/syndicate/personal, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, +/obj/item/reagent_containers/cup/beaker/large, /turf/open/floor/mineral/plastitanium, /area/centcom/syndicate_mothership/expansion_chemicalwarfare) "bo" = ( diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index b5a7ad1ddfaac..f7f77a7169ea2 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -195,6 +195,14 @@ #define BB_DRILLABLE_ICE "BB_drillable_ice" +//emotions we displays depending on our happiness +///emotions we display when happy +#define BB_HAPPY_EMOTIONS "happy_emotions" +///emotions we display when neutral +#define BB_MODERATE_EMOTIONS "moderate_emotions" +///emotions we display when depressed +#define BB_SAD_EMOTIONS "sad_emotions" + // Keys used by one and only one behavior // Used to hold state without making bigass lists /// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 330e2d48eb226..d77817a203980 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -304,3 +304,11 @@ #define BB_DEER_RESTING "deer_resting" ///time till our next rest duration #define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" + +//turtle +///our tree's ability +#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability" +///people we headbutt! +#define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" +///flore we must smell +#define BB_TURTLE_FLORA_TARGET "turtle_flora_target" diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 2c20765d3519b..5cb838603c98e 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -154,6 +154,13 @@ DEFINE_BITFIELD(status_flags, list( #define ATTACK_EFFECT_MECHTOXIN "mech_toxin" #define ATTACK_EFFECT_BOOP "boop" //Honk +/// Attack animation for sharp items +#define ATTACK_ANIMATION_SLASH "slash" +/// Attack animation for pointy items +#define ATTACK_ANIMATION_PIERCE "pierce" +/// Animation for blunt attacks +#define ATTACK_ANIMATION_BLUNT "blunt" + //the define for visible message range in combat #define SAMETILE_MESSAGE_RANGE 1 #define COMBAT_MESSAGE_RANGE 3 diff --git a/code/__DEFINES/construction/rcd.dm b/code/__DEFINES/construction/rcd.dm index a8d98215af1dc..4f898d5ae86ec 100644 --- a/code/__DEFINES/construction/rcd.dm +++ b/code/__DEFINES/construction/rcd.dm @@ -51,3 +51,6 @@ #define RCD_MEMORY_COST_BUFF 8 /// If set to TRUE in rcd_vals, will bypass the cooldown on slowing down frequent use #define RCD_RESULT_BYPASS_FREQUENT_USE_COOLDOWN "bypass_frequent_use_cooldown" + +/// How much longer does it take to deconstruct rwalls? +#define RCD_RWALL_DELAY_MULT 2 diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index bedfaf2fa0374..bb5b344a89a48 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -92,3 +92,6 @@ /// from /obj/projectile/energy/fisher/on_hit() or /obj/item/gun/energy/recharge/fisher when striking a target #define COMSIG_ATOM_SABOTEUR_ACT "hit_by_saboteur" #define COMSIG_SABOTEUR_SUCCESS 1 + +/// signal sent when a mouse is hovering over us, sent by atom/proc/on_mouse_entered +#define COMSIG_ATOM_MOUSE_ENTERED "mouse_entered" diff --git a/code/__DEFINES/dcs/signals/signals_mod.dm b/code/__DEFINES/dcs/signals/signals_mod.dm index 58fd8ca689ed1..c08377d50db42 100644 --- a/code/__DEFINES/dcs/signals/signals_mod.dm +++ b/code/__DEFINES/dcs/signals/signals_mod.dm @@ -45,3 +45,5 @@ #define COMSIG_MOD_TETHER_SNAP "mod_tether_snap" /// Called when a MOD module generats its worn overlay #define COMSIG_MODULE_GENERATE_WORN_OVERLAY "mod_module_generate_worn_overlay" +/// Called when the MOD control unit fetches its visor icon +#define COMSIG_MOD_GET_VISOR_OVERLAY "mod_get_visor_overlay" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 63ebfdf98b21f..a83badb9ee067 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -547,6 +547,9 @@ ///Sent from /obj/item/skillchip/on_remove() #define COMSIG_SKILLCHIP_REMOVED "skillchip_removed" +/// from /obj/machinery/computer/camera_advanced/shuttle_docker/gatherNavComputerOverlays() : (list/images_out) +#define COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED "shuttle_nav_computer_image_requested" + /// Sent from /obj/item/organ/wings/functional/proc/open_wings(): (mob/living/carbon/owner) #define COMSIG_WINGS_OPENED "wings_opened" /// Sent from /obj/item/organ/wings/functional/proc/close_wings(): (mob/living/carbon/owner) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 785d353982cfe..975b6037f6bb9 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -54,8 +54,10 @@ #define ui_combo "CENTER+4:24,SOUTH+1:7" //combo meter for martial arts //Lower right, persistent menu -#define ui_drop_throw "EAST-1:28,SOUTH+1:7" +#define ui_rest "EAST-1:28,SOUTH+1:7" +#define ui_drop_throw "EAST-1:28,SOUTH+1:24" #define ui_above_movement "EAST-2:26,SOUTH+1:7" +#define ui_above_movement_top "EAST-2:26, SOUTH+1:24" #define ui_above_intent "EAST-3:24, SOUTH+1:7" #define ui_movi "EAST-2:26,SOUTH:5" #define ui_acti "EAST-3:24,SOUTH:5" @@ -65,8 +67,7 @@ #define ui_crafting "EAST-4:22,SOUTH:5" #define ui_building "EAST-4:22,SOUTH:21" #define ui_language_menu "EAST-4:6,SOUTH:21" -#define ui_navigate_menu "EAST-4:22,SOUTH:5" -#define ui_floor_changer "EAST-3:24, SOUTH+1:3" +#define ui_navigate_menu "EAST-4:6,SOUTH:5" //Upper left (action buttons) #define ui_action_palette "WEST+0:23,NORTH-1:5" @@ -100,7 +101,11 @@ #define ui_living_healthdoll "EAST-1:28,CENTER-1:15" //Humans -#define ui_human_floor_changer "EAST-4:22, SOUTH+1:7" +#define ui_human_floor_changer "EAST-4:22,SOUTH:5" +#define ui_human_crafting "EAST-3:24,SOUTH+1:7" +#define ui_human_navigate "EAST-3:7,SOUTH+1:7" +#define ui_human_language "EAST-3:7,SOUTH+1:24" +#define ui_human_area "EAST-3:24,SOUTH+1:24" //Drones #define ui_drone_drop "CENTER+1:18,SOUTH:5" @@ -123,7 +128,7 @@ #define ui_borg_camera "CENTER+3:21,SOUTH:5" #define ui_borg_alerts "CENTER+4:21,SOUTH:5" #define ui_borg_language_menu "CENTER+4:19,SOUTH+1:6" -#define ui_borg_navigate_menu "CENTER+4:19,SOUTH+1:6" +#define ui_borg_navigate_menu "CENTER+4:3,SOUTH+1:6" #define ui_borg_floor_changer "EAST-1:28,SOUTH+1:39" //Aliens @@ -132,7 +137,7 @@ #define ui_alien_queen_finder "EAST,CENTER-3:15" #define ui_alien_storage_r "CENTER+1:18,SOUTH:5" #define ui_alien_language_menu "EAST-4:20,SOUTH:5" -#define ui_alien_navigate_menu "EAST-4:20,SOUTH:5" +#define ui_alien_navigate_menu "EAST-4:4,SOUTH:5" //AI #define ui_ai_core "BOTTOM:6,RIGHT-4" @@ -173,7 +178,7 @@ #define ui_pai_view_images "SOUTH:6,WEST+12" #define ui_pai_radio "SOUTH:6,WEST+13" #define ui_pai_language_menu "SOUTH+1:8,WEST+12:31" -#define ui_pai_navigate_menu "SOUTH+1:8,WEST+12:31" +#define ui_pai_navigate_menu "SOUTH+1:8,WEST+12:15" //Ghosts #define ui_ghost_spawners_menu "SOUTH:6,CENTER-3:24" @@ -182,8 +187,8 @@ #define ui_ghost_teleport "SOUTH:6,CENTER:24" #define ui_ghost_pai "SOUTH: 6, CENTER+1:24" #define ui_ghost_minigames "SOUTH: 6, CENTER+2:24" -#define ui_ghost_language_menu "SOUTH: 22, CENTER+3:22" -#define ui_ghost_floor_changer "SOUTH: 6, CENTER+3:23" +#define ui_ghost_language_menu "SOUTH: 6, CENTER+3:24" +#define ui_ghost_floor_changer "SOUTH: 6, CENTER+3:8" //Blobbernauts #define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 8548c284ced88..60be4d01182a7 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -253,8 +253,6 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define ismecha(A) (istype(A, /obj/vehicle/sealed/mecha)) -#define ismopable(A) (A && ((PLANE_TO_TRUE(A.plane) == FLOOR_PLANE) ? (A.layer <= FLOOR_CLEAN_LAYER) : (A.layer <= GAME_CLEAN_LAYER))) //If something can be cleaned by floor-cleaning devices such as mops or clean bots - #define isorgan(A) (istype(A, /obj/item/organ)) #define isclothing(A) (istype(A, /obj/item/clothing)) diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm index 5f025ad99cffb..8ae95933e646b 100644 --- a/code/__DEFINES/keybinding.dm +++ b/code/__DEFINES/keybinding.dm @@ -4,6 +4,9 @@ #define COMSIG_KB_ACTIVATED (1<<0) #define COMSIG_KB_EMOTE "keybinding_emote_down" +///Signal sent when a keybind is deactivated +#define DEACTIVATE_KEYBIND(A) "[A]_DEACTIVATED" + //Admin #define COMSIG_KB_ADMIN_ASAY_DOWN "keybinding_admin_asay_down" #define COMSIG_KB_ADMIN_DSAY_DOWN "keybinding_admin_dsay_down" @@ -54,6 +57,7 @@ #define COMSIG_KB_LIVING_DISABLE_COMBAT_DOWN "keybinding_living_disable_combat_down" #define COMSIG_KB_LIVING_TOGGLEMOVEINTENT_DOWN "keybinding_mob_togglemoveintent_down" #define COMSIG_KB_LIVING_TOGGLEMOVEINTENTALT_DOWN "keybinding_mob_togglemoveintentalt_down" +#define COMSIG_KB_LIVING_VIEW_PET_COMMANDS "keybinding_living_view_pet_commands" //Mob #define COMSIG_KB_MOB_FACENORTH_DOWN "keybinding_mob_facenorth_down" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 2c5d8722f1121..35dd5cc35ceb6 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -156,9 +156,7 @@ #define CATWALK_LAYER (14 + TOPDOWN_LAYER) #define LOWER_RUNE_LAYER (15 + TOPDOWN_LAYER) #define RUNE_LAYER (16 + TOPDOWN_LAYER) -/// [GAME_CLEAN_LAYER] but for floors. -/// Basically any layer below this (numerically) is "on" a floor for the purposes of washing -#define FLOOR_CLEAN_LAYER (21 + TOPDOWN_LAYER) +#define CLEANABLE_FLOOR_OBJECT_LAYER (21 + TOPDOWN_LAYER) //Placeholders in case the game plane and possibly other things between it and the floor plane are ever made into topdown planes @@ -185,9 +183,7 @@ #define BOT_PATH_LAYER 2.497 #define LOW_OBJ_LAYER 2.5 #define HIGH_PIPE_LAYER 2.54 -// Anything above this layer is not "on" a turf for the purposes of washing -// I hate this life of ours -#define GAME_CLEAN_LAYER 2.55 +#define CLEANABLE_OBJECT_LAYER 2.55 #define TRAM_STRUCTURE_LAYER 2.57 #define TRAM_FLOOR_LAYER 2.58 #define TRAM_WALL_LAYER 2.59 @@ -201,6 +197,7 @@ #define DOOR_HELPER_LAYER 2.72 //keep this above DOOR_ACCESS_HELPER_LAYER and OPEN_DOOR_LAYER since the others tend to have tiny sprites that tend to be covered up. #define PROJECTILE_HIT_THRESHHOLD_LAYER 2.75 //projectiles won't hit objects at or below this layer if possible #define TABLE_LAYER 2.8 +#define GIB_LAYER 2.85 // sit on top of tables, but below machines #define BELOW_OBJ_LAYER 2.9 #define LOW_ITEM_LAYER 2.95 //#define OBJ_LAYER 3 //For easy recordkeeping; this is a byond define diff --git a/code/__DEFINES/radial_defines.dm b/code/__DEFINES/radial_defines.dm new file mode 100644 index 0000000000000..35ae6eebb5984 --- /dev/null +++ b/code/__DEFINES/radial_defines.dm @@ -0,0 +1,6 @@ +#define NEXT_PAGE_ID "__next__" +#define DEFAULT_CHECK_DELAY 2 SECONDS + +#define BUTTON_SLIDE_IN (1<<0) +#define BUTTON_FADE_IN (1<<1) +#define BUTTON_FADE_OUT (1<<2) diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index 28172587e795a..bf3fd32918943 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -46,7 +46,8 @@ ///Health threshold for synthflesh and rezadone to unhusk someone #define UNHUSK_DAMAGE_THRESHOLD 50 ///Amount of synthflesh required to unhusk someone -#define SYNTHFLESH_UNHUSK_AMOUNT 100 +#define SYNTHFLESH_UNHUSK_AMOUNT 60 +#define SYNTHFLESH_UNHUSK_MAX 100 //used by chem masters and pill presses // The categories of reagent packaging diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index e868097b579c4..32718ae4784da 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1401,4 +1401,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait given to atoms currently affected by projectile dampeners #define TRAIT_GOT_DAMPENED "got_dampened" +/// Apply to movables to say "hey, this movable is technically flat on the floor, so it'd be mopped up by a mop" +#define TRAIT_MOPABLE "mopable" + // END TRAIT DEFINES diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index efee782aaeb8d..aa13b74f0e1cf 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -215,3 +215,8 @@ var/position_a = fluids_priority.Find(initial(a.required_fluid_type)) var/position_b = fluids_priority.Find(initial(b.required_fluid_type)) return cmp_numeric_asc(position_a, position_b) || cmp_text_asc(initial(b.name), initial(a.name)) + +///Sorts stock parts based on tier +/proc/cmp_rped_sort(obj/item/first_item, obj/item/second_item) + ///even though stacks aren't stock parts, get_part_rating() is defined on the item level (see /obj/item/proc/get_part_rating()) and defaults to returning 0. + return second_item.get_part_rating() - first_item.get_part_rating() diff --git a/code/__HELPERS/construction.dm b/code/__HELPERS/construction.dm index 166a009f06661..a0ebb3db0340d 100644 --- a/code/__HELPERS/construction.dm +++ b/code/__HELPERS/construction.dm @@ -46,9 +46,9 @@ return 0 /** - * Splits a stack. we don't use /obj/item/stack/proc/fast_split_stack because Byond complains that should only be called asynchronously. + * Splits a stack. we don't use /obj/item/stack/proc/split_stack because Byond complains that should only be called asynchronously. * This proc is also more faster because it doesn't deal with mobs, copying evidences or refreshing atom storages - * Has special internal uses for e.g. by the material container + * Has special internal uses for e.g. by the material container & RPED * * Arguments: * - [target][obj/item/stack]: the stack to split diff --git a/code/__HELPERS/hud.dm b/code/__HELPERS/hud.dm index 40a12767d28cd..d7d053c40bb11 100644 --- a/code/__HELPERS/hud.dm +++ b/code/__HELPERS/hud.dm @@ -3,14 +3,10 @@ var/y_off = round((i-1) / 2) return"CENTER+[x_off]:16,SOUTH+[y_off]:5" -/proc/ui_equip_position(mob/M) - var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) - return "CENTER:-16,SOUTH+[y_off+1]:5" - /proc/ui_swaphand_position(mob/M, which = LEFT_HANDS) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) - var/x_off = which == LEFT_HANDS ? -1 : 0 + var/x_off = (which == LEFT_HANDS) ? -1 : null var/y_off = round((M.held_items.len-1) / 2) - return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5" + return "CENTER[x_off]:16,SOUTH+[y_off+1]:5" /proc/ui_perk_position(perk_count) var/y_off = perk_count < 1 ? 0 : perk_count/2 diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 318506f1f7a1e..cb180abf20864 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -48,7 +48,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CHASM_STOPPER" = TRAIT_CHASM_STOPPER, "TRAIT_COMBAT_MODE_SKIP_INTERACTION" = TRAIT_COMBAT_MODE_SKIP_INTERACTION, "TRAIT_DEL_ON_SPACE_DUMP" = TRAIT_DEL_ON_SPACE_DUMP, - "TRAIT_VALID_DNA_INFUSION" = TRAIT_VALID_DNA_INFUSION, "TRAIT_FROZEN" = TRAIT_FROZEN, "TRAIT_HAS_LABEL" = TRAIT_HAS_LABEL, "TRAIT_HEARING_SENSITIVE" = TRAIT_HEARING_SENSITIVE, @@ -57,34 +56,36 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_IRRADIATED" = TRAIT_IRRADIATED, "TRAIT_IS_AQUARIUM" = TRAIT_IS_AQUARIUM, "TRAIT_LAVA_IMMUNE" = TRAIT_LAVA_IMMUNE, + "TRAIT_MOPABLE" = TRAIT_MOPABLE, "TRAIT_MOVE_FLOATING" = TRAIT_MOVE_FLOATING, "TRAIT_MOVE_FLYING" = TRAIT_MOVE_FLYING, "TRAIT_MOVE_GROUND" = TRAIT_MOVE_GROUND, "TRAIT_MOVE_PHASING" = TRAIT_MOVE_PHASING, "TRAIT_MOVE_UPSIDE_DOWN" = TRAIT_MOVE_UPSIDE_DOWN, - "TRAIT_MOVE_VENTCRAWLING" = TRAIT_MOVE_VENTCRAWLING, "TRAIT_MOVE_UPSIDE_DOWN" = TRAIT_MOVE_UPSIDE_DOWN, + "TRAIT_MOVE_VENTCRAWLING" = TRAIT_MOVE_VENTCRAWLING, + "TRAIT_NOT_BARFABLE" = TRAIT_NOT_BARFABLE, + "TRAIT_NOT_ENGRAVABLE" = TRAIT_NOT_ENGRAVABLE, "TRAIT_NO_FLOATING_ANIM" = TRAIT_NO_FLOATING_ANIM, "TRAIT_NO_MANIFEST_CONTENTS_ERROR" = TRAIT_NO_MANIFEST_CONTENTS_ERROR, "TRAIT_NO_MISSING_ITEM_ERROR" = TRAIT_NO_MISSING_ITEM_ERROR, "TRAIT_NO_THROW_HITPUSH" = TRAIT_NO_THROW_HITPUSH, - "TRAIT_NOT_BARFABLE" = TRAIT_NOT_BARFABLE, - "TRAIT_NOT_ENGRAVABLE" = TRAIT_NOT_ENGRAVABLE, "TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT" = TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT, "TRAIT_ON_HIT_EFFECT" = TRAIT_ON_HIT_EFFECT, "TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN, "TRAIT_SCARY_FISHERMAN" = TRAIT_SCARY_FISHERMAN, "TRAIT_SECLUDED_LOCATION" = TRAIT_SECLUDED_LOCATION, + "TRAIT_SILENT_REACTIONS" = TRAIT_SILENT_REACTIONS, "TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE, "TRAIT_SPELLS_TRANSFER_TO_LOC" = TRAIT_SPELLS_TRANSFER_TO_LOC, "TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH" = TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, "TRAIT_TELEKINESIS_CONTROLLED" = TRAIT_TELEKINESIS_CONTROLLED, "TRAIT_UNDERFLOOR" = TRAIT_UNDERFLOOR, "TRAIT_UNIQUE_IMMERSE" = TRAIT_UNIQUE_IMMERSE, + "TRAIT_VALID_DNA_INFUSION" = TRAIT_VALID_DNA_INFUSION, "TRAIT_WADDLING" = TRAIT_WADDLING, "TRAIT_WAS_RENAMED" = TRAIT_WAS_RENAMED, "TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE, - "TRAIT_SILENT_REACTIONS" = TRAIT_SILENT_REACTIONS, ), /datum/controller/subsystem/economy = list( "TRAIT_MARKET_CRASHING" = TRAIT_MARKET_CRASHING, diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 562266f51c031..5d092cbc1f6b5 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -110,32 +110,37 @@ last_hovored_ref = null if(!can_use(usr)) return + var/datum/hud/our_hud = usr.hud_used if(over_object == src) our_hud.hide_landings() return + if(istype(over_object, /atom/movable/screen/action_landing)) var/atom/movable/screen/action_landing/reserve = over_object reserve.hit_by(src) - our_hud.hide_landings() save_position() + our_hud.hide_landings() return - our_hud.hide_landings() if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll)) our_hud.position_action(src, SCRN_OBJ_IN_PALETTE) save_position() + our_hud.hide_landings() return + if(istype(over_object, /atom/movable/screen/movable/action_button)) var/atom/movable/screen/movable/action_button/button = over_object our_hud.position_action_relative(src, button) save_position() + our_hud.hide_landings() return . = ..() our_hud.position_action(src, screen_loc) save_position() + our_hud.hide_landings() /atom/movable/screen/movable/action_button/proc/save_position() var/mob/user = our_hud.mymob @@ -290,6 +295,7 @@ /atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud) src.our_hud = our_hud refresh_owner() + disable_landing() // If our hud already has elements, don't force hide us /atom/movable/screen/button_palette/update_name(updates) . = ..() @@ -311,6 +317,15 @@ icon_state = "[ui_name]_palette" +/atom/movable/screen/button_palette/proc/activate_landing() + // Reveal ourselves to the user + invisibility = INVISIBILITY_NONE + +/atom/movable/screen/button_palette/proc/disable_landing() + // If we have no elements in the palette, hide your ugly self please + if (!length(our_hud.palette_actions?.actions)) + invisibility = INVISIBILITY_ABSTRACT + /atom/movable/screen/button_palette/MouseEntered(location, control, params) . = ..() if(QDELETED(src)) diff --git a/code/_onclick/hud/alien.dm b/code/_onclick/hud/alien.dm index b9a0e3bf655f4..20e1e72f950fd 100644 --- a/code/_onclick/hud/alien.dm +++ b/code/_onclick/hud/alien.dm @@ -39,13 +39,13 @@ using = new /atom/movable/screen/swap_hand(null, src) using.icon = ui_style using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) + using.screen_loc = ui_swaphand_position(owner, 1) static_inventory += using using = new /atom/movable/screen/swap_hand(null, src) using.icon = ui_style using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) + using.screen_loc = ui_swaphand_position(owner, 2) static_inventory += using action_intent = new /atom/movable/screen/combattoggle/flashy(null, src) @@ -61,7 +61,6 @@ floor_change = new /atom/movable/screen/floor_changer(null, src) floor_change.icon = ui_style - floor_change.screen_loc = ui_above_intent static_inventory += floor_change using = new/atom/movable/screen/language_menu(null, src) @@ -77,11 +76,11 @@ using.screen_loc = ui_drop_throw static_inventory += using - using = new /atom/movable/screen/resist(null, src) - using.icon = ui_style - using.screen_loc = ui_above_movement - using.update_appearance() - hotkeybuttons += using + resist_icon = new /atom/movable/screen/resist(null, src) + resist_icon.icon = ui_style + resist_icon.screen_loc = ui_above_movement + resist_icon.update_appearance() + hotkeybuttons += resist_icon throw_icon = new /atom/movable/screen/throw_catch(null, src) throw_icon.icon = ui_style diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm index bb2b9fcb14aee..922953b01a36f 100644 --- a/code/_onclick/hud/alien_larva.dm +++ b/code/_onclick/hud/alien_larva.dm @@ -12,7 +12,6 @@ floor_change = new /atom/movable/screen/floor_changer(null, src) floor_change.icon = ui_style - floor_change.screen_loc = ui_above_intent static_inventory += floor_change healths = new /atom/movable/screen/healths/alien(null, src) diff --git a/code/_onclick/hud/generic_dextrous.dm b/code/_onclick/hud/generic_dextrous.dm index 4048fd91b16f6..134f91a2b8d40 100644 --- a/code/_onclick/hud/generic_dextrous.dm +++ b/code/_onclick/hud/generic_dextrous.dm @@ -3,11 +3,6 @@ ..() var/atom/movable/screen/using - using = new /atom/movable/screen/drop(null, src) - using.icon = ui_style - using.screen_loc = ui_drone_drop - static_inventory += using - pull_icon = new /atom/movable/screen/pull(null, src) pull_icon.icon = ui_style pull_icon.update_appearance() @@ -16,21 +11,20 @@ build_hand_slots() - using = new /atom/movable/screen/swap_hand(null, src) + using = new /atom/movable/screen/drop(null, src) using.icon = ui_style - using.icon_state = "swap_1_m" - using.screen_loc = ui_swaphand_position(owner,1) + using.screen_loc = ui_swaphand_position(owner, 1) static_inventory += using using = new /atom/movable/screen/swap_hand(null, src) using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) + using.icon_state = "act_swap" + using.screen_loc = ui_swaphand_position(owner, 2) static_inventory += using action_intent = new /atom/movable/screen/combattoggle/flashy(null, src) action_intent.icon = ui_style - action_intent.screen_loc = ui_combat_toggle + action_intent.screen_loc = ui_movi static_inventory += action_intent floor_change = new /atom/movable/screen/floor_changer(null, src) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index ab1e5fccfbb0d..ae1e619763f22 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -40,6 +40,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/atom/movable/screen/pull_icon var/atom/movable/screen/rest_icon var/atom/movable/screen/throw_icon + var/atom/movable/screen/resist_icon var/atom/movable/screen/module_store_icon var/atom/movable/screen/floor_change @@ -505,12 +506,17 @@ GLOBAL_LIST_INIT(available_ui_styles, list( static_inventory += hand_box hand_box.update_appearance() - var/i = 1 - for(var/atom/movable/screen/swap_hand/SH in static_inventory) - SH.screen_loc = ui_swaphand_position(mymob, IS_RIGHT_INDEX(i) ? RIGHT_HANDS : LEFT_HANDS) - i++ - for(var/atom/movable/screen/human/equip/E in static_inventory) - E.screen_loc = ui_equip_position(mymob) + var/num_of_swaps = 0 + for(var/atom/movable/screen/swap_hand/swap_hands in static_inventory) + num_of_swaps += 1 + + var/hand_num = 1 + for(var/atom/movable/screen/swap_hand/swap_hands in static_inventory) + var/hand_ind = RIGHT_HANDS + if (num_of_swaps > 1) + hand_ind = IS_RIGHT_INDEX(hand_num) ? LEFT_HANDS : RIGHT_HANDS + swap_hands.screen_loc = ui_swaphand_position(mymob, hand_ind) + hand_num += 1 if(ismob(mymob) && mymob.hud_used == src) show_hud(hud_version) @@ -581,11 +587,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list( /datum/hud/proc/generate_landings(atom/movable/screen/movable/action_button/button) listed_actions.generate_landing() palette_actions.generate_landing() + toggle_palette.activate_landing() /// Clears all currently visible landings /datum/hud/proc/hide_landings() listed_actions.clear_landing() palette_actions.clear_landing() + toggle_palette.disable_landing() // Updates any existing "owned" visuals, ensures they continue to be visible /datum/hud/proc/update_our_owner() diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 50954584a0df2..e17757501cf9e 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -4,6 +4,7 @@ /atom/movable/screen/human/toggle name = "toggle" icon_state = "toggle" + base_icon_state = "toggle" mouse_over_pointer = MOUSE_HAND_POINTER /atom/movable/screen/human/toggle/Click() @@ -23,17 +24,11 @@ usr.client.screen += targetmob.hud_used.toggleable_inventory targetmob.hud_used.hidden_inventory_update(usr) + update_appearance() -/atom/movable/screen/human/equip - name = "equip" - icon_state = "act_equip" - mouse_over_pointer = MOUSE_HAND_POINTER - -/atom/movable/screen/human/equip/Click() - if(ismecha(usr.loc)) // stops inventory actions in a mech - return TRUE - var/mob/living/carbon/human/H = usr - H.quick_equip() +/atom/movable/screen/human/toggle/update_icon_state() + icon_state = "[base_icon_state][hud?.inventory_shown ? "_active" : ""]" + return ..() /atom/movable/screen/ling icon = 'icons/hud/screen_changeling.dmi' @@ -63,14 +58,17 @@ using = new /atom/movable/screen/language_menu(null, src) using.icon = ui_style + using.screen_loc = ui_human_language static_inventory += using using = new /atom/movable/screen/navigate(null, src) using.icon = ui_style + using.screen_loc = ui_human_navigate static_inventory += using using = new /atom/movable/screen/area_creator(null, src) using.icon = ui_style + using.screen_loc = ui_human_area static_inventory += using action_intent = new /atom/movable/screen/combattoggle/flashy(null, src) @@ -78,23 +76,17 @@ action_intent.screen_loc = ui_combat_toggle static_inventory += action_intent - floor_change = new /atom/movable/screen/floor_changer(null, src) + floor_change = new /atom/movable/screen/floor_changer/vertical(null, src) floor_change.icon = ui_style floor_change.screen_loc = ui_human_floor_changer static_inventory += floor_change - using = new /atom/movable/screen/mov_intent(null, src) using.icon = ui_style using.icon_state = (owner.move_intent == MOVE_INTENT_RUN ? "running" : "walking") using.screen_loc = ui_movi static_inventory += using - using = new /atom/movable/screen/drop(null, src) - using.icon = ui_style - using.screen_loc = ui_drop_throw - static_inventory += using - inv_box = new /atom/movable/screen/inventory(null, src) inv_box.name = "uniform" inv_box.icon = ui_style @@ -115,16 +107,15 @@ build_hand_slots() - using = new /atom/movable/screen/swap_hand(null, src) + using = new /atom/movable/screen/drop(null, src) using.icon = ui_style - using.icon_state = "swap_1" - using.screen_loc = ui_swaphand_position(owner,1) + using.screen_loc = ui_swaphand_position(owner, 1) static_inventory += using using = new /atom/movable/screen/swap_hand(null, src) using.icon = ui_style - using.icon_state = "swap_2" - using.screen_loc = ui_swaphand_position(owner,2) + using.icon_state = "act_swap" + using.screen_loc = ui_swaphand_position(owner, 2) static_inventory += using inv_box = new /atom/movable/screen/inventory(null, src) @@ -190,21 +181,16 @@ inv_box.slot_id = ITEM_SLOT_SUITSTORE static_inventory += inv_box - using = new /atom/movable/screen/resist(null, src) - using.icon = ui_style - using.screen_loc = ui_above_intent - hotkeybuttons += using + resist_icon = new /atom/movable/screen/resist(null, src) + resist_icon.icon = ui_style + resist_icon.screen_loc = ui_above_movement + hotkeybuttons += resist_icon using = new /atom/movable/screen/human/toggle(null, src) using.icon = ui_style using.screen_loc = ui_inventory static_inventory += using - using = new /atom/movable/screen/human/equip() - using.icon = ui_style - using.screen_loc = ui_equip_position(mymob) - static_inventory += using - inv_box = new /atom/movable/screen/inventory(null, src) inv_box.name = "gloves" inv_box.icon = ui_style @@ -266,7 +252,7 @@ rest_icon = new /atom/movable/screen/rest(null, src) rest_icon.icon = ui_style - rest_icon.screen_loc = ui_above_movement + rest_icon.screen_loc = ui_rest rest_icon.update_appearance() static_inventory += rest_icon @@ -287,7 +273,7 @@ pull_icon = new /atom/movable/screen/pull(null, src) pull_icon.icon = ui_style - pull_icon.screen_loc = ui_above_intent + pull_icon.screen_loc = ui_above_movement_top pull_icon.update_appearance() static_inventory += pull_icon @@ -336,8 +322,6 @@ var/obj/item/organ/eyes/eyes = human_mob.get_organ_slot(ORGAN_SLOT_EYES) if(eyes?.no_glasses) blocked_slots |= ITEM_SLOT_EYES - if(human_mob.bodyshape & BODYSHAPE_DIGITIGRADE) - blocked_slots |= ITEM_SLOT_FEET for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(!inv.slot_id) diff --git a/code/_onclick/hud/new_player.dm b/code/_onclick/hud/new_player.dm index 01f79aa147684..594449575ea06 100644 --- a/code/_onclick/hud/new_player.dm +++ b/code/_onclick/hud/new_player.dm @@ -259,12 +259,13 @@ icon = 'icons/hud/lobby/join.dmi' icon_state = "" //Default to not visible base_icon_state = "join_game" + enabled = null // set in init /atom/movable/screen/lobby/button/join/Initialize(mapload, datum/hud/hud_owner) . = ..() - set_button_status(FALSE) switch(SSticker.current_state) if(GAME_STATE_PREGAME, GAME_STATE_STARTUP) + set_button_status(FALSE) RegisterSignal(SSticker, COMSIG_TICKER_ENTER_SETTING_UP, PROC_REF(show_join_button)) if(GAME_STATE_SETTING_UP) set_button_status(TRUE) @@ -330,6 +331,7 @@ icon = 'icons/hud/lobby/observe.dmi' icon_state = "observe_disabled" base_icon_state = "observe" + enabled = null // set in init /atom/movable/screen/lobby/button/observe/Initialize(mapload, datum/hud/hud_owner) . = ..() diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index ab95d3bb392eb..041a16b12a132 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -1,6 +1,3 @@ -#define NEXT_PAGE_ID "__next__" -#define DEFAULT_CHECK_DELAY 20 - GLOBAL_LIST_EMPTY(radial_menus) /atom/movable/screen/radial @@ -113,7 +110,7 @@ GLOBAL_LIST_EMPTY(radial_menus) var/hudfix_method = TRUE //TRUE to change anchor to user, FALSE to shift by py_shift var/py_shift = 0 - var/entry_animation = TRUE + var/button_animation_flags = BUTTON_SLIDE_IN ///A replacement icon state for the generic radial slice bg icon. Doesn't affect the next page nor the center buttons var/radial_slice_icon @@ -163,6 +160,8 @@ GLOBAL_LIST_EMPTY(radial_menus) var/atom/movable/screen/radial/slice/new_element = new /atom/movable/screen/radial/slice new_element.tooltips = use_tooltips new_element.set_parent(src) + if(button_animation_flags & BUTTON_FADE_IN) + new_element.alpha = 0 elements += new_element var/page = 1 @@ -186,9 +185,9 @@ GLOBAL_LIST_EMPTY(radial_menus) page_data[page] = current pages = page current_page = clamp(set_page, 1, pages) - update_screen_objects(entry_animation, click_on_hover) + update_screen_objects(button_animation_flags, click_on_hover) -/datum/radial_menu/proc/update_screen_objects(anim = FALSE, click_on_hover = FALSE) +/datum/radial_menu/proc/update_screen_objects(anim_flag = NONE, click_on_hover = FALSE) var/list/page_choices = page_data[current_page] var/angle_per_element = round(zone / page_choices.len) for(var/i in 1 to elements.len) @@ -198,11 +197,11 @@ GLOBAL_LIST_EMPTY(radial_menus) HideElement(element) element.click_on_hover = FALSE else - SetElement(element,page_choices[i],angle,anim = anim,anim_order = i) + SetElement(element,page_choices[i],angle,anim_flag = anim_flag,anim_order = i) // Only activate click on hover after the animation plays if (!click_on_hover) continue - if (anim) + if (anim_flag) addtimer(VARSET_CALLBACK(element, click_on_hover, TRUE), i * 0.5) else element.click_on_hover = TRUE @@ -217,11 +216,11 @@ GLOBAL_LIST_EMPTY(radial_menus) E.choice = null E.next_page = FALSE -/datum/radial_menu/proc/SetElement(atom/movable/screen/radial/slice/E,choice_id,angle,anim,anim_order) +/datum/radial_menu/proc/SetElement(atom/movable/screen/radial/slice/E, choice_id, angle, anim_flag, anim_order) //Position var/py = round(cos(angle) * radius) + py_shift var/px = round(sin(angle) * radius) - if(anim) + if(anim_flag & BUTTON_SLIDE_IN) var/timing = anim_order * 0.5 var/matrix/starting = matrix() starting.Scale(0.1,0.1) @@ -232,8 +231,11 @@ GLOBAL_LIST_EMPTY(radial_menus) E.pixel_y = py E.pixel_x = px - //Visuals - E.alpha = 255 + if(anim_flag & BUTTON_FADE_IN) + animate(E, alpha = 255, time = 0.15 SECONDS, easing = EASE_OUT) + else + E.alpha = 255 + E.mouse_opacity = MOUSE_OPACITY_ICON E.cut_overlays() E.vis_contents.Cut() @@ -266,7 +268,9 @@ GLOBAL_LIST_EMPTY(radial_menus) info_button.layer = RADIAL_CONTENT_LAYER E.vis_contents += info_button -/datum/radial_menu/New() +/datum/radial_menu/New(display_close_button) + if(!display_close_button) + return close_button = new close_button.set_parent(src) @@ -327,7 +331,9 @@ GLOBAL_LIST_EMPTY(radial_menus) menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing", layer = RADIAL_BACKGROUND_LAYER, pixel_x = offset_x, pixel_y = offset_y) SET_PLANE_EXPLICIT(menu_holder, ABOVE_HUD_PLANE, M) menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM - menu_holder.vis_contents += elements + close_button + menu_holder.vis_contents += elements + if(!isnull(close_button)) + menu_holder.vis_contents += close_button current_user.images += menu_holder /datum/radial_menu/proc/hide() @@ -345,6 +351,14 @@ GLOBAL_LIST_EMPTY(radial_menus) next_check = world.time + check_delay stoplag(1) +/datum/radial_menu/proc/remove_menu() + if(!(button_animation_flags & BUTTON_FADE_OUT)) + qdel(src) + return + for(var/atom/movable/element as anything in elements) + animate(element, alpha = 0, time = 0.15 SECONDS) + QDEL_IN(src, 0.5 SECONDS) + /datum/radial_menu/Destroy() Reset() hide() @@ -356,11 +370,11 @@ GLOBAL_LIST_EMPTY(radial_menus) Choices should be a list where list keys are movables or text used for element names and return value and list values are movables/icons/images used for element icons */ -/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, entry_animation = TRUE, click_on_hover = FALSE, user_space = FALSE) +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, button_animation_flags = BUTTON_SLIDE_IN, click_on_hover = FALSE, user_space = FALSE, check_delay = DEFAULT_CHECK_DELAY, display_close_button = TRUE, radial_menu_offset = list(0, 0)) if(!user || !anchor || !length(choices)) return - if(length(choices)==1 && autopick_single_option) + if(length(choices) == 1 && autopick_single_option) return choices[1] if(!uniqueid) @@ -372,8 +386,9 @@ GLOBAL_LIST_EMPTY(radial_menus) menu.finished = TRUE return - var/datum/radial_menu/menu = new - menu.entry_animation = entry_animation + var/datum/radial_menu/menu = new(display_close_button) + menu.button_animation_flags = button_animation_flags + menu.check_delay = check_delay GLOB.radial_menus[uniqueid] = menu if(radius) menu.radius = radius @@ -390,10 +405,12 @@ GLOBAL_LIST_EMPTY(radial_menus) var/turf/anchor_turf = get_turf(anchor) offset_x = (anchor_turf.x - user_turf.x) * ICON_SIZE_X + anchor.pixel_x - user.pixel_x offset_y = (anchor_turf.y - user_turf.y) * ICON_SIZE_Y + anchor.pixel_y - user.pixel_y + offset_x += radial_menu_offset[1] + offset_y += radial_menu_offset[2] menu.show_to(user, offset_x, offset_y) menu.wait(user, anchor, require_near) var/answer = menu.selected_choice - qdel(menu) + menu.remove_menu() GLOB.radial_menus -= uniqueid if(require_near && !in_range(anchor, user)) return diff --git a/code/_onclick/hud/radial_persistent.dm b/code/_onclick/hud/radial_persistent.dm index 5fe81d005bd43..d48c8d9eb456c 100644 --- a/code/_onclick/hud/radial_persistent.dm +++ b/code/_onclick/hud/radial_persistent.dm @@ -43,7 +43,7 @@ /datum/radial_menu/persistent/proc/change_choices(list/newchoices, tooltips = FALSE, animate = FALSE, keep_same_page = FALSE) if(!newchoices.len) return - entry_animation = FALSE + button_animation_flags = NONE var/target_page = keep_same_page ? current_page : 1 //Stores the current_page value before it's set back to 1 on Reset() Reset() set_choices(newchoices,tooltips, set_page = target_page) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index b72924fdbe573..fefd4ea889dad 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -364,7 +364,7 @@ name = "change floor" icon = 'icons/hud/screen_midnight.dmi' icon_state = "floor_change" - screen_loc = ui_floor_changer + screen_loc = ui_above_intent mouse_over_pointer = MOUSE_HAND_POINTER var/vertical = FALSE @@ -439,10 +439,12 @@ name = "resist" icon = 'icons/hud/screen_midnight.dmi' icon_state = "act_resist" + base_icon_state = "act_resist" plane = HUD_PLANE mouse_over_pointer = MOUSE_HAND_POINTER /atom/movable/screen/resist/Click() + flick("[base_icon_state]_on", src) if(isliving(usr)) var/mob/living/L = usr L.resist() @@ -464,7 +466,7 @@ var/mob/living/user = hud?.mymob if(!istype(user)) return ..() - icon_state = "[base_icon_state][user.resting ? 0 : null]" + icon_state = "[base_icon_state][user.resting ? "_on" : null]" return ..() /atom/movable/screen/storage @@ -537,7 +539,7 @@ /atom/movable/screen/throw_catch name = "throw/catch" icon = 'icons/hud/screen_midnight.dmi' - icon_state = "act_throw_off" + icon_state = "act_throw" mouse_over_pointer = MOUSE_HAND_POINTER /atom/movable/screen/throw_catch/Click() diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 3d17d9abe894c..70da5ac19de3a 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -444,21 +444,30 @@ else return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100 -/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, def_zone) - if(!I.force && !length(I.attack_verb_simple) && !length(I.attack_verb_continuous)) +/mob/living/proc/send_item_attack_message(obj/item/weapon, mob/living/user, hit_area, def_zone) + if(!weapon.force && !length(weapon.attack_verb_simple) && !length(weapon.attack_verb_continuous)) return - var/message_verb_continuous = length(I.attack_verb_continuous) ? "[pick(I.attack_verb_continuous)]" : "attacks" - var/message_verb_simple = length(I.attack_verb_simple) ? "[pick(I.attack_verb_simple)]" : "attack" - var/message_hit_area = get_hit_area_message(hit_area) - var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [I]!" - var/attack_message_victim = "Something [message_verb_continuous] you[message_hit_area] with [I]!" - var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I]!" + // Sanity in case one is null for some reason + var/picked_index = rand(max(length(weapon.attack_verb_simple), length(weapon.attack_verb_continuous))) + + var/message_verb_continuous = "attacks" + var/message_verb_simple = "attack" + var/message_hit_area = get_hit_area_message(hit_area) + // Sanity in case one is... longer than the other? + if (picked_index && length(weapon.attack_verb_continuous) >= picked_index) + message_verb_continuous = weapon.attack_verb_continuous[picked_index] + if (picked_index && length(weapon.attack_verb_simple) >= picked_index) + message_verb_simple = weapon.attack_verb_simple[picked_index] + + var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [weapon]!" + var/attack_message_victim = "Something [message_verb_continuous] you[message_hit_area] with [weapon]!" + var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [weapon]!" if(user in viewers(src, null)) - attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [I]!" - attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [I]!" + attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [weapon]!" + attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [weapon]!" if(user == src) - attack_message_victim = "You [message_verb_simple] yourself[message_hit_area] with [I]." + attack_message_victim = "You [message_verb_simple] yourself[message_hit_area] with [weapon]." visible_message(span_danger("[attack_message_spectator]"),\ span_userdanger("[attack_message_victim]"), null, COMBAT_MESSAGE_RANGE, user) if(is_blind()) diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 20194e66626ca..2b61cabb86074 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -524,6 +524,7 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec /datum/controller/subsystem/explosions/proc/shake_the_room(turf/epicenter, near_distance, far_distance, quake_factor, echo_factor, creaking, sound/near_sound = sound(get_sfx(SFX_EXPLOSION)), sound/far_sound = sound('sound/effects/explosion/explosionfar.ogg'), sound/echo_sound = sound('sound/effects/explosion/explosion_distant.ogg'), sound/creaking_sound = sound(get_sfx(SFX_EXPLOSION_CREAKING)), hull_creaking_sound = sound(get_sfx(SFX_HULL_CREAKING))) var/frequency = get_rand_frequency() var/blast_z = epicenter.z + var/area/epicenter_area = get_area(epicenter) if(isnull(creaking)) // Autoset creaking. var/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION) if(on_station && prob((quake_factor * QUAKE_CREAK_PROB) + (echo_factor * ECHO_CREAK_PROB))) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. @@ -559,7 +560,7 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec base_shake_amount = max(base_shake_amount, quake_factor * 3, 0) // Devastating explosions rock the station and ground shake_camera(listener, FAR_SHAKE_DURATION, min(base_shake_amount, FAR_SHAKE_CAP)) - else if(!isspaceturf(listener_turf) && echo_factor) // Big enough explosions echo through the hull. + else if(!isspaceturf(listener_turf) && !(!(epicenter_area.type in GLOB.the_station_areas) && SSmapping.is_planetary()) && echo_factor) // Big enough explosions echo through the hull. Except on planetary maps if the epicenter is not on the station's area. var/echo_volume if(quake_factor) echo_volume = 60 diff --git a/code/controllers/subsystem/movement/newtonian_movement.dm b/code/controllers/subsystem/movement/newtonian_movement.dm index 41db87d722b58..e4143669678b2 100644 --- a/code/controllers/subsystem/movement/newtonian_movement.dm +++ b/code/controllers/subsystem/movement/newtonian_movement.dm @@ -39,7 +39,7 @@ MOVEMENT_SUBSYSTEM_DEF(newtonian_movement) /datum/controller/subsystem/movement/newtonian_movement/proc/fire_moveloop(datum/move_loop/loop) // Loop isn't even running right now - if(!(loop.status & MOVELOOP_STATUS_QUEUED) || isnull(loop.queued_time)) + if(!(loop.status & MOVELOOP_STATUS_QUEUED)) return // Drop the loop, process it, and if its still valid - queue it again dequeue_loop(loop) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 9d33e977e2b7f..be89e7fa6271a 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -37,6 +37,15 @@ SUBSYSTEM_DEF(statpanels) var/ETA = SSshuttle.emergency.getModeStr() if(ETA) global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]" + + if(SSticker.reboot_timer) + var/reboot_time = timeleft(SSticker.reboot_timer) + if(reboot_time) + global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]" + // admin must have delayed round end + else if(SSticker.ready_for_reboot) + global_data += "Reboot: DELAYED" + src.currentrun = GLOB.clients.Copy() mc_data = null @@ -92,11 +101,14 @@ SUBSYSTEM_DEF(statpanels) return /datum/controller/subsystem/statpanels/proc/set_status_tab(client/target) +#if MIN_COMPILER_VERSION > 515 + #warn 516 is most certainly out of beta, remove this beta notice if you haven't already +#endif + var/static/list/beta_notice = list("", "You are on the BYOND 516 beta, various UIs and such may be broken!", "Please report issues, and switch back to BYOND 515 if things are causing too many issues for you.") if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() return - target.stat_panel.send_message("update_stat", list( - "global_data" = global_data, + "global_data" = (target.byond_version < 516) ? global_data : (global_data + beta_notice), "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)", "other_str" = target.mob?.get_status_tab_items(), )) diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index bb18a45b72d9a..03e7b6dcbd885 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -67,6 +67,9 @@ SUBSYSTEM_DEF(ticker) /// Why an emergency shuttle was called var/emergency_reason + /// ID of round reboot timer, if it exists + var/reboot_timer = null + /datum/controller/subsystem/ticker/Initialize() var/list/byond_sound_formats = list( "mid" = TRUE, @@ -698,11 +701,10 @@ SUBSYSTEM_DEF(ticker) var/start_wait = world.time UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever - sleep(delay - (world.time - start_wait)) + reboot_timer = addtimer(CALLBACK(src, PROC_REF(reboot_callback), reason, end_string), delay - (world.time - start_wait), TIMER_STOPPABLE) - if(delay_end && !skip_delay) - to_chat(world, span_boldannounce("Reboot was cancelled by an admin.")) - return + +/datum/controller/subsystem/ticker/proc/reboot_callback(reason, end_string) if(end_string) end_state = end_string @@ -710,6 +712,21 @@ SUBSYSTEM_DEF(ticker) world.Reboot() +/** + * Deletes the current reboot timer and nulls the var + * + * Arguments: + * * user - the user that cancelled the reboot, may be null + */ +/datum/controller/subsystem/ticker/proc/cancel_reboot(mob/user) + if(!reboot_timer) + to_chat(user, span_warning("There is no pending reboot!")) + return FALSE + to_chat(world, span_boldannounce("An admin has delayed the round end.")) + deltimer(reboot_timer) + reboot_timer = null + return TRUE + /datum/controller/subsystem/ticker/Shutdown() gather_newscaster() //called here so we ensure the log is created even upon admin reboot if(!round_end_sound) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm index 6cae6132d3688..4d7a3e7ad3064 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm @@ -31,11 +31,11 @@ var/list/final_list switch(happiness_value) if(HIGH_HAPPINESS_THRESHOLD to INFINITY) - final_list = happy_emotions + final_list = controller.blackboard[BB_HAPPY_EMOTIONS] || happy_emotions if(MODERATE_HAPPINESS_THRESHOLD to HIGH_HAPPINESS_THRESHOLD) - final_list = moderate_emotions + final_list = controller.blackboard[BB_MODERATE_EMOTIONS] || moderate_emotions else - final_list = depressed_emotions + final_list = controller.blackboard[BB_SAD_EMOTIONS] || depressed_emotions if(!length(final_list)) return controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(final_list)) diff --git a/code/datums/components/alternative_sharpness.dm b/code/datums/components/alternative_sharpness.dm new file mode 100644 index 0000000000000..e32c3ccf45d6c --- /dev/null +++ b/code/datums/components/alternative_sharpness.dm @@ -0,0 +1,60 @@ +/// Allows items to have different sharpness for right click attacks +/datum/component/alternative_sharpness + /// Sharpness we change the attack to + var/alt_sharpness = NONE + /// Overrides for continuous attack verbs when performing an alt attack + var/verbs_continuous = null + /// Overrides for simple attack verbs when performing an alt attack + var/verbs_simple = null + /// Value by which we offset our force during the attack + var/force_mod = 0 + /// Are we currently performing an alt attack? + var/alt_attacking = FALSE + /// Trait required for us to trigger + var/required_trait = null + // Old values before we overrode them + var/base_continuous = null + var/base_simple = null + var/base_sharpness = NONE + +/datum/component/alternative_sharpness/Initialize(alt_sharpness, verbs_continuous = null, verbs_simple = null, force_mod = 0, required_trait = null) + if (!isitem(parent)) + return COMPONENT_INCOMPATIBLE + var/obj/item/weapon = parent + src.alt_sharpness = alt_sharpness + src.verbs_continuous = verbs_continuous + src.verbs_simple = verbs_simple + src.force_mod = force_mod + src.required_trait = required_trait + base_continuous = weapon.attack_verb_continuous + base_simple = weapon.attack_verb_simple + +/datum/component/alternative_sharpness/RegisterWithParent() + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK_SECONDARY, PROC_REF(on_secondary_attack)) + +/datum/component/alternative_sharpness/proc/on_secondary_attack(obj/item/source, atom/target, mob/user, params) + SIGNAL_HANDLER + + if (alt_attacking || (required_trait && !HAS_TRAIT(source, required_trait))) + return + + alt_attacking = TRUE + source.force += force_mod + base_sharpness = source.sharpness + source.sharpness = alt_sharpness + if (!isnull(verbs_continuous)) + source.attack_verb_continuous = verbs_continuous + + if (!isnull(verbs_simple)) + source.attack_verb_simple = verbs_simple + + // I absolutely despise this but this is geniunely the best way to do this without creating and hooking up to a dozen signals and still risking failure edge cases + addtimer(CALLBACK(src, PROC_REF(disable_alt_attack)), 1) + +/datum/component/alternative_sharpness/proc/disable_alt_attack() + var/obj/item/weapon = parent + alt_attacking = FALSE + weapon.force -= force_mod + weapon.attack_verb_continuous = base_continuous + weapon.attack_verb_simple = base_simple + weapon.sharpness = base_sharpness diff --git a/code/datums/components/callouts.dm b/code/datums/components/callouts.dm index 52a3e007905c3..854f769f8a10a 100644 --- a/code/datums/components/callouts.dm +++ b/code/datums/components/callouts.dm @@ -111,7 +111,7 @@ for(var/datum/callout_option/callout_option as anything in callout_options) callout_items[callout_option] = image(icon = 'icons/hud/radial.dmi', icon_state = callout_option::icon_state) - var/datum/callout_option/selection = show_radial_menu(user, get_turf(clicked_atom), callout_items, entry_animation = FALSE, click_on_hover = TRUE, user_space = TRUE) + var/datum/callout_option/selection = show_radial_menu(user, get_turf(clicked_atom), callout_items, button_animation_flags = NONE, click_on_hover = TRUE, user_space = TRUE) if (!selection) return diff --git a/code/datums/components/cleaner.dm b/code/datums/components/cleaner.dm index 7072f271c7a6a..035f0c054dbaf 100644 --- a/code/datums/components/cleaner.dm +++ b/code/datums/components/cleaner.dm @@ -96,8 +96,8 @@ ADD_TRAIT(target, TRAIT_CURRENTLY_CLEANING, REF(src)) // We need to update our planes on overlay changes RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(cleaning_target_moved)) - var/mutable_appearance/low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, target, GAME_PLANE) - var/mutable_appearance/high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, target, ABOVE_GAME_PLANE) + var/mutable_appearance/low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, target, GAME_PLANE) + var/mutable_appearance/high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, target, ABOVE_GAME_PLANE) var/list/icon_offsets = target.get_oversized_icon_offsets() low_bubble.pixel_x = icon_offsets["x"] low_bubble.pixel_y = icon_offsets["y"] @@ -140,13 +140,13 @@ if(same_z_layer) return // First, get rid of the old overlay - var/mutable_appearance/old_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, old_turf, GAME_PLANE) - var/mutable_appearance/old_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, old_turf, ABOVE_GAME_PLANE) + var/mutable_appearance/old_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, old_turf, GAME_PLANE) + var/mutable_appearance/old_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, old_turf, ABOVE_GAME_PLANE) source.cut_overlay(old_low_bubble) source.cut_overlay(old_high_bubble) // Now, add the new one - var/mutable_appearance/new_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, new_turf, GAME_PLANE) - var/mutable_appearance/new_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", GAME_CLEAN_LAYER, new_turf, ABOVE_GAME_PLANE) + var/mutable_appearance/new_low_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, new_turf, GAME_PLANE) + var/mutable_appearance/new_high_bubble = mutable_appearance('icons/effects/effects.dmi', "bubbles", CLEANABLE_OBJECT_LAYER, new_turf, ABOVE_GAME_PLANE) source.add_overlay(new_low_bubble) source.add_overlay(new_high_bubble) diff --git a/code/datums/components/combustible_flooder.dm b/code/datums/components/combustible_flooder.dm index 07df03671c16c..c24fae55b843b 100644 --- a/code/datums/components/combustible_flooder.dm +++ b/code/datums/components/combustible_flooder.dm @@ -57,7 +57,11 @@ message_admins(admin_message) if(delete_parent && !QDELETED(parent)) - qdel(parent) // For things with the explodable component like plasma mats this isn't necessary, but there's no harm. + if(isobj(parent)) + var/obj/obj_parent = parent + obj_parent.deconstruct(disassembled = FALSE) + else + qdel(parent) // For things with the explodable component like plasma mats this isn't necessary, but there's no harm. qdel(src) /// fire_act reaction. diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index ca8c8fdc62f3e..04a53ce3a3aa6 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -1,16 +1,23 @@ -/datum/component/personal_crafting/Initialize() +/datum/component/personal_crafting + /// Custom screen_loc for our element + var/screen_loc_override + +/datum/component/personal_crafting/Initialize(screen_loc_override) + src.screen_loc_override = screen_loc_override if(ismob(parent)) RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(create_mob_button)) -/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL) +/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/user_client) SIGNAL_HANDLER - var/datum/hud/H = user.hud_used - var/atom/movable/screen/craft/C = new() - C.icon = H.ui_style - H.static_inventory += C - CL.screen += C - RegisterSignal(C, COMSIG_SCREEN_ELEMENT_CLICK, PROC_REF(component_ui_interact)) + var/datum/hud/hud = user.hud_used + var/atom/movable/screen/craft/craft_ui = new() + craft_ui.icon = hud.ui_style + if (screen_loc_override) + craft_ui.screen_loc = screen_loc_override + hud.static_inventory += craft_ui + user_client.screen += craft_ui + RegisterSignal(craft_ui, COMSIG_SCREEN_ELEMENT_CLICK, PROC_REF(component_ui_interact)) #define COOKING TRUE #define CRAFTING FALSE diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm index 9dc8db3bbc4f1..db77f865fd7ae 100644 --- a/code/datums/components/explodable.dm +++ b/code/datums/components/explodable.dm @@ -147,18 +147,20 @@ return // If we don't do this and this doesn't delete it can lock the MC into only processing Input, Timers, and Explosions. var/atom/bomb = parent - var/log = TRUE - if(light_impact_range < 1) - log = FALSE + var/do_log = light_impact_range >= 1 exploding = TRUE - explosion(bomb, devastation_range, heavy_impact_range, light_impact_range, flame_range, flash_range, log, uncapped) //epic explosion time + explosion(bomb, devastation_range, heavy_impact_range, light_impact_range, flame_range, flash_range, do_log, uncapped) //epic explosion time switch(delete_after) if(EXPLODABLE_DELETE_SELF) qdel(src) if(EXPLODABLE_DELETE_PARENT) - qdel(bomb) + if(isobj(bomb)) + var/obj/obj_bomb = bomb + obj_bomb.deconstruct(disassembled = FALSE) + else + qdel(bomb) else addtimer(CALLBACK(src, PROC_REF(reset_exploding)), 0.1 SECONDS) diff --git a/code/datums/components/food_storage.dm b/code/datums/components/food_storage.dm index 843f611e5ff3e..32fb27c7b729a 100644 --- a/code/datums/components/food_storage.dm +++ b/code/datums/components/food_storage.dm @@ -4,7 +4,7 @@ /datum/component/food_storage /// Reference to what we have in our food. - var/obj/item/stored_item + VAR_FINAL/obj/item/stored_item /// The amount of volume the food has on creation - Used for probabilities var/initial_volume = 10 /// Minimum size items that can be inserted @@ -13,16 +13,14 @@ var/bad_chance_of_discovery = 0 /// What are the odds we see the stored item before we bite it? var/good_chance_of_discovery = 100 - /// The stored item was found out somehow. - var/discovered = FALSE /datum/component/food_storage/Initialize(_minimum_weight_class = WEIGHT_CLASS_SMALL, _bad_chance = 0, _good_chance = 100) RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(try_inserting_item)) + RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) RegisterSignal(parent, COMSIG_CLICK_CTRL, PROC_REF(try_removing_item)) RegisterSignal(parent, COMSIG_FOOD_EATEN, PROC_REF(consume_food_storage)) - RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) - + RegisterSignals(parent, list(COMSIG_FOOD_CONSUMED, COMSIG_OBJ_DECONSTRUCT), PROC_REF(storage_consumed)) var/atom/food = parent initial_volume = food.reagents.total_volume @@ -33,12 +31,19 @@ food.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1 -/datum/component/food_storage/Destroy(force) - if(stored_item) - stored_item.forceMove(stored_item.drop_location()) - stored_item.dropped() - stored_item = null - . = ..() +/datum/component/food_storage/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, + COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, + COMSIG_CLICK_CTRL, + COMSIG_FOOD_CONSUMED, + COMSIG_FOOD_EATEN, + COMSIG_OBJ_DECONSTRUCT, + )) + if(QDELING(parent) || QDELETED(stored_item)) + return + stored_item.forceMove(stored_item.drop_location()) + stored_item = null /** Begins the process of inserted an item. * @@ -60,19 +65,17 @@ return NONE if(inserted_item.w_class > minimum_weight_class) - to_chat(user, span_warning("\The [inserted_item.name] won't fit in \the [parent].")) + to_chat(user, span_warning("[inserted_item] won't fit in [parent].")) return ITEM_INTERACT_BLOCKING if(!QDELETED(stored_item)) - to_chat(user, span_warning("There's something in \the [parent].")) - return ITEM_INTERACT_BLOCKING - - if(HAS_TRAIT(inserted_item, TRAIT_NODROP)) - to_chat(user, span_warning("\the [inserted_item] is stuck to your hand, you can't put into \the [parent]!")) + to_chat(user, span_warning("There's something in [parent].")) return ITEM_INTERACT_BLOCKING - user.visible_message(span_notice("[user.name] begins inserting [inserted_item.name] into \the [parent]."), \ - span_notice("You start to insert the [inserted_item.name] into \the [parent].")) + user.visible_message( + span_notice("[user] begins inserting [inserted_item] into [parent]."), + span_notice("You start to insert the [inserted_item] into [parent]."), + ) INVOKE_ASYNC(src, PROC_REF(insert_item), inserted_item, user) return ITEM_INTERACT_SUCCESS @@ -89,14 +92,11 @@ var/atom/food = parent - if(QDELETED(stored_item)) - return CLICK_ACTION_BLOCKING - if(!food.can_interact(user)) return CLICK_ACTION_BLOCKING - user.visible_message(span_notice("[user.name] begins tearing at \the [parent]."), \ - span_notice("You start to rip into \the [parent].")) + user.visible_message(span_notice("[user] begins tearing at [parent]."), \ + span_notice("You start to rip into [parent].")) INVOKE_ASYNC(src, PROC_REF(begin_remove_item), user) return CLICK_ACTION_SUCCESS @@ -110,9 +110,12 @@ /datum/component/food_storage/proc/insert_item(obj/item/inserted_item, mob/user) if(!do_after(user, 1.5 SECONDS, target = parent)) return + if(!user.temporarilyRemoveItemFromInventory(inserted_item)) + to_chat(user, span_warning("You can't seem to insert [inserted_item] into [parent].")) + return var/atom/food = parent - to_chat(user, span_notice("You slip [inserted_item.name] inside \the [parent].")) + to_chat(user, span_notice("You slip [inserted_item] inside [parent].")) inserted_item.forceMove(food) user.log_message("inserted [inserted_item] into [parent].", LOG_ATTACK) food.add_fingerprint(user) @@ -126,19 +129,22 @@ * user - person removing the item. */ /datum/component/food_storage/proc/begin_remove_item(mob/user) - if(do_after(user, 10 SECONDS, target = parent)) - remove_item(user) + if(!do_after(user, 10 SECONDS, target = parent)) + return + if(QDELETED(stored_item)) + to_chat(user, span_warning("There's nothing in [parent].")) + return + remove_item(user) /** * Removes the stored item, putting it in user's hands or on the ground, then updates the reference. */ /datum/component/food_storage/proc/remove_item(mob/user) if(user.put_in_hands(stored_item)) - user.visible_message(span_warning("[user.name] slowly pulls [stored_item.name] out of \the [parent]."), \ - span_warning("You slowly pull [stored_item.name] out of \the [parent].")) + user.visible_message(span_warning("[user] slowly pulls [stored_item] out of [parent]."), \ + span_warning("You slowly pull [stored_item] out of [parent].")) else - stored_item.dropped() - stored_item.visible_message(span_warning("[stored_item.name] falls out of \the [parent].")) + stored_item.visible_message(span_warning("[stored_item] falls out of [parent].")) update_stored_item() @@ -167,9 +173,10 @@ /// Chance of finding the held item = bad chance - 50 good_chance_of_discovery = bad_chance_of_discovery - 50 + var/discovered = FALSE if(prob(good_chance_of_discovery)) //finding the item, without biting it discovered = TRUE - to_chat(target, span_warning("It feels like there's something in \the [parent]...!")) + to_chat(target, span_warning("It feels like there's something in [parent]...!")) else if(prob(bad_chance_of_discovery)) //finding the item, BY biting it user.log_message("just fed [key_name(target)] \a [stored_item] which was hidden in [parent].", LOG_ATTACK) @@ -179,6 +186,14 @@ if(!QDELETED(stored_item) && discovered) INVOKE_ASYNC(src, PROC_REF(remove_item), user) +/// When fully consumed, just drop the item out on the ground. +/datum/component/food_storage/proc/storage_consumed(datum/source, mob/living/target, mob/living/user) + SIGNAL_HANDLER + if(QDELETED(stored_item)) + return + stored_item.forceMove(stored_item.drop_location()) + stored_item = null + /** Updates the reference of the stored item. * * Checks the food's contents for if an alternate item was placed into the food. diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm index 1a44da7eb6d8b..5498a8a81ef85 100644 --- a/code/datums/components/jetpack.dm +++ b/code/datums/components/jetpack.dm @@ -110,6 +110,7 @@ RegisterSignal(user, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_move_react)) RegisterSignal(user, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move)) RegisterSignal(user, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff)) + RegisterSignal(user, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT, PROC_REF(on_input_block)) last_stabilization_tick = world.time START_PROCESSING(SSnewtonian_movement, src) if (effect_type) @@ -118,7 +119,7 @@ /datum/component/jetpack/proc/deactivate(datum/source, mob/old_user) SIGNAL_HANDLER - UnregisterSignal(old_user, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE)) + UnregisterSignal(old_user, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT)) STOP_PROCESSING(SSnewtonian_movement, src) user = null @@ -158,12 +159,23 @@ last_stabilization_tick = world.time - if (!should_trigger(user) || !stabilize || isnull(user.drift_handler)) + if (!should_trigger(user) || !stabilize || !check_on_move.Invoke(FALSE) || isnull(user.drift_handler)) return var/max_drift_force = MOVE_DELAY_TO_DRIFT(user.cached_multiplicative_slowdown) user.drift_handler.stabilize_drift(user.client.intended_direction ? dir2angle(user.client.intended_direction) : null, user.client.intended_direction ? max_drift_force : 0, stabilization_force * (seconds_per_tick * 1 SECONDS)) +/datum/component/jetpack/proc/on_input_block(mob/source) + SIGNAL_HANDLER + + if (!should_trigger(source)) + return + + if (!check_on_move.Invoke(TRUE)) + return + + return DRIFT_ALLOW_INPUT + /datum/component/jetpack/proc/on_client_move(mob/source, list/move_args) SIGNAL_HANDLER @@ -179,11 +191,10 @@ var/max_drift_force = MOVE_DELAY_TO_DRIFT(source.cached_multiplicative_slowdown) var/applied_force = drift_force var/move_dir = source.client.intended_direction - // We're not moving anywhere, try to see if we can simulate pushing off a wall - if (isnull(source.drift_handler)) - var/atom/movable/backup = source.get_spacemove_backup(move_dir, FALSE) - if (backup && !(backup.dir & move_dir)) - applied_force = max_drift_force + // Try to see if we can simulate pushing off a wall + var/atom/movable/backup = source.get_spacemove_backup(move_dir, FALSE, include_floors = TRUE) + if (backup && !(backup.dir & move_dir)) + applied_force = max_drift_force // We don't want to force the loop to fire before stabilizing if we're going to, otherwise its effects will be delayed until the next tick which is jank var/force_stabilize = FALSE diff --git a/code/datums/components/leanable.dm b/code/datums/components/leanable.dm index b95fd734ad043..b823cf5ec503c 100644 --- a/code/datums/components/leanable.dm +++ b/code/datums/components/leanable.dm @@ -2,26 +2,21 @@ /datum/component/leanable /// How much will mobs that lean onto this object be offset var/leaning_offset = 11 - /// List of click modifiers that are required to be present for leaning to trigger - var/list/click_mods = null - /// Callback called for additional checks if a lean is valid - var/datum/callback/lean_check = null - /// Whenever this object can be leaned on from the same turf as its' own. Do not use without a custom lean_check! - var/same_turf = FALSE /// List of mobs currently leaning on our parent var/list/leaning_mobs = list() -/datum/component/leanable/Initialize(leaning_offset = 11, list/click_mods = null, datum/callback/lean_check = null, same_turf = FALSE) +/datum/component/leanable/Initialize(mob/living/leaner, leaning_offset = 11) . = ..() src.leaning_offset = leaning_offset - src.click_mods = click_mods - src.lean_check = lean_check - src.same_turf = same_turf + mousedrop_receive(parent, leaner, leaner) /datum/component/leanable/RegisterWithParent() RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) +/datum/component/leanable/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOUSEDROPPED_ONTO, COMSIG_MOVABLE_MOVED)) + /datum/component/leanable/Destroy(force) for (var/mob/living/leaner as anything in leaning_mobs) leaner.stop_leaning() @@ -30,17 +25,13 @@ /datum/component/leanable/proc/on_moved(datum/source) SIGNAL_HANDLER + for (var/mob/living/leaner as anything in leaning_mobs) leaner.stop_leaning() /datum/component/leanable/proc/mousedrop_receive(atom/source, atom/movable/dropped, mob/user, params) if (dropped != user) return - if (islist(click_mods)) - var/list/modifiers = params2list(params) - for (var/modifier in click_mods) - if (!LAZYACCESS(modifiers, modifier)) - return if (!iscarbon(dropped) && !iscyborg(dropped)) return var/mob/living/leaner = dropped @@ -49,9 +40,7 @@ if (HAS_TRAIT_FROM(leaner, TRAIT_UNDENSE, LEANING_TRAIT)) return var/turf/checked_turf = get_step(leaner, REVERSE_DIR(leaner.dir)) - if (checked_turf != get_turf(source) && (!same_turf || get_turf(source) != get_turf(leaner))) - return - if (!isnull(lean_check) && !lean_check.Invoke(dropped, params)) + if (checked_turf != get_turf(source)) return leaner.start_leaning(source, leaning_offset) leaning_mobs += leaner @@ -63,6 +52,13 @@ leaning_mobs -= source UnregisterSignal(source, list(COMSIG_LIVING_STOPPED_LEANING, COMSIG_QDELETING)) +/** + * Makes the mob lean on an atom + * Arguments + * + * * atom/lean_target - the target the mob is trying to lean on + * * leaning_offset - pixel offset to apply on the mob when leaning + */ /mob/living/proc/start_leaning(atom/lean_target, leaning_offset) var/new_x = lean_target.pixel_x + base_pixel_x + body_position_pixel_x_offset var/new_y = lean_target.pixel_y + base_pixel_y + body_position_pixel_y_offset @@ -95,6 +91,7 @@ /// 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 @@ -103,6 +100,7 @@ /mob/living/proc/stop_leaning() SIGNAL_HANDLER + UnregisterSignal(src, list( COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_LIVING_DISARM_HIT, @@ -117,5 +115,6 @@ /mob/living/proc/lean_dir_changed(atom/source, old_dir, new_dir) SIGNAL_HANDLER + if (old_dir != new_dir) INVOKE_ASYNC(src, PROC_REF(stop_leaning)) diff --git a/code/datums/components/mutant_hands.dm b/code/datums/components/mutant_hands.dm index 66e19852ae7e4..b3478d41f311b 100644 --- a/code/datums/components/mutant_hands.dm +++ b/code/datums/components/mutant_hands.dm @@ -147,6 +147,7 @@ icon = 'icons/effects/blood.dmi' icon_state = "bloodhand_left" base_icon_state = "bloodhand" + icon_angle = 90 item_flags = ABSTRACT | DROPDEL | HAND_ITEM resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index 9a42c485d5c06..143ac9ca1014c 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -3,11 +3,11 @@ * Watch for someone throwing or pointing at something and then go get it and bring it back. * If it's food we might eat it instead. */ -/datum/pet_command/point_targeting/fetch +/datum/pet_command/fetch command_name = "Fetch" command_desc = "Command your pet to retrieve something you throw or point at." - radial_icon = 'icons/mob/actions/actions_spells.dmi' - radial_icon_state = "summons" + radial_icon_state = "fetch" + requires_pointing = TRUE speech_commands = list("fetch") command_feedback = "bounces" pointed_reaction = "with great interest" @@ -16,22 +16,25 @@ /// If true, this is a poorly trained pet who will eat food you throw instead of bringing it back var/will_eat_targets = TRUE -/datum/pet_command/point_targeting/fetch/New(mob/living/parent) +/datum/pet_command/fetch/New(mob/living/parent) . = ..() if(isnull(parent)) return parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something -/datum/pet_command/point_targeting/fetch/add_new_friend(mob/living/tamer) +/datum/pet_command/fetch/add_new_friend(mob/living/tamer) . = ..() RegisterSignal(tamer, COMSIG_MOB_THROW, PROC_REF(listened_throw)) -/datum/pet_command/point_targeting/fetch/remove_friend(mob/living/unfriended) +/datum/pet_command/fetch/remove_friend(mob/living/unfriended) . = ..() UnregisterSignal(unfriended, COMSIG_MOB_THROW) +/datum/pet_command/fetch/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to fetch [target]!" + /// A friend has thrown something, if we're listening or at least not busy then go get it -/datum/pet_command/point_targeting/fetch/proc/listened_throw(mob/living/carbon/thrower) +/datum/pet_command/fetch/proc/listened_throw(mob/living/carbon/thrower) SIGNAL_HANDLER var/mob/living/parent = weak_parent.resolve() @@ -57,7 +60,7 @@ RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land)) /// A throw we were listening to has finished, see if it's in range for us to try grabbing it -/datum/pet_command/point_targeting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwingdatum) +/datum/pet_command/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwingdatum) SIGNAL_HANDLER UnregisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED) @@ -76,7 +79,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, thrower) // Don't try and fetch turfs or anchored objects if someone points at them -/datum/pet_command/point_targeting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) +/datum/pet_command/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) if (!istype(pointed_atom)) return FALSE if (pointed_atom.anchored) @@ -89,7 +92,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend) // Finally, plan our actions -/datum/pet_command/point_targeting/fetch/execute_action(datum/ai_controller/controller) +/datum/pet_command/fetch/execute_action(datum/ai_controller/controller) controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches) var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] diff --git a/code/datums/components/pet_commands/obeys_commands.dm b/code/datums/components/pet_commands/obeys_commands.dm index 8aaa7e7179294..4a68574d6e08c 100644 --- a/code/datums/components/pet_commands/obeys_commands.dm +++ b/code/datums/components/pet_commands/obeys_commands.dm @@ -3,12 +3,23 @@ * Manages a list of pet command datums, allowing you to boss it around * Creates a radial menu of pet commands when this creature is alt-clicked, if it has any */ +#define DEFAULT_RADIAL_VIEWING_DISTANCE 9 /datum/component/obeys_commands /// List of commands you can give to the owner of this component var/list/available_commands = list() + ///Users currently viewing our radial options + var/list/radial_viewers = list() + ///radius of our radial menu + var/radial_menu_radius = 48 + ///after how long we shutdown radial menus + var/radial_menu_lifetime = 7 SECONDS + ///offset to display the radial menu + var/list/radial_menu_offset + ///should the commands move with the pet owner's screen? + var/radial_relative_to_user /// The available_commands parameter should be passed as a list of typepaths -/datum/component/obeys_commands/Initialize(list/command_typepaths = list()) +/datum/component/obeys_commands/Initialize(list/command_typepaths = list(), list/radial_menu_offset = list(0, 0), radial_relative_to_user = FALSE) . = ..() if (!isliving(parent)) return COMPONENT_INCOMPATIBLE @@ -17,7 +28,8 @@ return COMPONENT_INCOMPATIBLE if (!length(command_typepaths)) CRASH("Initialised obedience component with no commands.") - + src.radial_menu_offset = radial_menu_offset + src.radial_relative_to_user = radial_relative_to_user for (var/command_path in command_typepaths) var/datum/pet_command/new_command = new command_path(parent) available_commands[new_command.command_name] = new_command @@ -30,7 +42,6 @@ RegisterSignal(parent, COMSIG_LIVING_BEFRIENDED, PROC_REF(add_friend)) RegisterSignal(parent, COMSIG_LIVING_UNFRIENDED, PROC_REF(remove_friend)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(display_menu)) /datum/component/obeys_commands/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_LIVING_UNFRIENDED, COMSIG_ATOM_EXAMINE, COMSIG_CLICK_ALT)) @@ -38,15 +49,26 @@ /// Add someone to our friends list /datum/component/obeys_commands/proc/add_friend(datum/source, mob/living/new_friend) SIGNAL_HANDLER - + RegisterSignal(new_friend, COMSIG_KB_LIVING_VIEW_PET_COMMANDS, PROC_REF(on_key_pressed)) + RegisterSignal(new_friend, DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS), PROC_REF(on_key_unpressed)) for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, add_new_friend), new_friend) +/datum/component/obeys_commands/proc/on_key_unpressed(mob/living/source) + SIGNAL_HANDLER + UnregisterSignal(source, COMSIG_ATOM_MOUSE_ENTERED) + +/datum/component/obeys_commands/proc/remove_from_viewers(mob/living/source) + radial_viewers -= REF(source) + /// Remove someone from our friends list /datum/component/obeys_commands/proc/remove_friend(datum/source, mob/living/old_friend) SIGNAL_HANDLER - + UnregisterSignal(old_friend, list( + COMSIG_KB_LIVING_VIEW_PET_COMMANDS, + DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS), + )) for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, remove_friend), old_friend) @@ -61,21 +83,34 @@ return examine_list += span_notice("[source.p_They()] seem[source.p_s()] happy to see you!") -/// Displays a radial menu of commands -/datum/component/obeys_commands/proc/display_menu(datum/source, mob/living/clicker) +/datum/component/obeys_commands/proc/on_key_pressed(mob/living/friend) + SIGNAL_HANDLER + RegisterSignal(friend, COMSIG_ATOM_MOUSE_ENTERED, PROC_REF(on_mouse_hover)) + +/datum/component/obeys_commands/proc/on_mouse_hover(mob/living/friend, atom/mouse_hovered) SIGNAL_HANDLER + if(mouse_hovered == parent) + display_menu(friend) + return + if(isliving(mouse_hovered)) + remove_from_viewers(friend) + +/// Displays a radial menu of commands +/datum/component/obeys_commands/proc/display_menu(mob/living/friend) var/mob/living/living_parent = parent - if (IS_DEAD_OR_INCAP(living_parent) || !clicker.can_perform_action(living_parent)) + if (IS_DEAD_OR_INCAP(living_parent) || friend.stat != CONSCIOUS) return - if (!(clicker in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST])) + if (!(friend in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST])) return // Not our friend, can't boss us around - - INVOKE_ASYNC(src, PROC_REF(display_radial_menu), clicker) - return CLICK_ACTION_SUCCESS + if(radial_viewers[REF(friend)]) + return + if(!can_see(friend, parent, DEFAULT_RADIAL_VIEWING_DISTANCE)) + return + INVOKE_ASYNC(src, PROC_REF(display_radial_menu), friend) /// Actually display the radial menu and then do something with the result -/datum/component/obeys_commands/proc/display_radial_menu(mob/living/clicker) +/datum/component/obeys_commands/proc/display_radial_menu(mob/living/friend) var/list/radial_options = list() for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] @@ -83,9 +118,22 @@ if (!choice) continue radial_options += choice - - var/pick = show_radial_menu(clicker, clicker, radial_options, tooltips = TRUE) - if (!pick) + radial_viewers[REF(friend)] = world.time + radial_menu_lifetime + var/pick = show_radial_menu(friend, parent, radial_options, radius = radial_menu_radius, button_animation_flags = BUTTON_FADE_IN | BUTTON_FADE_OUT, custom_check = CALLBACK(src, PROC_REF(check_menu_viewer), friend), check_delay = 0.15 SECONDS, display_close_button = FALSE, radial_menu_offset = radial_menu_offset, user_space = radial_relative_to_user) + remove_from_viewers(friend) + if(!pick) return var/datum/pet_command/picked_command = available_commands[pick] - picked_command.try_activate_command(clicker) + picked_command.try_activate_command(friend, radial_command = TRUE) + +/datum/component/obeys_commands/proc/check_menu_viewer(mob/living/user) + if(QDELETED(user) || !radial_viewers[REF(user)]) + return FALSE + if(world.time > radial_viewers[REF(user)]) + return FALSE + var/viewing_distance = DEFAULT_RADIAL_VIEWING_DISTANCE + if(!can_see(user, parent, viewing_distance)) + return FALSE + return TRUE + +#undef DEFAULT_RADIAL_VIEWING_DISTANCE diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index b9ada63bc897c..97dfbc8b5cb18 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -13,7 +13,7 @@ /// If true, command will not appear in radial menu and can only be accessed through speech var/hidden = FALSE /// Icon to display in radial menu - var/icon/radial_icon + var/icon/radial_icon = 'icons/hud/radial_pets.dmi' /// Icon state to display in radial menu var/radial_icon_state /// Speech strings to listen out for @@ -24,6 +24,12 @@ var/command_feedback /// How close a mob needs to be to a target to respond to a command var/sense_radius = 7 + /// does this pet command need a point to activate? + var/requires_pointing = FALSE + /// Blackboard key for targeting strategy, this is likely going to need it + var/targeting_strategy_key = BB_PET_TARGETING_STRATEGY + ///our pointed reaction we play + var/pointed_reaction /datum/pet_command/New(mob/living/parent) . = ..() @@ -34,10 +40,17 @@ RegisterSignal(tamer, COMSIG_MOB_SAY, PROC_REF(respond_to_command)) RegisterSignal(tamer, COMSIG_MOB_AUTOMUTE_CHECK, PROC_REF(waive_automute)) RegisterSignal(tamer, COMSIG_MOB_CREATED_CALLOUT, PROC_REF(respond_to_callout)) + if(requires_pointing) + RegisterSignal(tamer, COMSIG_MOVABLE_POINTED, PROC_REF(point_on_target)) /// Stop listening to a guy /datum/pet_command/proc/remove_friend(mob/living/unfriended) - UnregisterSignal(unfriended, list(COMSIG_MOB_SAY, COMSIG_MOB_AUTOMUTE_CHECK, COMSIG_MOB_CREATED_CALLOUT)) + UnregisterSignal(unfriended, list( + COMSIG_MOB_SAY, + COMSIG_MOB_AUTOMUTE_CHECK, + COMSIG_MOB_CREATED_CALLOUT, + COMSIG_MOVABLE_POINTED, + )) /// Stop the automute from triggering for commands (unless the spoken text is suspiciously longer than the command) /datum/pet_command/proc/waive_automute(mob/living/speaker, client/client, last_message, mute_type) @@ -49,7 +62,6 @@ /// Respond to something that one of our friends has asked us to do /datum/pet_command/proc/respond_to_command(mob/living/speaker, speech_args) SIGNAL_HANDLER - var/mob/living/parent = weak_parent.resolve() if (!parent) return @@ -60,7 +72,7 @@ if (!find_command_in_text(spoken_text)) return - try_activate_command(speaker) + try_activate_command(commander = speaker, radial_command = FALSE) /// Respond to a callout /datum/pet_command/proc/respond_to_callout(mob/living/speaker, datum/callout_option/callout, atom/target) @@ -83,7 +95,7 @@ if (!found_new_target) return - if (try_activate_command(speaker)) + if (try_activate_command(commander = speaker, radial_command = FALSE)) look_for_target(parent, target) /// Does this callout with this target trigger this command? @@ -103,48 +115,77 @@ return TRUE return FALSE -/// Apply a command state if conditions are right, return command if successful -/datum/pet_command/proc/try_activate_command(mob/living/commander) +/datum/pet_command/proc/pet_able_to_respond() var/mob/living/parent = weak_parent.resolve() - if (!parent) + if(isnull(parent) || isnull(parent.ai_controller)) return FALSE - if (!parent.ai_controller) // We stopped having a brain at some point + if(IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead return FALSE - if (IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead - return FALSE - if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == src) // We're already doing it + return TRUE + +/// Apply a command state if conditions are right, return command if successful +/datum/pet_command/proc/try_activate_command(mob/living/commander, radial_command) + if(!pet_able_to_respond()) return FALSE - set_command_active(parent, commander) + var/mob/living/parent = weak_parent.resolve() + set_command_active(parent, commander, radial_command) return TRUE +/datum/pet_command/proc/generate_emote_command(atom/target) + var/mob/living/living_pet = weak_parent?.resolve() + return isnull(living_pet) ? null : retrieve_command_text(living_pet, target) + +/datum/pet_command/proc/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to spring into action!" + /// Target the pointed atom for actions -/datum/pet_command/proc/look_for_target(mob/living/friend, atom/pointed_atom) +/datum/pet_command/proc/look_for_target(mob/living/friend, atom/potential_target) var/mob/living/parent = weak_parent.resolve() - if (!parent) - return FALSE - if (!parent.ai_controller) - return FALSE - if (IS_DEAD_OR_INCAP(parent)) + if(!pet_able_to_respond()) return FALSE - if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != src) // We're not listening right now + if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == potential_target) // That's already our target return FALSE - if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == pointed_atom) // That's already our target - return FALSE - if (!can_see(parent, pointed_atom, sense_radius)) + if (!can_see(parent, potential_target, sense_radius)) return FALSE parent.ai_controller.CancelActions() - set_command_target(parent, pointed_atom) + set_command_target(parent, potential_target) return TRUE /// Activate the command, extend to add visible messages and the like -/datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander) - set_command_target(parent, null) +/datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander, radial_command = FALSE) + parent.ai_controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead parent.ai_controller.set_blackboard_key(BB_ACTIVE_PET_COMMAND, src) if (command_feedback) parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this + if(!radial_command) + return + if(!requires_pointing) + var/manual_emote_text = generate_emote_command() + commander.manual_emote(manual_emote_text) + return + RegisterSignal(commander, COMSIG_MOB_CLICKON, PROC_REF(click_on_target)) + commander.client?.mouse_override_icon = 'icons/effects/mouse_pointers/pet_paw.dmi' + commander.update_mouse_pointer() + +/datum/pet_command/proc/click_on_target(mob/living/source, atom/target, list/modifiers) + SIGNAL_HANDLER + if(!can_see(source, target, 9)) + return COMSIG_MOB_CANCEL_CLICKON + on_target_set(source, target) + UnregisterSignal(source, COMSIG_MOB_CLICKON) + source.client?.mouse_override_icon = source.client::mouse_override_icon + source.update_mouse_pointer() + var/manual_emote_text = generate_emote_command(target) + if(!isnull(manual_emote_text)) + INVOKE_ASYNC(source, TYPE_PROC_REF(/atom, manual_emote), manual_emote_text) + return COMSIG_MOB_CANCEL_CLICKON + +/datum/pet_command/proc/point_on_target(mob/living/friend, atom/potential_target) + SIGNAL_HANDLER + on_target_set(friend, potential_target) /// Store the target for the AI blackboard /datum/pet_command/proc/set_command_target(mob/living/parent, atom/target) @@ -158,11 +199,6 @@ var/datum/radial_menu_choice/choice = new() choice.name = command_name choice.image = icon(icon = radial_icon, icon_state = radial_icon_state) - var/tooltip = command_desc - if (length(speech_commands)) - tooltip += "
Speak this command with the words [speech_commands.Join(", ")]." - choice.info = tooltip - return list("[command_name]" = choice) /** @@ -174,34 +210,15 @@ SHOULD_CALL_PARENT(FALSE) CRASH("Pet command execute action not implemented.") -/** - * # Point Targeting Pet Command - * As above but also listens for you pointing at something and marks it as a target - */ -/datum/pet_command/point_targeting - /// Text describing an action we perform upon receiving a new target - var/pointed_reaction - /// Blackboard key for targeting strategy, this is likely going to need it - var/targeting_strategy_key = BB_PET_TARGETING_STRATEGY - -/datum/pet_command/point_targeting/add_new_friend(mob/living/tamer) - . = ..() - RegisterSignal(tamer, COMSIG_MOVABLE_POINTED, PROC_REF(on_point)) - -/datum/pet_command/point_targeting/remove_friend(mob/living/unfriended) - . = ..() - UnregisterSignal(unfriended, COMSIG_MOVABLE_POINTED) - /// Target the pointed atom for actions -/datum/pet_command/point_targeting/proc/on_point(mob/living/friend, atom/pointed_atom, obj/effect/temp_visual/point/point) - SIGNAL_HANDLER - +/datum/pet_command/proc/on_target_set(mob/living/friend, atom/potential_target) var/mob/living/parent = weak_parent.resolve() if (!parent) - return FALSE + return parent.ai_controller.CancelActions() - if (look_for_target(friend, pointed_atom) && set_command_target(parent, pointed_atom)) - parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom] [pointed_reaction]!")) - return TRUE - return FALSE + if(!look_for_target(friend, potential_target) || !set_command_target(parent, potential_target)) + return + parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [potential_target] [pointed_reaction]!")) + + diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index b5372f9492a63..1ac49ce9a9c05 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -7,14 +7,16 @@ /datum/pet_command/idle command_name = "Stay" command_desc = "Command your pet to stay idle in this location." - radial_icon = 'icons/obj/bed.dmi' - radial_icon_state = "dogbed" + radial_icon_state = "halt" speech_commands = list("sit", "stay", "stop") command_feedback = "sits" /datum/pet_command/idle/execute_action(datum/ai_controller/controller) return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning +/datum/pet_command/idle/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to stay idle!" + /** * # Pet Command: Stop * Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you @@ -22,8 +24,7 @@ /datum/pet_command/free command_name = "Loose" command_desc = "Allow your pet to resume its natural behaviours." - radial_icon = 'icons/mob/actions/actions_spells.dmi' - radial_icon_state = "repulse" + radial_icon_state = "free" speech_commands = list("free", "loose") command_feedback = "relaxes" @@ -31,6 +32,9 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return // Just move on to the next planning subtree. +/datum/pet_command/free/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to go free!" + /** * # Pet Command: Follow * Tells a pet to follow you until you tell it to do something else @@ -38,8 +42,7 @@ /datum/pet_command/follow command_name = "Follow" command_desc = "Command your pet to accompany you." - radial_icon = 'icons/testing/turf_analysis.dmi' - radial_icon_state = "red_arrow" + radial_icon_state = "follow" speech_commands = list("heel", "follow") callout_type = /datum/callout_option/move ///the behavior we use to follow @@ -49,6 +52,9 @@ . = ..() set_command_target(parent, commander) +/datum/pet_command/follow/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to follow!" + /datum/pet_command/follow/execute_action(datum/ai_controller/controller) controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING @@ -60,14 +66,16 @@ /datum/pet_command/play_dead command_name = "Play Dead" command_desc = "Play a macabre trick." - radial_icon = 'icons/mob/simple/pets.dmi' - radial_icon_state = "puppy_dead" + radial_icon_state = "play_dead" speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often /datum/pet_command/play_dead/execute_action(datum/ai_controller/controller) controller.queue_behavior(/datum/ai_behavior/play_dead) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/play_dead/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to play dead!" + /** * # Pet Command: Good Boy * React if complimented @@ -115,16 +123,18 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/untargeted_ability/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to use an ability!" + /** * # Pet Command: Attack * Tells a pet to chase and bite the next thing you point at */ -/datum/pet_command/point_targeting/attack +/datum/pet_command/attack command_name = "Attack" command_desc = "Command your pet to attack things that you point out to it." - radial_icon = 'icons/effects/effects.dmi' - radial_icon_state = "bite" - + radial_icon_state = "attack" + requires_pointing = TRUE callout_type = /datum/callout_option/attack speech_commands = list("attack", "sic", "kill") command_feedback = "growl" @@ -135,7 +145,7 @@ var/attack_behaviour = /datum/ai_behavior/basic_melee_attack // Refuse to target things we can't target, chiefly other friends -/datum/pet_command/point_targeting/attack/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/attack/set_command_target(mob/living/parent, atom/target) if (!target) return var/mob/living/living_parent = parent @@ -149,28 +159,32 @@ return return ..() +/datum/pet_command/attack/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to attack [target]!" + /// Display feedback about not targeting something -/datum/pet_command/point_targeting/attack/proc/refuse_target(mob/living/parent, atom/target) +/datum/pet_command/attack/proc/refuse_target(mob/living/parent, atom/target) var/mob/living/living_parent = parent living_parent.balloon_alert_to_viewers("[refuse_reaction]") living_parent.visible_message(span_notice("[living_parent] refuses to attack [target].")) -/datum/pet_command/point_targeting/attack/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/execute_action(datum/ai_controller/controller) controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key) return SUBTREE_RETURN_FINISH_PLANNING /** * # Breed command. breed with a partner! */ -/datum/pet_command/point_targeting/breed +/datum/pet_command/breed command_name = "Breed" command_desc = "Command your pet to attempt to breed with a partner." - radial_icon = 'icons/mob/simple/animal.dmi' - radial_icon_state = "heart" + requires_pointing = TRUE + radial_icon_state = "breed" speech_commands = list("breed", "consummate") + ///the behavior we use to make babies var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies -/datum/pet_command/point_targeting/breed/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/breed/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !isliving(target)) return if(!HAS_TRAIT(parent, TRAIT_MOB_BREEDER) || !HAS_TRAIT(target, TRAIT_MOB_BREEDER)) @@ -184,21 +198,25 @@ return return ..() -/datum/pet_command/point_targeting/breed/execute_action(datum/ai_controller/controller) +/datum/pet_command/breed/execute_action(datum/ai_controller/controller) if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) controller.queue_behavior(reproduce_behavior, BB_CURRENT_PET_TARGET) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/breed/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to breed with [target]!" + /** * # Pet Command: Targetted Ability * Tells a pet to use some kind of ability on the next thing you point at */ -/datum/pet_command/point_targeting/use_ability +/datum/pet_command/use_ability command_name = "Use ability" command_desc = "Command your pet to use one of its special skills on something that you point out to it." radial_icon = 'icons/mob/actions/actions_spells.dmi' radial_icon_state = "projectile" + requires_pointing = TRUE speech_commands = list("shoot", "blast", "cast") command_feedback = "growl" pointed_reaction = "and growls" @@ -207,7 +225,7 @@ /// The AI behavior to use for the ability var/ability_behavior = /datum/ai_behavior/pet_use_ability -/datum/pet_command/point_targeting/use_ability/execute_action(datum/ai_controller/controller) +/datum/pet_command/use_ability/execute_action(datum/ai_controller/controller) if (!pet_ability_key) return var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key] @@ -218,6 +236,9 @@ controller.queue_behavior(ability_behavior, pet_ability_key, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/use_ability/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to use an ability on [target]!" + /datum/pet_command/protect_owner command_name = "Protect owner" command_desc = "Your pet will run to your aid." @@ -278,25 +299,39 @@ /** * # Fish command: command the mob to fish at the next fishing spot you point at. Requires the profound fisher component */ -/datum/pet_command/point_targeting/fish +/datum/pet_command/fish command_name = "Fish" command_desc = "Command your pet to try fishing at a nearby fishing spot." - radial_icon = 'icons/obj/aquarium/fish.dmi' - radial_icon_state = "goldfish" + requires_pointing = TRUE + radial_icon_state = "fish" speech_commands = list("fish") -// Refuse to target things we can't target, chiefly other friends -/datum/pet_command/point_targeting/fish/set_command_target(mob/living/parent, atom/target) - if (!target) - return - if(!parent.ai_controller || !HAS_TRAIT(parent, TRAIT_PROFOUND_FISHER)) - return - var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(/datum/targeting_strategy/fishing) - if (!targeter?.can_attack(parent, target)) - parent.balloon_alert_to_viewers("shakes head!") - return +/datum/pet_command/fish/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, BB_CURRENT_PET_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/fish/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to go fish!" + +/datum/pet_command/move + command_name = "Move" + command_desc = "Command your pet to move to a location!" + requires_pointing = TRUE + radial_icon_state = "move" + speech_commands = list("move", "walk") + ///the behavior we use to walk towards targets + var/datum/ai_behavior/walk_behavior = /datum/ai_behavior/travel_towards + +/datum/pet_command/move/set_command_target(mob/living/parent, atom/target) + if(isnull(target) || !can_see(parent, target, 9)) + return FALSE return ..() -/datum/pet_command/point_targeting/fish/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off, BB_CURRENT_PET_TARGET) +/datum/pet_command/move/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(walk_behavior, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/move/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to move!" diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm index ad0001ca8cbe2..5bf95b747e3cf 100644 --- a/code/datums/components/plumbing/_plumbing.dm +++ b/code/datums/components/plumbing/_plumbing.dm @@ -121,6 +121,8 @@ ///returns TRUE when they can give the specified amount and reagent. called by process request /datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net) + SHOULD_BE_PURE(TRUE) + if(amount <= 0) return diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm index d0aff2f50708c..4f2acf4710a7e 100644 --- a/code/datums/components/plumbing/reaction_chamber.dm +++ b/code/datums/components/plumbing/reaction_chamber.dm @@ -8,20 +8,25 @@ return COMPONENT_INCOMPATIBLE /datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net) - . = ..() var/obj/machinery/plumbing/reaction_chamber/reaction_chamber = parent - if(!. || !reaction_chamber.emptying || reagents.is_reacting) + + //cannot give when we outselves are requesting or reacting the reagents + if(!reaction_chamber.emptying || reagents.is_reacting) return FALSE + return ..() + /datum/component/plumbing/reaction_chamber/send_request(dir) var/obj/machinery/plumbing/reaction_chamber/chamber = parent + if(chamber.emptying) return //take in reagents var/present_amount var/diff - for(var/required_reagent in chamber.required_reagents) + var/list/datum/reagent/required_reagents = chamber.catalysts | chamber.required_reagents + for(var/datum/reagent/required_reagent as anything in required_reagents) //find how much amount is already present if at all and get the reagent reference present_amount = 0 for(var/datum/reagent/present_reagent as anything in reagents.reagent_list) @@ -30,10 +35,11 @@ break //compute how much more is needed - diff = min(chamber.required_reagents[required_reagent] - present_amount, MACHINE_REAGENT_TRANSFER) + diff = min(required_reagents[required_reagent] - present_amount, MACHINE_REAGENT_TRANSFER) if(diff >= CHEMICAL_QUANTISATION_LEVEL) // the closest we can ask for so values like 0.9999 become 1 process_request(diff, required_reagent, dir) - return + if(!chamber.catalysts[required_reagent]) //only block if not a catalyst as they can come in whenever they are available + return reagents.flags &= ~NO_REACT reagents.handle_reactions() diff --git a/code/datums/drift_handler.dm b/code/datums/drift_handler.dm index dc47f28819b59..dcf0771d8a5e8 100644 --- a/code/datums/drift_handler.dm +++ b/code/datums/drift_handler.dm @@ -36,7 +36,6 @@ RegisterSignal(drifting_loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(before_move)) RegisterSignal(drifting_loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(after_move)) RegisterSignal(drifting_loop, COMSIG_QDELETING, PROC_REF(loop_death)) - RegisterSignal(parent, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(attempt_halt)) if(drifting_loop.status & MOVELOOP_STATUS_RUNNING) drifting_start(drifting_loop) // There's a good chance it'll autostart, gotta catch that @@ -208,28 +207,28 @@ if(world.time < block_inputs_until) return COMSIG_MOB_CLIENT_BLOCK_PRE_MOVE -/datum/drift_handler/proc/attempt_halt(mob/source, movement_dir, continuous_move, atom/backup) - SIGNAL_HANDLER - - if ((backup.density || !backup.CanPass(source, get_dir(backup, source))) && (get_dir(source, backup) == movement_dir || source.loc == backup.loc)) +/datum/drift_handler/proc/attempt_halt(movement_dir, continuous_move, atom/backup) + if ((backup.density || !backup.CanPass(parent, get_dir(backup, parent))) && (get_dir(parent, backup) == movement_dir || parent.loc == backup.loc)) if (drift_force >= INERTIA_FORCE_THROW_FLOOR) - source.throw_at(backup, 1, floor(1 + (drift_force - INERTIA_FORCE_THROW_FLOOR) / INERTIA_FORCE_PER_THROW_FORCE), spin = FALSE) - return + parent.throw_at(backup, 1, floor(1 + (drift_force - INERTIA_FORCE_THROW_FLOOR) / INERTIA_FORCE_PER_THROW_FORCE), spin = FALSE) + return FALSE if (drift_force < INERTIA_FORCE_SPACEMOVE_GRAB || isnull(drifting_loop)) - return + return FALSE - if (!isnull(source.client) && source.client.intended_direction) - if ((source.client.intended_direction & movement_dir) && !(get_dir(source, backup) & movement_dir)) - return + if (ismob(parent)) + var/mob/source_user = parent + if (!isnull(source_user.client) && source_user.client.intended_direction) + if ((source_user.client.intended_direction & movement_dir) && !(get_dir(source_user, backup) & movement_dir)) + return FALSE - if (drift_force <= INERTIA_FORCE_SPACEMOVE_REDUCTION / source.inertia_force_weight) - glide_to_halt(get_loop_delay(source)) - return COMPONENT_PREVENT_SPACEMOVE_HALT + if (drift_force <= INERTIA_FORCE_SPACEMOVE_REDUCTION / parent.inertia_force_weight) + glide_to_halt(get_loop_delay(parent)) + return TRUE - drift_force -= INERTIA_FORCE_SPACEMOVE_REDUCTION / source.inertia_force_weight - drifting_loop.set_delay(get_loop_delay(source)) - return COMPONENT_PREVENT_SPACEMOVE_HALT + drift_force -= INERTIA_FORCE_SPACEMOVE_REDUCTION / parent.inertia_force_weight + drifting_loop.set_delay(get_loop_delay(parent)) + return TRUE /datum/drift_handler/proc/get_loop_delay(atom/movable/movable) return (DEFAULT_INERTIA_SPEED / ((1 - INERTIA_SPEED_COEF) + drift_force * INERTIA_SPEED_COEF)) * movable.inertia_move_multiplier diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 75caa272ef9bd..4be983b32113c 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -46,8 +46,10 @@ SIGNAL_HANDLER if(user.combat_mode || !is_type_in_list(possible_food, food_types)) return NONE - - try_eating(source, possible_food, user) + var/mob/living/living_source = source + if(living_source.stat != CONSCIOUS) + return NONE + return try_eating(source, possible_food, user) ? ITEM_INTERACT_SUCCESS : NONE /datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/datums/elements/leeching_walk.dm b/code/datums/elements/leeching_walk.dm index c9f547189e699..f5148b43a5a52 100644 --- a/code/datums/elements/leeching_walk.dm +++ b/code/datums/elements/leeching_walk.dm @@ -24,9 +24,8 @@ var/turf/mover_turf = get_turf(source) if(HAS_TRAIT(mover_turf, TRAIT_RUSTY)) ADD_TRAIT(source, TRAIT_BATON_RESISTANCE, type) - return - - REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) + else + REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) /** * Signal proc for [COMSIG_LIVING_LIFE]. @@ -43,17 +42,18 @@ // Heals all damage + Stamina var/need_mob_update = FALSE - need_mob_update += source.adjustBruteLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustFireLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustToxLoss(-3, updating_health = FALSE, forced = TRUE) // Slimes are people to - need_mob_update += source.adjustOxyLoss(-1.5, updating_health = FALSE) - need_mob_update += source.adjustStaminaLoss(-10, updating_stamina = FALSE) + var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved. + need_mob_update += source.adjustBruteLoss(-3 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustFireLoss(-3 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustToxLoss(-3 * delta_time, updating_health = FALSE, forced = TRUE) // Slimes are people too + need_mob_update += source.adjustOxyLoss(-1.5 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustStaminaLoss(-10 * delta_time, updating_stamina = FALSE) if(need_mob_update) source.updatehealth() // Reduces duration of stuns/etc - source.AdjustAllImmobility(-0.5 SECONDS) + source.AdjustAllImmobility((-0.5 SECONDS) * delta_time) // Heals blood loss if(source.blood_volume < BLOOD_VOLUME_NORMAL) - source.blood_volume += 2.5 * seconds_per_tick + source.blood_volume += 2.5 * delta_time // Slowly regulates your body temp - source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature)/5) + source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature) / 5) diff --git a/code/datums/elements/nav_computer_icon.dm b/code/datums/elements/nav_computer_icon.dm new file mode 100644 index 0000000000000..4e9b6a3a18893 --- /dev/null +++ b/code/datums/elements/nav_computer_icon.dm @@ -0,0 +1,44 @@ +/** + * element for atoms that have helper icons overlayed on their position in the shuttle navigation computer, such as airlocks + */ +/datum/element/nav_computer_icon + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + var/use_icon + var/use_icon_state + var/only_show_on_shuttle_edge + +/datum/element/nav_computer_icon/Attach(datum/target, use_icon, use_icon_state, only_show_on_shuttle_edge) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + + src.use_icon = use_icon + src.use_icon_state = use_icon_state + src.only_show_on_shuttle_edge = only_show_on_shuttle_edge + + RegisterSignal(target, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED, PROC_REF(provide_image)) + +/datum/element/nav_computer_icon/proc/provide_image(datum/source, list/images_out) + SIGNAL_HANDLER + var/obj/source_obj = source + var/turf/source_turf = get_turf(source_obj) + if(!source_turf) + return + if(only_show_on_shuttle_edge) + var/isOnEdge = FALSE + for(var/direction in GLOB.cardinals) + var/turf/turf = get_step(source_obj, direction) + if(!istype(turf?.loc, /area/shuttle)) + isOnEdge = TRUE + break + if(!isOnEdge) + return + + var/image/the_image = image(use_icon, source_turf, use_icon_state) + the_image.dir = source_obj.dir + images_out += the_image + +/datum/element/nav_computer_icon/Detach(datum/source) + . = ..() + UnregisterSignal(source, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED) diff --git a/code/datums/elements/pet_cult.dm b/code/datums/elements/pet_cult.dm index 36941e7b74299..f26e6e343e666 100644 --- a/code/datums/elements/pet_cult.dm +++ b/code/datums/elements/pet_cult.dm @@ -90,7 +90,8 @@ source.ai_controller.set_blackboard_key(BB_CULT_TEAM, team) var/static/list/new_pet_commands = list( - /datum/pet_command/point_targeting/attack, + /datum/pet_command/move, + /datum/pet_command/attack, /datum/pet_command/follow, /datum/pet_command/free, /datum/pet_command/idle, diff --git a/code/datums/elements/weapon_description.dm b/code/datums/elements/weapon_description.dm index eda7ca59b49e6..64d044fb74a3d 100644 --- a/code/datums/elements/weapon_description.dm +++ b/code/datums/elements/weapon_description.dm @@ -73,9 +73,9 @@ // Doesn't show the base notes for items that have the override notes variable set to true if(!source.override_notes) - if (source.sharpness & SHARP_EDGED) + if (source.get_sharpness() & SHARP_EDGED) readout += "It's sharp and could cause bleeding wounds." - if (source.sharpness & SHARP_POINTY) + if (source.get_sharpness() & SHARP_POINTY) readout += "It's pointy and could cause piercing wounds." // Make sure not to divide by 0 on accident if(source.force > 0) diff --git a/code/datums/keybinding/_keybindings.dm b/code/datums/keybinding/_keybindings.dm index dfcf492c1809f..a989d0d22a881 100644 --- a/code/datums/keybinding/_keybindings.dm +++ b/code/datums/keybinding/_keybindings.dm @@ -21,6 +21,8 @@ return SEND_SIGNAL(user.mob, keybind_signal) & COMSIG_KB_ACTIVATED /datum/keybinding/proc/up(client/user) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(user.mob, DEACTIVATE_KEYBIND(keybind_signal)) return FALSE /datum/keybinding/proc/can_use(client/user) diff --git a/code/datums/keybinding/living.dm b/code/datums/keybinding/living.dm index 390fd427f5ff4..61716ee6fe95a 100644 --- a/code/datums/keybinding/living.dm +++ b/code/datums/keybinding/living.dm @@ -16,10 +16,20 @@ . = ..() if(.) return - var/mob/living/L = user.mob - L.resist() + var/mob/living/owner = user.mob + owner.resist() + if (owner.hud_used?.resist_icon) + owner.hud_used.resist_icon.icon_state = "[owner.hud_used.resist_icon.base_icon_state]_on" return TRUE +/datum/keybinding/living/resist/up(client/user) + . = ..() + if(.) + return + var/mob/living/owner = user.mob + if (owner.hud_used?.resist_icon) + owner.hud_used.resist_icon.icon_state = owner.hud_used.resist_icon.base_icon_state + /datum/keybinding/living/look_up hotkey_keys = list("L") name = "look up" @@ -36,6 +46,7 @@ return TRUE /datum/keybinding/living/look_up/up(client/user) + . = ..() var/mob/living/L = user.mob L.end_look_up() return TRUE @@ -56,6 +67,7 @@ return TRUE /datum/keybinding/living/look_down/up(client/user) + . = ..() var/mob/living/L = user.mob L.end_look_down() return TRUE @@ -134,6 +146,7 @@ return TRUE /datum/keybinding/living/toggle_move_intent/up(client/user) + . = ..() var/mob/living/M = user.mob M.toggle_move_intent() return TRUE diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm index 4963e87266cbe..a1bd06742b555 100644 --- a/code/datums/keybinding/mob.dm +++ b/code/datums/keybinding/mob.dm @@ -191,3 +191,10 @@ if(.) return user.movement_locked = FALSE + +/datum/keybinding/living/view_pet_data + hotkey_keys = list("Shift") + name = "view_pet_commands" + full_name = "View Pet Commands" + description = "Hold down to see all the commands you can give your pets!" + keybind_signal = COMSIG_KB_LIVING_VIEW_PET_COMMANDS diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm index d0ad6544ca6e6..1dc2e69f2ca84 100644 --- a/code/datums/looping_sounds/_looping_sound.dm +++ b/code/datums/looping_sounds/_looping_sound.dm @@ -174,7 +174,7 @@ if(!each_once) . = play_from while(!isfile(.) && !isnull(.)) - . = pick_weight(.) + . = pick_weight_recursive(.) return . if(in_order) @@ -192,7 +192,7 @@ // Tree is a list of lists containign files // If an entry in the tree goes to 0 length, we cut it from the list tree += list(.) - . = pick_weight(.) + . = pick_weight_recursive(.) if(!isfile(.)) return diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index c4648a929b300..92f78074c9873 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -1,13 +1,17 @@ /datum/looping_sound/showering start_sound = 'sound/machines/shower/shower_start.ogg' start_length = 2 - mid_sounds = list('sound/machines/shower/shower_mid1.ogg' = 1, 'sound/machines/shower/shower_mid2.ogg' = 1, 'sound/machines/shower/shower_mid3.ogg' = 1) + mid_sounds = list( + 'sound/machines/shower/shower_mid1.ogg', + 'sound/machines/shower/shower_mid2.ogg', + 'sound/machines/shower/shower_mid3.ogg', + ) mid_length = 10 end_sound = 'sound/machines/shower/shower_end.ogg' volume = 20 /datum/looping_sound/supermatter - mid_sounds = list('sound/machines/sm/loops/calm.ogg' = 1) + mid_sounds = list('sound/machines/sm/loops/calm.ogg') mid_length = 60 volume = 40 extra_range = 25 @@ -16,14 +20,14 @@ vary = TRUE /datum/looping_sound/destabilized_crystal - mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1) + mid_sounds = list('sound/machines/sm/loops/delamming.ogg') mid_length = 60 volume = 55 extra_range = 15 vary = TRUE /datum/looping_sound/hypertorus - mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg' = 1) + mid_sounds = list('sound/machines/hypertorus/loops/hypertorus_nominal.ogg') mid_length = 60 volume = 55 extra_range = 15 @@ -32,35 +36,41 @@ /datum/looping_sound/generator start_sound = 'sound/machines/generator/generator_start.ogg' start_length = 4 - mid_sounds = list('sound/machines/generator/generator_mid1.ogg' = 1, 'sound/machines/generator/generator_mid2.ogg' = 1, 'sound/machines/generator/generator_mid3.ogg' = 1) + mid_sounds = list( + 'sound/machines/generator/generator_mid1.ogg', + 'sound/machines/generator/generator_mid2.ogg', + 'sound/machines/generator/generator_mid3.ogg', + ) mid_length = 4 end_sound = 'sound/machines/generator/generator_end.ogg' volume = 40 - /datum/looping_sound/deep_fryer start_sound = 'sound/machines/fryer/deep_fryer_immerse.ogg' //my immersions start_length = 10 - mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1) + mid_sounds = list( + 'sound/machines/fryer/deep_fryer_1.ogg', + 'sound/machines/fryer/deep_fryer_2.ogg', + ) mid_length = 2 end_sound = 'sound/machines/fryer/deep_fryer_emerge.ogg' volume = 15 /datum/looping_sound/clock - mid_sounds = list('sound/ambience/misc/ticking_clock.ogg' = 1) + mid_sounds = list('sound/ambience/misc/ticking_clock.ogg') mid_length = 40 volume = 50 ignore_walls = FALSE /datum/looping_sound/grill - mid_sounds = list('sound/machines/grill/grillsizzle.ogg' = 1) + mid_sounds = list('sound/machines/grill/grillsizzle.ogg') mid_length = 18 volume = 50 /datum/looping_sound/oven start_sound = 'sound/machines/oven/oven_loop_start.ogg' //my immersions start_length = 12 - mid_sounds = list('sound/machines/oven/oven_loop_mid.ogg' = 1) + mid_sounds = list('sound/machines/oven/oven_loop_mid.ogg') mid_length = 13 end_sound = 'sound/machines/oven/oven_loop_end.ogg' volume = 100 @@ -68,19 +78,25 @@ /datum/looping_sound/deep_fryer mid_length = 2 - mid_sounds = list('sound/machines/fryer/deep_fryer_1.ogg' = 1, 'sound/machines/fryer/deep_fryer_2.ogg' = 1) + mid_sounds = list( + 'sound/machines/fryer/deep_fryer_1.ogg', + 'sound/machines/fryer/deep_fryer_2.ogg', + ) volume = 30 /datum/looping_sound/microwave start_sound = 'sound/machines/microwave/microwave-start.ogg' start_length = 10 - mid_sounds = list('sound/machines/microwave/microwave-mid1.ogg' = 10, 'sound/machines/microwave/microwave-mid2.ogg' = 1) + mid_sounds = list( + 'sound/machines/microwave/microwave-mid1.ogg' = 10, + 'sound/machines/microwave/microwave-mid2.ogg' = 1, + ) mid_length = 10 end_sound = 'sound/machines/microwave/microwave-end.ogg' volume = 90 /datum/looping_sound/lathe_print - mid_sounds = list('sound/machines/lathe/lathe_print.ogg' = 1) + mid_sounds = list('sound/machines/lathe/lathe_print.ogg') mid_length = 20 volume = 50 vary = TRUE @@ -90,19 +106,19 @@ /datum/looping_sound/jackpot mid_length = 11 - mid_sounds = list('sound/machines/roulette/roulettejackpot.ogg' = 1) + mid_sounds = list('sound/machines/roulette/roulettejackpot.ogg') volume = 85 vary = TRUE /datum/looping_sound/server mid_sounds = list( - 'sound/machines/tcomms/tcomms_mid1.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid2.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid3.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid4.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid5.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid6.ogg' = 1, - 'sound/machines/tcomms/tcomms_mid7.ogg' = 1, + 'sound/machines/tcomms/tcomms_mid1.ogg', + 'sound/machines/tcomms/tcomms_mid2.ogg', + 'sound/machines/tcomms/tcomms_mid3.ogg', + 'sound/machines/tcomms/tcomms_mid4.ogg', + 'sound/machines/tcomms/tcomms_mid5.ogg', + 'sound/machines/tcomms/tcomms_mid6.ogg', + 'sound/machines/tcomms/tcomms_mid7.ogg', ) mid_length = 1.8 SECONDS extra_range = -8 @@ -116,7 +132,10 @@ start_sound = 'sound/machines/computer/computer_start.ogg' start_length = 7.2 SECONDS start_volume = 10 - mid_sounds = list('sound/machines/computer/computer_mid1.ogg' = 1, 'sound/machines/computer/computer_mid2.ogg' = 1) + mid_sounds = list( + 'sound/machines/computer/computer_mid1.ogg', + 'sound/machines/computer/computer_mid2.ogg', + ) mid_length = 1.8 SECONDS end_sound = 'sound/machines/computer/computer_end.ogg' end_volume = 10 @@ -141,7 +160,12 @@ falloff_exponent = 20 /datum/looping_sound/firealarm - mid_sounds = list('sound/machines/fire_alarm/FireAlarm1.ogg' = 1,'sound/machines/fire_alarm/FireAlarm2.ogg' = 1,'sound/machines/fire_alarm/FireAlarm3.ogg' = 1,'sound/machines/fire_alarm/FireAlarm4.ogg' = 1) + mid_sounds = list( + 'sound/machines/fire_alarm/FireAlarm1.ogg', + 'sound/machines/fire_alarm/FireAlarm2.ogg', + 'sound/machines/fire_alarm/FireAlarm3.ogg', + 'sound/machines/fire_alarm/FireAlarm4.ogg', + ) mid_length = 2.4 SECONDS volume = 30 @@ -151,30 +175,30 @@ falloff_exponent = 5 /datum/looping_sound/boiling - mid_sounds = list('sound/effects/bubbles/bubbles2.ogg' = 1) + mid_sounds = list('sound/effects/bubbles/bubbles2.ogg') mid_length = 7 SECONDS volume = 25 /datum/looping_sound/typing mid_sounds = list( - 'sound/machines/terminal/terminal_button01.ogg' = 1, - 'sound/machines/terminal/terminal_button02.ogg' = 1, - 'sound/machines/terminal/terminal_button03.ogg' = 1, - 'sound/machines/terminal/terminal_button04.ogg' = 1, - 'sound/machines/terminal/terminal_button05.ogg' = 1, - 'sound/machines/terminal/terminal_button06.ogg' = 1, - 'sound/machines/terminal/terminal_button07.ogg' = 1, - 'sound/machines/terminal/terminal_button08.ogg' = 1, + 'sound/machines/terminal/terminal_button01.ogg', + 'sound/machines/terminal/terminal_button02.ogg', + 'sound/machines/terminal/terminal_button03.ogg', + 'sound/machines/terminal/terminal_button04.ogg', + 'sound/machines/terminal/terminal_button05.ogg', + 'sound/machines/terminal/terminal_button06.ogg', + 'sound/machines/terminal/terminal_button07.ogg', + 'sound/machines/terminal/terminal_button08.ogg', ) mid_length = 0.3 SECONDS /datum/looping_sound/soup mid_sounds = list( - 'sound/effects/soup_boil/soup_boil1.ogg' = 1, - 'sound/effects/soup_boil/soup_boil2.ogg' = 1, - 'sound/effects/soup_boil/soup_boil3.ogg' = 1, - 'sound/effects/soup_boil/soup_boil4.ogg' = 1, - 'sound/effects/soup_boil/soup_boil5.ogg' = 1, + 'sound/effects/soup_boil/soup_boil1.ogg', + 'sound/effects/soup_boil/soup_boil2.ogg', + 'sound/effects/soup_boil/soup_boil3.ogg', + 'sound/effects/soup_boil/soup_boil4.ogg', + 'sound/effects/soup_boil/soup_boil5.ogg', ) mid_length = 3 SECONDS volume = 80 @@ -182,3 +206,19 @@ end_volume = 60 extra_range = MEDIUM_RANGE_SOUND_EXTRARANGE falloff_exponent = 4 + +/datum/looping_sound/cryo_cell + mid_sounds = list( + 'sound/machines/cryo/cryo_1.ogg', + 'sound/machines/cryo/cryo_2.ogg', + 'sound/machines/cryo/cryo_3.ogg', + 'sound/machines/cryo/cryo_4.ogg', + 'sound/machines/cryo/cryo_5.ogg', + 'sound/machines/cryo/cryo_6.ogg', + 'sound/machines/cryo/cryo_7.ogg', + 'sound/machines/cryo/cryo_8.ogg', + 'sound/machines/cryo/cryo_9.ogg', + 'sound/machines/cryo/cryo_10.ogg', + ) + mid_length = 5 SECONDS + volume = 50 diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index d2142b02a8bcf..b5ff49037e6f8 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -254,6 +254,7 @@ icon = 'icons/obj/weapons/staff.dmi' icon_state = "bostaff0" base_icon_state = "bostaff" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' block_chance = 50 diff --git a/code/datums/mutations/tongue_spike.dm b/code/datums/mutations/tongue_spike.dm index c6a48a32a144d..663dcd2541acd 100644 --- a/code/datums/mutations/tongue_spike.dm +++ b/code/datums/mutations/tongue_spike.dm @@ -45,6 +45,7 @@ desc = "Hardened biomass, shaped into a spike. Very pointy!" icon = 'icons/obj/weapons/thrown.dmi' icon_state = "tonguespike" + icon_angle = 45 force = 2 throwforce = 25 throw_speed = 4 diff --git a/code/datums/storage/subtypes/rped.dm b/code/datums/storage/subtypes/rped.dm index 9931cff7372fc..a54d52ca359c0 100644 --- a/code/datums/storage/subtypes/rped.dm +++ b/code/datums/storage/subtypes/rped.dm @@ -6,7 +6,6 @@ #define MAX_STACK_PICKUP 30 /datum/storage/rped - allow_quick_empty = TRUE allow_quick_gather = TRUE max_slots = 50 max_total_storage = 100 @@ -14,7 +13,7 @@ numerical_stacking = TRUE /** - * as of now only these stack components are required to build machines like[thermomaachine,crystallizer,electrolyzer] + * as of now only these stack components are required to build machines like[thermomachine,crystallizer,electrolyzer] * so we limit the rped to pick up only these stack types so players dont cheat and use this as a general storage medium */ var/static/list/allowed_material_types = list( @@ -33,11 +32,20 @@ ) /datum/storage/rped/can_insert(obj/item/to_insert, mob/user, messages = TRUE, force = FALSE) - . = ..() - if(!.) - return . + //only stock parts permited + if(to_insert.get_part_rating()) + return ..() + + //some exceptions to non stock parts + var/static/list/obj/item/exceptions = list( + /obj/item/stack, + /obj/item/circuitboard/machine, + /obj/item/circuitboard/computer, + ) + + return is_type_in_list(to_insert, exceptions) ? ..() : FALSE - //we check how much of glass,plasteel & cable the user can insert +/datum/storage/rped/attempt_insert(obj/item/to_insert, mob/user, override, force, messages) if(isstack(to_insert)) //user tried to insert invalid stacktype if(!is_type_in_list(to_insert, allowed_material_types) && !is_type_in_list(to_insert, allowed_bluespace_types)) @@ -53,60 +61,58 @@ //if yes count total bluespace stuff is the RPED and then compare the total amount to the value the user is trying to insert if(is_type_in_list(stack_content, allowed_bluespace_types)) present_amount += stack_content.amount + //count other normal stack stuff - else if(istype(to_insert,stack_content.type)) + else if(the_stack.merge_type == stack_content.merge_type) present_amount = stack_content.amount break - //no more storage for this specific stack type - if(MAX_STACK_PICKUP - present_amount == 0) - return FALSE + var/available = MAX_STACK_PICKUP - present_amount - //we want the user to insert the exact stack amount which is available so we dont have to bother subtracting & leaving left overs for the user - var/available = MAX_STACK_PICKUP-present_amount - if(available - the_stack.amount < 0) + //no more storage for this specific stack type + if(!available) return FALSE - else if(istype(to_insert, /obj/item/circuitboard/machine) || istype(to_insert, /obj/item/circuitboard/computer)) - return TRUE + var/obj/item/stack/target = the_stack + if(the_stack.amount > available) //take in only a portion of the stack that can fit in our quota + target = fast_split_stack(the_stack, available) + target.copy_evidences(the_stack) - //check normal insertion of other stock parts - else if(!to_insert.get_part_rating()) - return FALSE + . = ..(target, user, override, force, messages) + if(!. && target != the_stack) //in case of failure merge back the split amount into the original + the_stack.add(target.amount) + qdel(target) - return . - -/datum/storage/rped/mass_empty(datum/source, mob/user) - if(!allow_quick_empty) return - remove_lowest_tier(user.drop_location()) + return ..() -/** - * Searches through everything currently in storage, calculates the lowest tier of parts inside of it, - * and then dumps out every part that has the equal tier of parts. Likely a worse implementation of remove_all. - * - * Arguments - * * atom/dump_loc - where we're placing the item - */ -/datum/storage/rped/proc/remove_lowest_tier(atom/dump_loc = parent.drop_location()) +/datum/storage/rped/mass_empty(datum/source, mob/user) var/list/obj/item/parts_list = list() - var/current_lowest_tier = INFINITY - for(var/obj/item/thing in real_location) parts_list += thing + if(!parts_list.len) + return - if(parts_list.len > 0) - parts_list = reverse_range(sortTim(parts_list, GLOBAL_PROC_REF(cmp_rped_sort))) - current_lowest_tier = parts_list[1].get_part_rating() - if(ismob(parent.loc)) - parent.balloon_alert(parent.loc, "dropping lowest rated parts...") - for(var/obj/item/part in parts_list) - if(part.get_part_rating() != current_lowest_tier) - break - if(!attempt_remove(part, dump_loc, silent = TRUE)) - continue - part.pixel_x = part.base_pixel_x + rand(-8, 8) - part.pixel_y = part.base_pixel_y + rand(-8, 8) + var/current_lowest_tier = INFINITY + parts_list = reverse_range(sortTim(parts_list, GLOBAL_PROC_REF(cmp_rped_sort))) + current_lowest_tier = parts_list[1].get_part_rating() + if(ismob(parent.loc)) + parent.balloon_alert(parent.loc, "dropping lowest rated parts...") + + var/dump_loc = user.drop_location() + for(var/obj/item/part in parts_list) + if(part.get_part_rating() != current_lowest_tier) + break + if(!attempt_remove(part, dump_loc, silent = TRUE)) + continue + part.pixel_x = part.base_pixel_x + rand(-8, 8) + part.pixel_y = part.base_pixel_y + rand(-8, 8) + +///bluespace variant +/datum/storage/rped/bluespace + max_slots = 400 + max_total_storage = 800 + max_specific_storage = WEIGHT_CLASS_GIGANTIC #undef MAX_STACK_PICKUP diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index bb4bc85d8e95f..4bf2664fb34ce 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -17,6 +17,8 @@ /// How much blood we start losing when this wound is first applied var/initial_flow + /// How much our blood_flow will naturally decrease per second, even without gauze + var/clot_rate /// If gauzed, what percent of the internal bleeding actually clots of the total absorption rate var/gauzed_clot_rate @@ -72,8 +74,10 @@ return BLOOD_FLOW_STEADY if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) return BLOOD_FLOW_INCREASING - if(limb.current_gauze) + if(limb.current_gauze || clot_rate > 0) return BLOOD_FLOW_DECREASING + if(clot_rate < 0) + return BLOOD_FLOW_INCREASING return BLOOD_FLOW_STEADY /datum/wound/pierce/bleed/handle_process(seconds_per_tick, times_fired) @@ -92,10 +96,16 @@ if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS)) adjust_blood_flow(0.25 * seconds_per_tick) // old heparin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first + //gauze always reduces blood flow, even for non bleeders if(limb.current_gauze) + if(clot_rate > 0) + adjust_blood_flow(-clot_rate * seconds_per_tick) var/gauze_power = limb.current_gauze.absorption_rate limb.seep_gauze(gauze_power * seconds_per_tick) adjust_blood_flow(-gauze_power * gauzed_clot_rate * seconds_per_tick) + //otherwise, only clot if it's a bleeder + else if(limb.can_bleed()) + adjust_blood_flow(-clot_rate * seconds_per_tick) /datum/wound/pierce/bleed/adjust_blood_flow(adjust_by, minimum) . = ..() @@ -174,12 +184,13 @@ cauterization, or in extreme circumstances, exposure to extreme cold or vaccuum. \ Follow with food and a rest period." treat_text_short = "Apply bandaging or suturing." - examine_desc = "has a small, circular hole, gently bleeding" + examine_desc = "has a small, torn hole, gently bleeding" occur_text = "spurts out a thin stream of blood" sound_effect = 'sound/effects/wounds/pierce1.ogg' severity = WOUND_SEVERITY_MODERATE initial_flow = 1.5 gauzed_clot_rate = 0.8 + clot_rate = 0.03 internal_bleeding_chance = 30 internal_bleeding_coefficient = 1.25 threshold_penalty = 20 @@ -189,6 +200,11 @@ simple_treat_text = "Bandaging the wound will reduce blood loss, help the wound close by itself quicker, and speed up the blood recovery period. The wound itself can be slowly sutured shut." homemade_treat_text = "Tea stimulates the body's natural healing systems, slightly fastening clotting. The wound itself can be rinsed off on a sink or shower as well. Other remedies are unnecessary." +/datum/wound/pierce/bleed/moderate/update_descriptions() + if(!limb.can_bleed()) + examine_desc = "has a small, torn hole" + occur_text = "splits a small hole open" + /datum/wound_pregen_data/flesh_pierce/breakage abstract = FALSE @@ -196,13 +212,35 @@ threshold_minimum = 30 -/datum/wound/pierce/bleed/moderate/update_descriptions() +/datum/wound_pregen_data/flesh_pierce/breakage/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + if (isprojectile(damage_source)) + return 0 + return weight + +/datum/wound/pierce/bleed/moderate/projectile + name = "Minor Skin Penetration" + desc = "Patient's skin has been pierced through, causing severe bruising and minor internal bleeding in affected area." + treat_text = "Apply bandaging or suturing to the wound, make use of blood clotting agents, \ + cauterization, or in extreme circumstances, exposure to extreme cold or vaccuum. \ + Follow with food and a rest period." + examine_desc = "has a small, circular hole, gently bleeding" + clot_rate = 0 + +/datum/wound/pierce/bleed/moderate/projectile/update_descriptions() if(!limb.can_bleed()) examine_desc = "has a small, circular hole" occur_text = "splits a small hole open" +/datum/wound_pregen_data/flesh_pierce/breakage/projectile + wound_path_to_generate = /datum/wound/pierce/bleed/moderate/projectile + +/datum/wound_pregen_data/flesh_pierce/breakage/projectile/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + if (!isprojectile(damage_source)) + return 0 + return weight + /datum/wound/pierce/bleed/severe - name = "Open Puncture" + name = "Open Stab Puncture" desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability." treat_text = "Swiftly apply bandaging or suturing to the wound, make use of blood clotting agents or saline-glucose, \ cauterization, or in extreme circumstances, exposure to extreme cold or vaccuum. \ @@ -214,6 +252,7 @@ severity = WOUND_SEVERITY_SEVERE initial_flow = 2.25 gauzed_clot_rate = 0.6 + clot_rate = 0.02 internal_bleeding_chance = 60 internal_bleeding_coefficient = 1.5 threshold_penalty = 35 @@ -223,6 +262,10 @@ simple_treat_text = "Bandaging the wound is essential, and will reduce blood loss. Afterwards, the wound can be sutured shut, preferably while the patient is resting and/or grasping their wound." homemade_treat_text = "Bed sheets can be ripped up to make makeshift gauze. Flour, table salt, or salt mixed with water can be applied directly to stem the flow, though unmixed salt will irritate the skin and worsen natural healing. Resting and grabbing your wound will also reduce bleeding." +/datum/wound/pierce/bleed/severe/update_descriptions() + if(!limb.can_bleed()) + occur_text = "tears a hole open" + /datum/wound_pregen_data/flesh_pierce/open_puncture abstract = FALSE @@ -230,9 +273,23 @@ threshold_minimum = 50 -/datum/wound/pierce/bleed/severe/update_descriptions() - if(!limb.can_bleed()) - occur_text = "tears a hole open" +/datum/wound_pregen_data/flesh_pierce/open_puncture/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + if (isprojectile(damage_source)) + return 0 + return weight + +/datum/wound/pierce/bleed/severe/projectile + name = "Open Bullet Puncture" + examine_desc = "is pierced clear through, with bits of tissue obscuring the cleanly torn hole" + clot_rate = 0 + +/datum/wound_pregen_data/flesh_pierce/open_puncture/projectile + wound_path_to_generate = /datum/wound/pierce/bleed/severe/projectile + +/datum/wound_pregen_data/flesh_pierce/open_puncture/projectile/get_weight(obj/item/bodypart/limb, woundtype, damage, attack_direction, damage_source) + if (!isprojectile(damage_source)) + return 0 + return weight /datum/wound/pierce/bleed/severe/eye name = "Eyeball Puncture" diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 9afda5b0ddea5..c8c3111889eac 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -864,6 +864,8 @@ if (isnull(user)) return + SEND_SIGNAL(user, COMSIG_ATOM_MOUSE_ENTERED, src) + // Screentips var/datum/hud/active_hud = user.hud_used if(!active_hud) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index fe3646cd06204..e6fe68ea4c487 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1146,6 +1146,12 @@ loc = destination if(!same_loc) + if(loc == oldloc) + // when attempting to move an atom A into an atom B which already contains A, BYOND seems + // to silently refuse to move A to the new loc. This can really break stuff (see #77067) + stack_trace("Attempt to move [src] to [destination] was rejected by BYOND, possibly due to cyclic contents") + return FALSE + if(is_multi_tile && isturf(destination)) var/list/new_locs = block( destination, @@ -1491,9 +1497,16 @@ return -/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE) +/atom/movable/proc/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE, item_animation_override = null) if(!no_effect && (visual_effect_icon || used_item)) - do_item_attack_animation(attacked_atom, visual_effect_icon, used_item) + var/animation_type = item_animation_override || ATTACK_ANIMATION_BLUNT + if (used_item && !item_animation_override) + switch(used_item.get_sharpness()) + if (SHARP_EDGED) + animation_type = ATTACK_ANIMATION_SLASH + if (SHARP_POINTY) + animation_type = ATTACK_ANIMATION_PIERCE + do_item_attack_animation(attacked_atom, visual_effect_icon, used_item, animation_type = animation_type) if(attacked_atom == src) return //don't do an animation if attacking self diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 656ece7f8ee14..0a7a8298aa320 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -493,7 +493,6 @@ ///internal proc that removes all static power usage from the current area /obj/machinery/proc/unset_static_power() - PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) var/old_usage = static_power_usage @@ -1020,21 +1019,16 @@ return TRUE /obj/machinery/proc/exchange_parts(mob/user, obj/item/storage/part_replacer/replacer_tool) - if(!istype(replacer_tool)) + if(!istype(replacer_tool) || !component_parts) return FALSE - var/shouldplaysound = FALSE - if(!component_parts) - return FALSE - - if(!panel_open && !replacer_tool.works_from_distance) + var/works_from_distance = istype(replacer_tool, /obj/item/storage/part_replacer/bluespace) + if(!panel_open && !works_from_distance) to_chat(user, display_parts(user)) - if(shouldplaysound) - replacer_tool.play_rped_sound() return FALSE var/obj/item/circuitboard/machine/machine_board = locate(/obj/item/circuitboard/machine) in component_parts - if(replacer_tool.works_from_distance) + if(works_from_distance) to_chat(user, display_parts(user)) if(!machine_board) return FALSE @@ -1045,6 +1039,7 @@ * completly ignoring the tier 4 component inside * we also ignore stack components inside the RPED cause we dont exchange that */ + var/shouldplaysound = FALSE var/list/part_list = replacer_tool.get_sorted_parts(ignore_stacks = TRUE) if(!part_list.len) return FALSE @@ -1075,7 +1070,7 @@ if(!istype(secondary_part, required_type)) continue // If it's a corrupt or rigged cell, attempting to send it through Bluespace could have unforeseen consequences. - if(istype(secondary_part, /obj/item/stock_parts/power_store/cell) && replacer_tool.works_from_distance) + if(istype(secondary_part, /obj/item/stock_parts/power_store/cell) && works_from_distance) var/obj/item/stock_parts/power_store/cell/checked_cell = secondary_part // If it's rigged or corrupted, max the charge. Then explode it. if(checked_cell.rigged || checked_cell.corrupted) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 3e947d33d7bff..45a21eafef887 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -125,8 +125,6 @@ if(add_cabling(user, cable, time = 0)) if(!no_sound) replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) no_sound = TRUE return install_parts_from_part_replacer(user, replacer, no_sound = no_sound) // Recursive call to handle the next part @@ -140,8 +138,6 @@ if(add_glass(user, glass_sheets, time = 0)) if(!no_sound) replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) return TRUE return FALSE diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm index 8cd12610c748b..74e6d22e36355 100644 --- a/code/game/machinery/computer/teleporter.dm +++ b/code/game/machinery/computer/teleporter.dm @@ -116,7 +116,7 @@ say("Processing hub calibration to target...") calibrating = TRUE power_station.update_appearance() - addtimer(CALLBACK(src, PROC_REF(finish_calibration)), 50 * (3 - power_station.teleporter_hub.accuracy)) //Better parts mean faster calibration + addtimer(CALLBACK(src, PROC_REF(finish_calibration)), 5 SECONDS * (3 - power_station.teleporter_hub.accuracy)) //Better parts mean faster calibration return TRUE /obj/machinery/computer/teleporter/proc/set_teleport_target(new_target) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index f0b3434ec85c9..b90302111ab7b 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -112,6 +112,16 @@ return install_board(user, tool, by_hand = TRUE) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_BLOCKING return NONE +/obj/structure/frame/ranged_item_interaction(mob/living/user, obj/item/tool, list/modifiers) + . = NONE + + if(!istype(tool, /obj/item/storage/part_replacer/bluespace)) + return + + . = item_interaction(user, tool, modifiers) + if(. & ITEM_INTERACT_ANY_BLOCKER) + user.Beam(tool, icon_state = "rped_upgrade", time = 0.5 SECONDS) + /** * Installs the passed circuit board into the frame * @@ -173,7 +183,7 @@ if(QDELETED(target_board) || QDELETED(src) || QDELETED(user) || !(target_board in replacer) || !user.is_holding(replacer)) return FALSE // User still within range? - var/close_enough = replacer.works_from_distance || user.Adjacent(src) + var/close_enough = istype(replacer, /obj/item/storage/part_replacer/bluespace) || user.Adjacent(src) if(!close_enough) return FALSE @@ -182,8 +192,6 @@ install_parts_from_part_replacer(user, replacer, no_sound = TRUE) if(!no_sound) replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) return TRUE return FALSE 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 9a873b5e373f6..221881c6ea5b5 100644 --- a/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/carp_organs.dm @@ -98,6 +98,7 @@ name = "carp tooth" desc = "Looks sharp. Sharp enough to poke someone's eye out. Holy fuck it's big." icon_state = "carptooth" + icon_angle = -45 ///carp brain. you need to occasionally go to a new zlevel. think of it as... walking your dog! /obj/item/organ/brain/carp diff --git a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm index 5a06aa8e8c20d..e4315c4a5e245 100644 --- a/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/goliath_organs.dm @@ -99,6 +99,7 @@ icon = 'icons/obj/weapons/goliath_hammer.dmi' icon_state = "goliath_hammer" inhand_icon_state = "goliath_hammer" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/goliath_hammer_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/goliath_hammer_righthand.dmi' item_flags = ABSTRACT | DROPDEL diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 7cbd5126ca487..c3657c6cac391 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -181,6 +181,8 @@ // Click on the floor to close airlocks AddComponent(/datum/component/redirect_attack_hand_from_turf) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "airlock", TRUE) + RegisterSignal(src, COMSIG_MACHINERY_BROKEN, PROC_REF(on_break)) RegisterSignal(SSdcs, COMSIG_GLOB_GREY_TIDE, PROC_REF(grey_tide)) @@ -1172,13 +1174,15 @@ return TRUE -/obj/machinery/door/airlock/try_to_crowbar(obj/item/I, mob/living/user, forced = FALSE) - if(I.tool_behaviour == TOOL_CROWBAR && should_try_removing_electronics() && !operating) +/obj/machinery/door/airlock/try_to_crowbar(obj/item/tool, mob/living/user, forced = FALSE) + if(!isnull(tool) && tool.tool_behaviour == TOOL_CROWBAR && should_try_removing_electronics() && !operating) user.visible_message(span_notice("[user] removes the electronics from the airlock assembly."), \ span_notice("You start to remove electronics from the airlock assembly...")) - if(I.use_tool(src, user, 40, volume = 100)) + + if(tool.use_tool(src, user, 40, volume = 100)) deconstruct(TRUE, user) return + if(seal) to_chat(user, span_warning("Remove the seal first!")) return @@ -1188,37 +1192,48 @@ if(welded) to_chat(user, span_warning("It's welded, it won't budge!")) return - if(hasPower()) - if(forced) - var/check_electrified = isElectrified() //setting this so we can check if the mob got shocked during the do_after below - if(check_electrified && shock(user,100)) - return //it's like sticking a fork in a power socket - if(!density)//already open - return + if(!hasPower()) + if(operating) + return - if(!prying_so_hard) - var/time_to_open = 50 - playsound(src, 'sound/machines/airlock/airlock_alien_prying.ogg', 100, TRUE) //is it aliens or just the CE being a dick? - prying_so_hard = TRUE - if(I.use_tool(src, user, time_to_open, volume = 100)) - if(check_electrified && shock(user, 100)) - prying_so_hard = FALSE - return - open(BYPASS_DOOR_CHECKS) - take_damage(25, BRUTE, 0, 0) // Enough to sometimes spark - if(density && !open(BYPASS_DOOR_CHECKS)) - to_chat(user, span_warning("Despite your attempts, [src] refuses to open.")) - prying_so_hard = FALSE - return + if(istype(tool, /obj/item/fireaxe) && !HAS_TRAIT(tool, TRAIT_WIELDED)) //being fireaxe'd + to_chat(user, span_warning("You need to be wielding [tool] to do that!")) + return + + INVOKE_ASYNC(src, density ? PROC_REF(open) : PROC_REF(close), BYPASS_DOOR_CHECKS) + return + + if(!forced) to_chat(user, span_warning("The airlock's motors resist your efforts to force it!")) return - if(!operating) - if(istype(I, /obj/item/fireaxe) && !HAS_TRAIT(I, TRAIT_WIELDED)) //being fireaxe'd - to_chat(user, span_warning("You need to be wielding [I] to do that!")) - return - INVOKE_ASYNC(src, density ? PROC_REF(open) : PROC_REF(close), BYPASS_DOOR_CHECKS) + var/check_electrified = isElectrified() //setting this so we can check if the mob got shocked during the do_after below + if(check_electrified && shock(user,100)) + return //it's like sticking a fork in a power socket + + if(!density)//already open + return + + if(prying_so_hard) + return + + var/time_to_open = 5 SECONDS + playsound(src, 'sound/machines/airlock/airlock_alien_prying.ogg', 100, TRUE) //is it aliens or just the CE being a dick? + prying_so_hard = TRUE + + if(!tool.use_tool(src, user, time_to_open, volume = 100)) + prying_so_hard = FALSE + return + + if(check_electrified && shock(user, 100)) + prying_so_hard = FALSE + return + + open(BYPASS_DOOR_CHECKS) + take_damage(25, BRUTE, 0, 0) // Enough to sometimes spark + if(density && !open(BYPASS_DOOR_CHECKS)) + to_chat(user, span_warning("Despite your attempts, [src] refuses to open.")) /obj/machinery/door/airlock/open(forced = DEFAULT_DOOR_CHECKS) if(cycle_pump && !operating && !welded && !seal && locked && density) @@ -2227,7 +2242,7 @@ if(!hasPower()) to_chat(user, span_notice("You begin unlocking the airlock safety mechanism...")) if(do_after(user, 15 SECONDS, target = src)) - try_to_crowbar(src, user, TRUE) + try_to_crowbar(null, user, TRUE) return TRUE else // always open from the space side diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index b238f3416681f..5de6b4d61d3ea 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -548,7 +548,7 @@ if(welded || operating) return - var/atom/crowbar_owner = acting_object.loc //catchs mechs and any other non-mob using a crowbar + var/atom/crowbar_owner = acting_object?.loc || user // catches mechs and any other non-mob using a crowbar if(density) being_held_open = TRUE diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index cb33ed6c14f5a..b7ae0c9876a83 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -22,6 +22,12 @@ var/id = 1 /// The sound that plays when the door opens/closes var/animation_sound = 'sound/machines/blastdoor.ogg' + var/show_nav_computer_icon = TRUE + +/obj/machinery/door/poddoor/Initialize(mapload) + . = ..() + if(show_nav_computer_icon) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "airlock", TRUE) /datum/armor/door_poddoor melee = 50 diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index 56e2f5a9743b2..52c12835c2797 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -10,6 +10,7 @@ max_integrity = 100 recipe_type = /datum/crafting_recipe/shutters animation_sound = 'sound/machines/shutter.ogg' + show_nav_computer_icon = FALSE /obj/machinery/door/poddoor/shutters/animation_length(animation) switch(animation) diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm index d39d065232426..0f02d2ee3a7ef 100644 --- a/code/game/machinery/machine_frame.dm +++ b/code/game/machinery/machine_frame.dm @@ -259,8 +259,7 @@ if(play_sound && !no_sound) replacer.play_rped_sound() - if(replacer.works_from_distance) - user.Beam(src, icon_state = "rped_upgrade", time = 0.5 SECONDS) + return TRUE /obj/structure/frame/machine/can_be_unfasten_wrench(mob/user, silent) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index aabbf4be950fc..fd106ce10cdd7 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -483,13 +483,17 @@ DEFINE_BITFIELD(turret_flags, list( else if(iscarbon(A)) var/mob/living/carbon/C = A - //If not emagged, only target carbons that can use items - if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || !(C.mobility_flags & MOBILITY_USE))) - continue - - //If emagged, target all but dead carbons - if(mode == TURRET_LETHAL && C.stat == DEAD) - continue + switch(mode) + //If not emagged, only target carbons that can use items + if(TURRET_STUN) + if(!(C.mobility_flags & MOBILITY_USE)) + continue + if(HAS_TRAIT(C, TRAIT_INCAPACITATED)) + continue + //If emagged, target all but dead carbons + if(TURRET_LETHAL) + if(C.stat == DEAD) + continue //if the target is a human and not in our faction, analyze threat level if(ishuman(C) && !in_faction(C)) @@ -758,6 +762,7 @@ DEFINE_BITFIELD(turret_flags, list( /obj/machinery/porta_turret/syndicate/Initialize(mapload) . = ..() AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "turret", FALSE) /obj/machinery/porta_turret/syndicate/setup() return @@ -840,6 +845,7 @@ DEFINE_BITFIELD(turret_flags, list( return TRUE /obj/machinery/porta_turret/ai + scan_range = /obj/projectile/energy/electrode/ai_turrets::range + 1 turret_flags = TURRET_FLAG_SHOOT_CRIMINALS | TURRET_FLAG_SHOOT_ANOMALOUS | TURRET_FLAG_SHOOT_HEADS /obj/machinery/porta_turret/ai/assess_perp(mob/living/carbon/human/perp) diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm index 49f00741895fe..3e3fc5af30757 100644 --- a/code/game/machinery/stasis.dm +++ b/code/game/machinery/stasis.dm @@ -14,6 +14,9 @@ fair_market_price = 10 payment_department = ACCOUNT_MED interaction_flags_click = ALLOW_SILICON_REACH + use_power = IDLE_POWER_USE + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 3 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 3 var/stasis_enabled = TRUE var/last_stasis_sound = FALSE var/stasis_can_toggle = 0 @@ -25,6 +28,20 @@ AddElement(/datum/element/elevation, pixel_shift = 6) update_buckle_vars(dir) +/obj/machinery/stasis/RefreshParts() + . = ..() + + var/energy_rating = 0 + for(var/datum/stock_part/part in component_parts) + energy_rating += part.energy_rating() + + for(var/obj/item/stock_parts/part in component_parts) + energy_rating += part.energy_rating + + idle_power_usage = initial(idle_power_usage) / (energy_rating/2) + active_power_usage = initial(active_power_usage) / (energy_rating/2) + update_current_power_usage() + /obj/machinery/stasis/examine(mob/user) . = ..() . += span_notice("Alt-click to [stasis_enabled ? "turn off" : "turn on"] the machine.") diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index c46f6b351543d..262e014ee0b79 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -33,7 +33,7 @@ /obj/machinery/teleport/hub/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Probability of malfunction decreased by [(accuracy*25)-25]%.") + . += span_notice("The status display reads: Success chance is [70 + (accuracy * 10)]%.") /obj/machinery/teleport/hub/proc/link_power_station() if(power_station) diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 21eff5028b57e..14e56b3ed69f3 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,6 +1,6 @@ /obj/effect/decal/cleanable gender = PLURAL - layer = FLOOR_CLEAN_LAYER + layer = CLEANABLE_FLOOR_OBJECT_LAYER var/list/random_icon_states = null ///I'm sorry but cleanable/blood code is ass, and so is blood_DNA var/blood_state = "" @@ -15,6 +15,9 @@ var/datum/reagent/decal_reagent ///The amount of reagent this decal holds, if decal_reagent is defined var/reagent_amount = 0 + /// If TRUE, gains TRAIT_MOPABLE on init - thus this cleanable will cleaned if its turf is cleaned + /// Set to FALSE for things that hang high on the walls or things which generally shouldn't be mopped up + var/is_mopped = TRUE /// Creates a cleanable decal on a turf /// Use this if your decal is one of one, and thus we should not spawn it if it's there already @@ -40,6 +43,9 @@ handle_merge_decal(C) return INITIALIZE_HINT_QDEL + if(is_mopped) + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) + if(LAZYLEN(diseases)) var/list/datum/disease/diseases_to_add = list() for(var/datum/disease/D in diseases) diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index bc7923ac0ed47..f2543f33c867b 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -24,10 +24,12 @@ icon = 'icons/effects/blood.dmi' icon_state = "xgib1" plane = GAME_PLANE - layer = BELOW_OBJ_LAYER + layer = GIB_LAYER random_icon_states = list("xgib1", "xgib2", "xgib3", "xgib4", "xgib5", "xgib6") mergeable_decal = FALSE + is_mopped = TRUE // probably shouldn't be, but janitor powercreep + /obj/effect/decal/cleanable/xenoblood/xgibs/Initialize(mapload) . = ..() RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, PROC_REF(on_pipe_eject)) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index fcac7b06601c3..bf9493ac4fec1 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -75,6 +75,7 @@ plane = GAME_PLANE vis_flags = VIS_INHERIT_PLANE alpha = 180 + is_mopped = FALSE /obj/effect/decal/cleanable/blood/splatter/over_window/NeverShouldHaveComeHere(turf/here_turf) return isgroundlessturf(here_turf) @@ -111,7 +112,7 @@ desc = "They look bloody and gruesome." icon = 'icons/effects/blood.dmi' icon_state = "gib1" - layer = BELOW_OBJ_LAYER + layer = GIB_LAYER plane = GAME_PLANE random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6") mergeable_decal = FALSE @@ -121,6 +122,8 @@ decal_reagent = /datum/reagent/consumable/liquidgibs reagent_amount = 5 + is_mopped = TRUE // probably shouldn't be, but janitor powercreep + /obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases) . = ..() AddElement(/datum/element/squish_sound) @@ -356,6 +359,7 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache) random_icon_states = list("hitsplatter1", "hitsplatter2", "hitsplatter3") plane = GAME_PLANE layer = ABOVE_WINDOW_LAYER + is_mopped = FALSE /// The turf we just came from, so we can back up when we hit a wall var/turf/prev_loc /// The cached info about the blood diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index caf7428ef01fa..65d22c8d85669 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -11,7 +11,7 @@ icon = 'icons/obj/debris.dmi' icon_state = "ash" plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER mergeable_decal = FALSE beauty = -50 decal_reagent = /datum/reagent/ash @@ -153,6 +153,7 @@ resistance_flags = FLAMMABLE beauty = -100 clean_type = CLEAN_TYPE_HARD_DECAL + is_mopped = FALSE /obj/effect/decal/cleanable/cobweb/cobweb2 icon_state = "cobweb2" @@ -164,7 +165,7 @@ icon = 'icons/effects/effects.dmi' icon_state = "molten" plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER mergeable_decal = FALSE beauty = -150 clean_type = CLEAN_TYPE_HARD_DECAL @@ -251,7 +252,7 @@ desc = "A pile of chemicals. You can't quite tell what's inside it." gender = NEUTER plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER icon = 'icons/obj/debris.dmi' icon_state = "ash" @@ -330,7 +331,7 @@ icon = 'icons/obj/debris.dmi' icon_state = "paper_shreds" plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER /obj/effect/decal/cleanable/wrapping/pinata name = "pinata shreds" @@ -349,7 +350,7 @@ icon = 'icons/obj/debris.dmi' icon_state = "garbage" plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER beauty = -150 clean_type = CLEAN_TYPE_HARD_DECAL @@ -567,7 +568,9 @@ mergeable_decal = FALSE beauty = -10 plane = GAME_PLANE - layer = BELOW_OBJ_LAYER + layer = GIB_LAYER + clean_type = CLEAN_TYPE_HARD_DECAL + is_mopped = FALSE /obj/effect/decal/cleanable/rubble/Initialize(mapload) . = ..() diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 3f2957a9c9e16..7406cd910a1ba 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -6,7 +6,7 @@ icon = 'icons/mob/silicon/robots.dmi' icon_state = "gib1" plane = GAME_PLANE - layer = BELOW_OBJ_LAYER + layer = GIB_LAYER random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") blood_state = BLOOD_STATE_OIL bloodiness = BLOOD_AMOUNT_PER_DECAL @@ -14,6 +14,8 @@ beauty = -50 clean_type = CLEAN_TYPE_BLOOD + is_mopped = TRUE // probably shouldn't be, but janitor powercreep + /obj/effect/decal/cleanable/robot_debris/Initialize(mapload) . = ..() RegisterSignal(src, COMSIG_MOVABLE_PIPE_EJECTING, PROC_REF(on_pipe_eject)) diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index e27e6f91337fe..2e60cfb8bda41 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -15,7 +15,7 @@ if(isclosedturf(loc) && loc.density) // allows for wall graffiti to be seen SET_PLANE_IMPLICIT(src, GAME_PLANE) - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER if(e_name) name = e_name if(desc_override) diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index dc51e5079f4c8..bc9f8a0bfd639 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -84,7 +84,7 @@ icon = 'icons/effects/eldritch.dmi' icon_state = "cosmic_carpet" anchored = TRUE - layer = BELOW_OBJ_LAYER + layer = GIB_LAYER density = FALSE can_atmos_pass = ATMOS_PASS_NO initial_duration = 30 SECONDS diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index cd2ec6c6476f7..b83d9a78ac37f 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -22,6 +22,9 @@ ///Icon file for right inhand overlays var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' + /// Angle of the icon, used for piercing and slashing attack animations, clockwise from *east-facing* sprites + var/icon_angle = 0 + ///Icon file for mob worn overlays. var/icon/worn_icon ///Icon state for mob worn overlays, if null the normal icon_state will be used. @@ -178,7 +181,7 @@ ///for flags such as [GLASSESCOVERSEYES] var/flags_cover = 0 var/heat = 0 - ///All items with sharpness of SHARP_EDGED or higher will automatically get the butchering component. + /// All items with sharpness of SHARP_EDGED or higher will automatically get the butchering component. var/sharpness = NONE ///How a tool acts when you use it on something, such as wirecutters cutting wires while multitools measure power @@ -1593,44 +1596,162 @@ // This is instant on byond's end, but to our clients this looks like a quick drop animate(src, alpha = old_alpha, pixel_x = old_x, pixel_y = old_y, transform = old_transform, time = 3, easing = CUBIC_EASING) -/atom/movable/proc/do_item_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item) - var/image/attack_image - if(visual_effect_icon) - attack_image = image(icon = 'icons/effects/effects.dmi', icon_state = visual_effect_icon) - else if(used_item) - attack_image = image(icon = used_item) +/atom/movable/proc/do_item_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, animation_type = ATTACK_ANIMATION_BLUNT) + if (visual_effect_icon) + var/image/attack_image = image(icon = 'icons/effects/effects.dmi', icon_state = visual_effect_icon) attack_image.plane = attacked_atom.plane + 1 - // Scale the icon. attack_image.transform *= 0.4 // The icon should not rotate. attack_image.appearance_flags = APPEARANCE_UI + var/atom/movable/flick_visual/attack = attacked_atom.flick_overlay_view(attack_image, 1 SECONDS) + var/matrix/copy_transform = new(transform) + animate(attack, alpha = 175, transform = copy_transform.Scale(0.75), time = 0.3 SECONDS) + animate(time = 0.1 SECONDS) + animate(alpha = 0, time = 0.3 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) + return - // Set the direction of the icon animation. - var/direction = get_dir(src, attacked_atom) - if(direction & NORTH) - attack_image.pixel_y = -12 - else if(direction & SOUTH) - attack_image.pixel_y = 12 - - if(direction & EAST) - attack_image.pixel_x = -14 - else if(direction & WEST) - attack_image.pixel_x = 14 - - if(!direction) // Attacked self?! - attack_image.pixel_y = 12 - attack_image.pixel_x = 5 * (prob(50) ? 1 : -1) - - if(!attack_image) + if (isnull(used_item)) return + var/image/attack_image = image(icon = used_item) + attack_image.plane = attacked_atom.plane + 1 + // Scale the icon. + attack_image.transform *= 0.5 + // The icon should not rotate. + attack_image.appearance_flags = APPEARANCE_UI + var/atom/movable/flick_visual/attack = attacked_atom.flick_overlay_view(attack_image, 1 SECONDS) var/matrix/copy_transform = new(transform) + var/x_sign = 0 + var/y_sign = 0 + var/direction = get_dir(src, attacked_atom) + if (direction & NORTH) + y_sign = -1 + else if (direction & SOUTH) + y_sign = 1 + + if (direction & EAST) + x_sign = -1 + else if (direction & WEST) + x_sign = 1 + + // Attacking self, or something on the same turf as us + if (!direction) + y_sign = 1 + // Not a fan of this, but its the "cleanest" way to animate this + x_sign = 0.25 * (prob(50) ? 1 : -1) + // For piercing attacks + direction = SOUTH + // And animate the attack! - animate(attack, alpha = 175, transform = copy_transform.Scale(0.75), pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 0.3 SECONDS) - animate(time = 0.1 SECONDS) - animate(alpha = 0, time = 0.3 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) + switch (animation_type) + if (ATTACK_ANIMATION_BLUNT) + attack.pixel_x = 14 * x_sign + attack.pixel_y = 12 * y_sign + animate(attack, alpha = 175, transform = copy_transform.Scale(0.75), pixel_x = 4 * x_sign, pixel_y = 3 * y_sign, time = 0.2 SECONDS) + animate(time = 0.1 SECONDS) + animate(alpha = 0, time = 0.1 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) + + if (ATTACK_ANIMATION_PIERCE) + var/attack_angle = dir2angle(direction) + rand(-7, 7) + // Deducting 90 because we're assuming that icon_angle of 0 means an east-facing sprite + var/anim_angle = attack_angle - 90 - used_item.icon_angle + var/angle_mult = 1 + if (x_sign && y_sign) + angle_mult = 1.4 + attack.pixel_x = 22 * x_sign * angle_mult + attack.pixel_y = 18 * y_sign * angle_mult + attack.transform = attack.transform.Turn(anim_angle) + copy_transform = copy_transform.Turn(anim_angle) + animate( + attack, + pixel_x = (22 * x_sign - 12 * sin(attack_angle)) * angle_mult, + pixel_y = (18 * y_sign - 8 * cos(attack_angle)) * angle_mult, + time = 0.1 SECONDS, + easing = CUBIC_EASING|EASE_IN, + ) + animate( + attack, + alpha = 175, + transform = copy_transform.Scale(0.75), + pixel_x = (22 * x_sign + 26 * sin(attack_angle)) * angle_mult, + pixel_y = (18 * y_sign + 22 * cos(attack_angle)) * angle_mult, + time = 0.3 SECONDS, + easing = CUBIC_EASING|EASE_OUT, + ) + animate( + alpha = 0, + pixel_x = -3 * -(x_sign + sin(attack_angle)), + pixel_y = -2 * -(y_sign + cos(attack_angle)), + time = 0.1 SECONDS, + easing = CIRCULAR_EASING|EASE_OUT + ) + + if (ATTACK_ANIMATION_SLASH) + attack.pixel_x = 18 * x_sign + attack.pixel_y = 14 * y_sign + var/x_rot_sign = 0 + var/y_rot_sign = 0 + var/attack_dir = (prob(50) ? 1 : -1) + var/anim_angle = dir2angle(direction) - 90 - used_item.icon_angle + + if (x_sign) + y_rot_sign = attack_dir + if (y_sign) + x_rot_sign = attack_dir + + // Animations are flipped, so flip us too! + if (x_sign > 0 || y_sign < 0) + attack_dir *= -1 + + // We're swinging diagonally, use separate logic + var/anim_dir = attack_dir + if (x_sign && y_sign) + if (attack_dir < 0) + x_rot_sign = -x_sign * 1.4 + y_rot_sign = 0 + else + x_rot_sign = 0 + y_rot_sign = -y_sign * 1.4 + + // Flip us if we've been flipped *unless* we're flipped due to both axis + if ((x_sign < 0 && y_sign > 0) || (x_sign > 0 && y_sign < 0)) + anim_dir *= -1 + + attack.pixel_x += 10 * x_rot_sign + attack.pixel_y += 8 * y_rot_sign + attack.transform = attack.transform.Turn(anim_angle - 45 * anim_dir) + copy_transform = copy_transform.Scale(0.75) + animate(attack, alpha = 175, time = 0.3 SECONDS, flags = ANIMATION_PARALLEL) + animate(time = 0.1 SECONDS) + animate(alpha = 0, time = 0.1 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) + + animate(attack, transform = copy_transform.Turn(anim_angle + 45 * anim_dir), time = 0.3 SECONDS, flags = ANIMATION_PARALLEL) + + var/x_return = 10 * -x_rot_sign + var/y_return = 8 * -y_rot_sign + + if (!x_rot_sign) + x_return = 18 * x_sign + if (!y_rot_sign) + y_return = 14 * y_sign + + var/angle_mult = 1 + if (x_sign && y_sign) + angle_mult = 1.4 + if (attack_dir > 0) + x_return = 8 * x_sign + y_return = 14 * y_sign + else + x_return = 18 * x_sign + y_return = 6 * y_sign + + animate(attack, pixel_x = 4 * x_sign * angle_mult, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_IN, flags = ANIMATION_PARALLEL) + animate(pixel_x = x_return, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_OUT) + + animate(attack, pixel_y = 3 * y_sign * angle_mult, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_IN, flags = ANIMATION_PARALLEL) + animate(pixel_y = y_return, time = 0.2 SECONDS, easing = CIRCULAR_EASING | EASE_OUT) /// Common proc used by painting tools like spraycans and palettes that can access the entire 24 bits color space. /obj/item/proc/pick_painting_tool_color(mob/user, default_color) diff --git a/code/game/objects/items/boxcutter.dm b/code/game/objects/items/boxcutter.dm index 58be269bacddf..5452cd013fb82 100644 --- a/code/game/objects/items/boxcutter.dm +++ b/code/game/objects/items/boxcutter.dm @@ -5,6 +5,7 @@ icon_state = "boxcutter" inhand_icon_state = "boxcutter" base_icon_state = "boxcutter" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/equipment/boxcutter_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/boxcutter_righthand.dmi' inhand_icon_state = null diff --git a/code/game/objects/items/broom.dm b/code/game/objects/items/broom.dm index 32636b1a99c81..a48dfa1b94c1c 100644 --- a/code/game/objects/items/broom.dm +++ b/code/game/objects/items/broom.dm @@ -7,6 +7,7 @@ icon = 'icons/obj/service/janitor.dmi' icon_state = "broom0" base_icon_state = "broom" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' force = 8 diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 8045646b075ca..509a3a638560c 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -5,6 +5,7 @@ desc = "A versatile power tool. Useful for limbing trees and delimbing humans." icon = 'icons/obj/weapons/chainsaw.dmi' icon_state = "chainsaw" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/game/objects/items/courtroom.dm b/code/game/objects/items/courtroom.dm index 8baba2843143c..c8907cbe9366c 100644 --- a/code/game/objects/items/courtroom.dm +++ b/code/game/objects/items/courtroom.dm @@ -7,6 +7,7 @@ desc = "Order, order! No bombs in my courthouse." icon = 'icons/obj/weapons/hammer.dmi' icon_state = "gavelhammer" + icon_angle = -135 force = 5 throwforce = 6 w_class = WEIGHT_CLASS_SMALL diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm index fb6400fc7b36c..9af69c33d7fad 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -34,6 +34,7 @@ icon = 'icons/obj/weapons/club.dmi' icon_state = "hypertool" inhand_icon_state = "hypertool" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' toolspeed = 0.1 diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 4d538f46184d9..bc3a12a4fe540 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -16,6 +16,7 @@ icon = 'icons/obj/devices/tool.dmi' icon_state = "multitool" inhand_icon_state = "multitool" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' force = 5 @@ -292,6 +293,7 @@ desc = "Optimised version of a regular multitool. Streamlines processes handled by its internal microchip." icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_multitool" + icon_angle = 0 toolspeed = 0.5 #undef PROXIMITY_NEAR diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index ac9cbfec8211f..f00ac8128960b 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -351,7 +351,8 @@ if(isliving(talking_movable)) var/mob/living/talking_living = talking_movable var/volume_modifier = (talking_living.client?.prefs.read_preference(/datum/preference/numeric/sound_radio_noise)) - if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON && !LAZYACCESS(message_mods, MODE_SEQUENTIAL)) + if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON && !LAZYACCESS(message_mods, MODE_SEQUENTIAL) && COOLDOWN_FINISHED(src, audio_cooldown)) + COOLDOWN_START(src, audio_cooldown, 0.5 SECONDS) var/sound/radio_noise = sound('sound/items/radio/radio_talk.ogg', volume = volume_modifier) radio_noise.frequency = get_rand_frequency_low_range() SEND_SOUND(talking_living, radio_noise) @@ -439,7 +440,7 @@ COOLDOWN_START(src, audio_cooldown, 0.5 SECONDS) var/sound/radio_receive = sound('sound/items/radio/radio_receive.ogg', volume = volume_modifier) radio_receive.frequency = get_rand_frequency_low_range() - SEND_SOUND(holder, radio_noise) + SEND_SOUND(holder, radio_receive) if((SPAN_COMMAND in spans) && COOLDOWN_FINISHED(src, important_audio_cooldown)) COOLDOWN_START(src, important_audio_cooldown, 0.5 SECONDS) var/sound/radio_important = sound('sound/items/radio/radio_important.ogg', volume = volume_modifier) diff --git a/code/game/objects/items/devices/scanners/scanner_wand.dm b/code/game/objects/items/devices/scanners/scanner_wand.dm index 18dfc8200398b..bba046bd2fec9 100644 --- a/code/game/objects/items/devices/scanners/scanner_wand.dm +++ b/code/game/objects/items/devices/scanners/scanner_wand.dm @@ -3,6 +3,7 @@ icon = 'icons/obj/devices/scanner.dmi' icon_state = "scanner_wand" inhand_icon_state = "healthanalyzer" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' desc = "A wand that medically scans people. Inserting it into a medical kiosk makes it able to perform a health scan on the patient." diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index d8b7c1999aea4..341231d767a14 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -329,7 +329,7 @@ effective or pretty fucking useless. /obj/item/jammer name = "radio jammer" - desc = "Device used to disrupt nearby radio communication. Alternate function creates a powerful distruptor wave which disables all nearby listening devices." + desc = "Device used to disrupt nearby radio communication. Alternate function creates a powerful disruptor wave which disables all nearby listening devices." icon = 'icons/obj/devices/syndie_gadget.dmi' icon_state = "jammer" var/active = FALSE @@ -342,7 +342,7 @@ effective or pretty fucking useless. register_context() /obj/item/jammer/add_context(atom/source, list/context, obj/item/held_item, mob/user) - context[SCREENTIP_CONTEXT_LMB] = "Release distruptor wave" + context[SCREENTIP_CONTEXT_LMB] = "Release disruptor wave" context[SCREENTIP_CONTEXT_RMB] = "Toggle" return CONTEXTUAL_SCREENTIP_SET @@ -352,8 +352,8 @@ effective or pretty fucking useless. user.balloon_alert(user, "on cooldown!") return - user.balloon_alert(user, "distruptor wave released!") - to_chat(user, span_notice("You release a distruptor wave, disabling all nearby radio devices.")) + user.balloon_alert(user, "disruptor wave released!") + to_chat(user, span_notice("You release a disruptor wave, disabling all nearby radio devices.")) for (var/atom/potential_owner in view(7, user)) disable_radios_on(potential_owner) COOLDOWN_START(src, jam_cooldown, jam_cooldown_duration) @@ -379,8 +379,8 @@ effective or pretty fucking useless. user.balloon_alert(user, "out of reach!") return - interacting_with.balloon_alert(user, "radio distrupted!") - to_chat(user, span_notice("You release a directed distruptor wave, disabling all radio devices on [interacting_with].")) + interacting_with.balloon_alert(user, "radio disrupted!") + to_chat(user, span_notice("You release a directed disruptor wave, disabling all radio devices on [interacting_with].")) disable_radios_on(interacting_with) return ITEM_INTERACT_SUCCESS diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index aa98d325a7d46..ef1e4ebdee471 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -89,6 +89,7 @@ result = rigged_value . = result + playsound(src, 'sound/items/dice_roll.ogg', 50, TRUE) var/fake_result = roll(sides)//Daredevil isn't as good as he used to be var/comment = "" diff --git a/code/game/objects/items/dualsaber.dm b/code/game/objects/items/dualsaber.dm index 7f2e54984cae9..dd5ef68f0d395 100644 --- a/code/game/objects/items/dualsaber.dm +++ b/code/game/objects/items/dualsaber.dm @@ -2,13 +2,14 @@ * Double-Bladed Energy Swords - Cheridan */ /obj/item/dualsaber + name = "double-bladed energy sword" + desc = "Handle with care." icon = 'icons/obj/weapons/transforming_energy.dmi' icon_state = "dualsaber0" inhand_icon_state = "dualsaber0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - name = "double-bladed energy sword" - desc = "Handle with care." force = 3 throwforce = 5 throw_speed = 3 @@ -73,7 +74,9 @@ set_light_on(FALSE) /obj/item/dualsaber/get_sharpness() - return HAS_TRAIT(src, TRAIT_WIELDED) && sharpness + if (!HAS_TRAIT(src, TRAIT_WIELDED)) + return NONE + return ..() /obj/item/dualsaber/update_icon_state() icon_state = inhand_icon_state = HAS_TRAIT(src, TRAIT_WIELDED) ? "dualsaber[saber_color][HAS_TRAIT(src, TRAIT_WIELDED)]" : "dualsaber0" diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index b4150ecb72fea..a3b2422af61d0 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -5,6 +5,7 @@ icon_state = "fire_extinguisher0" worn_icon_state = "fire_extinguisher" inhand_icon_state = "fire_extinguisher" + icon_angle = 90 hitsound = 'sound/items/weapons/smash.ogg' pickup_sound = 'sound/items/handling/gas_tank/gas_tank_pick_up.ogg' drop_sound = 'sound/items/handling/gas_tank/gas_tank_drop.ogg' diff --git a/code/game/objects/items/fireaxe.dm b/code/game/objects/items/fireaxe.dm index 162af703ad2bd..3b6727d692367 100644 --- a/code/game/objects/items/fireaxe.dm +++ b/code/game/objects/items/fireaxe.dm @@ -4,13 +4,13 @@ GLOBAL_DATUM(bridge_axe, /obj/item/fireaxe) * Fireaxe */ /obj/item/fireaxe // DEM AXES MAN, marker -Agouri + name = "fire axe" + desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?" icon = 'icons/obj/weapons/fireaxe.dmi' icon_state = "fireaxe0" base_icon_state = "fireaxe" lefthand_file = 'icons/mob/inhands/weapons/axes_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/axes_righthand.dmi' - name = "fire axe" - desc = "Truly, the weapon of a madman. Who would think to fight fire with an axe?" force = 5 throwforce = 15 demolition_mod = 1.25 @@ -73,10 +73,11 @@ GLOBAL_DATUM(bridge_axe, /obj/item/fireaxe) * Bone Axe */ /obj/item/fireaxe/boneaxe // Blatant imitation of the fireaxe, but made out of bone. - icon_state = "bone_axe0" - base_icon_state = "bone_axe" name = "bone axe" desc = "A large, vicious axe crafted out of several sharpened bone plates and crudely tied together. Made of monsters, by killing monsters, for killing monsters." + icon_state = "bone_axe0" + base_icon_state = "bone_axe" + icon_angle = 180 force_unwielded = 5 force_wielded = 23 @@ -84,10 +85,11 @@ GLOBAL_DATUM(bridge_axe, /obj/item/fireaxe) * Metal Hydrogen Axe */ /obj/item/fireaxe/metal_h2_axe - icon_state = "metalh2_axe0" - base_icon_state = "metalh2_axe" name = "metallic hydrogen axe" desc = "A lightweight crowbar with an extreme sharp fire axe head attached. It trades its heft as a weapon by making it easier to carry around when holstered to suits without having to sacrifice your backpack." + icon_state = "metalh2_axe0" + base_icon_state = "metalh2_axe" + icon_angle = -45 force_unwielded = 5 force_wielded = 15 demolition_mod = 2 @@ -97,10 +99,10 @@ GLOBAL_DATUM(bridge_axe, /obj/item/fireaxe) //boarding axe /obj/item/fireaxe/boardingaxe - icon_state = "boarding_axe0" - base_icon_state = "boarding_axe" name = "boarding axe" desc = "A hulking cleaver that feels like a burden just looking at it. Seems excellent at halving obstacles like windows, airlocks, barricades and people." + icon_state = "boarding_axe0" + base_icon_state = "boarding_axe" force_unwielded = 5 force_wielded = 30 demolition_mod = 3 diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm index 48e7a2a21b1ae..3a41514413c61 100644 --- a/code/game/objects/items/food/bread.dm +++ b/code/game/objects/items/food/bread.dm @@ -420,6 +420,7 @@ /obj/item/food/baguette/combat block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED + icon_angle = -45 /// Force when wielded as a sword by a mime var/active_force = 20 /// Block chance when wielded as a sword by a mime diff --git a/code/game/objects/items/grenades/_grenade.dm b/code/game/objects/items/grenades/_grenade.dm index 780311fa4d149..664a31c226207 100644 --- a/code/game/objects/items/grenades/_grenade.dm +++ b/code/game/objects/items/grenades/_grenade.dm @@ -159,7 +159,7 @@ if(istype(user)) user.add_mob_memory(/datum/memory/bomb_planted, antagonist = src) active = TRUE - icon_state = initial(icon_state) + "_active" + icon_state = (base_icon_state || initial(icon_state)) + "_active" SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) addtimer(CALLBACK(src, PROC_REF(detonate)), isnull(delayoverride)? det_time : delayoverride) @@ -255,7 +255,7 @@ if(det_time == 0) det_time = "Instant" else - det_time = num2text(det_time * 0.1) + det_time = num2text(det_time * 0.1) var/old_selection = possible_fuse_time.Find(det_time) //Position of det_time in the list if(old_selection >= possible_fuse_time.len) diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index 2300d2c67174d..2bc9401c77451 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -54,6 +54,7 @@ /obj/item/grenade/stingbang name = "stingbang" icon_state = "timeg_locked" + base_icon_state = "timeg" inhand_icon_state = "flashbang" lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' @@ -64,6 +65,8 @@ /obj/item/grenade/stingbang/mega name = "mega stingbang" + icon_state = "timeg_mega_locked" + base_icon_state = "timeg_mega" shrapnel_type = /obj/projectile/bullet/pellet/stingball/mega shrapnel_radius = 12 @@ -122,6 +125,7 @@ name = "rotfrag grenade" desc = "A grenade that generates more shrapnel the more you rotate it in your hand after pulling the pin. This one releases shrapnel shards." icon_state = "timeg_locked" + base_icon_state = "timeg" inhand_icon_state = "flashbang" lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index befd9a619a3f4..a1ea5b1011a2e 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -515,7 +515,7 @@ to_chat(taker, span_nicegreen("[offerer] gives you \a [blown_kiss][cheek_kiss ? " on the cheek" : ""]!")) offerer.face_atom(taker) taker.face_atom(offerer) - offerer.do_item_attack_animation(taker, used_item=src) + offerer.do_item_attack_animation(taker, used_item = src) //We're still firing a shot here because I don't want to deal with some weird edgecase where direct impacting them with the projectile causes it to freak out because there's no angle or something blown_kiss.original = taker blown_kiss.fired_from = offerer @@ -539,6 +539,12 @@ color = COLOR_SYNDIE_RED kiss_type = /obj/projectile/kiss/syndie +/obj/item/hand_item/kisser/ink + name = "ink kiss" + desc = "Is that a blot of ink in your pocket or are you just happy to see me?" + color = COLOR_ALMOST_BLACK + kiss_type = /obj/projectile/kiss/ink + /obj/projectile/kiss name = "kiss" icon = 'icons/mob/simple/animal.dmi' @@ -639,6 +645,22 @@ var/obj/item/organ/heart/dont_go_breakin_my_heart = heartbreakee.get_organ_slot(ORGAN_SLOT_HEART) dont_go_breakin_my_heart.apply_organ_damage(999) +/obj/projectile/kiss/ink + name = "ink kiss" + color = COLOR_ALMOST_BLACK + damage = /obj/projectile/ink_spit::damage + damage_type = /obj/projectile/ink_spit::damage_type + armor_flag = /obj/projectile/ink_spit::armor_flag + armour_penetration = /obj/projectile/ink_spit::armour_penetration + impact_effect_type = /obj/projectile/ink_spit::impact_effect_type + hitsound = /obj/projectile/ink_spit::hitsound + hitsound_wall = /obj/projectile/ink_spit::hitsound_wall + +/obj/projectile/kiss/ink/on_hit(atom/target, blocked, pierce_hit) + . = ..() + var/obj/projectile/ink_spit/ink_spit = new (target) + ink_spit.on_hit(target) + // Based on energy gun characteristics /obj/projectile/kiss/syndie name = "syndie kiss" diff --git a/code/game/objects/items/inspector.dm b/code/game/objects/items/inspector.dm index 7783dcff072b5..d1092c343266a 100644 --- a/code/game/objects/items/inspector.dm +++ b/code/game/objects/items/inspector.dm @@ -8,7 +8,9 @@ */ /obj/item/inspector name = "\improper N-spect scanner" - desc = "Central Command standard issue inspection device. Can perform either wide area scans that central command can use to verify the security of the station, or detailed scan. Can scan people for contraband on their person or items being contraband." + desc = "Central Command standard issue inspection device. \ + Performs wide area scan reports for inspectors to use to verify the security and integrity of the station. \ + Can additionally be used for precision scans to determine if an item contains, or is itself, contraband." icon = 'icons/obj/devices/scanner.dmi' icon_state = "inspector" worn_icon_state = "salestagger" @@ -88,14 +90,16 @@ /obj/item/inspector/examine(mob/user) . = ..() + . += span_info("Use in-hand to scan the local area, creating an encrypted security inspection.") + . += span_info("Use on an item to scan if it contains, or is, contraband.") if(!cell_cover_open) - . += "Its cell cover is closed. It looks like it could be pried out, but doing so would require an appropriate tool." + . += span_notice("Its cell cover is closed. It looks like it could be pried out, but doing so would require an appropriate tool.") return - . += "Its cell cover is open, exposing the cell slot. It looks like it could be pried in, but doing so would require an appropriate tool." + . += span_notice("Its cell cover is open, exposing the cell slot. It looks like it could be pried in, but doing so would require an appropriate tool.") if(!cell) - . += "The slot for a cell is empty." + . += span_notice("The slot for a cell is empty.") else - . += "\The [cell] is firmly in place. [span_info("Ctrl-click with an empty hand to remove it.")]" + . += span_notice("\The [cell] is firmly in place. Ctrl-click with an empty hand to remove it.") /obj/item/inspector/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!user.Adjacent(interacting_with)) diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 19de11388abf4..1519d20a0f3b9 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -22,6 +22,7 @@ name = "fork" desc = "Pointy." icon_state = "fork" + icon_angle = -90 force = 4 w_class = WEIGHT_CLASS_TINY throwforce = 0 @@ -107,6 +108,7 @@ name = "Kitchen Toolset" icon = 'icons/obj/items_cyborg.dmi' icon_state = "sili_knife" + icon_angle = 0 desc = "A breakthrough in synthetic engineering, this tool is a knife programmed to dull when not used for cooking purposes, and can exchange the blade for a rolling pin" force = 0 throwforce = 0 @@ -152,6 +154,7 @@ icon_state = "rolling_pin" worn_icon_state = "rolling_pin" inhand_icon_state = "rolling_pin" + icon_angle = -45 force = 8 throwforce = 5 throw_speed = 3 @@ -185,6 +188,7 @@ desc = "Just be careful your food doesn't melt the spoon first." icon_state = "spoon" base_icon_state = "spoon" + icon_angle = -90 w_class = WEIGHT_CLASS_TINY obj_flags = CONDUCTS_ELECTRICITY force = 2 @@ -326,6 +330,7 @@ icon_state = "ladle" base_icon_state = "ladle" inhand_icon_state = "spoon" + icon_angle = 90 custom_price = PAYCHECK_LOWER * 4 spoon_sip_size = 3 // just a taste diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm index e089a5bc55d7d..fc7836bbc047d 100644 --- a/code/game/objects/items/knives.dm +++ b/code/game/objects/items/knives.dm @@ -7,6 +7,7 @@ righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi' inhand_icon_state = "knife" worn_icon_state = "knife" + icon_angle = -90 desc = "The original knife, it is said that all other knives are only copies of this one." obj_flags = CONDUCTS_ELECTRICITY force = 10 @@ -17,13 +18,15 @@ throw_speed = 3 throw_range = 6 custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 6) - attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("slash", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_EDGED armor_type = /datum/armor/item_knife wound_bonus = 5 bare_wound_bonus = 15 tool_behaviour = TOOL_KNIFE + var/list/alt_continuous = list("stabs", "pierces", "shanks") + var/list/alt_simple = list("stab", "pierce", "shank") /datum/armor/item_knife fire = 50 @@ -33,6 +36,9 @@ . = ..() AddElement(/datum/element/eyestab) set_butchering() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + make_stabby() ///Adds the butchering component, used to override stats for special cases /obj/item/knife/proc/set_butchering() @@ -43,6 +49,10 @@ ) //bonus chance increases depending on force +///Adds alt sharpness component, used for overrides +/obj/item/knife/proc/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) + /obj/item/knife/suicide_act(mob/living/user) user.visible_message(pick(span_suicide("[user] is slitting [user.p_their()] wrists with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide."), \ span_suicide("[user] is slitting [user.p_their()] throat with the [src.name]! It looks like [user.p_theyre()] trying to commit suicide."), \ @@ -56,6 +66,7 @@ icon_state = "bone_blade" inhand_icon_state = "bone_blade" worn_icon_state = "bone_blade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 @@ -72,6 +83,7 @@ icon = 'icons/obj/weapons/khopesh.dmi' icon_state = "bloodletter" worn_icon_state = "render" + icon_angle = -45 w_class = WEIGHT_CLASS_NORMAL /// Bleed stacks applied when an organic mob target is hit var/bleed_stacks_per_hit = 3 @@ -90,9 +102,10 @@ /obj/item/knife/butcher name = "butcher's cleaver" + desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." icon_state = "butch" inhand_icon_state = "butch" - desc = "A huge thing used for chopping and chopping up meat. This includes clowns and clown by-products." + icon_angle = -45 obj_flags = CONDUCTS_ELECTRICITY force = 15 throwforce = 10 @@ -103,12 +116,16 @@ custom_price = PAYCHECK_CREW * 5 wound_bonus = 15 +/obj/item/knife/butcher/make_stabby() + return + /obj/item/knife/hunting name = "hunting knife" - icon = 'icons/obj/weapons/stabby.dmi' desc = "Despite its name, it's mainly used for cutting meat from dead prey rather than actual hunting." + icon = 'icons/obj/weapons/stabby.dmi' inhand_icon_state = "huntingknife" icon_state = "huntingknife" + icon_angle = 180 wound_bonus = 10 /obj/item/knife/hunting/set_butchering() @@ -118,12 +135,16 @@ bonus_modifier = force + 10, \ ) +/obj/item/knife/hunting/make_stabby() + return + /obj/item/knife/combat name = "combat knife" + desc = "A military combat utility survival knife." icon = 'icons/obj/weapons/stabby.dmi' icon_state = "buckknife" worn_icon_state = "buckknife" - desc = "A military combat utility survival knife." + icon_angle = -45 embed_type = /datum/embed_data/combat_knife force = 20 throwforce = 20 @@ -141,6 +162,9 @@ . = ..() AddComponent(/datum/component/knockoff, 90, list(BODY_ZONE_PRECISE_MOUTH), slot_flags) //90% to knock off when wearing a mask +/obj/item/knife/combat/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5) + /obj/item/knife/combat/dropped(mob/living/user, slot) . = ..() if(user.get_item_by_slot(ITEM_SLOT_MASK) == src && !user.has_status_effect(/datum/status_effect/choke) && prob(20)) @@ -158,36 +182,33 @@ /obj/item/knife/combat/survival name = "survival knife" - icon = 'icons/obj/weapons/stabby.dmi' + desc = "A hunting grade survival knife." icon_state = "survivalknife" worn_icon_state = "survivalknife" embed_type = /datum/embed_data/combat_knife/weak - desc = "A hunting grade survival knife." force = 15 throwforce = 15 /obj/item/knife/combat/root name = "cahn'root dagger" - icon = 'icons/obj/weapons/stabby.dmi' + desc = "A root dagger, deceptively sharp. Perfect to hide and stab someone with, or make a couple and throw them at enemies." icon_state = "rootdagger" worn_icon_state = "root_dagger" lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' inhand_icon_state = "rootshiv" embed_type = /datum/embed_data/combat_knife/weak - desc = "A root dagger, deceptively sharp. Perfect to hide and stab someone with, or make a couple and throw them at enemies." force = 15 throwforce = 15 /obj/item/knife/combat/bone name = "bone dagger" + desc = "A sharpened bone. The bare minimum in survival." inhand_icon_state = "bone_dagger" - icon = 'icons/obj/weapons/stabby.dmi' icon_state = "bone_dagger" worn_icon_state = "bone_dagger" lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A sharpened bone. The bare minimum in survival." embed_type = /datum/embed_data/combat_knife/weak obj_flags = parent_type::obj_flags & ~CONDUCTS_ELECTRICITY force = 15 @@ -199,20 +220,21 @@ /obj/item/knife/combat/cyborg name = "cyborg knife" + desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." icon = 'icons/obj/items_cyborg.dmi' icon_state = "knife_cyborg" worn_icon_state = "knife_cyborg" //error sprite - this shouldn't have been dropped - desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." slot_flags = NONE //you can't put this in your mouth /obj/item/knife/shiv name = "glass shiv" + desc = "A makeshift glass shiv." icon = 'icons/obj/weapons/stabby.dmi' icon_state = "shiv" inhand_icon_state = "shiv" + icon_angle = -65 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "A makeshift glass shiv." obj_flags = parent_type::obj_flags & ~CONDUCTS_ELECTRICITY force = 8 throwforce = 12 @@ -221,11 +243,14 @@ armor_type = /datum/armor/none custom_materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT * 4) +/obj/item/knife/shiv/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -3) + /obj/item/knife/shiv/plasma name = "plasma shiv" + desc = "A makeshift plasma glass shiv." icon_state = "plasmashiv" inhand_icon_state = "plasmashiv" - desc = "A makeshift plasma glass shiv." force = 9 throwforce = 13 armor_type = /datum/armor/shiv_plasma @@ -242,9 +267,9 @@ /obj/item/knife/shiv/titanium name = "titanium shiv" + desc = "A makeshift titanium-infused glass shiv." icon_state = "titaniumshiv" inhand_icon_state = "titaniumshiv" - desc = "A makeshift titanium-infused glass shiv." throwforce = 14 throw_range = 7 wound_bonus = 10 @@ -262,9 +287,9 @@ /obj/item/knife/shiv/plastitanium name = "plastitanium shiv" + desc = "A makeshift titanium-infused plasma glass shiv." icon_state = "plastitaniumshiv" inhand_icon_state = "plastitaniumshiv" - desc = "A makeshift titanium-infused plasma glass shiv." force = 10 throwforce = 15 throw_speed = 4 @@ -285,9 +310,10 @@ /obj/item/knife/shiv/carrot name = "carrot shiv" + desc = "Unlike other carrots, you should probably keep this far away from your eyes." icon_state = "carrotshiv" inhand_icon_state = "carrotshiv" - desc = "Unlike other carrots, you should probably keep this far away from your eyes." + icon_angle = -45 custom_materials = null /obj/item/knife/shiv/carrot/suicide_act(mob/living/carbon/user) @@ -296,15 +322,17 @@ /obj/item/knife/shiv/parsnip name = "parsnip shiv" + desc = "Truly putting 'snip' in the 'parsnip', and it's not sub-par either!" icon_state = "parsnipshiv" inhand_icon_state = "parsnipshiv" - desc = "Truly putting 'snip' in the 'parsnip', and it's not sub-par either!" + icon_angle = -45 custom_materials = null /obj/item/knife/shiv/root name = "cahn'root shiv" + desc = "A root sharpened into a shiv. A root source of someone's stab wounds soon, most likely." icon_state = "rootshiv" inhand_icon_state = "rootshiv" - desc = "A root sharpened into a shiv. A root source of someone's stab wounds soon, most likely." + icon_angle = -45 custom_materials = null diff --git a/code/game/objects/items/maintenance_loot.dm b/code/game/objects/items/maintenance_loot.dm index 9d1c4fe676b84..1bba4f5b651de 100644 --- a/code/game/objects/items/maintenance_loot.dm +++ b/code/game/objects/items/maintenance_loot.dm @@ -8,6 +8,7 @@ icon = 'icons/obj/maintenance_loot.dmi' icon_state = "lead_pipe" inhand_icon_state = "lead_pipe" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' //wow, lore diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm index 775612026b192..03df05140388a 100644 --- a/code/game/objects/items/melee/baton.dm +++ b/code/game/objects/items/melee/baton.dm @@ -6,6 +6,7 @@ icon_state = "classic_baton" inhand_icon_state = "classic_baton" worn_icon_state = "classic_baton" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -306,6 +307,7 @@ desc = "A compact yet robust personal defense weapon. Can be concealed when folded." icon = 'icons/obj/weapons/baton.dmi' icon_state = "telebaton" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' inhand_icon_state = null @@ -396,6 +398,7 @@ icon = 'icons/obj/weapons/baton.dmi' icon_state = "contractor_baton" worn_icon_state = "contractor_baton" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -431,6 +434,7 @@ icon_state = "stunbaton" inhand_icon_state = "baton" worn_icon_state = "baton" + icon_angle = -45 force = 10 wound_bonus = 0 attack_verb_continuous = list("beats") @@ -724,6 +728,7 @@ icon_state = "stunprod" inhand_icon_state = "prod" worn_icon_state = null + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' w_class = WEIGHT_CLASS_HUGE diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index f78eec3898799..a386375b8277a 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -1,5 +1,6 @@ /obj/item/melee/energy icon = 'icons/obj/weapons/transforming_energy.dmi' + icon_angle = -45 max_integrity = 200 armor_type = /datum/armor/melee_energy attack_verb_continuous = list("hits", "taps", "pokes") @@ -63,8 +64,8 @@ sharpness_on = active_sharpness, \ hitsound_on = active_hitsound, \ w_class_on = active_w_class, \ - attack_verb_continuous_on = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts"), \ - attack_verb_simple_on = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut"), \ + attack_verb_continuous_on = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts"), \ + attack_verb_simple_on = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut"), \ ) RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) @@ -190,6 +191,14 @@ block_chance = 50 block_sound = 'sound/items/weapons/block_blade.ogg' embed_type = /datum/embed_data/esword + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/melee/energy/sword/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -10, TRAIT_TRANSFORM_ACTIVE) /obj/item/melee/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) @@ -322,8 +331,8 @@ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' hitsound = 'sound/items/weapons/blade1.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") force = 30 throwforce = 1 // Throwing or dropping the item deletes it. throw_speed = 3 @@ -333,10 +342,15 @@ w_class = WEIGHT_CLASS_BULKY /// Our linked spark system that emits from our sword. var/datum/effect_system/spark_spread/spark_system + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") //Most of the other special functions are handled in their own files. aka special snowflake code so kewl /obj/item/melee/energy/blade/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -10) spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, src) spark_system.attach(src) @@ -356,3 +370,4 @@ icon_state = "lightblade" inhand_icon_state = "lightblade" base_icon_state = "lightblade" + icon_angle = 0 diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 6ef4624f50e2a..8c26b704b13e6 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -9,6 +9,7 @@ icon_state = "chain" inhand_icon_state = "chain" worn_icon_state = "whip" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -34,18 +35,24 @@ icon = 'icons/obj/weapons/changeling_items.dmi' icon_state = "arm_blade" inhand_icon_state = "arm_blade" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' w_class = WEIGHT_CLASS_HUGE force = 20 throwforce = 10 hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_EDGED + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/melee/synthetic_arm_blade/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5) AddComponent(/datum/component/butchering, \ speed = 6 SECONDS, \ effectiveness = 80, \ @@ -58,6 +65,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "sabre" inhand_icon_state = "sabre" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY | UNIQUE_RENAME @@ -171,6 +179,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "parsnip_sabre" inhand_icon_state = "parsnip_sabre" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' force = 15 @@ -212,6 +221,7 @@ icon_state = "beesword" inhand_icon_state = "stinger" worn_icon_state = "stinger" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -248,6 +258,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "supermatter_sword_balanced" inhand_icon_state = "supermatter_sword" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' slot_flags = null @@ -304,6 +315,7 @@ ..() balanced = 0 icon_state = "supermatter_sword" + icon_angle = -45 /obj/item/melee/supermatter_sword/ex_act(severity, target) visible_message( @@ -361,6 +373,7 @@ icon = 'icons/obj/weapons/whip.dmi' icon_state = "whip" inhand_icon_state = "chain" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' worn_icon_state = "whip" @@ -385,6 +398,7 @@ icon_state = "roastingstick" inhand_icon_state = null worn_icon_state = "tele_baton" + icon_angle = -45 slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL item_flags = NONE @@ -510,6 +524,7 @@ icon_state = "default" inhand_icon_state = "default" worn_icon_state = "default_worn" + icon_angle = -45 greyscale_config = /datum/greyscale_config/cleric_mace greyscale_config_inhand_left = /datum/greyscale_config/cleric_mace_lefthand diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm index b2bd6d55d5cee..2896ce063013a 100644 --- a/code/game/objects/items/mop.dm +++ b/code/game/objects/items/mop.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/service/janitor.dmi' icon_state = "mop" inhand_icon_state = "mop" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' force = 8 diff --git a/code/game/objects/items/pitchfork.dm b/code/game/objects/items/pitchfork.dm index 1ece740d4a6df..99f714f09f3b5 100644 --- a/code/game/objects/items/pitchfork.dm +++ b/code/game/objects/items/pitchfork.dm @@ -8,6 +8,7 @@ icon = 'icons/obj/weapons/spear.dmi' icon_state = "pitchfork0" base_icon_state = "pitchfork" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' name = "pitchfork" diff --git a/code/game/objects/items/powerfist.dm b/code/game/objects/items/powerfist.dm index 77810ff3a89b6..871a6d2d3b28a 100644 --- a/code/game/objects/items/powerfist.dm +++ b/code/game/objects/items/powerfist.dm @@ -12,6 +12,7 @@ icon = 'icons/obj/antags/syndicate_tools.dmi' icon_state = "powerfist" inhand_icon_state = "powerfist" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm index 60f84e14651b9..3dab53cb8ff27 100644 --- a/code/game/objects/items/rcd/RCD.dm +++ b/code/game/objects/items/rcd/RCD.dm @@ -565,6 +565,18 @@ return owner.ui_status(user) return UI_CLOSE +/obj/item/construction/rcd/exosuit/build_delay(mob/user, delay, atom/target) + if(delay <= 0) + return TRUE + + var/obj/item/mecha_parts/mecha_equipment/rcd/module = loc + + //deconstruction can't be cancelled by ui changes + if(mode != RCD_DECONSTRUCT) + blueprint_changed = FALSE + + return module.do_after_mecha(target, user, delay) + /obj/item/construction/rcd/exosuit/get_matter(mob/user) if(silo_link) return ..() diff --git a/code/game/objects/items/rcd/RHD.dm b/code/game/objects/items/rcd/RHD.dm index ce9f211b6942e..85cdc21947b6d 100644 --- a/code/game/objects/items/rcd/RHD.dm +++ b/code/game/objects/items/rcd/RHD.dm @@ -63,6 +63,8 @@ return do_after(user, delay, target, extra_checks = CALLBACK(src, PROC_REF(blueprint_change))) /obj/item/construction/proc/blueprint_change() + PRIVATE_PROC(TRUE) + return !blueprint_changed ///used for examining the RCD and for its UI diff --git a/code/game/objects/items/religion.dm b/code/game/objects/items/religion.dm index 963274f26f268..613853787364f 100644 --- a/code/game/objects/items/religion.dm +++ b/code/game/objects/items/religion.dm @@ -337,6 +337,7 @@ desc = "It's a stick..?" icon = 'icons/obj/weapons/staff.dmi' icon_state = "godstaff-red" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' var/conversion_color = "#ffffff" @@ -427,8 +428,14 @@ force = 24 armour_penetration = 10 +/obj/item/claymore/weak/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -9) + /obj/item/claymore/weak/ceremonial desc = "A rusted claymore, once at the heart of a powerful scottish clan struck down and oppressed by tyrants, it has been passed down the ages as a symbol of defiance." force = 15 block_chance = 30 armour_penetration = 5 + +/obj/item/claymore/weak/ceremonial/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5) diff --git a/code/game/objects/items/rollertable_dock.dm b/code/game/objects/items/rollertable_dock.dm index d0067e8c67596..9b2c34bc61b41 100644 --- a/code/game/objects/items/rollertable_dock.dm +++ b/code/game/objects/items/rollertable_dock.dm @@ -9,23 +9,6 @@ . = ..() loaded = new(src) -/obj/structure/table/rolling/attackby(obj/item/wtable, mob/user, params) - if(!istype(wtable, /obj/item/rolling_table_dock)) - return ..() - var/obj/item/rolling_table_dock/rable = wtable - var/turf/target_table = get_turf(src) - if(rable.loaded) - to_chat(user, span_warning("You already have a roller table docked!")) - return - if(locate(/mob/living) in target_table) - to_chat(user, span_warning("You can't collect the table with that much on top!")) - return - else - rable.loaded = src - forceMove(rable) - user.visible_message(span_notice("[user] collects [src]."), balloon_alert(user, "you collect the [src].")) - return TRUE - /obj/item/rolling_table_dock/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) var/turf/target_turf = get_turf(interacting_with) if(target_turf.is_blocked_turf(TRUE) || (locate(/mob/living) in target_turf)) diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm index 701a0a819d002..a4adc353db3f7 100644 --- a/code/game/objects/items/shrapnel.dm +++ b/code/game/objects/items/shrapnel.dm @@ -4,6 +4,7 @@ weak_against_armour = TRUE icon = 'icons/obj/debris.dmi' icon_state = "large" + icon_angle = -45 w_class = WEIGHT_CLASS_TINY item_flags = DROPDEL sharpness = SHARP_EDGED diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index 6ad49bf836f4f..2ac99231d73b1 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -1,11 +1,12 @@ //spears /obj/item/spear + name = "spear" + desc = "A haphazardly-constructed yet still deadly weapon of ancient design." icon = 'icons/obj/weapons/spear.dmi' icon_state = "spearglass0" lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' - name = "spear" - desc = "A haphazardly-constructed yet still deadly weapon of ancient design." + icon_angle = -45 force = 10 w_class = WEIGHT_CLASS_BULKY slot_flags = ITEM_SLOT_BACK @@ -18,7 +19,7 @@ hitsound = 'sound/items/weapons/bladeslice.ogg' attack_verb_continuous = list("attacks", "pokes", "jabs", "tears", "lacerates", "gores") attack_verb_simple = list("attack", "poke", "jab", "tear", "lacerate", "gore") - sharpness = SHARP_EDGED // i know the whole point of spears is that they're pointy, but edged is more devastating at the moment so + sharpness = SHARP_POINTY max_integrity = 200 armor_type = /datum/armor/item_spear wound_bonus = -15 diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 7c922d6687d0d..e5514380bf8a4 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -32,6 +32,10 @@ var/sanitization /// How much we add to flesh_healing for burn wounds on application var/flesh_regeneration + /// Verb used when applying this object to someone + var/apply_verb = "treating" + /// Whether this item can be used on dead bodies + var/works_on_dead = FALSE /obj/item/stack/medical/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!isliving(interacting_with)) @@ -90,8 +94,8 @@ var/heal_zone = check_zone(user.zone_selected) if(!try_heal_checks(patient, user, heal_zone)) return FALSE - SSblackbox.record_feedback("nested tally", "medical_item_used", 1, list(type, auto_change_zone ? "auto" : "manual")) - patient.balloon_alert(user, "treating [parse_zone(heal_zone)]...") + SSblackbox.record_feedback("nested tally", "medical_item_used", 1, list("[auto_change_zone ? "auto" : "manual"]", "[type]")) + patient.balloon_alert(user, "[apply_verb] [parse_zone(heal_zone)]...") INVOKE_ASYNC(src, PROC_REF(try_heal), patient, user, heal_zone, FALSE, iscarbon(patient) && auto_change_zone) // auto change is useless for non-carbons return TRUE @@ -114,7 +118,6 @@ /obj/item/stack/medical/proc/try_heal(mob/living/patient, mob/living/user, healed_zone, silent = FALSE, auto_change_zone = TRUE) if(patient == user) if(!silent) - user.balloon_alert(user, "treating [parse_zone(healed_zone)]...") user.visible_message( span_notice("[user] starts to apply [src] on [user.p_them()]self..."), span_notice("You begin applying [src] on yourself..."), @@ -134,7 +137,6 @@ else if(other_delay) if(!silent) - patient.balloon_alert(user, "treating [parse_zone(healed_zone)]...") user.visible_message( span_notice("[user] starts to apply [src] on [patient]."), span_notice("You begin applying [src] on [patient]..."), @@ -181,7 +183,7 @@ var/preferred_target = check_zone(user.zone_selected) if(try_heal_checks(patient, user, preferred_target, silent = TRUE)) if(preferred_target != healed_zone) - patient.balloon_alert(user, "treating [parse_zone(preferred_target)]...") + patient.balloon_alert(user, "[apply_verb] [parse_zone(preferred_target)]...") try_heal(patient, user, preferred_target, TRUE, auto_change_zone) return @@ -211,7 +213,7 @@ var/next_picked = (preferred_target in other_affected_limbs) ? preferred_target : other_affected_limbs[1] if(next_picked != last_zone) - user.balloon_alert(user, "treating [parse_zone(next_picked)]...") + patient.balloon_alert(user, "[apply_verb] [parse_zone(next_picked)]...") try_heal(patient, user, next_picked, silent = TRUE, auto_change_zone = TRUE) /obj/item/stack/medical/proc/try_heal_manual_target(mob/living/carbon/patient, mob/living/user) @@ -223,7 +225,7 @@ var/new_zone = check_zone(user.zone_selected) if(!try_heal_checks(patient, user, new_zone)) return - patient.balloon_alert(user, "treating [parse_zone(new_zone)]...") + patient.balloon_alert(user, "[apply_verb] [parse_zone(new_zone)]...") try_heal(patient, user, new_zone, silent = TRUE, auto_change_zone = FALSE) /// Checks if the passed patient can be healed by the passed user @@ -233,10 +235,14 @@ /// Checks a bunch of stuff to see if we can heal the patient, including can_heal /// Gives a feedback if we can't ultimatly heal the patient (unless silent is TRUE) /obj/item/stack/medical/proc/try_heal_checks(mob/living/patient, mob/living/user, healed_zone, silent = FALSE) + if(!(healed_zone in GLOB.all_body_zones)) + stack_trace("Invalid zone ([healed_zone || "null"]) passed to try_heal_checks.") + healed_zone = BODY_ZONE_CHEST + if(!can_heal(patient, user, healed_zone, silent)) // has its own feedback return FALSE - if(patient.stat == DEAD) + if(!works_on_dead && patient.stat == DEAD) if(!silent) patient.balloon_alert(user, "[patient.p_theyre()] dead!") return FALSE @@ -343,6 +349,7 @@ other_delay = 2 SECONDS grind_results = list(/datum/reagent/medicine/c2/libital = 10) merge_type = /obj/item/stack/medical/bruise_pack + apply_verb = "applying to" /obj/item/stack/medical/bruise_pack/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is bludgeoning [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) @@ -365,6 +372,8 @@ splint_factor = 0.7 burn_cleanliness_bonus = 0.35 merge_type = /obj/item/stack/medical/gauze + apply_verb = "wrapping" + works_on_dead = TRUE var/obj/item/bodypart/gauzed_bodypart /obj/item/stack/medical/gauze/Destroy(force) @@ -403,7 +412,7 @@ return FALSE // gauze is only relevant for wounds, which are handled in the wounds themselves -/obj/item/stack/medical/gauze/try_heal(mob/living/patient, mob/living/user, silent, healed_zone, auto_change_zone) +/obj/item/stack/medical/gauze/try_heal(mob/living/patient, mob/living/user, healed_zone, silent, auto_change_zone) var/obj/item/bodypart/limb = patient.get_bodypart(healed_zone) var/treatment_delay = (user == patient ? self_delay : other_delay) var/any_scanned = FALSE @@ -436,8 +445,6 @@ visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) - patient.balloon_alert(user, "wrapping [parse_zone(healed_zone)]...") - if(!do_after(user, treatment_delay, target = patient)) return @@ -510,6 +517,7 @@ stop_bleeding = 0.6 grind_results = list(/datum/reagent/medicine/spaceacillin = 2) merge_type = /obj/item/stack/medical/suture + apply_verb = "suturing" /obj/item/stack/medical/suture/emergency name = "emergency suture" @@ -546,6 +554,7 @@ sanitization = 0.25 grind_results = list(/datum/reagent/medicine/c2/lenturi = 10) merge_type = /obj/item/stack/medical/ointment + apply_verb = "applying to" /obj/item/stack/medical/ointment/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is squeezing [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?")) @@ -581,7 +590,7 @@ return ..() icon_state = "regen_mesh_closed" -/obj/item/stack/medical/mesh/try_heal_checks(mob/living/patient, mob/living/user, silent = FALSE) +/obj/item/stack/medical/mesh/try_heal_checks(mob/living/patient, mob/living/user, healed_zone, silent = FALSE) if(!is_open) if(!silent) balloon_alert(user, "open it first!") @@ -643,6 +652,11 @@ heal_burn = 3 grind_results = list(/datum/reagent/consumable/aloejuice = 1) merge_type = /obj/item/stack/medical/aloe + apply_verb = "applying to" + +/obj/item/stack/medical/aloe/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt) + . = ..() + AddComponent(/datum/component/bakeable, /obj/item/food/badrecipe, rand(10 SECONDS, 15 SECONDS), FALSE) /obj/item/stack/medical/aloe/fresh amount = 2 @@ -663,6 +677,7 @@ grind_results = list(/datum/reagent/bone_dust = 10, /datum/reagent/carbon = 10) novariants = TRUE merge_type = /obj/item/stack/medical/bone_gel + apply_verb = "applying to" /obj/item/stack/medical/bone_gel/get_surgery_tool_overlay(tray_extended) return "gel" + (tray_extended ? "" : "_out") @@ -715,6 +730,8 @@ mob_throw_hit_sound = 'sound/misc/moist_impact.ogg' hitsound = 'sound/misc/moist_impact.ogg' merge_type = /obj/item/stack/medical/poultice + apply_verb = "applying to" + works_on_dead = TRUE /obj/item/stack/medical/poultice/post_heal_effects(amount_healed, mob/living/carbon/healed_mob, mob/living/user) . = ..() @@ -736,6 +753,7 @@ self_delay = 3 SECONDS other_delay = 1 SECONDS grind_results = list(/datum/reagent/medicine/c2/libital = 2) + apply_verb = "applying to" /obj/item/stack/medical/bandage/makeshift name = "makeshift bandage" diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 8415ffa3f407f..a3340fbd43a9d 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -287,6 +287,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( desc = "A nasty looking shard of glass." icon = 'icons/obj/debris.dmi' icon_state = "large" + icon_angle = -45 w_class = WEIGHT_CLASS_TINY force = 5 throwforce = 10 diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 0374b7a3744e7..b3fac29554de6 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -463,6 +463,78 @@ for(var/i in 1 to 3) new extra_to_spawn (src) +/obj/item/storage/toolbox/guncase/traitor + name = "makarov gun case" + desc = "A weapon's case. Has a blood-red 'S' stamped on the cover. There seems to be a strange switch along the side inside a plastic flap." + icon_state = "pistol_case" + base_icon_state = "pistol_case" + // What ammo box do we spawn in our case? + var/ammo_box_to_spawn = /obj/item/ammo_box/c9mm + // Timer for the bomb in the case. + var/explosion_timer + // Whether or not our case is exploding. Used for determining sprite changes. + var/currently_exploding = FALSE + +/obj/item/storage/toolbox/guncase/traitor/Initialize(mapload) + . = ..() + register_context() + +/obj/item/storage/toolbox/guncase/traitor/examine(mob/user) + . = ..() + . += span_notice("Activate the Evidence Disposal Explosive using Alt-Right-Click.") + +/obj/item/storage/toolbox/guncase/traitor/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + context[SCREENTIP_CONTEXT_ALT_RMB] = "Activate Evidence Disposal Explosive" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/storage/toolbox/guncase/traitor/PopulateContents() + new weapon_to_spawn (src) + for(var/i in 1 to 2) + new extra_to_spawn (src) + new ammo_box_to_spawn(src) + +/obj/item/storage/toolbox/guncase/traitor/update_icon_state() + . = ..() + if(currently_exploding) + icon_state = "[base_icon_state]_exploding" + else + icon_state = "[base_icon_state]" + +/obj/item/storage/toolbox/guncase/traitor/click_alt_secondary(mob/user) + . = ..() + var/i_dont_even_think_once_about_blowing_stuff_up = tgui_alert(user, "Would you like to activate the evidence disposal bomb now?", "BYE BYE", list("Yes","No")) + if(i_dont_even_think_once_about_blowing_stuff_up == "No") + return + explosion_timer = addtimer(CALLBACK(src, PROC_REF(think_fast_chucklenuts)), 5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE)) + to_chat(user, span_warning("You prime [src]'s evidence disposal bomb!")) + log_bomber(user, "has activated a", src, "for detonation") + playsound(src, 'sound/items/weapons/armbomb.ogg', 50, TRUE) + currently_exploding = TRUE + update_appearance() + +/// proc to handle our detonation +/obj/item/storage/toolbox/guncase/traitor/proc/think_fast_chucklenuts() + explosion(src, devastation_range = 0, heavy_impact_range = 0, light_impact_range = 2, explosion_cause = src) + qdel(src) + +/obj/item/storage/toolbox/guncase/traitor/ammunition + name = "makarov 9mm magazine case" + weapon_to_spawn = /obj/item/ammo_box/magazine/m9mm + +/obj/item/storage/toolbox/guncase/traitor/donksoft + name = "\improper Donksoft riot pistol gun case" + weapon_to_spawn = /obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine + extra_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + ammo_box_to_spawn = /obj/item/ammo_box/foambox/riot + +/obj/item/storage/toolbox/guncase/traitor/ammunition/donksoft + name = "\improper Donksoft riot pistol magazine case" + weapon_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + extra_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + ammo_box_to_spawn = /obj/item/ammo_box/foambox/riot + /obj/item/storage/toolbox/guncase/bulldog name = "bulldog gun case" weapon_to_spawn = /obj/item/gun/ballistic/shotgun/bulldog diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 3f813e50b45db..d945a3fd711db 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -461,7 +461,7 @@ /obj/item/storage/box/syndie_kit/chemical/Initialize(mapload) . = ..() - atom_storage.max_slots = 14 + atom_storage.max_slots = 15 /obj/item/storage/box/syndie_kit/chemical/PopulateContents() new /obj/item/reagent_containers/cup/bottle/polonium(src) @@ -469,6 +469,7 @@ new /obj/item/reagent_containers/cup/bottle/fentanyl(src) new /obj/item/reagent_containers/cup/bottle/formaldehyde(src) new /obj/item/reagent_containers/cup/bottle/spewium(src) + new /obj/item/reagent_containers/cup/bottle/syndol(src) new /obj/item/reagent_containers/cup/bottle/cyanide(src) new /obj/item/reagent_containers/cup/bottle/histamine(src) new /obj/item/reagent_containers/cup/bottle/initropidril(src) diff --git a/code/game/objects/items/syndie_spraycan.dm b/code/game/objects/items/syndie_spraycan.dm index 5690ecb7a28cc..bc1910595d425 100644 --- a/code/game/objects/items/syndie_spraycan.dm +++ b/code/game/objects/items/syndie_spraycan.dm @@ -171,7 +171,7 @@ /// Timer until the rune can be cleaned up off the floor var/protected_timer -/obj/effect/decal/cleanable/traitor_rune/traitor/Destroy() +/obj/effect/decal/cleanable/traitor_rune/Destroy() deltimer(protected_timer) QDEL_NULL(demoraliser) return ..() diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 8a7f18ed2cc5d..6d71ec0208ed7 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -16,6 +16,7 @@ icon = 'icons/obj/canisters.dmi' icon_state = "generic" inhand_icon_state = "generic_tank" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/tanks_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tanks_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm index 1c23937d2b589..eba0bc82054d2 100644 --- a/code/game/objects/items/tanks/watertank.dm +++ b/code/game/objects/items/tanks/watertank.dm @@ -290,6 +290,9 @@ /obj/item/extinguisher/mini/nozzle/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(AttemptRefill(interacting_with, user)) return NONE + return ..() + +/obj/item/extinguisher/mini/nozzle/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(nozzle_mode == EXTINGUISHER) return ..() diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 6fca04c7a3f40..ad62f3c086735 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -179,7 +179,17 @@ var/area/computer_area = get_area(target) if(!computer_area || (computer_area.area_flags & NOTELEPORT)) continue - if(computer.power_station?.teleporter_hub && computer.power_station.engaged) + + if(!computer.power_station || !computer.power_station.teleporter_hub) + continue + + if((computer.power_station.machine_stat & (NOPOWER|BROKEN|MAINT)) || computer.power_station.panel_open) + continue + + if((computer.power_station.teleporter_hub.machine_stat & (NOPOWER|BROKEN|MAINT)) || computer.power_station.teleporter_hub.panel_open) + continue + + if(computer.power_station.engaged) locations["[get_area(target)] (Active)"] = computer else locations["[get_area(target)] (Inactive)"] = computer diff --git a/code/game/objects/items/tongs.dm b/code/game/objects/items/tongs.dm index 81f71c6b3453e..ec72133a25776 100644 --- a/code/game/objects/items/tongs.dm +++ b/code/game/objects/items/tongs.dm @@ -6,6 +6,7 @@ icon_state = "tongs" base_icon_state = "tongs" inhand_icon_state = "fork" // close enough + icon_angle = -45 attack_verb_continuous = list("pinches", "tongs", "nips") attack_verb_simple = list("pinch", "tong", "nip") /// What are we holding in our tongs? diff --git a/code/game/objects/items/tools/crowbar.dm b/code/game/objects/items/tools/crowbar.dm index 38ca59038f04a..9081277ad9425 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/tools.dmi' icon_state = "crowbar" inhand_icon_state = "crowbar" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' usesound = 'sound/items/tools/crowbar.ogg' @@ -77,6 +78,7 @@ w_class = WEIGHT_CLASS_NORMAL icon = 'icons/obj/weapons/hammer.dmi' icon_state = "clawhammer" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' inhand_icon_state = "clawhammer" @@ -112,6 +114,7 @@ icon_state = "jaws" inhand_icon_state = "jawsoflife" worn_icon_state = "jawsoflife" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2.25, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/titanium = SHEET_MATERIAL_AMOUNT*1.75) @@ -192,6 +195,7 @@ icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_crowbar" worn_icon_state = "toolkit_engiborg_crowbar" //error sprite - this shouldn't have been dropped + icon_angle = 0 usesound = 'sound/items/tools/jaws_pry.ogg' force = 10 toolspeed = 0.5 @@ -203,6 +207,7 @@ base_icon_state = "mechremoval" inhand_icon_state = null icon = 'icons/obj/mechremoval.dmi' + icon_angle = -65 w_class = WEIGHT_CLASS_HUGE slot_flags = NONE toolspeed = 1.25 diff --git a/code/game/objects/items/tools/spess_knife.dm b/code/game/objects/items/tools/spess_knife.dm index 3550f4df5aeda..f1148fbd397bc 100644 --- a/code/game/objects/items/tools/spess_knife.dm +++ b/code/game/objects/items/tools/spess_knife.dm @@ -8,6 +8,7 @@ worn_icon_state = "spess_knife" inside_belt_icon_state = "spess_knife" inhand_icon_state = "spess_knife" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' w_class = WEIGHT_CLASS_TINY diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index 564403acf0ec3..25f7e75593fb6 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -5,6 +5,7 @@ icon_state = "wrench" inhand_icon_state = "wrench" worn_icon_state = "wrench" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -43,6 +44,7 @@ desc = "A polarized wrench. It causes anything placed between the jaws to turn." icon = 'icons/obj/antags/abductor.dmi' inside_belt_icon_state = "wrench_alien" + icon_angle = -135 custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium =SHEET_MATERIAL_AMOUNT, /datum/material/diamond =SHEET_MATERIAL_AMOUNT) usesound = 'sound/effects/empulse.ogg' toolspeed = 0.1 @@ -89,6 +91,7 @@ desc = "An advanced robotic wrench, powered by internal hydraulics. Twice as fast as the handheld version." icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_wrench" + icon_angle = 0 toolspeed = 0.5 /obj/item/wrench/combat @@ -97,6 +100,7 @@ icon_state = "wrench_combat" inhand_icon_state = "wrench_combat" inside_belt_icon_state = "wrench_combat" + icon_angle = -90 attack_verb_continuous = list("devastates", "brutalizes", "commits a war crime against", "obliterates", "humiliates") attack_verb_simple = list("devastate", "brutalize", "commit a war crime against", "obliterate", "humiliate") tool_behaviour = null @@ -132,4 +136,5 @@ desc = "A wrench designed to grab into airlock's bolting system and raise it regardless of the airlock's power status." icon_state = "bolter_wrench" inhand_icon_state = "bolter_wrench" + icon_angle = -90 w_class = WEIGHT_CLASS_NORMAL diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 3b67c096c54b5..f3630424a657f 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -565,6 +565,7 @@ desc = "A cheap, plastic replica of an energy sword. Realistic sounds! Ages 8 and up." icon_state = "e_sword" inhand_icon_state = "e_sword" + icon_angle = -45 icon = 'icons/obj/weapons/transforming_energy.dmi' lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' @@ -682,6 +683,7 @@ icon = 'icons/obj/toys/toy.dmi' icon_state = "foamblade" inhand_icon_state = "arm_blade" + icon_angle = -180 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' attack_verb_continuous = list("pricks", "absorbs", "gores") @@ -781,6 +783,7 @@ icon_state = "katana" inhand_icon_state = "katana" worn_icon_state = "katana" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -788,9 +791,17 @@ force = 5 throwforce = 5 w_class = WEIGHT_CLASS_NORMAL - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices") - attack_verb_simple = list("attack", "slash", "stab", "slice") + attack_verb_continuous = list("attacks", "slashes", "slices") + attack_verb_simple = list("attack", "slash", "slice") hitsound = 'sound/items/weapons/bladeslice.ogg' + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/toy/katana/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) /* * Snap pops @@ -1121,6 +1132,7 @@ icon = 'icons/obj/weapons/khopesh.dmi' icon_state = "render" inhand_icon_state = "cultdagger" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_SMALL diff --git a/code/game/objects/items/v8_engine.dm b/code/game/objects/items/v8_engine.dm index c75f1ebc13af5..20b7dd406a551 100644 --- a/code/game/objects/items/v8_engine.dm +++ b/code/game/objects/items/v8_engine.dm @@ -48,6 +48,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "house_edge0" inhand_icon_state = "house_edge0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_HUGE diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index a731772b0902c..4427294aef8ec 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -3,6 +3,7 @@ name = "banhammer" icon = 'icons/obj/weapons/hammer.dmi' icon_state = "toyhammer" + icon_angle = -45 slot_flags = ITEM_SLOT_BELT throwforce = 0 force = 1 @@ -47,6 +48,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/hammer.dmi' icon_state = "balloon_mallet" inhand_icon_state = "balloon_mallet" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' siemens_coefficient = 0 @@ -88,6 +90,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/sword.dmi' icon_state = "sord" inhand_icon_state = "sord" + icon_angle = -35 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -109,6 +112,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/sword.dmi' icon_state = "claymore" inhand_icon_state = "claymore" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' hitsound = 'sound/items/weapons/bladeslice.ogg' @@ -117,14 +121,16 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 40 throwforce = 10 w_class = WEIGHT_CLASS_NORMAL - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") block_chance = 50 block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED max_integrity = 200 armor_type = /datum/armor/item_claymore resistance_flags = FIRE_PROOF + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /datum/armor/item_claymore fire = 100 @@ -132,11 +138,18 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 /obj/item/claymore/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + make_stabby() AddComponent(/datum/component/butchering, \ speed = 4 SECONDS, \ effectiveness = 105, \ ) +// Applies alt sharpness component, for overrides +/obj/item/claymore/proc/make_stabby() + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -15) + /obj/item/claymore/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!")) return BRUTELOSS @@ -356,6 +369,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "katana" inhand_icon_state = "katana" worn_icon_state = "katana" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -364,14 +378,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 throwforce = 10 w_class = WEIGHT_CLASS_HUGE hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") block_chance = 50 block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED max_integrity = 200 armor_type = /datum/armor/item_katana resistance_flags = FIRE_PROOF + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/katana/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -15) /datum/armor/item_katana fire = 100 @@ -438,15 +460,16 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "carpenter_hammer" inhand_icon_state = "carpenter_hammer" worn_icon_state = "clawhammer" //plaecholder + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' desc = "Uncanny looking hammer." - force = 20 - throwforce = 20 + force = 17 + throwforce = 14 throw_range = 4 w_class = WEIGHT_CLASS_NORMAL wound_bonus = 20 - demolition_mod = 1.25 + demolition_mod = 1.15 slot_flags = ITEM_SLOT_BELT /obj/item/carpenter_hammer/Initialize(mapload) @@ -475,6 +498,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/sword.dmi' icon_state = "switchblade" base_icon_state = "switchblade" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' desc = "A sharp, concealable, spring-loaded knife." @@ -491,6 +515,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 resistance_flags = FIRE_PROOF /// Whether the switchblade starts extended or not. var/start_extended = FALSE + var/list/alt_continuous = list("stabs", "pierces", "shanks") + var/list/alt_simple = list("stab", "pierce", "shank") /obj/item/switchblade/get_all_tool_behaviours() return list(TOOL_KNIFE) @@ -512,10 +538,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 sharpness_on = SHARP_EDGED, \ hitsound_on = 'sound/items/weapons/bladeslice.ogg', \ w_class_on = WEIGHT_CLASS_NORMAL, \ - attack_verb_continuous_on = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts"), \ - attack_verb_simple_on = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut"), \ + attack_verb_continuous_on = list("slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts"), \ + attack_verb_simple_on = list("slash", "slice", "tear", "lacerate", "rip", "dice", "cut"), \ ) + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5, TRAIT_TRANSFORM_ACTIVE) + RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) /obj/item/switchblade/proc/on_transform(obj/item/source, mob/user, active) @@ -567,6 +597,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 base_icon_state = "bambostaff" inhand_icon_state = "bambostaff0" worn_icon_state = "bambostaff0" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' @@ -589,6 +620,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/staff.dmi' icon_state = "cane" inhand_icon_state = "stick" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 5 @@ -630,6 +662,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/staff.dmi' icon_state = "crutch_med" inhand_icon_state = "crutch_med" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 12 @@ -682,6 +715,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 desc = "Traditionally used by the blind to help them see. Folds down to be easier to transport." icon_state = "cane_white" inhand_icon_state = "cane_white" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 1 @@ -756,6 +790,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/staff.dmi' icon_state = "cane" inhand_icon_state = "stick" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 3 @@ -788,6 +823,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 desc = "For the beating to death of lizards with their own tails." icon = 'icons/obj/weapons/club.dmi' icon_state = "tailclub" + icon_angle = -25 force = 14 throwforce = 1 // why are you throwing a club do you even weapon throw_speed = 1 @@ -895,6 +931,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/bat.dmi' icon_state = "baseball_bat" inhand_icon_state = "baseball_bat" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 12 @@ -1045,6 +1082,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "flyswatter" inhand_icon_state = "flyswatter" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' force = 1 @@ -1135,6 +1173,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/club.dmi' icon_state = "gohei" inhand_icon_state = "gohei" + icon_angle = -65 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' @@ -1145,6 +1184,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "swordon" inhand_icon_state = "swordon" worn_icon_state = "swordon" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_BULKY @@ -1155,8 +1195,16 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 14 throwforce = 12 hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/melee/moonlight_greatsword/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) //High Frequency Blade @@ -1167,6 +1215,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon = 'icons/obj/weapons/sword.dmi' icon_state = "hfrequency0" worn_icon_state = "hfrequency0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' force = 10 diff --git a/code/game/objects/items/wizard_weapons.dm b/code/game/objects/items/wizard_weapons.dm index e5750c06bb2d4..9cbbce1a4c8fe 100644 --- a/code/game/objects/items/wizard_weapons.dm +++ b/code/game/objects/items/wizard_weapons.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/weapons/hammer.dmi' icon_state = "singularity_hammer0" base_icon_state = "singularity_hammer" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' worn_icon_state = "singularity_hammer" @@ -77,6 +78,7 @@ icon_state = "mjollnir0" base_icon_state = "mjollnir" worn_icon_state = "mjollnir" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 91991bec3a774..7a58e3ef48641 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -69,18 +69,29 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) return var/total_force = (attacking_item.force * attacking_item.demolition_mod) + var/damage = take_damage(total_force, attacking_item.damtype, MELEE, TRUE, get_dir(src, user), attacking_item.armour_penetration) - var/damage = take_damage(total_force, attacking_item.damtype, MELEE, 1, get_dir(src, user)) + // Sanity in case one is null for some reason + var/picked_index = rand(max(length(attacking_item.attack_verb_simple), length(attacking_item.attack_verb_continuous))) - var/damage_verb = "hit" + var/message_verb_continuous = "attacks" + var/message_verb_simple = "attack" + // Sanity in case one is... longer than the other? + if (picked_index && length(attacking_item.attack_verb_continuous) >= picked_index) + message_verb_continuous = attacking_item.attack_verb_continuous[picked_index] + if (picked_index && length(attacking_item.attack_verb_simple) >= picked_index) + message_verb_simple = attacking_item.attack_verb_simple[picked_index] + + if(attacking_item.demolition_mod > 1 && prob(damage * 5)) + message_verb_simple = "pulverise" + message_verb_continuous = "pulverises" - if(attacking_item.demolition_mod > 1 && damage) - damage_verb = "pulverise" if(attacking_item.demolition_mod < 1) - damage_verb = "ineffectively pierce" + message_verb_simple = "ineffectively " + message_verb_simple + message_verb_continuous = "ineffectively " + message_verb_continuous - user.visible_message(span_danger("[user] [damage_verb][plural_s(damage_verb)] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"), \ - span_danger("You [damage_verb] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"), null, COMBAT_MESSAGE_RANGE) + user.visible_message(span_danger("[user] [message_verb_continuous] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"), \ + span_danger("You [message_verb_simple] [src] with [attacking_item][damage ? "." : ", [no_damage_feedback]!"]"), null, COMBAT_MESSAGE_RANGE) log_combat(user, src, "attacked", attacking_item) /obj/assume_air(datum/gas_mixture/giver) diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 38aadbb266229..8cc1012995de0 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -317,6 +317,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0) icon = 'icons/obj/chairs.dmi' icon_state = "chair_toppled" inhand_icon_state = "chair" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/items/chairs_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/chairs_righthand.dmi' w_class = WEIGHT_CLASS_HUGE diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 207b85122dd21..edfbe8e38c0e4 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1222,4 +1222,14 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) /obj/structure/closet/proc/add_to_roundstart_list() GLOB.roundstart_station_closets += src +///Spears deal bonus damages to lockers +/obj/structure/closet/attacked_by(obj/item/attacking_item, mob/living/user) + if(istype(attacking_item, /obj/item/spear)) + take_damage(attacking_item.force * 2, attacking_item.damtype, MELEE, 1, get_dir(src, user)) + user.visible_message(span_danger("[user] stabs with precision [src]'s electronics with [attacking_item]!"), + span_danger("You stab with precision [src]'s electronics with [attacking_item]!"), null, COMBAT_MESSAGE_RANGE) + log_combat(user, src, "attacked", attacking_item) + return + return ..() + #undef LOCKER_FULL diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm index 6a8dc8e31cde7..9a546e8154d9f 100644 --- a/code/game/objects/structures/lavaland/geyser.dm +++ b/code/game/objects/structures/lavaland/geyser.dm @@ -121,6 +121,7 @@ icon = 'icons/obj/watercloset.dmi' icon_state = "plunger" worn_icon_state = "plunger" + icon_angle = 90 slot_flags = ITEM_SLOT_MASK flags_inv = HIDESNOUT diff --git a/code/game/objects/structures/showcase.dm b/code/game/objects/structures/showcase.dm index 2158a88a6b656..c0f19da6dbdbe 100644 --- a/code/game/objects/structures/showcase.dm +++ b/code/game/objects/structures/showcase.dm @@ -127,8 +127,8 @@ /obj/structure/showcase/katana name = "seppuku katana" - density = 0 desc = "Welp, only one way to recover your honour." + density = 0 icon = 'icons/obj/weapons/sword.dmi' icon_state = "katana" diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm index 9f7660b05e6ed..26d05f6edd178 100644 --- a/code/game/objects/structures/shower.dm +++ b/code/game/objects/structures/shower.dm @@ -273,7 +273,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16)) if(!radioactive_shower) // note it is possible to have a clean_shower that is radioactive (+70% water mixed with +20% radiation) wash_flags |= CLEAN_RAD - target.wash(wash_flags) + + if (isturf(target)) + target.wash(wash_flags, TRUE) + else + target.wash(wash_flags) reagents.expose(target, (TOUCH), SHOWER_EXPOSURE_MULTIPLIER * SHOWER_SPRAY_VOLUME / max(reagents.total_volume, SHOWER_SPRAY_VOLUME)) if(!isliving(target)) @@ -345,10 +349,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16)) // Wash up. wash_atom(loc) - for(var/atom/movable/movable_content as anything in loc) - if(!ismopable(movable_content)) // Mopables will be cleaned anyways by the turf wash above - wash_atom(movable_content) // Reagent exposure is handled in wash_atom - reagents.remove_all(SHOWER_SPRAY_VOLUME) /obj/machinery/shower/on_deconstruction(disassembled = TRUE) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 448c3eb5425a7..9adb4f75a2f94 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -390,6 +390,23 @@ LAZYNULL(attached_items) // safety return ..() +/obj/structure/table/rolling/item_interaction(mob/living/user, obj/item/rolling_table_dock/rable, list/modifiers) + . = NONE + if(!istype(rable)) + return + + if(rable.loaded) + to_chat(user, span_warning("You already have \a [rable.loaded] docked!")) + return ITEM_INTERACT_FAILURE + if(locate(/mob/living) in get_turf(src)) + to_chat(user, span_warning("You can't collect \the [src] with that much on top!")) + return ITEM_INTERACT_FAILURE + + rable.loaded = src + forceMove(rable) + user.visible_message(span_notice("[user] collects \the [src]."), span_notice("you collect \the [src].")) + return ITEM_INTERACT_SUCCESS + /obj/structure/table/rolling/AfterPutItemOnTable(obj/item/thing, mob/living/user) . = ..() LAZYADD(attached_items, thing) diff --git a/code/game/objects/structures/water_structures/sink.dm b/code/game/objects/structures/water_structures/sink.dm index 1cd3f7d7aaa53..3a6dfbb2a2c14 100644 --- a/code/game/objects/structures/water_structures/sink.dm +++ b/code/game/objects/structures/water_structures/sink.dm @@ -73,10 +73,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14)) if(busy) to_chat(user, span_warning("Someone's already washing here!")) return + var/selected_area = user.parse_zone_with_bodypart(user.zone_selected) - var/washing_face = 0 + var/washing_face = FALSE if(selected_area in list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES)) - washing_face = 1 + washing_face = TRUE + + playsound(src, 'sound/machines/sink-faucet.ogg', 50) user.visible_message(span_notice("[user] starts washing [user.p_their()] [washing_face ? "face" : "hands"]..."), \ span_notice("You start washing your [washing_face ? "face" : "hands"]...")) busy = TRUE @@ -206,6 +209,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink, (-14)) if(!user.combat_mode || (O.item_flags & NOBLUDGEON)) to_chat(user, span_notice("You start washing [O]...")) + playsound(src, 'sound/machines/sink-faucet.ogg', 50) busy = TRUE if(!do_after(user, 4 SECONDS, target = src)) busy = FALSE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 71c3ef9b6713b..9924713d93908 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -82,12 +82,11 @@ /obj/structure/window/mouse_drop_receive(atom/dropping, mob/user, params) . = ..() - if (added_leaning || (flags_1 & ON_BORDER_1)) + if (flags_1 & ON_BORDER_1) return - /// For performance reasons and to cut down on init times we are "lazy-loading" the leaning component when someone drags their sprite onto us, and then calling dragging code again to trigger the component - AddComponent(/datum/component/leanable, 11) - added_leaning = TRUE - dropping.base_mouse_drop_handler(src, null, null, params) + + //Adds the component only once. We do it here & not in Initialize() because there are tons of windows & we don't want to add to their init times + LoadComponent(/datum/component/leanable, dropping) /obj/structure/window/examine(mob/user) . = ..() diff --git a/code/game/say.dm b/code/game/say.dm index d8cb91c4ea429..1e5b4bce45070 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -165,7 +165,7 @@ GLOBAL_LIST_INIT(freqtospan, list( if(istype(dialect) && dialect.display_icon(src)) languageicon = "[dialect.get_icon()] " - messagepart = " [say_emphasis(messagepart)]" + messagepart = " [messagepart]" return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]" @@ -223,8 +223,14 @@ GLOBAL_LIST_INIT(freqtospan, list( if(copytext_char(input, -2) == "!!") spans |= SPAN_YELL - var/spanned = attach_spans(input, spans) - return "[say_mod], \"[spanned]\"" + /* all inputs should be fully figured out past this point */ + + var/processed_input = say_emphasis(input) //This MUST be done first so that we don't get clipped by spans + processed_input = attach_spans(processed_input, spans) + + var/processed_say_mod = say_emphasis(say_mod) + + return "[processed_say_mod], \"[processed_input]\"" /// Transforms the speech emphasis mods from [/atom/movable/proc/say_emphasis] into the appropriate HTML tags. Includes escaping. #define ENCODE_HTML_EMPHASIS(input, char, html, varname) \ @@ -235,8 +241,8 @@ GLOBAL_LIST_INIT(freqtospan, list( /atom/movable/proc/say_emphasis(input) ENCODE_HTML_EMPHASIS(input, "\\|", "i", italics) ENCODE_HTML_EMPHASIS(input, "\\+", "b", bold) - ENCODE_HTML_EMPHASIS(input, "_", "u", underline) - var/static/regex/remove_escape_backlashes = regex("\\\\(_|\\+|\\|)", "g") // Removes backslashes used to escape text modification. + ENCODE_HTML_EMPHASIS(input, "\\_", "u", underline) + var/static/regex/remove_escape_backlashes = regex("\\\\(\\_|\\+|\\|)", "g") // Removes backslashes used to escape text modification. input = remove_escape_backlashes.Replace_char(input, "$1") return input diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm index 739ee5aeae0f4..2a17160ae4c85 100644 --- a/code/game/turfs/closed/wall/reinf_walls.dm +++ b/code/game/turfs/closed/wall/reinf_walls.dm @@ -215,9 +215,13 @@ dismantle_wall() /turf/closed/wall/r_wall/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.canRturf || the_rcd.construction_mode == RCD_WALLFRAME) + if (the_rcd.construction_mode == RCD_WALLFRAME) return ..() - + if(!the_rcd.canRturf) + return + . = ..() + if (.) + .["delay"] *= RCD_RWALL_DELAY_MULT /turf/closed/wall/r_wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) if(the_rcd.canRturf || rcd_data["[RCD_DESIGN_MODE]"] == RCD_WALLFRAME) diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index 99ea3327d6c46..402d24c6c830d 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -51,13 +51,8 @@ underlays += underlay_appearance /turf/closed/wall/mouse_drop_receive(atom/dropping, mob/user, params) - . = ..() - if (added_leaning) - return - /// For performance reasons and to cut down on init times we are "lazy-loading" the leaning component when someone drags their sprite onto us, and then calling dragging code again to trigger the component - AddComponent(/datum/component/leanable, 11) - added_leaning = TRUE - dropping.base_mouse_drop_handler(src, null, null, params) + //Adds the component only once. We do it here & not in Initialize() because there are tons of walls & we don't want to add to their init times + LoadComponent(/datum/component/leanable, dropping) /turf/closed/wall/atom_destruction(damage_flag) . = ..() diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 6ad32fe7a652c..27aa1d36920b3 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -337,11 +337,7 @@ for(var/mob/living/basic/slime/M in src) M.apply_water() - wash(CLEAN_WASH) - for(var/atom/movable/movable_content as anything in src) - if(ismopable(movable_content)) // Will have already been washed by the wash call above at this point. - continue - movable_content.wash(CLEAN_WASH) + wash(CLEAN_WASH, TRUE) return TRUE /turf/open/handle_slip(mob/living/slipper, knockdown_amount, obj/slippable, lube, paralyze_amount, force_drop) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 4b50ee0e9a3b3..bd78317799fa6 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -702,19 +702,14 @@ GLOBAL_LIST_EMPTY(station_turfs) var/reac_volume = reagents[reagent] . |= reagent.expose_turf(src, reac_volume) -/** - * Called when this turf is being washed. Washing a turf will also wash any mopable floor decals - */ -/turf/wash(clean_types) +// When our turf is washed, we may wash everything on top of the turf +// By default we will only wash mopable things (like blood or vomit) +// but you may optionally pass in all_contents = TRUE to wash everything +/turf/wash(clean_types, all_contents = FALSE) . = ..() - - for(var/am in src) - if(am == src) - continue - var/atom/movable/movable_content = am - if(!ismopable(movable_content)) - continue - movable_content.wash(clean_types) + for(var/atom/movable/to_clean as anything in src) + if(all_contents || HAS_TRAIT(to_clean, TRAIT_MOPABLE)) + to_clean.wash(clean_types) /turf/set_density(new_value) var/old_density = density diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm index 0bc1ec01f5d4f..c87c424661fd2 100644 --- a/code/modules/admin/greyscale_modify_menu.dm +++ b/code/modules/admin/greyscale_modify_menu.dm @@ -241,14 +241,24 @@ This is highly likely to cause massive amounts of lag as every object in the gam config.EnableAutoRefresh(config_owner_type) /datum/greyscale_modify_menu/proc/ReadColorsFromString(colorString) - var/list/new_split_colors = list() + //length validation var/list/colors = splittext(colorString, "#") - for(var/index in 2 to min(length(colors), config.expected_colors + 1)) + if(length(colors) <= 1) //doesn't even begin with a # so isn't even a color + return FALSE + colors.Cut(1, 2) //removes the white space as a consequence of the string beginning with a # + if(colors.len != config.expected_colors) //not the expected length + return FALSE + + //value validation + var/list/new_split_colors = list() + for(var/index in 1 to config.expected_colors) var/color = "#[colors[index]]" if(!findtext(color, GLOB.is_color) && (!unlocked || !findtext(color, GLOB.is_alpha_color))) return FALSE new_split_colors += color split_colors = new_split_colors + + //all good return TRUE /datum/greyscale_modify_menu/proc/randomize_color(color_index) diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 73987622202e6..6bd97dcaa2005 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -140,9 +140,14 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm permissions_assets.send(usr.client) var/admin_key = href_list["key"] var/admin_ckey = ckey(admin_key) - var/datum/admins/D = GLOB.admin_datums[admin_ckey] - var/use_db + var/task = href_list["editrights"] + var/datum/admins/target_admin_datum = GLOB.admin_datums[admin_ckey] + if(!target_admin_datum) + target_admin_datum = GLOB.deadmins[admin_ckey] + if (!target_admin_datum && task != "add") + return + var/use_db var/skip var/legacy_only if(task == "activate" || task == "deactivate" || task == "sync" || task == "verify") @@ -152,7 +157,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm to_chat(usr, "Editing the rank of this admin is blocked by server configuration.", confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions") - if((D.ranks & GLOB.protected_ranks).len > 0) + if((target_admin_datum.ranks & GLOB.protected_ranks).len > 0) to_chat(usr, "Editing the flags of this rank is blocked by server configuration.", confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions")) @@ -173,16 +178,11 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm use_db = FALSE if(QDELETED(usr)) return - if(task != "add") - D = GLOB.admin_datums[admin_ckey] - if(!D) - D = GLOB.deadmins[admin_ckey] - if(!D) - return - if((task != "sync") && !check_if_greater_rights_than_holder(D)) - message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.") - log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.") - return + + if(target_admin_datum && (task != "sync" && task != "verify") && !check_if_greater_rights_than_holder(target_admin_datum)) + message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.") + log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.") + return switch(task) if("add") admin_ckey = add_admin(admin_ckey, admin_key, use_db) @@ -194,24 +194,24 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only) if("remove") - remove_admin(admin_ckey, admin_key, use_db, D) + remove_admin(admin_ckey, admin_key, use_db, target_admin_datum) if("rank") - change_admin_rank(admin_ckey, admin_key, use_db, D, legacy_only) + change_admin_rank(admin_ckey, admin_key, use_db, target_admin_datum, legacy_only) if("permissions") - change_admin_flags(admin_ckey, admin_key, D) + change_admin_flags(admin_ckey, admin_key, target_admin_datum) if("activate") - force_readmin(admin_key, D) + force_readmin(admin_key, target_admin_datum) if("deactivate") - force_deadmin(admin_key, D) + force_deadmin(admin_key, target_admin_datum) if("sync") - sync_lastadminrank(admin_ckey, admin_key, D) + sync_lastadminrank(admin_ckey, admin_key, target_admin_datum) if("verify") var/msg = "has authenticated [admin_ckey]" message_admins("[key_name_admin(usr)] [msg]") log_admin("[key_name(usr)] [msg]") - D.bypass_2fa = TRUE - D.associate(GLOB.directory[admin_ckey]) + target_admin_datum.bypass_2fa = TRUE + target_admin_datum.associate(GLOB.directory[admin_ckey]) edit_admin_permissions() /datum/admins/proc/add_admin(admin_ckey, admin_key, use_db) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 6a2666eef9bed..f517a7a7a51cb 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1731,7 +1731,7 @@ if(FAX.fax_id != href_list["destination"]) continue FAX.receive(locate(href_list["print_fax"]), href_list["sender_name"]) - + return else if(href_list["play_internet"]) if(!check_rights(R_SOUND)) diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index e43164419d59c..177d1f3172f7d 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -348,6 +348,34 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w priority_announce("The NAP is now in full effect.", null, SSstation.announcer.get_rand_report_sound()) else priority_announce("The NAP has been revoked.", null, SSstation.announcer.get_rand_report_sound()) + if("send_shuttle_back") + if (!is_funmin) + return + if (SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + to_chat(usr, span_warning("Emergency shuttle not currently in transit!"), confidential = TRUE) + return + var/make_announcement = tgui_alert(usr, "Make a CentCom announcement?", "Emergency shuttle return", list("Yes", "Custom Text", "No")) || "No" + var/announcement_text = "Emergency shuttle trajectory overriden, rerouting course back to [station_name()]." + if (make_announcement == "Custom Text") + announcement_text = tgui_input_text(usr, "Custom CentCom announcement", "Emergency shuttle return", multiline = TRUE) || announcement_text + var/new_timer = tgui_input_number(usr, "How long should the shuttle remain in transit?", "When are we droppin' boys?", 180, 600) + if (isnull(new_timer) || SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Shuttle Back")) + message_admins("[key_name_admin(holder)] sent the escape shuttle back to the station") + if (make_announcement != "No") + priority_announce( + text = announcement_text, + title = "Shuttle Trajectory Override", + sound = 'sound/announcer/announcement/announce_dig.ogg', + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "grey", + ) + SSshuttle.emergency.timer = INFINITY + if (new_timer > 0) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(return_escape_shuttle), make_announcement), new_timer SECONDS) + else + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(return_escape_shuttle), make_announcement) if("blackout") if(!is_funmin) return @@ -673,6 +701,27 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w T.flick_overlay_static(portal_appearance[GET_TURF_PLANE_OFFSET(T) + 1], 15) playsound(T, 'sound/effects/magic/lightningbolt.ogg', rand(80, 100), TRUE) +/// Docks the emergency shuttle back to the station and resets its' state +/proc/return_escape_shuttle(make_announcement) + if (SSshuttle.emergency.initiate_docking(SSshuttle.getDock("emergency_home"), force = TRUE) != DOCKING_SUCCESS) + message_admins("Emergency shuttle was unable to dock back to the station!") + SSshuttle.emergency.timer = 1 // Prevents softlocks + return + if (make_announcement != "No") + priority_announce( + text = "[SSshuttle.emergency] has returned to the station.", + title = "Emergency Shuttle Override", + sound = ANNOUNCER_SHUTTLEDOCK, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "grey", + ) + SSshuttle.emergency.mode = SHUTTLE_IDLE + SSshuttle.emergency.timer = 0 + // Docks the pods back (don't ask about physics) + for (var/obj/docking_port/mobile/pod/pod in SSshuttle.mobile_docking_ports) + if (pod.previous) + pod.initiate_docking(pod.previous, force = TRUE) + /datum/everyone_is_an_antag_controller var/chosen_antag = "" var/objective = "" diff --git a/code/modules/admin/verbs/server.dm b/code/modules/admin/verbs/server.dm index 5ac9fd272b45c..133d36c3dd0f0 100644 --- a/code/modules/admin/verbs/server.dm +++ b/code/modules/admin/verbs/server.dm @@ -66,6 +66,12 @@ ADMIN_VERB(restart, R_SERVER, "Reboot World", "Restarts the world immediately.", #undef HARDEST_RESTART #undef TGS_RESTART +ADMIN_VERB(cancel_reboot, R_SERVER, "Cancel Reboot", "Cancels a pending world reboot.", ADMIN_CATEGORY_SERVER) + if(!SSticker.cancel_reboot(user)) + return + log_admin("[key_name(user)] cancelled the pending world reboot.") + message_admins("[key_name_admin(user)] cancelled the pending world reboot.") + ADMIN_VERB(end_round, R_SERVER, "End Round", "Forcibly ends the round and allows the server to restart normally.", ADMIN_CATEGORY_SERVER) var/confirm = tgui_alert(user, "End the round and restart the game world?", "End Round", list("Yes", "Cancel")) if(confirm != "Yes") @@ -131,6 +137,8 @@ ADMIN_VERB(delay_round_end, R_SERVER, "Delay Round End", "Prevent the server fro SSticker.delay_end = TRUE SSticker.admin_delay_notice = delay_reason + if(SSticker.reboot_timer) + SSticker.cancel_reboot(user) log_admin("[key_name(user)] delayed the round end for reason: [SSticker.admin_delay_notice]") message_admins("[key_name_admin(user)] delayed the round end for reason: [SSticker.admin_delay_notice]") diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm index d70dc3f0e6723..b2900b810f360 100644 --- a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm +++ b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm @@ -29,6 +29,7 @@ desc = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras." icon_state = "gizmo_scan" inhand_icon_state = "silencer" + icon_angle = -45 var/mode = GIZMO_SCAN var/datum/weakref/marked_target_weakref var/obj/machinery/abductor/console/console @@ -105,6 +106,7 @@ desc = "A compact device used to shut down communications equipment." icon_state = "silencer" inhand_icon_state = "gizmo" + icon_angle = -45 /obj/item/abductor/silencer/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if(!AbductorCheck(user)) @@ -145,6 +147,7 @@ or to send a command to a test subject with a charged gland." icon_state = "mind_device_message" inhand_icon_state = "silencer" + icon_angle = -45 var/mode = MIND_DEVICE_MESSAGE /obj/item/abductor/mind_device/attack_self(mob/user) @@ -297,6 +300,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi' icon_state = "wonderprodStun" inhand_icon_state = "wonderprod" + icon_angle = -45 force = 7 wound_bonus = FALSE @@ -549,6 +553,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} name = "alien scalpel" desc = "It's a gleaming sharp knife made out of silvery-green metal." icon = 'icons/obj/antags/abductor.dmi' + icon_angle = 180 surgical_tray_overlay = "scalpel_alien" toolspeed = 0.25 @@ -557,6 +562,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} desc = "You've never seen this before." icon = 'icons/obj/antags/abductor.dmi' surgical_tray_overlay = "hemostat_alien" + icon_angle = 180 toolspeed = 0.25 /obj/item/retractor/alien @@ -564,6 +570,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} desc = "You're not sure if you want the veil pulled back." icon = 'icons/obj/antags/abductor.dmi' surgical_tray_overlay = "retractor_alien" + icon_angle = 180 toolspeed = 0.25 /obj/item/circular_saw/alien @@ -571,6 +578,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} desc = "Do the aliens also lose this, and need to find an alien hatchet?" icon = 'icons/obj/antags/abductor.dmi' surgical_tray_overlay = "saw_alien" + icon_angle = 180 toolspeed = 0.25 /obj/item/surgicaldrill/alien @@ -578,6 +586,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} desc = "Maybe alien surgeons have finally found a use for the drill." icon = 'icons/obj/antags/abductor.dmi' surgical_tray_overlay = "drill_alien" + icon_angle = 180 toolspeed = 0.25 /obj/item/cautery/alien @@ -586,6 +595,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} Unless..." icon = 'icons/obj/antags/abductor.dmi' surgical_tray_overlay = "cautery_alien" + icon_angle = 180 toolspeed = 0.25 /obj/item/clothing/head/helmet/abductor @@ -620,6 +630,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} desc = "Effectively just a Space Swiss Army Knife. Contains a multitude of integrated tools. Right-click it to switch which toolset is active." icon_state = "omnitool" inhand_icon_state = "silencer" + icon_angle = -45 toolspeed = 0.25 tool_behaviour = null usesound = 'sound/items/pshoom/pshoom.ogg' diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 874141237a983..c1721cc57e692 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -188,6 +188,7 @@ icon = 'icons/obj/weapons/changeling_items.dmi' icon_state = "arm_blade" inhand_icon_state = "arm_blade" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL @@ -197,14 +198,16 @@ throw_range = 0 throw_speed = 0 hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_EDGED wound_bonus = 10 bare_wound_bonus = 10 armour_penetration = 35 var/can_drop = FALSE var/fake = FALSE + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/melee/arm_blade/Initialize(mapload,silent,synthetic) . = ..() @@ -213,6 +216,9 @@ loc.visible_message(span_warning("A grotesque blade forms around [loc.name]\'s arm!"), span_warning("Our arm twists and mutates, transforming it into a deadly blade."), span_hear("You hear organic matter ripping and tearing!")) if(synthetic) can_drop = TRUE + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5) AddComponent(/datum/component/butchering, \ speed = 6 SECONDS, \ effectiveness = 80, \ @@ -276,6 +282,7 @@ icon = 'icons/obj/weapons/changeling_items.dmi' icon_state = "tentacle" inhand_icon_state = "tentacle" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NOBLUDGEON @@ -356,26 +363,31 @@ chain = firer.Beam(src, icon_state = "tentacle", emissive = FALSE) ..() -/obj/projectile/tentacle/proc/reset_throw(mob/living/carbon/human/H) - if(H.throw_mode) - H.throw_mode_off(THROW_MODE_TOGGLE) //Don't annoy the changeling if he doesn't catch the item +/obj/projectile/tentacle/proc/reset_throw(mob/living/carbon/human/user) + if(user.throw_mode) + user.throw_mode_off(THROW_MODE_TOGGLE) //Don't annoy the changeling if he doesn't catch the item -/obj/projectile/tentacle/proc/tentacle_grab(mob/living/carbon/human/H, mob/living/carbon/C) - if(H.Adjacent(C)) - if(H.get_active_held_item() && !H.get_inactive_held_item()) - H.swap_hand() - if(H.get_active_held_item()) +/obj/projectile/tentacle/proc/tentacle_grab(mob/living/carbon/human/user, mob/living/carbon/victim) + if(!user.Adjacent(victim)) + return + + if(user.get_active_held_item() && !user.get_inactive_held_item()) + user.swap_hand() + + if(user.get_active_held_item()) + return + + victim.grabbedby(user) + victim.grippedby(user, instant = TRUE) //instant aggro grab + + for(var/obj/item/weapon in user.held_items) + if(weapon.get_sharpness()) + victim.visible_message(span_danger("[user] impales [victim] with [user.p_their()] [weapon.name]!"), span_userdanger("[user] impales you with [user.p_their()] [weapon.name]!")) + victim.apply_damage(weapon.force, BRUTE, BODY_ZONE_CHEST, attacking_item = weapon) + user.do_item_attack_animation(victim, used_item = weapon, animation_type = ATTACK_ANIMATION_PIERCE) + user.add_mob_blood(victim) + playsound(get_turf(user),weapon.hitsound,75,TRUE) return - C.grabbedby(H) - C.grippedby(H, instant = TRUE) //instant aggro grab - for(var/obj/item/I in H.held_items) - if(I.get_sharpness()) - C.visible_message(span_danger("[H] impales [C] with [H.p_their()] [I.name]!"), span_userdanger("[H] impales you with [H.p_their()] [I.name]!")) - C.apply_damage(I.force, BRUTE, BODY_ZONE_CHEST, attacking_item = I) - H.do_item_attack_animation(C, used_item = I) - H.add_mob_blood(C) - playsound(get_turf(H),I.hitsound,75,TRUE) - return /obj/projectile/tentacle/on_hit(atom/movable/target, blocked = 0, pierce_hit) if(!isliving(firer) || !ismovable(target)) diff --git a/code/modules/antagonists/clown_ops/outfits.dm b/code/modules/antagonists/clown_ops/outfits.dm index fb025e40dbd20..10793237c33c9 100644 --- a/code/modules/antagonists/clown_ops/outfits.dm +++ b/code/modules/antagonists/clown_ops/outfits.dm @@ -10,6 +10,7 @@ r_pocket = /obj/item/bikehorn id = /obj/item/card/id/advanced/chameleon backpack_contents = list( + /obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine = 1, //The clown op equivalent to the Ansem /obj/item/pen/edagger = 1, /obj/item/dnainjector/clumsymut = 1, //in case you want to be clumsy for the memes /obj/item/storage/box/syndie_kit/clownpins = 1, //for any guns that you get your grubby little clown op mitts on diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 32d1ca218769b..d889ffb2a545f 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -14,6 +14,7 @@ icon_state = "render" inhand_icon_state = "cultdagger" worn_icon_state = "render" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' inhand_x_dimension = 32 @@ -61,6 +62,7 @@ Striking a noncultist, however, will tear their flesh."} icon_state = "cultblade" inhand_icon_state = "cultblade" worn_icon_state = "cultblade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 @@ -75,10 +77,12 @@ Striking a noncultist, however, will tear their flesh."} bare_wound_bonus = 20 hitsound = 'sound/items/weapons/bladeslice.ogg' block_sound = 'sound/items/weapons/parry.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") /// If TRUE, it can be used at will by anyone, non-cultists included var/free_use = FALSE + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/melee/cultblade/Initialize(mapload) . = ..() @@ -86,6 +90,9 @@ Striking a noncultist, however, will tear their flesh."} speed = 4 SECONDS, \ effectiveness = 100, \ ) + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5) ADD_TRAIT(src, TRAIT_CONTRABAND, INNATE_TRAIT) /obj/item/melee/cultblade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) @@ -1131,6 +1138,7 @@ Striking a noncultist, however, will tear their flesh."} icon_state = "occultpoleaxe0" base_icon_state = "occultpoleaxe" inhand_icon_state = "occultpoleaxe0" + icon_angle = -45 w_class = WEIGHT_CLASS_HUGE force = 17 throwforce = 40 diff --git a/code/modules/antagonists/cult/cult_structure_archives.dm b/code/modules/antagonists/cult/cult_structure_archives.dm index d4867659651f8..050fe7361a587 100644 --- a/code/modules/antagonists/cult/cult_structure_archives.dm +++ b/code/modules/antagonists/cult/cult_structure_archives.dm @@ -49,8 +49,9 @@ /obj/structure/destructible/cult/item_dispenser/archives/succcess_message(mob/living/user, obj/item/spawned_item) to_chat(user, span_cult_italic("You summon [spawned_item] from [src]!")) -// Preset for the library that doesn't spawn runed metal on destruction. +// Preset for the library that doesn't spawn runed metal on destruction, or glow. /obj/structure/destructible/cult/item_dispenser/archives/library + icon_state = "tomealtar_off" debris = list() #undef CULT_BLINDFOLD diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index af7254119ddc5..f4b424e4f2076 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -82,6 +82,7 @@ Runes can either be invoked by one's self or with many different cultists. Each var/image/I = image(icon = 'icons/effects/blood.dmi', icon_state = null, loc = src) I.override = TRUE add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "cult_runes", I) + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) /obj/effect/rune/examine(mob/user) . = ..() diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index 7b608223a9803..1cee767dc969e 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -5,6 +5,7 @@ icon = 'icons/obj/weapons/khopesh.dmi' icon_state = "eldritch_blade" inhand_icon_state = "eldritch_blade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 @@ -21,8 +22,8 @@ demolition_mod = 0.8 hitsound = 'sound/items/weapons/bladeslice.ogg' armour_penetration = 35 - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") var/after_use_message = "" /obj/item/melee/sickly_blade/examine(mob/user) diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm index d545e258a302e..d3e1765a0b92c 100644 --- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm @@ -226,7 +226,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/star_gazer + /datum/pet_command/attack/star_gazer ) /datum/heretic_knowledge/ultimate/cosmic_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice) @@ -242,7 +242,7 @@ star_gazer_mob.maxHealth = INFINITY star_gazer_mob.health = INFINITY user.AddComponent(/datum/component/death_linked, star_gazer_mob) - star_gazer_mob.AddComponent(/datum/component/obeys_commands, star_gazer_commands) + star_gazer_mob.AddComponent(/datum/component/obeys_commands, star_gazer_commands, radial_menu_lifetime = 15 SECONDS, radial_relative_to_user = TRUE) star_gazer_mob.AddComponent(/datum/component/damage_aura, range = 7, burn_damage = 0.5, simple_damage = 0.5, immune_factions = list(FACTION_HERETIC), current_owner = user) star_gazer_mob.befriend(user) var/datum/action/cooldown/open_mob_commands/commands_action = new /datum/action/cooldown/open_mob_commands() diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 41db760f53ae6..4a60d372445e1 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -287,12 +287,13 @@ return var/need_mob_update = FALSE - need_mob_update += source.adjustBruteLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustFireLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustToxLoss(-5, updating_health = FALSE, forced = TRUE) - need_mob_update += source.adjustOxyLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustStaminaLoss(-20, updating_stamina = FALSE) + var/base_heal_amt = 2.5 * DELTA_WORLD_TIME(SSmobs) + need_mob_update += source.adjustBruteLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustFireLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustToxLoss(-base_heal_amt, updating_health = FALSE, forced = TRUE) + need_mob_update += source.adjustOxyLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustStaminaLoss(-base_heal_amt * 4, updating_stamina = FALSE) if(source.blood_volume < BLOOD_VOLUME_NORMAL) - source.blood_volume += 5 * seconds_per_tick + source.blood_volume += base_heal_amt if(need_mob_update) source.updatehealth() diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm index be8f103678e09..7eee200dd4a8c 100644 --- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm +++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm @@ -69,6 +69,7 @@ var/image/silicon_image = image(icon = 'icons/obj/service/hand_of_god_structures.dmi', icon_state = null, loc = src) silicon_image.override = TRUE add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "cosmic", silicon_image) + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) /obj/effect/cosmic_rune/attack_paw(mob/living/user, list/modifiers) return attack_hand(user, modifiers) diff --git a/code/modules/antagonists/heretic/rust_effect.dm b/code/modules/antagonists/heretic/rust_effect.dm index 9af6c4f6d89a0..294fe42cff12a 100644 --- a/code/modules/antagonists/heretic/rust_effect.dm +++ b/code/modules/antagonists/heretic/rust_effect.dm @@ -13,3 +13,4 @@ pixel_x = rand(-6, 6) icon_state = "small_rune_[rand(1, 12)]" update_appearance() + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm index f3d37b8768259..eb8a3a4769b0e 100644 --- a/code/modules/antagonists/heretic/structures/carving_knife.dm +++ b/code/modules/antagonists/heretic/structures/carving_knife.dm @@ -5,6 +5,7 @@ but only few can evoke the dangers that lurk beneath reality." icon = 'icons/obj/antags/eldritch.dmi' icon_state = "rune_carver" + icon_angle = -45 obj_flags = CONDUCTS_ELECTRICITY sharpness = SHARP_EDGED w_class = WEIGHT_CLASS_SMALL @@ -12,8 +13,8 @@ force = 10 throwforce = 20 hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") actions_types = list(/datum/action/item_action/rune_shatter) embed_type = /datum/embed_data/rune_carver @@ -25,6 +26,14 @@ var/list/datum/weakref/current_runes = list() /// Turfs that you cannot draw carvings on var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava)) + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/melee/rune_carver/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) /datum/embed_data/rune_carver ignore_throwspeed_threshold = TRUE diff --git a/code/modules/antagonists/heretic/transmutation_rune.dm b/code/modules/antagonists/heretic/transmutation_rune.dm index a2bf4af77f4fa..ee25b0a18fdda 100644 --- a/code/modules/antagonists/heretic/transmutation_rune.dm +++ b/code/modules/antagonists/heretic/transmutation_rune.dm @@ -17,6 +17,7 @@ var/image/silicon_image = image(icon = 'icons/effects/eldritch.dmi', icon_state = null, loc = src) silicon_image.override = TRUE add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "heretic_rune", silicon_image) + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) /obj/effect/heretic_rune/examine(mob/user) . = ..() diff --git a/code/modules/antagonists/nightmare/nightmare_equipment.dm b/code/modules/antagonists/nightmare/nightmare_equipment.dm index 52a687f9ac795..1d356d3dd9d18 100644 --- a/code/modules/antagonists/nightmare/nightmare_equipment.dm +++ b/code/modules/antagonists/nightmare/nightmare_equipment.dm @@ -6,6 +6,7 @@ icon = 'icons/obj/weapons/changeling_items.dmi' icon_state = "arm_blade" inhand_icon_state = "arm_blade" + icon_angle = 180 force = 25 armour_penetration = 35 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' diff --git a/code/modules/antagonists/ninja/energy_katana.dm b/code/modules/antagonists/ninja/energy_katana.dm index efd993550915f..c26cb44d39c43 100644 --- a/code/modules/antagonists/ninja/energy_katana.dm +++ b/code/modules/antagonists/ninja/energy_katana.dm @@ -17,6 +17,7 @@ icon_state = "energy_katana" inhand_icon_state = "energy_katana" worn_icon_state = "energy_katana" + icon_angle = 35 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' force = 30 @@ -28,8 +29,8 @@ pickup_sound = 'sound/items/unsheath.ogg' drop_sound = 'sound/items/sheath.ogg' block_sound = 'sound/items/weapons/block_blade.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT sharpness = SHARP_EDGED max_integrity = 200 diff --git a/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm b/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm index f88c09188cf48..5367a5cd89cb5 100644 --- a/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm +++ b/code/modules/antagonists/voidwalker/voidwalker_void_eater.dm @@ -7,6 +7,7 @@ icon = 'icons/obj/weapons/voidwalker_items.dmi' icon_state = "tentacle" inhand_icon_state = "tentacle" + icon_angle = 180 force = 25 armour_penetration = 35 lefthand_file = 'icons/mob/inhands/antag/voidwalker_lefthand.dmi' diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 465765c75a332..9b0b16ef9f6e1 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -10,6 +10,7 @@ icon_state = "bone_blade" inhand_icon_state = "bone_blade" worn_icon_state = "bone_blade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 @@ -401,6 +402,7 @@ desc = "This scepter allows you to conjure, force push and detonate Runic Vendors. It can hold up to 3 charges that can be recovered with a simple magical channeling. A modern spin on the old Geomancy spells." icon_state = "vendor_staff" inhand_icon_state = "vendor_staff" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' icon = 'icons/obj/weapons/guns/magic.dmi' diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm index 6d08cd539fed5..74c647777bf02 100644 --- a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm +++ b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm @@ -79,6 +79,7 @@ silicon_image.override = TRUE add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "wizard_rune", silicon_image) announce_rune() + ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT) /// I cast Summon Security /obj/effect/grand_rune/proc/announce_rune() diff --git a/code/modules/art/statues.dm b/code/modules/art/statues.dm index eeb0cfa9cb432..a6a15c9773220 100644 --- a/code/modules/art/statues.dm +++ b/code/modules/art/statues.dm @@ -265,6 +265,7 @@ icon = 'icons/obj/art/statue.dmi' icon_state = "chisel" inhand_icon_state = "screwdriver_nuke" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 9705246fa593c..f3877a1424d03 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -544,7 +544,7 @@ SSair.add_to_rebuild_queue(src) /obj/machinery/atmospherics/update_name() - if(!override_naming) + if(!override_naming && !HAS_TRAIT(src, TRAIT_WAS_RENAMED)) name = "[GLOB.pipe_color_name[pipe_color]] [initial(name)]" return ..() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm index b26f0c028d1af..c8cb2e849caec 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm @@ -7,6 +7,7 @@ device_type = BINARY layer = GAS_PUMP_LAYER pipe_flags = PIPING_BRIDGE + obj_flags = parent_type::obj_flags | UNIQUE_RENAME /obj/machinery/atmospherics/components/binary/set_init_directions() switch(dir) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 97b9741701f8a..65a587578e595 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -101,6 +101,8 @@ var/datum/gas_machine_connector/internal_connector /// Check if the machine has been turned on var/on = FALSE + /// The sound loop that can be heard when the generator is processing. + var/datum/looping_sound/cryo_cell/soundloop /datum/armor/unary_cryo_cell energy = 100 @@ -119,6 +121,7 @@ occupant_vis = new(mapload, src) vis_contents += occupant_vis internal_connector = new(loc, src, dir, CELL_VOLUME * 0.5) + soundloop = new(src) register_context() @@ -130,6 +133,7 @@ QDEL_NULL(radio) QDEL_NULL(beaker) QDEL_NULL(internal_connector) + QDEL_NULL(soundloop) return ..() @@ -378,10 +382,14 @@ /obj/machinery/cryo_cell/begin_processing() . = ..() SSair.start_processing_machine(src) + if(soundloop) + soundloop.start() /obj/machinery/cryo_cell/end_processing() . = ..() SSair.stop_processing_machine(src) + if(soundloop) + soundloop.stop() /obj/machinery/cryo_cell/on_set_is_operational(old_value) //Turned off diff --git a/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm index 157cbae9af0df..e852fccdc31c0 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/machine_connector.dm @@ -57,8 +57,12 @@ /** * Called when the machine has been moved, reconnect to the pipe network */ -/datum/gas_machine_connector/proc/moved_connected_machine() +/datum/gas_machine_connector/proc/moved_connected_machine(obj/machinery/source, atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) SIGNAL_HANDLER + if(forced) // Called from parent doing abstract_move() + gas_connector.abstract_move(get_turf(connected_machine)) + return // No side-effects means no disconnections + gas_connector.forceMove(get_turf(connected_machine)) reconnect_connector() diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 9fdfe1f629ec2..cefaeef91f3c3 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -107,8 +107,9 @@ COMSIG_BITRUNNER_ALERT_SEVER, COMSIG_BITRUNNER_CACHE_SEVER, COMSIG_BITRUNNER_LADDER_SEVER, - COMSIG_LIVING_DEATH, COMSIG_LIVING_PILL_CONSUMED, + COMSIG_LIVING_DEATH, + COMSIG_QDELETING, COMSIG_MOB_APPLY_DAMAGE, )) diff --git a/code/modules/cargo/packs/_packs.dm b/code/modules/cargo/packs/_packs.dm index b6c533050f675..bfa95b2a7ed25 100644 --- a/code/modules/cargo/packs/_packs.dm +++ b/code/modules/cargo/packs/_packs.dm @@ -106,7 +106,7 @@ name = "mining order" hidden = TRUE crate_name = "shaft mining delivery crate" - access = list(ACCESS_MINING) + access = ACCESS_MINING /datum/supply_pack/custom/New(purchaser, cost, list/contains) . = ..() @@ -117,7 +117,7 @@ /datum/supply_pack/custom/minerals name = "materials order" crate_name = "galactic materials market delivery crate" - access = list() + access = FALSE crate_type = /obj/structure/closet/crate/cardboard /datum/supply_pack/custom/minerals/New(purchaser, cost, list/contains) diff --git a/code/modules/cargo/packs/general.dm b/code/modules/cargo/packs/general.dm index e294d1bd4a1ef..5f3a4645933d1 100644 --- a/code/modules/cargo/packs/general.dm +++ b/code/modules/cargo/packs/general.dm @@ -137,9 +137,9 @@ /datum/supply_pack/misc/funeral - name = "Funeral Supply crate" + name = "Funeral Supplies Crate" desc = "At the end of the day, someone's gonna want someone dead. Give them a proper send-off with these \ - funeral supplies! Contains a coffin with burial garmets and flowers." + funeral supplies! Contains a coffin with burial garments and flowers." cost = CARGO_CRATE_VALUE * 1.6 access_view = ACCESS_CHAPEL_OFFICE contains = list(/obj/item/clothing/under/misc/burial, @@ -165,7 +165,7 @@ /datum/supply_pack/misc/religious_supplies name = "Religious Supplies Crate" desc = "Keep your local chaplain happy and well-supplied, lest they call down judgement upon your \ - cargo bay. Contains two bottles of holywater, bibles, chaplain robes, and burial garmets." + cargo bay. Contains two bottles of holy water, bibles, chaplain robes, and burial garments." cost = CARGO_CRATE_VALUE * 6 // it costs so much because the Space Church needs funding to build a cathedral access_view = ACCESS_CHAPEL_OFFICE contains = list(/obj/item/reagent_containers/cup/glass/bottle/holywater = 2, diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index 62008d8be48e0..da51ee497f66d 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -259,3 +259,10 @@ cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/storage/fish_case/tiziran = 2) crate_name = "tiziran fish crate" + +/datum/supply_pack/critter/turtle + name = "Turtle Crate" + desc = "Cute flora turtles that'll emit good vibes to nearby plants!" + cost = CARGO_CRATE_VALUE * 2 + contains = list(/mob/living/basic/turtle) + crate_name = "flora-turtle crate" diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index fbf27521365a3..3487edb78fdd4 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -128,17 +128,6 @@ var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) return initial(species_type.digitigrade_customization) == DIGITIGRADE_OPTIONAL - -/datum/preference/choiced/lizard_legs/is_accessible(datum/preferences/preferences) - . = ..() - - if(!.) - return - - var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) - - return initial(species_type.digitigrade_customization) & DIGITIGRADE_OPTIONAL - /datum/preference/choiced/lizard_snout savefile_key = "feature_lizard_snout" savefile_identifier = PREFERENCE_CHARACTER diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 4f2202e8114e4..c498435677b11 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -336,7 +336,7 @@ /obj/item/clothing/suit/armor/balloon_vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(isitem(hitby)) var/obj/item/item_hit = hitby - if(item_hit.sharpness) + if(item_hit.get_sharpness()) pop() if(istype(hitby, /obj/projectile/bullet)) diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 0c09afb90cd2d..8f838ea339bc9 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -10,6 +10,7 @@ icon = 'icons/obj/aquarium/fish.dmi' lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi' righthand_file = 'icons/mob/inhands/fish_righthand.dmi' + icon_angle = 180 force = 6 throwforce = 6 throw_range = 8 diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index a4b1e5924f8f3..5fcfdd07ff350 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -5,6 +5,7 @@ desc = "You can fish with this." icon = 'icons/obj/fishing.dmi' icon_state = "fishing_rod" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/fishing_rod_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/fishing_rod_righthand.dmi' inhand_icon_state = "rod" @@ -140,7 +141,7 @@ var/list/block = list() var/get_percent = HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH) block += span_info("You think you can cast it up to [get_cast_range()] tiles away.") - block += get_stat_info(get_percent, difficulty_modifier, "Fishing will be", "easier", "harder", "with this fishing rod") + block += get_stat_info(get_percent, difficulty_modifier * 0.01, "Fishing will be", "easier", "harder", "with this fishing rod", offset = 0) block += get_stat_info(get_percent, experience_multiplier, "You will gain experience", "faster", "slower") block += get_stat_info(get_percent, completion_speed_mult, "You should complete the minigame", "faster", "slower") block += get_stat_info(get_percent, bait_speed_mult, "Reeling is", "faster", "slower") @@ -148,6 +149,7 @@ block += get_stat_info(get_percent, bounciness_mult, "This fishing rod is ", "bouncier", "less bouncy", "than a normal one", less_is_better = TRUE) block += get_stat_info(get_percent, gravity_mult, "The lure will sink", "faster", "slower", span_info = TRUE) + list_clear_nulls(block) . += examine_block(block.Join("\n")) if(get_percent && (material_flags & MATERIAL_EFFECTS) && length(custom_materials)) @@ -172,15 +174,16 @@ . += examine_block(block.Join("\n")) ///Used in examine_more to reduce all the copypasta when getting more information about the various stats of the fishing rod. -/obj/item/fishing_rod/proc/get_stat_info(get_percent, value, prefix, easier, harder, suffix = "with this fishing rod", span_info = FALSE, less_is_better = FALSE) +/obj/item/fishing_rod/proc/get_stat_info(get_percent, value, prefix, easier, harder, suffix = "with this fishing rod", span_info = FALSE, less_is_better = FALSE, offset = 1) if(value == 1) return - var/percent = get_percent ? "[abs(value)]% " : "" - var/harder_easier = value > 1 ? easier : harder + value -= offset + var/percent = get_percent ? "[abs(value * 100)]% " : "" + var/harder_easier = value > 0 ? easier : harder . = "[prefix] [percent][harder_easier] [suffix]." if(span_info) return span_info(.) - if(less_is_better ? value < 1 : value > 1) + if(less_is_better ? value < 0 : value > 0) return span_nicegreen(.) return span_danger(.) diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index abf3a298462cb..cb0eaecc9890d 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -367,7 +367,10 @@ 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) || isfish(result)) + if(ispath(result, /mob/living) && bait && (HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT) || HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))) + final_table[result] = round(final_table[result] * result_multiplier, 1) + + else 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) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 6f2a38d4d6146..8177651973546 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -507,6 +507,7 @@ /obj/item/seeds/random = 1, /mob/living/basic/frog = 1, /mob/living/basic/axolotl = 1, + /mob/living/basic/turtle = 2, ) fish_counts = list( /obj/item/food/grown/grass = 10, diff --git a/code/modules/hallucination/delusions.dm b/code/modules/hallucination/delusions.dm index 0760d05ff46c6..da12f117803d1 100644 --- a/code/modules/hallucination/delusions.dm +++ b/code/modules/hallucination/delusions.dm @@ -230,6 +230,22 @@ return ..() +/datum/hallucination/delusion/preset/seccies + dynamic_delusion = TRUE + random_hallucination_weight = 0 + delusion_name = "Security" + affects_others = TRUE + affects_us = FALSE + +/datum/hallucination/delusion/preset/seccies/make_delusion_image(mob/over_who) + delusion_appearance = get_dynamic_human_appearance( + outfit_path = /datum/outfit/job/security, + bloody_slots = prob(5) ? ALL : NONE, + r_hand = prob(15) ? /obj/item/melee/baton/security/loaded : null, + l_hand = prob(15) ? /obj/item/melee/baton/security/loaded : null, + ) + return ..() + /// Hallucination used by the nightmare vision goggles to turn everyone except you into mares /datum/hallucination/delusion/preset/mare delusion_icon_file = 'icons/obj/clothing/masks.dmi' diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index b670cd869e9b2..33462d2ab4380 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -218,32 +218,6 @@ afflicted.adjustStaminaLoss(20) afflicted.adjust_eye_blur(4 SECONDS) -/obj/projectile/hallucination/taser - name = "electrode" - damage_type = BURN - hal_icon_state = "spark" - color = COLOR_YELLOW - hal_fire_sound = 'sound/items/weapons/taser.ogg' - hal_hitsound = 'sound/items/weapons/taserhit.ogg' - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/projectile/hallucination/taser/apply_effect_to_hallucinator(mob/living/afflicted) - afflicted.Paralyze(10 SECONDS) - afflicted.adjust_stutter(40 SECONDS) - if(HAS_TRAIT(afflicted, TRAIT_HULK)) - afflicted.say(pick( - ";RAAAAAAAARGH!", - ";HNNNNNNNNNGGGGGGH!", - ";GWAAAAAAAARRRHHH!", - "NNNNNNNNGGGGGGGGHH!", - ";AAAAAAARRRGH!"), - forced = "hulk (hallucinating)", - ) - else if(!afflicted.check_stun_immunity(CANKNOCKDOWN)) - addtimer(CALLBACK(afflicted, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS) - /obj/projectile/hallucination/disabler name = "disabler beam" damage_type = STAMINA diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 6d9c380112b7c..6d80e1683fbe1 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -434,8 +434,8 @@ GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf playsound(src, SFX_SPARKS, 75, TRUE) obj_flags |= EMAGGED if (user) - balloon_alert(user, "safety protocols destroyed") // im gonna keep this once since this perfectly describes it, and the to_chat is just flavor - to_chat(user, span_warning("You vastly increase projector power and override the safety and security protocols.")) + balloon_alert(user, "safety protocols destroyed") // im gonna keep this once since this perfectly describes it + to_chat(user, span_warning("You override the safety and security protocols.")) user.log_message("emagged the Holodeck Control Console.", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] emagged the Holodeck Control Console.") diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 1b81661d24517..0ffc337204244 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -424,6 +424,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "cultivator" inhand_icon_state = "cultivator" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -442,6 +443,7 @@ /obj/item/cultivator/rake name = "rake" icon_state = "rake" + icon_angle = -45 w_class = WEIGHT_CLASS_NORMAL attack_verb_continuous = list("slashes", "slices", "bashes", "claws") attack_verb_simple = list("slash", "slice", "bash", "claw") @@ -473,6 +475,7 @@ name = "cyborg cultivator" icon = 'icons/obj/items_cyborg.dmi' icon_state = "sili_cultivator" + icon_angle = 0 /obj/item/hatchet name = "hatchet" @@ -480,6 +483,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "hatchet" inhand_icon_state = "hatchet" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -523,6 +527,7 @@ name = "cyborg hatchet" icon = 'icons/obj/items_cyborg.dmi' icon_state = "sili_hatchet" + icon_angle = 0 /obj/item/scythe name = "scythe" @@ -530,6 +535,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "scythe0" inhand_icon_state = "scythe0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' force = 15 @@ -594,6 +600,7 @@ icon_state = "secateurs" inhand_icon_state = null worn_icon_state = "cutters" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -621,6 +628,7 @@ name = "cyborg secateurs" icon = 'icons/obj/items_cyborg.dmi' icon_state = "sili_secateur" + icon_angle = 0 /obj/item/geneshears name = "botanogenetic plant shears" diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index c8a3e10b8e931..ac2e0e3b767f6 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -559,7 +559,7 @@ if(weedlevel == new_weedlevel) return SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_WEEDLEVEL, new_weedlevel) - weedlevel = new_weedlevel + weedlevel = max(new_weedlevel, 0) if(update_icon) update_appearance() diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm index 09d4bc285645b..f23fdb7f76d50 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm @@ -108,6 +108,7 @@ icon_state = "claymore_gold" inhand_icon_state = "claymore_gold" worn_icon_state = "claymore_gold" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_BULKY @@ -116,9 +117,17 @@ block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") menu_description = "A sharp claymore which provides a low chance of blocking incoming melee attacks. Can be worn on the back or belt." + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/nullrod/claymore/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -3) /obj/item/nullrod/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(attack_type == PROJECTILE_ATTACK || attack_type == LEAP_ATTACK) @@ -132,6 +141,7 @@ icon_state = "cultblade" inhand_icon_state = "cultblade" worn_icon_state = "cultblade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 @@ -192,6 +202,7 @@ icon_state = "e_sword_on_blue" inhand_icon_state = "e_sword_on_blue" worn_icon_state = "swordblue" + icon_angle = -45 slot_flags = ITEM_SLOT_BELT hitsound = 'sound/items/weapons/blade1.ogg' block_sound = 'sound/items/weapons/block_blade.ogg' @@ -221,6 +232,7 @@ icon_state = "hfrequency0" inhand_icon_state = "hfrequency1" worn_icon_state = "hfrequency0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_BULKY @@ -231,6 +243,14 @@ attack_verb_simple = list("chop", "slice", "cut", "zandatsu") hitsound = 'sound/items/weapons/rapierhit.ogg' menu_description = "A sharp blade which partially penetrates armor. Very effective at butchering bodies. Can be worn on the back." + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") + +/obj/item/nullrod/vibro/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -3) /obj/item/nullrod/vibro/Initialize(mapload) . = ..() @@ -258,6 +278,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "talking_sword" inhand_icon_state = "talking_sword" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' worn_icon_state = "talking_sword" @@ -320,6 +341,7 @@ icon = 'icons/obj/weapons/staff.dmi' icon_state = "godstaff-red" inhand_icon_state = "godstaff-red" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' w_class = WEIGHT_CLASS_HUGE @@ -352,6 +374,7 @@ icon_state = "sord" inhand_icon_state = "sord" worn_icon_state = "sord" + icon_angle = -35 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' force = 4.13 @@ -376,6 +399,7 @@ icon_state = "hammeron" inhand_icon_state = "hammeron" worn_icon_state = "hammeron" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' w_class = WEIGHT_CLASS_BULKY @@ -427,13 +451,14 @@ icon = 'icons/obj/weapons/khopesh.dmi' icon_state = "clownrender" inhand_icon_state = "cultdagger" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' worn_icon_state = "render" hitsound = 'sound/items/bikehorn.ogg' sharpness = SHARP_EDGED - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") menu_description = "A sharp dagger. Fits in pockets. Can be worn on the belt. Honk." // Pride-struck Hammer - Transfers reagents in your body to those you hit. @@ -447,6 +472,7 @@ icon_state = "pride" inhand_icon_state = "pride" worn_icon_state = "pride" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' force = 16 @@ -479,6 +505,7 @@ icon_state = "chain" inhand_icon_state = "chain" worn_icon_state = "whip" + icon_angle = -90 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -520,6 +547,7 @@ icon = 'icons/obj/weapons/changeling_items.dmi' icon_state = "arm_blade" inhand_icon_state = "arm_blade" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' slot_flags = null @@ -588,6 +616,7 @@ base_icon_state = "bostaff" inhand_icon_state = "bostaff0" worn_icon_state = "bostaff0" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' menu_description = "A staff which provides a medium-low chance of blocking incoming melee attacks and deals less damage, unless dual-wielded. Can be worn on the back." @@ -616,20 +645,26 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "crysknife" inhand_icon_state = "crysknife" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_HUGE sharpness = SHARP_EDGED slot_flags = null hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") item_flags = SLOWS_WHILE_IN_HAND menu_description = "A sharp knife. Randomly speeds or slows its user at a regular intervals. Capable of butchering bodies. Cannot be worn anywhere." + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/nullrod/tribal_knife/Initialize(mapload) . = ..() START_PROCESSING(SSobj, src) + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -3) AddComponent(/datum/component/butchering, \ speed = 5 SECONDS, \ effectiveness = 100, \ @@ -654,6 +689,7 @@ icon = 'icons/obj/weapons/spear.dmi' icon_state = "pitchfork0" inhand_icon_state = "pitchfork0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' worn_icon_state = "pitchfork0" @@ -691,6 +727,7 @@ icon_state = "hypertool" inhand_icon_state = "hypertool" worn_icon_state = "hypertool" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -709,6 +746,7 @@ icon = 'icons/obj/weapons/spear.dmi' icon_state = "ratvarian_spear" inhand_icon_state = "ratvarian_spear" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/antag/clockwork_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/clockwork_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -728,6 +766,7 @@ icon = 'icons/obj/weapons/spear.dmi' icon_state = "ratvarian_spear" inhand_icon_state = "ratvarian_spear" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/antag/clockwork_lefthand.dmi' righthand_file = 'icons/mob/inhands/antag/clockwork_righthand.dmi' slot_flags = ITEM_SLOT_BELT @@ -751,6 +790,7 @@ icon_state = "nullsword" inhand_icon_state = "nullsword" worn_icon_state = "nullsword" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' w_class = WEIGHT_CLASS_BULKY @@ -759,14 +799,23 @@ bare_wound_bonus = 30 slot_flags = ITEM_SLOT_BELT block_sound = 'sound/items/weapons/parry.ogg' - sharpness = SHARP_POINTY + sharpness = SHARP_EDGED hitsound = 'sound/items/weapons/bladeslice.ogg' - attack_verb_continuous = list("attacks", "punctures", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "puncture", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slashes", "slice", "tear", "lacerate", "rip", "dice", "cut") menu_description = "A blade that deals variable, low amounts of damage, but does easily inflict wounds. \ The stronger your swinging arm is, the stronger the blade is, though only slightly. \ Against debilitated targets, can also deal additional sneak attack damage with a very high wound chance." + var/list/alt_continuous = list("stabs", "pierces", "impales", "punctures") + var/list/alt_simple = list("stab", "pierce", "impale", "puncture") + +/obj/item/nullrod/nullblade/Initialize(mapload) + . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) + /obj/item/nullrod/nullblade/melee_attack_chain(mob/user, atom/target, params) //Track our actual force separately var/old_force = force diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm index ce4a30efa9299..df2e4f277b7da 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm @@ -30,6 +30,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and icon_state = "vorpalscythe" inhand_icon_state = "vorpalscythe" worn_icon_state = null + icon_angle = -35 // Scythes look better when slightly angled lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' inhand_x_dimension = 64 diff --git a/code/modules/library/book.dm b/code/modules/library/book.dm index 0a190b946622f..3a95bf56aaab5 100644 --- a/code/modules/library/book.dm +++ b/code/modules/library/book.dm @@ -196,7 +196,7 @@ if(!user.combat_mode) return FALSE //special check for wirecutter's because they don't have a sharp edge - if((carving_item.sharpness & SHARP_EDGED) || (carving_item.tool_behaviour == TOOL_WIRECUTTER)) + if((carving_item.get_sharpness() & SHARP_EDGED) || (carving_item.tool_behaviour == TOOL_WIRECUTTER)) balloon_alert(user, "carving out...") if(!do_after(user, 3 SECONDS, target = src)) balloon_alert(user, "interrupted!") diff --git a/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm b/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm index d6b2d9ffaa754..c531ae75e0dfc 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/caravanambush.dm @@ -4,6 +4,7 @@ icon_state = "wrench_caravan" desc = "A prototype of a new wrench design, allegedly the red color scheme makes it go faster." name = "experimental wrench" + icon_angle = -90 toolspeed = 0.3 /obj/item/screwdriver/caravan diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm index 88b9e9f9503f4..d348ad6ac190c 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -100,7 +100,7 @@ stored_organ = pick_weight(allowed_organs) /obj/structure/meateor_fluff/flesh_pod/attackby(obj/item/attacking_item, mob/user, params) - if (attacking_item.sharpness & SHARP_EDGED) + if (attacking_item.get_sharpness() & SHARP_EDGED) cut_open(user) return return ..() diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index c5bdfa9daa239..09982715f23c3 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -6,15 +6,16 @@ * a good tradeoff and a decent playstyle. */ /obj/item/kinetic_crusher + name = "proto-kinetic crusher" + desc = "An early design of the proto-kinetic accelerator, it is little more than a combination of various mining tools cobbled together, \ + forming a high-tech club. While it is an effective mining tool, it did little to aid any but the most skilled and/or \ + suicidal miners against local fauna." icon = 'icons/obj/mining.dmi' icon_state = "crusher" inhand_icon_state = "crusher0" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' - name = "proto-kinetic crusher" - desc = "An early design of the proto-kinetic accelerator, it is little more than a combination of various mining tools cobbled together, \ - forming a high-tech club. While it is an effective mining tool, it did little to aid any but the most skilled and/or \ - suicidal miners against local fauna." resistance_flags = FIRE_PROOF force = 0 //You can't hit stuff unless wielded w_class = WEIGHT_CLASS_BULKY @@ -51,6 +52,17 @@ ) //technically it's huge and bulky, but this provides an incentive to use it AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20) + register_context() + +/obj/item/kinetic_crusher/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(!held_item) + context[SCREENTIP_CONTEXT_RMB] = "Detach trophy" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item) && held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Detach all trophies" + return CONTEXTUAL_SCREENTIP_SET /obj/item/kinetic_crusher/Destroy() QDEL_LIST(trophies) @@ -85,6 +97,46 @@ crusher_trophy.remove_from(src, user) return ITEM_INTERACT_SUCCESS +// adapted from kinetic accelerator attack_hand_secodary +/obj/item/kinetic_crusher/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(!LAZYLEN(trophies)) + return SECONDARY_ATTACK_CONTINUE_CHAIN + + var/list/display_names = list() + var/list/items = list() + for(var/trophies_length in 1 to length(trophies)) + var/obj/item/crusher_trophy/trophy = trophies[trophies_length] + display_names[trophy.name] = REF(trophy) + var/image/item_image = image(icon = trophy.icon, icon_state = trophy.icon_state) + if(length(trophy.overlays)) + item_image.copy_overlays(trophy) + items["[trophy.name]"] = item_image + + var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 36, require_near = TRUE, tooltips = TRUE) + if(!pick) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + var/trophy_reference = display_names[pick] + var/obj/item/crusher_trophy/trophy_to_remove = locate(trophy_reference) in trophies + if(!istype(trophy_to_remove)) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + trophy_to_remove.remove_from(src, user) + if(!user.put_in_hands(trophy_to_remove)) + trophy_to_remove.forceMove(drop_location()) + + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/kinetic_crusher/proc/check_menu(mob/living/carbon/human/user) + if(!istype(user)) + return FALSE + if(user.incapacitated) + return FALSE + return TRUE + /obj/item/kinetic_crusher/pre_attack(atom/A, mob/living/user, params) . = ..() if(.) diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index 06f8ec1828dd1..23d59fbfb71f2 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/mining.dmi' icon_state = "pickaxe" inhand_icon_state = "pickaxe" + icon_angle = -45 obj_flags = CONDUCTS_ELECTRICITY slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK force = 15 @@ -65,6 +66,7 @@ name = "mining drill" icon_state = "handdrill" inhand_icon_state = "handdrill" + icon_angle = 0 slot_flags = ITEM_SLOT_BELT toolspeed = 0.6 //available from roundstart, faster than a pickaxe. usesound = 'sound/items/weapons/drill.ogg' @@ -121,6 +123,7 @@ icon = 'icons/obj/mining.dmi' icon_state = "shovel" inhand_icon_state = "shovel" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -157,6 +160,7 @@ desc = "A small tool for digging and moving dirt." icon_state = "spade" inhand_icon_state = "spade" + icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' force = 5 @@ -167,6 +171,7 @@ name = "cyborg spade" icon = 'icons/obj/items_cyborg.dmi' icon_state = "sili_shovel" + icon_angle = 0 toolspeed = 0.6 worn_icon_state = null @@ -212,6 +217,7 @@ icon = 'icons/obj/mining.dmi' icon_state = "trench_tool" inhand_icon_state = "trench_tool" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -301,6 +307,7 @@ desc = "A gigantic wrench made illegal because of its many incidents involving this tool." icon_state = "giant_wrench" icon = 'icons/obj/weapons/giant_wrench.dmi' + icon_angle = 0 inhand_icon_state = null lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 3b33119aa24e7..cfe087a8ab38a 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -46,6 +46,7 @@ desc = "The strange technology of this large club allows various nigh-magical teleportation feats. It used to beat you, but now you can set the beat." icon_state = "hierophant_club_ready_beacon" inhand_icon_state = "hierophant_club_ready_beacon" + icon_angle = -135 icon = 'icons/obj/mining_zones/artefacts.dmi' lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' @@ -649,6 +650,7 @@ icon = 'icons/obj/weapons/sword.dmi' icon_state = "spectral" inhand_icon_state = "spectral" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -658,17 +660,22 @@ throwforce = 1 hitsound = 'sound/effects/ghost2.ogg' block_sound = 'sound/items/weapons/parry.ogg' - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend") resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF var/summon_cooldown = 0 var/list/mob/dead/observer/spirits + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/melee/ghost_sword/Initialize(mapload) . = ..() spirits = list() START_PROCESSING(SSobj, src) SSpoints_of_interest.make_point_of_interest(src) + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) AddComponent(\ /datum/component/butchering, \ speed = 15 SECONDS, \ @@ -784,6 +791,7 @@ desc = "The ability to fill the emergency shuttle with lava. What more could you want out of life?" icon_state = "lavastaff" inhand_icon_state = "lavastaff" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' icon = 'icons/obj/weapons/guns/magic.dmi' @@ -985,6 +993,7 @@ desc = "An ancient staff retrieved from the remains of Legion. The wind stirs as you move it." icon_state = "staffofstorms" inhand_icon_state = "staffofstorms" + icon_angle = -45 icon = 'icons/obj/weapons/guns/magic.dmi' lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index bb36b6d91027c..31c4c1177acc4 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -73,6 +73,7 @@ righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' icon_state = "asclepius_dormant" inhand_icon_state = "asclepius_dormant" + icon_angle = -45 var/activated = FALSE /obj/item/rod_of_asclepius/Initialize(mapload) @@ -990,6 +991,7 @@ Even with the weapon destroyed, all the pieces containing the creature have coagulated back together to find a new host." icon = 'icons/obj/mining_zones/artefacts.dmi' icon_state = "cursed_katana" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' force = 15 @@ -998,8 +1000,8 @@ block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED w_class = WEIGHT_CLASS_HUGE - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") hitsound = 'sound/items/weapons/bladeslice.ogg' resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | FREEZE_PROOF var/shattered = FALSE @@ -1012,9 +1014,14 @@ ATTACK_CLOAK = list(COMBO_STEPS = list(LEFT_ATTACK, RIGHT_ATTACK, LEFT_ATTACK, RIGHT_ATTACK), COMBO_PROC = PROC_REF(cloak)), ATTACK_SHATTER = list(COMBO_STEPS = list(RIGHT_ATTACK, LEFT_ATTACK, RIGHT_ATTACK, LEFT_ATTACK), COMBO_PROC = PROC_REF(shatter)), ) + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/cursed_katana/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) AddComponent( \ /datum/component/combo_attacks, \ combos = combo_list, \ @@ -1078,6 +1085,7 @@ user.visible_message(span_warning("[user] does a wide slice!"), span_notice("You do a wide slice!")) playsound(src, 'sound/items/weapons/bladeslice.ogg', 50, TRUE) + user.do_item_attack_animation(target, used_item = src, animation_type = ATTACK_ANIMATION_SLASH) var/turf/user_turf = get_turf(user) var/dir_to_target = get_dir(user_turf, get_turf(target)) var/static/list/cursed_katana_slice_angles = list(0, -45, 45, -90, 90) //so that the animation animates towards the target clicked and not towards a side target @@ -1117,6 +1125,7 @@ user.visible_message(span_warning("[user] cuts [target]'s tendons!"), span_notice("You tendon cut [target]!")) to_chat(target, span_userdanger("Your tendons have been cut by [user]!")) + user.do_item_attack_animation(target, used_item = src, animation_type = ATTACK_ANIMATION_SLASH) target.apply_damage(damage = 15, sharpness = SHARP_EDGED, wound_bonus = 15) user.do_attack_animation(target, ATTACK_EFFECT_DISARM) playsound(src, 'sound/items/weapons/rapierhit.ogg', 50, TRUE) diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index d2823eae2b580..5e77627e00d55 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -104,7 +104,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) if(isliving(mob_poi)) serialized += get_living_data(mob_poi) - var/list/antag_data = get_antag_data(mob_poi.mind) + var/list/antag_data = get_antag_data(mob_poi.mind, user?.client?.holder) if(length(antag_data)) serialized += antag_data antagonists += list(serialized) @@ -151,11 +151,11 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) /// Helper function to get threat type, group, overrides for job and icon -/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind) as /list +/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind, is_admin) as /list var/list/serialized = list() for(var/datum/antagonist/antag as anything in poi_mind.antag_datums) - if(!antag.show_to_ghosts) + if(!antag.show_to_ghosts && !is_admin) continue serialized["antag"] = antag.name diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 68cd6a231e326..14de9f04068d2 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -127,7 +127,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/clean, + /datum/pet_command/clean, ) /mob/living/basic/bot/cleanbot/Initialize(mapload) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 1b6d840062208..0a6a4b03b4354 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -200,14 +200,15 @@ return return ..() -/datum/pet_command/point_targeting/clean +/datum/pet_command/clean command_name = "Clean" command_desc = "Command a cleanbot to clean the mess." + requires_pointing = TRUE radial_icon = 'icons/obj/service/janitor.dmi' radial_icon_state = "mop" speech_commands = list("clean", "mop") -/datum/pet_command/point_targeting/clean/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/clean/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !istype(target, /obj/effect/decal/cleanable)) return if(isnull(parent.ai_controller)) @@ -216,7 +217,7 @@ return return ..() -/datum/pet_command/point_targeting/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) +/datum/pet_command/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm index 17b257d8c987b..775e22101bc17 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm @@ -49,18 +49,18 @@ ) ///our neutral voicelines var/static/list/neutral_voicelines = list( - REPAIRBOT_VOICED_BRICK = 'sound/voice/repairbot/brick.ogg', - REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', - REPAIRBOT_VOICED_FIX_IT = 'sound/voice/repairbot/fixit.ogg', - REPAIRBOT_VOICED_FIX_TOUCH = 'sound/voice/repairbot/fixtouch.ogg', - REPAIRBOT_VOICED_HOLE = 'sound/voice/repairbot/patchingholes.ogg', - REPAIRBOT_VOICED_PAY = 'sound/voice/repairbot/pay.ogg', + REPAIRBOT_VOICED_BRICK = 'sound/mobs/non-humanoids/repairbot/brick.ogg', + REPAIRBOT_VOICED_ENTROPY = 'sound/mobs/non-humanoids/repairbot/entropy.ogg', + REPAIRBOT_VOICED_FIX_IT = 'sound/mobs/non-humanoids/repairbot/fixit.ogg', + REPAIRBOT_VOICED_FIX_TOUCH = 'sound/mobs/non-humanoids/repairbot/fixtouch.ogg', + REPAIRBOT_VOICED_HOLE = 'sound/mobs/non-humanoids/repairbot/patchingholes.ogg', + REPAIRBOT_VOICED_PAY = 'sound/mobs/non-humanoids/repairbot/pay.ogg', ) ///our emagged voicelines var/static/list/emagged_voicelines = list( - REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', - REPAIRBOT_VOICED_STRINGS = 'sound/voice/repairbot/strings.ogg', - REPAIRBOT_VOICED_PASSION = 'sound/voice/repairbot/passionproject.ogg', + REPAIRBOT_VOICED_ENTROPY = 'sound/mobs/non-humanoids/repairbot/entropy.ogg', + REPAIRBOT_VOICED_STRINGS = 'sound/mobs/non-humanoids/repairbot/strings.ogg', + REPAIRBOT_VOICED_PASSION = 'sound/mobs/non-humanoids/repairbot/passionproject.ogg', ) ///types we can retrieve from our ui var/static/list/retrievable_types = list( diff --git a/code/modules/mob/living/basic/drone/drone_tools.dm b/code/modules/mob/living/basic/drone/drone_tools.dm index 8408738bf6a3d..dfb05a8d3a9bc 100644 --- a/code/modules/mob/living/basic/drone/drone_tools.dm +++ b/code/modules/mob/living/basic/drone/drone_tools.dm @@ -34,6 +34,7 @@ icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_crowbar" inhand_icon_state = "crowbar" + icon_angle = 0 item_flags = NO_MAT_REDEMPTION /obj/item/screwdriver/drone @@ -61,6 +62,7 @@ icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_wrench" inhand_icon_state = "wrench" + icon_angle = 0 item_flags = NO_MAT_REDEMPTION /obj/item/weldingtool/drone @@ -84,6 +86,7 @@ desc = "A multitool built into your chassis." icon = 'icons/obj/items_cyborg.dmi' icon_state = "toolkit_engiborg_multitool" + icon_angle = 0 item_flags = NO_MAT_REDEMPTION toolspeed = 0.5 diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm index e9dd79fd5ef06..09bf3a5819592 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm @@ -57,7 +57,7 @@ /datum/pet_command/beehive/enter, /datum/pet_command/beehive/exit, /datum/pet_command/follow/bee, - /datum/pet_command/point_targeting/attack/swirl, + /datum/pet_command/attack/swirl, /datum/pet_command/scatter, ) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 1081c9b7b63b8..8472098662466 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -113,8 +113,9 @@ required_distance = 0 ///swirl around the owner in menacing fashion -/datum/pet_command/point_targeting/attack/swirl +/datum/pet_command/attack/swirl command_name = "Swirl" + requires_pointing = TRUE command_desc = "Your pets will swirl around you and attack whoever you point at!" speech_commands = list("swirl", "spiral", "swarm") pointed_reaction = null @@ -123,7 +124,7 @@ ///the owner we will swarm around var/key_to_swarm = BB_SWARM_TARGET -/datum/pet_command/point_targeting/attack/swirl/try_activate_command(mob/living/commander) +/datum/pet_command/attack/swirl/try_activate_command(mob/living/commander, radial_command) var/mob/living/living_pawn = weak_parent.resolve() if(isnull(living_pawn)) return @@ -134,7 +135,7 @@ controller.set_blackboard_key(key_to_swarm, commander) return ..() -/datum/pet_command/point_targeting/attack/swirl/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/swirl/execute_action(datum/ai_controller/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) return ..() controller.queue_behavior(/datum/ai_behavior/swirl_around_target, BB_SWARM_TARGET) @@ -186,7 +187,7 @@ radial_icon = 'icons/obj/service/hydroponics/equipment.dmi' radial_icon_state = "beebox" -/datum/pet_command/beehive/try_activate_command(mob/living/commander) +/datum/pet_command/beehive/try_activate_command(mob/living/commander, radial_command) var/mob/living/living_pawn = weak_parent.resolve() if(isnull(living_pawn)) return diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm index 57a0ddfe322d8..7ffd7bc657ef6 100644 --- a/code/modules/mob/living/basic/heretic/star_gazer.dm +++ b/code/modules/mob/living/basic/heretic/star_gazer.dm @@ -103,7 +103,7 @@ can_attack_turfs = TRUE can_attack_dense_objects = TRUE -/datum/pet_command/point_targeting/attack/star_gazer +/datum/pet_command/attack/star_gazer speech_commands = list("attack", "sic", "kill", "slash them") command_feedback = "stares!" pointed_reaction = "stares intensely!" diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm index b82092147f67d..6056b1919090b 100644 --- a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm @@ -43,11 +43,12 @@ //commands to give when tamed var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/good_boy/wolf, /datum/pet_command/follow/wolf, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack, + /datum/pet_command/fetch, /datum/pet_command/play_dead, /datum/pet_command/protect_owner, ) diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper.dm b/code/modules/mob/living/basic/jungle/leaper/leaper.dm index 94babd0218e5b..d4f310d07aba1 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper.dm @@ -40,13 +40,14 @@ ///list of pet commands we can issue var/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/follow, /datum/pet_command/untargeted_ability/blood_rain, /datum/pet_command/untargeted_ability/summon_toad, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/flop, - /datum/pet_command/point_targeting/use_ability/bubble, + /datum/pet_command/attack, + /datum/pet_command/use_ability/flop, + /datum/pet_command/use_ability/bubble, ) /mob/living/basic/leaper/Initialize(mapload) diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm index e776117a3a596..b5c917bf2a46e 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm @@ -39,7 +39,7 @@ ability_key = BB_LEAPER_SUMMON finish_planning = FALSE -/datum/pet_command/point_targeting/use_ability/flop +/datum/pet_command/use_ability/flop command_name = "Flop" command_desc = "Command your pet to belly flop your target!" radial_icon = 'icons/mob/actions/actions_items.dmi' @@ -47,7 +47,7 @@ speech_commands = list("flop", "crush") pet_ability_key = BB_LEAPER_FLOP -/datum/pet_command/point_targeting/use_ability/bubble +/datum/pet_command/use_ability/bubble command_name = "Poison Bubble" command_desc = "Launch poisonous bubbles at your target!" radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' @@ -55,6 +55,9 @@ speech_commands = list("bubble", "shoot") pet_ability_key = BB_LEAPER_BUBBLE +/datum/pet_command/use_ability/bubble/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to shoot a bubble towards [target]!" + /datum/pet_command/untargeted_ability/blood_rain command_name = "Blood Rain" command_desc = "Let it rain poisonous blood!" @@ -63,6 +66,8 @@ speech_commands = list("blood", "rain", "volley") ability_key = BB_LEAPER_VOLLEY +/datum/pet_command/untargeted_ability/blood_rain/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to unleash a volley of rain!" /datum/pet_command/untargeted_ability/summon_toad command_name = "Summon Toads" @@ -71,3 +76,6 @@ radial_icon_state = "frog_trash" speech_commands = list("frogs", "bombers") ability_key = BB_LEAPER_SUMMON + +/datum/pet_command/untargeted_ability/summon_toad/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to summon some explosive frogs!" diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm index 0df19c5c27d31..9778aff8753df 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm @@ -214,9 +214,9 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/solarbeam, - /datum/pet_command/point_targeting/use_ability/rapidseeds, + /datum/pet_command/attack, + /datum/pet_command/use_ability/solarbeam, + /datum/pet_command/use_ability/rapidseeds, ) //abilities diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm index ee93a9c12366f..ae4e7b75c92b0 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -166,7 +166,7 @@ finish_planning = FALSE ///pet commands -/datum/pet_command/point_targeting/use_ability/solarbeam +/datum/pet_command/use_ability/solarbeam command_name = "Launch solarbeam" command_desc = "Command your pet to launch a solarbeam at your target!" radial_icon = 'icons/effects/beam.dmi' @@ -174,10 +174,17 @@ speech_commands = list("beam", "solar") pet_ability_key = BB_SOLARBEAM_ABILITY -/datum/pet_command/point_targeting/use_ability/rapidseeds +/datum/pet_command/use_ability/solarbeam/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to use a solar beam on [target]!" + + +/datum/pet_command/use_ability/rapidseeds command_name = "Rapid seeds" command_desc = "Command your pet to launch a volley of seeds at your target!" radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' radial_icon_state = "seedling" speech_commands = list("rapid", "seeds", "volley") pet_ability_key = BB_RAPIDSEEDS_ABILITY + +/datum/pet_command/use_ability/rapidseeds/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to unleash a volley of seeds on [target]!" diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm index 014cfb626be0a..9b1916c9ac0f3 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_loot.dm @@ -21,7 +21,7 @@ icon_state = "brimdust" icon = 'icons/obj/mining.dmi' plane = GAME_PLANE - layer = GAME_CLEAN_LAYER + layer = CLEANABLE_OBJECT_LAYER mergeable_decal = FALSE /obj/effect/decal/cleanable/brimdust/Initialize(mapload) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index 5a1166962be55..ce1c4e7cf1982 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -36,7 +36,7 @@ /datum/pet_command/free, /datum/pet_command/grub_spit, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, ) /mob/living/basic/mining/goldgrub/Initialize(mapload) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 8ea2467a2a813..3d5d73a7343d1 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -211,6 +211,8 @@ /datum/pet_command/grub_spit command_name = "Spit" + radial_icon = 'icons/obj/ore.dmi' + radial_icon_state = "uranium" command_desc = "Ask your grub pet to spit out its ores." speech_commands = list("spit", "ores") @@ -222,4 +224,7 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/grub_spit/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to spit its ores!" + #undef BURROW_RANGE diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm index 5bfbbb1051a7f..347e9a95b61ce 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm @@ -61,7 +61,7 @@ if(isnull(ore_food)) balloon_alert(src, "no food!") else - melee_attack(ore_food) + UnarmedAttack(ore_food, TRUE, modifiers) return FALSE /mob/living/basic/mining/gutlunch/proc/after_birth(mob/living/basic/mining/gutlunch/grub/baby, mob/living/partner) @@ -111,11 +111,12 @@ //pet commands when we tame the gutluncher var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/breed/gutlunch, + /datum/pet_command/attack, + /datum/pet_command/breed/gutlunch, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, /datum/pet_command/mine_walls, ) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm index 261f6d22a021b..4b329a0003aa8 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -102,10 +102,11 @@ /datum/pet_command/mine_walls command_name = "Mine" + radial_icon_state = "mine" command_desc = "Command your pet to mine down walls." speech_commands = list("mine", "smash") -/datum/pet_command/mine_walls/try_activate_command(mob/living/commander) +/datum/pet_command/mine_walls/try_activate_command(mob/living/commander, radial_command) var/mob/living/parent = weak_parent.resolve() if(isnull(parent)) return @@ -121,10 +122,13 @@ return SUBTREE_RETURN_FINISH_PLANNING controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) +/datum/pet_command/mine_walls/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to start mining!" + //pet commands -/datum/pet_command/point_targeting/breed/gutlunch +/datum/pet_command/breed/gutlunch -/datum/pet_command/point_targeting/breed/gutlunch/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/breed/gutlunch/set_command_target(mob/living/parent, atom/target) if(GLOB.gutlunch_count >= MAXIMUM_GUTLUNCH_POP) parent.balloon_alert_to_viewers("can't reproduce anymore!") return diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index 0b8babf82ec30..7e7d3e71819bf 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -32,7 +32,7 @@ /// The type of charging ability we give this mob var/charge_type = /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster /// The pet command for the charging ability we give this mob - var/charge_command = /datum/pet_command/point_targeting/use_ability/lob_charge + var/charge_command = /datum/pet_command/use_ability/lob_charge /// At which speed do we amputate limbs var/snip_speed = 5 SECONDS ///Lobstrosities are natural anglers. This rapresent their proficiency at fishing when not mindless @@ -72,10 +72,11 @@ var/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/move, + /datum/pet_command/attack, charge_command, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fish, + /datum/pet_command/fish, ) AddComponent(/datum/component/happiness) AddComponent(/datum/component/obeys_commands, pet_commands) @@ -153,7 +154,7 @@ ai_controller = /datum/ai_controller/basic_controller/lobstrosity/juvenile snip_speed = 6.5 SECONDS charge_type = /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/shrimp - charge_command = /datum/pet_command/point_targeting/use_ability/lob_charge/shrimp + charge_command = /datum/pet_command/use_ability/lob_charge/shrimp base_fishing_level = SKILL_LEVEL_NOVICE /// What do we become when we grow up? var/mob/living/basic/mining/lobstrosity/grow_type = /mob/living/basic/mining/lobstrosity @@ -254,7 +255,7 @@ charger.apply_status_effect(/datum/status_effect/tired_post_charge/lesser) ///Command the lobster to charge at someone. -/datum/pet_command/point_targeting/use_ability/lob_charge +/datum/pet_command/use_ability/lob_charge command_name = "Charge" command_desc = "Command your lobstrosity to charge against someone." radial_icon = 'icons/mob/actions/actions_items.dmi' @@ -265,7 +266,7 @@ pet_ability_key = BB_TARGETED_ACTION ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/long_ranged -/datum/pet_command/point_targeting/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) if (!target) return var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(parent.ai_controller.blackboard[targeting_strategy_key]) @@ -274,5 +275,5 @@ return FALSE return ..() -/datum/pet_command/point_targeting/use_ability/lob_charge/shrimp +/datum/pet_command/use_ability/lob_charge/shrimp ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/short_ranged diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm index f492c83e74bac..539aa47cb287d 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -41,8 +41,8 @@ var/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack, + /datum/pet_command/fetch, ) /mob/living/basic/mining/mook/Initialize(mapload) diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm index 15da812a0b237..2f374e2dda4b2 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -1,7 +1,7 @@ ///commands the chief can pick from GLOBAL_LIST_INIT(mook_commands, list( - new /datum/pet_command/point_targeting/attack, - new /datum/pet_command/point_targeting/fetch, + new /datum/pet_command/attack, + new /datum/pet_command/fetch, )) /datum/ai_controller/basic_controller/mook @@ -346,7 +346,7 @@ GLOBAL_LIST_INIT(mook_commands, list( if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) return if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/point_targeting/attack) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/attack) return var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] @@ -356,7 +356,7 @@ GLOBAL_LIST_INIT(mook_commands, list( if(get_dist(ore_target, living_pawn) <= 1) return - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/point_targeting/fetch) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/fetch) /datum/ai_behavior/issue_commands action_cooldown = 5 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm index 8bf54b7165b28..3909a392195a8 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm @@ -47,11 +47,13 @@ GLOBAL_LIST_EMPTY(raptor_population) var/ridable_component = /datum/component/riding/creature/raptor //pet commands when we tame the raptor var/static/list/pet_commands = list( + /datum/pet_command/breed, /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/attack, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, ) ///things we inherited from our parent var/datum/raptor_inheritance/inherited_stats @@ -158,7 +160,7 @@ GLOBAL_LIST_EMPTY(raptor_population) if(isnull(ore_food)) balloon_alert(src, "no food!") else - melee_attack(ore_food) + UnarmedAttack(ore_food, TRUE, modifiers) return FALSE /mob/living/basic/raptor/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index c9edfb0471f27..b14d1469ccd1f 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -42,13 +42,14 @@ ///the commands our owner can give us var/static/list/pet_commands = list( /datum/pet_command/idle/minebot, + /datum/pet_command/move, /datum/pet_command/protect_owner/minebot, /datum/pet_command/minebot_ability/light, /datum/pet_command/minebot_ability/dump, /datum/pet_command/automate_mining, /datum/pet_command/free/minebot, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/minebot, + /datum/pet_command/attack/minebot, ) ///possible colors the bot can have var/static/list/possible_colors= list( diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index 0d66c36c04ab6..39248a63295ae 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -281,14 +281,16 @@ /datum/pet_command/automate_mining command_name = "Automate mining" command_desc = "Make your minebot automatically mine!" - radial_icon = 'icons/obj/mining.dmi' - radial_icon_state = "pickaxe" + radial_icon_state = "mine" speech_commands = list("mine") callout_type = /datum/callout_option/mine /datum/pet_command/automate_mining/valid_callout_target(mob/living/speaker, datum/callout_option/callout, atom/target) return ismineralturf(target) +/datum/pet_command/automate_mining/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to start mining!" + /datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) @@ -315,6 +317,9 @@ radial_icon_state = "mech_lights_off" ability_key = BB_MINEBOT_LIGHT_ABILITY +/datum/pet_command/minebot_ability/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to toggle its lights!" + /datum/pet_command/minebot_ability/dump command_name = "Dump ore" command_desc = "Make your minebot dump all its ore!" @@ -322,10 +327,13 @@ radial_icon_state = "mech_eject" ability_key = BB_MINEBOT_DUMP_ABILITY -/datum/pet_command/point_targeting/attack/minebot +/datum/pet_command/minebot_ability/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to dump its ore!" + +/datum/pet_command/attack/minebot attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot -/datum/pet_command/point_targeting/attack/minebot/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/minebot/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) var/mob/living/living_pawn = controller.pawn if(!living_pawn.combat_mode) diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index fd8920d2ca0e3..8fb053eef094c 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -7,10 +7,10 @@ speech_commands = list("good dog") // Set correct attack behaviour -/datum/pet_command/point_targeting/attack/dog +/datum/pet_command/attack/dog attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog -/datum/pet_command/point_targeting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) +/datum/pet_command/attack/dog/set_command_active(mob/living/parent, mob/living/commander) . = ..() parent.ai_controller.set_blackboard_key(BB_DOG_HARASS_HARM, TRUE) @@ -38,11 +38,12 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/move, /datum/pet_command/good_boy/dog, /datum/pet_command/follow/dog, /datum/pet_command/perform_trick_sequence, - /datum/pet_command/point_targeting/attack/dog, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack/dog, + /datum/pet_command/fetch, /datum/pet_command/play_dead, ) ///icon state of the collar we can wear diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 737f7b21391fd..fab5e79a3134b 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -31,9 +31,10 @@ ///list of our pet commands we follow var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/attack, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.dm b/code/modules/mob/living/basic/pets/orbie/orbie.dm index b4099a8c6344c..202bc84d37eb1 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie.dm @@ -39,8 +39,9 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/move, /datum/pet_command/untargeted_ability/pet_lights, - /datum/pet_command/point_targeting/use_ability/take_photo, + /datum/pet_command/use_ability/take_photo, /datum/pet_command/follow/orbie, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm index a978b750d5036..aef780533795f 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm @@ -123,17 +123,24 @@ return SUBTREE_RETURN_FINISH_PLANNING return ..() -/datum/pet_command/point_targeting/use_ability/take_photo +/datum/pet_command/use_ability/pet_lights/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to toggle its lights!" + +/datum/pet_command/use_ability/take_photo command_name = "Photo" command_desc = "Make your pet take a photo!" - radial_icon = 'icons/mob/simple/pets.dmi' - radial_icon_state = "orbie_lights_action" + radial_icon = 'icons/obj/art/camera.dmi' + radial_icon_state = "camera" speech_commands = list("photo", "picture", "image") command_feedback = "Readys camera mode" pet_ability_key = BB_PHOTO_ABILITY targeting_strategy_key = BB_TARGETING_STRATEGY -/datum/pet_command/point_targeting/use_ability/take_photo/execute_action(datum/ai_controller/controller) +/datum/pet_command/use_ability/take_photo/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to take a photo of [target]!" + + +/datum/pet_command/use_ability/take_photo/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 3) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING @@ -152,6 +159,9 @@ return FALSE return findtext(spoken_text, text_command) +/datum/pet_command/perform_trick_sequence/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to dance!" + /datum/pet_command/perform_trick_sequence/execute_action(datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn var/list/trick_sequence = controller.blackboard[BB_TRICK_SEQUENCE] diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm index 77263b1748963..41b928aa75103 100644 --- a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm @@ -236,3 +236,6 @@ radial_icon_state = "1" speech_commands = list("rune", "revival") ability_key = BB_RUNE_ABILITY + +/datum/pet_command/untargeted_ability/draw_rune/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to draw a rune!" diff --git a/code/modules/mob/living/basic/slime/ai/pet_command.dm b/code/modules/mob/living/basic/slime/ai/pet_command.dm index 211d7aa552cd8..4b50b2b32b9e8 100644 --- a/code/modules/mob/living/basic/slime/ai/pet_command.dm +++ b/code/modules/mob/living/basic/slime/ai/pet_command.dm @@ -1,4 +1,4 @@ -/datum/pet_command/point_targeting/attack/slime +/datum/pet_command/attack/slime speech_commands = list("attack", "sic", "kill", "eat", "feed") command_feedback = "blorbles" pointed_reaction = "and blorbles" @@ -6,7 +6,7 @@ var/hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime -/datum/pet_command/point_targeting/attack/slime/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/slime/execute_action(datum/ai_controller/controller) var/mob/living/basic/slime/slime_pawn = controller.pawn if(isslime(slime_pawn) && slime_pawn.can_feed_on(controller.blackboard[BB_CURRENT_PET_TARGET], check_friendship = TRUE)) diff --git a/code/modules/mob/living/basic/slime/slime.dm b/code/modules/mob/living/basic/slime/slime.dm index 010913f44258b..8623f69b0e4cd 100644 --- a/code/modules/mob/living/basic/slime/slime.dm +++ b/code/modules/mob/living/basic/slime/slime.dm @@ -97,7 +97,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/slime, + /datum/pet_command/attack/slime, ) /// Our evolve action 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 6f843857578a6..0e0ba3b39a1ae 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -59,7 +59,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack + /datum/pet_command/attack ) /// Carp want to eat raw meat var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm index 810c45603862b..26894850b1989 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -1,6 +1,6 @@ #define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 -/datum/pet_command/point_targeting/use_ability/magicarp +/datum/pet_command/use_ability/magicarp pet_ability_key = BB_MAGICARP_SPELL /datum/ai_planning_subtree/attack_obstacle_in_path/carp diff --git a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm index 65d16cfb490dd..3b6d1e5e922fd 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm @@ -56,8 +56,8 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list( /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/magicarp, + /datum/pet_command/attack, + /datum/pet_command/use_ability/magicarp, ) /// List of all projectiles we can fire. /// Non-static, because subtypes can have their own lists. diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index eae137787ed0d..07ad70a29e3bc 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -179,10 +179,12 @@ if(SEND_SIGNAL(target, COMSIG_RAT_INTERACT, src) & COMPONENT_RAT_INTERACTED) return FALSE - if(isnull(mind) || !combat_mode) + if(isnull(mind) || combat_mode) return TRUE - poison_target(target) + if(poison_target(target)) + return FALSE + return TRUE /// Checks if we are allowed to attack this mob. Will return TRUE if we are potentially allowed to attack, but if we end up in a case where we should NOT attack, return FALSE. @@ -204,10 +206,17 @@ return TRUE -/// Attempts to add rat spit to a target, effectively poisoning it to whoever eats it. Yuckers. +/** + * Attempts to add rat spit to a target, effectively poisoning it to whoever eats it. Yuckers. + * Returns TRUE if the target is valid for adding rat spit + * Returns FALSE if the target is invalid for adding rat spit + * Arguments + * + * * atom/lean_target - the target we try to add the spit to + */ /mob/living/basic/regal_rat/proc/poison_target(atom/target) if(isnull(target.reagents) || !target.is_injectable(src, allowmobs = TRUE)) - return + return FALSE visible_message( span_warning("[src] starts licking [target] passionately!"), @@ -216,10 +225,11 @@ ) if (!do_after(src, 2 SECONDS, target, interaction_key = REGALRAT_INTERACTION)) - return + return TRUE // don't return false here because they tried to lick and the do_after was interrupted, otherwise cancelling the do_after will make them hit the target. target.reagents.add_reagent(/datum/reagent/rat_spit, rand(1,3), no_react = TRUE) balloon_alert(src, "licked") + return TRUE /** * Conditionally "eat" cheese object and heal, if injured. diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm index f7997e589695d..74f6c76b459e7 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -65,7 +65,7 @@ /datum/pet_command/free, /datum/pet_command/protect_owner, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/mouse + /datum/pet_command/attack/mouse ) /// Commands you can give to glockroaches var/static/list/glockroach_commands = list( @@ -73,7 +73,7 @@ /datum/pet_command/free, /datum/pet_command/protect_owner/glockroach, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/glockroach + /datum/pet_command/attack/glockroach ) /datum/action/cooldown/mob_cooldown/riot/IsAvailable(feedback = FALSE) @@ -211,7 +211,7 @@ return TRUE // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targeting/attack/mouse +/datum/pet_command/attack/mouse speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine pointed_reaction = "and squeaks aggressively" @@ -219,7 +219,7 @@ attack_behaviour = /datum/ai_behavior/basic_melee_attack // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targeting/attack/glockroach +/datum/pet_command/attack/glockroach speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" pointed_reaction = "and cocks its gun" diff --git a/code/modules/mob/living/basic/turtle/turtle.dm b/code/modules/mob/living/basic/turtle/turtle.dm new file mode 100644 index 0000000000000..4d7c73edec6ca --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle.dm @@ -0,0 +1,240 @@ +#define PATH_PEST_KILLER "path_pest_killer" +#define PATH_PLANT_HEALER "path_plant_healer" +#define PATH_PLANT_MUTATOR "path_plant_mutator" +#define REQUIRED_TREE_GROWTH 250 +#define UPPER_BOUND_VOLUME 50 +#define LOWER_BOUND_VOLUME 10 + +/mob/living/basic/turtle + name = "turtle" + desc = "Dog." + icon_state = "turtle" + icon_living = "turtle" + icon_dead = "turtle_dead" + base_icon_state = "turtle" + icon = 'icons/mob/simple/pets.dmi' + butcher_results = list(/obj/item/food/meat/slab = 3, /obj/item/food/pickle = 1, /obj/item/stack/sheet/mineral/wood = 10) + mob_biotypes = MOB_ORGANIC | MOB_PLANT + mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT + health = 100 + maxHealth = 100 + speed = 5 + verb_say = "snaps" + verb_ask = "snaps curiously" + verb_exclaim = "snaps loudly" + verb_yell = "snaps loudly" + faction = list(FACTION_NEUTRAL) + ai_controller = /datum/ai_controller/basic_controller/turtle + ///our displayed tree + var/mutable_appearance/grown_tree + ///growth progress of our tree + var/list/path_growth_progress = list( + PATH_PLANT_HEALER = 0, + PATH_PLANT_MUTATOR = 0, + PATH_PEST_KILLER = 0, + ) + ///what nutrients leads to each evolution path + var/static/list/path_requirements = list( + //plant healers + /datum/reagent/plantnutriment/eznutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/robustharvestnutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/endurogrow = PATH_PLANT_HEALER, + //plant mutators + /datum/reagent/plantnutriment/left4zednutriment = PATH_PLANT_MUTATOR, + /datum/reagent/uranium = PATH_PLANT_MUTATOR, + //pest killers + /datum/reagent/toxin/pestkiller = PATH_PEST_KILLER, + ) + ///if we are fully grown, what is our path + var/developed_path + ///our last east/west direction + var/last_direction = WEST + +/mob/living/basic/turtle/Initialize(mapload) + . = ..() + + desc = pick( + "Likely Dog...", + "Praise the Dog!", + "Dog ahead.", + "Could this be a Dog?", + ) + var/static/list/eatable_food = list(/obj/item/seeds) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + AddElement(/datum/element/basic_eating, food_types = eatable_food) + AddComponent(/datum/component/happiness) + RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(pre_eat_food)) + update_appearance() + create_reagents(150, REAGENT_HOLDER_ALIVE) + add_verb(src, /mob/living/proc/toggle_resting) + START_PROCESSING(SSprocessing, src) + +/mob/living/basic/turtle/setDir(newdir) + if(REVERSE_DIR(last_direction) & newdir) + transform = transform.Scale(-1, 1) + last_direction = REVERSE_DIR(last_direction) + return ..() + +/mob/living/basic/turtle/proc/retrieve_destined_path() + var/current_max_growth = 0 + var/destined_path + for(var/evolution_path in path_growth_progress) + if(path_growth_progress[evolution_path] > current_max_growth) + destined_path = evolution_path + current_max_growth = path_growth_progress[evolution_path] + if(isnull(destined_path)) + destined_path = PATH_PLANT_HEALER + return destined_path + +/mob/living/basic/turtle/process(seconds_per_tick) + if(isnull(reagents) || !length(reagents.reagent_list)) //if we have no reagents, default to our highest destined path + set_plant_growth(retrieve_destined_path(), 0.5) + return + + for(var/datum/reagent/existing_reagent as anything in reagents.reagent_list) + var/evolution_path = path_requirements[existing_reagent.type] + + switch(existing_reagent.volume) + if(UPPER_BOUND_VOLUME to INFINITY) + set_plant_growth(evolution_path, 3) + if(LOWER_BOUND_VOLUME to UPPER_BOUND_VOLUME) + set_plant_growth(evolution_path, 2) + if(1 to LOWER_BOUND_VOLUME) + set_plant_growth(evolution_path, 1) + + reagents.remove_reagent(existing_reagent.type, 0.5) + +/mob/living/basic/turtle/proc/set_plant_growth(evolution_path, amount) + path_growth_progress[evolution_path] += amount + if(path_growth_progress[evolution_path] >= REQUIRED_TREE_GROWTH) + evolve_turtle(evolution_path) + +/mob/living/basic/turtle/examine(mob/user) + . = ..() + + if(stat == DEAD) + . += span_notice("Its tree seems to be all withered...") + return + + var/destined_path = retrieve_destined_path() + var/current_max_growth = path_growth_progress[destined_path] + + var/text_to_display = "Its tree seems to be exuding " + switch(destined_path) + if(PATH_PEST_KILLER) + text_to_display += "pest killing" + if(PATH_PLANT_HEALER) + text_to_display += "plant healing" + if(PATH_PLANT_MUTATOR) + text_to_display += "plant mutating" + + text_to_display += " properties... which [current_max_growth >= REQUIRED_TREE_GROWTH ? "seems to be fully grown" : "is yet to develop"]." + . += span_notice(text_to_display) + + +/mob/living/basic/turtle/proc/evolve_turtle(evolution_path) + var/static/list/evolution_gains = list( + PATH_PLANT_HEALER = list( + "tree_appearance" = "healer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/healer, + ), + PATH_PEST_KILLER = list( + "tree_appearance" = "killer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/killer, + ), + PATH_PLANT_MUTATOR = list( + "tree_appearance" = "mutator_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/mutator, + ), + ) + + var/tree_icon_state = evolution_gains[evolution_path]["tree_appearance"] + grown_tree = mutable_appearance(icon = 'icons/mob/simple/turtle_trees.dmi', icon_state = tree_icon_state) + + var/new_ability_path = evolution_gains[evolution_path]["tree_ability"] + developed_path = evolution_path + var/datum/action/cooldown/tree_ability = new new_ability_path(src) + tree_ability?.Grant(src) + ai_controller?.set_blackboard_key(BB_TURTLE_TREE_ABILITY, tree_ability) + STOP_PROCESSING(SSprocessing, src) + update_appearance() + +/mob/living/basic/turtle/update_icon_state() + . = ..() + if(stat == DEAD) + return + icon_state = resting ? "[base_icon_state]_resting" : base_icon_state + +/mob/living/basic/turtle/update_overlays() + . = ..() + if(stat == DEAD) + var/mutable_appearance/dead_overlay = mutable_appearance(icon = 'icons/mob/simple/pets.dmi', icon_state = developed_path ? "dead_tree" : "growing_tree") + dead_overlay.pixel_y = -2 + . += dead_overlay + return + var/pixel_offset = resting ? -2 : 2 + var/mutable_appearance/living_tree = grown_tree ? grown_tree : mutable_appearance(icon = icon, icon_state = "growing_tree") + living_tree.pixel_y = pixel_offset + . += living_tree + +/mob/living/basic/turtle/update_resting() + . = ..() + if(stat == DEAD) + return + update_appearance() + +/mob/living/basic/turtle/item_interaction(mob/living/user, obj/item/used_item, list/modifiers) + if(!istype(used_item, /obj/item/reagent_containers)) + return NONE + + if(isnull(used_item.reagents)) + balloon_alert(user, "empty!") + return ITEM_INTERACT_SUCCESS + + if(stat == DEAD) + balloon_alert(user, "its dead!") + return ITEM_INTERACT_SUCCESS + + var/should_transfer = FALSE + for(var/reagent in path_requirements) + if(used_item.reagents.has_reagent(reagent)) + should_transfer = TRUE + break + + if(!should_transfer) + balloon_alert(user, "refuses to drink!") + return ITEM_INTERACT_SUCCESS + + if(!do_after(user, 1.5 SECONDS, target = src)) + return ITEM_INTERACT_SUCCESS + + used_item.reagents.trans_to(reagents, 5) + balloon_alert(user, "drinks happily") + playsound(src, 'sound/items/drink.ogg', vol = 25, vary = TRUE) + return ITEM_INTERACT_SUCCESS + +/mob/living/basic/turtle/proc/pre_eat_food(datum/source, obj/item/seeds/potential_food) + SIGNAL_HANDLER + + if(!istype(potential_food)) + return NONE + if(ispath(potential_food.product, /obj/item/food/grown)) + addtimer(CALLBACK(src, PROC_REF(process_food), potential_food.product), 30 SECONDS) + return NONE + +/mob/living/basic/turtle/proc/process_food(food_path) + if(QDELETED(src) || stat != CONSCIOUS) + return + new food_path(drop_location()) + balloon_alert_to_viewers("spits out some food") + +/mob/living/basic/turtle/death(gibbed) + . = ..() + STOP_PROCESSING(SSprocessing, src) + +#undef PATH_PEST_KILLER +#undef PATH_PLANT_HEALER +#undef PATH_PLANT_MUTATOR +#undef REQUIRED_TREE_GROWTH +#undef UPPER_BOUND_VOLUME +#undef LOWER_BOUND_VOLUME diff --git a/code/modules/mob/living/basic/turtle/turtle_ability.dm b/code/modules/mob/living/basic/turtle/turtle_ability.dm new file mode 100644 index 0000000000000..3c3172b8cd558 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ability.dm @@ -0,0 +1,140 @@ +#define TREE_FIELD_DURATION_EFFECT 10 SECONDS +#define WARP_ANIMATE_TIME 0.35 SECONDS + +/datum/action/cooldown/mob_cooldown/turtle_tree + name = "Tree Ability" + desc = "Invoke your tree's special ability." + cooldown_time = 2 MINUTES + click_to_activate = FALSE + button_icon = 'icons/mob/simple/pets.dmi' + button_icon_state = "turtle" + ///type of effect our tree releases + var/effect_path + ///how many times our ability affects surroundings + var/maximum_intervals = 5 + ///time between each interval + var/time_between_intervals = 3 SECONDS + ///range our tree affects + var/tree_range = 5 + ///warp effect to apply some distortion to our field + var/atom/movable/warp_effect/turtle_field/warp + +/datum/action/cooldown/mob_cooldown/turtle_tree/Activate(atom/target) + . = ..() + warp = new(owner) + RegisterSignal(warp, COMSIG_QDELETING, PROC_REF(remove_warp)) + warp.alpha = 0 + owner.vis_contents += warp + + for(var/index in 0 to maximum_intervals) + addtimer(CALLBACK(src, PROC_REF(tree_effect)), time_between_intervals * index) + + animate(warp, transform = matrix(), alpha = warp::alpha, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(warp_extinguish)), (time_between_intervals * maximum_intervals) + 3 SECONDS) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/warp_extinguish() + if(QDELETED(warp)) + return + animate(warp, alpha = 0, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(remove_warp)), WARP_ANIMATE_TIME) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/remove_warp() + SIGNAL_HANDLER + + UnregisterSignal(warp, COMSIG_QDELETING) + warp = null + +///effect we apply on our trees +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/tree_effect() + SHOULD_CALL_PARENT(TRUE) + return (pre_effect_apply()) + +///things we should check for before applying our effects +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/pre_effect_apply() + if(QDELETED(owner) || owner.stat == DEAD) + return FALSE + var/obj/effect/tree_effect = new effect_path + owner.vis_contents += tree_effect + return TRUE + +///healer tree, heals nearby plants by small amounts +/datum/action/cooldown/mob_cooldown/turtle_tree/healer + effect_path = /obj/effect/temp_visual/circle_wave/tree/healer + ///amount we heal plants by + var/heal_amount = 5 + +/datum/action/cooldown/mob_cooldown/turtle_tree/healer/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + if(isnull(hydro.myseed)) + continue + hydro.adjust_plant_health(heal_amount) + +///killer tree, kills plant's pests and weeds, aswell as nearby vermin +/datum/action/cooldown/mob_cooldown/turtle_tree/killer + effect_path = /obj/effect/temp_visual/circle_wave/tree/killer + ///amount we heal plants by + var/vermin_damage_amount = 20 + ///type of vermin our field affects + var/static/list/vermin_mob_targets = typecacheof(list( + /mob/living/basic/cockroach, + /mob/living/basic/mouse/rat, + )) + ///how much we reduce weed levels + var/weed_level_reduce = 2 + +/datum/action/cooldown/mob_cooldown/turtle_tree/killer/tree_effect() + . = ..() + if(!.) + return + + for(var/atom/possible_target as anything in oview(tree_range, owner)) + + if(is_type_in_typecache(possible_target, vermin_mob_targets)) + var/mob/living/living_target = possible_target + living_target.apply_damage(vermin_damage_amount) + continue + + if(!istype(possible_target, /obj/machinery/hydroponics)) + continue + + var/obj/machinery/hydroponics/hydro = possible_target + if(isnull(hydro.myseed)) + continue + hydro.set_weedlevel(hydro.weedlevel - weed_level_reduce) + +///mutator tree, mutates nearby plants! +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator + effect_path = /obj/effect/temp_visual/circle_wave/tree/mutator + ///how much we mutate plants + var/mutator_boost = 1 + +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + hydro.myseed?.adjust_instability(mutator_boost) + +/atom/movable/warp_effect/turtle_field + alpha = 75 + +///effects we give our tree abilities depending on their type +/obj/effect/temp_visual/circle_wave/tree + vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE + duration = 10 SECONDS + amount_to_scale = 3 + +/obj/effect/temp_visual/circle_wave/tree/healer + color = "#28a3bc" + +/obj/effect/temp_visual/circle_wave/tree/killer + color = "#ce3ebf" + +/obj/effect/temp_visual/circle_wave/tree/mutator + color = "#c49f26" + +#undef TREE_FIELD_DURATION_EFFECT +#undef WARP_ANIMATE_TIME diff --git a/code/modules/mob/living/basic/turtle/turtle_ai.dm b/code/modules/mob/living/basic/turtle/turtle_ai.dm new file mode 100644 index 0000000000000..1af7c3111f78c --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ai.dm @@ -0,0 +1,92 @@ +/datum/ai_controller/basic_controller/turtle + blackboard = list( + BB_HAPPY_EMOTIONS = list( + "wiggles its tree in excitement!", + "raises its head up high!", + "wags its tail enthusiastically!", + ), + BB_MODERATE_EMOTIONS = list( + "keeps its head level, eyes half-closed.", + "basks in the light peacefully.", + ), + BB_SAD_EMOTIONS = list( + "looks towards the floor in dissapointment...", + "the leaves on its tree droop...", + ), + ) + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/express_happiness, + /datum/ai_planning_subtree/use_mob_ability/turtle_tree, + /datum/ai_planning_subtree/find_and_hunt_target/headbutt_people, //playfully headbutt people's legs + /datum/ai_planning_subtree/find_and_hunt_target/sniff_flora, //mmm the aroma + ) + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree + ability_key = BB_TURTLE_TREE_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/happiness_count = controller.blackboard[BB_BASIC_HAPPINESS] * 100 + if(happiness_count > 75) + return ..() + if(!SPT_PROB(happiness_count / 50, seconds_per_tick)) + return + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/sniff_flora + target_key = BB_TURTLE_FLORA_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/sniff_flora + hunting_behavior = /datum/ai_behavior/hunt_target/sniff_flora + hunt_targets = list( + /obj/machinery/hydroponics, + /obj/item/kirbyplants, + ) + hunt_range = 5 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/sniff_flora + action_cooldown = 1 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/sniff_flora/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(!istype(dinner)) + return TRUE + if(isnull(dinner.myseed)) + return FALSE + if(dinner.weedlevel > 5 || dinner.pestlevel > 5) //too smelly + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/sniff_flora + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") + +/datum/ai_planning_subtree/find_and_hunt_target/headbutt_people + target_key = BB_TURTLE_HEADBUTT_VICTIM + finding_behavior = /datum/ai_behavior/find_hunt_target/human_to_headbutt + hunting_behavior = /datum/ai_behavior/hunt_target/headbutt_leg + hunt_targets = list(/mob/living/carbon/human) + hunt_range = 4 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/human_to_headbutt + action_cooldown = 2 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/human_to_headbutt/valid_dinner(mob/living/source, mob/living/carbon/human/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(dinner.stat != CONSCIOUS) + return FALSE + if(isnull(dinner.get_bodypart(BODY_ZONE_R_LEG)) && isnull(dinner.get_bodypart(BODY_ZONE_L_LEG))) //no legs to headbutt! + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/headbutt_leg + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("playfully headbutts [hunted]'s legs!") + diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 73974cf248d74..f9875bae327b8 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -467,9 +467,9 @@ /obj/item/organ/brain/felinid //A bit smaller than average brain_size = 0.8 -/obj/item/organ/brain/lizard //A bit smaller than average +/obj/item/organ/brain/lizard name = "lizard brain" - desc = "This juicy piece of meat has a oversized brain stem and cerebellum, with not much of a limbic system to speak of at all. You would expect it's owner to be pretty cold blooded." + desc = "This juicy piece of meat has a oversized brain stem and cerebellum, with not much of a limbic system to speak of at all. You would expect its owner to be pretty cold blooded." organ_traits = list(TRAIT_TACKLING_TAILED_DEFENDER) /obj/item/organ/brain/abductor diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 82ff199e895e5..b416cd2c41e26 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -120,7 +120,7 @@ return throw_mode = THROW_MODE_DISABLED if(hud_used) - hud_used.throw_icon.icon_state = "act_throw_off" + hud_used.throw_icon.icon_state = "act_throw" SEND_SIGNAL(src, COMSIG_LIVING_THROW_MODE_TOGGLE, throw_mode) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 2c5d2df051758..b5bc0f4909edc 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -84,19 +84,28 @@ return TRUE return ..() -/mob/living/carbon/send_item_attack_message(obj/item/I, mob/living/user, hit_area, def_zone) - if(!I.force && !length(I.attack_verb_simple) && !length(I.attack_verb_continuous)) +/mob/living/carbon/send_item_attack_message(obj/item/weapon, mob/living/user, hit_area, def_zone) + if(!weapon.force && !length(weapon.attack_verb_simple) && !length(weapon.attack_verb_continuous)) return var/obj/item/bodypart/hit_bodypart = get_bodypart(def_zone) if(isnull(hit_bodypart)) // ?? return ..() - var/message_verb_continuous = length(I.attack_verb_continuous) ? "[pick(I.attack_verb_continuous)]" : "attacks" - var/message_verb_simple = length(I.attack_verb_simple) ? "[pick(I.attack_verb_simple)]" : "attack" + // Sanity in case one is null for some reason + var/picked_index = rand(max(length(weapon.attack_verb_simple), length(weapon.attack_verb_continuous))) + + var/message_verb_continuous = "attacks" + var/message_verb_simple = "attack" + var/message_hit_area = get_hit_area_message(hit_area) + // Sanity in case one is... longer than the other? + if (picked_index && length(weapon.attack_verb_continuous) >= picked_index) + message_verb_continuous = weapon.attack_verb_continuous[picked_index] + if (picked_index && length(weapon.attack_verb_simple) >= picked_index) + message_verb_simple = weapon.attack_verb_simple[picked_index] var/extra_wound_details = "" - if(I.damtype == BRUTE && hit_bodypart.can_dismember()) + if(weapon.damtype == BRUTE && hit_bodypart.can_dismember()) var/mangled_state = hit_bodypart.get_mangled_state() @@ -111,24 +120,21 @@ var/dismemberable = ((hit_bodypart.dismemberable_by_wound()) || hit_bodypart.dismemberable_by_total_damage()) if (dismemberable) extra_wound_details = ", threatening to sever it entirely" - else if((has_interior && (has_exterior && exterior_ready_to_dismember) && I.get_sharpness())) + else if((has_interior && (has_exterior && exterior_ready_to_dismember) && weapon.get_sharpness())) var/bone_text = hit_bodypart.get_internal_description() - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the [bone_text]" - else if(has_exterior && ((has_interior && interior_ready_to_dismember) && I.get_sharpness())) + extra_wound_details = ", [weapon.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the [bone_text]" + else if(has_exterior && ((has_interior && interior_ready_to_dismember) && weapon.get_sharpness())) var/tissue_text = hit_bodypart.get_external_description() - extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining [tissue_text]" - - var/message_hit_area = "" - if(hit_area) - message_hit_area = " in the [hit_area]" - var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" - var/attack_message_victim = "You're [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!" - var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I][extra_wound_details]!" + extra_wound_details = ", [weapon.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining [tissue_text]" + + var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [weapon][extra_wound_details]!" + var/attack_message_victim = "You're [message_verb_continuous][message_hit_area] with [weapon][extra_wound_details]!" + var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [weapon][extra_wound_details]!" if(user in viewers(src, null)) - attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [I][extra_wound_details]!" - attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [I][extra_wound_details]!" + attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [weapon][extra_wound_details]!" + attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [weapon][extra_wound_details]!" if(user == src) - attack_message_victim = "You [message_verb_simple] yourself[message_hit_area] with [I][extra_wound_details]!" + attack_message_victim = "You [message_verb_simple] yourself[message_hit_area] with [weapon][extra_wound_details]!" visible_message(span_danger("[attack_message_spectator]"),\ span_userdanger("[attack_message_victim]"), null, COMBAT_MESSAGE_RANGE, user) if(user != src) @@ -656,7 +662,8 @@ if (HAS_TRAIT(src, TRAIT_GENELESS)) return FALSE - if (run_armor_check(attack_flag = BIO, absorb_text = "Your armor protects you from [scramble_source]!") >= 100) + if (run_armor_check(attack_flag = BIO, silent = TRUE) >= 100) + to_chat(src, span_warning("Your armor shields you from [scramble_source]!")) return FALSE if (!length(GLOB.bioscrambler_valid_organs) || !length(GLOB.bioscrambler_valid_parts)) diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index c13ac14b100c1..56c687df7bb1b 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -11,6 +11,7 @@ usable_hands = 0 //Populated on init through list/bodyparts mobility_flags = MOBILITY_FLAGS_CARBON_DEFAULT blocks_emissive = EMISSIVE_BLOCK_NONE + mouse_drop_zone = TRUE // STOP_OVERLAY_UPDATE_BODY_PARTS is removed after we call update_body_parts() during init. living_flags = ALWAYS_DEATHGASP|STOP_OVERLAY_UPDATE_BODY_PARTS ///List of [/obj/item/organ]s in the mob. They don't go in the contents for some reason I don't want to know. diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 1becef215b01b..b63195b2f5988 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -24,7 +24,7 @@ . = ..() RegisterSignal(src, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(clean_face)) - AddComponent(/datum/component/personal_crafting) + AddComponent(/datum/component/personal_crafting, ui_human_crafting) AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6) AddComponent(/datum/component/bloodysoles/feet, FOOTPRINT_SPRITE_SHOES) AddElement(/datum/element/ridable, /datum/component/riding/creature/human) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 254144d063d65..73f63b8bfe110 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -191,7 +191,7 @@ var/damage = HAS_TRAIT(user, TRAIT_PERFECT_ATTACKER) ? monkey_mouth.unarmed_damage_high : rand(monkey_mouth.unarmed_damage_low, monkey_mouth.unarmed_damage_high) if(!damage) return FALSE - if(check_block(user, damage, "the [user.name]")) + if(check_block(user, damage, "the [user.name]", attack_type = UNARMED_ATTACK)) return FALSE apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, MELEE)) return TRUE @@ -248,9 +248,6 @@ return TRUE apply_damage(damage, BRUTE, affecting, armor_block) - - - /mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L, list/modifiers) . = ..() if(!.) @@ -258,7 +255,7 @@ var/damage = rand(L.melee_damage_lower, L.melee_damage_upper) if(!damage) return - if(check_block(L, damage, "the [L.name]")) + if(check_block(L, damage, "the [L.name]", attack_type = UNARMED_ATTACK)) return FALSE if(stat != DEAD) L.amount_grown = min(L.amount_grown + damage, L.max_grown) diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index c8b05c1ae4d58..fad518a323465 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -262,12 +262,20 @@ if(HAS_TRAIT(user, TRAIT_KISS_OF_DEATH)) kiss_type = /obj/item/hand_item/kisser/death + var/datum/action/cooldown/ink_spit/ink_action = locate() in user.actions + if(ink_action?.IsAvailable()) + kiss_type = /obj/item/hand_item/kisser/ink + ink_action.StartCooldown() + else + ink_action = null + var/obj/item/kiss_blower = new kiss_type(user) if(user.put_in_hands(kiss_blower)) to_chat(user, span_notice("You ready your kiss-blowing hand.")) else qdel(kiss_blower) to_chat(user, span_warning("You're incapable of blowing a kiss in your current state.")) + ink_action?.ResetCooldown() /datum/emote/living/laugh key = "laugh" @@ -733,6 +741,9 @@ if(!emote_is_valid(user, our_message)) return FALSE + if(type_override) + emote_type = type_override + if(!params) var/user_emote_type = get_custom_emote_type_from_user() diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index eef97d35655d7..6c6793ff99950 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -128,6 +128,7 @@ bare_wound_bonus = proj.bare_wound_bonus, sharpness = proj.sharpness, attack_direction = get_dir(proj.starting, src), + attacking_item = proj, ) apply_effects( @@ -290,7 +291,7 @@ return FALSE if(SEND_SIGNAL(src, COMSIG_LIVING_GRAB, target) & (COMPONENT_CANCEL_ATTACK_CHAIN|COMPONENT_SKIP_ATTACK)) return FALSE - if(target.check_block(src, 0, "[src]'s grab")) + if(target.check_block(src, 0, "[src]'s grab", UNARMED_ATTACK)) return FALSE target.grabbedby(src) return TRUE @@ -397,7 +398,7 @@ return FALSE var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) - if(check_block(user, damage, "[user]'s [user.attack_verb_simple]", MELEE_ATTACK/*or UNARMED_ATTACK?*/, user.armour_penetration, user.melee_damage_type)) + if(check_block(user, damage, "[user]'s [user.attack_verb_simple]", UNARMED_ATTACK, user.armour_penetration, user.melee_damage_type)) return FALSE if(user.attack_sound) @@ -512,7 +513,7 @@ /mob/living/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers) SEND_SIGNAL(src, COMSIG_MOB_ATTACK_ALIEN, user, modifiers) if(LAZYACCESS(modifiers, RIGHT_CLICK)) - if(check_block(user, 0, "[user]'s tackle", MELEE_ATTACK, 0, BRUTE)) + if(check_block(user, 0, "[user]'s tackle", UNARMED_ATTACK, 0, BRUTE)) return FALSE user.do_attack_animation(src, ATTACK_EFFECT_DISARM) return TRUE @@ -521,7 +522,7 @@ if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, span_warning("You don't want to hurt anyone!")) return FALSE - if(check_block(user, user.melee_damage_upper, "[user]'s slash", MELEE_ATTACK, 0, BRUTE)) + if(check_block(user, user.melee_damage_upper, "[user]'s slash", UNARMED_ATTACK, 0, BRUTE)) return FALSE user.do_attack_animation(src) return TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm index 7e2d8de24694a..07b8322bd7c48 100644 --- a/code/modules/mob/living/silicon/robot/robot_defines.dm +++ b/code/modules/mob/living/silicon/robot/robot_defines.dm @@ -16,6 +16,7 @@ has_limbs = TRUE hud_type = /datum/hud/robot unique_name = TRUE + mouse_drop_zone = TRUE ///Represents the cyborg's model (engineering, medical, etc.) var/obj/item/robot_model/model = null diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index f19a3991ca34f..743bdad8c4511 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -281,6 +281,9 @@ if (SEND_SIGNAL(src, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, movement_dir, continuous_move, backup) & COMPONENT_PREVENT_SPACEMOVE_HALT) return FALSE + if (drift_handler?.attempt_halt(movement_dir, continuous_move, backup)) + return FALSE + if(continuous_move || !istype(backup) || !movement_dir || backup.anchored) return TRUE @@ -300,8 +303,9 @@ /** * Finds a target near a mob that is viable for pushing off when moving. * Takes the intended movement direction as input, alongside if the context is checking if we're allowed to continue drifting + * If include_floors is TRUE, includes floors *with gravity* */ -/mob/get_spacemove_backup(moving_direction, continuous_move) +/mob/get_spacemove_backup(moving_direction, continuous_move, include_floors = FALSE) var/atom/secondary_backup var/list/priority_dirs = (moving_direction in GLOB.cardinals) ? GLOB.cardinals : GLOB.diagonals for(var/atom/pushover as anything in range(1, get_turf(src))) @@ -309,13 +313,15 @@ continue if(isarea(pushover)) continue + var/is_priority = pushover.loc == loc || (get_dir(src, pushover) in priority_dirs) if(isturf(pushover)) var/turf/turf = pushover if(isspaceturf(turf)) continue if(!turf.density && !mob_negates_gravity()) - continue - if (get_dir(src, pushover) in priority_dirs) + if (!include_floors || !turf.has_gravity()) + continue + if (is_priority) return pushover secondary_backup = pushover continue @@ -343,13 +349,13 @@ if(moving_direction == get_dir(src, pushover)) // Can't push "off" of something that you're walking into continue if(rebound.anchored) - if (get_dir(src, rebound) in priority_dirs) + if (is_priority) return rebound secondary_backup = rebound continue if(pulling == rebound) continue - if (get_dir(src, rebound) in priority_dirs) + if (is_priority) return rebound secondary_backup = rebound return secondary_backup diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm index 8d5c5c209540d..3b1f2a648bc76 100644 --- a/code/modules/mod/mod_control.dm +++ b/code/modules/mod/mod_control.dm @@ -771,3 +771,10 @@ mod_link.end_call() mod_link.frequency = null + +/obj/item/mod/control/proc/get_visor_overlay(mutable_appearance/standing) + var/list/overrides = list() + SEND_SIGNAL(src, COMSIG_MOD_GET_VISOR_OVERLAY, standing, overrides) + if (length(overrides)) + return overrides[1] + return mutable_appearance(worn_icon, "[skin]-helmet-visor", layer = standing.layer + 0.1) diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm index 3eef7d47f158b..ba5e1711fcb29 100644 --- a/code/modules/mod/modules/modules_antag.dm +++ b/code/modules/mod/modules/modules_antag.dm @@ -102,6 +102,17 @@ else REMOVE_TRAIT(mod.wearer, TRAIT_HEAD_INJURY_BLOCKED, REF(src)) +/obj/item/mod/module/armor_booster/on_install() + RegisterSignal(mod, COMSIG_MOD_GET_VISOR_OVERLAY, PROC_REF(on_visor_overlay)) + +/obj/item/mod/module/armor_booster/on_uninstall(deleting) + UnregisterSignal(mod, COMSIG_MOD_GET_VISOR_OVERLAY) + +/obj/item/mod/module/armor_booster/proc/on_visor_overlay(datum/source, mutable_appearance/standing, list/overrides) + SIGNAL_HANDLER + if (active) + overrides += mutable_appearance(overlay_icon_file, "module_armorbooster_visor-[mod.skin]", layer = standing.layer + 0.1) + ///Energy Shield - Gives you a rechargeable energy shield that nullifies attacks. /obj/item/mod/module/energy_shield name = "MOD energy shield module" diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 3f4dfe405f738..a766e3f6e4da2 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -664,13 +664,15 @@ complexity = 1 idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3 incompatible_modules = list(/obj/item/mod/module/plasma_stabilizer) - overlay_state_inactive = "module_plasma" required_slots = list(ITEM_SLOT_HEAD) -/obj/item/mod/module/plasma_stabilizer/generate_worn_overlay() - if(locate(/obj/item/mod/module/infiltrator) in mod.modules) +/obj/item/mod/module/plasma_stabilizer/generate_worn_overlay(mutable_appearance/standing) + if (!active) return list() - return ..() + var/mutable_appearance/visor_overlay = mod.get_visor_overlay(standing) + visor_overlay.appearance_flags |= RESET_COLOR + visor_overlay.color = COLOR_VOID_PURPLE + return list(visor_overlay) /obj/item/mod/module/plasma_stabilizer/on_equip() ADD_TRAIT(mod.wearer, TRAIT_HEAD_ATMOS_SEALED, REF(src)) diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm index 48089b0125c9a..7344993802cbc 100644 --- a/code/modules/mod/modules/modules_maint.dm +++ b/code/modules/mod/modules/modules_maint.dm @@ -87,7 +87,6 @@ desc = "A Super Cool Awesome Visor (SCAV), intended for modular suits." icon_state = "rave_visor" complexity = 1 - overlay_state_inactive = "module_rave" required_slots = list(ITEM_SLOT_HEAD|ITEM_SLOT_MASK) /// The client colors applied to the wearer. var/datum/client_colour/rave_screen @@ -132,9 +131,13 @@ SEND_SOUND(mod.wearer, sound('sound/machines/terminal/terminal_off.ogg', volume = 50, channel = CHANNEL_JUKEBOX)) /obj/item/mod/module/visor/rave/generate_worn_overlay(mutable_appearance/standing) - . = ..() - for(var/mutable_appearance/appearance as anything in .) - appearance.color = isnull(music_player.active_song_sound) ? null : rainbow_order[rave_number] + if (!active) + return list() + var/mutable_appearance/visor_overlay = mod.get_visor_overlay(standing) + visor_overlay.appearance_flags |= RESET_COLOR + if (!isnull(music_player.active_song_sound)) + visor_overlay.color = rainbow_order[rave_number] + return list(visor_overlay) /obj/item/mod/module/visor/rave/on_active_process(seconds_per_tick) rave_number++ diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index 5053b6c6b2cbe..343a09a980d30 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -29,6 +29,16 @@ if(screen_on) . += span_notice("Alt-click to close it.") +/obj/item/modular_computer/laptop/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + . = ..() + if(screen_on) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Close" + context[SCREENTIP_CONTEXT_RMB] = "Interact" + else + context[SCREENTIP_CONTEXT_RMB] = "Open" + + return CONTEXTUAL_SCREENTIP_SET + /obj/item/modular_computer/laptop/Initialize(mapload) . = ..() @@ -70,13 +80,6 @@ return user.put_in_hand(src, H.held_index) -/obj/item/modular_computer/laptop/attack_hand(mob/user, list/modifiers) - . = ..() - if(.) - return - if(screen_on && isturf(loc)) - return attack_self(user) - /obj/item/modular_computer/laptop/proc/try_toggle_open(mob/living/user) if(issilicon(user)) return @@ -94,6 +97,14 @@ try_toggle_open(user) // Close it. return CLICK_ACTION_SUCCESS +/obj/item/modular_computer/laptop/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + attack_self(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + /obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null) if(screen_on) to_chat(user, span_notice("You close \the [src].")) diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm index f2935853a6db5..62a269d477e46 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -1,4 +1,5 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department", "NT Complaint Department", "NT Customer Relations", "Nanotrasen Tech Support", "NT Internal Affairs Dept")) +GLOBAL_VAR_INIT(fax_autoprinting, FALSE) /obj/machinery/fax name = "Fax Machine" @@ -325,7 +326,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department history_add("Send", params["name"]) - GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", fax_paper) + GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", list("paper" = fax_paper, "destination_id" = params["id"], "sender_name" = fax_name)) to_chat(GLOB.admins, span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"), type = MESSAGE_TYPE_PRAYER, @@ -333,6 +334,14 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department for(var/client/staff as anything in GLOB.admins) if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification)) SEND_SOUND(staff, sound('sound/misc/server-ready.ogg')) + + if(GLOB.fax_autoprinting) + for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin)) + if(FAX.fax_id != params["id"]) + continue + FAX.receive(fax_paper, fax_name) + break + log_fax(fax_paper, params["id"], params["name"]) loaded_item_ref = null update_appearance() diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 60c6aeb4dfef5..ff113e2b2cd59 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -11,12 +11,13 @@ * Pens */ /obj/item/pen - desc = "It's a normal black ink pen." name = "pen" + desc = "It's a normal black ink pen." icon = 'icons/obj/service/bureaucracy.dmi' icon_state = "pen" inhand_icon_state = "pen" worn_icon_state = "pen" + icon_angle = -135 slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS throwforce = 0 w_class = WEIGHT_CLASS_TINY @@ -304,8 +305,8 @@ * (Alan) Edaggers */ /obj/item/pen/edagger - attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") //these won't show up if the pen is off - attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") //these won't show up if the pen is off + attack_verb_simple = list("slash", "slice", "tear", "lacerate", "rip", "dice", "cut") sharpness = SHARP_POINTY armour_penetration = 20 bare_wound_bonus = 10 @@ -322,9 +323,14 @@ var/hidden_desc = "It's a normal black ink pe- Wait. That's a thing used to stab people!" /// The real icons used when extended. var/hidden_icon = "edagger" + var/list/alt_continuous = list("stabs", "pierces", "shanks") + var/list/alt_simple = list("stab", "pierce", "shank") /obj/item/pen/edagger/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5, TRAIT_TRANSFORM_ACTIVE) AddComponent(/datum/component/butchering, \ speed = 6 SECONDS, \ butcher_sound = 'sound/items/weapons/blade1.ogg', \ diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm index 33c063bbfed20..f16091cc21b26 100644 --- a/code/modules/plumbing/plumbers/_plumb_machinery.dm +++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm @@ -102,117 +102,3 @@ user.balloon_alert_to_viewers("finished plunging") reagents.expose(get_turf(src), TOUCH) //splash on the floor reagents.clear_reagents() - -/** - * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents - * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors - */ -/datum/reagents/plumbing - -/** - * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded. - * Arguments - * - * * atom/target - the target we are transfering to - * * amount - amount to transfer - * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered - * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the - * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them) - * NONE means everything is transfered regardless of how much space is available in the receiver in proportions - */ -/datum/reagents/plumbing/trans_to( - atom/target, - amount = 1, - multiplier = 1, //unused for plumbing - datum/reagent/target_id, - preserve_data = TRUE, //unused for plumbing - no_react = FALSE, //unused for plumbing we always want reactions - mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines - remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us - methods = LINEAR, //default round robin technique for transferring reagents - show_message = TRUE, //unused for plumbing, used for logging only - ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time -) - if(QDELETED(target) || !total_volume) - return FALSE - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to trans_to [amount] amount of reagents") - return FALSE - - if(!isnull(target_id) && !ispath(target_id)) - stack_trace("invalid target reagent id [target_id] passed to trans_to") - return FALSE - - var/datum/reagents/target_holder - if(istype(target, /datum/reagents)) - target_holder = target - else - target_holder = target.reagents - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return FALSE - - //Set up new reagents to inherit the old ongoing reactions - transfer_reactions(target_holder) - - var/list/cached_reagents = reagent_list - var/list/reagents_to_remove = list() - var/transfer_amount - var/transfered_amount - var/total_transfered_amount = 0 - - var/round_robin = methods & LINEAR - var/part - var/to_transfer - if(round_robin) - to_transfer = amount - else - part = amount / total_volume - - //first add reagents to target - for(var/datum/reagent/reagent as anything in cached_reagents) - if(round_robin && !to_transfer) - break - - if(!isnull(target_id)) - if(reagent.type == target_id) - force_stop_reagent_reacting(reagent) - transfer_amount = min(amount, reagent.volume) - else - continue - else - if(round_robin) - transfer_amount = min(to_transfer, reagent.volume) - else - transfer_amount = reagent.volume * part - - if(reagent.intercept_reagents_transfer(target_holder, amount)) - update_total() - target_holder.update_total() - continue - - transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. - if(!transfered_amount) - continue - reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) - total_transfered_amount += transfered_amount - if(round_robin) - to_transfer -= transfered_amount - - if(!isnull(target_id)) - break - - //remove chemicals that were added above - for(var/list/data as anything in reagents_to_remove) - var/datum/reagent/reagent = data["R"] - transfer_amount = data["T"] - remove_reagent(reagent.type, transfer_amount) - - //handle reactions - target_holder.handle_reactions() - src.handle_reactions() - - return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) diff --git a/code/modules/plumbing/plumbers/_plumb_reagents.dm b/code/modules/plumbing/plumbers/_plumb_reagents.dm new file mode 100644 index 0000000000000..4cce127c8b822 --- /dev/null +++ b/code/modules/plumbing/plumbers/_plumb_reagents.dm @@ -0,0 +1,261 @@ + +/** + * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents + * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors + */ +/datum/reagents/plumbing + +/** + * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded. + * Arguments + * + * * atom/target - the target we are transfering to + * * amount - amount to transfer + * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered + * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the + * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them) + * NONE means everything is transfered regardless of how much space is available in the receiver in proportions + */ +/datum/reagents/plumbing/trans_to( + atom/target, + amount = 1, + multiplier = 1, //unused for plumbing + datum/reagent/target_id, + preserve_data = TRUE, //unused for plumbing + no_react = FALSE, //unused for plumbing we always want reactions + mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines + remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us + methods = LINEAR, //default round robin technique for transferring reagents + show_message = TRUE, //unused for plumbing, used for logging only + ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time +) + if(QDELETED(target) || !total_volume) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + if(!isnull(target_id) && !ispath(target_id)) + stack_trace("invalid target reagent id [target_id] passed to trans_to") + return FALSE + + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + else + target_holder = target.reagents + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + //Set up new reagents to inherit the old ongoing reactions + transfer_reactions(target_holder) + + var/list/cached_reagents = reagent_list + var/list/reagents_to_remove = list() + var/transfer_amount + var/transfered_amount + var/total_transfered_amount = 0 + + var/round_robin = methods & LINEAR + var/part + var/to_transfer + if(round_robin) + to_transfer = amount + else + part = amount / total_volume + + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(round_robin && !to_transfer) + break + + if(!isnull(target_id)) + if(reagent.type == target_id) + force_stop_reagent_reacting(reagent) + transfer_amount = min(amount, reagent.volume) + else + continue + else + if(round_robin) + transfer_amount = min(to_transfer, reagent.volume) + else + transfer_amount = reagent.volume * part + + if(reagent.intercept_reagents_transfer(target_holder, amount)) + update_total() + target_holder.update_total() + continue + + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. + if(!transfered_amount) + continue + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + total_transfered_amount += transfered_amount + if(round_robin) + to_transfer -= transfered_amount + + if(!isnull(target_id)) + break + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + remove_reagent(reagent.type, transfer_amount) + + //handle reactions + target_holder.handle_reactions() + src.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) + +///Excludes catalysts during the emptying process +/datum/reagents/plumbing/reaction_chamber + +///Returns the total volume of reagents without the catalysts +/datum/reagents/plumbing/reaction_chamber/proc/get_catalyst_excluded_volume() + SHOULD_NOT_OVERRIDE(TRUE) + + . = 0 + if(!total_volume) + return + + var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom + var/list/datum/reagent/catalysts = reactor.catalysts + + var/working_volume + var/catalyst_volume + var/list/cached_reagents = reagent_list + for(var/datum/reagent/reagent as anything in cached_reagents) + catalyst_volume = catalysts[reagent.type] + working_volume = reagent.volume + + //regular reagent add to total as normal + if(!catalyst_volume) + . += working_volume + continue + + //only add the excess to total as that's what will get transferred + if(working_volume > catalyst_volume) + . += working_volume - catalyst_volume + . = min(round(., CHEMICAL_VOLUME_ROUNDING), maximum_volume) + +/datum/reagents/plumbing/reaction_chamber/trans_to( + atom/target, + amount = 1, + multiplier = 1, + datum/reagent/target_id, + preserve_data = TRUE, + no_react = FALSE, + mob/transferred_by, + remove_blacklisted = FALSE, + methods = LINEAR, + show_message = TRUE, + ignore_stomach = FALSE +) + var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom + var/list/datum/reagent/catalysts = reactor.catalysts + + //usual stuff + if(!catalysts.len) + return ..() + + if(QDELETED(target)) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + if(!isnull(target_id) && !ispath(target_id)) + stack_trace("invalid target reagent id [target_id] passed to trans_to") + return FALSE + + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + else + target_holder = target.reagents + var/list/cached_reagents = reagent_list + + var/actual_volume = get_catalyst_excluded_volume() + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, actual_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + //Set up new reagents to inherit the old ongoing reactions + transfer_reactions(target_holder) + + var/list/reagents_to_remove = list() + var/working_volume + var/catalyst_volume + var/transfer_amount + var/transfered_amount + var/total_transfered_amount = 0 + + var/round_robin = methods & LINEAR + var/part + var/to_transfer + if(round_robin) + to_transfer = amount + else + part = amount / actual_volume + + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(round_robin && !to_transfer) + break + working_volume = reagent.volume + + catalyst_volume = catalysts[reagent.type] + if(catalyst_volume) //we have a working catalyst + if(reagent.volume <= catalyst_volume) //dont transfer since we have the required volume + continue + else + working_volume -= catalyst_volume //dump out the excess + + if(!isnull(target_id)) + if(reagent.type == target_id) + force_stop_reagent_reacting(reagent) + transfer_amount = min(amount, working_volume) + else + continue + else + if(round_robin) + transfer_amount = min(to_transfer, working_volume) + else + transfer_amount = working_volume * part + + if(reagent.intercept_reagents_transfer(target_holder, amount)) + update_total() + target_holder.update_total() + continue + + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. + if(!transfered_amount) + continue + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + total_transfered_amount += transfered_amount + if(round_robin) + to_transfer -= transfered_amount + + if(!isnull(target_id)) + break + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + remove_reagent(reagent.type, transfer_amount) + + //handle reactions + target_holder.handle_reactions() + src.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm index 9828c9e697f85..551b95fc0b7d7 100644 --- a/code/modules/plumbing/plumbers/reaction_chamber.dm +++ b/code/modules/plumbing/plumbers/reaction_chamber.dm @@ -9,19 +9,23 @@ icon_state = "reaction_chamber" buffer = 200 reagent_flags = TRANSPARENT | NO_REACT + reagents = /datum/reagents/plumbing/reaction_chamber /** * list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled. * example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50) */ - var/list/required_reagents = list() - + var/list/datum/reagent/required_reagents = list() + ///list of catalyst reagents to take + var/list/datum/reagent/catalysts = list() ///our reagent goal has been reached, so now we lock our inputs and start emptying var/emptying = FALSE - ///towards which temperature do we build (except during draining)? var/target_temperature = 300 +/obj/machinery/plumbing/reaction_chamber/Destroy() + return ..() + /obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt, layer) . = ..() AddComponent(/datum/component/plumbing/reaction_chamber, bolt, layer) @@ -36,15 +40,17 @@ SIGNAL_HANDLER UnregisterSignal(reagents, list(COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, COMSIG_REAGENTS_REACTED, COMSIG_QDELETING)) + return NONE /// Handles stopping the emptying process when the chamber empties. -/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/holder, ...) +/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/plumbing/reaction_chamber/holder, ...) SIGNAL_HANDLER - if(!holder.total_volume && emptying) //we were emptying, but now we aren't + if(!holder.get_catalyst_excluded_volume() && emptying) //we were emptying, but now we aren't emptying = FALSE holder.flags |= NO_REACT + return NONE /obj/machinery/plumbing/reaction_chamber/process(seconds_per_tick) @@ -60,8 +66,15 @@ //do other stuff with final solution handle_reagents(seconds_per_tick) -///For subtypes that want to do additional reagent handling +/** + * For subtypes that want to do additional reagent handling + * Arguments + * + * * seconds_per_tick - passed down from process() + */ /obj/machinery/plumbing/reaction_chamber/proc/handle_reagents(seconds_per_tick) + PROTECTED_PROC(TRUE) + return /obj/machinery/plumbing/reaction_chamber/power_change() @@ -81,11 +94,21 @@ var/list/reagents_data = list() for(var/datum/reagent/required_reagent as anything in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath var/list/reagent_data = list() + if(catalysts[required_reagent]) + continue reagent_data["name"] = initial(required_reagent.name) reagent_data["volume"] = required_reagents[required_reagent] reagents_data += list(reagent_data) + var/list/catalyst_data = list() + for(var/datum/reagent/required_catalyst as anything in catalysts) + var/list/reagent_data = list() + reagent_data["name"] = initial(required_catalyst.name) + reagent_data["volume"] = catalysts[required_catalyst] + catalyst_data += list(reagent_data) + .["reagents"] = reagents_data + .["catalysts"] = catalyst_data .["emptying"] = emptying .["temperature"] = round(reagents.chem_temp, 0.1) .["targetTemp"] = target_temperature @@ -108,9 +131,9 @@ if(!input_reagent) return FALSE - if(!required_reagents.Find(input_reagent)) + if(!required_reagents[input_reagent]) var/input_amount = text2num(params["amount"]) - if(!isnull(input_amount)) + if(input_amount) required_reagents[input_reagent] = input_amount return TRUE return FALSE @@ -129,6 +152,25 @@ return TRUE return FALSE + if("catalyst") + var/reagent = get_chem_id(params["chem"]) + + if(!reagent) + return FALSE + + if(reagent && !catalysts[reagent]) + catalysts[reagent] = required_reagents[reagent] + return TRUE + else + return FALSE + + if("catremove") + var/reagent = get_chem_id(params["chem"]) + if(reagent) + catalysts -= reagent + return TRUE + return FALSE + var/result = handle_ui_act(action, params, ui, state) if(isnull(result)) result = FALSE @@ -136,6 +178,8 @@ /// For custom handling of ui actions from inside a subtype /obj/machinery/plumbing/reaction_chamber/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + PROTECTED_PROC(TRUE) + return null ///Chemistry version of reaction chamber that allows for acid and base buffers to be used while reacting diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index 5b9d983cf1dd6..7db89987d2d44 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -173,6 +173,8 @@ var/light_color = NONSENSICAL_VALUE if(!isnull(color)) light_color = color + if (cached_color_filter) + light_color = apply_matrix_to_color(COLOR_WHITE, cached_color_filter["color"], cached_color_filter["space"] || COLORSPACE_RGB) set_light(light_setting_list[setting], light_power, light_color) /obj/machinery/power/floodlight/add_context( diff --git a/code/modules/power/lighting/light_items.dm b/code/modules/power/lighting/light_items.dm index 357507d0aa4f9..a5b40e0534ff9 100644 --- a/code/modules/power/lighting/light_items.dm +++ b/code/modules/power/lighting/light_items.dm @@ -55,6 +55,7 @@ icon_state = "ltube" base_state = "ltube" inhand_icon_state = "ltube" + icon_angle = -45 brightness = 8 custom_price = PAYCHECK_CREW * 0.5 @@ -75,6 +76,7 @@ desc = "A replacement light bulb." icon_state = "lbulb" base_state = "lbulb" + icon_angle = -90 inhand_icon_state = "contvapour" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index e839800158f63..2112002e50745 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -1,11 +1,18 @@ +///Minimum pressure of gases pumped through the turbine #define MINIMUM_TURBINE_PRESSURE 0.01 +///Returns the minimum pressure if it falls below the value #define PRESSURE_MAX(value)(max((value), MINIMUM_TURBINE_PRESSURE)) +///Use emissive for overlays +#define EMISSIVE_OVERLAY (1 << 0) +///No turned off overlay +#define NO_INACTIVE_OVERLAY (1 << 1) /obj/machinery/power/turbine + icon = 'icons/obj/machines/engine/turbine.dmi' density = TRUE resistance_flags = FIRE_PROOF can_atmos_pass = ATMOS_PASS_DENSITY - processing_flags = NONE + processing_flags = START_PROCESSING_MANUALLY ///Checks if the machine is processing or not var/active = FALSE @@ -17,15 +24,8 @@ var/obj/item/turbine_parts/part_path ///The gas mixture this turbine part is storing var/datum/gas_mixture/machine_gasmix - - ///Our overlay when active - var/active_overlay = "" - ///Our overlay when off - var/off_overlay = "" - ///Our overlay when open - var/open_overlay = "" - ///Should we use emissive appearance? - var/emissive = FALSE + ///Flags for our overlays + var/overlay_flags = NONE /obj/machinery/power/turbine/Initialize(mapload, gas_theoretical_volume) . = ..() @@ -59,41 +59,6 @@ deactivate_parts() return ..() -/** - * Handles all the calculations needed for the gases, work done, temperature increase/decrease - * - * Arguments - * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine - * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix. - * ideally should be same as input mix but varying texmperatur & pressures can cause varying results - * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture. - * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done - * by the inlet from the rotor to get the true work done - * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only - */ -/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1) - //pump gases. if no gases were transferred then no work was done - var/output_pressure = PRESSURE_MAX(output_mix.return_pressure()) - var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size) - if(!transferred_gases) - return 0 - - //compute work done - var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER - if(work_amount_to_remove) - work_done = work_done - work_amount_to_remove - - //compute temperature & work from temperature if that is a lower value - var/output_mix_heat_capacity = output_mix.heat_capacity() - if(!output_mix_heat_capacity) - return 0 - work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER) - output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB) - return work_done - -/obj/machinery/power/turbine/block_superconductivity() - return TRUE - /obj/machinery/power/turbine/add_context(atom/source, list/context, obj/item/held_item, mob/user) if(isnull(held_item)) return NONE @@ -128,7 +93,7 @@ . = ..() if(installed_part) . += span_notice("Currently at tier [installed_part.current_tier].") - if(installed_part.current_tier + 1 < installed_part.max_tier) + if(installed_part.current_tier + 1 < TURBINE_PART_TIER_FOUR) . += span_notice("Can be upgraded by using a tier [installed_part.current_tier + 1] part.") . += span_notice("The [installed_part.name] can be [EXAMINE_HINT("pried")] out.") else @@ -140,15 +105,54 @@ /obj/machinery/power/turbine/update_overlays() . = ..() + if(panel_open) - . += open_overlay + . += "[base_icon_state]_open" if(active) - . += active_overlay - if(emissive) - . += emissive_appearance(icon, active_overlay, src) - else - . += off_overlay + . += "[base_icon_state]_on" + if(overlay_flags & EMISSIVE_OVERLAY) + . += emissive_appearance(icon, "[base_icon_state]_on", src) + else if(!(overlay_flags & NO_INACTIVE_OVERLAY)) + . += "[base_icon_state]_off" + + +/** + * Handles all the calculations needed for the gases, work done, temperature increase/decrease + * + * Arguments + * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine + * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix. + * ideally should be same as input mix but varying texmperatur & pressures can cause varying results + * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture. + * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done + * by the inlet from the rotor to get the true work done + * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only + */ +/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1) + PROTECTED_PROC(TRUE) + + //pump gases. if no gases were transferred then no work was done + var/output_pressure = PRESSURE_MAX(output_mix.return_pressure()) + var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size) + if(!transferred_gases) + return 0 + + //compute work done + var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER + if(work_amount_to_remove) + work_done = work_done - work_amount_to_remove + + //compute temperature & work from temperature if that is a lower value + var/output_mix_heat_capacity = output_mix.heat_capacity() + if(!output_mix_heat_capacity) + return 0 + work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER) + output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB) + return work_done + +/obj/machinery/power/turbine/block_superconductivity() + return TRUE /obj/machinery/power/turbine/screwdriver_act(mob/living/user, obj/item/tool) . = ITEM_INTERACT_BLOCKING @@ -165,7 +169,7 @@ deactivate_parts(user) else activate_parts(user) - update_appearance() + update_appearance(UPDATE_OVERLAYS) return ITEM_INTERACT_SUCCESS @@ -229,22 +233,22 @@ if(gone == installed_part) installed_part = null -/obj/machinery/power/turbine/attackby(obj/item/turbine_parts/object, mob/user, params) - //not the correct part +/obj/machinery/power/turbine/item_interaction(mob/living/user, obj/item/turbine_parts/object, list/modifiers) + . = NONE if(!istype(object, part_path)) return ..() //not in a state to accep the part. return TRUE so we don't bash the machine and damage it if(active) balloon_alert(user, "turn off the machine first!") - return TRUE + return ITEM_INTERACT_BLOCKING if(!panel_open) balloon_alert(user, "open the maintenance hatch first!") - return TRUE + return ITEM_INTERACT_BLOCKING //install the part if(!do_after(user, 2 SECONDS, src)) - return TRUE + return ITEM_INTERACT_BLOCKING if(installed_part) user.put_in_hands(installed_part) balloon_alert(user, "replaced part with the one in hand") @@ -252,22 +256,21 @@ balloon_alert(user, "installed new part") user.transferItemToLoc(object, src) installed_part = object - return TRUE + return ITEM_INTERACT_SUCCESS /// Gets the efficiency of the installed part, returns 0 if no part is installed /obj/machinery/power/turbine/proc/get_efficiency() + SHOULD_BE_PURE(TRUE) + return installed_part?.part_efficiency || 0 /obj/machinery/power/turbine/inlet_compressor name = "inlet compressor" desc = "The input side of a turbine generator, contains the compressor." - icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "inlet_compressor" + base_icon_state = "inlet" circuit = /obj/item/circuitboard/machine/turbine_compressor part_path = /obj/item/turbine_parts/compressor - active_overlay = "inlet_animation" - off_overlay = "inlet_off" - open_overlay = "inlet_open" /// The rotor this inlet is linked to var/obj/machinery/power/turbine/core_rotor/rotor @@ -296,6 +299,8 @@ * Returns temperature of the gas mix absorbed only if some work was done */ /obj/machinery/power/turbine/inlet_compressor/proc/compress_gases() + SHOULD_NOT_OVERRIDE(TRUE) + compressor_work = 0 compressor_pressure = MINIMUM_TURBINE_PRESSURE if(QDELETED(input_turf)) @@ -314,16 +319,14 @@ return input_turf_mixture.temperature +//===========================OUTLET============================================== /obj/machinery/power/turbine/turbine_outlet name = "turbine outlet" desc = "The output side of a turbine generator, contains the turbine and the stator." - icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "turbine_outlet" + base_icon_state = "outlet" circuit = /obj/item/circuitboard/machine/turbine_stator part_path = /obj/item/turbine_parts/stator - active_overlay = "outlet_animation" - off_overlay = "outlet_off" - open_overlay = "outlet_open" /// The rotor this outlet is linked to var/obj/machinery/power/turbine/core_rotor/rotor @@ -343,6 +346,8 @@ /// push gases from its gas mix to output turf /obj/machinery/power/turbine/turbine_outlet/proc/expel_gases() + SHOULD_NOT_OVERRIDE(TRUE) + if(QDELETED(output_turf)) output_turf = get_step(loc, dir) //turf is blocked don't eject gases @@ -358,18 +363,16 @@ //return ejected gases return ejected_gases +//===========================================CORE ROTOR========================================= /obj/machinery/power/turbine/core_rotor name = "core rotor" desc = "The middle part of a turbine generator, contains the rotor and the main computer." - icon = 'icons/obj/machines/engine/turbine.dmi' icon_state = "core_rotor" - active_overlay = "core_light" - open_overlay = "core_open" - active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION - emissive = TRUE + base_icon_state = "core" can_change_cable_layer = TRUE circuit = /obj/item/circuitboard/machine/turbine_rotor part_path = /obj/item/turbine_parts/rotor + overlay_flags = EMISSIVE_OVERLAY | NO_INACTIVE_OVERLAY ///ID to easily connect the main part of the turbine to the computer var/mapping_id @@ -378,9 +381,9 @@ ///Reference to the turbine var/obj/machinery/power/turbine/turbine_outlet/turbine ///Rotation per minute the machine is doing - var/rpm + var/rpm = 0 ///Amount of power the machine is producing - var/produced_energy + var/produced_energy = 0 ///Check to see if all parts are connected to the core var/all_parts_connected = FALSE ///Max rmp that the installed parts can handle, limits the rpms @@ -462,16 +465,21 @@ //works same as regular left click return multitool_act(user, tool) -/// convinience proc for balloon alert which returns if viewer is null +/** + * convinience proc for balloon alert which returns if viewer is null + * Arguments + * + * * mob/viewer - the player receiving the message + * * text - the message + */ /obj/machinery/power/turbine/core_rotor/proc/feedback(mob/viewer, text) + PRIVATE_PROC(TRUE) + if(isnull(viewer)) return balloon_alert(viewer, text) -/** - * Called to activate the complete machine, checks for part presence, correct orientation and installed parts - * Registers the input/output turfs - */ +///Called to activate the complete machine, checks for part presence, correct orientation and installed parts /obj/machinery/power/turbine/core_rotor/activate_parts(mob/user, check_only = FALSE) //if this is not a checkup and all parts are connected then we have nothing to do if(!check_only && all_parts_connected) @@ -479,13 +487,18 @@ //locate compressor & turbine, when checking we simply check to see if they are still there if(!check_only) - compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, REVERSE_DIR(dir)) - turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, dir) + compressor = locate() in get_step(src, REVERSE_DIR(dir)) + turbine = locate() in get_step(src, dir) - //maybe look for them the other way around. we want the rotor to allign with them either way for player convinience - if(!compressor && !turbine) - compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, dir) - turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, REVERSE_DIR(dir)) + //maybe look for them the other way around. this means the rotor is facing the wrong way + if(QDELETED(compressor) && QDELETED(turbine)) + compressor = locate() in get_step(src, dir) + turbine = locate() in get_step(src, REVERSE_DIR(dir)) + + //show corrective actions + if(!QDELETED(compressor) || !QDELETED(turbine)) + feedback(user, "rotor is facing the wrong way!") + return (all_parts_connected = FALSE) //sanity checks for compressor if(QDELETED(compressor)) @@ -505,17 +518,17 @@ if(QDELETED(turbine)) feedback(user, "missing turbine!") return (all_parts_connected = FALSE) - if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir)) + if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir)) //make sure it's not perpendicular to the rotor feedback(user, "turbine not aligned with rotor!") return (all_parts_connected = FALSE) if(!turbine.can_connect) - feedback(user, "turbine panel is either open or is misplaced!") //we say misplaced because can_connect becomes FALSE when this turbine is moved + feedback(user, "close turbine panel!") //we say misplaced because can_connect becomes FALSE when this turbine is moved return (all_parts_connected = FALSE) if(!turbine.installed_part) feedback(user, "turbine is missing stator part!") return (all_parts_connected = FALSE) - //final sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's + //sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's if(compressor.dir != turbine.dir) feedback(user, "turbine & compressor are not facing away from each other!") return (all_parts_connected = FALSE) @@ -537,85 +550,60 @@ * Allows to null the various machines and references from the main core */ /obj/machinery/power/turbine/core_rotor/deactivate_parts() - if(all_parts_connected) - power_off() + toggle_power(force_off = TRUE) compressor?.rotor = null compressor = null turbine?.rotor = null turbine = null all_parts_connected = FALSE disconnect_from_network() - SSair.stop_processing_machine(src) /obj/machinery/power/turbine/core_rotor/on_deconstruction(disassembled) deactivate_parts() return ..() /// Toggle power on and off, not safe -/obj/machinery/power/turbine/core_rotor/proc/toggle_power() - if(active) - power_off() - return - power_on() - -/** - * Activate all three parts, not safe, it assumes the machine already connected and properly working - * It does a minimun check to ensure the parts still exist - */ -/obj/machinery/power/turbine/core_rotor/proc/power_on() - if(active || QDELETED(compressor) || QDELETED(turbine)) - return - active = TRUE - compressor.active = TRUE - turbine.active = TRUE - call_parts_update_appearance() - SSair.start_processing_machine(src) - -/// Calls all parts update appearance proc. -/obj/machinery/power/turbine/core_rotor/proc/call_parts_update_appearance() - update_appearance() - if(!QDELETED(compressor)) - compressor.update_appearance() - if(!QDELETED(turbine)) - turbine.update_appearance() +/obj/machinery/power/turbine/core_rotor/proc/toggle_power(force_off) + SHOULD_NOT_OVERRIDE(TRUE) + + //toggle status + if(force_off) + if(!active) //was already off + return + active = FALSE + else + active = !active -/** - * Deactivate all three parts, not safe, it assumes the machine already connected and properly working - * will try to turn off whatever components are left of this machine - */ -/obj/machinery/power/turbine/core_rotor/proc/power_off() - if(!active) - return - active = FALSE + //update operation status of parts + update_appearance(UPDATE_OVERLAYS) if(!QDELETED(compressor)) - compressor.active = FALSE + compressor.active = active + compressor.update_appearance(UPDATE_OVERLAYS) if(!QDELETED(turbine)) - turbine.active = FALSE - call_parts_update_appearance() - SSair.stop_processing_machine(src) + turbine.active = active + turbine.update_appearance(UPDATE_OVERLAYS) -/// Returns true if all parts have their panel closed -/obj/machinery/power/turbine/core_rotor/proc/all_parts_ready() - if(QDELETED(compressor)) - return FALSE - if(QDELETED(turbine)) - return FALSE - return !panel_open && !compressor.panel_open && !turbine.panel_open + //start or stop processing + if(active) + update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage) + begin_processing() + else + unset_static_power() + end_processing() /// Getter for turbine integrity, return the amount in % /obj/machinery/power/turbine/core_rotor/proc/get_turbine_integrity() + SHOULD_NOT_OVERRIDE(TRUE) + var/integrity = damage / 500 integrity = max(round(100 - integrity * 100, 0.01), 0) return integrity -/obj/machinery/power/turbine/core_rotor/process_atmos() +/obj/machinery/power/turbine/core_rotor/process(seconds_per_tick) if(!active || !activate_parts(check_only = TRUE) || (machine_stat & BROKEN) || !powered(ignore_use_power = TRUE)) - power_off() + deactivate_parts() return PROCESS_KILL - //use power to operate internal electronics & stuff - update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage) - //===============COMPRESSOR WORKING========// //Transfer gases from turf to compressor var/temperature = compressor.compress_gases() @@ -668,9 +656,9 @@ work_done = max(work_done - compressor.compressor_work * TURBINE_COMPRESSOR_STATOR_INTERACTION_MULTIPLIER - turbine_work, 0) //calculate final acheived rpm rpm = ((work_done * compressor.get_efficiency()) ** turbine.get_efficiency()) * get_efficiency() / TURBINE_RPM_CONVERSION - rpm = FLOOR(min(rpm, max_allowed_rpm), 1) + rpm = min(ROUND_UP(rpm), max_allowed_rpm) //add energy into the grid, also use part of it for turbine operation - produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION + produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION * seconds_per_tick add_avail(produced_energy) /obj/item/paper/guides/jobs/atmos/turbine @@ -686,3 +674,5 @@ #undef PRESSURE_MAX #undef MINIMUM_TURBINE_PRESSURE +#undef EMISSIVE_OVERLAY +#undef NO_INACTIVE_OVERLAY diff --git a/code/modules/power/turbine/turbine_computer.dm b/code/modules/power/turbine/turbine_computer.dm index 36a556daefb42..7771bda03f8df 100644 --- a/code/modules/power/turbine/turbine_computer.dm +++ b/code/modules/power/turbine/turbine_computer.dm @@ -20,8 +20,8 @@ register_machine(main) break -/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/tool) - var/obj/item/multitool/multitool = tool +/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/multitool/multitool) + . = ITEM_INTERACT_FAILURE if(!istype(multitool.buffer, /obj/machinery/power/turbine/core_rotor)) to_chat(user, span_notice("Wrong machine type in [multitool] buffer...")) return @@ -29,12 +29,21 @@ to_chat(user, span_notice("Changing [src] bluespace network...")) if(!do_after(user, 0.2 SECONDS, src)) return + playsound(get_turf(user), 'sound/machines/click.ogg', 10, TRUE) register_machine(multitool.buffer) to_chat(user, span_notice("You link [src] to the console in [multitool]'s buffer.")) - return TRUE + return ITEM_INTERACT_SUCCESS + +/** + * Links the rotor with this computer + * Arguments + * + * * obj/machinery/power/turbine/core_rotor/machine - the machine to link + */ +/obj/machinery/computer/turbine_computer/proc/register_machine(obj/machinery/power/turbine/core_rotor/machine) + PRIVATE_PROC(TRUE) -/obj/machinery/computer/turbine_computer/proc/register_machine(machine) turbine_core = WEAKREF(machine) /obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui) @@ -45,26 +54,27 @@ ui.open() /obj/machinery/computer/turbine_computer/ui_data(mob/user) - var/list/data = list() + . = list() + //do we have the main rotor with all parts connected var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve() - data["connected"] = !!QDELETED(main_control) - if(!main_control) + if(QDELETED(main_control) || !main_control.all_parts_connected) + .["connected"] = FALSE return + else + .["connected"] = TRUE - data["active"] = main_control.active - data["rpm"] = main_control.rpm ? main_control.rpm : 0 - data["power"] = main_control.produced_energy ? main_control.produced_energy : 0 - data["integrity"] = main_control.get_turbine_integrity() - data["parts_linked"] = main_control.all_parts_connected - data["parts_ready"] = main_control.all_parts_ready() - - data["max_rpm"] = main_control.max_allowed_rpm - data["max_temperature"] = main_control.max_allowed_temperature - data["temp"] = main_control.compressor?.input_turf?.air.temperature || 0 - data["regulator"] = QDELETED(main_control.compressor) ? 0 : main_control.compressor.intake_regulator + //operation status + .["active"] = main_control.active + .["rpm"] = main_control.rpm + .["power"] = energy_to_power(main_control.produced_energy) + .["integrity"] = main_control.get_turbine_integrity() - return data + //running parameters + .["max_rpm"] = main_control.max_allowed_rpm + .["max_temperature"] = main_control.max_allowed_temperature + .["temp"] = main_control.compressor.input_turf?.air.temperature || 0 + .["regulator"] = main_control.compressor.intake_regulator /obj/machinery/computer/turbine_computer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() @@ -74,19 +84,35 @@ switch(action) if("toggle_power") var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve() - if(!main_control || !main_control.all_parts_connected || main_control.rpm > 1000) - return TRUE - if(!main_control.activate_parts(usr, check_only = TRUE)) - return TRUE + if(!main_control) + return FALSE + + if(!main_control.active) //turning on the machine requires all part to be linked + if(!main_control.activate_parts(ui.user, check_only = TRUE)) + return FALSE + else if(main_control.rpm > 1000) //turning off requires rpm to be less than 1000 + return FALSE + main_control.toggle_power() main_control.rpm = 0 main_control.produced_energy = 0 - . = TRUE + return TRUE + if("regulate") - var/intake_size = text2num(params["regulate"]) + var/intake_size = params["regulate"] + if(isnull(intake_size)) + return FALSE + + intake_size = text2num(intake_size) + if(isnull(intake_size)) + return FALSE + var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve() - if(intake_size == null || !main_control) - return - if(!QDELETED(main_control.compressor)) - main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1) - . = TRUE + if(!main_control) + return FALSE + + if(QDELETED(main_control.compressor)) + return FALSE + + main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1) + return TRUE diff --git a/code/modules/power/turbine/turbine_parts.dm b/code/modules/power/turbine/turbine_parts.dm index 4215fccf39fe0..e568f9e096469 100644 --- a/code/modules/power/turbine/turbine_parts.dm +++ b/code/modules/power/turbine/turbine_parts.dm @@ -1,3 +1,8 @@ +///String to access turbine part typepath to upgrade +#define TURBINE_UPGRADE_PART "part" +///String to access turbine part required amount to upgrade +#define TURBINE_UPGRADE_AMOUNT "amount" + /obj/item/turbine_parts name = "turbine parts" desc = "you really should call an admin" @@ -8,90 +13,62 @@ var/part_efficiency = 0 ///Efficiency increase amount for each tier var/part_efficiency_increase_amount = 0 - ///Current part tier var/current_tier = TURBINE_PART_TIER_ONE - ///Max part tier - var/max_tier = TURBINE_PART_TIER_FOUR - - ///Stores the path of the material for the second tier upgrade - var/obj/item/stack/sheet/second_tier_material = /obj/item/stack/sheet/plasteel - ///Amount of second tier material for the upgrade - var/second_tier_material_amount = 10 - - ///Stores the path of the material for the third tier upgrade - var/obj/item/stack/sheet/third_tier_material = /obj/item/stack/sheet/mineral/titanium - ///Amount of third tier material for the upgrade - var/third_tier_material_amount = 10 - - ///Stores the path of the material for the fourth tier upgrade - var/obj/item/stack/sheet/fourth_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen - ///Amount of fourth tier material for the upgrade - var/fourth_tier_material_amount = 5 - ///Max rpm reachable by the part var/max_rpm = 35000 - ///Multiplier to increase the max rpm per tier, max should be around 500000 rpm - var/max_rpm_tier_multiplier = 2.5 - ///Max temperature achievable by the part before the turbine starts to take damage var/max_temperature = 50000 - ///Max temperature exponential value per tier - var/max_temperature_tier_exponential = 1.2 /obj/item/turbine_parts/examine(mob/user) . = ..() - . += "This is a tier [current_tier] turbine part, rated for [max_rpm] rpm and [max_temperature] K." - var/upgrade_material_name_amount - switch(current_tier) - if(TURBINE_PART_TIER_ONE) - upgrade_material_name_amount = "[second_tier_material_amount] [initial(second_tier_material.name)] sheets" - if(TURBINE_PART_TIER_TWO) - upgrade_material_name_amount = "[third_tier_material_amount] [initial(third_tier_material.name)] sheets" - if(TURBINE_PART_TIER_THREE) - upgrade_material_name_amount = "[fourth_tier_material_amount] [initial(fourth_tier_material.name)] sheets" + . += span_notice("This is a tier [current_tier] turbine part, rated for [max_rpm] rpm and [max_temperature] K.") - if(upgrade_material_name_amount) - . += "Can be upgraded with [upgrade_material_name_amount]." + var/list/required_parts = get_tier_upgrades() + if(length(required_parts)) + var/obj/item/stack/material = required_parts[TURBINE_UPGRADE_PART] + . += span_notice("Can be upgraded with [required_parts[TURBINE_UPGRADE_AMOUNT]] [initial(material.name)] sheets.") else - . += "Is already at max tier." + . += span_notice("Is already at max tier.") + +///Returns a list containing the typepath & amount of it required to upgrade to the next tier +/obj/item/turbine_parts/proc/get_tier_upgrades() + PROTECTED_PROC(TRUE) + SHOULD_BE_PURE(TRUE) + RETURN_TYPE(/list) -/obj/item/turbine_parts/attackby(obj/item/attacking_item, mob/user, params) - if(current_tier >= max_tier) - return FALSE switch(current_tier) if(TURBINE_PART_TIER_ONE) - if(!istype(attacking_item, second_tier_material)) - return - var/obj/item/stack/sheet/second_tier = attacking_item - if(do_after(user, 1 SECONDS, src) && second_tier.use(second_tier_material_amount)) - current_tier = 2 - part_efficiency += part_efficiency_increase_amount - max_rpm *= max_rpm_tier_multiplier - max_temperature = max_temperature ** max_temperature_tier_exponential - return TRUE + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/plasteel, TURBINE_UPGRADE_AMOUNT = 10) if(TURBINE_PART_TIER_TWO) - if(!istype(attacking_item, third_tier_material)) - return - var/obj/item/stack/sheet/third_tier = attacking_item - if(do_after(user, 2 SECONDS, src) && third_tier.use(third_tier_material_amount)) - current_tier = 3 - part_efficiency += part_efficiency_increase_amount - max_rpm *= max_rpm_tier_multiplier - max_temperature = max_temperature ** max_temperature_tier_exponential - return TRUE + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 10) if(TURBINE_PART_TIER_THREE) - if(!istype(attacking_item, fourth_tier_material)) - return - var/obj/item/stack/sheet/fourth_tier = attacking_item - if(do_after(user, 3 SECONDS, src) && fourth_tier.use(fourth_tier_material_amount)) - current_tier = 4 - part_efficiency += part_efficiency_increase_amount - max_rpm *= max_rpm_tier_multiplier - max_temperature = max_temperature ** max_temperature_tier_exponential - return TRUE - - return ..() + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 5) + +/obj/item/turbine_parts/item_interaction(mob/living/user, obj/item/attacking_item, list/modifiers) + . = NONE + + var/list/required_parts = get_tier_upgrades() + if(!length(required_parts)) + balloon_alert(user, "already at max tier!") + return ITEM_INTERACT_FAILURE + + var/obj/item/stack/sheet/material = attacking_item + if(!istype(material, required_parts[TURBINE_UPGRADE_PART])) + balloon_alert(user, "incorrect part!") + return ITEM_INTERACT_FAILURE + + var/amount = required_parts[TURBINE_UPGRADE_AMOUNT] + if(material.amount < amount) + balloon_alert(user, "requires [amount] sheets!") + return ITEM_INTERACT_FAILURE + + if(do_after(user, current_tier SECONDS, src) && material.use(amount)) + current_tier += 1 + part_efficiency += part_efficiency_increase_amount + max_rpm *= 2.5 + max_temperature = max_temperature ** 1.2 + return ITEM_INTERACT_SUCCESS /obj/item/turbine_parts/compressor name = "compressor part" @@ -113,9 +90,15 @@ icon_state = "stator_part" part_efficiency = 0.85 part_efficiency_increase_amount = 0.015 - second_tier_material = /obj/item/stack/sheet/mineral/titanium - third_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen - fourth_tier_material = /obj/item/stack/sheet/mineral/zaukerite - second_tier_material_amount = 15 - third_tier_material_amount = 15 - fourth_tier_material_amount = 10 + +/obj/item/turbine_parts/stator/get_tier_upgrades() + switch(current_tier) + if(TURBINE_PART_TIER_ONE) + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 15) + if(TURBINE_PART_TIER_TWO) + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 15) + if(TURBINE_PART_TIER_THREE) + return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/zaukerite, TURBINE_UPGRADE_AMOUNT = 10) + +#undef TURBINE_UPGRADE_PART +#undef TURBINE_UPGRADE_AMOUNT diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm index 7fb22e42ef5a9..6fa8ee80c6de8 100644 --- a/code/modules/projectiles/ammunition/energy/stun.dm +++ b/code/modules/projectiles/ammunition/energy/stun.dm @@ -16,6 +16,9 @@ /obj/item/ammo_casing/energy/electrode/old e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE) +/obj/item/ammo_casing/energy/electrode/ai_turrets + projectile_type = /obj/projectile/energy/electrode/ai_turrets + /obj/item/ammo_casing/energy/disabler projectile_type = /obj/projectile/beam/disabler select_name = "disable" diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index 8b0bc1da7e5b8..05d4c4bf4c99e 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -1,44 +1,4 @@ -/obj/item/ammo_box/magazine/m10mm - name = "pistol magazine (10mm)" - desc = "A gun magazine." - icon_state = "9x19p" - base_icon_state = "9x19p" - ammo_type = /obj/item/ammo_casing/c10mm - caliber = CALIBER_10MM - max_ammo = 8 - multiple_sprites = AMMO_BOX_FULL_EMPTY - multiple_sprite_use_base = TRUE - -/obj/item/ammo_box/magazine/m10mm/fire - name = "pistol magazine (10mm incendiary)" - icon_state = "9x19pI" - base_icon_state = "9x19pI" - desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target." - ammo_type = /obj/item/ammo_casing/c10mm/fire - -/obj/item/ammo_box/magazine/m10mm/hp - name = "pistol magazine (10mm HP)" - icon_state = "9x19pH" - base_icon_state = "9x19pH" - desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." - ammo_type = /obj/item/ammo_casing/c10mm/hp - -/obj/item/ammo_box/magazine/m10mm/ap - name = "pistol magazine (10mm AP)" - icon_state = "9x19pA" - base_icon_state = "9x19pA" - desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." - ammo_type = /obj/item/ammo_casing/c10mm/ap - -/obj/item/ammo_box/magazine/m45 - name = "handgun magazine (.45)" - icon_state = "45-8" - base_icon_state = "45" - ammo_type = /obj/item/ammo_casing/c45 - caliber = CALIBER_45 - max_ammo = 8 - multiple_sprites = AMMO_BOX_PER_BULLET - multiple_sprite_use_base = TRUE +// Makarov (9mm) // /obj/item/ammo_box/magazine/m9mm name = "pistol magazine (9mm)" @@ -46,7 +6,7 @@ base_icon_state = "9x19p" ammo_type = /obj/item/ammo_casing/c9mm caliber = CALIBER_9MM - max_ammo = 8 + max_ammo = 12 multiple_sprites = AMMO_BOX_FULL_EMPTY multiple_sprite_use_base = TRUE @@ -71,6 +31,8 @@ desc= "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." ammo_type = /obj/item/ammo_casing/c9mm/ap +// Stechkin APS (9mm) // + /obj/item/ammo_box/magazine/m9mm_aps name = "stechkin pistol magazine (9mm)" icon_state = "9mmaps-15" @@ -86,25 +48,50 @@ /obj/item/ammo_box/magazine/m9mm_aps/fire name = "stechkin pistol magazine (9mm incendiary)" ammo_type = /obj/item/ammo_casing/c9mm/fire - max_ammo = 15 /obj/item/ammo_box/magazine/m9mm_aps/hp name = "stechkin pistol magazine (9mm HP)" ammo_type = /obj/item/ammo_casing/c9mm/hp - max_ammo = 15 /obj/item/ammo_box/magazine/m9mm_aps/ap name = "stechkin pistol magazine (9mm AP)" ammo_type = /obj/item/ammo_casing/c9mm/ap - max_ammo = 15 -/obj/item/ammo_box/magazine/m50 - name = "handgun magazine (.50ae)" - icon_state = "50ae" - ammo_type = /obj/item/ammo_casing/a50ae - caliber = CALIBER_50AE - max_ammo = 7 - multiple_sprites = AMMO_BOX_PER_BULLET +// Ansem (10mm) // + +/obj/item/ammo_box/magazine/m10mm + name = "pistol magazine (10mm)" + desc = "A gun magazine." + icon_state = "9x19p" + base_icon_state = "9x19p" + ammo_type = /obj/item/ammo_casing/c10mm + caliber = CALIBER_10MM + max_ammo = 8 + multiple_sprites = AMMO_BOX_FULL_EMPTY + multiple_sprite_use_base = TRUE + +/obj/item/ammo_box/magazine/m10mm/fire + name = "pistol magazine (10mm incendiary)" + icon_state = "9x19pI" + base_icon_state = "9x19pI" + desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target." + ammo_type = /obj/item/ammo_casing/c10mm/fire + +/obj/item/ammo_box/magazine/m10mm/hp + name = "pistol magazine (10mm HP)" + icon_state = "9x19pH" + base_icon_state = "9x19pH" + desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." + ammo_type = /obj/item/ammo_casing/c10mm/hp + +/obj/item/ammo_box/magazine/m10mm/ap + name = "pistol magazine (10mm AP)" + icon_state = "9x19pA" + base_icon_state = "9x19pA" + desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." + ammo_type = /obj/item/ammo_casing/c10mm/ap + +// Regal Condor (10mm) // /obj/item/ammo_box/magazine/r10mm name = "regal condor magazine (10mm Reaper)" @@ -115,3 +102,25 @@ max_ammo = 8 multiple_sprites = AMMO_BOX_PER_BULLET multiple_sprite_use_base = TRUE + +// M1911 (.45) // + +/obj/item/ammo_box/magazine/m45 + name = "handgun magazine (.45)" + icon_state = "45-8" + base_icon_state = "45" + ammo_type = /obj/item/ammo_casing/c45 + caliber = CALIBER_45 + max_ammo = 8 + multiple_sprites = AMMO_BOX_PER_BULLET + multiple_sprite_use_base = TRUE + +// Desert Eagle (.50 AE) // + +/obj/item/ammo_box/magazine/m50 + name = "handgun magazine (.50ae)" + icon_state = "50ae" + ammo_type = /obj/item/ammo_casing/a50ae + caliber = CALIBER_50AE + max_ammo = 7 + multiple_sprites = AMMO_BOX_PER_BULLET diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm index 4f666e119b84d..695388280ebc4 100644 --- a/code/modules/projectiles/boxes_magazines/external/toy.dm +++ b/code/modules/projectiles/boxes_magazines/external/toy.dm @@ -20,7 +20,7 @@ /obj/item/ammo_box/magazine/toy/pistol name = "foam force pistol magazine" icon_state = "9x19p" - max_ammo = 8 + max_ammo = 12 multiple_sprites = AMMO_BOX_FULL_EMPTY /obj/item/ammo_box/magazine/toy/pistol/riot diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 45c1e9c65ea09..93ef571258bcc 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -477,7 +477,7 @@ /obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1) if(isnull(chambered)) return ..() - if(can_misfire && chambered.can_misfire != FALSE) + if(can_misfire) misfire_probability += misfire_percentage_increment misfire_probability = clamp(misfire_probability, 0, misfire_probability_cap) if(chambered.can_misfire) diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm index bd84e5f794188..dae77b0936833 100644 --- a/code/modules/projectiles/guns/ballistic/toy.dm +++ b/code/modules/projectiles/guns/ballistic/toy.dm @@ -17,6 +17,7 @@ /obj/item/gun/ballistic/automatic/toy/riot spawn_magazine_type = /obj/item/ammo_box/magazine/toy/smg/riot + /obj/item/gun/ballistic/automatic/pistol/toy name = "foam force pistol" desc = "A small, easily concealable toy handgun. Ages 8 and up." @@ -31,6 +32,9 @@ magazine = new /obj/item/ammo_box/magazine/toy/pistol/riot(src) return ..() +/obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine + projectile_damage_multiplier = 1.4 + /obj/item/gun/ballistic/shotgun/toy name = "foam force shotgun" desc = "A toy shotgun with wood furniture and a four-shell capacity underneath. Ages 8 and up." diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm index e826e1392bf28..f6421ab7e1b82 100644 --- a/code/modules/projectiles/guns/energy/energy_gun.dm +++ b/code/modules/projectiles/guns/energy/energy_gun.dm @@ -136,7 +136,7 @@ inhand_icon_state = "turretlaser" slot_flags = null w_class = WEIGHT_CLASS_HUGE - ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) + ammo_type = list(/obj/item/ammo_casing/energy/electrode/ai_turrets, /obj/item/ammo_casing/energy/laser) weapon_weight = WEAPON_HEAVY trigger_guard = TRIGGER_GUARD_NONE ammo_x_offset = 2 diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index d542f2299148c..ba63e41af4c06 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -225,6 +225,11 @@ update_appearance() /obj/projectile/kinetic/on_range() + if(!pressure_decrease_active && !lavaland_equipment_pressure_check(get_turf(src))) + name = "weakened [name]" + damage = damage * pressure_decrease + pressure_decrease_active = TRUE + strike_thing(loc) ..() diff --git a/code/modules/projectiles/guns/magic/arcane_barrage.dm b/code/modules/projectiles/guns/magic/arcane_barrage.dm index 74be54a6323e6..3534b99675df9 100644 --- a/code/modules/projectiles/guns/magic/arcane_barrage.dm +++ b/code/modules/projectiles/guns/magic/arcane_barrage.dm @@ -6,6 +6,7 @@ icon_state = "arcane_barrage" inhand_icon_state = "arcane_barrage" base_icon_state = "arcane_barrage" + icon_angle = 90 lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' slot_flags = null diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index 6a383befa54d3..e84bb9cd1f316 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -3,6 +3,7 @@ ammo_type = /obj/item/ammo_casing/magic/nothing worn_icon_state = null icon_state = "staff" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' item_flags = NEEDS_PERMIT | NO_MAT_REDEMPTION @@ -242,6 +243,7 @@ ammo_type = /obj/item/ammo_casing/magic/spellblade icon_state = "spellblade" inhand_icon_state = "spellblade" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' hitsound = 'sound/items/weapons/rapierhit.ogg' diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index 0a82f44318e79..6f59998ba61d5 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -4,6 +4,7 @@ ammo_type = /obj/item/ammo_casing/magic icon_state = "nothingwand" inhand_icon_state = "wand" + icon_angle = -45 lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' righthand_file = 'icons/mob/inhands/items_righthand.dmi' base_icon_state = "nothingwand" diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm index c3462abdd9144..2034b400e172f 100644 --- a/code/modules/projectiles/guns/special/meat_hook.dm +++ b/code/modules/projectiles/guns/special/meat_hook.dm @@ -8,6 +8,7 @@ ammo_type = /obj/item/ammo_casing/magic/hook icon_state = "hook" inhand_icon_state = "hook" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' fire_sound = 'sound/items/weapons/batonextend.ogg' diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 1a21ac4f82d89..065a4899fc728 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -420,12 +420,18 @@ // Shooting yourself point-blank if (firer == original) original = null + if (firer == fired_from) + fired_from = null firer = null /obj/projectile/proc/original_deleted(datum/source) SIGNAL_HANDLER original = null +/obj/projectile/proc/fired_from_deleted(datum/source) + SIGNAL_HANDLER + fired_from = null + /obj/projectile/proc/on_ricochet(atom/target) ricochets++ if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range) @@ -752,11 +758,13 @@ /obj/projectile/proc/fire(fire_angle, atom/direct_target) LAZYINITLIST(impacted) - if (fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) if (firer) RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted)) SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original) + if (fired_from) + if (firer != fired_from) + RegisterSignal(fired_from, COMSIG_QDELETING, PROC_REF(fired_from_deleted)) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) if (original) if (firer != original) RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted)) diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm index fb5c041b33875..168e7b0a98935 100644 --- a/code/modules/projectiles/projectile/energy/stun.dm +++ b/code/modules/projectiles/projectile/energy/stun.dm @@ -2,28 +2,397 @@ name = "electrode" icon_state = "spark" color = COLOR_YELLOW - paralyze = 10 SECONDS - stutter = 10 SECONDS - jitter = 40 SECONDS hitsound = 'sound/items/weapons/taserhit.ogg' - range = 7 + range = 5 + reflectable = FALSE tracer_type = /obj/effect/projectile/tracer/stun muzzle_type = /obj/effect/projectile/muzzle/stun impact_type = /obj/effect/projectile/impact/stun + /// How much stamina damage will the tase deal in 1 second + VAR_PROTECTED/tase_stamina = 60 + /// Electrodes that follow the projectile + VAR_PRIVATE/datum/weakref/beam_weakref + /// We need to track who was the ORIGINAL firer of the projectile specifically to ensure deflects work correctly + VAR_PRIVATE/datum/weakref/initial_firer_weakref -/obj/projectile/energy/electrode/on_hit(atom/target, blocked = 0, pierce_hit) +/obj/projectile/energy/electrode/is_hostile_projectile() + return TRUE + +/obj/projectile/energy/electrode/Destroy() + QDEL_NULL(beam_weakref) + return ..() + +/obj/projectile/energy/electrode/fire(fire_angle, atom/direct_target) + if(firer) + beam_weakref = WEAKREF(firer.Beam( + BeamTarget = src, + icon = 'icons/effects/beam.dmi', + icon_state = "electrodes_nozap", + maxdistance = maximum_range + 1, + beam_type = /obj/effect/ebeam/electrodes_nozap, + )) + initial_firer_weakref = WEAKREF(firer) + return ..() + +/obj/projectile/energy/electrode/on_hit(mob/living/target, blocked = 0, pierce_hit) . = ..() - if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! - do_sparks(1, TRUE, src) - else if(iscarbon(target)) - var/mob/living/carbon/C = target - C.add_mood_event("tased", /datum/mood_event/tased) - SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) - if(HAS_TRAIT(C, TRAIT_HULK)) - C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - else if(!C.check_stun_immunity(CANKNOCKDOWN)) - addtimer(CALLBACK(C, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS) + if(pierce_hit) + return + if(. == BULLET_ACT_BLOCK || blocked >= 100 || !isliving(target)) + return + // we need a "from", otherwise, where does the electricity come from? + if(isnull(fired_from)) + target.visible_message( + span_warning("[src]\s collide with [target] harmlessly[isfloorturf(target.loc) ? ", before falling to [target.loc]" : ""]."), + span_notice("[src] collide with you harmlessly[isfloorturf(target.loc) ? ", before falling to [target.loc]" : ""]."), + ) + return + + do_sparks(1, TRUE, src) + do_sparks(1, TRUE, fired_from) + target.apply_status_effect( + /*type = *//datum/status_effect/tased, + /*taser = */fired_from, + /*firer = */initial_firer_weakref?.resolve() || firer, + /*tase_stamina = */tase_stamina, + /*energy_drain = */STANDARD_CELL_CHARGE * 0.05, + /*electrode_name = */"\the [src]\s", + /*tase_range = */maximum_range + 1, + ) /obj/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet do_sparks(1, TRUE, src) - ..() + return ..() + +/obj/projectile/energy/electrode/ai_turrets + tase_stamina = 120 + +/// Status effect tracking being tased by someone! +/datum/status_effect/tased + id = "being_tased" + status_type = STATUS_EFFECT_MULTIPLE + alert_type = null + tick_interval = 0.25 SECONDS + on_remove_on_mob_delete = TRUE + /// What atom is tasing us? + VAR_PRIVATE/datum/taser + /// What atom is using the atom tasing us? Sometimes the same as the taser, such as with turrets. + VAR_PRIVATE/atom/movable/firer + /// The beam datum representing the taser electrodes + VAR_PRIVATE/datum/beam/tase_line + /// How much stamina damage does it aim to cause in a second? + VAR_FINAL/stamina_per_second = 80 + /// How much energy does the taser use per tick? + VAR_FINAL/energy_drain = STANDARD_CELL_CHARGE * 0.05 + /// What do we name the electrodes? + VAR_FINAL/electrode_name + /// How far can the taser reach? + VAR_FINAL/tase_range = 6 + +/datum/status_effect/tased/on_creation( + mob/living/new_owner, + datum/fired_from, + atom/movable/firer, + tase_stamina = 80, + energy_drain = STANDARD_CELL_CHARGE * 0.05, + electrode_name = "the electrodes", + tase_range = 6, +) + if(isnull(fired_from) || isnull(firer) || !can_tase_with(fired_from)) + qdel(src) + return + + src.stamina_per_second = tase_stamina + src.energy_drain = energy_drain + src.electrode_name = electrode_name + src.tase_range = tase_range + + . = ..() + if(!.) + return + + set_taser(fired_from) + set_firer(firer) + +/// Checks if the passed atom is captable of being used to tase someone +/datum/status_effect/tased/proc/can_tase_with(datum/with_what) + if(istype(with_what, /obj/item/gun/energy)) + var/obj/item/gun/energy/taser_gun = with_what + if(isnull(taser_gun.cell)) + return FALSE + + else if(istype(with_what, /obj/machinery)) + var/obj/machinery/taser_machine = with_what + if(!taser_machine.is_operational) + return FALSE + + return TRUE + +/// Actually does the tasing with the passed atom +/// Returns TRUE if the tasing was successful, FALSE if it failed +/datum/status_effect/tased/proc/do_tase_with(atom/with_what, seconds_between_ticks) + if(!can_see(taser, owner, 5)) + return FALSE + if(istype(with_what, /obj/item/gun/energy)) + var/obj/item/gun/energy/taser_gun = with_what + if(!taser_gun.cell?.use(energy_drain * seconds_between_ticks)) + return FALSE + taser_gun.update_appearance() + return TRUE + + if(istype(taser, /obj/machinery)) + var/obj/machinery/taser_machine = taser + if(!taser_machine.is_operational) + return FALSE + if(!taser_machine.use_energy(energy_drain * seconds_between_ticks, force = FALSE)) + return FALSE + return TRUE + + if(istype(taser, /obj/item/mecha_parts/mecha_equipment)) + var/obj/item/mecha_parts/mecha_equipment/taser_equipment = taser + if(!taser_equipment.chassis \ + || !taser_equipment.active \ + || taser_equipment.get_integrity() <= 1 \ + || taser_equipment.chassis.is_currently_ejecting \ + || taser_equipment.chassis.equipment_disabled \ + || !taser_equipment.chassis.use_energy(energy_drain * seconds_between_ticks)) + return FALSE + return TRUE + + return TRUE + +/datum/status_effect/tased/on_apply() + if(issilicon(owner) || isbot(owner) || isdrone(owner) || HAS_TRAIT(owner, TRAIT_PIERCEIMMUNE)) + owner.visible_message(span_warning("[capitalize(electrode_name)] fail to catch [owner][isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]!")) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_RESIST, PROC_REF(try_remove_taser)) + RegisterSignal(owner, COMSIG_CARBON_PRE_MISC_HELP, PROC_REF(someome_removing_taser)) + SEND_SIGNAL(owner, COMSIG_LIVING_MINOR_SHOCK) + if(!owner.has_status_effect(type)) + // does not use the status effect api because we snowflake it a bit + owner.throw_alert(type, /atom/movable/screen/alert/tazed) + owner.add_mood_event("tased", /datum/mood_event/tased) + owner.add_movespeed_modifier(/datum/movespeed_modifier/being_tased) + if(!HAS_TRAIT(owner, TRAIT_ANALGESIA)) + owner.emote("scream") + if(HAS_TRAIT(owner, TRAIT_HULK)) + owner.say(pick( + ";RAAAAAAAARGH!", + ";HNNNNNNNNNGGGGGGH!", + ";GWAAAAAAAARRRHHH!", + "NNNNNNNNGGGGGGGGHH!", + ";AAAAAAARRRGH!", + ), forced = "hulk") + if(ishuman(owner)) + var/mob/living/carbon/human/human_owner = owner + human_owner.force_say() + return TRUE + +/datum/status_effect/tased/on_remove() + if(istype(taser, /obj/machinery/porta_turret)) + var/obj/machinery/porta_turret/taser_turret = taser + taser_turret.manual_control = initial(taser_turret.manual_control) + taser_turret.always_up = initial(taser_turret.always_up) + taser_turret.check_should_process() + else if(istype(taser, /obj/machinery/power/emitter)) + var/obj/machinery/power/emitter/taser_emitter = taser + taser_emitter.manual = initial(taser_emitter.manual) + + var/mob/living/mob_firer = firer + if(istype(mob_firer)) + mob_firer.remove_movespeed_modifier(/datum/movespeed_modifier/tasing_someone) + + if(!QDELING(owner) && !owner.has_status_effect(type)) + owner.adjust_jitter_up_to(10 SECONDS, 1 MINUTES) + owner.remove_movespeed_modifier(/datum/movespeed_modifier/being_tased) + owner.clear_alert(type) + + taser = null + firer = null + QDEL_NULL(tase_line) + +/datum/status_effect/tased/tick(seconds_between_ticks) + if(!do_tase_with(taser, seconds_between_ticks)) + end_tase() + return + + owner.adjust_stutter_up_to(10 SECONDS, 20 SECONDS) + owner.adjust_jitter_up_to(20 SECONDS, 30 SECONDS) + if(owner.stat <= SOFT_CRIT) + owner.do_jitter_animation(INFINITY) // maximum POWER + + // You are damp, that's bad when you're being tased + if(owner.fire_stacks < 0) + owner.apply_damage(max(1, owner.fire_stacks * -0.5 * seconds_between_ticks), FIRE, spread_damage = TRUE) + if(SPT_PROB(25, seconds_between_ticks)) + do_sparks(1, FALSE, owner) + + // clumsy people might hit their head while being tased + if(HAS_TRAIT(owner, TRAIT_CLUMSY) && owner.body_position == LYING_DOWN && SPT_PROB(20, seconds_between_ticks)) + owner.apply_damage(10, BRUTE, BODY_ZONE_HEAD) + playsound(owner, 'sound/effects/tableheadsmash.ogg', 75, TRUE) + + // the actual stunning is here + if(!owner.check_stun_immunity(CANSTUN|CANKNOCKDOWN)) + owner.apply_damage(stamina_per_second * seconds_between_ticks, STAMINA) + +/// Sets the passed atom as the "taser" +/datum/status_effect/tased/proc/set_taser(datum/new_taser) + taser = new_taser + RegisterSignals(taser, list(COMSIG_QDELETING, COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), PROC_REF(end_tase)) + RegisterSignal(taser, COMSIG_GUN_TRY_FIRE, PROC_REF(block_firing)) + // snowflake cases! yay! + if(istype(taser, /obj/machinery/porta_turret)) + var/obj/machinery/porta_turret/taser_turret = taser + taser_turret.manual_control = TRUE + taser_turret.always_up = TRUE + else if(istype(taser, /obj/machinery/power/emitter)) + var/obj/machinery/power/emitter/taser_emitter = taser + taser_emitter.manual = TRUE + +/// Sets the passed atom as the person operating the taser, the "firer" +/datum/status_effect/tased/proc/set_firer(atom/new_firer) + firer = new_firer + if(taser != firer) // Turrets, notably, are both + RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(end_tase)) + + RegisterSignal(firer, COMSIG_MOB_CLICKON, PROC_REF(user_cancel_tase)) + + // Ensures AI mobs or turrets don't tase players until they run out of power + var/mob/living/mob_firer = new_firer + if(!istype(mob_firer) || isnull(mob_firer.client)) + // If multiple things are tasing the same mob, give up sooner, so they can select a new target potentially + addtimer(CALLBACK(src, PROC_REF(end_tase)), (owner.has_status_effect(type) != src) ? 2 SECONDS : 8 SECONDS) + if(istype(mob_firer)) + mob_firer.add_movespeed_modifier(/datum/movespeed_modifier/tasing_someone) + + if(firer == owner) + return + + tase_line = firer.Beam( + BeamTarget = owner, + icon = 'icons/effects/beam.dmi', + icon_state = "electrodes", + maxdistance = tase_range, + beam_type = /obj/effect/ebeam/reacting/electrodes, + ) + RegisterSignal(tase_line, COMSIG_BEAM_ENTERED, PROC_REF(disrupt_tase)) + RegisterSignal(tase_line, COMSIG_QDELETING, PROC_REF(end_tase)) + // moves the tase beam up or down if the target moves up or down + tase_line.RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, TYPE_PROC_REF(/datum/beam, redrawing)) + +/datum/status_effect/tased/proc/block_firing(...) + SIGNAL_HANDLER + return COMPONENT_CANCEL_GUN_FIRE + +/datum/status_effect/tased/proc/user_cancel_tase(mob/living/source, atom/clicked_on, modifiers) + SIGNAL_HANDLER + if(clicked_on != owner) + return NONE + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + return NONE + end_tase() + source.changeNext_move(CLICK_CD_GRABBING) + return COMSIG_MOB_CANCEL_CLICKON + +/datum/status_effect/tased/proc/end_tase(...) + SIGNAL_HANDLER + if(QDELING(src)) + return + owner.visible_message( + span_warning("[capitalize(electrode_name)] stop shocking [owner][isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]."), + span_notice("[capitalize(electrode_name)] stop shocking you[isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]."), + ) + qdel(src) + +/datum/status_effect/tased/proc/try_remove_taser(datum/source) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(try_remove_taser_async), owner) + +/datum/status_effect/tased/proc/someome_removing_taser(datum/source, mob/living/helper) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(try_remove_taser_async), helper) + return COMPONENT_BLOCK_MISC_HELP + +/datum/status_effect/tased/proc/try_remove_taser_async(mob/living/remover) + if(DOING_INTERACTION(remover, id)) + return + owner.shake_up_animation() + playsound(owner, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1) + remover.visible_message( + span_warning("[owner] tries to remove [electrode_name][remover == owner ? "" : " from [owner]"]!"), + span_notice("You try to remove [electrode_name][remover == owner ? "" : " from [owner]"]!"), + ) + // If embedding was less... difficult to work with, I would make tasers rely on an embedded object to handle this + if(!do_after(remover, 5 SECONDS, owner, extra_checks = CALLBACK(src, PROC_REF(try_remove_taser_checks)), interaction_key = id)) + return + remover.visible_message( + span_warning("[owner] removes [electrode_name] from [remover == owner ? "[owner.p_their()]" : "[owner]'s"] body!"), + span_notice("You remove [electrode_name][remover == owner ? "" : " from [owner]'s body"]!"), + ) + end_tase() + +/datum/status_effect/tased/proc/try_remove_taser_checks() + return !QDELETED(src) + +/datum/status_effect/tased/proc/disrupt_tase(datum/beam/source, obj/effect/ebeam/beam_effect, atom/movable/entering) + SIGNAL_HANDLER + + if(!isliving(entering) || entering == taser || entering == firer || entering == owner) + return + if(entering.pass_flags & (PASSMOB|PASSGRILLE|PASSTABLE)) + return + var/mob/living/disruptor = entering + if(!HAS_TRAIT(entering, TRAIT_CLUMSY) || prob(50)) + if(isliving(firer)) + // taser firer can lie down so people can cross over it! + var/mob/living/firer_living = firer + if(firer_living.body_position != disruptor.body_position) + return + else + // otherwise you can limbo under it + if(disruptor.body_position == LYING_DOWN) + return + disruptor.visible_message( + span_warning("[disruptor] gets tangled in [electrode_name]!"), + span_warning("You get tangled in [electrode_name]!"), + ) + if(!disruptor.check_stun_immunity(CANSTUN|CANKNOCKDOWN)) + disruptor.apply_damage(90, STAMINA) + disruptor.Knockdown(5 SECONDS) + disruptor.adjust_jitter_up_to(10 SECONDS, 30 SECONDS) + qdel(src) + +/// Screen alert for being tased, clicking does a resist +/atom/movable/screen/alert/tazed + name = "Tased!" + desc = "You're being tased! You can click this or resist to attempt to stop it, assuming you've not already collapsed." + icon_state = "stun" + clickable_glow = TRUE + +/atom/movable/screen/alert/tazed/Click(location, control, params) + . = ..() + if(!.) + return + var/mob/living/clicker = usr + clicker.resist() + +/obj/effect/ebeam/electrodes_nozap + name = "electrodes" + alpha = 192 + +/obj/effect/ebeam/reacting/electrodes + name = "electrodes" + light_system = OVERLAY_LIGHT + light_on = TRUE + light_color = COLOR_YELLOW + light_power = 1 + light_range = 1.5 + +// movespeed mods +/datum/movespeed_modifier/tasing_someone + multiplicative_slowdown = 2 + +/datum/movespeed_modifier/being_tased + multiplicative_slowdown = 4 diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index bcb6cac2f183d..22abc46d04009 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -415,7 +415,7 @@ if(amount == -1) // Set custom amount var/mob/user = ui.user //Hold a reference of the user if the UI is closed - amount = tgui_input_number(user, "Enter amount to transfer", "Transfer amount") + amount = FLOOR(tgui_input_number(user, "Enter amount to transfer", "Transfer amount", round_value = FALSE), CHEMICAL_VOLUME_ROUNDING) if(!amount || !user.can_perform_action(src)) return FALSE diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index e359e301423e8..2093491015641 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -524,7 +524,25 @@ to_chat(carbies, span_danger("You feel your burns and bruises healing! It stings like hell!")) carbies.add_mood_event("painful_medicine", /datum/mood_event/painful_medicine) - if(HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN) && carbies.getFireLoss() < UNHUSK_DAMAGE_THRESHOLD && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) + reac_volume >= SYNTHFLESH_UNHUSK_AMOUNT)) + + //don't unhusked non husked mobs + if (!HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN)) + return + + //don't try to unhusk mobs above burn damage threshold + if (carbies.getFireLoss() > UNHUSK_DAMAGE_THRESHOLD) + return + + var/datum/reagent/synthflesh = carbies.reagents.has_reagent(/datum/reagent/medicine/c2/synthflesh) + var/current_volume = synthflesh ? synthflesh.volume : 0 + var/current_purity = synthflesh ? synthflesh.purity : 0 + + if (methods & TOUCH) //touch does not apply chems to blood, we want to combine the two volumes before attempting to unhusk + current_purity = current_volume > 0 ? (current_volume * current_purity + reac_volume * creation_purity) / (current_volume + reac_volume) : creation_purity + current_volume += reac_volume + + //when purity = 100%, 60u to unhusk, when purity = 60%, 100u to unhusk. + if(current_volume >= SYNTHFLESH_UNHUSK_MAX || current_volume * current_purity >= SYNTHFLESH_UNHUSK_AMOUNT) carbies.cure_husk(BURN) carbies.visible_message(span_nicegreen("A rubbery liquid coats [carbies]'s burns. [carbies] looks a lot healthier!")) //we're avoiding using the phrases "burnt flesh" and "burnt skin" here because carbies could be a skeleton or a golem or something diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 28c299c87f0af..4a4bf137b3c4d 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -865,3 +865,54 @@ ) new /obj/structure/bouncy_castle(gored.loc, gored) gored.gib() + +/datum/reagent/drug/syndol + name = "Syndol" + description = "A potent and addictive hallucinogen used by syndicate agents disorient certain targets. \ + It is said that the hallucinations it causes are tailored to the user's fears, but tests have been inconclusive, \ + with subjects in security and assistants reporting wildly different experiences." + color = "#c90000" + taste_description = "metallic" + ph = 7 + overdose_threshold = 10 + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + addiction_types = list(/datum/addiction/hallucinogens = 20) + /// Track the active hallucination we're giving out so we don't replace it by accident + VAR_PRIVATE/datum/weakref/active_hallucination_weakref + +/datum/reagent/drug/syndol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + var/obj/item/organ/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) + if(isnull(liver) || !(liver.organ_flags & affected_organ_flags)) + return + // non-trivial but not immediately dangerous liver damage + liver.apply_organ_damage(0.5 * REM * seconds_per_tick) + // anti-hallucinogens can counteract the effects + if(HAS_TRAIT(affected_mob, TRAIT_HALLUCINATION_IMMUNE) || affected_mob.reagents.has_reagent(/datum/reagent/medicine/haloperidol, amount = 3, needs_metabolizing = TRUE)) + QDEL_NULL(active_hallucination_weakref) + return + + // and the main event, funny hallucinations + if(active_hallucination_weakref?.resolve()) + return + var/greatest_fear + if(HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM)) + greatest_fear = /datum/hallucination/delusion/preset/syndies + else if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM) || HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM)) + greatest_fear = /datum/hallucination/delusion/preset/seccies + + if(greatest_fear) + // 5 minutes = 15 units, roughly. we cancel the hallucination early when we exit the mob, anyway + active_hallucination_weakref = WEAKREF(affected_mob.cause_hallucination(greatest_fear, name, duration = 5 MINUTES, skip_nearby = !overdosed)) + else + // if they're just some random schmuck, give them random hallucinations + affected_mob.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS) + +/datum/reagent/drug/syndol/on_mob_end_metabolize(mob/living/affected_mob) + . = ..() + affected_mob.adjust_hallucinations(-16 SECONDS) + QDEL_NULL(active_hallucination_weakref) + +/datum/reagent/drug/syndol/overdose_start(mob/living/affected_mob) + // no message, just refresh the hallucination + QDEL_NULL(active_hallucination_weakref) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 4444ef843c8cf..b5397344d0a62 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1389,12 +1389,7 @@ if(reac_volume < 1) return - exposed_turf.wash(clean_types) - for(var/am in exposed_turf) - var/atom/movable/movable_content = am - if(ismopable(movable_content)) // Mopables will be cleaned anyways by the turf wash - continue - movable_content.wash(clean_types) + exposed_turf.wash(clean_types, TRUE) for(var/mob/living/basic/slime/exposed_slime in exposed_turf) exposed_slime.adjustToxLoss(rand(5,10)) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index c401d39f020ed..0ad3b55fb59d3 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -19,11 +19,17 @@ /datum/reagent/nitroglycerin name = "Nitroglycerin" - description = "Nitroglycerin is a heavy, colorless, oily, explosive liquid obtained by nitrating glycerol." + description = "Nitroglycerin is a heavy, colorless, oily liquid obtained by nitrating glycerol. \ + It is commonly used to treat heart conditions, but also in the creation of explosives." color = COLOR_GRAY taste_description = "oil" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/nitroglycerin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH + /datum/reagent/stabilizing_agent name = "Stabilizing Agent" description = "Keeps unstable chemicals stable. This does not work on everything." diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm index 4c757e0e14691..2a81c39886be6 100644 --- a/code/modules/reagents/reagent_containers/cups/bottle.dm +++ b/code/modules/reagents/reagent_containers/cups/bottle.dm @@ -37,6 +37,11 @@ desc = "A small bottle of spewium." list_reagents = list(/datum/reagent/toxin/spewium = 30) +/obj/item/reagent_containers/cup/bottle/syndol + name = "syndol bottle" + desc = "A small bottle of syndol." + list_reagents = list(/datum/reagent/drug/syndol = 30) + /obj/item/reagent_containers/cup/bottle/morphine name = "morphine bottle" desc = "A small bottle of morphine." diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index 90cc93e54111f..469d2d9cfe320 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -10,6 +10,7 @@ icon = 'icons/obj/drinks/bottles.dmi' icon_state = "glassbottle" worn_icon_state = "bottle" + icon_angle = 90 fill_icon_thresholds = list(0, 10, 20, 30, 40, 50, 60, 70, 80, 90) custom_price = PAYCHECK_CREW * 1.1 amount_per_transfer_from_this = 10 @@ -699,7 +700,7 @@ if(spillable) return - if(attacking_item.sharpness != SHARP_EDGED) + if(attacking_item.get_sharpness() != SHARP_EDGED) return if(attacking_item != user.get_active_held_item()) //no TK allowed diff --git a/code/modules/religion/sparring/ceremonial_gear.dm b/code/modules/religion/sparring/ceremonial_gear.dm index 2c7e73b5a7540..d755704a6d346 100644 --- a/code/modules/religion/sparring/ceremonial_gear.dm +++ b/code/modules/religion/sparring/ceremonial_gear.dm @@ -5,6 +5,7 @@ icon_state = "default" inhand_icon_state = "default" icon = 'icons/obj/weapons/ritual_weapon.dmi' + icon_angle = -45 //does the exact thing we want so heck why not greyscale_config = /datum/greyscale_config/ceremonial_blade @@ -18,8 +19,8 @@ throwforce = 1 //10 wound_bonus = CANT_WOUND // bad for sparring w_class = WEIGHT_CLASS_NORMAL - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") block_chance = 3 //30 block_sound = 'sound/items/weapons/parry.ogg' sharpness = SHARP_EDGED @@ -27,6 +28,8 @@ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE //doesn't affect stats of the weapon as to avoid gamering your opponent with a dope weapon armor_type = /datum/armor/item_ceremonial_blade resistance_flags = FIRE_PROOF + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /datum/armor/item_ceremonial_blade fire = 100 @@ -34,6 +37,9 @@ /obj/item/ceremonial_blade/Initialize(mapload) . = ..() + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) AddComponent(/datum/component/butchering, \ speed = 4 SECONDS, \ effectiveness = 105, \ diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm index 99a9bba1cc84f..3106a925acd2c 100644 --- a/code/modules/requests/request_manager.dm +++ b/code/modules/requests/request_manager.dm @@ -154,6 +154,10 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) to_chat(usr, "You do not have permission to do this, you require +ADMIN", confidential = TRUE) return + if (action == "toggleprint") + GLOB.fax_autoprinting = !GLOB.fax_autoprinting + return TRUE + // Get the request this relates to var/id = params["id"] != null ? text2num(params["id"]) : null if (!id) @@ -228,9 +232,20 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) if(request.req_type != REQUEST_FAX) to_chat(usr, "Request doesn't have a paper to read.", confidential = TRUE) return TRUE - var/obj/item/paper/request_message = request.additional_information + var/obj/item/paper/request_message = request.additional_information["paper"] request_message.ui_interact(usr) return TRUE + if ("print") + if (request.req_type != REQUEST_FAX) + to_chat(usr, "Request doesn't have a paper to print.", confidential = TRUE) + return TRUE + for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin)) + if(FAX.fax_id != request.additional_information["destination_id"]) + continue + var/obj/item/paper/request_message = request.additional_information["paper"] + var/sender_name = request.additional_information["sender_name"] + FAX.receive(request_message, sender_name) + return TRUE if ("play") if(request.req_type != REQUEST_INTERNET_SOUND) to_chat(usr, "Request doesn't have a sound to play.", confidential = TRUE) @@ -257,6 +272,7 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) "timestamp" = request.timestamp, "timestamp_str" = gameTimestamp(wtime = request.timestamp) )) + data["fax_autoprinting"] = GLOB.fax_autoprinting return data #undef REQUEST_PRAYER diff --git a/code/modules/research/part_replacer.dm b/code/modules/research/part_replacer.dm new file mode 100644 index 0000000000000..6c5bb5f64646e --- /dev/null +++ b/code/modules/research/part_replacer.dm @@ -0,0 +1,186 @@ +///RPED. Allows installing & exchaging parts on machines +/obj/item/storage/part_replacer + name = "rapid part exchange device" + desc = "Special mechanical module made to store, sort, and apply standard machine parts." + icon_state = "RPED" + inhand_icon_state = "RPED" + worn_icon_state = "RPED" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + storage_type = /datum/storage/rped + +/obj/item/storage/part_replacer/interact_with_atom(obj/attacked_object, mob/living/user, list/modifiers) + if(user.combat_mode || !istype(attacked_object) || HAS_TRAIT(attacked_object, TRAIT_COMBAT_MODE_SKIP_INTERACTION)) + return ITEM_INTERACT_SKIP_TO_ATTACK + + //its very important to NOT block so frames can still interact with it + if(!ismachinery(attacked_object) || istype(attacked_object, /obj/machinery/computer)) + return NONE + + var/obj/machinery/attacked_machinery = attacked_object + if(!LAZYLEN(attacked_machinery.component_parts)) + return ITEM_INTERACT_FAILURE + + return attacked_machinery.exchange_parts(user, src) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_FAILURE + +///Plays the sound for RPED exhanging or installing parts. +/obj/item/storage/part_replacer/proc/play_rped_sound() + playsound(src, 'sound/items/tools/rped.ogg', 40, TRUE) + +/** + * Gets parts sorted in order of their tier + * Arguments + * + * * ignore_stacks - should the final list contain stacks + */ +/obj/item/storage/part_replacer/proc/get_sorted_parts(ignore_stacks = FALSE) + RETURN_TYPE(/list/obj/item) + + var/list/obj/item/part_list = list() + //Assemble a list of current parts, then sort them by their rating! + for(var/obj/item/component_part in contents) + //No need to put circuit boards in this list or stacks when exchanging parts + if(istype(component_part, /obj/item/circuitboard) || (ignore_stacks && istype(component_part, /obj/item/stack))) + continue + part_list += component_part + //Sort the parts. This ensures that higher tier items are applied first. + sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort)) + + return part_list + +///Bluespace RPED. Allows exchanging parts from a distance & through cameras +/obj/item/storage/part_replacer/bluespace + name = "bluespace rapid part exchange device" + desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts." + icon_state = "BS_RPED" + inhand_icon_state = "BS_RPED" + w_class = WEIGHT_CLASS_NORMAL + storage_type = /datum/storage/rped/bluespace + +/obj/item/storage/part_replacer/bluespace/Initialize(mapload) + . = ..() + + RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(on_part_entered)) + RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_part_exited)) + +/obj/item/storage/part_replacer/bluespace/interact_with_atom(obj/attacked_object, mob/living/user, list/modifiers) + . = ..() + if(. & ITEM_INTERACT_ANY_BLOCKER) + user.Beam(attacked_object, icon_state = "rped_upgrade", time = 0.5 SECONDS) + +/obj/item/storage/part_replacer/bluespace/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + return interact_with_atom(interacting_with, user, modifiers) + +/obj/item/storage/part_replacer/bluespace/play_rped_sound() + if(prob(1)) + playsound(src, 'sound/items/pshoom/pshoom_2.ogg', 40, TRUE) + return + playsound(src, 'sound/items/pshoom/pshoom.ogg', 40, TRUE) + +/** + * Signal handler for when a part has been inserted into the BRPED. + * + * If the inserted item is a rigged or corrupted cell, does some logging. + * + * If it has a reagent holder, clears the reagents and registers signals to prevent new + * reagents being added and registers clean up signals on inserted item's removal from + * the BRPED. + */ +/obj/item/storage/part_replacer/bluespace/proc/on_part_entered(datum/source, obj/item/inserted_component) + SIGNAL_HANDLER + + if(istype(inserted_component, /obj/item/stock_parts/power_store)) + var/obj/item/stock_parts/power_store/inserted_cell = inserted_component + if(inserted_cell.rigged || inserted_cell.corrupted) + message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].") + usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME) + usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK) + return + + if(inserted_component.reagents) + if(length(inserted_component.reagents.reagent_list)) + inserted_component.reagents.clear_reagents() + to_chat(usr, span_notice("[src] churns as [inserted_component] has its reagents emptied into bluespace.")) + RegisterSignal(inserted_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT, PROC_REF(on_insered_component_reagent_pre_add)) + +/** + * Signal handler for when the reagents datum of an inserted part has reagents added to it. + * + * Registers the PRE_ADD variant which allows the signal handler to stop reagents being + * added. + * + * Simply returns COMPONENT_CANCEL_REAGENT_ADD. We never want to allow people to add + * reagents to beakers in BRPEDs as they can then be used for spammable remote bombing. + */ +/obj/item/storage/part_replacer/bluespace/proc/on_insered_component_reagent_pre_add(datum/source, reagent, amount, reagtemp, data, no_react) + SIGNAL_HANDLER + + return COMPONENT_CANCEL_REAGENT_ADD + +/** + * Signal handler for a part is removed from the BRPED. + * + * Does signal registration cleanup on its reagents, if it has any. + */ +/obj/item/storage/part_replacer/bluespace/proc/on_part_exited(datum/source, obj/item/removed_component) + SIGNAL_HANDLER + + if(removed_component.reagents) + UnregisterSignal(removed_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT) + +//RPED with tiered contents +/obj/item/storage/part_replacer/bluespace/tier1/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/servo(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/matter_bin(src) + new /obj/item/stock_parts/power_store/cell/high(src) + +/obj/item/storage/part_replacer/bluespace/tier2/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/stock_parts/capacitor/adv(src) + new /obj/item/stock_parts/scanning_module/adv(src) + new /obj/item/stock_parts/servo/nano(src) + new /obj/item/stock_parts/micro_laser/high(src) + new /obj/item/stock_parts/matter_bin/adv(src) + new /obj/item/stock_parts/power_store/cell/super(src) + +/obj/item/storage/part_replacer/bluespace/tier3/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/stock_parts/capacitor/super(src) + new /obj/item/stock_parts/scanning_module/phasic(src) + new /obj/item/stock_parts/servo/pico(src) + new /obj/item/stock_parts/micro_laser/ultra(src) + new /obj/item/stock_parts/matter_bin/super(src) + new /obj/item/stock_parts/power_store/cell/hyper(src) + +/obj/item/storage/part_replacer/bluespace/tier4/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/servo/femto(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + new /obj/item/stock_parts/power_store/cell/bluespace(src) + +//used in a cargo crate +/obj/item/storage/part_replacer/cargo/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/servo(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/matter_bin(src) + +///Cyborg variant +/obj/item/storage/part_replacer/cyborg + name = "rapid part exchange device" + desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has an extra large compartment for more parts." + icon_state = "borgrped" + inhand_icon_state = "RPED" + lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' + storage_type = /datum/storage/rped/bluespace diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm index 9583934446936..beafc7ba260ce 100644 --- a/code/modules/research/stock_parts.dm +++ b/code/modules/research/stock_parts.dm @@ -1,218 +1,6 @@ /*Power cells are in code\modules\power\cell.dm If you create T5+ please take a pass at mech_fabricator.dm. The parts being good enough allows it to go into minus values and create materials out of thin air when printing stuff.*/ -/obj/item/storage/part_replacer - name = "rapid part exchange device" - desc = "Special mechanical module made to store, sort, and apply standard machine parts." - icon_state = "RPED" - inhand_icon_state = "RPED" - worn_icon_state = "RPED" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - w_class = WEIGHT_CLASS_HUGE - var/works_from_distance = FALSE - var/pshoom_or_beepboopblorpzingshadashwoosh = 'sound/items/tools/rped.ogg' - var/alt_sound = null - -/obj/item/storage/part_replacer/Initialize(mapload) - . = ..() - create_storage(storage_type = /datum/storage/rped) - -/obj/item/storage/part_replacer/proc/part_replace_action(obj/attacked_object, mob/living/user) - if(!ismachinery(attacked_object) || istype(attacked_object, /obj/machinery/computer)) - return FALSE - - var/obj/machinery/attacked_machinery = attacked_object - if(!LAZYLEN(attacked_machinery.component_parts)) - return FALSE - - if(attacked_machinery.exchange_parts(user, src) && works_from_distance) - user.Beam(attacked_machinery, icon_state = "rped_upgrade", time = 0.5 SECONDS) - return TRUE - -/obj/item/storage/part_replacer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) - if(part_replace_action(interacting_with, user)) - return ITEM_INTERACT_SUCCESS - return NONE - -/obj/item/storage/part_replacer/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) - if(!works_from_distance) - return NONE - if(part_replace_action(interacting_with, user)) - user.Beam(interacting_with, icon_state = "rped_upgrade", time = 0.5 SECONDS) - return ITEM_INTERACT_SUCCESS - if(istype(interacting_with, /obj/structure/frame)) - // Cursed snowflake but we need to handle frame ranged interaction here - // Likely no longer necessary with the new framework, revisit later - interacting_with.item_interaction(user, src) - user.Beam(interacting_with, icon_state = "rped_upgrade", time = 0.5 SECONDS) - return ITEM_INTERACT_SUCCESS - return NONE - -/obj/item/storage/part_replacer/proc/play_rped_sound() - //Plays the sound for RPED exhanging or installing parts. - if(alt_sound && prob(1)) - playsound(src, alt_sound, 40, TRUE) - else - playsound(src, pshoom_or_beepboopblorpzingshadashwoosh, 40, TRUE) - -/obj/item/storage/part_replacer/bluespace - name = "bluespace rapid part exchange device" - desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts." - icon_state = "BS_RPED" - inhand_icon_state = "BS_RPED" - w_class = WEIGHT_CLASS_NORMAL - works_from_distance = TRUE - pshoom_or_beepboopblorpzingshadashwoosh = 'sound/items/pshoom/pshoom.ogg' - alt_sound = 'sound/items/pshoom/pshoom_2.ogg' - -/obj/item/storage/part_replacer/bluespace/Initialize(mapload) - . = ..() - - atom_storage.max_slots = 400 - atom_storage.max_total_storage = 800 - atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC - - RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(on_part_entered)) - RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_part_exited)) - -/** - * Signal handler for when a part has been inserted into the BRPED. - * - * If the inserted item is a rigged or corrupted cell, does some logging. - * - * If it has a reagent holder, clears the reagents and registers signals to prevent new - * reagents being added and registers clean up signals on inserted item's removal from - * the BRPED. - */ -/obj/item/storage/part_replacer/bluespace/proc/on_part_entered(datum/source, obj/item/inserted_component) - SIGNAL_HANDLER - - if(istype(inserted_component, /obj/item/stock_parts/power_store)) - var/obj/item/stock_parts/power_store/inserted_cell = inserted_component - if(inserted_cell.rigged || inserted_cell.corrupted) - message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].") - usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME) - usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK) - return - - if(inserted_component.reagents) - if(length(inserted_component.reagents.reagent_list)) - inserted_component.reagents.clear_reagents() - to_chat(usr, span_notice("[src] churns as [inserted_component] has its reagents emptied into bluespace.")) - RegisterSignal(inserted_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT, PROC_REF(on_insered_component_reagent_pre_add)) - -/** - * Signal handler for when the reagents datum of an inserted part has reagents added to it. - * - * Registers the PRE_ADD variant which allows the signal handler to stop reagents being - * added. - * - * Simply returns COMPONENT_CANCEL_REAGENT_ADD. We never want to allow people to add - * reagents to beakers in BRPEDs as they can then be used for spammable remote bombing. - */ -/obj/item/storage/part_replacer/bluespace/proc/on_insered_component_reagent_pre_add(datum/source, reagent, amount, reagtemp, data, no_react) - SIGNAL_HANDLER - - return COMPONENT_CANCEL_REAGENT_ADD - -/** - * Signal handler for a part is removed from the BRPED. - * - * Does signal registration cleanup on its reagents, if it has any. - */ -/obj/item/storage/part_replacer/bluespace/proc/on_part_exited(datum/source, obj/item/removed_component) - SIGNAL_HANDLER - - if(removed_component.reagents) - UnregisterSignal(removed_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT) - - -/obj/item/storage/part_replacer/bluespace/tier1 - -/obj/item/storage/part_replacer/bluespace/tier1/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/servo(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/matter_bin(src) - new /obj/item/stock_parts/power_store/cell/high(src) - -/obj/item/storage/part_replacer/bluespace/tier2 - -/obj/item/storage/part_replacer/bluespace/tier2/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/stock_parts/capacitor/adv(src) - new /obj/item/stock_parts/scanning_module/adv(src) - new /obj/item/stock_parts/servo/nano(src) - new /obj/item/stock_parts/micro_laser/high(src) - new /obj/item/stock_parts/matter_bin/adv(src) - new /obj/item/stock_parts/power_store/cell/super(src) - -/obj/item/storage/part_replacer/bluespace/tier3 - -/obj/item/storage/part_replacer/bluespace/tier3/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/stock_parts/capacitor/super(src) - new /obj/item/stock_parts/scanning_module/phasic(src) - new /obj/item/stock_parts/servo/pico(src) - new /obj/item/stock_parts/micro_laser/ultra(src) - new /obj/item/stock_parts/matter_bin/super(src) - new /obj/item/stock_parts/power_store/cell/hyper(src) - -/obj/item/storage/part_replacer/bluespace/tier4 - -/obj/item/storage/part_replacer/bluespace/tier4/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/servo/femto(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - new /obj/item/stock_parts/power_store/cell/bluespace(src) - -/obj/item/storage/part_replacer/cargo //used in a cargo crate - -/obj/item/storage/part_replacer/cargo/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/servo(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/matter_bin(src) - -/obj/item/storage/part_replacer/cyborg - name = "rapid part exchange device" - desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has an extra large compartment for more parts." - icon_state = "borgrped" - inhand_icon_state = "RPED" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - -/obj/item/storage/part_replacer/cyborg/Initialize(mapload) - . = ..() - atom_storage.max_slots = 400 - atom_storage.max_total_storage = 800 - atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC - -/obj/item/storage/part_replacer/proc/get_sorted_parts(ignore_stacks = FALSE) - var/list/part_list = list() - //Assemble a list of current parts, then sort them by their rating! - for(var/obj/item/component_part in contents) - //No need to put circuit boards in this list or stacks when exchanging parts - if(istype(component_part, /obj/item/circuitboard) || (ignore_stacks && istype(component_part, /obj/item/stack))) - continue - part_list += component_part - //Sort the parts. This ensures that higher tier items are applied first. - sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort)) - return part_list - -/proc/cmp_rped_sort(obj/item/first_item, obj/item/second_item) - /** - * even though stacks aren't stock parts, get_part_rating() is defined on the item level (see /obj/item/proc/get_part_rating()) and defaults to returning 0. - */ - return second_item.get_part_rating() - first_item.get_part_rating() /obj/item/stock_parts name = "stock part" diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm index 3cc46b9be32ec..c73ef832c548c 100644 --- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm +++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm @@ -87,6 +87,7 @@ Slimecrossing Weapons icon = 'icons/obj/science/slimecrossing.dmi' icon_state = "bloodgun" inhand_icon_state = "bloodgun" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' item_flags = ABSTRACT | DROPDEL diff --git a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm index 7c588e06dc005..3e9bf0cbe3be0 100644 --- a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm +++ b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm @@ -34,7 +34,7 @@ . = ..() actions += new /datum/action/innate/shuttledocker_rotate(src) actions += new /datum/action/innate/shuttledocker_place(src) - + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE) set_init_ports() if(connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src))) @@ -124,6 +124,7 @@ SET_PLANE(I, ABOVE_GAME_PLANE, shuttle_turf) I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT the_eye.placement_images[I] = list(x_off, y_off) + gatherNavComputerIcons() return TRUE @@ -134,6 +135,7 @@ var/list/to_add = list() to_add += the_eye.placement_images to_add += the_eye.placed_images + to_add += the_eye.extra_images if(!see_hidden) to_add += SSshuttle.hidden_shuttle_turf_images @@ -147,12 +149,55 @@ var/list/to_remove = list() to_remove += the_eye.placement_images to_remove += the_eye.placed_images + to_remove += the_eye.extra_images if(!see_hidden) to_remove += SSshuttle.hidden_shuttle_turf_images user.client.images -= to_remove user.client.view_size.resetToDefault() +/obj/machinery/computer/camera_advanced/shuttle_docker/proc/shuttle_turf_from_coords(list/coords) + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj + var/shuttleDir = shuttle_port.dir + var/curDir = the_eye.dir + var/list/adjustedCoords = coords.Copy() + + // Rotate coords so they match the current shuttle docking port's dir + if(turn(curDir, -90) == shuttleDir) + adjustedCoords[1] = coords[2] + y_offset + adjustedCoords[2] = -(coords[1] + x_offset) + else if(turn(curDir, 90) == shuttleDir) + adjustedCoords[1] = -(coords[2] + y_offset) + adjustedCoords[2] = coords[1] + x_offset + else if(turn(curDir, 180) == shuttleDir) + adjustedCoords[1] = -(coords[1] + x_offset) + adjustedCoords[2] = -(coords[2] + y_offset) + else + adjustedCoords[1] = coords[1] + x_offset + adjustedCoords[2] = coords[2] + y_offset + + return locate(shuttle_port.x + adjustedCoords[1], shuttle_port.y + adjustedCoords[2], shuttle_port.z) + +/obj/machinery/computer/camera_advanced/shuttle_docker/proc/gatherNavComputerIcons() + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj + var/list/placement_image_cache = the_eye.placement_images + var/list/extra_image_cache = the_eye.extra_images + for(var/i in 1 to placement_image_cache.len) + var/image/placement_image = placement_image_cache[i] + var/list/coords = placement_image_cache[placement_image] + var/turf/shuttle_turf = shuttle_turf_from_coords(coords) + var/list/images_to_add = list() + for(var/atom/atom as anything in shuttle_turf) + SEND_SIGNAL(atom, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED, images_to_add) + for(var/i2 in 1 to images_to_add.len) + var/image/extra_image = images_to_add[i2] + extra_image.dir = turn(extra_image.dir, dir2angle(the_eye.dir) - dir2angle(shuttle_port.dir)) + extra_image.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z) + extra_image.layer = ABOVE_NORMAL_TURF_LAYER + SET_PLANE(extra_image, ABOVE_GAME_PLANE, the_eye) + extra_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + extra_image_cache[extra_image] = coords.Copy() + /obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot() if(designating_target_loc || !current_user) return @@ -226,7 +271,7 @@ /obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot() var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj - var/list/image_cache = the_eye.placement_images + var/list/image_cache = the_eye.placement_images + the_eye.extra_images the_eye.setDir(turn(the_eye.dir, -90)) for(var/i in 1 to image_cache.len) var/image/pic = image_cache[i] @@ -235,6 +280,7 @@ coords[1] = coords[2] coords[2] = -Tmp pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z) + pic.dir = turn(pic.dir, -90) var/Tmp = x_offset x_offset = y_offset y_offset = -Tmp @@ -267,6 +313,12 @@ else I.icon_state = "red" . = SHUTTLE_DOCKER_BLOCKED + var/list/extra_image_cache = the_eye.extra_images + for(var/i in 1 to extra_image_cache.len) + var/image/image = extra_image_cache[i] + var/list/coords = extra_image_cache[image] + var/turf/turf = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z) + image.loc = turf /obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T, list/overlappers) // Too close to the map edge is never allowed @@ -322,6 +374,7 @@ use_visibility = FALSE var/list/image/placement_images = list() var/list/image/placed_images = list() + var/list/image/extra_images = list() /mob/eye/camera/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE) . = ..() diff --git a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm index 5ce62f8c04226..df1922ae9daab 100644 --- a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm +++ b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm @@ -33,6 +33,7 @@ /obj/machinery/computer/shuttle/Initialize(mapload) . = ..() + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE) connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src)) /obj/machinery/computer/shuttle/ui_interact(mob/user, datum/tgui/ui) diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index 2f14388fd4958..04fa06dcfaac8 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "retractor" inhand_icon_state = "retractor" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*3, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 1.5) @@ -25,6 +26,7 @@ /obj/item/retractor/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_retractor" + icon_angle = 45 /obj/item/hemostat name = "hemostat" @@ -32,6 +34,7 @@ icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "hemostat" inhand_icon_state = "hemostat" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25) @@ -55,6 +58,7 @@ /obj/item/hemostat/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_hemostat" + icon_angle = 45 /obj/item/cautery name = "cautery" @@ -62,6 +66,7 @@ icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "cautery" inhand_icon_state = "cautery" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/glass = SMALL_MATERIAL_AMOUNT*7.5) @@ -89,6 +94,7 @@ /obj/item/cautery/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_cautery" + icon_angle = 45 /obj/item/cautery/advanced name = "searing tool" @@ -201,6 +207,7 @@ icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "scalpel" inhand_icon_state = "scalpel" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY @@ -212,8 +219,8 @@ throw_speed = 3 throw_range = 5 custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT) - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut") hitsound = 'sound/items/weapons/bladeslice.ogg' sharpness = SHARP_EDGED tool_behaviour = TOOL_SCALPEL @@ -222,6 +229,8 @@ bare_wound_bonus = 15 /// How this looks when placed in a surgical tray var/surgical_tray_overlay = "scalpel_normal" + var/list/alt_continuous = list("stabs", "pierces", "impales") + var/list/alt_simple = list("stab", "pierce", "impale") /obj/item/scalpel/Initialize(mapload) . = ..() @@ -231,6 +240,9 @@ bonus_modifier = 0, \ ) AddElement(/datum/element/eyestab) + alt_continuous = string_list(alt_continuous) + alt_simple = string_list(alt_simple) + AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple) /obj/item/scalpel/get_surgery_tool_overlay(tray_extended) return surgical_tray_overlay @@ -242,6 +254,7 @@ /obj/item/scalpel/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_scalpel" + icon_angle = 0 /obj/item/scalpel/augment desc = "Ultra-sharp blade attached directly to your bone for extra-accuracy." @@ -253,6 +266,7 @@ icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "saw" inhand_icon_state = "saw" + icon_angle = 180 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' hitsound = 'sound/items/weapons/circsawhit.ogg' @@ -297,6 +311,7 @@ /obj/item/circular_saw/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_saw" + icon_angle = 0 /obj/item/circular_saw/augment desc = "A small but very fast spinning saw. It rips and tears until it is done." @@ -461,12 +476,13 @@ name = "mechanical pinches" desc = "An agglomerate of rods and gears." icon = 'icons/obj/medical/surgery_tools.dmi' - custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5) icon_state = "adv_retractor" inhand_icon_state = "adv_retractor" surgical_tray_overlay = "retractor_advanced" + icon_angle = 0 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5) w_class = WEIGHT_CLASS_NORMAL toolspeed = 0.7 @@ -507,6 +523,7 @@ desc = "A type of heavy duty surgical shears used for achieving a clean separation between limb and patient. Keeping the patient still is imperative to be able to secure and align the shears." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "shears" + icon_angle = 90 obj_flags = CONDUCTS_ELECTRICITY item_flags = SURGICAL_TOOL toolspeed = 1 @@ -592,6 +609,7 @@ desc = "For setting things right." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "bonesetter" + icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25) @@ -609,6 +627,7 @@ /obj/item/bonesetter/cyborg icon = 'icons/mob/silicon/robot_items.dmi' icon_state = "toolkit_medborg_bonesetter" + icon_angle = 45 /obj/item/blood_filter name = "blood filter" diff --git a/code/modules/transport/tram/tram_doors.dm b/code/modules/transport/tram/tram_doors.dm index 6e1680bcb4c15..5eb3be234e3d1 100644 --- a/code/modules/transport/tram/tram_doors.dm +++ b/code/modules/transport/tram/tram_doors.dm @@ -211,7 +211,7 @@ if(!hasPower() && density) balloon_alert(user, "pulling emergency exit...") if(do_after(user, 4 SECONDS, target = src)) - try_to_crowbar(src, user, TRUE) + try_to_crowbar(null, user, TRUE) return TRUE /** diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 4d16d24e9e93a..6e6045d675cb5 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -311,6 +311,7 @@ #include "unit_test.dm" #include "verify_config_tags.dm" #include "verify_emoji_names.dm" +#include "washing.dm" #include "weird_food.dm" #include "wizard_loadout.dm" #include "worn_icons.dm" diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 6a2bda4ee25af..241d7b54c39af 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -254,6 +254,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) //Yet more templates /obj/machinery/restaurant_portal, //Template type + /obj/machinery/power/turbine, + //Template type /obj/effect/mob_spawn, //Template type /obj/structure/holosign/robot_seat, diff --git a/code/modules/unit_tests/washing.dm b/code/modules/unit_tests/washing.dm new file mode 100644 index 0000000000000..c0239e9244fc0 --- /dev/null +++ b/code/modules/unit_tests/washing.dm @@ -0,0 +1,43 @@ +/datum/unit_test/washing + /// Stuff we want to test that isn't cleanables, just to make sure they are getting cleaned when they should + var/list/cleanable_bonus_list = list( + /obj/effect/rune, + /obj/item/clothing/gloves/color/black, + /mob/living/carbon/human/dummy/consistent, + ) + + /// Tracks if we caught the clean signal, to know we washed successfully + VAR_PRIVATE/clean_sig_caught = 0 + +/datum/unit_test/washing/Run() + for(var/i in subtypesof(/obj/effect/decal/cleanable) + cleanable_bonus_list) + var/atom/movable/to_clean = allocate(i) + var/mopable = HAS_TRAIT(to_clean, TRAIT_MOPABLE) + + clean_sig_caught = 0 + RegisterSignal(to_clean, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_caught)) + run_loc_floor_bottom_left.wash(CLEAN_ALL) + // mopables are cleaned when their turf is cleaned + if(mopable) + if(clean_sig_caught == 0) + TEST_FAIL("[i] was not cleaned when its turf was cleaned (cleaning only mopables)!") + if(clean_sig_caught > 1) + TEST_FAIL("[i] was cleaned more than once when its turf was cleaned (cleaning only mopables)!") + // non-mopables require the all_contents = TRUE flag to be cleaned + else + if(clean_sig_caught != 0) + TEST_FAIL("[i] was cleaned when its turf was cleaned (cleaning only mopables)!") + run_loc_floor_bottom_left.wash(CLEAN_ALL, TRUE) + if(clean_sig_caught == 0) + TEST_FAIL("[i] was not cleaned when its turf was cleaned (cleaning all contents)!") + if(clean_sig_caught > 1) + TEST_FAIL("[i] was cleaned more than once when its turf was cleaned (cleaning all contents)!") + + if(!QDELETED(to_clean)) + if(istype(to_clean, /obj/effect/decal/cleanable)) + TEST_FAIL("[i] was not deleted when its turf was cleaned!") + qdel(to_clean) + +/datum/unit_test/washing/proc/clean_caught(...) + SIGNAL_HANDLER + clean_sig_caught += 1 diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm index 2276485a2b7b5..7000fb7e9a766 100644 --- a/code/modules/uplink/uplink_items/ammunition.dm +++ b/code/modules/uplink/uplink_items/ammunition.dm @@ -7,19 +7,19 @@ surplus = 40 /datum/uplink_item/ammo/toydarts - name = "Box of Riot Darts" - desc = "A box of 40 Donksoft riot darts, for reloading any compatible foam dart magazine. Don't forget to share!" - item = /obj/item/ammo_box/foambox/riot + name = "Donksoft Riot Pistol Ammunition Case" + desc = "A case containing three spare magazines for the Donksoft riot pistol, along with a box of loose riot darts." + item = /obj/item/storage/toolbox/guncase/traitor/ammunition/donksoft cost = 2 - surplus = 0 uplink_item_flags = SYNDIE_TRIPS_CONTRABAND purchasable_from = ~UPLINK_SERIOUS_OPS /datum/uplink_item/ammo/pistol - name = "9mm Handgun Magazine" - desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol." - item = /obj/item/ammo_box/magazine/m9mm - cost = 1 + name = "9mm Magazine Case" + desc = "A case containing three additional 8-round 9mm magazines, compatible with the Makarov pistol, as well as \ + a box of loose 9mm ammunition." + item = /obj/item/storage/toolbox/guncase/traitor/ammunition + cost = 2 purchasable_from = ~UPLINK_ALL_SYNDIE_OPS uplink_item_flags = SYNDIE_TRIPS_CONTRABAND diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 5c5e0390b5046..9a346da2d5351 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -106,3 +106,9 @@ Contains enough special solution to spray a single super-size seditious symbol, subjecting station staff to slippery suffering." item = /obj/item/traitor_spraycan cost = 1 + +/datum/uplink_item/badass/pinpointer + name = "Surplus Pinpointer" + desc = "Provides a surplus pinpointer, left over from the previous models that were abandoned in favor of a SAAS cloud-based PDA app." + item = /obj/item/pinpointer/nuke/syndicate + cost = 2 diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 0c86d8731e00b..092ec4c384782 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -7,19 +7,22 @@ category = /datum/uplink_category/dangerous /datum/uplink_item/dangerous/foampistol - name = "Toy Pistol with Riot Darts" - desc = "An innocent-looking toy pistol designed to fire foam darts. Comes loaded with riot-grade \ - darts effective at incapacitating a target." - item = /obj/item/gun/ballistic/automatic/pistol/toy/riot - cost = 2 + name = "Donksoft Riot Pistol Case" + desc = "A case containing an innocent-looking toy pistol designed to fire foam darts at higher than normal velocity. \ + Comes loaded with riot-grade darts effective at incapacitating a target, two spare magazines and a box of loose \ + riot darts. Perfect for nonlethal takedowns at range, as well as deniability. While not included in the kit, the \ + pistol is compatible with suppressors, which can be purchased separately." + item = /obj/item/storage/toolbox/guncase/traitor/donksoft + cost = 6 surplus = 10 purchasable_from = ~UPLINK_SERIOUS_OPS /datum/uplink_item/dangerous/pistol - name = "Makarov Pistol" - desc = "A small, easily concealable handgun that uses 9mm auto rounds in 8-round magazines and is compatible \ - with suppressors." - item = /obj/item/gun/ballistic/automatic/pistol + name = "Makarov Pistol Case" + desc = "A weapon case containing an unknown variant of the Makarov pistol, along with two spare magazines and a box of loose 9mm ammunition. \ + Chambered in 9mm. Perfect for frequent skirmishes with security, as well as ensuring you have enough firepower to outlast the competition. \ + While not included in the kit, the pistol is compatible with suppressors, which can be purchased seperately." + item = /obj/item/storage/toolbox/guncase/traitor cost = 7 purchasable_from = ~UPLINK_ALL_SYNDIE_OPS diff --git a/code/modules/vehicles/lavaboat.dm b/code/modules/vehicles/lavaboat.dm index fbe130d969709..307c47ab9e248 100644 --- a/code/modules/vehicles/lavaboat.dm +++ b/code/modules/vehicles/lavaboat.dm @@ -23,6 +23,7 @@ icon = 'icons/mob/rideables/vehicles.dmi' icon_state = "oar" inhand_icon_state = "oar" + icon_angle = 45 lefthand_file = 'icons/mob/inhands/items/lavaland_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/lavaland_righthand.dmi' force = 12 diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm index f00444ae598b0..6d9765e6b0588 100644 --- a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm +++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm @@ -79,11 +79,10 @@ var/datum/gas_mixture/tank_air = internal_tank.return_air() var/datum/gas_mixture/cabin_air = chassis.cabin_air var/release_pressure = internal_tank.release_pressure - var/cabin_pressure = cabin_air.return_pressure() - if(cabin_pressure < release_pressure) + if(cabin_air.return_pressure() < release_pressure) tank_air.release_gas_to(cabin_air, release_pressure) - if(cabin_pressure) - cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, GAS_CO2) + if(cabin_air.has_gas(/datum/gas/carbon_dioxide)) + cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, /datum/gas/carbon_dioxide) /obj/item/mecha_parts/mecha_equipment/air_tank/proc/process_pump(seconds_per_tick) if(!tank_pump_active) diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm index c30e67a274633..651ccf999bb70 100644 --- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm @@ -217,31 +217,32 @@ attempt_refill(usr) return TRUE +///Maximum range the RCD can construct at. +#define RCD_RANGE 3 + /obj/item/mecha_parts/mecha_equipment/rcd name = "mounted RCD" desc = "An exosuit-mounted Rapid Construction Device." icon_state = "mecha_rcd" equip_cooldown = 0 // internal RCD already handles it energy_drain = 0 // internal RCD handles power consumption based on matter use - range = MECHA_MELEE|MECHA_RANGED + range = MECHA_MELEE | MECHA_RANGED item_flags = NO_MAT_REDEMPTION - ///Maximum range the RCD can construct at. - var/rcd_range = 3 + + ///The location the mech was when it began using the rcd + var/atom/initial_location = FALSE ///Whether or not to deconstruct instead. var/deconstruct_active = FALSE - ///The type of internal RCD this equipment uses. - var/rcd_type = /obj/item/construction/rcd/exosuit ///The internal RCD item used by this equipment. - var/obj/item/construction/rcd/internal_rcd + var/obj/item/construction/rcd/exosuit/internal_rcd /obj/item/mecha_parts/mecha_equipment/rcd/Initialize(mapload) . = ..() - internal_rcd = new rcd_type(src) - GLOB.rcd_list += src + internal_rcd = new(src) /obj/item/mecha_parts/mecha_equipment/rcd/Destroy() - GLOB.rcd_list -= src - qdel(internal_rcd) + initial_location = null + QDEL_NULL(internal_rcd) return ..() /obj/item/mecha_parts/mecha_equipment/rcd/get_snowflake_data() @@ -277,16 +278,31 @@ internal_rcd.ui_interact(driver) return TRUE + +/obj/item/mecha_parts/mecha_equipment/rcd/do_after_checks(atom/target) + // Checks if mech moved during operation + if(chassis.loc != initial_location) + return FALSE + + // Cancel build if design changes + if(!deconstruct_active && internal_rcd.blueprint_changed) + return FALSE + + return ..() + /obj/item/mecha_parts/mecha_equipment/rcd/action(mob/source, atom/target, list/modifiers) if(!action_checks(target)) return - if(get_dist(chassis, target) > rcd_range) + // No meson action! + if (!(target in view(RCD_RANGE, get_turf(chassis)))) + return + if(get_dist(chassis, target) > RCD_RANGE) balloon_alert(source, "out of range!") return - if(!internal_rcd) // if it somehow went missing - internal_rcd = new rcd_type(src) - stack_trace("Exosuit-mounted RCD had no internal RCD!") + initial_location = chassis.loc + ..() // do this now because the do_after can take a while + var/construction_mode = internal_rcd.mode if(deconstruct_active) // deconstruct isn't in the RCD menu so switch it to deconstruct mode and set it back when it's done internal_rcd.mode = RCD_DECONSTRUCT @@ -294,11 +310,13 @@ internal_rcd.mode = construction_mode return TRUE -/obj/item/mecha_parts/mecha_equipment/rcd/attackby(obj/item/attacking_item, mob/user, params) +/obj/item/mecha_parts/mecha_equipment/rcd/interact_with_atom(obj/item/attacking_item, mob/living/user, list/modifiers) + . = NONE if(istype(attacking_item, /obj/item/rcd_upgrade)) internal_rcd.install_upgrade(attacking_item, user) - return - return ..() + return ITEM_INTERACT_SUCCESS + +#undef RCD_RANGE //Dunno where else to put this so shrug /obj/item/mecha_parts/mecha_equipment/ripleyupgrade diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm index c62b2a0d9ce45..c811e94c30948 100644 --- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm @@ -68,6 +68,7 @@ var/obj/projectile/projectile_obj = new projectile(get_turf(src)) projectile_obj.log_override = TRUE //we log being fired ourselves a little further down. projectile_obj.firer = chassis + projectile_obj.fired_from = src // mech = firer, equipment = fired from projectile_obj.aim_projectile(target, source, modifiers, spread) if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways var/mob/living/shooter = source @@ -193,6 +194,7 @@ icon_state = "mecha_honker" energy_drain = 200 equip_cooldown = 150 + projectiles_per_shot = 0 range = MECHA_MELEE|MECHA_RANGED kickback = FALSE mech_flags = EXOSUIT_MODULE_HONK diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index 2bcc17115b060..60b578d0962eb 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -26,6 +26,7 @@ /obj/item/key/janitor desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"." icon_state = "keyjanitor" + icon_angle = 90 force = 2 w_class = WEIGHT_CLASS_SMALL throwforce = 9 diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 779d253f29cc9..79e6b86f54537 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1161,15 +1161,15 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock) return FALSE /obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/replacer) - if(!istype(replacer)) - return FALSE - if(!component_parts || !refill_canister) + if(!istype(replacer) || !component_parts || !refill_canister) return FALSE - if(!panel_open || replacer.works_from_distance) + var/works_from_distance = istype(replacer, /obj/item/storage/part_replacer/bluespace) + + if(!panel_open || works_from_distance) to_chat(user, display_parts(user)) - if(!panel_open && !replacer.works_from_distance) + if(!panel_open && !works_from_distance) return FALSE var/restocked = 0 diff --git a/code/modules/vending/mail.dm b/code/modules/vending/mail.dm index 1e091a3128756..6bc3648b056d1 100644 --- a/code/modules/vending/mail.dm +++ b/code/modules/vending/mail.dm @@ -46,11 +46,13 @@ /obj/machinery/mailsorter/proc/get_unload_turf() return get_step(src, output_dir) +/// Opening the maintenance panel. /obj/machinery/mailsorter/screwdriver_act(mob/living/user, obj/item/tool) default_deconstruction_screwdriver(user, "[base_icon_state]-off", base_icon_state, tool) update_appearance(UPDATE_OVERLAYS) return ITEM_INTERACT_SUCCESS +/// Deconstructing the mail sorter. /obj/machinery/mailsorter/crowbar_act(mob/living/user, obj/item/tool) default_deconstruction_crowbar(tool) return ITEM_INTERACT_SUCCESS @@ -63,11 +65,15 @@ . += span_notice("Alt-click to rotate the output direction.") /obj/machinery/mailsorter/Destroy() + QDEL_LIST(mail_list) + . = ..() + +/obj/machinery/mailsorter/on_deconstruction(disassembled) drop_all_mail() . = ..() -/// Drops all enevlopes on the machine turf. Only occurs when the machine is broken. -/obj/machinery/mailsorter/proc/drop_all_mail(damage_flag) +/// Drops all enevlopes on the machine turf. +/obj/machinery/mailsorter/proc/drop_all_mail() if(!isturf(get_turf(src))) QDEL_LIST(mail_list) return @@ -90,10 +96,6 @@ /obj/machinery/mailsorter/proc/accept_check(obj/item/weapon) var/static/list/accepted_items = list( /obj/item/mail, - /obj/item/mail/envelope, - /obj/item/mail/junkmail, - /obj/item/mail/mail_strike, - /obj/item/mail/traitor, /obj/item/paper, ) return is_type_in_list(weapon, accepted_items) @@ -151,10 +153,13 @@ if (some_recipient) var/datum/job/recipient_job = some_recipient.assigned_role var/datum/job_department/primary_department = recipient_job.departments_list?[1] - var/datum/job_department/main_department = primary_department.department_name - if (main_department == sorting_dept) - sorted_mail.Add(some_mail) - sorted ++ + if (primary_department == null) // permabrig is temporary, tide is forever + unable_to_sort ++ + else + var/datum/job_department/main_department = primary_department.department_name + if (main_department == sorting_dept) + sorted_mail.Add(some_mail) + sorted ++ else unable_to_sort ++ if (length(sorted_mail) == 0) diff --git a/html/changelogs/AutoChangeLog-pr-88095.yml b/html/changelogs/AutoChangeLog-pr-88095.yml deleted file mode 100644 index e086157571ec9..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88095.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "NecromancerAnne (code), orcacora (sprites)" -delete-after: True -changes: - - rscadd: "Adds NT BR-38 Battle Rifles. A hybrid weapon. Find it in your local armory and cargo catalogue today. (Keep away from EMPs)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88201.yml b/html/changelogs/AutoChangeLog-pr-88201.yml deleted file mode 100644 index 9a4d04ed56dd7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88201.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "SmArtKar, LemonInTheDark" -delete-after: True -changes: - - rscadd: "Changed how spraycans color items - \"old\" mode is still availible via right click." - - refactor: "Refactored how some items and effects color things so that they look prettier." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88376.yml b/html/changelogs/AutoChangeLog-pr-88376.yml deleted file mode 100644 index a2b835a41e5b7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88376.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Majkl-J" -delete-after: True -changes: - - bugfix: "Overcharged SMESes no longer spam runtime when timers chug up" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88378.yml b/html/changelogs/AutoChangeLog-pr-88378.yml deleted file mode 100644 index f7379479edc0a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88378.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "EnterTheJake" -delete-after: True -changes: - - bugfix: "temporary blocks such as blade heretic orbiting knives properly stop body throws." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88394.yml b/html/changelogs/AutoChangeLog-pr-88394.yml deleted file mode 100644 index b0a9c845b8b61..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88394.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Runi-c" -delete-after: True -changes: - - balance: "medical doctors can buy Reagent Dartgun from traitor uplink" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88404.yml b/html/changelogs/AutoChangeLog-pr-88404.yml new file mode 100644 index 0000000000000..0e26dc409bf11 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88404.yml @@ -0,0 +1,4 @@ +author: "lovegreenstuff" +delete-after: True +changes: + - rscadd: "catalyst function for plumbing reaction chambers" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88453.yml b/html/changelogs/AutoChangeLog-pr-88453.yml deleted file mode 100644 index 9b393cf047bb4..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88453.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "grungussuss" -delete-after: True -changes: - - bugfix: "fixed access on birdshot engi mulebot delivery window" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88490.yml b/html/changelogs/AutoChangeLog-pr-88490.yml deleted file mode 100644 index cf22aadacd500..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88490.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "Actually fixed alt-clicking aquariums." \ No newline at end of file diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml index 4508642bfe03f..d3055633ddf57 100644 --- a/html/changelogs/archive/2024-12.yml +++ b/html/changelogs/archive/2024-12.yml @@ -332,3 +332,255 @@ - bugfix: Fixed moths only being able to fly if they spawn in zero gravity grungussuss: - bugfix: '*me emote works again' +2024-12-14: + EnterTheJake: + - bugfix: temporary blocks such as blade heretic orbiting knives properly stop body + throws. + Ghommie: + - bugfix: Actually fixed alt-clicking aquariums. + Majkl-J: + - bugfix: Aloe cream no longer catches fire seconds after finishing baking + - bugfix: Overcharged SMESes no longer spam runtime when timers chug up + NecromancerAnne (code), orcacora (sprites): + - rscadd: Adds NT BR-38 Battle Rifles. A hybrid weapon. Find it in your local armory + and cargo catalogue today. (Keep away from EMPs) + Runi-c: + - balance: medical doctors can buy Reagent Dartgun from traitor uplink + SmArtKar: + - bugfix: Fixes primed stingbangs being invisible + SmArtKar, LemonInTheDark: + - rscadd: Changed how spraycans color items - "old" mode is still availible via + right click. + - refactor: Refactored how some items and effects color things so that they look + prettier. + SyncIt21: + - code_imp: improved code for leaning + distributivgesetz: + - code_imp: Fixed rare cases where moving an object somewhere could silently fail, + but still run unintended code. Report any weird issues on Github + grungussuss: + - bugfix: fixed regal rat attack logic + - bugfix: fixed access on birdshot engi mulebot delivery window + mcbalaam: + - qol: Now all antagonists are visible to an admin in the orbit menu! + norsvenska: + - spellcheck: The Lance and Raven shuttle airlocks are now properly labelled emergency + airlocks, rather than emegency airlocks. + - spellcheck: The radio jammer now releases disruptor waves, rather than distruptor + waves. + timothymtorres: + - sound: Add water sound to sinks +2024-12-15: + Ghommie: + - bugfix: Fixed the displayed stats when examining fishing rods twice. + LT3: + - bugfix: Fixed unconstructed solar panels on Nebulastation port aft solars + Melbert: + - rscadd: Adds Syndol to the chemical kit, an addictive hallucinogen that applies + bonus effects when security officers, assistants, or clowns are exposed. + - bugfix: Metalgen works as a lockpick; igniting a crate metalgen'd into plasma + will properly drop its contents. + - code_imp: Hiding stuff in food should generally work more consistently now. + - bugfix: Fixes players not doing the "searching for item" do-after for items hidden + in food. + - qol: When dragging an item (like, with your mouse cursor. not physically), your + cursor updates when hovering humans or cyborgs to indicate you're hovering over + a human or cyborg. + Paxilmaniac: + - bugfix: Fixes resin sprayers not working if the target is more than one tile away + from you + necromanceranne: + - code_imp: Various mob attack procs are treated as unarmed attacks as a baseline + assumption, rather than melee attacks. + timothymtorres: + - code_imp: Improve looping sounds to allow nested and non-associative lists +2024-12-16: + JoshAdamPowell: + - map: In the new year's budget the syndicate have decided that chemists need beakers + to do their job properly. + Melbert: + - qol: Treatment message now better reflects what you're doing ("suturing", "applying", + etc) + - bugfix: Gauze is now stickier (and will actually apply to bodyparts) + OrionTheFox: + - bugfix: fixed the outdated N-Spect description falsely claiming it can scan people. + It can't. Nanotrasen denies all claims it ever broke sapient right to privacy + by giving crew full-body scanners in a handheld format. + SmArtKar: + - rscadd: Rave and plasma stabilizer MODules now utilize theme-specific visors + - qol: Jetpacks should ACTUALLY feel better now + SyncIt21: + - bugfix: Turbine converts energy to power correctly & shows correct reading with + multitool + - refactor: turbine code has been overall improved. report bugs on github + tontyGH: + - bugfix: Underlining your messages in loud mode shouldn't break anymore +2024-12-17: + Melbert: + - balance: Tasers are now more realistic + - rscdel: Electrodes are no longer in the hallucination projectile pool + OrionTheFox: + - image: resprited the Blood Cult Archives/Altar (the two summoning tables) + SyncIt21: + - code_imp: improved code for RPED + - refactor: RPED attack chain has been refactored. Reports bugs on github +2024-12-18: + SmArtKar: + - rscadd: Added new (purely visual) animations to sharp and pointy items. + - rscadd: Certain items, like knives and swords, now have a secondary stabbing attack. + - balance: Spears are now pointy and no longer act as oversized knives. + - balance: Structure damage is now affected by attacking item's AP. + - bugfix: You will now see the same attack verb in chat as everyone else. +2024-12-19: + 00-Steven: + - bugfix: Custom emotes done via the custom emote keybind default to both audible + and visible again. + Darkened-Earth: + - bugfix: NanoTrasen has shown a xenobiology containment engineer for Delta class + stations a very important lesson between inlets and outlets + Hatterhat: + - qol: You can now pull singular trophies off kinetic crushers with RMB. + - qol: Kinetic crushers now have screentips informing people that they can remove + trophies via empty hand (RMB) or crowbar (LMB). + Melbert: + - bugfix: Gibs get bulk cleaned if you clean the turf again + - refactor: Changed how things determine "I can be bulk cleaned if I clean the turf + underneath me", let me know if you notice anything not getting bulk cleaned + or weird things getting bulk cleaned + SmArtKar: + - bugfix: Fixed a Honkerblast 5000 runtime + - bugfix: Fixed mech KA AOE dealing full damage in pressurized environments + - bugfix: Fixed c38 not misfiring when chambered in c357 + - qol: Resist button now has visible feedback. + - qol: Readjusted UI layout. + - image: Completely redrawn Midnight and Midnight-derived UIs! + mikederkan: + - bugfix: fixes a minor spelling/grammatical error in the Funeral Supply and Religious + Supplies crates. + timothymtorres: + - sound: Add fishtank looping sounds to cryo cells when in use. Sound is from https://freesound.org/people/DudeAwesome/sounds/386023/ + - sound: Add dice rolling sound + tontyGH: + - bugfix: /datum/gas_machine_connector will abstract_move() if their parent also + abstract_move()s, preventing a runtime +2024-12-20: + Melbert: + - bugfix: Meshes should work better + - bugfix: Medical item usage is correctly blackbox logged + - bugfix: Double balloon alerts from medical item usage + - bugfix: Poultice works on dead people + SmArtKar: + - bugfix: Fixed an airlock unlocking runtime +2024-12-21: + Deadgebert: + - balance: Synthflesh required for unhusking now between 60u and 100u depending + on purity. + Melbert: + - bugfix: Lobby button sprites +2024-12-22: + Absolucy: + - rscadd: Added a notice for clients on BYOND 516 that 516 is still in beta, and + UI elements may not be fully compatible yet. + - qol: Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't + fuck you over nearly as much if you're relying on the healing. + Cruix: + - rscadd: Shuttle navigation computers now show the location of airlocks, turrets, + and the shuttle control consoles on the ship outline while placing a custom + landing location. + Darkened-Earth: + - bugfix: Alarm ranchers have wrangled up a rogue fire alarm in Meta class station + custom offices and relocated it to a safe habitat + Gaxeer: + - bugfix: modular computer laptops can now be interacted with RMB, instead of picked + up + - qol: modular computer laptops can now be interacted with RMB when open, or opened + with RMB when closed. Also screentips for this added + Kocma-san: + - rscadd: added a "print" button to the request manager + - rscadd: admin fax can now send exotic items + - rscadd: added "Auto-print Faxes" button to the request manager, which allows you + to enable automatic printing of requests on the admin fax + - bugfix: fixed a bug where pressing the print button would cause the receive animation + to appear on all admin faxes. + - bugfix: fix radio sound output when receiving a message + - sound: the sound of receiving your own messages over the radio is no longer played + Melbert: + - rscadd: Nitroglycerin now heals heart damage. + SmArtKar: + - bugfix: Fixed floodlights not being affected by spraycan painting + - balance: Mech-mounted RCD no longer has wallhacks + - balance: Reinforced walls now take double the time to get deconstructed when using + an RCD + - map: Added a Condi-Master to Birdshot's bar + - bugfix: Fixed shoes slot being semi-transparent when you have digitigrade legs + - spellcheck: Bioscrambler no longer informs you about "your armor softening the + blow!" + - bugfix: Chemmaster no longer rounds reagent amounts when trying to transfer a + custom amount + Sparex: + - map: Added lifting benches/workout benches to the fitness room for IceboxStation.dmm + SyncIt21: + - bugfix: fixed runtime when sealing mecha cabin with air tank installed + - bugfix: personal ordered crates can be unlocked & relocked as many times again + after the 1st attempt + - bugfix: greyscale modify menu has better validation for player entered colours + - code_imp: improved code for mecha rcd + - bugfix: mecha rcd will cancel its action when the mech is rotated or moves during + the action + TealSeer: + - qol: Atmos devices like valves and pumps can now be renamed with a pen. + UnokiAs: + - rscadd: Make spears able to break open lockers in melee. + grungussuss: + - rscdel: screenshake from explosions will no longer happen when it's really far + and not on a station area (turf) + mcbalaam: + - bugfix: The mail sorter no longer runtimes processing assistant mail + timothymtorres: + - spellcheck: Update teleporter machine desc to be accurate + - spellcheck: Fix holodeck emag message claiming to increase power + - rscadd: Add power efficiency when stasis bed stock parts are upgraded +2024-12-23: + AyIong: + - bugfix: Fixed scrollbar colors and background position in TGUI on Byond 516 + Ben10Omintrix: + - rscadd: adds flora-turtles. obtainable through cargo or by fishing from the hydroponics + tray + - qol: holding shift and hovering over your pet will display a list of commands + you can click from + - bugfix: fixes the fishing pet command not working + DATA-xPUNGED: + - map: removed the sec record computer from the Icemoon Listening Post. + Melbert: + - balance: Carpenter hammer force 20 -> 17 + - balance: Carpenter hammer throwforce 20 -> 14 + - balance: Carpenter hammer demo mod 1.25 -> 1.15 + NecromancerAnne (code), SmArtKar (sprites): + - balance: Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo. + - balance: Basic ammo for either weapon comes in weapon cases of three extra magazines + at an affordable price. + - balance: Donksoft Toy Pistols from the uplink are much stronger than their standard + counterparts, but now priced at 6 TC. + - balance: Makarovs and Toy pistols have a magazine capacity of 12 rounds. + - balance: Gun/Ammo cases from the traitor uplink can be destroyed by activating + the disposal bomb. Press Alt-Right-Click on the case to start the timer. + Rhials: + - rscadd: Nukie uplinks now offer an OG-style nuke pinpointer in their Badassery + shop section. + TealSeer: + - qol: The time until the server reboots is now visible in the status tab. + - admin: Added a cancel reboot verb to the server tab. + grungussuss: + - code_imp: removed an extra proc override in digitigrade legs preference logic + code +2024-12-24: + SmArtKar: + - admin: Admins can now return the shuttle back to the station without ending the + round + - bugfix: Fixed moth wing stabilization working in zero-g as long as you keep moving + - bugfix: Fixed showers not passively washing objects + SyncIt21: + - bugfix: rolling tables can be rolled up again + carlarctg: + - rscadd: Kissing while you have the ink infusion ability off cooldown gives you + an ink kiss diff --git a/html/statbrowser.css b/html/statbrowser.css index d8c0f92b626f4..d49cb3d6e2667 100644 --- a/html/statbrowser.css +++ b/html/statbrowser.css @@ -1,3 +1,13 @@ +.light:root { + --scrollbar-base: #f2f2f2; + --scrollbar-thumb: #a7a7a7; +} + +html, +body { + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-base); +} + body { font-family: Verdana, Geneva, Tahoma, sans-serif; font-size: 12px; @@ -177,9 +187,16 @@ img { margin-bottom: 1em; } -/* Dark theme colors */ +/** + * MARK: Dark theme colors + */ +.dark:root { + --scrollbar-base: #151515; + --scrollbar-thumb: #363636; +} + body.dark { - background-color: #131313; + background-color: #151515; color: #b2c4dd; scrollbar-base-color: #1c1c1c; scrollbar-face-color: #3b3b3b; @@ -201,7 +218,7 @@ body.dark { } .dark #menu { - background-color: #131313; + background-color: #151515; } .dark #menu.tabs-classic .button.active { diff --git a/html/statbrowser.js b/html/statbrowser.js index 3fe115943a702..d33713f65f746 100644 --- a/html/statbrowser.js +++ b/html/statbrowser.js @@ -705,9 +705,11 @@ function draw_verbs(cat) { function set_theme(which) { if (which == "light") { document.body.className = ""; + document.documentElement.className = 'light'; set_style_sheet("browserOutput_white"); } else if (which == "dark") { document.body.className = "dark"; + document.documentElement.className = 'dark'; set_style_sheet("browserOutput"); } } diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi index 41bdf992bbf41..12bbd4788f0f9 100644 Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ diff --git a/icons/effects/mouse_pointers/pet_paw.dmi b/icons/effects/mouse_pointers/pet_paw.dmi new file mode 100644 index 0000000000000..a4443a6896721 Binary files /dev/null and b/icons/effects/mouse_pointers/pet_paw.dmi differ diff --git a/icons/effects/nav_computer_indicators.dmi b/icons/effects/nav_computer_indicators.dmi new file mode 100644 index 0000000000000..ff6a616c2968c Binary files /dev/null and b/icons/effects/nav_computer_indicators.dmi differ diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi index 23865a80f0354..6a54c8e4bb36d 100644 Binary files a/icons/hud/64x16_actions.dmi and b/icons/hud/64x16_actions.dmi differ diff --git a/icons/hud/radial_pets.dmi b/icons/hud/radial_pets.dmi new file mode 100644 index 0000000000000..a9ce7a1d0062c Binary files /dev/null and b/icons/hud/radial_pets.dmi differ diff --git a/icons/hud/screen_alien.dmi b/icons/hud/screen_alien.dmi index 5f3806dc7bb57..e1f513f5b8fdc 100644 Binary files a/icons/hud/screen_alien.dmi and b/icons/hud/screen_alien.dmi differ diff --git a/icons/hud/screen_clockwork.dmi b/icons/hud/screen_clockwork.dmi index 809dfe8f1a833..d620da7e7903e 100644 Binary files a/icons/hud/screen_clockwork.dmi and b/icons/hud/screen_clockwork.dmi differ diff --git a/icons/hud/screen_detective.dmi b/icons/hud/screen_detective.dmi index 818bbcfc78b6f..0516ce6f079b8 100644 Binary files a/icons/hud/screen_detective.dmi and b/icons/hud/screen_detective.dmi differ diff --git a/icons/hud/screen_glass.dmi b/icons/hud/screen_glass.dmi index 3cf16106cd868..08db68e4d247b 100644 Binary files a/icons/hud/screen_glass.dmi and b/icons/hud/screen_glass.dmi differ diff --git a/icons/hud/screen_midnight.dmi b/icons/hud/screen_midnight.dmi index 5de157be030f4..d748823d4ce4c 100644 Binary files a/icons/hud/screen_midnight.dmi and b/icons/hud/screen_midnight.dmi differ diff --git a/icons/hud/screen_operative.dmi b/icons/hud/screen_operative.dmi index 5fbaa7c5d5d24..8719ef18d396c 100644 Binary files a/icons/hud/screen_operative.dmi and b/icons/hud/screen_operative.dmi differ diff --git a/icons/hud/screen_plasmafire.dmi b/icons/hud/screen_plasmafire.dmi index d8b669c96c805..652beabadd806 100644 Binary files a/icons/hud/screen_plasmafire.dmi and b/icons/hud/screen_plasmafire.dmi differ diff --git a/icons/hud/screen_retro.dmi b/icons/hud/screen_retro.dmi index f3621569d76d1..21275e5320cf3 100644 Binary files a/icons/hud/screen_retro.dmi and b/icons/hud/screen_retro.dmi differ diff --git a/icons/hud/screen_slimecore.dmi b/icons/hud/screen_slimecore.dmi index a6160087ab889..790575deae7df 100644 Binary files a/icons/hud/screen_slimecore.dmi and b/icons/hud/screen_slimecore.dmi differ diff --git a/icons/hud/screen_trasenknox.dmi b/icons/hud/screen_trasenknox.dmi index 4e17feb5211d0..bb6f82caafb2c 100644 Binary files a/icons/hud/screen_trasenknox.dmi and b/icons/hud/screen_trasenknox.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_clothing.dmi b/icons/mob/clothing/modsuit/mod_clothing.dmi index 815983f7094e6..8a39f37bf0b1f 100644 Binary files a/icons/mob/clothing/modsuit/mod_clothing.dmi and b/icons/mob/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_modules.dmi b/icons/mob/clothing/modsuit/mod_modules.dmi index 3c68a87d5491d..d6dfa4c50a212 100644 Binary files a/icons/mob/clothing/modsuit/mod_modules.dmi and b/icons/mob/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index b7e8642387228..512a48e813b0d 100644 Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ diff --git a/icons/mob/simple/turtle_trees.dmi b/icons/mob/simple/turtle_trees.dmi new file mode 100644 index 0000000000000..3e5cf1d4065fa Binary files /dev/null and b/icons/mob/simple/turtle_trees.dmi differ diff --git a/icons/obj/antags/cult/structures.dmi b/icons/obj/antags/cult/structures.dmi index 982742e876492..e42ce6c2202f4 100644 Binary files a/icons/obj/antags/cult/structures.dmi and b/icons/obj/antags/cult/structures.dmi differ diff --git a/icons/obj/machines/engine/turbine.dmi b/icons/obj/machines/engine/turbine.dmi index 1ae45eb2a1b1f..afd9839af1341 100644 Binary files a/icons/obj/machines/engine/turbine.dmi and b/icons/obj/machines/engine/turbine.dmi differ diff --git a/icons/obj/storage/case.dmi b/icons/obj/storage/case.dmi index 94b7251f93f46..65b40a403ea2e 100644 Binary files a/icons/obj/storage/case.dmi and b/icons/obj/storage/case.dmi differ diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi index c65f6d0e9fb80..628b271d423e5 100644 Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ diff --git a/icons/ui/inventory/back.png b/icons/ui/inventory/back.png index 210045c1c669e..e8226bc55e32a 100644 Binary files a/icons/ui/inventory/back.png and b/icons/ui/inventory/back.png differ diff --git a/icons/ui/inventory/belt.png b/icons/ui/inventory/belt.png index 9bec60c7bb216..ef3e5586e476e 100644 Binary files a/icons/ui/inventory/belt.png and b/icons/ui/inventory/belt.png differ diff --git a/icons/ui/inventory/collar.png b/icons/ui/inventory/collar.png index a7d1b5278497e..2e8c875851ed8 100644 Binary files a/icons/ui/inventory/collar.png and b/icons/ui/inventory/collar.png differ diff --git a/icons/ui/inventory/ears.png b/icons/ui/inventory/ears.png index 3c3ba34929495..19752ed10d713 100644 Binary files a/icons/ui/inventory/ears.png and b/icons/ui/inventory/ears.png differ diff --git a/icons/ui/inventory/glasses.png b/icons/ui/inventory/glasses.png index 3dff0cd5992d9..9c8b0950abcab 100644 Binary files a/icons/ui/inventory/glasses.png and b/icons/ui/inventory/glasses.png differ diff --git a/icons/ui/inventory/gloves.png b/icons/ui/inventory/gloves.png index 87c829eb626ea..f5c9de27b8efb 100644 Binary files a/icons/ui/inventory/gloves.png and b/icons/ui/inventory/gloves.png differ diff --git a/icons/ui/inventory/hand_l.png b/icons/ui/inventory/hand_l.png index ab9535180d6a0..8923d81b5b754 100644 Binary files a/icons/ui/inventory/hand_l.png and b/icons/ui/inventory/hand_l.png differ diff --git a/icons/ui/inventory/hand_r.png b/icons/ui/inventory/hand_r.png index e6b0d3cb9daf2..b3b16b4e0cdf9 100644 Binary files a/icons/ui/inventory/hand_r.png and b/icons/ui/inventory/hand_r.png differ diff --git a/icons/ui/inventory/head.png b/icons/ui/inventory/head.png index 2d1b65159bb85..650be20fe6307 100644 Binary files a/icons/ui/inventory/head.png and b/icons/ui/inventory/head.png differ diff --git a/icons/ui/inventory/id.png b/icons/ui/inventory/id.png index 397b30ca42f84..0505f38bc19eb 100644 Binary files a/icons/ui/inventory/id.png and b/icons/ui/inventory/id.png differ diff --git a/icons/ui/inventory/mask.png b/icons/ui/inventory/mask.png index dbaa80ec1ac74..3690e6eca76c0 100644 Binary files a/icons/ui/inventory/mask.png and b/icons/ui/inventory/mask.png differ diff --git a/icons/ui/inventory/neck.png b/icons/ui/inventory/neck.png index 74cf1e33eafae..a6018e149c789 100644 Binary files a/icons/ui/inventory/neck.png and b/icons/ui/inventory/neck.png differ diff --git a/icons/ui/inventory/pocket.png b/icons/ui/inventory/pocket.png index 2e08ed41b05a2..0d31594a5a553 100644 Binary files a/icons/ui/inventory/pocket.png and b/icons/ui/inventory/pocket.png differ diff --git a/icons/ui/inventory/shoes.png b/icons/ui/inventory/shoes.png index b1b19c633972d..170f81b5daa5a 100644 Binary files a/icons/ui/inventory/shoes.png and b/icons/ui/inventory/shoes.png differ diff --git a/icons/ui/inventory/suit.png b/icons/ui/inventory/suit.png index 71b877677febc..e3122ed47dc3c 100644 Binary files a/icons/ui/inventory/suit.png and b/icons/ui/inventory/suit.png differ diff --git a/icons/ui/inventory/suit_storage.png b/icons/ui/inventory/suit_storage.png index 0dd12ed4f8f16..f35bd3783f7a7 100644 Binary files a/icons/ui/inventory/suit_storage.png and b/icons/ui/inventory/suit_storage.png differ diff --git a/icons/ui/inventory/uniform.png b/icons/ui/inventory/uniform.png index 56576429e8d0a..7f5951e3a0f4d 100644 Binary files a/icons/ui/inventory/uniform.png and b/icons/ui/inventory/uniform.png differ diff --git a/sound/attributions.txt b/sound/attributions.txt index a6cbf21f8369b..bd2e408c6c558 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -23,7 +23,6 @@ champagne_pop.ogg is credited to ultradust on freesound https://freesound.org/pe can_open.ogg adapted from https://freesound.org/people/MaxDemianAGL/sounds/130031/ can_shake.ogg adapted from https://freesound.org/people/mcmast/sounds/456703/ - splatter.ogg adapted from https://freesound.org/people/Rocktopus/sounds/233418/ hohoho.ogg and hehe.ogg are cut from a recording by Nanakisan on freesound: https://freesound.org/people/Nanakisan/sounds/253534/ mbox_full.ogg and mbox_end.ogg make use of The Ragtime Drummer by James Lent, in the public domain @@ -181,8 +180,6 @@ https://freesound.org/people/shw489/sounds/234389/ soup_boil1.ogg through soup_boil5.ogg and soup_boil_end.ogg are taken from Boiling Soup from Freesoung.org (CC4) and converted to OGG / split apart (but is otherwise unchanged): https://freesound.org/people/jorickhoofd/sounds/632783/ - - valve_opening.ogg was made by mixing water flowing samples from: https://freesound.org/people/scriotxstudios/sounds/349111/?attribution=1 and squeaky scrape sound from: https://freesound.org/people/Department64/sounds/669028/ which was modified with lower pitch @@ -191,7 +188,6 @@ liquid_pour2.ogg and liquid_pour3.ogg were cut from https://freesound.org/people/MattRuthSound/sounds/561896/ https://freesound.org/people/MattRuthSound/sounds/561895/ - roaring_fire.ogg made from: 10835 big fire loop.wav by Robinhood76 -- https://freesound.org/s/612277/ -- License: Attribution NonCommercial 4.0 fire_puff made from: Bonfire Being Lit by samararaine -- https://freesound.org/s/186374/ -- License: Creative Commons 0 @@ -208,8 +204,6 @@ Bottle Tap.wav by alex_alexalex -- https://freesound.org/s/395492/ -- License: A beaker_place.ogg was made by cutting and lowering pitch: place glass object.wav by milpower -- https://freesound.org/s/353105/ -- License: Creative Commons 0 - - glass_reverse.ogg is adapted from a combination of: https://freesound.org/people/C_Rogers/sounds/203368/ -- glass-shattering-hit_01.ogg by C_Rogers on freesound.org (CC0) https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading of Shattering Glass (Small) by Czarcazas -- https://freesound.org/s/330800/ -- License: Attribution 3.0 @@ -217,3 +211,10 @@ https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading o sound/effects/bonk.ogg - recorded by oranges on a coke zero bottle, edited by ninjanomnom, released to public domain sound\items\weapons\hammer_death_scream.ogg - Undefeatablesos' scream recorded by Niron3206, edited by Niron3206, License: Creative Commons 0 + +sound/machines/sink-faucet.ogg -- https://freesound.org/people/FOSSarts/sounds/740086/ -- by FOSSarts (CC0) + +cryo_1.ogg, cryo_2.ogg, cryo_3.ogg, cryo_4.ogg, cryo_5.ogg, cryo_6.ogg, cryo_7.ogg, cryo_8.ogg, cryo_9.ogg, cryo_10.ogg: +converted to OGG / split apart (but is otherwise unchanged) -- original from https://freesound.org/people/DudeAwesome/sounds/386023/ by DudeAwesome -- License: CC BY 4.0 + +sound/items/dice_roll.ogg -- https://freesound.org/people/Crovic/sounds/661935/ -- by Crovic (CC0) diff --git a/sound/items/dice_roll.ogg b/sound/items/dice_roll.ogg new file mode 100644 index 0000000000000..71048df839b4d Binary files /dev/null and b/sound/items/dice_roll.ogg differ diff --git a/sound/machines/cryo/cryo_1.ogg b/sound/machines/cryo/cryo_1.ogg new file mode 100644 index 0000000000000..daad20b1f4934 Binary files /dev/null and b/sound/machines/cryo/cryo_1.ogg differ diff --git a/sound/machines/cryo/cryo_10.ogg b/sound/machines/cryo/cryo_10.ogg new file mode 100644 index 0000000000000..eedc518222f9b Binary files /dev/null and b/sound/machines/cryo/cryo_10.ogg differ diff --git a/sound/machines/cryo/cryo_2.ogg b/sound/machines/cryo/cryo_2.ogg new file mode 100644 index 0000000000000..7375eff09ebec Binary files /dev/null and b/sound/machines/cryo/cryo_2.ogg differ diff --git a/sound/machines/cryo/cryo_3.ogg b/sound/machines/cryo/cryo_3.ogg new file mode 100644 index 0000000000000..4cbb033daff51 Binary files /dev/null and b/sound/machines/cryo/cryo_3.ogg differ diff --git a/sound/machines/cryo/cryo_4.ogg b/sound/machines/cryo/cryo_4.ogg new file mode 100644 index 0000000000000..6dff7c7a1c17e Binary files /dev/null and b/sound/machines/cryo/cryo_4.ogg differ diff --git a/sound/machines/cryo/cryo_5.ogg b/sound/machines/cryo/cryo_5.ogg new file mode 100644 index 0000000000000..ff11f6d173d3f Binary files /dev/null and b/sound/machines/cryo/cryo_5.ogg differ diff --git a/sound/machines/cryo/cryo_6.ogg b/sound/machines/cryo/cryo_6.ogg new file mode 100644 index 0000000000000..575b727958ba1 Binary files /dev/null and b/sound/machines/cryo/cryo_6.ogg differ diff --git a/sound/machines/cryo/cryo_7.ogg b/sound/machines/cryo/cryo_7.ogg new file mode 100644 index 0000000000000..f6fcb2bd0e36c Binary files /dev/null and b/sound/machines/cryo/cryo_7.ogg differ diff --git a/sound/machines/cryo/cryo_8.ogg b/sound/machines/cryo/cryo_8.ogg new file mode 100644 index 0000000000000..2884ffa5bd2c6 Binary files /dev/null and b/sound/machines/cryo/cryo_8.ogg differ diff --git a/sound/machines/cryo/cryo_9.ogg b/sound/machines/cryo/cryo_9.ogg new file mode 100644 index 0000000000000..c650837ed8b1e Binary files /dev/null and b/sound/machines/cryo/cryo_9.ogg differ diff --git a/sound/machines/sink-faucet.ogg b/sound/machines/sink-faucet.ogg new file mode 100644 index 0000000000000..7102a3940308f Binary files /dev/null and b/sound/machines/sink-faucet.ogg differ diff --git a/sound/voice/repairbot/brick.ogg b/sound/mobs/non-humanoids/repairbot/brick.ogg similarity index 100% rename from sound/voice/repairbot/brick.ogg rename to sound/mobs/non-humanoids/repairbot/brick.ogg diff --git a/sound/voice/repairbot/cantanymore.ogg b/sound/mobs/non-humanoids/repairbot/cantanymore.ogg similarity index 100% rename from sound/voice/repairbot/cantanymore.ogg rename to sound/mobs/non-humanoids/repairbot/cantanymore.ogg diff --git a/sound/voice/repairbot/entropy.ogg b/sound/mobs/non-humanoids/repairbot/entropy.ogg similarity index 100% rename from sound/voice/repairbot/entropy.ogg rename to sound/mobs/non-humanoids/repairbot/entropy.ogg diff --git a/sound/voice/repairbot/fixit.ogg b/sound/mobs/non-humanoids/repairbot/fixit.ogg similarity index 100% rename from sound/voice/repairbot/fixit.ogg rename to sound/mobs/non-humanoids/repairbot/fixit.ogg diff --git a/sound/voice/repairbot/fixtouch.ogg b/sound/mobs/non-humanoids/repairbot/fixtouch.ogg similarity index 100% rename from sound/voice/repairbot/fixtouch.ogg rename to sound/mobs/non-humanoids/repairbot/fixtouch.ogg diff --git a/sound/voice/repairbot/passionproject.ogg b/sound/mobs/non-humanoids/repairbot/passionproject.ogg similarity index 100% rename from sound/voice/repairbot/passionproject.ogg rename to sound/mobs/non-humanoids/repairbot/passionproject.ogg diff --git a/sound/voice/repairbot/patchingholes.ogg b/sound/mobs/non-humanoids/repairbot/patchingholes.ogg similarity index 100% rename from sound/voice/repairbot/patchingholes.ogg rename to sound/mobs/non-humanoids/repairbot/patchingholes.ogg diff --git a/sound/voice/repairbot/pay.ogg b/sound/mobs/non-humanoids/repairbot/pay.ogg similarity index 100% rename from sound/voice/repairbot/pay.ogg rename to sound/mobs/non-humanoids/repairbot/pay.ogg diff --git a/sound/voice/repairbot/strings.ogg b/sound/mobs/non-humanoids/repairbot/strings.ogg similarity index 100% rename from sound/voice/repairbot/strings.ogg rename to sound/mobs/non-humanoids/repairbot/strings.ogg diff --git a/strings/tips.txt b/strings/tips.txt index 5f33cb87bc7fe..e6fc672c5c494 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -227,6 +227,7 @@ Basketball requires skill. Spinning drains stamina, reduces accuracy, but gives Basketball requires teamwork. Passing the ball with LMB is instant and costs no stamina. Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it. Different weapons have different strengths. Some weapons, such as spears, floor tiles, and throwing stars, deal more damage when thrown compared to when attacked normally. +Spears are capable of breaking into secure lockers by striking them in melee! Don't be afraid to ask for help, whether from your peers or from admins. Experiment with different setups of the Supermatter engine to maximize output, but don't risk the crew's safety to do so! Felinids get temporarily distracted by laser pointers. Use this to your advantage when being pursued by one. diff --git a/tgstation.dme b/tgstation.dme index b68ce9bc9d6c7..0080adde3560a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -187,6 +187,7 @@ #include "code\__DEFINES\pronouns.dm" #include "code\__DEFINES\qdel.dm" #include "code\__DEFINES\quirks.dm" +#include "code\__DEFINES\radial_defines.dm" #include "code\__DEFINES\radiation.dm" #include "code\__DEFINES\radio.dm" #include "code\__DEFINES\radioactive_nebula.dm" @@ -1047,6 +1048,7 @@ #include "code\datums\components\ai_has_target_timer.dm" #include "code\datums\components\ai_listen_to_weather.dm" #include "code\datums\components\ai_retaliate_advanced.dm" +#include "code\datums\components\alternative_sharpness.dm" #include "code\datums\components\amputating_limbs.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\appearance_on_aggro.dm" @@ -1535,6 +1537,7 @@ #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\muffles_speech.dm" +#include "code\datums\elements\nav_computer_icon.dm" #include "code\datums\elements\nerfed_pulling.dm" #include "code\datums\elements\no_crit_hitting.dm" #include "code\datums\elements\noisy_movement.dm" @@ -5159,6 +5162,9 @@ #include "code\modules\mob\living\basic\trooper\syndicate.dm" #include "code\modules\mob\living\basic\trooper\trooper.dm" #include "code\modules\mob\living\basic\trooper\trooper_ai.dm" +#include "code\modules\mob\living\basic\turtle\turtle.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ability.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" @@ -5523,6 +5529,7 @@ #include "code\modules\photography\photos\photo.dm" #include "code\modules\plumbing\ducts.dm" #include "code\modules\plumbing\plumbers\_plumb_machinery.dm" +#include "code\modules\plumbing\plumbers\_plumb_reagents.dm" #include "code\modules\plumbing\plumbers\acclimator.dm" #include "code\modules\plumbing\plumbers\bottler.dm" #include "code\modules\plumbing\plumbers\destroyer.dm" @@ -5839,6 +5846,7 @@ #include "code\modules\research\designs.dm" #include "code\modules\research\destructive_analyzer.dm" #include "code\modules\research\experimentor.dm" +#include "code\modules\research\part_replacer.dm" #include "code\modules\research\rdconsole.dm" #include "code\modules\research\rdmachines.dm" #include "code\modules\research\research_disk.dm" diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 2308d720436c8..b3cdfa6e88074 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -1182,10 +1182,18 @@ $border-width-px: $border-width * 1px; } .chat_alert_#{$color-name} .minor_announcement_text { - background-color: darken(map.get($alert-stripe-colors, $color-name), 5); + background-color: color.adjust( + map.get($alert-stripe-colors, $color-name), + $lightness: -5%, + $space: hsl + ); } .chat_alert_#{$color-name} .major_announcement_text { - background-color: darken(map.get($alert-stripe-colors, $color-name), 5); + background-color: color.adjust( + map.get($alert-stripe-colors, $color-name), + $lightness: -5%, + $space: hsl + ); } } diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index a3d0688ce362c..e46cd52d5075f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -1200,16 +1200,18 @@ $border-width-px: $border-width * 1px; } .chat_alert_#{$color-name} .minor_announcement_text { - background-color: lighten( + background-color: color.adjust( map.get($alert-stripe-alternate-colors, $color-name), - 5 + $lightness: 5%, + $space: hsl ); } .chat_alert_#{$color-name} .major_announcement_text { - background-color: lighten( + background-color: color.adjust( map.get($alert-stripe-alternate-colors, $color-name), - 5 + $lightness: 5%, + $space: hsl ); } } diff --git a/tgui/packages/tgui-say/styles/button.scss b/tgui/packages/tgui-say/styles/button.scss index 89e1cceca943a..69802c045ec5e 100644 --- a/tgui/packages/tgui-say/styles/button.scss +++ b/tgui/packages/tgui-say/styles/button.scss @@ -15,7 +15,11 @@ padding: 0; width: 2.6rem; &:hover { - background-color: lighten(colors.$button, 10%); + background-color: color.adjust( + colors.$button, + $lightness: 10%, + $space: hsl + ); } } diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss index 5113af953e740..ce76ba10a1622 100644 --- a/tgui/packages/tgui-say/styles/colors.scss +++ b/tgui/packages/tgui-say/styles/colors.scss @@ -33,7 +33,7 @@ $channel_keys: map.keys($_channel_map) !default; $channel-map: (); @each $channel in $channel_keys { - $channel-map: map-merge( + $channel-map: map.merge( $channel-map, ( $channel: map.get($_channel_map, $channel), diff --git a/tgui/packages/tgui-say/styles/main.scss b/tgui/packages/tgui-say/styles/main.scss index 8e164aa21fa7f..bafc850e242a0 100644 --- a/tgui/packages/tgui-say/styles/main.scss +++ b/tgui/packages/tgui-say/styles/main.scss @@ -26,14 +26,14 @@ } @each $channel, $color in colors.$channel-map { - $darkened: darken($color, 20%); + $darkened: color.adjust($color, $lightness: -20%, $space: hsl); .button-#{$channel} { - border-color: darken($color, 10%); + border-color: color.adjust($color, $lightness: -10%, $space: hsl); color: $color; &:hover { - border-color: lighten($color, 10%); - color: lighten($color, 5%); + border-color: color.adjust($color, $lightness: 10%, $space: hsl); + color: color.adjust($color, $lightness: 5%, $space: hsl); } } @@ -50,11 +50,11 @@ animation: gradient 10s linear infinite; background: linear-gradient( to right, - darken($color, 35%), + color.adjust($color, $lightness: -35%, $space: hsl), $color, - lighten($color, 10%), + color.adjust($color, $lightness: 10%, $space: hsl), $color, - darken($color, 35%) + color.adjust($color, $lightness: -35%, $space: hsl) ); background-position: 0% 0%; background-size: 500% auto; diff --git a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx index 765862ab4b86c..2f9a9cd901630 100644 --- a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx +++ b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx @@ -13,7 +13,7 @@ import { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -type Reagent = { +export type Reagent = { name: string; volume: number; }; diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx index cdc2b6fac97b9..255e669efcf04 100644 --- a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx +++ b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx @@ -13,12 +13,13 @@ import { round, toFixed } from 'tgui-core/math'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -import { MixingData } from './ChemMixingChamber'; +import { MixingData, Reagent } from './ChemMixingChamber'; type ReactingData = MixingData & { ph: number; reagentAcidic: number; reagentAlkaline: number; + catalysts: Reagent[]; }; export const ChemReactionChamber = (props) => { @@ -36,8 +37,9 @@ export const ChemReactionChamber = (props) => { reagentAlkaline, } = data; const reagents = data.reagents || []; + const catalysts = data.catalysts || []; return ( - + @@ -130,8 +132,9 @@ export const ChemReactionChamber = (props) => {
@@ -189,7 +192,6 @@ export const ChemReactionChamber = (props) => { { {reagent.volume} + + + +
+ +
+ + + {catalysts.map((reagent) => ( + + + + {reagent.name + ':'} + + + {reagent.volume} + + + + + + + ))} + + +
+
diff --git a/tgui/packages/tgui/interfaces/RequestManager.tsx b/tgui/packages/tgui/interfaces/RequestManager.tsx index 9a009106788b7..5bc63512f3fbb 100644 --- a/tgui/packages/tgui/interfaces/RequestManager.tsx +++ b/tgui/packages/tgui/interfaces/RequestManager.tsx @@ -3,6 +3,7 @@ * @copyright 2021 bobbahbrown (https://github.com/bobbahbrown) * @license MIT */ +import { BooleanLike } from 'common/react'; import { createSearch, decodeHtmlEntities } from 'common/string'; import { useState } from 'react'; @@ -12,6 +13,7 @@ import { Window } from '../layouts'; type Data = { requests: Request[]; + fax_autoprinting: BooleanLike; }; type Request = { @@ -72,6 +74,15 @@ export const RequestManager = (props) => { buttons={ + act('toggleprint')} + tooltip={ + 'Enables automatic printing of fax requests to the admin fax machine. By default, this fax is located in the briefing room at the central command station' + } + > + Auto-print Faxes + setSearchText(value)} @@ -146,7 +157,12 @@ const RequestControls = (props) => { )} {request.req_type === 'request_fax' && ( - + <> + + + )} {request.req_type === 'request_internet_sound' && ( diff --git a/tgui/packages/tgui/interfaces/Secrets.jsx b/tgui/packages/tgui/interfaces/Secrets.jsx index 3e3ccd70cc0ff..fbe0f56a9124a 100644 --- a/tgui/packages/tgui/interfaces/Secrets.jsx +++ b/tgui/packages/tgui/interfaces/Secrets.jsx @@ -425,13 +425,13 @@ const FunTab = (props) => { /> - - Your admin button here, coder! - + content="Send Shuttle Back" + onClick={() => act('send_shuttle_back')} + /> diff --git a/tgui/packages/tgui/interfaces/TurbineComputer.tsx b/tgui/packages/tgui/interfaces/TurbineComputer.tsx index 4d1305b88b5c8..f67a09f2687a8 100644 --- a/tgui/packages/tgui/interfaces/TurbineComputer.tsx +++ b/tgui/packages/tgui/interfaces/TurbineComputer.tsx @@ -9,7 +9,9 @@ import { NumberInput, ProgressBar, Section, + Stack, } from '../components'; +import { formatPower } from '../format'; import { Window } from '../layouts'; type TurbineInfo = { @@ -19,105 +21,99 @@ type TurbineInfo = { power: number; temp: number; integrity: number; - parts_linked: BooleanLike; - parts_ready: BooleanLike; max_rpm: number; max_temperature: number; regulator: number; }; -export const TurbineComputer = (props) => { +const TurbineDisplay = (props) => { const { act, data } = useBackend(); - const parts_not_connected = !data.parts_linked && ( - - - { - 'Parts not connected, use a multitool on the core rotor before trying again' - } - - + + return ( +
= 1000)} + onClick={() => act('toggle_power')} + > + {data.active ? 'Online' : 'Offline'} + + } + > + + + + act('regulate', { + regulate: value * 0.01, + }) + } + /> + + + + + + {data.rpm} RPM + + + {data.max_rpm} RPM + + + {data.temp} K + + + {data.max_temperature} K + + + {formatPower(data.power)} + + +
); - const parts_not_ready = data.parts_linked && !data.parts_ready && ( +}; + +const OutOfService = (props) => { + return ( - - { - 'Some parts have open maintenance hatchet, please close them before starting' - } - + + + + { + 'Parts not connected, close all mantainence panels/use a multitool on the rotor before trying again' + } + + + ); +}; + +export const TurbineComputer = (props) => { + const { data } = useBackend(); + return ( -
= 1000) || !data.parts_linked} - onClick={() => act('toggle_power')} - /> - } - > - {parts_not_connected} - {parts_not_ready} - - - - act('regulate', { - regulate: value * 0.01, - }) - } - /> - - - - - - {data.rpm} RPM - - - {data.max_rpm} RPM - - - {data.temp} K - - - {data.max_temperature} K - - - {data.power * 4 * 0.001} kW - - -
+ {data.connected ? : }
); diff --git a/tgui/packages/tgui/layouts/Layout.tsx b/tgui/packages/tgui/layouts/Layout.tsx index 173ed1cbb432d..f3f395e540eca 100644 --- a/tgui/packages/tgui/layouts/Layout.tsx +++ b/tgui/packages/tgui/layouts/Layout.tsx @@ -21,6 +21,7 @@ type Props = Partial<{ export function Layout(props: Props) { const { className, theme = 'nanotrasen', children, ...rest } = props; + document.documentElement.className = `theme-${theme}`; return (
diff --git a/tgui/packages/tgui/styles/layouts/Layout.scss b/tgui/packages/tgui/styles/layouts/Layout.scss index ecf750ecc0740..745112eab47c8 100644 --- a/tgui/packages/tgui/styles/layouts/Layout.scss +++ b/tgui/packages/tgui/styles/layouts/Layout.scss @@ -5,12 +5,28 @@ @use 'sass:color'; @use '../base'; +@use '../functions.scss' as *; +$luminance: luminance(base.$color-bg); $scrollbar-color-multiplier: 1 !default; +$scrollbar-base: color.scale( + base.$color-bg, + $lightness: -33% * $scrollbar-color-multiplier +); +$scrollbar-face: color.scale( + base.$color-bg, + $lightness: if($luminance > 0.05, 30%, 10%) * $scrollbar-color-multiplier +); +// Fancy scrollbar +html, +body { + scrollbar-color: $scrollbar-face $scrollbar-base; +} + +// Remove with 516, IE legacy code .Layout, .Layout * { - // Fancy scrollbar scrollbar-base-color: color.scale( base.$color-bg, $lightness: -25% * $scrollbar-color-multiplier diff --git a/tgui/packages/tgui/styles/layouts/TitleBar.scss b/tgui/packages/tgui/styles/layouts/TitleBar.scss index 517f2b8ad811e..8fd7239d55b1f 100644 --- a/tgui/packages/tgui/styles/layouts/TitleBar.scss +++ b/tgui/packages/tgui/styles/layouts/TitleBar.scss @@ -105,7 +105,7 @@ $shadow-color: hsla(0, 0%, 0%, 0.1) !default; min-width: base.rem(20px); padding: 2px 4px; padding: base.rem(2px) base.rem(4px); - background-color: darken(colors.$good, 10%); + background-color: color.adjust(colors.$good, $lightness: -10%, $space: hsl); color: hsl(120, 100%, 100%); text-align: center; } diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 64c54a9debf2a..a00d7cd973461 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -66,7 +66,7 @@ // NT Theme .Layout__content { background-image: url('../assets/bg-nanotrasen.svg'); - background-size: 70%; + background-size: 70% 70%; background-position: center; background-repeat: no-repeat; }