diff --git a/code/__DEFINES/time_defines.dm b/code/__DEFINES/time_defines.dm new file mode 100644 index 000000000000..d40f492be3a5 --- /dev/null +++ b/code/__DEFINES/time_defines.dm @@ -0,0 +1,18 @@ +#define MILLISECONDS *0.01 + +#define DECISECONDS *1 //the base unit all of these defines are scaled by, because byond uses that as a unit of measurement for some fucking reason + +// So you can be all 10 SECONDS +#define SECONDS *10 + +#define MINUTES *600 + +#define HOURS *36000 + +#define TICKS *world.tick_lag + +#define SECONDS_TO_LIFE_CYCLES /2 + +#define DS2TICKS(DS) ((DS)/world.tick_lag) + +#define TICKS2DS(T) ((T) TICKS) diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index bf944777d231..65075c74c0a9 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -1,22 +1,3 @@ -#define MILLISECONDS *0.01 - -#define DECISECONDS *1 //the base unit all of these defines are scaled by, because byond uses that as a unit of measurement for some fucking reason - -// So you can be all 10 SECONDS -#define SECONDS *10 - -#define MINUTES *600 - -#define HOURS *36000 - -#define TICKS *world.tick_lag - -#define SECONDS_TO_LIFE_CYCLES /2 - -#define DS2TICKS(DS) ((DS)/world.tick_lag) - -#define TICKS2DS(T) ((T) TICKS) - /* This proc should only be used for world/Topic. * If you want to display the time for which dream daemon has been running ("round time") use worldtime2text. * If you want to display the canonical station "time" (aka the in-character time of the station) use station_time_timestamp diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 99a0b48e5734..520a6e7abbc5 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1562,12 +1562,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) if(areas) . |= T.loc -/proc/turf_clear(turf/T) - for(var/atom/A in T) - if(A.simulated) - return FALSE - return TRUE - /proc/screen_loc2turf(scr_loc, turf/origin) var/tX = splittext(scr_loc, ",") var/tY = splittext(tX[2], ":") diff --git a/code/datums/components/scope.dm b/code/datums/components/scope.dm index 4ad8faa83d65..59e32cfedd70 100644 --- a/code/datums/components/scope.dm +++ b/code/datums/components/scope.dm @@ -58,6 +58,9 @@ )) /datum/component/scope/process() + if(!tracker) + STOP_PROCESSING(SSprojectiles, src) + return var/mob/user_mob = tracker.owner var/client/user_client = user_mob.client if(!user_client) diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index bb31a2381a10..2fd7f426a9c8 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -351,7 +351,7 @@ for(var/mob/living/L in range(10, our_scope.given_turf)) if(locks >= LWAP_LOCK_CAP) return - if(L == owner || L.stat == DEAD || isslime(L) || ismonkeybasic(L) || L.invisibility > owner.see_invisible) //xenobio moment + if(L == owner || L.stat == DEAD || isslime(L) || ismonkeybasic(L) || L.invisibility > owner.see_invisible || isLivingSSD(L)) //xenobio moment continue new /obj/effect/temp_visual/single_user/lwap_ping(owner.loc, owner, L) locks++ diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm index 8e05021c240c..fb40dd438f33 100644 --- a/code/game/gamemodes/miniantags/morph/morph.dm +++ b/code/game/gamemodes/miniantags/morph/morph.dm @@ -199,6 +199,10 @@ failed_ambush() to_chat(src, "You moved out of your ambush spot!") +/mob/living/simple_animal/hostile/morph/add_ventcrawl() + . = ..() + on_move() + /mob/living/simple_animal/hostile/morph/death(gibbed) . = ..() add_to_all_human_data_huds() diff --git a/code/game/machinery/vendors/custom_vendors.dm b/code/game/machinery/vendors/custom_vendors.dm index 82e0b0c98913..12ca4b0f06a1 100644 --- a/code/game/machinery/vendors/custom_vendors.dm +++ b/code/game/machinery/vendors/custom_vendors.dm @@ -1,3 +1,7 @@ +#define INSERT_FAIL 0 +#define INSERT_DONE 1 +#define INSERT_NEEDS_INPUT 2 + /obj/machinery/economy/vending/custom name = "\improper CrewVend 3000" refill_canister = null @@ -17,6 +21,9 @@ return linked_pos?.linked_account || ..() /obj/machinery/economy/vending/custom/item_interaction(mob/living/user, obj/item/used, list/modifiers) + if(user.a_intent == INTENT_HARM) + return ..() + if(istype(used, /obj/item/eftpos)) visible_message("[src] beeps as [user] links it to [used].", "You hear something beep.") if(!isnull(linked_pos)) @@ -30,9 +37,25 @@ return ITEM_INTERACT_COMPLETE else if(locked()) return ..() - if(!user.canUnEquip(used, FALSE)) + + try_add_stock(user, used) + return ITEM_INTERACT_COMPLETE + +/// Tries to add something to the vendor. can_wait returns INSERT_NEEDS_INPUT if it would wait for user input, quiet suppresses success messages, and bag is used when the item is being transferred from a storage item. +/obj/machinery/economy/vending/custom/proc/try_add_stock(mob/living/user, obj/item/used, can_wait = TRUE, quiet = FALSE, obj/item/storage/bag = null) + if(isnull(bag) && !user.canUnEquip(used, FALSE)) to_chat(user, "\The [used] is stuck to your hand!") - return ITEM_INTERACT_COMPLETE + return INSERT_FAIL + else if(bag) + if(!Adjacent(user)) + to_chat(user, "You can't reach [src] from here!") + return INSERT_FAIL + if(!user.is_holding(bag)) + to_chat(user, "\The [bag] isn't in your hand anymore!") + return INSERT_FAIL + if(used.loc != bag) + to_chat(user, "\The [used] isn't in [bag] anymore!") + return INSERT_FAIL for(var/datum/data/vending_product/physical/record in physical_product_records) if(record.get_amount_left() == 0) @@ -42,33 +65,82 @@ var/obj/item/existing = record.items[1] if(existing.should_stack_with(used)) record.items += used - user.unequip(used) + if(isnull(bag)) + user.unequip(used) + else + bag.remove_from_storage(used) used.moveToNullspace() - user.visible_message("[user] puts [used] into [src].", "") - return ITEM_INTERACT_COMPLETE + if(!quiet) + user.visible_message("[user] puts [used] into [src].", "") + return INSERT_DONE + + if(!can_wait) + return INSERT_NEEDS_INPUT var/price = tgui_input_number(user, "How much do you want to sell [used] for?") if(!isnum(price)) - return ITEM_INTERACT_COMPLETE + return INSERT_FAIL if(!Adjacent(user)) to_chat(user, "You can't reach [src] from here!") - return ITEM_INTERACT_COMPLETE - if(!user.is_holding(used)) - to_chat(user, "\The [used] isn't in your hand anymore!") - return ITEM_INTERACT_COMPLETE - if(!user.canUnEquip(used, FALSE)) - to_chat(user, "\The [used] is stuck to your hand!") - return ITEM_INTERACT_COMPLETE + return INSERT_FAIL + if(isnull(bag)) + if(!user.is_holding(used)) + to_chat(user, "\The [used] isn't in your hand anymore!") + return INSERT_FAIL + if(!user.canUnEquip(used, FALSE)) + to_chat(user, "\The [used] is stuck to your hand!") + return INSERT_FAIL + else + if(!user.is_holding(bag)) + to_chat(user, "\The [bag] isn't in your hand anymore!") + return INSERT_FAIL + if(used.loc != bag) + to_chat(user, "\The [used] isn't in [bag] anymore!") + return INSERT_FAIL var/datum/data/vending_product/physical/record = new(used.name, used.icon, used.icon_state) record.items += used record.price = price physical_product_records += record SStgui.update_uis(src, TRUE) - user.unequip(used) + if(isnull(bag)) + user.unequip(used) + else + bag.remove_from_storage(used) used.moveToNullspace() - user.visible_message("[user] puts [used] into [src].", "You put [used] into [src].") - return ITEM_INTERACT_COMPLETE + if(!quiet) + user.visible_message("[user] puts [used] into [src].", "You put [used] into [src].") + return INSERT_DONE + +/obj/machinery/economy/vending/custom/MouseDrop_T(atom/dragged, mob/user, params) + if(!istype(dragged, /obj/item/storage)) + return ..() + + var/obj/item/storage/bag = dragged + var/inserted = FALSE + for(var/obj/item/thing in bag.contents.Copy()) + var/result = try_add_stock(user, thing, can_wait = FALSE, quiet = TRUE, bag = bag) + if(result == INSERT_FAIL) + break + if(result == INSERT_DONE) + inserted = TRUE + continue + + // result == INSERT_NEEDS_INPUT + if(inserted) + user.visible_message("[user] transfers some things from [bag] into [src].", "You transfer some things from [bag] into [src].") + // We've reported on our insertions so far, don't repeat it. + inserted = FALSE + + // Try again, this time expecting it to wait. + result = try_add_stock(user, thing, bag = bag) + if(result == INSERT_FAIL) + break + + if(inserted) + user.visible_message("[user] transfers everything from [bag] into [src].", "You transfer everything from [bag] into [src].") + + return TRUE /obj/machinery/economy/vending/custom/crowbar_act(mob/user, obj/item/I) if(!isnull(linked_pos) && linked_pos.transaction_locked) @@ -82,3 +154,7 @@ physical_product_records -= R physical_hidden_records -= R SStgui.update_uis(src, TRUE) + +#undef INSERT_FAIL +#undef INSERT_DONE +#undef INSERT_NEEDS_INPUT diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index d1b2605b4b99..dfa081f7d09f 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -59,7 +59,7 @@ /obj/item/card/emag/magic_key/interact_with_atom(atom/target, mob/living/user, list/modifiers) if(!isairlock(target)) - return ITEM_INTERACT_COMPLETE + return NONE var/obj/machinery/door/D = target D.locked = FALSE D.update_icon() diff --git a/code/game/objects/items/weapons/dice.dm b/code/game/objects/items/weapons/dice.dm index 536bb735ccde..6bbda1895167 100644 --- a/code/game/objects/items/weapons/dice.dm +++ b/code/game/objects/items/weapons/dice.dm @@ -197,6 +197,13 @@ qdel(I) if(5) //Monkeying + if(ismachineperson(user)) + playsound(get_turf(user), 'sound/machines/ding.ogg', 100, 1) + var/obj/fresh_toast = new /obj/item/food/toast(get_turf(user)) + fresh_toast.desc += " It came out of [user]!" + to_chat(user, "Your internal structure is getting really toasty!") + user.gib() + return T.visible_message("[user] transforms into a monkey!") user.monkeyize() if(6) diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 912db6732717..8d82961b4663 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -213,6 +213,20 @@ throw_range = 0 throw_speed = 0 var/datum/action/changeling/weapon/parent_action + /// Used for deleting gun after hitting something + var/hit_something = FALSE + /// True if we're shooting our shot -- used to track shooting to prevent deleting mid shot + var/shooting_right_now = FALSE + +/obj/item/gun/magic/tentacle/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) + shooting_right_now = TRUE + . = ..() + shooting_right_now = FALSE + check_should_delete() + +/obj/item/gun/magic/tentacle/proc/check_should_delete() + if(!shooting_right_now && hit_something) + qdel(src) /obj/item/gun/magic/tentacle/customised_abstract_text(mob/living/carbon/owner) return "[owner.p_their(TRUE)] [owner.l_hand == src ? "left arm" : "right arm"] has been turned into a grotesque tentacle." @@ -294,7 +308,8 @@ /obj/item/projectile/tentacle/on_hit(atom/target, blocked = 0) var/mob/living/carbon/human/H = firer - qdel(source.gun) + source.gun.hit_something = TRUE + source.gun.check_should_delete() if(blocked >= 100) return FALSE if(isitem(target)) @@ -353,8 +368,8 @@ add_attack_logs(H, C, "imobilised with a changeling tentacle") if(!iscarbon(H)) return TRUE - var/obj/item/restraints/legcuffs/beartrap/changeling/B = new(H.loc) - B.Crossed(C) + var/obj/item/restraints/legcuffs/beartrap/changeling/B = new(get_turf(L)) + B.on_atom_entered(C, L) return TRUE if(INTENT_HARM) diff --git a/code/modules/events/infestation.dm b/code/modules/events/infestation.dm index 4b362a79ad76..b9ee7130e7db 100644 --- a/code/modules/events/infestation.dm +++ b/code/modules/events/infestation.dm @@ -27,17 +27,27 @@ /datum/event/infestation/start() var/list/turf/simulated/floor/turfs = list() - spawn_area_type = pick(spawn_areas) - for(var/areapath in typesof(spawn_area_type)) - var/area/A = locate(areapath) - if(!A) - log_debug("Failed to locate area for infestation event!") - kill() - return - for(var/turf/simulated/floor/F in A.contents) - if(turf_clear(F)) - turfs += F + // shuffle in place so we don't have do that dance where we make a copy of + // the list, then pick and take, then do some conditional logic to make sure + // there's still areas to choose from, etc etc, it's a small list, it's cheap + shuffle_inplace(spawn_areas) + for(var/spawn_area in spawn_areas) + for(var/area_type in typesof(spawn_area)) + var/area/destination = locate(area_type) + if(!destination) + continue + for(var/turf/simulated/floor/F in destination.contents) + if(!is_blocked_turf(F)) + turfs += F + if(length(turfs)) + spawn_area_type = area_type + spawn_on_turfs(turfs) + return + + log_debug("Failed to locate area for infestation event!") + kill() +/datum/event/infestation/proc/spawn_on_turfs(list/turfs) var/list/spawn_types = list() var/max_number vermin = rand(0, 2) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 04cfc4d45625..fb1ddae797b6 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -842,7 +842,7 @@ emp_act return TRUE /mob/living/carbon/human/projectile_hit_check(obj/item/projectile/P) - return (HAS_TRAIT(src, TRAIT_FLOORED) || HAS_TRAIT(src, TRAIT_NOKNOCKDOWNSLOWDOWN)) && !density && !(P.always_hit_living_nondense && (stat != DEAD)) // hit mobs that are intentionally lying down to prevent combat crawling. + return (HAS_TRAIT(src, TRAIT_FLOORED) || HAS_TRAIT(src, TRAIT_NOKNOCKDOWNSLOWDOWN)) && !density && !(P.always_hit_living_nondense && (stat != DEAD) && !isLivingSSD(src)) // hit mobs that are intentionally lying down to prevent combat crawling. /mob/living/carbon/human/canBeHandcuffed() return has_left_hand() || has_right_hand() diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 2f625649540c..479dd84f1100 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -21,7 +21,7 @@ return (!mover.density || !density || horizontal) /mob/proc/projectile_hit_check(obj/item/projectile/P) - return !(P.always_hit_living_nondense && (stat != DEAD)) && !density + return !(P.always_hit_living_nondense && (stat != DEAD) && !isLivingSSD(src)) && !density /client/verb/toggle_throw_mode() set hidden = 1 diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 54dadd93b812..7d433e8a9187 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -187,6 +187,8 @@ var/icon_off = "camera_off" var/size = 3 var/see_ghosts = FALSE //for the spoop of it + /// Cult portals and unconcealed runes have a minor form of invisibility + var/see_cult = TRUE var/current_photo_num = 1 var/digital = FALSE /// Should camera light up the scene @@ -291,6 +293,10 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor atoms.Add(A) continue + // AI can't see unconcealed runes or cult portals + if(A.invisibility == INVISIBILITY_RUNES && see_cult) + atoms.Add(A) + continue if(A.invisibility) if(see_ghosts && isobserver(A)) var/mob/dead/observer/O = A diff --git a/code/modules/paperwork/silicon_photography.dm b/code/modules/paperwork/silicon_photography.dm index 283ec043d772..0b55e74003e9 100644 --- a/code/modules/paperwork/silicon_photography.dm +++ b/code/modules/paperwork/silicon_photography.dm @@ -15,6 +15,7 @@ /// camera AI can take pictures with /obj/item/camera/siliconcam/ai_camera name = "AI photo camera" + see_cult = FALSE /// camera cyborgs can take pictures with /obj/item/camera/siliconcam/robot_camera diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm index e0afec9fa40c..facb53a60ac2 100644 --- a/code/modules/power/engines/supermatter/supermatter.dm +++ b/code/modules/power/engines/supermatter/supermatter.dm @@ -474,7 +474,7 @@ // Pass all the gas related code an empty gas container removed = new() damage_archived = damage - if(!removed || !removed.total_moles() || isspaceturf(T)) //we're in space or there is no gas to process + if(!removed || removed.total_moles() <= 0 || isspaceturf(T)) //we're in space or there is no gas to process if(takes_damage) damage += max((power / 1000) * DAMAGE_INCREASE_MULTIPLIER, 0.1) // always does at least some damage else @@ -577,18 +577,18 @@ //Power * 0.55 * a value between 1 and 0.8 var/device_energy = power * REACTION_POWER_MODIFIER - // Calculate temperature change in terms of thermal energy, scaled by the average specific heat of the gas. - if(removed.total_moles()) - var/produced_joules = max(0, ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER) * heat_multiplier) - produced_joules *= (removed.heat_capacity() / removed.total_moles()) - removed.set_temperature((removed.thermal_energy() + produced_joules) / removed.heat_capacity()) - //Calculate how much gas to release //Varies based on power and gas content removed.set_toxins(removed.toxins() + max(((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER) * gas_multiplier, 0)) //Varies based on power, gas content, and heat removed.set_oxygen(removed.oxygen() + max((((device_energy + removed.temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER) * gas_multiplier, 0)) + // Calculate temperature change in terms of thermal energy, scaled by the average specific heat of the gas. + if(removed.total_moles() >= 1) + var/produced_joules = max(0, ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER) * heat_multiplier) + produced_joules *= (removed.heat_capacity() / removed.total_moles()) + removed.set_temperature((removed.thermal_energy() + produced_joules) / removed.heat_capacity()) + if(produces_gas) env.merge(removed) diff --git a/code/modules/projectiles/guns/energy/special_eguns.dm b/code/modules/projectiles/guns/energy/special_eguns.dm index 585190d4bed9..7d5fab6f293e 100644 --- a/code/modules/projectiles/guns/energy/special_eguns.dm +++ b/code/modules/projectiles/guns/energy/special_eguns.dm @@ -357,7 +357,7 @@ P.precision = 0 P.failchance = 0 P.can_multitool_to_remove = 1 - if(W.name == "bluespace beam") + if(W.name == "wormhole beam") qdel(blue) blue = P else diff --git a/code/tests/_game_test.dm b/code/tests/_game_test.dm index a7853c1d277b..24a5d2a32e9c 100644 --- a/code/tests/_game_test.dm +++ b/code/tests/_game_test.dm @@ -19,6 +19,8 @@ GLOBAL_LIST_EMPTY(game_test_chats) #define TEST_ASSERT_ANY_CHATLOG(puppet, text) if(!puppet.any_chatlog_has_text(text)) { return Fail("Expected `[text]` in any chatlog but got [jointext(puppet.get_chatlogs(), "\n")]", __FILE__, __LINE__) } +#define TEST_ASSERT_NOT_CHATLOG(puppet, text) if(puppet.any_chatlog_has_text(text)) { return Fail("Didn't expect `[text]` in any chatlog but got [jointext(puppet.get_chatlogs(), "\n")]", __FILE__, __LINE__) } + /// Asserts that the two parameters passed are equal, fails otherwise /// Optionally allows an additional message in the case of a failure #define TEST_ASSERT_EQUAL(a, b, message) do { \ diff --git a/code/tests/_game_test_puppeteer.dm b/code/tests/_game_test_puppeteer.dm index e4c6c039a7c2..3c340d925d8e 100644 --- a/code/tests/_game_test_puppeteer.dm +++ b/code/tests/_game_test_puppeteer.dm @@ -48,6 +48,16 @@ origin_test.Fail("could not spawn obj [obj_type] near [src]") +/datum/test_puppeteer/proc/use_item_in_hand() + var/obj/item/item = puppet.get_active_hand() + if(!item) + return + + item.activate_self(puppet) + puppet.next_click = world.time + puppet.next_move = world.time + return TRUE + /datum/test_puppeteer/proc/click_on(target, params) var/datum/test_puppeteer/puppet_target = target if(istype(puppet_target)) diff --git a/modular_ss220/events/code/headcrabs.dm b/modular_ss220/events/code/headcrabs.dm index f6a71bceb4d8..f16f19994e81 100644 --- a/modular_ss220/events/code/headcrabs.dm +++ b/modular_ss220/events/code/headcrabs.dm @@ -18,7 +18,7 @@ var/area/randomarea = pick(availableareas) var/list/turf/simulated/floor/turfs = list() for(var/turf/simulated/floor/F in randomarea) - if(turf_clear(F)) + if(!is_blocked_turf(F)) turfs += F var/list/spawn_types = list() var/max_number diff --git a/modular_ss220/events/code/infestation_extended.dm b/modular_ss220/events/code/infestation_extended.dm index 67214e1d7ff0..04654bc43478 100644 --- a/modular_ss220/events/code/infestation_extended.dm +++ b/modular_ss220/events/code/infestation_extended.dm @@ -40,7 +40,7 @@ kill() return for(var/turf/simulated/floor/F in A.contents) - if(turf_clear(F)) + if(!is_blocked_turf(F)) turfs += F var/list/spawn_types = list() diff --git a/paradise.dme b/paradise.dme index 6df31d62b74a..33f8c7c3f3eb 100644 --- a/paradise.dme +++ b/paradise.dme @@ -135,6 +135,7 @@ #include "code\__DEFINES\tgs.dm" #include "code\__DEFINES\tgui_defines.dm" #include "code\__DEFINES\tickets_defines.dm" +#include "code\__DEFINES\time_defines.dm" #include "code\__DEFINES\tools_defines.dm" #include "code\__DEFINES\turfs.dm" #include "code\__DEFINES\typeids.dm" diff --git a/rustlibs.dll b/rustlibs.dll index 1c7b3cec49f6..fd0fde8a12b5 100644 Binary files a/rustlibs.dll and b/rustlibs.dll differ diff --git a/rustlibs_prod.dll b/rustlibs_prod.dll index 7b60ae6cf9e5..e91053a07f08 100644 Binary files a/rustlibs_prod.dll and b/rustlibs_prod.dll differ