" + footnote_pile
+#ifndef MAP_TEST
print_command_report(., "[command_name()] Status Summary", announce=FALSE)
if(greenshift)
priority_announce("Thanks to the tireless efforts of our security and intelligence divisions, there are currently no credible threats to [station_name()]. All station construction projects have been authorized. Have a secure shift!", "Security Report", SSstation.announcer.get_rand_report_sound(), color_override = "green")
@@ -366,6 +367,7 @@ SUBSYSTEM_DEF(dynamic)
if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_BLUE)
SSsecurity_level.set_level(SEC_LEVEL_BLUE, announce = FALSE)
priority_announce("[SSsecurity_level.current_security_level.elevating_to_announcement]\n\nA summary has been copied and printed to all communications consoles.", "Security level elevated.", ANNOUNCER_INTERCEPT, color_override = SSsecurity_level.current_security_level.announcement_color)
+#endif
return .
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 98f847e4be6ae35..241622509afafe1 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -52,7 +52,11 @@ SUBSYSTEM_DEF(events)
//checks if we should select a random event yet, and reschedules if necessary
/datum/controller/subsystem/events/proc/checkEvent()
if(scheduled <= world.time)
+#ifdef MAP_TEST
+ message_admins("Random event skipped (Game is compiled in MAP_TEST mode)")
+#else
spawnEvent()
+#endif
reschedule()
//decides which world.time we should select another random event at.
diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm
index 2021964609afdb1..5b3320999f83ef3 100644
--- a/code/controllers/subsystem/explosions.dm
+++ b/code/controllers/subsystem/explosions.dm
@@ -1,4 +1,8 @@
#define EXPLOSION_THROW_SPEED 4
+#define EXPLOSION_BLOCK_LIGHT 2.5
+#define EXPLOSION_BLOCK_HEAVY 1.5
+#define EXPLOSION_BLOCK_DEV 1
+
GLOBAL_LIST_EMPTY(explosions)
SUBSYSTEM_DEF(explosions)
@@ -127,25 +131,26 @@ SUBSYSTEM_DEF(explosions)
var/our_x = explode.x
var/our_y = explode.y
var/dist = CHEAP_HYPOTENUSE(our_x, our_y, x0, y0)
+ var/block = 0
if(newmode == "Yes")
if(explode != epicenter)
var/our_block = cached_exp_block[get_step_towards(explode, epicenter)]
- dist += our_block
+ block += our_block
cached_exp_block[explode] = our_block + explode.explosive_resistance
else
cached_exp_block[explode] = explode.explosive_resistance
dist = round(dist, 0.01)
- if(dist < dev)
+ if(dist + (block * EXPLOSION_BLOCK_DEV) < dev)
explode.color = "red"
explode.maptext = MAPTEXT("[dist]")
- else if (dist < heavy)
+ else if (dist + (block * EXPLOSION_BLOCK_HEAVY) < heavy)
explode.color = "yellow"
- explode.maptext = MAPTEXT("[dist]")
- else if (dist < light)
+ explode.maptext = MAPTEXT("[dist + (block * EXPLOSION_BLOCK_HEAVY)]")
+ else if (dist + (block * EXPLOSION_BLOCK_LIGHT) < light)
explode.color = "blue"
- explode.maptext = MAPTEXT("[dist]")
+ explode.maptext = MAPTEXT("[dist + (block * EXPLOSION_BLOCK_LIGHT)]")
else
continue
@@ -397,26 +402,26 @@ SUBSYSTEM_DEF(explosions)
var/our_x = explode.x
var/our_y = explode.y
var/dist = CHEAP_HYPOTENUSE(our_x, our_y, x0, y0)
-
+ var/block = 0
// Using this pattern, block will flow out from blocking turfs, essentially caching the recursion
// This is safe because if get_step_towards is ever anything but caridnally off, it'll do a diagonal move
// So we always sample from a "loop" closer
- // It's kind of behaviorly unimpressive that that's a problem for the future
+ // It's kind of behaviorly unimpressive but that's a problem for the future
if(reactionary)
if(explode == epicenter)
cached_exp_block[explode] = explode.explosive_resistance
else
var/our_block = cached_exp_block[get_step_towards(explode, epicenter)]
- dist += our_block
+ block += our_block
cached_exp_block[explode] = our_block + explode.explosive_resistance
var/severity = EXPLODE_NONE
- if(dist < devastation_range)
+ if(dist + (block * EXPLOSION_BLOCK_DEV) < devastation_range)
severity = EXPLODE_DEVASTATE
- else if(dist < heavy_impact_range)
+ else if(dist + (block * EXPLOSION_BLOCK_HEAVY) < heavy_impact_range)
severity = EXPLODE_HEAVY
- else if(dist < light_impact_range)
+ else if(dist + (block * EXPLOSION_BLOCK_LIGHT) < light_impact_range)
severity = EXPLODE_LIGHT
if(explode == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag
@@ -730,3 +735,6 @@ SUBSYSTEM_DEF(explosions)
currentpart = SSEXPLOSIONS_TURFS
#undef EXPLOSION_THROW_SPEED
+#undef EXPLOSION_BLOCK_LIGHT
+#undef EXPLOSION_BLOCK_HEAVY
+#undef EXPLOSION_BLOCK_DEV
diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm
index 8516ed98ea9497f..1141e53acfc67a4 100644
--- a/code/controllers/subsystem/minor_mapping.dm
+++ b/code/controllers/subsystem/minor_mapping.dm
@@ -7,13 +7,15 @@ SUBSYSTEM_DEF(minor_mapping)
flags = SS_NO_FIRE
/datum/controller/subsystem/minor_mapping/Initialize()
- #ifdef UNIT_TESTS // This whole subsystem just introduces a lot of odd confounding variables into unit test situations, so let's just not bother with doing an initialize here.
+// This whole subsystem just introduces a lot of odd confounding variables into unit test situations,
+// so let's just not bother with doing an initialize here.
+#if defined(MAP_TEST) || defined(UNIT_TESTS)
return SS_INIT_NO_NEED
- #else
+#else
trigger_migration(CONFIG_GET(number/mice_roundstart))
place_satchels(satchel_amount = 10) //SKYRAT EDIT CHANGE - ORIGINAL : place_satchels(satchel_amount = 2)
return SS_INIT_SUCCESS
- #endif // the mice are easily the bigger problem, but let's just avoid anything that could cause some bullshit.
+#endif
/// Spawns some critters on exposed wires, usually but not always mice
/datum/controller/subsystem/minor_mapping/proc/trigger_migration(to_spawn=10)
diff --git a/code/controllers/subsystem/ore_generation.dm b/code/controllers/subsystem/ore_generation.dm
index ca8aa09d6111c37..e36dd577794e123 100644
--- a/code/controllers/subsystem/ore_generation.dm
+++ b/code/controllers/subsystem/ore_generation.dm
@@ -20,27 +20,6 @@ SUBSYSTEM_DEF(ore_generation)
var/list/ore_vent_minerals = list()
/// A tracker of how many of each ore vent size we have in the game. Useful for tracking purposes.
- var/list/ore_vent_sizes = list(
- LARGE_VENT_TYPE = 0,
- MEDIUM_VENT_TYPE = 0,
- SMALL_VENT_TYPE = 0,
- )
- /// Ores spawned by proximity to an ore vent. Useful for logging purposes.
- var/list/post_ore_random = list(
- "1" = 0,
- "2" = 0,
- "3" = 0,
- "4" = 0,
- "5" = 0,
- )
- /// Ores spawned randomly on the map without proximity to an ore vent. Useful for logging purposes.
- var/list/post_ore_manual = list(
- "1" = 0,
- "2" = 0,
- "3" = 0,
- "4" = 0,
- "5" = 0,
- )
/datum/controller/subsystem/ore_generation/Initialize()
//Basically, we're going to round robin through the list of ore vents and assign a mineral to them until complete.
@@ -56,8 +35,43 @@ SUBSYSTEM_DEF(ore_generation)
else
stallbreaker++
if(stallbreaker >= length(possible_vents))
- return SS_INIT_SUCCESS //We've done all we can here.
+ break //We've done all we can here. break inner loop
continue
+ if(stallbreaker >= length(possible_vents))
+ break //We've done all we can here. break outer loop
+
+ /// Handles roundstart logging
+ logger.Log(
+ LOG_CATEGORY_CAVE_GENERATION,
+ "Ore Generation spawned the following ores based on vent proximity",
+ list(
+ "[ORE_WALL_FAR]" = GLOB.post_ore_random["[ORE_WALL_FAR]"],
+ "[ORE_WALL_LOW]" = GLOB.post_ore_random["[ORE_WALL_LOW]"],
+ "[ORE_WALL_MEDIUM]" = GLOB.post_ore_random["[ORE_WALL_MEDIUM]"],
+ "[ORE_WALL_HIGH]" = GLOB.post_ore_random["[ORE_WALL_HIGH]"],
+ "[ORE_WALL_VERY_HIGH]" = GLOB.post_ore_random["[ORE_WALL_VERY_HIGH]"],
+ ),
+ )
+ logger.Log(
+ LOG_CATEGORY_CAVE_GENERATION,
+ "Ore Generation spawned the following ores randomly",
+ list(
+ "[ORE_WALL_FAR]" = GLOB.post_ore_manual["[ORE_WALL_FAR]"],
+ "[ORE_WALL_LOW]" = GLOB.post_ore_manual["[ORE_WALL_LOW]"],
+ "[ORE_WALL_MEDIUM]" = GLOB.post_ore_manual["[ORE_WALL_MEDIUM]"],
+ "[ORE_WALL_HIGH]" = GLOB.post_ore_manual["[ORE_WALL_HIGH]"],
+ "[ORE_WALL_VERY_HIGH]" = GLOB.post_ore_manual["[ORE_WALL_VERY_HIGH]"],
+ ),
+ )
+ logger.Log(
+ LOG_CATEGORY_CAVE_GENERATION,
+ "Ore Generation spawned the following vent sizes",
+ list(
+ "large" = LAZYACCESS(GLOB.ore_vent_sizes, LARGE_VENT_TYPE),
+ "medium" = LAZYACCESS(GLOB.ore_vent_sizes, MEDIUM_VENT_TYPE),
+ "small" = LAZYACCESS(GLOB.ore_vent_sizes, SMALL_VENT_TYPE),
+ ),
+ )
return SS_INIT_SUCCESS
/datum/controller/subsystem/ore_generation/fire(resumed)
diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm
index e42247c71a9961b..eae40a733b0ec04 100644
--- a/code/controllers/subsystem/persistence/_persistence.dm
+++ b/code/controllers/subsystem/persistence/_persistence.dm
@@ -37,6 +37,14 @@ SUBSYSTEM_DEF(persistence)
/// Will be null'd once the persistence system initializes, and never read from again.
var/list/obj/item/storage/photo_album/queued_photo_albums
+ /// A json_database to data/piggy banks.json
+ /// Schema is persistence_id => array of coins, space cash and holochips.
+ var/datum/json_database/piggy_banks_database
+ /// List of persistene ids which piggy banks.
+ var/list/queued_broken_piggy_ids
+
+ var/list/broken_piggy_banks
+
var/rounds_since_engine_exploded = 0
var/delam_highscore = 0
var/tram_hits_this_round = 0
diff --git a/code/controllers/subsystem/persistence/piggy_banks.dm b/code/controllers/subsystem/persistence/piggy_banks.dm
new file mode 100644
index 000000000000000..240fd98ab0c9e37
--- /dev/null
+++ b/code/controllers/subsystem/persistence/piggy_banks.dm
@@ -0,0 +1,56 @@
+///This proc is used to initialize holochips, cash and coins inside our persistent piggy bank.
+/datum/controller/subsystem/persistence/proc/load_piggy_bank(obj/item/piggy_bank/piggy)
+ if(isnull(piggy_banks_database))
+ piggy_banks_database = new("data/piggy_banks.json")
+
+ var/list/data = piggy_banks_database.get_key(piggy.persistence_id)
+ if(isnull(data))
+ return
+ var/total_value = 0
+ for(var/iteration in 1 to length(data))
+ var/money_path = text2path(data[iteration])
+ if(!money_path) //For a reason or another, it was removed.
+ continue
+ var/obj/item/spawned
+ if(ispath(money_path, /obj/item/holochip))
+ //We want to safely access the assoc of this position and not that of last key that happened to match this one.
+ var/list/key_and_assoc = data.Copy(iteration, iteration + 1)
+ var/amount = key_and_assoc["[money_path]"]
+ spawned = new money_path (piggy, amount)
+ //the operations are identical to those of chips, but they're different items, so I'll keep them separated.
+ else if(ispath(money_path, /obj/item/stack/spacecash))
+ var/list/key_and_assoc = data.Copy(iteration, iteration + 1)
+ var/amount = key_and_assoc["[money_path]"]
+ spawned = new money_path (piggy, amount)
+ else if(ispath(money_path, /obj/item/coin))
+ spawned = new money_path (piggy)
+ else
+ stack_trace("Unsupported path found in the data of a persistent piggy bank. item: [money_path], id:[piggy.persistence_id]")
+ continue
+ total_value += spawned.get_item_credit_value()
+ if(total_value >= piggy.maximum_value)
+ break
+
+///This proc is used to save money stored inside our persistent the piggy bank for the next time it's loaded.
+/datum/controller/subsystem/persistence/proc/save_piggy_bank(obj/item/piggy_bank/piggy)
+ if(isnull(piggy_banks_database))
+ return
+
+ if(queued_broken_piggy_ids)
+ for(var/broken_id in queued_broken_piggy_ids)
+ piggy_banks_database.remove(broken_id)
+ queued_broken_piggy_ids = null
+
+ var/list/data = list()
+ for(var/obj/item/item as anything in piggy.contents)
+ var/piggy_value = 1
+ if(istype(item, /obj/item/holochip))
+ var/obj/item/holochip/chip = item
+ piggy_value = chip.credits
+ else if(istype(item, /obj/item/stack/spacecash))
+ var/obj/item/stack/spacecash/cash = item
+ piggy_value = cash.amount
+ else if(!istype(item, /obj/item/coin))
+ continue
+ data += list("[item.type]" = piggy_value)
+ piggy_banks_database.set_key(piggy.persistence_id, data)
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
index acee4480b946f0f..1cfbb144e5f8af0 100644
--- a/code/controllers/subsystem/processing/instruments.dm
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -20,7 +20,10 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
var/static/current_instrument_channels = 0
/// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
var/static/list/synthesizer_instrument_ids
- var/static/list/note_sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL)
+ var/static/list/note_sustain_modes = list(
+ SUSTAIN_LINEAR,
+ SUSTAIN_EXPONENTIAL,
+ )
/datum/controller/subsystem/processing/instruments/Initialize()
initialize_instrument_data()
diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm
index cae0f4105867c83..d05a0b5d30f7bfb 100644
--- a/code/controllers/subsystem/processing/station.dm
+++ b/code/controllers/subsystem/processing/station.dm
@@ -118,6 +118,12 @@ PROCESSING_SUBSYSTEM_DEF(station)
var/neutral_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/neutral_station_traits)))
var/negative_trait_budget = text2num(pick_weight(CONFIG_GET(keyed_list/negative_station_traits)))
+#ifdef MAP_TEST
+ positive_trait_budget = 0
+ neutral_trait_budget = 0
+ negative_trait_budget = 0
+#endif
+
pick_traits(STATION_TRAIT_POSITIVE, positive_trait_budget)
pick_traits(STATION_TRAIT_NEUTRAL, neutral_trait_budget)
pick_traits(STATION_TRAIT_NEGATIVE, negative_trait_budget)
diff --git a/code/controllers/subsystem/queuelinks.dm b/code/controllers/subsystem/queuelinks.dm
index 6a3b8288821624b..a6d56cf622ec9ca 100644
--- a/code/controllers/subsystem/queuelinks.dm
+++ b/code/controllers/subsystem/queuelinks.dm
@@ -18,16 +18,30 @@ SUBSYSTEM_DEF(queuelinks)
if(isnull(id))
CRASH("Attempted to add to queue with no ID; [what]")
- var/datum/queue_link/link
- if(isnull(queues[id]))
+ var/datum/queue_link/link = queues[id]
+ if(isnull(link))
link = new /datum/queue_link(id)
queues[id] = link
- else
- link = queues[id]
if(link.add(what, queue_max))
queues -= id
+/**
+ * Pop a queue link without waiting for it to reach its max size.
+ * This is useful for those links that do not have a fixed size and thus may not pop.
+ */
+/datum/controller/subsystem/queuelinks/proc/pop_link(id)
+ if(isnull(id))
+ CRASH("Attempted to pop a queue with no ID")
+
+ var/datum/queue_link/link = queues[id]
+ if(isnull(queues[id]))
+ CRASH("Attempted to pop a non-existant queue: [id]")
+
+ link.pop()
+ queues -= id
+
+
/datum/queue_link
/// atoms in our queue
var/list/partners = list()
@@ -50,17 +64,17 @@ SUBSYSTEM_DEF(queuelinks)
if(queue_max != 0 && max != 0 && max != queue_max)
CRASH("Tried to change queue size to [max] from [queue_max]!")
else if(!queue_max)
- queue_max = max
-
+ queue_max = max
+
if(!queue_max || length(partners) < queue_max)
return
-
+
pop()
return TRUE
/datum/queue_link/proc/pop()
for(var/atom/item as anything in partners)
- item.MatchedLinks(id, partners)
+ item.MatchedLinks(id, partners - item)
qdel(src)
/datum/queue_link/Destroy()
diff --git a/code/controllers/subsystem/stock_market.dm b/code/controllers/subsystem/stock_market.dm
index c9f632c7faf129b..6c4341adc8d8ad0 100644
--- a/code/controllers/subsystem/stock_market.dm
+++ b/code/controllers/subsystem/stock_market.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(stock_market)
name = "Stock Market"
- wait = 20 SECONDS
+ wait = 60 SECONDS
init_order = INIT_ORDER_DEFAULT
runlevels = RUNLEVEL_GAME
@@ -28,7 +28,7 @@ SUBSYSTEM_DEF(stock_market)
materials_trends[possible_market] = rand(MARKET_TREND_DOWNWARD,MARKET_TREND_UPWARD) //aka -1 to 1
materials_trend_life += possible_market
- materials_trend_life[possible_market] = rand(1,10)
+ materials_trend_life[possible_market] = rand(1,3)
materials_quantity += possible_market
materials_quantity[possible_market] = possible_market.tradable_base_quantity + (rand(-(possible_market.tradable_base_quantity) * 0.5, possible_market.tradable_base_quantity * 0.5))
@@ -80,7 +80,7 @@ SUBSYSTEM_DEF(stock_market)
materials_trends[mat] = MARKET_TREND_DOWNWARD
else
materials_trends[mat] = MARKET_TREND_STABLE
- materials_trend_life[mat] = rand(3,10) // Change our trend life for x number of fires of the subsystem
+ materials_trend_life[mat] = rand(1,3) // Change our trend life for x number of fires of the subsystem
else
materials_trend_life[mat] -= 1
@@ -88,14 +88,14 @@ SUBSYSTEM_DEF(stock_market)
var/quantity_change = 0
switch(trend)
if(MARKET_TREND_UPWARD)
- price_change = ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05)) //If we don't ceil, small numbers will get trapped at low values
- quantity_change = -round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05))
+ price_change = ROUND_UP(gaussian(price_units * 0.30, price_baseline * 0.15)) //If we don't ceil, small numbers will get trapped at low values
+ quantity_change = -round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15))
if(MARKET_TREND_STABLE)
price_change = round(gaussian(0, price_baseline * 0.01))
- quantity_change = round(gaussian(0, quantity_baseline * 0.01))
+ quantity_change = round(gaussian(0, quantity_baseline * 0.5))
if(MARKET_TREND_DOWNWARD)
- price_change = -ROUND_UP(gaussian(price_units * 0.1, price_baseline * 0.05))
- quantity_change = round(gaussian(quantity_baseline * 0.05, quantity_baseline * 0.05))
+ price_change = -ROUND_UP(gaussian(price_units * 0.3, price_baseline * 0.15))
+ quantity_change = round(gaussian(quantity_baseline * 0.15, quantity_baseline * 0.15))
materials_prices[mat] = round(clamp(price_units + price_change, price_minimum, price_maximum))
materials_quantity[mat] = round(clamp(stock_quantity + quantity_change, 0, quantity_baseline * 2))
diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm
index b525f66576c2fbd..91f624972adced6 100644
--- a/code/datums/ai/_ai_controller.dm
+++ b/code/datums/ai/_ai_controller.dm
@@ -34,7 +34,7 @@ multiple modular subtrees with behaviors
///Stored arguments for behaviors given during their initial creation
var/list/behavior_args = list()
///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans.
- var/pathing_attempts
+ var/consecutive_pathing_attempts
///Can the AI remain in control if there is a client?
var/continue_processing_when_client = FALSE
///distance to give up on target
diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm
index a7d43d600b1cdee..14f0d03207959fe 100644
--- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm
+++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm
@@ -50,7 +50,9 @@
if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it
return FALSE
- if(isturf(living_mob.loc) && isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting
+ if(!isturf(living_mob.loc))
+ return FALSE
+ if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting
return FALSE
if(isliving(the_target)) //Targeting vs living mobs
diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm
index 43e37f66e8c8aa1..b70375ef3933fc6 100644
--- a/code/datums/ai/generic/generic_behaviors.dm
+++ b/code/datums/ai/generic/generic_behaviors.dm
@@ -333,7 +333,7 @@
//just in case- it won't do anything if the instrument isn't playing
song.stop_playing()
- song.ParseSong(song_lines)
+ song.ParseSong(new_song = song_lines)
song.repeat = 10
song.volume = song.max_volume - 10
finish_action(controller, TRUE)
diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm
index af29e83f1a4a3ad..3f455b2acd0abfc 100644
--- a/code/datums/ai/movement/_ai_movement.dm
+++ b/code/datums/ai/movement/_ai_movement.dm
@@ -8,22 +8,25 @@
//Override this to setup the moveloop you want to use
/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance)
SHOULD_CALL_PARENT(TRUE)
- controller.pathing_attempts = 0
+ controller.consecutive_pathing_attempts = 0
controller.set_blackboard_key(BB_CURRENT_MIN_MOVE_DISTANCE, min_distance)
moving_controllers[controller] = current_movement_target
/datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller)
- controller.pathing_attempts = 0
+ controller.consecutive_pathing_attempts = 0
moving_controllers -= controller
// We got deleted as we finished an action
if(!QDELETED(controller.pawn))
SSmove_manager.stop_looping(controller.pawn, SSai_movement)
/datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller)
- controller.pathing_attempts++
- if(controller.pathing_attempts >= max_pathing_attempts)
+ controller.consecutive_pathing_attempts++
+ if(controller.consecutive_pathing_attempts >= max_pathing_attempts)
controller.CancelActions()
+/datum/ai_movement/proc/reset_pathing_failures(datum/ai_controller/controller)
+ controller.consecutive_pathing_attempts = 0
+
///Should the movement be allowed to happen? return TRUE if it can, FALSE otherwise
/datum/ai_movement/proc/allowed_to_move(datum/move_loop/source)
SHOULD_BE_PURE(TRUE)
@@ -68,7 +71,8 @@
//Anything to do post movement
/datum/ai_movement/proc/post_move(datum/move_loop/source, succeeded)
SIGNAL_HANDLER
- if(succeeded != FALSE)
- return
var/datum/ai_controller/controller = source.extra_info
+ if(succeeded != MOVELOOP_FAILURE)
+ reset_pathing_failures(controller)
+ return
increment_pathing_failures(controller)
diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm
index 6024b7e7562ccdc..825feefabfa3b56 100644
--- a/code/datums/ai/movement/ai_movement_jps.dm
+++ b/code/datums/ai/movement/ai_movement_jps.dm
@@ -51,3 +51,6 @@
/datum/ai_movement/jps/bot/travel_to_beacon
maximum_length = AI_BOT_PATH_LENGTH
+
+/datum/ai_movement/jps/modsuit
+ maximum_length = MOD_AI_RANGE
diff --git a/code/datums/ai/objects/mod.dm b/code/datums/ai/objects/mod.dm
index 2bb555d281bf737..67d8121a4e16f63 100644
--- a/code/datums/ai/objects/mod.dm
+++ b/code/datums/ai/objects/mod.dm
@@ -5,7 +5,7 @@
BB_MOD_IMPLANT,
)
max_target_distance = MOD_AI_RANGE //a little spicy but its one specific item that summons it, and it doesn't run otherwise
- ai_movement = /datum/ai_movement/jps
+ ai_movement = /datum/ai_movement/jps/modsuit
///ID card generated from the suit's required access. Used for pathing.
var/obj/item/card/id/advanced/id_card
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index fc30d7fb3b8f6f9..d78f2abe9bcf0b0 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -419,7 +419,7 @@
desc = "Patient seems to oxidise things around them at random, and seem to believe they are aiding a creature in climbing a mountin."
scan_desc = "C_)L(#_I_##M;B"
gain_text = span_warning("The rusted climb shall finish at the peak")
- lose_text = span_notice("The rusted climb? Whats that? An odd dream to be sure.")
+ lose_text = span_notice("The rusted climb? What's that? An odd dream to be sure.")
random_gain = FALSE
/datum/brain_trauma/severe/rusting/on_life(seconds_per_tick, times_fired)
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index e24ecd99c61776c..31f316221301e91 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -426,3 +426,49 @@
to_chat(victim, "[span_name("[name]")] exclaims, \"[span_robot("[beepskys_cry]")]")
if(victim.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
victim.create_chat_message(src, raw_message = beepskys_cry, spans = list("robotic"))
+
+// Used by Veteran Security Advisor job.
+/datum/brain_trauma/special/ptsd
+ name = "Combat PTSD"
+ desc = "The patient is experiencing PTSD stemming from past combat exposure, resulting in a lack of emotions. Additionally, they are experiencing mild hallucinations."
+ scan_desc = "PTSD"
+ gain_text = span_warning("You're thrust back into the chaos of past! Explosions! Gunfire! Emotions, gone AWOL!")
+ lose_text = span_notice("You feel flashbacks of past fade, as your emotions return and mind clear.")
+ resilience = TRAUMA_RESILIENCE_ABSOLUTE
+ can_gain = TRUE
+ random_gain = FALSE
+ /// Our cooldown declare for causing hallucinations
+ COOLDOWN_DECLARE(ptsd_hallucinations)
+ var/list/ptsd_hallucinations_list = list(
+ /datum/hallucination/fake_sound/normal/boom,
+ /datum/hallucination/fake_sound/normal/distant_boom,
+ /datum/hallucination/stray_bullet,
+ /datum/hallucination/battle/gun/disabler,
+ /datum/hallucination/battle/gun/laser,
+ /datum/hallucination/battle/bomb,
+ /datum/hallucination/battle/e_sword,
+ /datum/hallucination/battle/harm_baton,
+ /datum/hallucination/battle/stun_prod,
+ )
+
+/datum/brain_trauma/special/ptsd/on_life(seconds_per_tick, times_fired)
+ if(owner.stat != CONSCIOUS)
+ return
+
+ if(!COOLDOWN_FINISHED(src, ptsd_hallucinations))
+ return
+
+ owner.cause_hallucination(pick(ptsd_hallucinations_list), "Caused by The Combat PTSD brain trauma")
+ COOLDOWN_START(src, ptsd_hallucinations, rand(10 SECONDS, 10 MINUTES))
+
+/datum/brain_trauma/special/ptsd/on_gain()
+ owner.add_mood_event("combat_ptsd", /datum/mood_event/desentized)
+ owner.mob_mood?.mood_modifier -= 1 //Basically nothing can change your mood
+ owner.mob_mood?.sanity_level = SANITY_DISTURBED //Makes sanity on a unstable level unless cured
+ ..()
+
+/datum/brain_trauma/special/ptsd/on_lose()
+ owner.clear_mood_event("combat_ptsd")
+ owner.mob_mood?.mood_modifier += 1
+ owner.mob_mood?.sanity_level = SANITY_GREAT
+ return ..()
diff --git a/code/datums/components/attached_sticker.dm b/code/datums/components/attached_sticker.dm
deleted file mode 100644
index 49541a6b37c474e..000000000000000
--- a/code/datums/components/attached_sticker.dm
+++ /dev/null
@@ -1,78 +0,0 @@
-// The attached sticker
-
-/datum/component/attached_sticker
- dupe_mode = COMPONENT_DUPE_ALLOWED
- ///The overlay we apply to things we stick to
- var/mutable_appearance/sticker_overlay
- ///The turf our COMSIG_TURF_EXPOSE is registered to, so we can unregister it later.
- var/turf/signal_turf
- ///Our physical sticker to drop
- var/obj/item/sticker
- ///Can we be washed off?
- var/washable = TRUE
-
-/datum/component/attached_sticker/Initialize(px, py, obj/stick, mob/living/user, cleanable=TRUE)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
- washable = cleanable
- var/atom/atom_parent = parent
- sticker = stick
- sticker_overlay = mutable_appearance(stick.icon, stick.icon_state , layer = atom_parent.layer + 1, appearance_flags = RESET_COLOR | PIXEL_SCALE)
- sticker_overlay.pixel_x = px
- sticker_overlay.pixel_y = py
- atom_parent.add_overlay(sticker_overlay)
- if(isliving(parent) && user)
- var/mob/living/victim = parent
- if(victim.client)
- user.log_message("stuck [sticker] to [key_name(victim)]", LOG_ATTACK)
- victim.log_message("had [sticker] stuck to them by [key_name(user)]", LOG_ATTACK)
- else if(isturf(parent) && (sticker.resistance_flags & FLAMMABLE))
- //register signals on the users turf instead because we can assume they are on flooring sticking it to a wall so it should burn (otherwise it would fruitlessly check wall temperature)
- signal_turf = (user && isclosedturf(parent)) ? get_turf(user) : parent
- RegisterSignal(signal_turf, COMSIG_TURF_EXPOSE, PROC_REF(on_turf_expose))
- sticker.moveToNullspace()
- RegisterSignal(sticker, COMSIG_QDELETING, PROC_REF(peel))
-
-/datum/component/attached_sticker/Destroy()
- var/atom/as_atom = parent
- as_atom.cut_overlay(sticker_overlay)
- sticker_overlay = null
- if(sticker)
- QDEL_NULL(sticker)
- return ..()
-
-///Move sticker item from nullspace, delete this component, cut overlay
-/datum/component/attached_sticker/proc/peel(atom/source)
- SIGNAL_HANDLER
- if(!QDELETED(sticker))
- var/atom/as_atom = parent
- sticker.forceMove(isturf(as_atom) ? as_atom : as_atom.drop_location())
- sticker.pixel_y = rand(-4,1)
- sticker.pixel_x = rand(-3,3)
- sticker = null
- if(!QDELETED(src))
- qdel(src)
-
-/datum/component/attached_sticker/RegisterWithParent()
- if(sticker.resistance_flags & FLAMMABLE)
- RegisterSignal(parent, COMSIG_LIVING_IGNITED, PROC_REF(peel))
- if(washable)
- RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(peel))
- RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(peel))
- ADD_TRAIT(parent, TRAIT_STICKERED, REF(sticker))
-
-/datum/component/attached_sticker/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_LIVING_IGNITED, COMSIG_QDELETING))
- if(signal_turf)
- UnregisterSignal(signal_turf, COMSIG_TURF_EXPOSE)
- signal_turf = null
- if(washable)
- UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
- REMOVE_TRAIT(parent, TRAIT_STICKERED, REF(sticker))
-
-///Signal handler for COMSIG_TURF_EXPOSE, deletes this sticker if the temperature is above 100C and it is flammable
-/datum/component/attached_sticker/proc/on_turf_expose(datum/source, datum/gas_mixture/air, exposed_temperature)
- SIGNAL_HANDLER
- if(exposed_temperature <= FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
- return
- peel()
diff --git a/code/datums/components/bullet_intercepting.dm b/code/datums/components/bullet_intercepting.dm
index c176de54b94c5df..32e757c1823e136 100644
--- a/code/datums/components/bullet_intercepting.dm
+++ b/code/datums/components/bullet_intercepting.dm
@@ -12,8 +12,10 @@
var/mob/wearer
/// Callback called when we catch a projectile
var/datum/callback/on_intercepted
+ /// Number of things we can block before we delete ourself (stop being able to block)
+ var/block_charges = INFINITY
-/datum/component/bullet_intercepting/Initialize(block_chance = 2, block_type = BULLET, active_slots, datum/callback/on_intercepted)
+/datum/component/bullet_intercepting/Initialize(block_chance = 2, block_type = BULLET, active_slots, datum/callback/on_intercepted, block_charges = INFINITY)
. = ..()
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
@@ -21,6 +23,7 @@
src.block_type = block_type
src.active_slots = active_slots
src.on_intercepted = on_intercepted
+ src.block_charges = block_charges
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_parent_equipped))
RegisterSignal(parent, COMSIG_ITEM_PRE_UNEQUIP, PROC_REF(on_unequipped))
@@ -55,11 +58,14 @@
/// Called when wearer is shot, check if we're going to block the hit
/datum/component/bullet_intercepting/proc/on_wearer_shot(mob/living/victim, list/signal_args, obj/projectile/bullet)
SIGNAL_HANDLER
- if (victim != wearer || victim.stat == DEAD || bullet.armor_flag != block_type )
- return
+ if (victim != wearer || victim.stat == DEAD || bullet.armor_flag != block_type)
+ return NONE
if (!prob(block_chance))
- return
+ return NONE
on_intercepted?.Invoke(victim, bullet)
+ block_charges--
+ if (block_charges <= 0)
+ qdel(src)
return PROJECTILE_INTERRUPT_HIT
/// Called when wearer is deleted, stop tracking them
diff --git a/code/datums/components/crafting/entertainment.dm b/code/datums/components/crafting/entertainment.dm
index 8b2bfa407315c36..dae810c220ae1ec 100644
--- a/code/datums/components/crafting/entertainment.dm
+++ b/code/datums/components/crafting/entertainment.dm
@@ -1,3 +1,22 @@
+/datum/crafting_recipe/moffers
+ name = "Moffers"
+ result = /obj/item/clothing/shoes/clown_shoes/moffers
+ time = 6 SECONDS //opportunity to rethink your life
+ reqs = list(
+ /obj/item/stack/sheet/animalhide/mothroach = 2,
+ /obj/item/clothing/shoes/clown_shoes = 1,
+ )
+ parts = list(/obj/item/clothing/shoes/clown_shoes = 1)
+ blacklist = list(
+ /obj/item/clothing/shoes/clown_shoes/combat,
+ /obj/item/clothing/shoes/clown_shoes/banana_shoes,
+ /obj/item/clothing/shoes/clown_shoes/banana_shoes/combat,
+ /obj/item/clothing/shoes/clown_shoes/jester,
+ /obj/item/clothing/shoes/clown_shoes/meown_shoes,
+ /obj/item/clothing/shoes/clown_shoes/moffers,
+ )
+ category = CAT_ENTERTAINMENT
+
/datum/crafting_recipe/mothplush
name = "Moth Plushie"
result = /obj/item/toy/plush/moth
@@ -8,6 +27,16 @@
)
category = CAT_ENTERTAINMENT
+/datum/crafting_recipe/sharkplush
+ name = "Shark Plushie"
+ result = /obj/item/toy/plush/shark
+ reqs = list(
+ /obj/item/clothing/suit/hooded/shark_costume = 1,
+ /obj/item/grown/cotton = 10,
+ /obj/item/stack/sheet/cloth = 5,
+ )
+ category = CAT_ENTERTAINMENT
+
/datum/crafting_recipe/mixedbouquet
name = "Mixed bouquet"
result = /obj/item/bouquet
diff --git a/code/datums/components/crafting/equipment.dm b/code/datums/components/crafting/equipment.dm
index 741d53ed56b6c13..e7971488d638fbc 100644
--- a/code/datums/components/crafting/equipment.dm
+++ b/code/datums/components/crafting/equipment.dm
@@ -83,6 +83,17 @@
time = 5 SECONDS
category = CAT_EQUIPMENT
+/datum/crafting_recipe/barbeque_grill
+ name = "Barbeque grill"
+ result = /obj/machinery/grill
+ reqs = list(
+ /obj/item/stack/sheet/iron = 5,
+ /obj/item/stack/rods = 5,
+ /obj/item/assembly/igniter = 1,
+ )
+ time = 7 SECONDS
+ category = CAT_EQUIPMENT
+
/datum/crafting_recipe/secure_closet
name = "Secure Closet"
result = /obj/structure/closet/secure_closet
diff --git a/code/datums/components/crafting/tailoring.dm b/code/datums/components/crafting/tailoring.dm
index bb01a4d78dc0f03..56a6bfb2c3f18f0 100644
--- a/code/datums/components/crafting/tailoring.dm
+++ b/code/datums/components/crafting/tailoring.dm
@@ -345,6 +345,27 @@
)
category = CAT_CLOTHING
+/datum/crafting_recipe/shark_costume
+ name = "shark costume"
+ result = /obj/item/clothing/suit/hooded/shark_costume
+ time = 2 SECONDS
+ reqs = list(
+ /obj/item/stack/sheet/leather = 5,
+ /obj/item/stack/sheet/animalhide/carp = 5,
+ )
+ category = CAT_CLOTHING
+
+/datum/crafting_recipe/shork_costume
+ name = "shork costume"
+ result = /obj/item/clothing/suit/hooded/shork_costume
+ time = 2 SECONDS
+ tool_behaviors = list(TOOL_WIRECUTTER)
+ reqs = list(
+ /obj/item/clothing/suit/hooded/shark_costume = 1,
+ )
+ category = CAT_CLOTHING
+
+
/datum/crafting_recipe/sturdy_shako
name = "Sturdy Shako"
result = /obj/item/clothing/head/hats/hos/shako
diff --git a/code/datums/components/crank_recharge.dm b/code/datums/components/crank_recharge.dm
index 455fa9298f908d0..9c2a1737658e274 100644
--- a/code/datums/components/crank_recharge.dm
+++ b/code/datums/components/crank_recharge.dm
@@ -2,6 +2,8 @@
/datum/component/crank_recharge
/// Our cell to charge
var/obj/item/stock_parts/cell/charging_cell
+ /// Whether we spin our gun to reload (and therefore need the relevant trait)
+ var/spin_to_win = FALSE
/// How much charge we give our cell on each crank
var/charge_amount
/// How long is the cooldown time between each charge
@@ -14,13 +16,14 @@
var/is_charging = FALSE
COOLDOWN_DECLARE(charge_sound_cooldown)
-/datum/component/crank_recharge/Initialize(charging_cell, charge_amount = 500, cooldown_time = 2 SECONDS, charge_sound = 'sound/weapons/laser_crank.ogg', charge_sound_cooldown_time = 1.8 SECONDS)
+/datum/component/crank_recharge/Initialize(charging_cell, spin_to_win = FALSE, charge_amount = 500, cooldown_time = 2 SECONDS, charge_sound = 'sound/weapons/laser_crank.ogg', charge_sound_cooldown_time = 1.8 SECONDS)
. = ..()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
if(isnull(charging_cell) || !istype(charging_cell, /obj/item/stock_parts/cell))
return COMPONENT_INCOMPATIBLE
src.charging_cell = charging_cell
+ src.spin_to_win = spin_to_win
src.charge_amount = charge_amount
src.cooldown_time = cooldown_time
src.charge_sound = charge_sound
@@ -45,6 +48,10 @@
return
if(is_charging)
return
+ if(spin_to_win && !HAS_TRAIT(user, TRAIT_GUNFLIP))
+ source.balloon_alert(user, "need holster to spin!")
+ return
+
is_charging = TRUE
if(COOLDOWN_FINISHED(src, charge_sound_cooldown))
COOLDOWN_START(src, charge_sound_cooldown, charge_sound_cooldown_time)
@@ -57,4 +64,6 @@
SEND_SIGNAL(parent, COMSIG_UPDATE_AMMO_HUD) // SKYRAT EDIT ADDITION - AMMO COUNT HUD
source.update_appearance()
is_charging = FALSE
+ if(spin_to_win)
+ source.SpinAnimation(4, 2) //What a badass
source.balloon_alert(user, "charged")
diff --git a/code/datums/components/echolocation.dm b/code/datums/components/echolocation.dm
index 5e4f7528415d794..13ef8f4ed243159 100644
--- a/code/datums/components/echolocation.dm
+++ b/code/datums/components/echolocation.dm
@@ -4,11 +4,11 @@
/// Time between echolocations.
var/cooldown_time = 1.8 SECONDS
/// Time for the image to start fading out.
- var/image_expiry_time = 1.5 SECONDS
+ var/image_expiry_time = 1.4 SECONDS
/// Time for the image to fade in.
- var/fade_in_time = 0.5 SECONDS
+ var/fade_in_time = 0.4 SECONDS
/// Time for the image to fade out and delete itself.
- var/fade_out_time = 0.5 SECONDS
+ var/fade_out_time = 0.4 SECONDS
/// Are images static? If yes, spawns them on the turf and makes them not change location. Otherwise they change location and pixel shift with the original.
var/images_are_static = TRUE
/// With mobs that have this echo group in their echolocation receiver trait, we share echo images.
@@ -32,7 +32,7 @@
/// Cooldown for the echolocation.
COOLDOWN_DECLARE(cooldown_last)
-/datum/component/echolocation/Initialize(echo_range, cooldown_time, image_expiry_time, fade_in_time, fade_out_time, images_are_static, blocking_trait, echo_group, echo_icon, color_path)
+/datum/component/echolocation/Initialize(echo_range, cooldown_time, image_expiry_time, fade_in_time, fade_out_time, images_are_static, blocking_trait, echo_group, echo_icon = "echo", color_path)
. = ..()
var/mob/living/echolocator = parent
if(!istype(echolocator))
diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm
index 7ee368e543e3487..2763d583f819c93 100644
--- a/code/datums/components/fishing_spot.dm
+++ b/code/datums/components/fishing_spot.dm
@@ -16,6 +16,7 @@
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(handle_attackby))
RegisterSignal(parent, COMSIG_FISHING_ROD_CAST, PROC_REF(handle_cast))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more))
/datum/component/fishing_spot/Destroy()
fish_source = null
diff --git a/code/datums/components/food/ice_cream_holder.dm b/code/datums/components/food/ice_cream_holder.dm
index f3db67efab5c0ae..4161c749703609c 100644
--- a/code/datums/components/food/ice_cream_holder.dm
+++ b/code/datums/components/food/ice_cream_holder.dm
@@ -58,6 +58,7 @@
RegisterSignal(owner, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(on_item_attack_obj))
RegisterSignal(owner, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
+ RegisterSignal(owner, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
if(change_name)
RegisterSignal(owner, COMSIG_ATOM_UPDATE_NAME, PROC_REF(on_update_name))
if(!change_desc)
@@ -105,20 +106,27 @@
else
source.desc = replacetext(replacetext("[flavour.desc_prefix] [flavour.desc]", "$CONE_NAME", initial(source.name)), "$CUSTOM_NAME", key)
else /// Many flavours.
- source.desc = "A delicious [initial(source.name)] filled with scoops of [english_list(scoops)] icecream. That's as many as [scoops_len] scoops!"
+ source.desc = "A delicious [initial(source.name)] filled with scoops of [english_list(scoops)] ice cream. That's as many as [scoops_len] scoops!"
+
+/datum/component/ice_cream_holder/proc/on_examine(atom/source, mob/mob, list/examine_list)
+ SIGNAL_HANDLER
+ if(length(scoops) < max_scoops)
+ examine_list += span_tinynoticeital("you could use a ice cream vat to fill it with yummy ice cream...")
/datum/component/ice_cream_holder/proc/on_examine_more(atom/source, mob/mob, list/examine_list)
SIGNAL_HANDLER
var/scoops_len = length(scoops)
+ if(!scoops_len)
+ return
if(scoops_len == 1 || length(unique_list(scoops)) == 1) /// Only one flavour.
var/key = scoops[1]
var/datum/ice_cream_flavour/flavour = GLOB.ice_cream_flavours[LAZYACCESS(special_scoops, key) || key]
if(flavour?.desc) //I scream.
- examine_list += "[source.p_Theyre()] filled with scoops of [flavour ? flavour.name : "broken, unhappy"] icecream."
+ examine_list += "[source.p_Theyre()] filled with scoops of [flavour ? flavour.name : "broken, unhappy"] ice cream."
else
examine_list += replacetext(replacetext("[source.p_Theyre()] [flavour.desc]", "$CONE_NAME", initial(source.name)), "$CUSTOM_NAME", key)
else /// Many flavours.
- examine_list += "[source.p_Theyre()] filled with scoops of [english_list(scoops)] icecream. That's as many as [scoops_len] scoops!"
+ examine_list += "[source.p_Theyre()] filled with scoops of [english_list(scoops)] ice cream. That's as many as [scoops_len] scoops!"
/datum/component/ice_cream_holder/proc/on_update_overlays(atom/source, list/new_overlays)
SIGNAL_HANDLER
@@ -330,14 +338,14 @@ GLOBAL_LIST_INIT_TYPED(ice_cream_flavours, /datum/ice_cream_flavour, init_ice_cr
/datum/ice_cream_flavour/custom
name = ICE_CREAM_CUSTOM
color = COLOR_STARLIGHT //has its own mutable appearance overlay it will be overwritten with anyways.
- desc = "filled with artisanal icecream. Made with real $CUSTOM_NAME. Ain't that something."
+ desc = "filled with artisanal ice cream. Made with real $CUSTOM_NAME. Ain't that something."
ingredients = list(/datum/reagent/consumable/milk, /datum/reagent/consumable/ice)
ingredients_text = "optional flavorings"
takes_custom_ingredients = TRUE
/datum/ice_cream_flavour/custom/korta
name = ICE_CREAM_KORTA_CUSTOM
- desc = "filled with artisanal lizard-friendly icecream. Made with real $CUSTOM_NAME. Ain't that something."
+ desc = "filled with artisanal lizard-friendly ice cream. Made with real $CUSTOM_NAME. Ain't that something."
ingredients = list(/datum/reagent/consumable/korta_milk, /datum/reagent/consumable/ice)
ingredients_text = "optional flavorings"
@@ -354,7 +362,7 @@ GLOBAL_LIST_INIT_TYPED(ice_cream_flavours, /datum/ice_cream_flavour, init_ice_cr
/datum/ice_cream_flavour/bland
name = ICE_CREAM_BLAND
color = COLOR_ICECREAM_CUSTOM
- desc = "filled with anemic, flavorless icecream. You wonder why this was ever scooped..."
+ desc = "filled with anemic, flavorless ice cream. You wonder why this was ever scooped..."
hidden = TRUE
#undef SWEETENER_PER_SCOOP
diff --git a/code/datums/components/material/remote_materials.dm b/code/datums/components/material/remote_materials.dm
index e418d4276be1046..568b018e58b2b0a 100644
--- a/code/datums/components/material/remote_materials.dm
+++ b/code/datums/components/material/remote_materials.dm
@@ -84,23 +84,9 @@ handles linking back and forth.
silo = null
- var/static/list/allowed_mats = list(
- /datum/material/iron,
- /datum/material/glass,
- /datum/material/silver,
- /datum/material/gold,
- /datum/material/diamond,
- /datum/material/plasma,
- /datum/material/uranium,
- /datum/material/bananium,
- /datum/material/titanium,
- /datum/material/bluespace,
- /datum/material/plastic,
- )
-
mat_container = parent.AddComponent( \
/datum/component/material_container, \
- allowed_mats, \
+ SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \
local_size, \
mat_container_flags, \
container_signals = mat_container_signals, \
diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm
index efff82b703a6caa..19c7528db8bf3c0 100644
--- a/code/datums/components/overlay_lighting.dm
+++ b/code/datums/components/overlay_lighting.dm
@@ -398,7 +398,7 @@
return
if(current_holder && overlay_lighting_flags & LIGHTING_ON)
current_holder.underlays -= cone
- cone.alpha = min(200, (abs(new_power) * 90)+20)
+ cone.alpha = min(120, (abs(new_power) * 60) + 15)
if(current_holder && overlay_lighting_flags & LIGHTING_ON)
current_holder.underlays += cone
diff --git a/code/datums/components/payment.dm b/code/datums/components/payment.dm
index 1220614e9c3864d..5e79b28fd57246d 100644
--- a/code/datums/components/payment.dm
+++ b/code/datums/components/payment.dm
@@ -19,11 +19,6 @@
var/datum/bank_account/target_acc
///Does this payment component respect same-department-discount?
var/department_discount = FALSE
- ///A static typecache of all the money-based items that can be actively used as currency.
- var/static/list/allowed_money = typecacheof(list(
- /obj/item/stack/spacecash,
- /obj/item/holochip,
- /obj/item/coin))
/datum/component/payment/Initialize(_cost, _target, _style)
target_acc = _target
@@ -80,13 +75,13 @@
//Here is all the possible non-ID payment methods.
var/list/counted_money = list()
var/physical_cash_total = 0
- for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), allowed_money)) //Coins, cash, and credits.
+ for(var/obj/item/credit in typecache_filter_list(user.get_all_contents(), GLOB.allowed_money)) //Coins, cash, and credits.
if(physical_cash_total > total_cost)
break
physical_cash_total += credit.get_item_credit_value()
counted_money += credit
- if(is_type_in_typecache(user.pulling, allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled).
+ if(is_type_in_typecache(user.pulling, GLOB.allowed_money) && (physical_cash_total < total_cost)) //Coins(Pulled).
var/obj/item/counted_credit = user.pulling
physical_cash_total += counted_credit.get_item_credit_value()
counted_money += counted_credit
@@ -134,9 +129,11 @@
* Attempts to charge a mob, user, an integer number of credits, total_cost, directly from an ID card/bank account.
*/
/datum/component/payment/proc/handle_card(mob/living/user, obj/item/card/id/idcard, total_cost)
- var/atom/atom_parent = parent
+ var/atom/movable/atom_parent = parent
if(!idcard)
+ if(transaction_style == PAYMENT_VENDING)
+ to_chat(user, span_warning("No card found."))
return FALSE
if(!idcard?.registered_account)
switch(transaction_style)
@@ -146,6 +143,13 @@
to_chat(user, span_warning("ARE YOU JOKING. YOU DON'T HAVE A BANK ACCOUNT ON YOUR ID YOU IDIOT."))
if(PAYMENT_CLINICAL)
to_chat(user, span_warning("ID Card lacks a bank account. Advancing."))
+ if(PAYMENT_VENDING)
+ to_chat(user, span_warning("No account found."))
+
+ return FALSE
+
+ if(!idcard.registered_account.account_job)
+ atom_parent.say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
return FALSE
if(!(idcard.registered_account.has_money(total_cost)))
@@ -156,6 +160,8 @@
to_chat(user, span_warning("YOU MORON. YOU ABSOLUTE BAFOON. YOU INSUFFERABLE TOOL. YOU ARE POOR."))
if(PAYMENT_CLINICAL)
to_chat(user, span_warning("ID Card lacks funds. Aborting."))
+ if(PAYMENT_VENDING)
+ to_chat(user, span_warning("You do not possess the funds to purchase that."))
atom_parent.balloon_alert(user, "needs [total_cost] credit\s!")
return FALSE
target_acc.transfer_money(idcard.registered_account, total_cost, "Nanotrasen: Usage of Corporate Machinery")
diff --git a/code/datums/components/riding/riding_vehicle.dm b/code/datums/components/riding/riding_vehicle.dm
index 2a556ac8944c560..d6c5a665d1557eb 100644
--- a/code/datums/components/riding/riding_vehicle.dm
+++ b/code/datums/components/riding/riding_vehicle.dm
@@ -169,6 +169,8 @@
/datum/component/riding/vehicle/scooter/skateboard
vehicle_move_delay = 1.5
ride_check_flags = RIDER_NEEDS_LEGS | UNBUCKLE_DISABLED_RIDER
+ ///If TRUE, the vehicle will be slower (but safer) to ride on walk intent.
+ var/can_slow_down = TRUE
/datum/component/riding/vehicle/scooter/skateboard/handle_specials()
. = ..()
@@ -177,8 +179,82 @@
set_vehicle_dir_layer(EAST, OBJ_LAYER)
set_vehicle_dir_layer(WEST, OBJ_LAYER)
+/datum/component/riding/vehicle/scooter/skateboard/RegisterWithParent()
+ . = ..()
+ if(can_slow_down)
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ var/obj/vehicle/ridden/scooter/skateboard/board = parent
+ if(istype(board))
+ board.can_slow_down = can_slow_down
+
+/datum/component/riding/vehicle/scooter/skateboard/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ examine_list += span_notice("Going slow and nice at [EXAMINE_HINT("walk")] speed will prevent crashing into things.")
+
+/datum/component/riding/vehicle/scooter/skateboard/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
+ . = ..()
+ if(can_slow_down)
+ RegisterSignal(rider, COMSIG_MOVE_INTENT_TOGGLED, PROC_REF(toggle_move_delay))
+ toggle_move_delay(rider)
+
+/datum/component/riding/vehicle/scooter/skateboard/handle_unbuckle(mob/living/rider)
+ . = ..()
+ if(can_slow_down)
+ toggle_move_delay(rider)
+ UnregisterSignal(rider, COMSIG_MOVE_INTENT_TOGGLED)
+
+/datum/component/riding/vehicle/scooter/skateboard/proc/toggle_move_delay(mob/living/rider)
+ SIGNAL_HANDLER
+ vehicle_move_delay = initial(vehicle_move_delay)
+ if(rider.move_intent == MOVE_INTENT_WALK)
+ vehicle_move_delay += 0.6
+
+/datum/component/riding/vehicle/scooter/skateboard/pro
+ vehicle_move_delay = 1
+
+///This one lets the rider ignore gravity, move in zero g and son on, but only on ground turfs or at most one z-level above them.
+/datum/component/riding/vehicle/scooter/skateboard/hover
+ vehicle_move_delay = 1
+ override_allow_spacemove = TRUE
+
+/datum/component/riding/vehicle/scooter/skateboard/hover/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(check_grav))
+ RegisterSignal(parent, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(check_drifting))
+ hover_check()
+
+///Makes sure that the vehicle is grav-less if capable of zero-g movement. Forced gravity will honestly screw this.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/check_grav(datum/source, turf/gravity_turf, list/gravs)
+ SIGNAL_HANDLER
+ if(override_allow_spacemove)
+ gravs += 0
+
+///Makes sure the vehicle isn't drifting while it can be maneuvered.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/check_drifting(datum/source, movement_dir, continuous_move)
+ SIGNAL_HANDLER
+ if(override_allow_spacemove)
+ return COMSIG_MOVABLE_STOP_SPACEMOVE
+
+/datum/component/riding/vehicle/scooter/skateboard/hover/vehicle_moved(atom/movable/source, oldloc, dir, forced)
+ . = ..()
+ hover_check(TRUE)
+
+///Makes sure that the hoverboard can move in zero-g in (open) space but only there's a ground turf on the z-level below.
+/datum/component/riding/vehicle/scooter/skateboard/hover/proc/hover_check(is_moving = FALSE)
+ var/atom/movable/movable = parent
+ if(!isopenspaceturf(movable.loc))
+ override_allow_spacemove = TRUE
+ return
+ var/turf/open/our_turf = movable.loc
+ var/turf/turf_below = GET_TURF_BELOW(our_turf)
+ if(our_turf.zPassOut(DOWN) && (isnull(turf_below) || (isopenspaceturf(turf_below) && turf_below.zPassIn(DOWN) && turf_below.zPassOut(DOWN))))
+ override_allow_spacemove = FALSE
+ if(turf_below)
+ our_turf.zFall(movable, falling_from_move = is_moving)
+
/datum/component/riding/vehicle/scooter/skateboard/wheelys
vehicle_move_delay = 1.75 // SKYRAT EDIT - ORIGINAL: 0
+ can_slow_down = FALSE
/datum/component/riding/vehicle/scooter/skateboard/wheelys/handle_specials()
. = ..()
diff --git a/code/datums/components/shrink.dm b/code/datums/components/shrink.dm
index 67cd3d39e23cd79..d2615ea2f777057 100644
--- a/code/datums/components/shrink.dm
+++ b/code/datums/components/shrink.dm
@@ -15,6 +15,7 @@
if(isliving(parent_atom))
var/mob/living/L = parent_atom
ADD_TRAIT(L, TRAIT_UNDENSE, SHRUNKEN_TRAIT)
+ RegisterSignal(L, COMSIG_MOB_SAY, PROC_REF(handle_shrunk_speech))
L.add_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
if(iscarbon(L))
var/mob/living/carbon/C = L
@@ -30,6 +31,10 @@
span_userdanger("Everything grows bigger!"))
QDEL_IN(src, shrink_time)
+/datum/component/shrink/proc/handle_shrunk_speech(mob/living/little_guy, list/speech_args)
+ SIGNAL_HANDLER
+ speech_args[SPEECH_SPANS] |= SPAN_SMALL_VOICE
+
/datum/component/shrink/Destroy()
var/atom/parent_atom = parent
parent_atom.transform = parent_atom.transform.Scale(2,2)
@@ -38,6 +43,7 @@
var/mob/living/L = parent_atom
L.remove_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
REMOVE_TRAIT(L, TRAIT_UNDENSE, SHRUNKEN_TRAIT)
+ UnregisterSignal(L, COMSIG_MOB_SAY)
if(ishuman(L))
var/mob/living/carbon/human/H = L
H.physiology.damage_resistance += 100
diff --git a/code/datums/components/sizzle.dm b/code/datums/components/sizzle.dm
index ce91e9593f75f7d..957586aa806b8e0 100644
--- a/code/datums/components/sizzle.dm
+++ b/code/datums/components/sizzle.dm
@@ -1,26 +1,31 @@
/datum/component/sizzle
- var/mutable_appearance/sizzling
- var/sizzlealpha = 0
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
-/datum/component/sizzle/Initialize()
+ ///The sizzling appearance to put on top of the food item
+ var/mutable_appearance/sizzling
+ ///The amount of time the food item has been sizzling for
+ var/grilled_time = 0
+
+/datum/component/sizzle/Initialize(grilled_time)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
- setup_sizzle()
-
-/datum/component/sizzle/InheritComponent(datum/component/C, i_am_original)
- var/atom/food = parent
- sizzlealpha += 5
- sizzling.alpha = sizzlealpha
- food.cut_overlay(sizzling)
- food.add_overlay(sizzling)
-/datum/component/sizzle/proc/setup_sizzle()
var/atom/food = parent
var/icon/grill_marks = icon(food.icon, food.icon_state)
grill_marks.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
grill_marks.Blend(icon('icons/obj/machines/kitchen.dmi', "grillmarks"), ICON_MULTIPLY) //adds grill marks and the remaining white areas become transparent
sizzling = new(grill_marks)
- sizzling.alpha = sizzlealpha
food.add_overlay(sizzling)
+ src.grilled_time = grilled_time
+
+/datum/component/sizzle/InheritComponent(datum/component/C, i_am_original, grilled_time)
+ var/atom/food = parent
+ sizzling.alpha += 5
+ food.cut_overlay(sizzling)
+ food.add_overlay(sizzling)
+ src.grilled_time = grilled_time
+
+///Returns how long the food item has been sizzling for
+/datum/component/sizzle/proc/time_elapsed()
+ return src.grilled_time
diff --git a/code/datums/components/sticker.dm b/code/datums/components/sticker.dm
new file mode 100644
index 000000000000000..0562f6048077ea3
--- /dev/null
+++ b/code/datums/components/sticker.dm
@@ -0,0 +1,111 @@
+/**
+ * ### Sticker component
+ *
+ * Component that draws supplied atom's icon over parent object with specified offset,
+ * icon centering is handled inside.
+ */
+/datum/component/sticker
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+
+ /// Either `turf` or `null`, used to connect to `COMSIG_TURF_EXPOSE` signal when parent is a turf.
+ var/turf/listening_turf
+ /// Refernce to a "stickered" atom.
+ var/atom/movable/our_sticker
+ /// Reference to the created overlay, used during component deletion.
+ var/mutable_appearance/sticker_overlay
+
+/datum/component/sticker/Initialize(atom/stickering_atom, mob/user, dir = NORTH, px = 0, py = 0)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.our_sticker = our_sticker
+
+ if(isliving(parent) && !isnull(user))
+ var/mob/living/victim = parent
+
+ if(!isnull(victim.client))
+ user.log_message("stuck [stickering_atom] to [key_name(victim)]", LOG_ATTACK)
+ victim.log_message("had [stickering_atom] stuck to them by [key_name(user)]", LOG_ATTACK)
+
+ stick(stickering_atom, px, py)
+ register_turf_signals(dir)
+
+/datum/component/sticker/Destroy(force)
+ var/atom/parent_atom = parent
+ parent_atom.cut_overlay(sticker_overlay)
+
+ unregister_turf_signals()
+
+ REMOVE_TRAIT(parent, TRAIT_STICKERED, REF(src))
+
+ QDEL_NULL(our_sticker)
+ QDEL_NULL(sticker_overlay)
+ return ..()
+
+/datum/component/sticker/RegisterWithParent()
+ if(isliving(parent))
+ RegisterSignal(parent, COMSIG_LIVING_IGNITED, PROC_REF(on_ignite))
+ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
+
+/datum/component/sticker/UnregisterFromParent()
+ if(isliving(parent))
+ UnregisterSignal(parent, COMSIG_LIVING_IGNITED)
+ UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
+
+/// Subscribes to `COMSIG_TURF_EXPOSE` if parent atom is a turf. If turf is closed - subscribes to signal
+/datum/component/sticker/proc/register_turf_signals(dir)
+ if(!isturf(parent))
+ return
+
+ listening_turf = isclosedturf(parent) ? get_step(parent, dir) : parent
+ RegisterSignal(listening_turf, COMSIG_TURF_EXPOSE, PROC_REF(on_turf_expose))
+
+/// Unsubscribes from `COMSIG_TURF_EXPOSE` if `listening_turf` is not `null`.
+/datum/component/sticker/proc/unregister_turf_signals()
+ if(isnull(listening_turf))
+ return
+
+ UnregisterSignal(listening_turf, COMSIG_TURF_EXPOSE)
+
+/// Handles overlay creation from supplied atom, adds created icon to the parent object, moves source atom to the nullspace.
+/datum/component/sticker/proc/stick(atom/movable/stickering_atom, px, py)
+ our_sticker = stickering_atom
+ our_sticker.moveToNullspace()
+
+ var/atom/parent_atom = parent
+
+ sticker_overlay = mutable_appearance(icon = our_sticker.icon, icon_state = our_sticker.icon_state, layer = parent_atom.layer + 0.01, appearance_flags = RESET_COLOR)
+ sticker_overlay.pixel_w = px - world.icon_size / 2
+ sticker_overlay.pixel_z = py - world.icon_size / 2
+
+ parent_atom.add_overlay(sticker_overlay)
+
+ ADD_TRAIT(parent, TRAIT_STICKERED, REF(src))
+
+/// Moves stickered atom from the nullspace, deletes component.
+/datum/component/sticker/proc/peel()
+ var/atom/parent_atom = parent
+ var/turf/drop_location = isnull(listening_turf) ? parent_atom.drop_location() : listening_turf
+
+ our_sticker.forceMove(drop_location)
+ our_sticker = null
+
+ qdel(src)
+
+/datum/component/sticker/proc/on_ignite(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/datum/component/sticker/proc/on_clean(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ peel()
+
+ return COMPONENT_CLEANED
+
+/datum/component/sticker/proc/on_turf_expose(datum/source, datum/gas_mixture/air, exposed_temperature)
+ SIGNAL_HANDLER
+
+ if(exposed_temperature >= FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
+ qdel(src)
diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm
index d50b9736e8f9859..8b532277e34af33 100644
--- a/code/datums/components/style/style.dm
+++ b/code/datums/components/style/style.dm
@@ -96,8 +96,6 @@
if(multitooled)
src.multitooled = multitooled
- RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_parent_multitool))
-
ADD_TRAIT(mob_parent, TRAIT_STYLISH, REF(src)) // SKYRAT EDIT ADD - allows style meter chads to do flips
/datum/component/style/RegisterWithParent()
@@ -345,12 +343,6 @@
INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, put_in_hands), target)
source.visible_message(span_notice("[source] quickly swaps [weapon] out with [target]!"), span_notice("You quickly swap [weapon] with [target]."))
-
-/datum/component/style/proc/on_parent_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes)
- multitooled = !multitooled
- user.balloon_alert(user, "meter [multitooled ? "" : "un"]hacked")
-
-
// Point givers
/datum/component/style/proc/on_punch(mob/living/carbon/human/punching_person, atom/attacked_atom, proximity)
SIGNAL_HANDLER
diff --git a/code/datums/components/style/style_meter.dm b/code/datums/components/style/style_meter.dm
index 8c5b5ea87df60bc..72688f41c527494 100644
--- a/code/datums/components/style/style_meter.dm
+++ b/code/datums/components/style/style_meter.dm
@@ -17,7 +17,6 @@
/obj/item/style_meter/Initialize(mapload)
. = ..()
meter_appearance = mutable_appearance(icon, icon_state)
- RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool))
/obj/item/style_meter/Destroy(force)
if(istype(loc, /obj/item/clothing/glasses))
@@ -28,28 +27,30 @@
. = ..()
. += span_notice("You feel like a multitool could be used on this.")
-/obj/item/style_meter/afterattack(atom/movable/attacked_atom, mob/user, proximity_flag, click_parameters)
- . = ..()
- if(!istype(attacked_atom, /obj/item/clothing/glasses))
- return
+/obj/item/style_meter/interact_with_atom(atom/interacting_with, mob/living/user)
+ if(!istype(interacting_with, /obj/item/clothing/glasses))
+ return NONE
- forceMove(attacked_atom)
- attacked_atom.add_overlay(meter_appearance)
- RegisterSignal(attacked_atom, COMSIG_ITEM_EQUIPPED, PROC_REF(check_wearing))
- RegisterSignal(attacked_atom, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
- RegisterSignal(attacked_atom, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
- RegisterSignal(attacked_atom, COMSIG_CLICK_ALT, PROC_REF(on_altclick))
- RegisterSignal(attacked_atom, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool))
+ . = ITEM_INTERACT_SUCCESS
+
+ forceMove(interacting_with)
+ interacting_with.add_overlay(meter_appearance)
+ RegisterSignal(interacting_with, COMSIG_ITEM_EQUIPPED, PROC_REF(check_wearing))
+ RegisterSignal(interacting_with, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+ RegisterSignal(interacting_with, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(interacting_with, COMSIG_CLICK_ALT, PROC_REF(on_altclick))
+ RegisterSignal(interacting_with, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(redirect_multitool))
balloon_alert(user, "style meter attached")
playsound(src, 'sound/machines/click.ogg', 30, TRUE)
- if(!iscarbon(attacked_atom.loc))
- return
+ if(!iscarbon(interacting_with.loc))
+ return .
- var/mob/living/carbon/carbon_wearer = attacked_atom.loc
- if(carbon_wearer.glasses != attacked_atom)
- return
+ var/mob/living/carbon/carbon_wearer = interacting_with.loc
+ if(carbon_wearer.glasses != interacting_with)
+ return .
style_meter = carbon_wearer.AddComponent(/datum/component/style, multitooled)
+ return .
/obj/item/style_meter/Moved(atom/old_loc, Dir, momentum_change)
. = ..()
@@ -98,15 +99,17 @@
return COMPONENT_CANCEL_CLICK_ALT
-
-/// Signal proc for when the glasses or the meter is multitooled
-/obj/item/style_meter/proc/on_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes)
+/obj/item/style_meter/multitool_act(mob/living/user, obj/item/tool)
multitooled = !multitooled
- if(style_meter)
- SEND_SIGNAL(style_meter, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), user, tool, recipes)
- else
- balloon_alert(user, "meter [multitooled ? "" : "un"]hacked")
+ balloon_alert(user, "meter [multitooled ? "" : "un"]hacked")
+ style_meter?.multitooled = multitooled
+ return ITEM_INTERACT_SUCCESS
+
+/// Redirect multitooling on our glasses to our style meter
+/obj/item/style_meter/proc/redirect_multitool(datum/source, mob/living/user, obj/item/tool, ...)
+ SIGNAL_HANDLER
+ return multitool_act(user, tool)
/// Unregister signals and just generally clean up ourselves after being removed from glasses
/obj/item/style_meter/proc/clean_up(atom/movable/old_location)
diff --git a/code/datums/components/surgery_initiator.dm b/code/datums/components/surgery_initiator.dm
index ab18408b62996a5..360e7ab9b51cc8f 100644
--- a/code/datums/components/surgery_initiator.dm
+++ b/code/datums/components/surgery_initiator.dm
@@ -339,7 +339,7 @@
span_notice("You drape [parent] over [target]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name]."),
)
- if(!(HAS_TRAIT(target, TRAIT_NUMBED) || target.stat >= UNCONSCIOUS)) ///skyrat add start - warning for unanesthetized surgery
+ if(!(HAS_TRAIT(target, TRAIT_ANALGESIA) || target.stat >= UNCONSCIOUS)) ///skyrat add start - warning for unanesthetized surgery
target.balloon_alert(user, "not numbed!") ///skyrat add end
log_combat(user, target, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index 128a48d30a929c6..0254022b5665cca 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -443,7 +443,7 @@
if(human_sacker.get_mob_height() <= HUMAN_HEIGHT_SHORTEST) //JUST YOU WAIT TILL I FIND A CHAIR, BUDDY, THEN YOU'LL BE SORRY
attack_mod -= 2
- if(human_sacker.mob_mood.sanity_level == SANITY_INSANE) //I've gone COMPLETELY INSANE
+ if(human_sacker.mob_mood.sanity_level == SANITY_LEVEL_INSANE) //I've gone COMPLETELY INSANE
attack_mod += 15
human_sacker.adjustStaminaLoss(100) //AHAHAHAHAHAHAHAHA
diff --git a/code/datums/components/tameable.dm b/code/datums/components/tameable.dm
index 67325b489d3670d..43f48005bf89e56 100644
--- a/code/datums/components/tameable.dm
+++ b/code/datums/components/tameable.dm
@@ -10,10 +10,8 @@
var/bonus_tame_chance
///Current chance to tame on interaction
var/current_tame_chance
- ///For effects once soemthing is tamed
- var/datum/callback/after_tame
-/datum/component/tameable/Initialize(food_types, tame_chance, bonus_tame_chance, datum/callback/after_tame, unique = TRUE)
+/datum/component/tameable/Initialize(food_types, tame_chance, bonus_tame_chance, unique = TRUE)
if(!isatom(parent)) //yes, you could make a tameable toolbox.
return COMPONENT_INCOMPATIBLE
@@ -24,18 +22,12 @@
src.current_tame_chance = tame_chance
if(bonus_tame_chance)
src.bonus_tame_chance = bonus_tame_chance
- if(after_tame)
- src.after_tame = after_tame
src.unique = unique
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(try_tame))
RegisterSignal(parent, COMSIG_SIMPLEMOB_SENTIENCEPOTION, PROC_REF(on_tame)) //Instantly succeeds
RegisterSignal(parent, COMSIG_SIMPLEMOB_TRANSFERPOTION, PROC_REF(on_tame)) //Instantly succeeds
-/datum/component/tameable/Destroy(force)
- after_tame = null
- return ..()
-
/datum/component/tameable/proc/try_tame(datum/source, obj/item/food, mob/living/attacker, params)
SIGNAL_HANDLER
if(!is_type_in_list(food, food_types))
@@ -70,9 +62,9 @@
return living_parent.faction.Find(REF(potential_friend))
///Ran once taming succeeds
-/datum/component/tameable/proc/on_tame(atom/source, mob/living/tamer, atom/food, inform_tamer = FALSE)
+/datum/component/tameable/proc/on_tame(atom/source, mob/living/tamer, obj/item/food, inform_tamer = FALSE)
SIGNAL_HANDLER
- after_tame?.Invoke(tamer, food)//Run custom behavior if needed
+ source.tamed(tamer, food)//Run custom behavior if needed
if(isliving(parent) && isliving(tamer))
INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, befriend), tamer)
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index e4e6e611ebca11a..5007d8caeb92d3a 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -375,18 +375,18 @@
// PDA signal responses
-/datum/component/uplink/proc/new_ringtone(datum/source, atom/source, new_ring_text)
+/datum/component/uplink/proc/new_ringtone(datum/source, mob/living/user, new_ring_text)
SIGNAL_HANDLER
if(trim(lowertext(new_ring_text)) != trim(lowertext(unlock_code)))
if(trim(lowertext(new_ring_text)) == trim(lowertext(failsafe_code)))
- failsafe(source)
+ failsafe(user)
return COMPONENT_STOP_RINGTONE_CHANGE
return
locked = FALSE
- if(ismob(source))
- interact(null, source)
- to_chat(source, span_hear("The computer softly beeps."))
+ if(ismob(user))
+ interact(null, user)
+ to_chat(user, span_hear("The computer softly beeps."))
return COMPONENT_STOP_RINGTONE_CHANGE
/datum/component/uplink/proc/check_detonate()
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 2a7423a1fc19088..fc56cfc9c43fb31 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -514,7 +514,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
mutant_bodyparts = species.get_mutant_bodyparts(features, existing_mutant_bodyparts = randomize_features ? list() : mutant_bodyparts) // SKYRAT EDIT ADDITION
update_dna_identity()
-/datum/dna/stored //subtype used by brain mob's stored_dna
+/datum/dna/stored //subtype used by brain mob's stored_dna and the crew manifest
/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna.
return
diff --git a/code/controllers/subsystem/eigenstate.dm b/code/datums/eigenstate.dm
similarity index 66%
rename from code/controllers/subsystem/eigenstate.dm
rename to code/datums/eigenstate.dm
index f167fdf24d1efd1..3bba74632099772 100644
--- a/code/controllers/subsystem/eigenstate.dm
+++ b/code/datums/eigenstate.dm
@@ -1,7 +1,7 @@
-///Subsystem used to teleport people to a linked web of itterative entries. If one entry is deleted, the 2 around it will forge a link instead.
-SUBSYSTEM_DEF(eigenstates)
- name = "Eigenstates"
- flags = SS_NO_INIT | SS_NO_FIRE
+GLOBAL_DATUM_INIT(eigenstate_manager, /datum/eigenstate_manager, new)
+
+///A singleton used to teleport people to a linked web of itterative entries. If one entry is deleted, the 2 around it will forge a link instead.
+/datum/eigenstate_manager
///The list of objects that something is linked to indexed by UID
var/list/eigen_targets = list()
///UID to object reference
@@ -12,7 +12,7 @@ SUBSYSTEM_DEF(eigenstates)
var/spark_time = 0
///Creates a new link of targets unique to their own id
-/datum/controller/subsystem/eigenstates/proc/create_new_link(targets)
+/datum/eigenstate_manager/proc/create_new_link(targets, subtle = TRUE)
if(length(targets) <= 1)
return FALSE
for(var/atom/target as anything in targets) //Clear out any connected
@@ -20,40 +20,44 @@ SUBSYSTEM_DEF(eigenstates)
if(!already_linked)
continue
if(length(eigen_targets[already_linked]) > 1) //Eigenstates are notorious for having cliques!
- target.visible_message("[target] fizzes, it's already linked to something else!")
+ if(!subtle)
+ target.visible_message("[target] fizzes, it's already linked to something else!")
targets -= target
continue
- target.visible_message("[target] fizzes, collapsing it's unique wavefunction into the others!") //If we're in a eigenlink all on our own and are open to new friends
+ if(!subtle)
+ target.visible_message("[target] fizzes, collapsing it's unique wavefunction into the others!") //If we're in a eigenlink all on our own and are open to new friends
remove_eigen_entry(target) //clearup for new stuff
//Do we still have targets?
if(!length(targets))
return FALSE
var/atom/visible_atom = targets[1] //The object that'll handle the messages
if(length(targets) == 1)
- visible_atom.visible_message("[targets[1]] fizzes, there's nothing it can link to!")
+ if(!subtle)
+ visible_atom.visible_message("[targets[1]] fizzes, there's nothing it can link to!")
return FALSE
- eigen_targets["[id_counter]"] = list() //Add to the master list
+ var/subtle_keyword = subtle ? "subtle" : ""
+ eigen_targets["[id_counter][subtle_keyword]"] = list() //Add to the master list
for(var/atom/target as anything in targets)
- eigen_targets["[id_counter]"] += target
- eigen_id[target] = "[id_counter]"
+ eigen_targets["[id_counter][subtle_keyword]"] += target
+ eigen_id[target] = "[id_counter][subtle_keyword]"
RegisterSignal(target, COMSIG_CLOSET_INSERT, PROC_REF(use_eigenlinked_atom))
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(remove_eigen_entry))
- RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), PROC_REF(tool_interact))
+ if(!subtle)
+ RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), PROC_REF(tool_interact))
target.RegisterSignal(target, COMSIG_EIGENSTATE_ACTIVATE, TYPE_PROC_REF(/obj/structure/closet,bust_open))
ADD_TRAIT(target, TRAIT_BANNED_FROM_CARGO_SHUTTLE, REF(src))
- var/obj/item = target
- if(item)
- item.color = COLOR_PERIWINKLEE //Tint the locker slightly.
- item.alpha = 200
- do_sparks(3, FALSE, item)
+ if(!subtle)
+ target.add_atom_colour(COLOR_PERIWINKLEE, FIXED_COLOUR_PRIORITY) //Tint the locker slightly.
+ target.alpha = 200
+ do_sparks(3, FALSE, target)
visible_atom.visible_message("The items shimmer and fizzle, turning a shade of violet blue.")
id_counter++
return TRUE
///reverts everything back to start
-/datum/controller/subsystem/eigenstates/Destroy()
+/datum/eigenstate_manager/eigenstates/Destroy()
for(var/index in 1 to id_counter)
for(var/entry in eigen_targets["[index]"])
remove_eigen_entry(entry)
@@ -63,12 +67,12 @@ SUBSYSTEM_DEF(eigenstates)
return ..()
///removes an object reference from the master list
-/datum/controller/subsystem/eigenstates/proc/remove_eigen_entry(atom/entry)
+/datum/eigenstate_manager/proc/remove_eigen_entry(atom/entry)
SIGNAL_HANDLER
var/id = eigen_id[entry]
eigen_targets[id] -= entry
eigen_id -= entry
- entry.color = COLOR_WHITE
+ entry.remove_atom_colour(FIXED_COLOUR_PRIORITY, COLOR_PERIWINKLEE)
entry.alpha = 255
UnregisterSignal(entry, list(
COMSIG_QDELETING,
@@ -83,13 +87,14 @@ SUBSYSTEM_DEF(eigenstates)
eigen_targets -= targets
///Finds the object within the master list, then sends the thing to the object's location
-/datum/controller/subsystem/eigenstates/proc/use_eigenlinked_atom(atom/object_sent_from, atom/movable/thing_to_send)
+/datum/eigenstate_manager/proc/use_eigenlinked_atom(atom/object_sent_from, atom/movable/thing_to_send)
SIGNAL_HANDLER
var/id = eigen_id[object_sent_from]
if(!id)
stack_trace("[object_sent_from] attempted to eigenlink to something that didn't have a valid id!")
return FALSE
+ var/subtle = findtext(id, "subtle")
var/list/items = eigen_targets[id]
var/index = (items.Find(object_sent_from))+1 //index + 1
if(!index)
@@ -104,19 +109,21 @@ SUBSYSTEM_DEF(eigenstates)
if(check_teleport_valid(thing_to_send, eigen_target, TELEPORT_CHANNEL_EIGENSTATE))
thing_to_send.forceMove(get_turf(eigen_target))
else
- object_sent_from.balloon_alert(thing_to_send, "nothing happens!")
+ if(!subtle)
+ object_sent_from.balloon_alert(thing_to_send, "nothing happens!")
return FALSE
//Create ONE set of sparks for ALL times in iteration
- if(spark_time != world.time)
+ if(!subtle && spark_time != world.time)
do_sparks(5, FALSE, eigen_target)
do_sparks(5, FALSE, object_sent_from)
- spark_time = world.time
+ spark_time = world.time
//Calls a special proc for the atom if needed (closets use bust_open())
SEND_SIGNAL(eigen_target, COMSIG_EIGENSTATE_ACTIVATE)
- return COMPONENT_CLOSET_INSERT_INTERRUPT
+ if(!subtle)
+ return COMPONENT_CLOSET_INSERT_INTERRUPT
///Prevents tool use on the item
-/datum/controller/subsystem/eigenstates/proc/tool_interact(atom/source, mob/user, obj/item/item)
+/datum/eigenstate_manager/proc/tool_interact(atom/source, mob/user, obj/item/item)
SIGNAL_HANDLER
to_chat(user, span_notice("The unstable nature of [source] makes it impossible to use [item] on [source.p_them()]!"))
return ITEM_INTERACT_BLOCKING
diff --git a/code/datums/elements/basic_body_temp_sensitive.dm b/code/datums/elements/basic_body_temp_sensitive.dm
index 8e11ed92575ea03..8bb1cae854631ed 100644
--- a/code/datums/elements/basic_body_temp_sensitive.dm
+++ b/code/datums/elements/basic_body_temp_sensitive.dm
@@ -47,14 +47,15 @@
if(basic_mob.bodytemperature < min_body_temp)
basic_mob.adjust_health(cold_damage * seconds_per_tick)
- switch(cold_damage)
- if(1 to 5)
- basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 1)
- if(5 to 10)
- basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 2)
- if(10 to INFINITY)
- basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 3)
- gave_alert = TRUE
+ if(!basic_mob.has_status_effect(/datum/status_effect/inebriated))
+ switch(cold_damage)
+ if(1 to 5)
+ basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 1)
+ if(5 to 10)
+ basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 2)
+ if(10 to INFINITY)
+ basic_mob.throw_alert(ALERT_TEMPERATURE, /atom/movable/screen/alert/cold, 3)
+ gave_alert = TRUE
else if(basic_mob.bodytemperature > max_body_temp)
basic_mob.adjust_health(heat_damage * seconds_per_tick)
diff --git a/code/datums/elements/bed_tucking.dm b/code/datums/elements/bed_tucking.dm
index 70b10d4a58c0cc5..58f5640c31c75e9 100644
--- a/code/datums/elements/bed_tucking.dm
+++ b/code/datums/elements/bed_tucking.dm
@@ -1,7 +1,7 @@
/// Tucking element, for things that can be tucked into bed.
/datum/element/bed_tuckable
element_flags = ELEMENT_BESPOKE
- argument_hash_start_idx = 2
+ argument_hash_start_idx = 3
/// our pixel_x offset - how much the item moves x when in bed (+x is closer to the pillow)
var/x_offset = 0
/// our pixel_y offset - how much the item move y when in bed (-y is closer to the middle)
@@ -11,7 +11,7 @@
/// our starting angle for the item
var/starting_angle = 0
-/datum/element/bed_tuckable/Attach(obj/target, x = 0, y = 0, rotation = 0)
+/datum/element/bed_tuckable/Attach(obj/target, mapload = FALSE, x = 0, y = 0, rotation = 0)
. = ..()
if(!isitem(target))
return ELEMENT_INCOMPATIBLE
@@ -20,6 +20,13 @@
y_offset = y
starting_angle = rotation
RegisterSignal(target, COMSIG_ITEM_ATTACK_ATOM, PROC_REF(tuck_into_bed))
+ if(!mapload)
+ return
+ var/turf/our_home = get_turf(target)
+ var/obj/structure/bed/eepy = locate(/obj/structure/bed) in our_home
+ if(isnull(eepy))
+ return
+ tuck(target, eepy)
/datum/element/bed_tuckable/Detach(obj/target)
. = ..()
@@ -42,6 +49,10 @@
return
to_chat(tucker, span_notice("You lay [tucked] out on [target_bed]."))
+ tuck(tucked, target_bed)
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/element/bed_tuckable/proc/tuck(obj/item/tucked, obj/structure/bed/target_bed)
tucked.dir = target_bed.dir
tucked.pixel_x = target_bed.dir & EAST ? -x_offset : x_offset
tucked.pixel_y = y_offset
@@ -50,8 +61,6 @@
tucked.transform = turn(tucked.transform, rotation_degree)
RegisterSignal(tucked, COMSIG_ITEM_PICKUP, PROC_REF(untuck))
- return COMPONENT_NO_AFTERATTACK
-
/**
* If we rotate our object, then we need to un-rotate it when it's picked up
*
diff --git a/code/datums/elements/shatters_when_thrown.dm b/code/datums/elements/can_shatter.dm
similarity index 69%
rename from code/datums/elements/shatters_when_thrown.dm
rename to code/datums/elements/can_shatter.dm
index cbb5994852c810d..73b025ad83c0839 100644
--- a/code/datums/elements/shatters_when_thrown.dm
+++ b/code/datums/elements/can_shatter.dm
@@ -1,7 +1,8 @@
/**
* When attached to something, will make that thing shatter into shards on throw impact or z level falling
+ * Or even when used as a weapon if the 'shatters_as_weapon' arg is TRUE
*/
-/datum/element/shatters_when_thrown
+/datum/element/can_shatter
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
@@ -12,7 +13,12 @@
/// What sound plays when the thing we're attached to shatters
var/shattering_sound
-/datum/element/shatters_when_thrown/Attach(datum/target, shard_type = /obj/item/plate_shard, number_of_shards = 5, shattering_sound = 'sound/items/ceramic_break.ogg')
+/datum/element/can_shatter/Attach(datum/target,
+ shard_type = /obj/item/plate_shard,
+ number_of_shards = 5,
+ shattering_sound = 'sound/items/ceramic_break.ogg',
+ shatters_as_weapon = FALSE,
+ )
. = ..()
if(!ismovable(target))
@@ -24,26 +30,28 @@
RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
RegisterSignal(target, COMSIG_ATOM_ON_Z_IMPACT, PROC_REF(on_z_impact))
+ if(shatters_as_weapon)
+ RegisterSignal(target, COMSIG_ITEM_POST_ATTACK_ATOM, PROC_REF(on_post_attack_atom))
-/datum/element/shatters_when_thrown/Detach(datum/target)
+/datum/element/can_shatter/Detach(datum/target)
. = ..()
UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT, COMSIG_ATOM_ON_Z_IMPACT))
/// Tells the parent to shatter if we impact a lower zlevel
-/datum/element/shatters_when_thrown/proc/on_z_impact(datum/source, turf/impacted_turf, levels)
+/datum/element/can_shatter/proc/on_z_impact(datum/source, turf/impacted_turf, levels)
SIGNAL_HANDLER
shatter(source, impacted_turf)
/// Tells the parent to shatter if we are thrown and impact something
-/datum/element/shatters_when_thrown/proc/on_throw_impact(datum/source, atom/hit_atom)
+/datum/element/can_shatter/proc/on_throw_impact(datum/source, atom/hit_atom)
SIGNAL_HANDLER
shatter(source, hit_atom)
/// Handles the actual shattering part, throwing shards of whatever is defined on the component everywhere
-/datum/element/shatters_when_thrown/proc/shatter(atom/movable/source, atom/hit_atom)
+/datum/element/can_shatter/proc/shatter(atom/movable/source, atom/hit_atom)
var/generator/scatter_gen = generator(GEN_CIRCLE, 0, 48, NORMAL_RAND)
var/scatter_turf = get_turf(hit_atom)
@@ -64,3 +72,7 @@
return
else
qdel(source)
+
+/datum/element/can_shatter/proc/on_post_attack_atom(obj/item/source, atom/attacked_atom, mob/living/user)
+ SIGNAL_HANDLER
+ shatter(source, attacked_atom)
diff --git a/code/datums/elements/elevation.dm b/code/datums/elements/elevation.dm
index ffa6de398b6e483..92fba97a09414d2 100644
--- a/code/datums/elements/elevation.dm
+++ b/code/datums/elements/elevation.dm
@@ -113,6 +113,8 @@
for(var/mob/living/living in target)
ADD_TRAIT(living, TRAIT_ON_ELEVATED_SURFACE, REF(src))
RegisterSignal(living, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_set_buckled))
+ RegisterSignal(living, SIGNAL_ADDTRAIT(TRAIT_IGNORE_ELEVATION), PROC_REF(on_ignore_elevation_add))
+ RegisterSignal(living, SIGNAL_REMOVETRAIT(TRAIT_IGNORE_ELEVATION), PROC_REF(on_ignore_elevation_remove))
elevate_mob(living)
/datum/element/elevation_core/Detach(datum/source)
@@ -133,7 +135,7 @@
continue
REMOVE_TRAIT(living, TRAIT_ON_ELEVATED_SURFACE, REF(src))
elevate_mob(living, -pixel_shift)
- UnregisterSignal(living, COMSIG_LIVING_SET_BUCKLED)
+ UnregisterSignal(living, list(COMSIG_LIVING_SET_BUCKLED, SIGNAL_ADDTRAIT(TRAIT_IGNORE_ELEVATION), SIGNAL_REMOVETRAIT(TRAIT_IGNORE_ELEVATION)))
return ..()
/datum/element/elevation_core/proc/on_entered(turf/source, atom/movable/entered, atom/old_loc)
@@ -143,6 +145,8 @@
var/elevate_time = isturf(old_loc) && source.Adjacent(old_loc) ? ELEVATE_TIME : 0
elevate_mob(entered, elevate_time = elevate_time)
RegisterSignal(entered, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_set_buckled))
+ RegisterSignal(entered, SIGNAL_ADDTRAIT(TRAIT_IGNORE_ELEVATION), PROC_REF(on_ignore_elevation_add))
+ RegisterSignal(entered, SIGNAL_REMOVETRAIT(TRAIT_IGNORE_ELEVATION), PROC_REF(on_ignore_elevation_remove))
/datum/element/elevation_core/proc/on_initialized_on(turf/source, atom/movable/spawned)
SIGNAL_HANDLER
@@ -152,15 +156,17 @@
/datum/element/elevation_core/proc/on_exited(turf/source, atom/movable/gone)
SIGNAL_HANDLER
if((isnull(gone.loc) || !HAS_TRAIT_FROM(gone.loc, TRAIT_ELEVATED_TURF, REF(src))) && isliving(gone))
- // Always unregister the signal, we're still leaving even if already shifted down.
- UnregisterSignal(gone, COMSIG_LIVING_SET_BUCKLED)
+ // Always unregister the signals, we're still leaving even if not affected by elevation.
+ UnregisterSignal(gone, list(COMSIG_LIVING_SET_BUCKLED, SIGNAL_ADDTRAIT(TRAIT_IGNORE_ELEVATION), SIGNAL_REMOVETRAIT(TRAIT_IGNORE_ELEVATION)))
if(!HAS_TRAIT_FROM(gone, TRAIT_ON_ELEVATED_SURFACE, REF(src)))
return
REMOVE_TRAIT(gone, TRAIT_ON_ELEVATED_SURFACE, REF(src))
var/elevate_time = isturf(gone.loc) && source.Adjacent(gone.loc) ? ELEVATE_TIME : 0
elevate_mob(gone, -pixel_shift, elevate_time)
-/datum/element/elevation_core/proc/elevate_mob(mob/living/target, z_shift = pixel_shift, elevate_time = ELEVATE_TIME)
+/datum/element/elevation_core/proc/elevate_mob(mob/living/target, z_shift = pixel_shift, elevate_time = ELEVATE_TIME, force = FALSE)
+ if(HAS_TRAIT(target, TRAIT_IGNORE_ELEVATION) && !force)
+ return
var/buckled_to_vehicle = FALSE
if(target.buckled)
if(isvehicle(target.buckled))
@@ -181,6 +187,8 @@
*/
/datum/element/elevation_core/proc/on_set_buckled(mob/living/source, atom/movable/new_buckled)
SIGNAL_HANDLER
+ if(HAS_TRAIT(source, TRAIT_IGNORE_ELEVATION))
+ return
if(source.buckled)
if(isvehicle(source.buckled))
animate(source.buckled, pixel_z = -pixel_shift, time = ELEVATE_TIME, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL)
@@ -193,6 +201,14 @@
else if(!isliving(new_buckled))
animate(source, pixel_z = -pixel_shift, time = ELEVATE_TIME, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL)
+/datum/element/elevation_core/proc/on_ignore_elevation_add(mob/living/source, trait)
+ SIGNAL_HANDLER
+ elevate_mob(source, -pixel_shift, force = TRUE)
+
+/datum/element/elevation_core/proc/on_ignore_elevation_remove(mob/living/source, trait)
+ SIGNAL_HANDLER
+ elevate_mob(source, pixel_shift)
+
/datum/element/elevation_core/proc/on_reset_elevation(turf/source, list/current_values)
SIGNAL_HANDLER
current_values[ELEVATION_CURRENT_PIXEL_SHIFT] = pixel_shift
diff --git a/code/datums/elements/food/grilled_item.dm b/code/datums/elements/food/grilled_item.dm
index f657b969f062ea7..de6c2ef41c1b915 100644
--- a/code/datums/elements/food/grilled_item.dm
+++ b/code/datums/elements/food/grilled_item.dm
@@ -9,30 +9,27 @@
var/atom/this_food = target
switch(grill_time) //no 0-20 to prevent spam
- if(20 to 30)
+ if(20 SECONDS to 30 SECONDS)
this_food.name = "lightly-grilled [this_food.name]"
this_food.desc += " It's been lightly grilled."
- if(30 to 80)
+ if(30 SECONDS to 80 SECONDS)
this_food.name = "grilled [this_food.name]"
this_food.desc += " It's been grilled."
- if(80 to 100)
+ if(80 SECONDS to 100 SECONDS)
this_food.name = "heavily grilled [this_food.name]"
this_food.desc += " It's been heavily grilled."
- if(100 to INFINITY) //grill marks reach max alpha
+ if(100 SECONDS to INFINITY) //grill marks reach max alpha
this_food.name = "Powerfully Grilled [this_food.name]"
this_food.desc = "A [this_food.name]. Reminds you of your wife, wait, no, it's prettier!"
- if(grill_time > 20)
- ADD_TRAIT(this_food, TRAIT_FOOD_GRILLED, ELEMENT_TRAIT(type))
- if(grill_time > 30)
+ if(grill_time > 30 SECONDS && isnull(this_food.GetComponent(/datum/component/edible)))
this_food.AddComponent(/datum/component/edible, foodtypes = FRIED)
/datum/element/grilled_item/Detach(atom/source, ...)
source.name = initial(source.name)
source.desc = initial(source.desc)
- REMOVE_TRAIT(source, TRAIT_FOOD_GRILLED, ELEMENT_TRAIT(type))
qdel(source.GetComponent(/datum/component/edible)) // Don't care if it was initially edible
return ..()
diff --git a/code/datums/elements/frozen.dm b/code/datums/elements/frozen.dm
index b88cc2afa224009..434968dd4d5e0f2 100644
--- a/code/datums/elements/frozen.dm
+++ b/code/datums/elements/frozen.dm
@@ -55,9 +55,13 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
Detach(source)
///signal handler for COMSIG_MOVABLE_POST_THROW that shatters our target after impacting after a throw
-/datum/element/frozen/proc/shatter_on_throw(datum/target)
+/datum/element/frozen/proc/shatter_on_throw(datum/target, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
var/obj/obj_target = target
+ if(ismob(throwingdatum.thrower))
+ log_combat(throwingdatum.thrower, target, "shattered", addition = "from being thrown due to [target] being frozen.")
+ else
+ log_combat(throwingdatum.thrower, target, "launched", addition = "shattering it due to being frozen.")
obj_target.visible_message(span_danger("[obj_target] shatters into a million pieces!"))
obj_target.obj_flags |= NO_DECONSTRUCTION // disable item spawning
obj_target.deconstruct(FALSE) // call pre-deletion specialized code -- internals release gas etc
diff --git a/code/datums/elements/inverted_movement.dm b/code/datums/elements/inverted_movement.dm
new file mode 100644
index 000000000000000..537e7343893e75c
--- /dev/null
+++ b/code/datums/elements/inverted_movement.dm
@@ -0,0 +1,14 @@
+/datum/element/inverted_movement
+
+/datum/element/inverted_movement/Attach(datum/target)
+ . = ..()
+ if(!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+ RegisterSignal(target, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(invert_movement))
+
+/datum/element/inverted_movement/Detach(datum/source)
+ UnregisterSignal(source, COMSIG_MOB_CLIENT_PRE_MOVE)
+ return ..()
+
+/datum/element/inverted_movement/proc/invert_movement(mob/living/source, move_args)
+ move_args[MOVE_ARG_DIRECTION] = REVERSE_DIR(move_args[MOVE_ARG_DIRECTION])
diff --git a/code/datums/elements/sticker.dm b/code/datums/elements/sticker.dm
deleted file mode 100644
index 3cc8e977daf20f0..000000000000000
--- a/code/datums/elements/sticker.dm
+++ /dev/null
@@ -1,53 +0,0 @@
-#define MAX_ALLOWED_STICKERS 12
-
-/datum/element/sticker
- ///The typepath for our attached sticker component
- var/stick_type = /datum/component/attached_sticker
- ///If TRUE, our attached_sticker can be washed off
- var/washable = TRUE
-
-/datum/element/sticker/Attach(datum/target, sticker_type, cleanable=TRUE)
- . = ..()
- if(!isitem(target))
- return ELEMENT_INCOMPATIBLE
- RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_afterattack))
- RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
- if(sticker_type)
- stick_type = sticker_type
- washable = cleanable
-
-/datum/element/sticker/Detach(datum/source)
- . = ..()
- UnregisterSignal(source, list(COMSIG_ITEM_AFTERATTACK, COMSIG_MOVABLE_IMPACT))
-
-/datum/element/sticker/proc/on_afterattack(obj/item/source, atom/target, mob/living/user, prox, params)
- SIGNAL_HANDLER
- if(!prox)
- return
- if(!isatom(target))
- return
- var/list/parameters = params2list(params)
- if(!LAZYACCESS(parameters, ICON_X) || !LAZYACCESS(parameters, ICON_Y))
- return
- var/divided_size = world.icon_size / 2
- var/px = text2num(LAZYACCESS(parameters, ICON_X)) - divided_size
- var/py = text2num(LAZYACCESS(parameters, ICON_Y)) - divided_size
-
- user.do_attack_animation(target)
- if(do_stick(source, target, user, px, py))
- target.balloon_alert_to_viewers("sticker sticked")
-
-///Add our stick_type to the target with px and py as pixel x and pixel y respectively
-/datum/element/sticker/proc/do_stick(obj/item/source, atom/target, mob/living/user, px, py)
- if(COUNT_TRAIT_SOURCES(target, TRAIT_STICKERED) >= MAX_ALLOWED_STICKERS)
- source.balloon_alert_to_viewers("sticker won't stick!")
- return FALSE
- target.AddComponent(stick_type, px, py, source, user, washable)
- return TRUE
-
-/datum/element/sticker/proc/on_throw_impact(obj/item/source, atom/hit_atom, datum/thrownthing/throwingdatum)
- SIGNAL_HANDLER
- if(prob(50) && do_stick(source, hit_atom, null, rand(-7,7), rand(-7,7)))
- hit_atom.balloon_alert_to_viewers("sticker landed on sticky side!")
-
-#undef MAX_ALLOWED_STICKERS
diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
index 28433ba9064b6fe..6b9465bf46af077 100644
--- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
+++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_items.dm
@@ -335,3 +335,8 @@
name = "Flower Worn"
icon_file = 'icons/mob/clothing/head/hydroponics.dmi'
json_config = 'code/datums/greyscale/json_configs/simple_flower_worn.json'
+
+/datum/greyscale_config/piggy_bank
+ name = "Piggy Bank"
+ icon_file = 'icons/obj/fluff/general.dmi'
+ json_config = 'code/datums/greyscale/json_configs/piggy_bank.json'
diff --git a/code/datums/greyscale/json_configs/piggy_bank.json b/code/datums/greyscale/json_configs/piggy_bank.json
new file mode 100644
index 000000000000000..71876213e197f59
--- /dev/null
+++ b/code/datums/greyscale/json_configs/piggy_bank.json
@@ -0,0 +1,10 @@
+{
+ "piggy_bank": [
+ {
+ "type": "icon_state",
+ "icon_state": "piggy_bank",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ]
+}
diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm
index ea46cc91319fa9a..46f691564bb4bc9 100644
--- a/code/datums/id_trim/jobs.dm
+++ b/code/datums/id_trim/jobs.dm
@@ -1183,6 +1183,36 @@
)
job = /datum/job/station_engineer
+/datum/id_trim/job/veteran_advisor
+ assignment = "Veteran Security Advisor"
+ trim_state = "trim_veteranadvisor"
+ department_color = COLOR_SECURITY_RED
+ subdepartment_color = COLOR_COMMAND_BLUE
+ sechud_icon_state = SECHUD_VETERAN_ADVISOR
+ minimal_access = list(
+ ACCESS_COMMAND,
+ ACCESS_BRIG,
+ ACCESS_BRIG_ENTRANCE,
+ ACCESS_COURT,
+ ACCESS_MECH_SECURITY,
+ ACCESS_MINERAL_STOREROOM,
+ ACCESS_SECURITY,
+ ACCESS_WEAPONS,
+ )
+ extra_access = list()
+ template_access = list()
+ job = /datum/job/veteran_advisor
+
+/datum/id_trim/job/veteran_advisor/refresh_trim_access()
+ . = ..()
+
+ if(!.)
+ return
+
+ // Config check for if sec has maint access.
+ if(CONFIG_GET(flag/security_has_maint_access))
+ access |= list(ACCESS_MAINT_TUNNELS)
+
/datum/id_trim/job/virologist
assignment = "Virologist"
trim_state = "trim_virologist"
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index 3c5f6ab62d0cb1b..e2897a772e530c5 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -4,7 +4,7 @@
desc = "Common iron ore often found in sedimentary and igneous layers of the crust."
color = "#878687"
greyscale_colors = "#878687"
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/iron
ore_type = /obj/item/stack/ore/iron
value_per_unit = 5 / SHEET_MATERIAL_AMOUNT
@@ -25,10 +25,10 @@
color = "#88cdf1"
greyscale_colors = "#88cdf196"
alpha = 150
- categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
integrity_modifier = 0.1
sheet_type = /obj/item/stack/sheet/glass
- ore_type = /obj/item/stack/ore/glass
+ ore_type = /obj/item/stack/ore/glass/basalt
shard_type = /obj/item/shard
debris_type = /obj/effect/decal/cleanable/glass
value_per_unit = 5 / SHEET_MATERIAL_AMOUNT
@@ -47,12 +47,12 @@
/datum/material/glass/on_applied_obj(atom/source, amount, material_flags)
. = ..()
if(!isstack(source))
- source.AddElement(/datum/element/shatters_when_thrown, shard_type, round(amount / SHEET_MATERIAL_AMOUNT), SFX_SHATTER)
+ source.AddElement(/datum/element/can_shatter, shard_type, round(amount / SHEET_MATERIAL_AMOUNT), SFX_SHATTER)
/datum/material/glass/on_removed(atom/source, amount, material_flags)
. = ..()
- source.RemoveElement(/datum/element/shatters_when_thrown, shard_type)
+ source.RemoveElement(/datum/element/can_shatter, shard_type)
/*
Color matrices are like regular colors but unlike with normal colors, you can go over 255 on a channel.
@@ -65,7 +65,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Silver"
color = list(255/255, 284/255, 302/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0)
greyscale_colors = "#e3f1f8"
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/silver
ore_type = /obj/item/stack/ore/silver
value_per_unit = 50 / SHEET_MATERIAL_AMOUNT
@@ -86,7 +86,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
color = list(340/255, 240/255, 50/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //gold is shiny, but not as bright as bananium
greyscale_colors = "#dbdd4c"
strength_modifier = 1.2
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/gold
ore_type = /obj/item/stack/ore/gold
value_per_unit = 125 / SHEET_MATERIAL_AMOUNT
@@ -107,7 +107,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Highly pressurized carbon"
color = list(48/255, 272/255, 301/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0)
greyscale_colors = "#71c8f784"
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/diamond
ore_type = /obj/item/stack/ore/diamond
alpha = 132
@@ -130,7 +130,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Uranium"
color = rgb(48, 237, 26)
greyscale_colors = rgb(48, 237, 26)
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/uranium
ore_type = /obj/item/stack/ore/uranium
value_per_unit = 100 / SHEET_MATERIAL_AMOUNT
@@ -170,7 +170,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Isn't plasma a state of matter? Oh whatever."
color = list(298/255, 46/255, 352/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0)
greyscale_colors = "#c162ec"
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/plasma
ore_type = /obj/item/stack/ore/plasma
value_per_unit = 200 / SHEET_MATERIAL_AMOUNT
@@ -204,7 +204,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
greyscale_colors = "#4e7dffC8"
alpha = 200
starlight_color = COLOR_BLUE
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_ITEM_MATERIAL = TRUE)
beauty_modifier = 0.5
sheet_type = /obj/item/stack/sheet/bluespace_crystal
ore_type = /obj/item/stack/ore/bluespace_crystal
@@ -225,7 +225,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Material with hilarious properties"
color = list(460/255, 464/255, 0, 0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //obnoxiously bright yellow
greyscale_colors = "#ffff00"
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/bananium
ore_type = /obj/item/stack/ore/bananium
value_per_unit = 1000 / SHEET_MATERIAL_AMOUNT
@@ -256,7 +256,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
color = "#b3c0c7"
greyscale_colors = "#b3c0c7"
strength_modifier = 1.3
- categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/titanium
ore_type = /obj/item/stack/ore/titanium
value_per_unit = 125 / SHEET_MATERIAL_AMOUNT
@@ -297,7 +297,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
strength_modifier = 0.85
sheet_type = /obj/item/stack/sheet/plastic
ore_type = /obj/item/stack/ore/slag //No plastic or coal ore, so we use slag.
- categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
+ categories = list(MAT_CATEGORY_SILO = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
value_per_unit = 25 / SHEET_MATERIAL_AMOUNT
beauty_modifier = -0.01
armor_modifiers = list(MELEE = 1.5, BULLET = 1.1, LASER = 0.3, ENERGY = 0.5, BOMB = 1, BIO = 1, FIRE = 1.1, ACID = 1)
diff --git a/code/datums/mood_events/eldritch_painting_events.dm b/code/datums/mood_events/eldritch_painting_events.dm
index 7df89104263bace..df801998c1d9836 100644
--- a/code/datums/mood_events/eldritch_painting_events.dm
+++ b/code/datums/mood_events/eldritch_painting_events.dm
@@ -17,13 +17,13 @@
mood_change = 5
timeout = 3 MINUTES
-/datum/mood_event/eldritch_painting/weeping_withdrawl
+/datum/mood_event/eldritch_painting/weeping_withdrawal
description = "My mind is clear from his influence."
mood_change = 1
timeout = 3 MINUTES
/datum/mood_event/eldritch_painting/desire_heretic
- description = "A part gained, the manus takes and gives. What did it take from me?"
+ description = "A part gained, the mansus takes and gives. What did it take from me?"
mood_change = -2
timeout = 3 MINUTES
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 32b9772dc0709de..0cfb48f7a9823dc 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -483,3 +483,9 @@
/datum/mood_event/all_nighter
description = "I didn't sleep at all last night. I'm exhausted."
mood_change = -5
+
+//Used by the Veteran Advisor trait job
+/datum/mood_event/desentized
+ description = "Nothing will ever rival with what I seen in the past..."
+ mood_change = -3
+ special_screen_obj = "mood_desentized"
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index 38806aa7529dd95..035cf64db80504f 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -232,7 +232,7 @@
instability = 5
power_coeff = 1
conflicts = list(/datum/mutation/human/glow/anti)
- var/glow_power = 2.5
+ var/glow_power = 2
var/glow_range = 2.5
var/glow_color
var/obj/effect/dummy/lighting_obj/moblight/glow
diff --git a/code/datums/records/manifest.dm b/code/datums/records/manifest.dm
index c13acad9f05311d..3a942325cbac6bc 100644
--- a/code/datums/records/manifest.dm
+++ b/code/datums/records/manifest.dm
@@ -111,7 +111,7 @@ GLOBAL_DATUM_INIT(manifest, /datum/manifest, new)
person_gender = "Male"
if(person.gender == "female")
person_gender = "Female"
- var/datum/dna/record_dna = new()
+ var/datum/dna/stored/record_dna = new()
person.dna.copy_dna(record_dna)
// SKYRAT EDIT ADDITION BEGIN - ALTERNATIVE_JOB_TITLES
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index f66d06a4bba6b9f..d10b477c083c245 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -29,6 +29,13 @@
description = "WELCOME TO CLOWN PLANET! HONK HONK HONK etc.!"
suffix = "lavaland_biodome_clown_planet.dmm"
+/datum/map_template/ruin/lavaland/lizgas
+ name = "The Lizard's Gas(Lava)"
+ id = "lizgas2"
+ description = "A recently opened gas station from the Lizard's Gas franchise."
+ suffix = "lavaland_surface_gas.dmm"
+ allow_duplicates = FALSE
+
/datum/map_template/ruin/lavaland/cube
name = "The Wishgranter Cube"
id = "wishgranter-cube"
diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm
index 8ad478e6c83a9f6..c588475094be141 100644
--- a/code/datums/station_traits/job_traits.dm
+++ b/code/datums/station_traits/job_traits.dm
@@ -60,21 +60,18 @@
for (var/mob/dead/new_player/signee as anything in lobby_candidates)
if (isnull(signee) || !signee.client || !signee.mind || signee.ready != PLAYER_READY_TO_PLAY)
LAZYREMOVE(lobby_candidates, signee)
- if (!LAZYLEN(lobby_candidates))
- on_failed_assignment()
- return // Nobody signed up :(
- for(var/_ in 1 to position_amount)
+
+ var/datum/job/our_job = SSjob.GetJobType(job_to_add)
+ while(length(lobby_candidates) && position_amount > 0)
var/mob/dead/new_player/picked_player = pick_n_take(lobby_candidates)
- picked_player.mind.assigned_role = new job_to_add()
- lobby_candidates = null
+ picked_player.mind.set_assigned_role(our_job)
+ position_amount--
-/// Called if we didn't assign a role before the round began, we add it to the latejoin menu instead
-/datum/station_trait/job/proc/on_failed_assignment()
- var/datum/job/our_job = SSjob.GetJob(job_to_add::title)
- our_job.total_positions = position_amount
+ our_job.total_positions = max(0, position_amount)
+ lobby_candidates = null
/datum/station_trait/job/can_display_lobby_button(client/player)
- var/datum/job/our_job = SSjob.GetJob(job_to_add::title)
+ var/datum/job/our_job = SSjob.GetJobType(job_to_add)
return our_job.player_old_enough(player) && ..()
/// Adds a gorilla to the cargo department, replacing the sloth and the mech
@@ -165,6 +162,21 @@
new /obj/item/reagent_containers/cup/coffeepot(picked_turf)
new /obj/item/storage/box/coffeepack(picked_turf)
+/datum/station_trait/job/veteran_advisor
+ name = "Veteran Advisor"
+ button_desc = "Sign up to become a DISABLED but hard boiled Veteran Advisor of Nanotrasen Security Force. Advise HoS and Captain, train Officers, all while fighting your PTSD."
+ weight = 2
+ report_message = "Veteran Security Advisor has been assigned to your station to help with Security matters."
+ show_in_report = TRUE
+ can_roll_antag = CAN_ROLL_PROTECTED
+ job_to_add = /datum/job/veteran_advisor
+
+/* SKYRAT EDIT -- REMOVAL -- We handle the lobby a bit differently for time being
+/datum/station_trait/job/veteran_advisor/on_lobby_button_update_overlays(atom/movable/screen/lobby/button/sign_up/lobby_button, list/overlays)
+ . = ..()
+ overlays += "veteran_advisor"
+*/
+
#undef CAN_ROLL_ALWAYS
#undef CAN_ROLL_PROTECTED
#undef CAN_ROLL_NEVER
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
index f12f9bed8558046..905bfed2e8e4c62 100644
--- a/code/datums/station_traits/neutral_traits.dm
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -362,6 +362,41 @@
show_in_report = TRUE
report_message = "There sure are a lot of trees out there."
+/datum/station_trait/linked_closets
+ name = "Closet Anomaly"
+ trait_type = STATION_TRAIT_NEUTRAL
+ show_in_report = TRUE
+ weight = 1
+ report_message = "We've reports of high amount of trace eigenstasium on your station. Ensure that your closets are working correctly."
+
+/datum/station_trait/linked_closets/on_round_start()
+ . = ..()
+ var/list/roundstart_non_secure_closets = GLOB.roundstart_station_closets.Copy()
+ for(var/obj/structure/closet/closet in roundstart_non_secure_closets)
+ if(closet.secure)
+ roundstart_non_secure_closets -= closet
+
+ /**
+ * The number of links to perform.
+ * Combined with 50/50 the probability of the link being triangular, the boundaries of any given
+ * on-station, non-secure closet being linked are as high as 1 in 7/8 and as low as 1 in 16-17,
+ * nearing an a mean of 1 in 9 to 11/12 the more repetitions are done.
+ *
+ * There are more than 220 roundstart closets on meta, around 150 of which aren't secure,
+ * so, about 13 to 17 closets will be affected by this most of the times.
+ */
+ var/number_of_links = round(length(roundstart_non_secure_closets) * (rand(350, 450)*0.0001), 1)
+ for(var/repetition in 1 to number_of_links)
+ var/closets_left = length(roundstart_non_secure_closets)
+ if(closets_left < 2)
+ return
+ var/list/targets = list()
+ for(var/how_many in 1 to min(closets_left, rand(2,3)))
+ targets += pick_n_take(roundstart_non_secure_closets)
+ if(closets_left == 1) //there's only one closet left. Let's not leave it alone.
+ targets += roundstart_non_secure_closets[1]
+ GLOB.eigenstate_manager.create_new_link(targets)
+
/datum/station_trait/triple_ai
name = "AI Triumvirate"
trait_type = STATION_TRAIT_NEUTRAL
diff --git a/code/datums/status_effects/_status_effect_helpers.dm b/code/datums/status_effects/_status_effect_helpers.dm
index 0ee952200610661..f887afd91428e9c 100644
--- a/code/datums/status_effects/_status_effect_helpers.dm
+++ b/code/datums/status_effects/_status_effect_helpers.dm
@@ -56,7 +56,7 @@
. = FALSE
for(var/datum/status_effect/existing_effect as anything in status_effects)
- if(existing_effect.id == initial(removed_effect.id) && existing_effect.before_remove(arguments))
+ if(existing_effect.id == initial(removed_effect.id) && existing_effect.before_remove(arglist(arguments)))
qdel(existing_effect)
. = TRUE
@@ -84,6 +84,17 @@
return null
+///Gets every status effect of an ID and returns all of them in a list, rather than the individual 'has_status_effect'
+/mob/living/proc/get_all_status_effect_of_id(datum/status_effect/checked_effect)
+ RETURN_TYPE(/list/datum/status_effect)
+
+ var/list/all_effects_of_type = list()
+ for(var/datum/status_effect/present_effect as anything in status_effects)
+ if(present_effect.id == initial(checked_effect.id))
+ all_effects_of_type += present_effect
+
+ return all_effects_of_type
+
/**
* Checks if this mob has a status effect that shares the passed effect's ID
* and has the passed sources are in its list of sources (ONLY works for grouped efects!)
diff --git a/code/datums/status_effects/buffs/food/chilling.dm b/code/datums/status_effects/buffs/food/chilling.dm
new file mode 100644
index 000000000000000..8b1d60fbcc15602
--- /dev/null
+++ b/code/datums/status_effects/buffs/food/chilling.dm
@@ -0,0 +1,13 @@
+///food effect applied by ice cream and frozen treats
+/datum/status_effect/food/chilling
+ alert_type = /atom/movable/screen/alert/status_effect/icecream_chilling //different path, so we sprite one state and not five.
+
+/datum/status_effect/food/chilling/tick(seconds_between_ticks)
+ var/minimum_temp = (BODYTEMP_HEAT_DAMAGE_LIMIT - 12 * strength)
+ if(owner.bodytemperature >= minimum_temp)
+ owner.adjust_bodytemperature(-2.75 * strength * seconds_between_ticks, min_temp = minimum_temp)
+
+/atom/movable/screen/alert/status_effect/icecream_chilling
+ desc = "Nothing beats a cup of ice cream during hot, plasma-floody day..."
+ icon_state = "food_icecream"
+
diff --git a/code/datums/status_effects/buffs/food_traits.dm b/code/datums/status_effects/buffs/food/food_traits.dm
similarity index 51%
rename from code/datums/status_effects/buffs/food_traits.dm
rename to code/datums/status_effects/buffs/food/food_traits.dm
index ebe22116dd0dea8..dfd0b888aa09677 100644
--- a/code/datums/status_effects/buffs/food_traits.dm
+++ b/code/datums/status_effects/buffs/food/food_traits.dm
@@ -1,8 +1,7 @@
/datum/status_effect/food/trait/shockimmune
- alert_type = /atom/movable/screen/alert/status_effect/food_trait_shockimmune
+ alert_type = /atom/movable/screen/alert/status_effect/food/trait_shockimmune
trait = TRAIT_SHOCKIMMUNE
-/atom/movable/screen/alert/status_effect/food_trait_shockimmune
+/atom/movable/screen/alert/status_effect/food/trait_shockimmune
name = "Grounded"
desc = "That meal made me feel like a superconductor..."
- icon_state = "food_buff_4"
diff --git a/code/datums/status_effects/buffs/food/haste.dm b/code/datums/status_effects/buffs/food/haste.dm
new file mode 100644
index 000000000000000..8dbe7a7762efef3
--- /dev/null
+++ b/code/datums/status_effects/buffs/food/haste.dm
@@ -0,0 +1,27 @@
+///Haste makes the eater move and act faster
+/datum/status_effect/food/haste
+
+/datum/status_effect/food/haste/on_apply()
+ var/datum/movespeed_modifier/food_haste/speed_mod = new()
+ speed_mod.multiplicative_slowdown = -0.04 * strength
+ owner.add_movespeed_modifier(speed_mod, update = TRUE)
+ var/datum/actionspeed_modifier/status_effect/food_haste/action_mod = new()
+ action_mod.multiplicative_slowdown = -0.06 * strength
+ owner.add_actionspeed_modifier(action_mod, update = TRUE)
+ return ..()
+
+/datum/status_effect/food/haste/be_replaced()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/food_haste)
+ owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/food_haste)
+ return ..()
+
+/datum/status_effect/food/haste/on_remove()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/food_haste, update = TRUE)
+ owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/food_haste, update = TRUE)
+ return ..()
+
+/datum/movespeed_modifier/food_haste
+ multiplicative_slowdown = -0.04
+
+/datum/actionspeed_modifier/status_effect/food_haste
+ multiplicative_slowdown = -0.06
diff --git a/code/datums/status_effects/buffs/food_haste.dm b/code/datums/status_effects/buffs/food_haste.dm
deleted file mode 100644
index 9daf859fb19c7f6..000000000000000
--- a/code/datums/status_effects/buffs/food_haste.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/// Haste makes the eater move faster
-/datum/status_effect/food/haste
- var/datum/movespeed_modifier/food_haste/modifier
-
-/datum/status_effect/food/haste/on_apply()
- modifier = new()
- modifier.multiplicative_slowdown = -0.04 * strength
- owner.add_movespeed_modifier(modifier, update = TRUE)
- return ..()
-
-/datum/status_effect/food/haste/be_replaced()
- owner.remove_movespeed_modifier(modifier, update = TRUE)
- return ..()
-
-/datum/status_effect/food/haste/on_remove()
- owner.remove_movespeed_modifier(modifier, update = TRUE)
- return ..()
-
-/datum/movespeed_modifier/food_haste
- multiplicative_slowdown = -0.1
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index ee1c79fb6267365..1e5bdb91da94fae 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -278,7 +278,7 @@
. = ..()
if(!.)
return
- owner.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS, TRAIT_NUMBED), TRAIT_STATUS_EFFECT(id)) // SKYRAT EDIT CHANGE - ORIGINAL: owner.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS), TRAIT_STATUS_EFFECT(id))
+ owner.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS, TRAIT_ANALGESIA), TRAIT_STATUS_EFFECT(id)) // SKYRAT EDIT CHANGE - ORIGINAL: owner.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS), TRAIT_STATUS_EFFECT(id))
owner.throw_alert("stasis numbed", /atom/movable/screen/alert/numbed) //SKYRAT EDIT ADDITION - STASIS APPLIES NUMBED
owner.add_filter("stasis_status_ripple", 2, list("type" = "ripple", "flags" = WAVE_BOUNDED, "radius" = 0, "size" = 2))
var/filter = owner.get_filter("stasis_status_ripple")
@@ -294,7 +294,7 @@
owner.Sleeping(15 SECONDS) //SKYRAT EDIT END
/datum/status_effect/grouped/stasis/on_remove()
- owner.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS, TRAIT_NUMBED), TRAIT_STATUS_EFFECT(id)) // SKYRAT EDIT CHANGE - ORIGINAL: owner.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS), TRAIT_STATUS_EFFECT(id))
+ owner.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS, TRAIT_ANALGESIA), TRAIT_STATUS_EFFECT(id)) // SKYRAT EDIT CHANGE - ORIGINAL: owner.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED, TRAIT_STASIS), TRAIT_STATUS_EFFECT(id))
owner.clear_alert("stasis numbed") //SKYRAT EDIT ADDITION - STASIS APPLIED NUMBED
owner.remove_filter("stasis_status_ripple")
update_time_of_death()
@@ -346,7 +346,7 @@
/datum/status_effect/crusher_mark
id = "crusher_mark"
duration = 300 //if you leave for 30 seconds you lose the mark, deal with it
- status_type = STATUS_EFFECT_REPLACE
+ status_type = STATUS_EFFECT_MULTIPLE
alert_type = null
var/mutable_appearance/marked_underlay
var/obj/item/kinetic_crusher/hammer_synced
@@ -373,9 +373,9 @@
QDEL_NULL(marked_underlay)
return ..()
-/datum/status_effect/crusher_mark/be_replaced()
- owner.underlays -= marked_underlay //if this is being called, we should have an owner at this point.
- ..()
+//we will only clear ourselves if the crusher is the one that owns us.
+/datum/status_effect/crusher_mark/before_remove(obj/item/kinetic_crusher/attacking_hammer)
+ return (attacking_hammer == hammer_synced)
/datum/status_effect/stacking/saw_bleed
id = "saw_bleed"
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index 2f32ff5b3bedfd0..5bf8269bbbfdace 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -30,8 +30,7 @@
qdel(src)
return
if(isbasicmob(owner))
- var/mob/living/basic/basic_owner = owner
- if(!(basic_owner.basic_mob_flags & FLAMMABLE_MOB))
+ if(!check_basic_mob_immunity(owner))
qdel(src)
return
@@ -94,6 +93,10 @@
stacks = max(0, min(stack_limit, stacks + new_stacks))
cache_stacks()
+/// Checks if the applicable basic mob is immune to the status effect we're trying to apply. Returns TRUE if it is, FALSE if it isn't.
+/datum/status_effect/fire_handler/proc/check_basic_mob_immunity(mob/living/basic/basic_owner)
+ return (basic_owner.basic_mob_flags & FLAMMABLE_MOB)
+
/**
* Refresher for mob's fire_stacks
*/
@@ -272,11 +275,6 @@
overlays |= created_overlay
-/obj/effect/dummy/lighting_obj/moblight/fire
- name = "fire"
- light_color = LIGHT_COLOR_FIRE
- light_range = LIGHT_RANGE_FIRE
-
/datum/status_effect/fire_handler/wet_stacks
id = "wet_stacks"
@@ -292,3 +290,6 @@
if(particle_effect)
return
particle_effect = new(owner, /particles/droplets)
+
+/datum/status_effect/fire_handler/wet_stacks/check_basic_mob_immunity(mob/living/basic/basic_owner)
+ return !(basic_owner.basic_mob_flags & IMMUNE_TO_GETTING_WET)
diff --git a/code/datums/status_effects/food_effects.dm b/code/datums/status_effects/food_effects.dm
index e41ef67ad1059dc..deba7bf750b6b7e 100644
--- a/code/datums/status_effects/food_effects.dm
+++ b/code/datums/status_effects/food_effects.dm
@@ -3,37 +3,24 @@
id = "food_buff"
duration = 5 MINUTES // Same as food mood buffs
status_type = STATUS_EFFECT_REPLACE // Only one food buff allowed
+ alert_type = /atom/movable/screen/alert/status_effect/food
/// Buff power
var/strength
/datum/status_effect/food/on_creation(mob/living/new_owner, timeout_mod = 1, strength = 1)
src.strength = strength
//Generate alert when not specified
- if(alert_type == /atom/movable/screen/alert/status_effect)
- alert_type = "/atom/movable/screen/alert/status_effect/food/buff_[strength]"
if(isnum(timeout_mod))
duration *= timeout_mod
. = ..()
+ if(istype(linked_alert, /atom/movable/screen/alert/status_effect/food))
+ linked_alert.icon_state = "[linked_alert.base_icon_state]_[strength]"
/atom/movable/screen/alert/status_effect/food
name = "Hand-crafted meal"
desc = "Eating it made me feel better."
icon_state = "food_buff_1"
-
-/atom/movable/screen/alert/status_effect/food/buff_1
- icon_state = "food_buff_1"
-
-/atom/movable/screen/alert/status_effect/food/buff_2
- icon_state = "food_buff_2"
-
-/atom/movable/screen/alert/status_effect/food/buff_3
- icon_state = "food_buff_3"
-
-/atom/movable/screen/alert/status_effect/food/buff_4
- icon_state = "food_buff_4"
-
-/atom/movable/screen/alert/status_effect/food/buff_5
- icon_state = "food_buff_5"
+ base_icon_state = "food_buff"
/// Makes you gain a trait
/datum/status_effect/food/trait
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 3f267cb3bad019b..9efc867a043e5f0 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -562,7 +562,7 @@
return ..()
/datum/status_effect/tinlux_light/on_apply()
- mob_light_obj = owner.mob_light(2)
+ mob_light_obj = owner.mob_light(2, 1.5, "#ccff33")
return TRUE
/datum/status_effect/tinlux_light/on_remove()
diff --git a/code/datums/status_effects/song_effects.dm b/code/datums/status_effects/song_effects.dm
index 066ac457a9f42ee..f61253c987d77f8 100644
--- a/code/datums/status_effects/song_effects.dm
+++ b/code/datums/status_effects/song_effects.dm
@@ -44,7 +44,7 @@
var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj
/datum/status_effect/song/light/on_apply()
- mob_light_obj = owner.mob_light(3, color = LIGHT_COLOR_DIM_YELLOW)
+ mob_light_obj = owner.mob_light(3, 1.5, color = LIGHT_COLOR_DIM_YELLOW)
playsound(owner, 'sound/weapons/fwoosh.ogg', 75, FALSE)
return TRUE
diff --git a/code/datums/stock_market_events.dm b/code/datums/stock_market_events.dm
index 81142d2300224dc..4907bf784f63af0 100644
--- a/code/datums/stock_market_events.dm
+++ b/code/datums/stock_market_events.dm
@@ -83,7 +83,7 @@
/datum/stock_market_event/large_boost
name = "Large Boost!"
trend_value = MARKET_TREND_UPWARD
- trend_duration = 3
+ trend_duration = 4
circumstance = list(
"has just released a new product that raised the price of ",
"discovered a new valuable use for ",
@@ -93,14 +93,14 @@
/datum/stock_market_event/large_boost/start_event()
. = ..()
var/price_units = SSstock_market.materials_prices[mat]
- SSstock_market.materials_prices[mat] += round(gaussian(price_units * 0.5, price_units * 0.1))
+ SSstock_market.materials_prices[mat] += round(gaussian(price_units, price_units * 0.15))
SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit)
create_news()
/datum/stock_market_event/large_drop
name = "Large Drop!"
trend_value = MARKET_TREND_DOWNWARD
- trend_duration = 5
+ trend_duration = 4
circumstance = list(
"'s latest product has seen major controversy, and resulted in a price drop for ",
"has been hit with a major lawsuit, resulting in a price drop for ",
@@ -110,6 +110,42 @@
/datum/stock_market_event/large_drop/start_event()
. = ..()
var/price_units = SSstock_market.materials_prices[mat]
- SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.1))
+ SSstock_market.materials_prices[mat] -= round(gaussian(price_units * 1.5, price_units * 0.15))
SSstock_market.materials_prices[mat] = clamp(SSstock_market.materials_prices[mat], price_minimum * mat.value_per_unit, price_maximum * mat.value_per_unit)
create_news()
+
+/datum/stock_market_event/hotcakes
+ name = "Selling like Hotcakes!"
+ trend_value = MARKET_TREND_UPWARD
+ trend_duration = 1
+ circumstance = list(
+ "has just released a new product that is dominating the market for ",
+ "is hitting it big! Dramatically stocking and raising the price of ",
+ ", in a surprise move, monopolized supply and has raised the price of ",
+ )
+
+/datum/stock_market_event/hotcakes/start_event()
+ . = ..()
+ SSstock_market.materials_prices[mat] = round(price_maximum * mat.value_per_unit)
+ create_news()
+
+/datum/stock_market_event/lockdown
+ name = "Lockdown!"
+ trend_value = MARKET_TREND_DOWNWARD
+ trend_duration = 2
+ circumstance = list(
+ "is being investigated by the Galactic Trade Commission, resulting in a halt of trade for ",
+ ", in a stunning move, has been embargoed by TerraGov, resulting in a halt of trade of ",
+ )
+
+/datum/stock_market_event/lockdown/handle()
+ . = ..()
+ SSstock_market.materials_quantity[mat] = 0 //Force the material to be unavailable.
+
+/datum/stock_market_event/lockdown/end_event()
+ . = ..()
+ SSstock_market.materials_quantity[mat] = initial(mat.tradable_base_quantity) //Force the material to be available again.
+ SSstock_market.materials_prices[mat] = initial(mat.value_per_unit) * SHEET_MATERIAL_AMOUNT //Force the price to be reset once the lockdown is over.
+ create_news()
+
+
diff --git a/code/datums/wires/collar_bomb.dm b/code/datums/wires/collar_bomb.dm
new file mode 100644
index 000000000000000..574a3d52dbcfbf1
--- /dev/null
+++ b/code/datums/wires/collar_bomb.dm
@@ -0,0 +1,33 @@
+/datum/wires/collar_bomb
+ proper_name = "Collar Bomb"
+ randomize = TRUE // Only one wire, no need for blueprints
+ holder_type = /obj/item/clothing/neck/collar_bomb
+ wires = list(WIRE_ACTIVATE)
+
+/datum/wires/collar_bomb/interactable(mob/user)
+ . = ..()
+ if(user.get_item_by_slot(ITEM_SLOT_NECK) == holder)
+ return FALSE
+
+/datum/wires/collar_bomb/on_pulse(wire, mob/user)
+ var/obj/item/clothing/neck/collar_bomb/collar = holder
+ if(collar.active)
+ return ..()
+ collar.explosive_countdown(ticks_left = 5)
+ if(!ishuman(collar.loc))
+ return ..()
+ var/mob/living/carbon/human/brian = collar.loc
+ if(brian.get_item_by_slot(ITEM_SLOT_NECK) != collar)
+ return ..()
+ var/mob/living/triggerer = user
+ var/obj/item/assembly/assembly
+ if(isnull(triggerer))
+ assembly = assemblies[colors[1]]
+ if(assembly)
+ triggerer = get_mob_by_key(assembly.fingerprintslast)
+ brian.investigate_log("has had their [collar] triggered [triggerer ? "by [user || assembly][assembly ? " last touched by triggerer" : ""]" : ""].", INVESTIGATE_DEATHS)
+ return ..()
+
+///I'd rather not get people killed by EMP here.
+/datum/wires/collar_bomb/emp_pulse()
+ return
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index db4900a1edd0e01..86d1ade828c00ea 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -35,6 +35,10 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
sound_environment = SOUND_ENVIRONMENT_PLAIN
ambientsounds = list('sound/ambience/shore.ogg', 'sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg')
+/area/awaymission/museum/cafeteria
+ name = "Nanotrasen Museum Cafeteria"
+ sound_environment = SOUND_ENVIRONMENT_ROOM
+
/area/awaymission/errorroom
name = "Super Secret Room"
static_lighting = FALSE
diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm
index c16d65059ff5c83..3d4c0fa41722677 100644
--- a/code/game/area/areas/ruins/lavaland.dm
+++ b/code/game/area/areas/ruins/lavaland.dm
@@ -10,6 +10,9 @@
name = "\improper Clown Biodome"
ambientsounds = list('sound/ambience/clown.ogg')
+/area/ruin/lizard_gaslava
+ name = "\improper Lizard's Gas(Lava)"
+
/area/ruin/unpowered/gaia
name = "\improper Patch of Eden"
diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm
index 71b86bafe722113..ad841856c64622e 100644
--- a/code/game/atom/_atom.dm
+++ b/code/game/atom/_atom.dm
@@ -794,6 +794,10 @@
/atom/proc/setClosed()
return
+///Called after the atom is 'tamed' for type-specific operations, Usually called by the tameable component but also other things.
+/atom/proc/tamed(mob/living/tamer, obj/item/food)
+ return
+
/**
* Used to attempt to charge an object with a payment component.
*
diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm
index 078615dba75ee03..4b91f396095fdc4 100644
--- a/code/game/atom/atom_tool_acts.dm
+++ b/code/game/atom/atom_tool_acts.dm
@@ -25,8 +25,8 @@
return early_sig_return
var/interact_return = is_left_clicking \
- ? tool.interact_with_atom(src, user) \
- : tool.interact_with_atom_secondary(src, user)
+ ? tool.interact_with_atom(src, user, modifiers) \
+ : tool.interact_with_atom_secondary(src, user, modifiers)
if(interact_return)
return interact_return
@@ -92,7 +92,7 @@
* Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
* Return NONE to allow default interaction / tool handling.
*/
-/obj/item/proc/interact_with_atom(atom/interacting_with, mob/living/user)
+/obj/item/proc/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return NONE
/**
@@ -104,8 +104,8 @@
* Return an ITEM_INTERACT_ flag in the event the interaction was handled, to cancel further interaction code.
* Return NONE to allow default interaction / tool handling.
*/
-/obj/item/proc/interact_with_atom_secondary(atom/interacting_with, mob/living/user)
- return interact_with_atom(interacting_with, user)
+/obj/item/proc/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ return interact_with_atom(interacting_with, user, modifiers)
/*
* Tool-specific behavior procs.
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 9603e87067d802f..582df12516f5d78 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -425,7 +425,7 @@
if(z_move_flags & ZMOVE_CAN_FLY_CHECKS && !(movement_type & (FLYING|FLOATING)) && has_gravity(start))
if(z_move_flags & ZMOVE_FEEDBACK)
if(rider)
- to_chat(rider, span_warning("[src] is is not capable of flight."))
+ to_chat(rider, span_warning("[src] [p_are()] incapable of flight."))
else
to_chat(src, span_warning("You are not Superman."))
return FALSE
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index ba9667b3e5809dd..a0e2eab029b6dfd 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -95,10 +95,19 @@
/obj/machinery/autolathe/proc/AfterMaterialInsert(container, obj/item/item_inserted, last_inserted_id, mats_consumed, amount_inserted, atom/context)
SIGNAL_HANDLER
- flick("autolathe_[item_inserted.has_material_type(/datum/material/glass) ? "r" : "o"]", src)
-
//we use initial(active_power_usage) because higher tier parts will have higher active usage but we have no benifit from it
- directly_use_power(ROUND_UP((amount_inserted / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * 0.01 * initial(active_power_usage)))
+ if(directly_use_power(ROUND_UP((amount_inserted / (MAX_STACK_SIZE * SHEET_MATERIAL_AMOUNT)) * 0.02 * initial(active_power_usage))))
+ flick_overlay_view(mutable_appearance('icons/obj/machines/lathes.dmi', "autolathe_mat"), 1 SECONDS)
+
+ var/datum/material/highest_mat_ref
+ var/highest_mat = 0
+ for(var/datum/material/mat as anything in mats_consumed)
+ var/present_mat = mats_consumed[mat]
+ if(present_mat > highest_mat)
+ highest_mat = present_mat
+ highest_mat_ref = mat
+
+ flick_overlay_view(material_insertion_animation(highest_mat_ref.greyscale_colors), 1 SECONDS)
/obj/machinery/autolathe/ui_interact(mob/user, datum/tgui/ui)
if(!is_operational)
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 5824f93dcc736da..429ecd70b664903 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -21,6 +21,7 @@
armor_type = /datum/armor/machinery_button
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.02
resistance_flags = LAVA_PROOF | FIRE_PROOF
+ interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OPEN
/obj/machinery/button/indestructible
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 69c01a22f62c7df..9dc50ff9ce7c0ae 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -185,7 +185,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0)
/obj/machinery/camera/proc/on_saboteur(datum/source, disrupt_duration)
SIGNAL_HANDLER
- emp_act(EMP_LIGHT, reset_time = disrupt_duration)
+ //lasts twice as much so we don't have to constantly shoot cameras just to be S T E A L T H Y
+ emp_act(EMP_LIGHT, reset_time = disrupt_duration * 2)
return COMSIG_SABOTEUR_SUCCESS
/obj/machinery/camera/proc/post_emp_reset(thisemp, previous_network)
diff --git a/code/game/machinery/computer/accounting.dm b/code/game/machinery/computer/accounting.dm
index 475bf404c1ce057..d804b8efe5d942a 100644
--- a/code/game/machinery/computer/accounting.dm
+++ b/code/game/machinery/computer/accounting.dm
@@ -21,10 +21,9 @@
for(var/current_account as anything in SSeconomy.bank_accounts_by_id)
var/datum/bank_account/current_bank_account = SSeconomy.bank_accounts_by_id[current_account]
- var/job_title = current_bank_account.account_job?.title
player_accounts += list(list(
"name" = current_bank_account.account_holder,
- "job" = job_title ? job_title : "No Job", // because this can be null
+ "job" = current_bank_account.account_job?.title || "No job", // because this can be null
"balance" = round(current_bank_account.account_balance),
"modifier" = round((current_bank_account.payday_modifier * 0.9), 0.1),
))
@@ -32,4 +31,3 @@
data["AuditLog"] = audit_list
data["Crashing"] = HAS_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING)
return data
-
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index 157a3a77fbee91b..f3b43e07caa98df 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -10,7 +10,6 @@
. = ..()
AddComponent(/datum/component/simple_rotation)
register_context()
- update_appearance(UPDATE_ICON_STATE)
/obj/structure/frame/computer/deconstruct(disassembled = TRUE)
if(!(obj_flags & NO_DECONSTRUCTION))
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 37514c142fe8ab7..da807f52764e2ed 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -50,7 +50,7 @@
/obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui)
. = ..()
- if(!user.can_perform_action(src, NEED_DEXTERITY)) //prevents monkeys from using camera consoles
+ if(!user.client) //prevents errors by trying to pass clients that don't exist.
return
// Update UI
ui = SStgui.try_update_ui(user, src, ui)
diff --git a/code/game/machinery/computer/orders/order_items/mining/order_pka.dm b/code/game/machinery/computer/orders/order_items/mining/order_pka.dm
index 251343e6f8e2c6a..f239e9f2a7eaf9d 100644
--- a/code/game/machinery/computer/orders/order_items/mining/order_pka.dm
+++ b/code/game/machinery/computer/orders/order_items/mining/order_pka.dm
@@ -40,3 +40,7 @@
/datum/orderable_item/accelerator/minebot_passthrough
item_path = /obj/item/borg/upgrade/modkit/minebot_passthrough
cost_per_order = 800
+
+/datum/orderable_item/accelerator/friendly_fire
+ item_path = /obj/item/borg/upgrade/modkit/human_passthrough
+ cost_per_order = 750
diff --git a/code/game/machinery/computer/records/records.dm b/code/game/machinery/computer/records/records.dm
index 531e1725e332545..baf0560b5f6a0db 100644
--- a/code/game/machinery/computer/records/records.dm
+++ b/code/game/machinery/computer/records/records.dm
@@ -35,6 +35,7 @@
return FALSE
var/value = trim(params["value"], MAX_BROADCAST_LEN)
+ investigate_log("[key_name(usr)] changed the field: \"[field]\" with value: \"[target.vars[field]]\" to new value: \"[value || "Unknown"]\"", INVESTIGATE_RECORDS)
target.vars[field] = value || "Unknown"
return TRUE
@@ -56,6 +57,7 @@
if("login")
authenticated = secure_login(usr)
+ investigate_log("[key_name(usr)] [authenticated ? "successfully logged" : "failed to log"] into the [src].", INVESTIGATE_RECORDS)
return TRUE
if("logout")
diff --git a/code/game/machinery/computer/records/security.dm b/code/game/machinery/computer/records/security.dm
index 0064cf570a69105..ec2bec3d2669006 100644
--- a/code/game/machinery/computer/records/security.dm
+++ b/code/game/machinery/computer/records/security.dm
@@ -163,6 +163,7 @@
return TRUE
if("delete_record")
+ investigate_log("[usr] deleted record: \"[target]\".", INVESTIGATE_RECORDS)
qdel(target)
return TRUE
@@ -179,8 +180,9 @@
return TRUE
if("set_note")
- var/note = params["note"]
- target.security_note = trim(note, MAX_MESSAGE_LEN)
+ var/note = trim(params["note"], MAX_MESSAGE_LEN)
+ investigate_log("[usr] has changed the security note of record: \"[target]\" from \"[target.security_note]\" to \"[note]\".")
+ target.security_note = note
return TRUE
if("set_wanted")
@@ -243,14 +245,19 @@
return FALSE
if(user != editing_crime.author && !has_armory_access(user)) // only warden/hos/command can edit crimes they didn't author
+ investigate_log("[user] attempted to edit crime: \"[editing_crime.name]\" for target: \"[target.name]\" but failed due to lacking armoury access and not being the author of the crime.", INVESTIGATE_RECORDS)
return FALSE
if(params["name"] && length(params["name"]) > 2 && params["name"] != editing_crime.name)
- editing_crime.name = trim(params["name"], MAX_CRIME_NAME_LEN)
+ var/new_name = trim(params["name"], MAX_CRIME_NAME_LEN)
+ investigate_log("[user] edited crime: \"[editing_crime.name]\" for target: \"[target.name]\", changing the name to: \"[new_name]\".", INVESTIGATE_RECORDS)
+ editing_crime.name = new_name
return TRUE
if(params["details"] && length(params["description"]) > 2 && params["name"] != editing_crime.name)
- editing_crime.details = trim(params["details"], MAX_MESSAGE_LEN)
+ var/new_details = trim(params["details"], MAX_MESSAGE_LEN)
+ investigate_log("[user] edited crime \"[editing_crime.name]\" for target: \"[target.name]\", changing the details to: \"[new_details]\" from: \"[editing_crime.details]\".", INVESTIGATE_RECORDS)
+ editing_crime.details = new_details
return TRUE
return FALSE
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index e624e3f33d705a5..b3c1e055679d0ac 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -13,6 +13,10 @@
/// The current (de/con)struction state of the frame
var/state = FRAME_STATE_EMPTY
+/obj/structure/frame/Initialize(mapload)
+ . = ..()
+ update_appearance(UPDATE_ICON_STATE)
+
/obj/structure/frame/examine(user)
. = ..()
if(circuit)
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index db09a0db5603660..aa7e66824023f85 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -239,7 +239,7 @@
var/obj/item/I = AM
if(!density || (I.w_class < WEIGHT_CLASS_NORMAL && !LAZYLEN(I.GetAccess())))
return
- if(check_access(I))
+ if(requiresID() && check_access(I))
open()
else
do_animate("deny")
diff --git a/code/game/machinery/machine_frame.dm b/code/game/machinery/machine_frame.dm
index a3c074937f0f765..5a6ff12c3d10231 100644
--- a/code/game/machinery/machine_frame.dm
+++ b/code/game/machinery/machine_frame.dm
@@ -12,7 +12,6 @@
/obj/structure/frame/machine/Initialize(mapload)
. = ..()
register_context()
- update_appearance(UPDATE_ICON_STATE)
/obj/structure/frame/machine/Destroy()
QDEL_LIST(components)
@@ -26,8 +25,8 @@
return ..()
/obj/structure/frame/machine/try_dissassemble(mob/living/user, obj/item/tool, disassemble_time)
- if(anchored)
- balloon_alert(user, "must be unsecured first!")
+ if(anchored && state == FRAME_STATE_EMPTY) //when using a screwdriver on an incomplete frame(missing components) no point checking for this
+ balloon_alert(user, "must be unanchored first!")
return FALSE
return ..()
@@ -36,14 +35,15 @@
if(isnull(held_item))
return
+ if(held_item.tool_behaviour == TOOL_WRENCH && !circuit?.needs_anchored)
+ context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor"
+ return CONTEXTUAL_SCREENTIP_SET
+
switch(state)
if(FRAME_STATE_EMPTY)
if(istype(held_item, /obj/item/stack/cable_coil))
context[SCREENTIP_CONTEXT_LMB] = "Wire Frame"
return CONTEXTUAL_SCREENTIP_SET
- else if(held_item.tool_behaviour == TOOL_WRENCH)
- context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor"
- return CONTEXTUAL_SCREENTIP_SET
else if(held_item.tool_behaviour == TOOL_WELDER)
context[SCREENTIP_CONTEXT_LMB] = "Unweld frame"
return CONTEXTUAL_SCREENTIP_SET
@@ -61,11 +61,6 @@
if(held_item.tool_behaviour == TOOL_CROWBAR)
context[SCREENTIP_CONTEXT_LMB] = "Pry out components"
return CONTEXTUAL_SCREENTIP_SET
- else if(held_item.tool_behaviour == TOOL_WRENCH)
- if(!circuit.needs_anchored)
- context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor"
- return CONTEXTUAL_SCREENTIP_SET
- return NONE
else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
var/needs_components = FALSE
for(var/component in req_components)
@@ -470,5 +465,6 @@
return TRUE
/obj/structure/frame/machine/secured
+ icon_state = "box_1"
state = FRAME_STATE_WIRED
anchored = TRUE
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index af6b477e90ba86d..d9e3787fd9ead12 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -248,7 +248,7 @@ Buildable meters
return TRUE
// no conflicts found
- var/obj/machinery/atmospherics/built_machine = new pipe_type(loc, , , p_init_dir)
+ var/obj/machinery/atmospherics/built_machine = new pipe_type(loc, null, fixed_dir(), p_init_dir)
build_pipe(built_machine)
built_machine.on_construction(user, pipe_color, piping_layer)
transfer_fingerprints_to(built_machine)
@@ -356,9 +356,6 @@ Buildable meters
return FALSE
/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A)
- A.setDir(fixed_dir())
- A.set_init_directions(p_init_dir)
-
if(pipename)
A.name = pipename
if(A.on)
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index 809171af1efa7cd..f5f8327607af3f3 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -103,6 +103,8 @@ DEFINE_BITFIELD(turret_flags, list(
var/datum/action/turret_toggle/toggle_action
/// Mob that is remotely controlling the turret
var/mob/remote_controller
+ /// While the cooldown is still going on, it cannot be re-enabled.
+ COOLDOWN_DECLARE(disabled_time)
/datum/armor/machinery_porta_turret
melee = 50
@@ -133,18 +135,32 @@ DEFINE_BITFIELD(turret_flags, list(
if(!has_cover)
INVOKE_ASYNC(src, PROC_REF(popUp))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
+
AddElement(/datum/element/hostile_machine)
-/obj/machinery/porta_turret/proc/toggle_on(set_to)
- var/current = on
- if (!isnull(set_to))
- on = set_to
- else
- on = !on
- if (current != on)
- check_should_process()
- if (!on)
- popDown()
+///Toggles the turret on or off depending on the value of the turn_on arg.
+/obj/machinery/porta_turret/proc/toggle_on(turn_on = TRUE)
+ if(on == turn_on)
+ return
+ if(on && !COOLDOWN_FINISHED(src, disabled_time))
+ return
+ on = turn_on
+ check_should_process()
+ if (!on)
+ popDown()
+
+///Prevents turned from being turned on for a duration, then restarts them after that if the second ard is true.
+/obj/machinery/porta_turret/proc/set_disabled(duration = 6 SECONDS, will_restart = on)
+ COOLDOWN_START(src, disabled_time, duration)
+ if(will_restart)
+ addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), duration + 1) //the cooldown isn't over until the tick after its end.
+ toggle_on(FALSE)
+
+/obj/machinery/porta_turret/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(set_disabled), disrupt_duration)
+ return COMSIG_SABOTEUR_SUCCESS
/obj/machinery/porta_turret/proc/check_should_process()
if (datum_flags & DF_ISPROCESSING)
@@ -256,7 +272,7 @@ DEFINE_BITFIELD(turret_flags, list(
switch(action)
if("power")
if(anchored)
- toggle_on()
+ toggle_on(!on)
return TRUE
else
to_chat(usr, span_warning("It has to be secured first!"))
@@ -364,10 +380,8 @@ DEFINE_BITFIELD(turret_flags, list(
audible_message(span_hear("[src] hums oddly..."))
obj_flags |= EMAGGED
controllock = TRUE
- toggle_on(FALSE) //turns off the turret temporarily
+ set_disabled(6 SECONDS)
update_appearance()
- //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit
- addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), 6 SECONDS)
//turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here
return TRUE
@@ -385,11 +399,9 @@ DEFINE_BITFIELD(turret_flags, list(
if(prob(20))
turret_flags |= TURRET_FLAG_SHOOT_ALL // Shooting everyone is a pretty big deal, so it's least likely to get turned on
- toggle_on(FALSE)
+ set_disabled(rand(6 SECONDS, 20 SECONDS))
remove_control()
- addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), rand(60,600))
-
/obj/machinery/porta_turret/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0)
. = ..()
if(. && atom_integrity > 0) //damage received
@@ -1186,17 +1198,14 @@ DEFINE_BITFIELD(turret_flags, list(
installation = /obj/item/gun/energy/laser/bluetag
team_color = "blue"
-/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/P)
+/obj/machinery/porta_turret/lasertag/bullet_act(obj/projectile/projectile)
. = ..()
- if(on)
- if(team_color == "blue")
- if(istype(P, /obj/projectile/beam/lasertag/redtag))
- toggle_on(FALSE)
- addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), 10 SECONDS)
- else if(team_color == "red")
- if(istype(P, /obj/projectile/beam/lasertag/bluetag))
- toggle_on(FALSE)
- addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), 10 SECONDS)
+ if(!on)
+ return
+ if(team_color == "blue" && istype(projectile, /obj/projectile/beam/lasertag/redtag))
+ set_disabled(10 SECONDS)
+ else if(team_color == "red" && istype(projectile, /obj/projectile/beam/lasertag/bluetag))
+ set_disabled(10 SECONDS)
#undef TURRET_STUN
#undef TURRET_LETHAL
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index 1014393c008362a..5d4fd671c9623de 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -19,22 +19,9 @@
var/datum/component/material_container/materials
/obj/machinery/recycler/Initialize(mapload)
- var/list/allowed_materials = list(
- /datum/material/iron,
- /datum/material/glass,
- /datum/material/silver,
- /datum/material/plasma,
- /datum/material/gold,
- /datum/material/diamond,
- /datum/material/plastic,
- /datum/material/uranium,
- /datum/material/bananium,
- /datum/material/titanium,
- /datum/material/bluespace
- )
materials = AddComponent(
/datum/component/material_container, \
- allowed_materials, \
+ SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \
INFINITY, \
MATCONTAINER_NO_INSERT \
)
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index 7fe6c59075f3fd1..f21bfc4788426ed 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -124,11 +124,28 @@
desc = "You know who to call."
light_power = 2
+/obj/effect/decal/cleanable/greenglow/radioactive
+ name = "radioactive goo"
+ desc = "Holy crap, stop looking at this and move away immediately! It's radioactive!"
+ light_power = 5
+ light_range = 3
+ light_color = LIGHT_COLOR_NUCLEAR
+
+/obj/effect/decal/cleanable/greenglow/radioactive/Initialize(mapload, list/datum/disease/diseases)
+ . = ..()
+ AddComponent(
+ /datum/component/radioactive_emitter, \
+ cooldown_time = 5 SECONDS, \
+ range = 4, \
+ threshold = RAD_MEDIUM_INSULATION, \
+ )
+
/obj/effect/decal/cleanable/cobweb
name = "cobweb"
desc = "Somebody should remove that."
gender = NEUTER
layer = WALL_OBJ_LAYER
+ icon = 'icons/effects/web.dmi'
icon_state = "cobweb1"
resistance_flags = FLAMMABLE
beauty = -100
diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm
index c3fad6d26b61d7b..874c53fa83c7db3 100644
--- a/code/game/objects/effects/effect_system/effects_sparks.dm
+++ b/code/game/objects/effects/effect_system/effects_sparks.dm
@@ -17,8 +17,8 @@
icon_state = "sparks"
anchored = TRUE
light_system = OVERLAY_LIGHT
- light_range = 2
- light_power = 0.5
+ light_range = 1.5
+ light_power = 0.8
light_color = LIGHT_COLOR_FIRE
/obj/effect/particle_effect/sparks/Initialize(mapload)
diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm
index 377c8470480be93..0c050579de45b23 100644
--- a/code/game/objects/effects/effects.dm
+++ b/code/game/objects/effects/effects.dm
@@ -48,6 +48,7 @@
///The abstract effect ignores even more effects and is often typechecked for atoms that should truly not be fucked with.
/obj/effect/abstract
+ resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF
/obj/effect/abstract/singularity_pull()
return
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index f6151a3cbf602c0..2a6d1c039ad1516 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -36,7 +36,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
/obj/effect/landmark/start/proc/after_round_start()
// We'd like to keep these around for unit tests, so we can check that they exist.
-#ifndef UNIT_TESTS
+#if !defined(UNIT_TESTS) && !defined(MAP_TEST)
if(delete_after_roundstart)
qdel(src)
#endif
diff --git a/code/game/objects/effects/lighting.dm b/code/game/objects/effects/lighting.dm
index 1de9fad39eee42c..caeedbd22bb89b5 100644
--- a/code/game/objects/effects/lighting.dm
+++ b/code/game/objects/effects/lighting.dm
@@ -37,6 +37,7 @@
name = "mob fire lighting"
light_color = LIGHT_COLOR_FIRE
light_range = LIGHT_RANGE_FIRE
+ light_power = 2
/obj/effect/dummy/lighting_obj/moblight/species
name = "species lighting"
diff --git a/code/game/objects/effects/material_insert.dm b/code/game/objects/effects/material_insert.dm
new file mode 100644
index 000000000000000..9ca86226b24b911
--- /dev/null
+++ b/code/game/objects/effects/material_insert.dm
@@ -0,0 +1,22 @@
+/**
+ * Creates a mutable appearance with the material color applied for its insertion animation into an autolathe or techfab
+ * Arguments
+ *
+ * * color - the material color that will be applied
+ */
+/proc/material_insertion_animation(color)
+ RETURN_TYPE(/mutable_appearance)
+
+ var/static/list/mutable_appearance/apps = list()
+
+ var/mutable_appearance/cached_app = apps[color]
+ if(isnull(cached_app))
+ var/icon/modified_icon = icon('icons/obj/machines/research.dmi', "material_insertion")
+
+ //assuming most of the icon is white we find what ratio to scale the intensity of each part roughly
+ var/list/rgb_list = rgb2num(color)
+ modified_icon.SetIntensity(rgb_list[1] / 255, rgb_list[2] / 255, rgb_list[3] / 255)
+ cached_app = mutable_appearance(modified_icon, "material_insertion")
+
+ apps[color] = cached_app
+ return cached_app
diff --git a/code/game/objects/effects/posters/contraband.dm b/code/game/objects/effects/posters/contraband.dm
index 2bb2fcce50e4696..52528c251b65979 100644
--- a/code/game/objects/effects/posters/contraband.dm
+++ b/code/game/objects/effects/posters/contraband.dm
@@ -625,3 +625,35 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/blood_geometer
icon_state = "singletank_bomb"
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/singletank_bomb, 32)
+
+///a special poster meant to fool people into thinking this is a bombable wall at a glance.
+/obj/structure/sign/poster/contraband/fake_bombable
+ name = "fake bombable poster"
+ desc = "We do a little trolling."
+ icon_state = "fake_bombable"
+ never_random = TRUE
+
+/obj/structure/sign/poster/contraband/fake_bombable/Initialize(mapload)
+ . = ..()
+ var/turf/our_wall = get_turf_pixel(src)
+ name = our_wall.name
+
+/obj/structure/sign/poster/contraband/fake_bombable/examine(mob/user)
+ var/turf/our_wall = get_turf_pixel(src)
+ . = our_wall.examine(user)
+ . += span_notice("It seems to be slightly cracked...")
+
+/obj/structure/sign/poster/contraband/fake_bombable/ex_act(severity, target)
+ addtimer(CALLBACK(src, PROC_REF(fall_off_wall)), 2.5 SECONDS)
+ return FALSE
+
+/obj/structure/sign/poster/contraband/fake_bombable/proc/fall_off_wall()
+ if(QDELETED(src) || !isturf(loc))
+ return
+ var/turf/our_wall = get_turf_pixel(src)
+ our_wall.balloon_alert_to_viewers("it was a ruse!")
+ roll_and_drop(loc)
+ playsound(loc, 'sound/items/handling/paper_drop.ogg', 50, TRUE)
+
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/poster/contraband/fake_bombable, 32)
diff --git a/code/game/objects/effects/posters/poster.dm b/code/game/objects/effects/posters/poster.dm
index 75a3a26ce26f28f..c4703d700c4a45f 100644
--- a/code/game/objects/effects/posters/poster.dm
+++ b/code/game/objects/effects/posters/poster.dm
@@ -180,7 +180,7 @@
qdel(src)
else
to_chat(user, span_notice("You carefully remove the poster from the wall."))
- roll_and_drop(Adjacent(user) ? get_turf(user) : loc)
+ roll_and_drop(Adjacent(user) ? get_turf(user) : loc, user)
/obj/structure/sign/poster/attack_hand(mob/user, list/modifiers)
. = ..()
@@ -207,11 +207,12 @@
return FALSE
return !user.gloves || !(user.gloves.body_parts_covered & HANDS) || HAS_TRAIT(user, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(user.gloves, TRAIT_FINGERPRINT_PASSTHROUGH)
-/obj/structure/sign/poster/proc/roll_and_drop(atom/location)
+/obj/structure/sign/poster/proc/roll_and_drop(atom/location, mob/user)
pixel_x = 0
pixel_y = 0
var/obj/item/poster/rolled_poster = new poster_item_type(location, src) // /obj/structure/sign/poster/wanted/roll_and_drop() has some snowflake handling due to icon memes, if you make a major change to this, don't forget to update it too. <3
- forceMove(rolled_poster)
+ if(!user?.put_in_hands(rolled_poster))
+ forceMove(rolled_poster)
return rolled_poster
//separated to reduce code duplication. Moved here for ease of reference and to unclutter r_wall/attackby()
@@ -246,7 +247,7 @@
var/turf/user_drop_location = get_turf(user) //cache this so it just falls to the ground if they move. also no tk memes allowed.
if(!do_after(user, PLACE_SPEED, placed_poster, extra_checks = CALLBACK(placed_poster, TYPE_PROC_REF(/obj/structure/sign/poster, snowflake_closed_turf_check), src)))
- placed_poster.roll_and_drop(user_drop_location)
+ placed_poster.roll_and_drop(user_drop_location, user)
return
placed_poster.on_placed_poster(user)
diff --git a/code/game/objects/effects/spawners/random/ai_module.dm b/code/game/objects/effects/spawners/random/ai_module.dm
index 1ca7a05f2e94d72..b63efa178e28a5c 100644
--- a/code/game/objects/effects/spawners/random/ai_module.dm
+++ b/code/game/objects/effects/spawners/random/ai_module.dm
@@ -40,6 +40,7 @@
/obj/item/ai_module/remove,
/obj/item/ai_module/core/full/dagothbot, // SKYRAT EDIT - EDITION
/obj/item/ai_module/core/full/texas, // SKYRAT EDIT - EDITION
+ /obj/item/ai_module/core/full/emperor, // SKYRAT EDIT - EDITION
)
/obj/effect/spawner/random/aimodule/harmful
diff --git a/code/game/objects/effects/spawners/random/contraband.dm b/code/game/objects/effects/spawners/random/contraband.dm
index ca5acbdbe6767b6..e65a73cfe4ce7e4 100644
--- a/code/game/objects/effects/spawners/random/contraband.dm
+++ b/code/game/objects/effects/spawners/random/contraband.dm
@@ -21,22 +21,20 @@
/obj/item/storage/fancy/cigarettes/cigpack_syndicate = 10,
/obj/item/storage/fancy/cigarettes/cigpack_shadyjims = 10,
/obj/item/storage/box/donkpockets = 10,
+ /obj/effect/spawner/random/contraband/plus = 10,
/obj/item/reagent_containers/pill/maintenance = 5,
- /obj/effect/spawner/random/contraband/plus = 5,
)
/obj/effect/spawner/random/contraband/plus
name = "contraband loot spawner plus"
desc = "Where'd ya find this?"
loot = list(
- /obj/effect/spawner/random/contraband/prison = 40,
/obj/item/clothing/under/syndicate = 20,
/obj/item/reagent_containers/cup/bottle/thermite = 20,
- /obj/item/reagent_containers/pill/maintenance = 10,
/obj/item/restraints/legcuffs/beartrap = 10,
- /obj/effect/spawner/random/contraband/narcotics = 10,
- /obj/item/seeds/kronkus = 5,
- /obj/item/seeds/odious_puffball = 5,
+ /obj/item/food/drug/saturnx = 5,
+ /obj/item/reagent_containers/cup/blastoff_ampoule = 5,
+ /obj/item/food/drug/moon_rock = 5,
/obj/item/grenade/empgrenade = 5,
/obj/effect/spawner/random/contraband/armory = 1,
)
diff --git a/code/game/objects/effects/spawners/random/vending.dm b/code/game/objects/effects/spawners/random/vending.dm
index 74ece7f24f93d8c..014f07d2967c458 100644
--- a/code/game/objects/effects/spawners/random/vending.dm
+++ b/code/game/objects/effects/spawners/random/vending.dm
@@ -18,6 +18,11 @@
loot_type_path = /obj/machinery/vending/snack
loot = list()
+/obj/effect/spawner/random/vending/snackvend/Initialize(mapload)
+ if(check_holidays(HOTDOG_DAY))
+ loot += /obj/machinery/vending/hotdog
+ return ..()
+
/obj/effect/spawner/random/vending/colavend
name = "spawn random cola vending machine"
desc = "Automagically transforms into a random cola vendor. If you see this while in a shift, please create a bug report."
diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm
index 5023f9bd8254e7c..2d0f1b9b14de219 100644
--- a/code/game/objects/effects/spiderwebs.dm
+++ b/code/game/objects/effects/spiderwebs.dm
@@ -1,7 +1,8 @@
-//generic procs copied from obj/effect/alien
+#define SPIDER_WEB_TINT "web_colour_tint"
+
/obj/structure/spider
name = "web"
- icon = 'icons/effects/effects.dmi'
+ icon = 'icons/effects/web.dmi'
desc = "It's stringy and sticky."
anchored = TRUE
density = FALSE
@@ -22,7 +23,7 @@
damage_amount *= 1.25
if(BRUTE)
damage_amount *= 0.25
- . = ..()
+ return ..()
/obj/structure/spider/should_atmos_process(datum/gas_mixture/air, exposed_temperature)
return exposed_temperature > 350
@@ -31,17 +32,37 @@
take_damage(5, BURN, 0, 0)
/obj/structure/spider/stickyweb
+ plane = FLOOR_PLANE
+ layer = MID_TURF_LAYER
+ icon = 'icons/obj/smooth_structures/stickyweb.dmi'
+ base_icon_state = "stickyweb"
+ icon_state = "stickyweb-0"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB + SMOOTH_GROUP_WALLS
///Whether or not the web is from the genetics power
var/genetic = FALSE
///Whether or not the web is a sealed web
var/sealed = FALSE
- icon_state = "stickyweb1"
+ ///Do we need to offset this based on a sprite frill?
+ var/has_frill = TRUE
+ /// Chance that someone will get stuck when trying to cross this tile
+ var/stuck_chance = 50
+ /// Chance that a bullet will hit this instead of flying through it
+ var/projectile_stuck_chance = 30
+
+/obj/structure/spider/stickyweb/Initialize(mapload)
+ // Offset on init so that they look nice in the map editor
+ if (has_frill)
+ pixel_x = -9
+ pixel_y = -9
+ return ..()
/obj/structure/spider/stickyweb/attack_hand(mob/user, list/modifiers)
.= ..()
if(.)
return
- if(!HAS_TRAIT(user,TRAIT_WEB_WEAVER))
+ if(!HAS_TRAIT(user, TRAIT_WEB_WEAVER))
return
loc.balloon_alert_to_viewers("weaving...")
if(!do_after(user, 2 SECONDS))
@@ -51,11 +72,6 @@
var/obj/item/stack/sheet/cloth/woven_cloth = new /obj/item/stack/sheet/cloth
user.put_in_hands(woven_cloth)
-/obj/structure/spider/stickyweb/Initialize(mapload)
- if(!sealed && prob(50))
- icon_state = "stickyweb2"
- . = ..()
-
/obj/structure/spider/stickyweb/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(genetic)
@@ -67,65 +83,125 @@
return TRUE
if(mover.pulledby && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER))
return TRUE
- if(prob(50))
- loc.balloon_alert(mover, "stuck in web!")
+ if(prob(stuck_chance))
+ stuck_react(mover)
return FALSE
- else if(isprojectile(mover))
- return prob(30)
-
-/obj/structure/spider/stickyweb/sealed
- name = "sealed web"
- desc = "A solid thick wall of web, airtight enough to block air flow."
- icon_state = "sealedweb"
- sealed = TRUE
- can_atmos_pass = ATMOS_PASS_NO
+ return .
+ if(isprojectile(mover))
+ return prob(projectile_stuck_chance)
+ return .
-/obj/structure/spider/stickyweb/sealed/Initialize(mapload)
- . = ..()
- air_update_turf(TRUE, TRUE)
+/// Show some feedback when you can't pass through something
+/obj/structure/spider/stickyweb/proc/stuck_react(atom/movable/stuck_guy)
+ loc.balloon_alert(stuck_guy, "stuck in web!")
+ stuck_guy.Shake(duration = 0.1 SECONDS)
-/obj/structure/spider/stickyweb/genetic //for the spider genes in genetics
+/// Web made by geneticists, needs special handling to allow them to pass through their own webs
+/obj/structure/spider/stickyweb/genetic
genetic = TRUE
+ desc = "It's stringy, sticky, and came out of your coworker."
+ /// Mob with special permission to cross this web
var/mob/living/allowed_mob
/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob)
- allowed_mob = allowedmob
. = ..()
+ // Tint it purple so that spiders don't get confused about why they can't cross this one
+ add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffaaf8ff", "size" = 0.1))
+
+/obj/structure/spider/stickyweb/genetic/Initialize(mapload, allowedmob)
+ allowed_mob = allowedmob
+ return ..()
/obj/structure/spider/stickyweb/genetic/CanAllowThrough(atom/movable/mover, border_dir)
- . = ..() //this is the normal spider web return aka a spider would make this TRUE
+ . = ..()
if(mover == allowed_mob)
return TRUE
else if(isliving(mover)) //we change the spider to not be able to go through here
if(mover.pulledby == allowed_mob)
return TRUE
if(prob(50))
- loc.balloon_alert(mover, "stuck in web!")
+ stuck_react(mover)
return FALSE
else if(isprojectile(mover))
return prob(30)
+ return .
-/obj/structure/spider/solid
- name = "solid web"
- icon = 'icons/effects/effects.dmi'
- desc = "A solid wall of web, thick enough to block air flow."
- icon_state = "solidweb"
+/// Web with a 100% chance to intercept movement
+/obj/structure/spider/stickyweb/very_sticky
+ max_integrity = 20
+ desc = "Extremely sticky silk, you're not easily getting through there."
+ stuck_chance = 100
+ projectile_stuck_chance = 100
+
+/obj/structure/spider/stickyweb/very_sticky/Initialize(mapload)
+ . = ..()
+ add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffffaaff", "size" = 0.1))
+
+/obj/structure/spider/stickyweb/very_sticky/update_overlays()
+ . = ..()
+ var/mutable_appearance/web_overlay = mutable_appearance(icon = 'icons/effects/web.dmi', icon_state = "sticky_overlay", layer = layer + 1)
+ web_overlay.pixel_x -= pixel_x
+ web_overlay.pixel_y -= pixel_y
+ . += web_overlay
+
+
+/// Web 'wall'
+/obj/structure/spider/stickyweb/sealed
+ name = "sealed web"
+ desc = "A solid wall of web, dense enough to block air flow."
+ icon = 'icons/obj/smooth_structures/webwall.dmi'
+ base_icon_state = "webwall"
+ icon_state = "webwall-0"
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL
+ plane = GAME_PLANE
+ layer = OBJ_LAYER
+ sealed = TRUE
+ has_frill = FALSE
can_atmos_pass = ATMOS_PASS_NO
+
+/obj/structure/spider/stickyweb/sealed/Initialize(mapload)
+ . = ..()
+ air_update_turf(TRUE, TRUE)
+
+/// Walls which reflects lasers
+/obj/structure/spider/stickyweb/sealed/reflector
+ name = "reflective silk screen"
+ desc = "Hardened webbing treated with special chemicals which cause it to repel projectiles."
+ icon = 'icons/obj/smooth_structures/webwall_reflector.dmi'
+ base_icon_state = "webwall_reflector"
+ icon_state = "webwall_reflector-0"
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL_MIRROR
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL_MIRROR
+ max_integrity = 30
+ opacity = TRUE
+ flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD
+ receive_ricochet_chance_mod = INFINITY
+
+/// Opaque and durable web 'wall'
+/obj/structure/spider/stickyweb/sealed/tough
+ name = "hardened web"
+ desc = "Webbing hardened through a chemical process into a durable barrier."
+ icon = 'icons/obj/smooth_structures/webwall_dark.dmi'
+ base_icon_state = "webwall_dark"
+ icon_state = "webwall_dark-0"
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_WALL_TOUGH
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_WALL_TOUGH
opacity = TRUE
- density = TRUE
max_integrity = 90
layer = ABOVE_MOB_LAYER
resistance_flags = FIRE_PROOF | FREEZE_PROOF
-/obj/structure/spider/solid/Initialize(mapload)
- . = ..()
- air_update_turf(TRUE, TRUE)
-
+/// Web 'door', blocks atmos but not movement
/obj/structure/spider/passage
name = "web passage"
- icon = 'icons/effects/effects.dmi'
- desc = "A messy connection of webs blocking the other side, but not solid enough to prevent passage."
- icon_state = "webpassage"
+ desc = "An opaque curtain of web which seals in air but doesn't impede passage."
+ icon = 'icons/obj/smooth_structures/stickyweb_rotated.dmi'
+ base_icon_state = "stickyweb_rotated"
+ icon_state = "stickyweb_rotated-0"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB_ROOF
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB_ROOF + SMOOTH_GROUP_WALLS
can_atmos_pass = ATMOS_PASS_NO
opacity = TRUE
max_integrity = 60
@@ -135,7 +211,10 @@
/obj/structure/spider/passage/Initialize(mapload)
. = ..()
+ pixel_x = -9
+ pixel_y = -9
air_update_turf(TRUE, TRUE)
+ add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ffffffff", "alpha" = 0.8, "size" = 0.1))
/obj/structure/spider/cocoon
name = "cocoon"
@@ -165,56 +244,31 @@
A.forceMove(T)
return ..()
-/obj/structure/spider/sticky
- name = "sticky web"
- icon = 'icons/effects/effects.dmi'
- desc = "Extremely soft and sticky silk."
- icon_state = "verystickyweb"
- max_integrity = 20
-
-/obj/structure/spider/sticky/CanAllowThrough(atom/movable/mover, border_dir)
- . = ..()
- if(HAS_TRAIT(mover, TRAIT_WEB_SURFER))
- return TRUE
- if(!isliving(mover))
- return
- if(!isnull(mover.pulledby) && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER))
- return TRUE
- loc.balloon_alert(mover, "stuck in web!")
- return FALSE
-
+/// Web caltrops
/obj/structure/spider/spikes
name = "web spikes"
- icon = 'icons/effects/effects.dmi'
desc = "Silk hardened into small yet deadly spikes."
- icon_state = "webspikes1"
+ plane = FLOOR_PLANE
+ layer = MID_TURF_LAYER
+ icon = 'icons/obj/smooth_structures/stickyweb_spikes.dmi'
+ base_icon_state = "stickyweb_spikes"
+ icon_state = "stickyweb_spikes-0"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SPIDER_WEB
+ canSmoothWith = SMOOTH_GROUP_SPIDER_WEB + SMOOTH_GROUP_WALLS
max_integrity = 40
/obj/structure/spider/spikes/Initialize(mapload)
. = ..()
+ pixel_x = -9
+ pixel_y = -9
+ add_filter(SPIDER_WEB_TINT, 10, list("type" = "outline", "color" = "#ac0000ff", "size" = 0.1))
AddComponent(/datum/component/caltrop, min_damage = 20, max_damage = 30, flags = CALTROP_NOSTUN | CALTROP_BYPASS_SHOES)
-/obj/structure/spider/reflector
- name = "Reflective silk screen"
- icon = 'icons/effects/effects.dmi'
- desc = "Made up of an extremly reflective silk material looking at it hurts."
- icon_state = "reflector"
- max_integrity = 30
- density = TRUE
- opacity = TRUE
- anchored = TRUE
- flags_ricochet = RICOCHET_SHINY | RICOCHET_HARD
- receive_ricochet_chance_mod = INFINITY
-
-/obj/structure/spider/reflector/Initialize(mapload)
- . = ..()
- air_update_turf(TRUE, TRUE)
-
/obj/structure/spider/effigy
name = "web effigy"
- icon = 'icons/effects/effects.dmi'
desc = "A giant spider! Fortunately, this one is just a statue of hardened webbing."
- icon_state = "webcarcass"
+ icon_state = "effigy"
max_integrity = 125
density = TRUE
anchored = FALSE
@@ -222,3 +276,5 @@
/obj/structure/spider/effigy/Initialize(mapload)
. = ..()
AddElement(/datum/element/temporary_atom, 1 MINUTES)
+
+#undef SPIDER_WEB_TINT
diff --git a/code/game/objects/effects/wanted_poster.dm b/code/game/objects/effects/wanted_poster.dm
index 6859a185d6eaa3d..6cb36838fbcacae 100644
--- a/code/game/objects/effects/wanted_poster.dm
+++ b/code/game/objects/effects/wanted_poster.dm
@@ -93,9 +93,10 @@
poster_icon.Blend(letter_icon, ICON_OVERLAY)
startX = startX + 4
-/obj/structure/sign/poster/wanted/roll_and_drop(atom/location)
+/obj/structure/sign/poster/wanted/roll_and_drop(atom/location, mob/user)
pixel_x = 0
pixel_y = 0
var/obj/item/poster/rolled_poster = new poster_item_type(location, original_icon, wanted_name, desc, posterHeaderText, posterHeaderColor)
- forceMove(rolled_poster)
+ if(!user?.put_in_hands(rolled_poster))
+ forceMove(rolled_poster)
return rolled_poster
diff --git a/code/game/objects/items/busts_and_figurines.dm b/code/game/objects/items/busts_and_figurines.dm
new file mode 100644
index 000000000000000..afc4a58334e9050
--- /dev/null
+++ b/code/game/objects/items/busts_and_figurines.dm
@@ -0,0 +1,139 @@
+/obj/item/statuebust
+ name = "bust"
+ desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it
+ icon = 'icons/obj/art/statue.dmi'
+ icon_state = "bust"
+ force = 15
+ throwforce = 10
+ throw_speed = 5
+ throw_range = 2
+ attack_verb_continuous = list("busts")
+ attack_verb_simple = list("bust")
+ var/impressiveness = 45
+
+/obj/item/statuebust/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/art, impressiveness)
+ AddElement(/datum/element/beauty, 1000)
+
+/obj/item/statuebust/hippocratic
+ name = "hippocrates bust"
+ desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine."
+ icon_state = "hippocratic"
+ impressiveness = 50
+ // If it hits the prob(reference_chance) chance, this is set to TRUE. Adds medical HUD when wielded, but has a 10% slower attack speed and is too bloody to make an oath with.
+ var/reference = FALSE
+ // Chance for above.
+ var/reference_chance = 1
+ // Minimum time inbetween oaths.
+ COOLDOWN_DECLARE(oath_cd)
+
+/obj/item/statuebust/hippocratic/evil
+ reference_chance = 100
+
+/obj/item/statuebust/hippocratic/Initialize(mapload)
+ . = ..()
+ if(prob(reference_chance))
+ name = "Solemn Vow"
+ desc = "Art lovers will cherish the bust of Hippocrates, commemorating a time when medics still thought doing no harm was a good idea."
+ attack_speed = CLICK_CD_SLOW
+ reference = TRUE
+
+/obj/item/statuebust/hippocratic/examine(mob/user)
+ . = ..()
+ if(reference)
+ . += span_notice("You could activate the bust in-hand to swear or forswear a Hippocratic Oath... but it seems like somebody decided it was more of a Hippocratic Suggestion. This thing is caked with bits of blood and gore.")
+ return
+ . += span_notice("You can activate the bust in-hand to swear or forswear a Hippocratic Oath! This has no effects except pacifism or bragging rights. Does not remove other sources of pacifism. Do not eat.")
+
+/obj/item/statuebust/hippocratic/equipped(mob/living/carbon/human/user, slot)
+ ..()
+ if(!(slot & ITEM_SLOT_HANDS))
+ return
+ var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
+ our_hud.show_to(user)
+ ADD_TRAIT(user, TRAIT_MEDICAL_HUD, type)
+
+/obj/item/statuebust/hippocratic/dropped(mob/living/carbon/human/user)
+ ..()
+ if(HAS_TRAIT_NOT_FROM(user, TRAIT_MEDICAL_HUD, type))
+ return
+ var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
+ our_hud.hide_from(user)
+ REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, type)
+
+/obj/item/statuebust/hippocratic/attack_self(mob/user)
+ if(!iscarbon(user))
+ to_chat(user, span_warning("You remember how the Hippocratic Oath specifies 'my fellow human beings' and realize that it's completely meaningless to you."))
+ return
+
+ if(reference)
+ to_chat(user, span_warning("As you prepare yourself to swear the Oath, you realize that doing so on a blood-caked bust is probably not a good idea."))
+ return
+
+ if(!COOLDOWN_FINISHED(src, oath_cd))
+ to_chat(user, span_warning("You've sworn or forsworn an oath too recently to undo your decisions. The bust looks at you with disgust."))
+ return
+
+ COOLDOWN_START(src, oath_cd, 5 MINUTES)
+
+ if(HAS_TRAIT_FROM(user, TRAIT_PACIFISM, type))
+ to_chat(user, span_warning("You've already sworn a vow. You start preparing to rescind it..."))
+ if(do_after(user, 5 SECONDS, target = user))
+ user.say("Yeah this Hippopotamus thing isn't working out. I quit!", forced = "hippocratic hippocrisy")
+ REMOVE_TRAIT(user, TRAIT_PACIFISM, type)
+
+ // they can still do it for rp purposes
+ if(HAS_TRAIT_NOT_FROM(user, TRAIT_PACIFISM, type))
+ to_chat(user, span_warning("You already don't want to harm people, this isn't going to do anything!"))
+
+
+ to_chat(user, span_notice("You remind yourself of the Hippocratic Oath's contents and prepare to swear yourself to it..."))
+ if(do_after(user, 4 SECONDS, target = user))
+ user.say("I swear to fulfill, to the best of my ability and judgment, this covenant:", forced = "hippocratic oath")
+ else
+ return fuck_it_up(user)
+ if(do_after(user, 2 SECONDS, target = user))
+ user.say("I will apply, for the benefit of the sick, all measures that are required, avoiding those twin traps of overtreatment and therapeutic nihilism.", forced = "hippocratic oath")
+ else
+ return fuck_it_up(user)
+ if(do_after(user, 3 SECONDS, target = user))
+ user.say("I will remember that I remain a member of society, with special obligations to all my fellow human beings, those sound of mind and body as well as the infirm.", forced = "hippocratic oath")
+ else
+
+ return fuck_it_up(user)
+ if(do_after(user, 3 SECONDS, target = user))
+ user.say("If I do not violate this oath, may I enjoy life and art, respected while I live and remembered with affection thereafter. May I always act so as to preserve the finest traditions of my calling and may I long experience the joy of healing those who seek my help.", forced = "hippocratic oath")
+ else
+ return fuck_it_up(user)
+
+ to_chat(user, span_notice("Contentment, understanding, and purpose washes over you as you finish the oath. You consider for a second the concept of harm and shudder."))
+ ADD_TRAIT(user, TRAIT_PACIFISM, type)
+
+// Bully the guy for fucking up.
+/obj/item/statuebust/hippocratic/proc/fuck_it_up(mob/living/carbon/user)
+ to_chat(user, span_warning("You forget what comes next like a dumbass. The Hippocrates bust looks down on you, disappointed."))
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
+ COOLDOWN_RESET(src, oath_cd)
+
+/obj/item/maneki_neko
+ name = "Maneki-Neko"
+ desc = "A figurine of a cat holding a coin, said to bring fortune and wealth, and perpetually moving its paw in a beckoning gesture."
+ icon = 'icons/obj/fluff/general.dmi'
+ icon_state = "maneki-neko"
+ w_class = WEIGHT_CLASS_SMALL
+ force = 5
+ throwforce = 5
+ throw_speed = 3
+ throw_range = 5
+ attack_verb_continuous = list("bashes", "beckons", "hit")
+ attack_verb_simple = list("bash", "beckon", "hit")
+
+/obj/item/maneki_neko/Initialize(mapload)
+ . = ..()
+ //Not compatible with greyscale configs because it's animated.
+ color = pick_weight(list(COLOR_WHITE = 3, COLOR_GOLD = 2, COLOR_DARK = 1))
+ var/mutable_appearance/neko_overlay = mutable_appearance(icon, "maneki-neko-overlay", appearance_flags = RESET_COLOR)
+ add_overlay(neko_overlay)
+ AddElement(/datum/element/art, GOOD_ART)
+ AddElement(/datum/element/beauty, 800)
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index f655842e7caeb6f..d8b30eaa070eec5 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -125,9 +125,8 @@
/obj/item/card/id/Initialize(mapload)
. = ..()
- var/datum/bank_account/blank_bank_account = new /datum/bank_account("Unassigned", player_account = FALSE)
+ var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.GetJobType(/datum/job/unassigned), player_account = FALSE)
registered_account = blank_bank_account
- blank_bank_account.account_job = new /datum/job/unassigned
registered_account.replaceable = TRUE
// Applying the trim updates the label and icon, so don't do this twice.
@@ -446,7 +445,7 @@
context[SCREENTIP_CONTEXT_RMB] = "Project pay stand"
if(isnull(registered_account) || registered_account.replaceable) //Same check we use when we check if we can assign an account
context[SCREENTIP_CONTEXT_ALT_RMB] = "Assign account"
- if(!registered_account.replaceable || registered_account.account_balance > 0)
+ else if(registered_account.account_balance > 0)
context[SCREENTIP_CONTEXT_ALT_LMB] = "Withdraw credits"
return CONTEXTUAL_SCREENTIP_SET
@@ -1234,7 +1233,7 @@
/obj/item/card/id/advanced/debug/Initialize(mapload)
. = ..()
registered_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
- registered_account.account_job = new /datum/job/admin // so we can actually use this account without being filtered as a "departmental" card
+ registered_account.account_job = SSjob.GetJobType(/datum/job/admin) // so we can actually use this account without being filtered as a "departmental" card
/obj/item/card/id/advanced/prisoner
name = "prisoner ID card"
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index a46732f171a518b..fef95b9b8226a44 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -744,7 +744,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
custom_price = PAYCHECK_CREW * 1.1
light_system = OVERLAY_LIGHT
light_range = 2
- light_power = 0.6
+ light_power = 1.3
light_color = LIGHT_COLOR_FIRE
light_on = FALSE
/// Whether the lighter is lit.
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 6ea3b516069f8e3..907bdbc9e22eefc 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -20,6 +20,7 @@
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT * 0.5, /datum/material/glass= SMALL_MATERIAL_AMOUNT * 0.2)
actions_types = list(/datum/action/item_action/toggle_light)
light_system = OVERLAY_LIGHT_DIRECTIONAL
+ light_color = COLOR_LIGHT_ORANGE
light_range = 4
light_power = 1
light_on = FALSE
@@ -95,7 +96,7 @@
return light_on != old_light_on // If the value of light_on didn't change, return false. Otherwise true.
/obj/item/flashlight/attack_self(mob/user)
- toggle_light(user)
+ return toggle_light(user)
/obj/item/flashlight/attack_hand_secondary(mob/user, list/modifiers)
attack_self(user)
@@ -295,6 +296,8 @@
w_class = WEIGHT_CLASS_TINY
obj_flags = CONDUCTS_ELECTRICITY
light_range = 2
+ light_power = 0.8
+ light_color = "#CCFFFF"
COOLDOWN_DECLARE(holosign_cooldown)
/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag)
@@ -352,6 +355,8 @@
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
force = 9 // Not as good as a stun baton.
light_range = 5 // A little better than the standard flashlight.
+ light_power = 0.8
+ light_color = "#99ccff"
hitsound = 'sound/weapons/genhit1.ogg'
// the desk lamps are a bit special
@@ -398,6 +403,7 @@
heat = 1000
light_color = LIGHT_COLOR_FLARE
light_system = OVERLAY_LIGHT
+ light_power = 2
grind_results = list(/datum/reagent/sulfur = 15)
sound_on = 'sound/items/match_strike.ogg'
toggle_context = FALSE
@@ -416,7 +422,7 @@
/obj/item/flashlight/flare/Initialize(mapload)
. = ..()
if(randomize_fuel)
- fuel = rand(25 MINUTES, 35 MINUTES)
+ fuel = rand(10 MINUTES, 15 MINUTES)
if(light_on)
attack_verb_continuous = string_list(list("burns", "singes"))
attack_verb_simple = string_list(list("burn", "singe"))
@@ -523,8 +529,9 @@
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
heat = 1000
- light_color = LIGHT_COLOR_FIRE
light_range = 2
+ light_power = 1.5
+ light_color = LIGHT_COLOR_FIRE
fuel = 35 MINUTES
randomize_fuel = FALSE
trash_type = /obj/item/trash/candle
@@ -638,6 +645,7 @@
name = "torch"
desc = "A torch fashioned from some leaves and a log."
light_range = 4
+ light_power = 1.3
icon_state = "torch"
inhand_icon_state = "torch"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
@@ -655,20 +663,24 @@
lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
desc = "A mining lantern."
- light_range = 6 // luminosity when on
+ light_range = 5 // luminosity when on
+ light_power = 1.5
+ light_color = "#ffcc66"
light_system = OVERLAY_LIGHT
/obj/item/flashlight/lantern/heirloom_moth
name = "old lantern"
desc = "An old lantern that has seen plenty of use."
- light_range = 4
+ light_range = 3.5
/obj/item/flashlight/lantern/syndicate
name = "suspicious lantern"
desc = "A suspicious looking lantern."
icon_state = "syndilantern"
inhand_icon_state = "syndilantern"
- light_range = 10
+ light_range = 6
+ light_power = 2
+ light_color = "#ffffe6"
/obj/item/flashlight/lantern/jade
name = "jade lantern"
@@ -686,7 +698,8 @@
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
custom_materials = null
- light_range = 7 //luminosity when on
+ light_range = 6 //luminosity when on
+ light_color = "#ffff66"
light_system = OVERLAY_LIGHT
/obj/item/flashlight/emp
@@ -746,13 +759,14 @@
emp_cur_charges = 100
// Glowsticks, in the uncomfortable range of similar to flares,
-// but not similar enough to make it worth a refactor
+// Flares need to process (for hotspots) tho so this becomes irrelevant
/obj/item/flashlight/glowstick
name = "glowstick"
desc = "A military-grade glowstick."
custom_price = PAYCHECK_LOWER
w_class = WEIGHT_CLASS_SMALL
- light_range = 4
+ light_range = 3.5
+ light_power = 2
light_system = OVERLAY_LIGHT
color = LIGHT_COLOR_GREEN
icon_state = "glowstick"
@@ -764,35 +778,74 @@
toggle_context = FALSE
/// How many seconds of fuel we have left
var/fuel = 0
+ /// How much max fuel we have
+ var/max_fuel = 0
+ /// The timer id powering our burning
+ var/timer_id = TIMER_ID_NULL
/obj/item/flashlight/glowstick/Initialize(mapload)
- fuel = rand(50 MINUTES, 60 MINUTES)
+ fuel = rand(20 MINUTES, 25 MINUTES)
+ max_fuel = fuel
set_light_color(color)
return ..()
-/obj/item/flashlight/glowstick/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/item/flashlight/glowstick/process(seconds_per_tick)
- fuel = max(fuel - seconds_per_tick * (1 SECONDS), 0)
- if(fuel <= 0)
+/// Burns down the glowstick by the specified time
+/// Returns the amount of time we need to burn before a visual change will occur
+/obj/item/flashlight/glowstick/proc/burn_down(amount = 0)
+ fuel -= amount
+ var/fuel_target = 0
+ if(fuel >= max_fuel)
+ fuel_target = max_fuel * 0.4
+ else if(fuel >= max_fuel * 0.4)
+ fuel_target = max_fuel * 0.3
+ set_light_range(3)
+ set_light_power(1.5)
+ else if(fuel >= max_fuel * 0.3)
+ fuel_target = max_fuel * 0.2
+ set_light_range(2)
+ set_light_power(1.25)
+ else if(fuel >= max_fuel * 0.2)
+ fuel_target = max_fuel * 0.1
+ set_light_power(1)
+ else if(fuel >= max_fuel * 0.1)
+ fuel_target = 0
+ set_light_range(1.5)
+ set_light_power(0.5)
+
+ var/time_to_burn = round(fuel - fuel_target)
+ // Less then a ds? go home
+ if(time_to_burn <= 0)
turn_off()
- STOP_PROCESSING(SSobj, src)
+
+ return time_to_burn
+
+/obj/item/flashlight/glowstick/proc/burn_loop(amount = 0)
+ timer_id = TIMER_ID_NULL
+ var/burn_next = burn_down(amount)
+ if(burn_next <= 0)
+ return
+ timer_id = addtimer(CALLBACK(src, PROC_REF(burn_loop), burn_next), burn_next, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_OVERRIDE)
+
+/obj/item/flashlight/glowstick/proc/turn_on()
+ set_light_on(TRUE) // Just in case
+ var/datum/action/toggle = locate(/datum/action/item_action/toggle_light) in actions
+ // No sense having a toggle light action that we don't use eh?
+ if(toggle)
+ remove_item_action(toggle)
+ burn_loop()
/obj/item/flashlight/glowstick/proc/turn_off()
+ var/datum/action/toggle = locate(/datum/action/item_action/toggle_light) in actions
+ if(fuel && !toggle)
+ add_item_action(/datum/action/item_action/toggle_light)
+ if(timer_id != TIMER_ID_NULL)
+ var/expected_burn_time = burn_down(0) // This is dumb I'm sorry
+ burn_down(expected_burn_time - timeleft(timer_id))
+ deltimer(timer_id)
+ timer_id = TIMER_ID_NULL
set_light_on(FALSE)
update_appearance(UPDATE_ICON)
-/obj/item/flashlight/glowstick/update_appearance(updates=ALL)
- . = ..()
- if(fuel <= 0)
- set_light_on(FALSE)
- return
- if(light_on)
- set_light_on(TRUE)
- return
-
/obj/item/flashlight/glowstick/update_icon_state()
. = ..()
icon_state = "[base_icon_state][(fuel <= 0) ? "-empty" : ""]"
@@ -807,6 +860,13 @@
glowstick_overlay.color = color
. += glowstick_overlay
+/obj/item/flashlight/glowstick/toggle_light(mob/user)
+ if(fuel <= 0)
+ return FALSE
+ if(light_on)
+ return FALSE
+ return ..()
+
/obj/item/flashlight/glowstick/attack_self(mob/user)
if(fuel <= 0)
balloon_alert(user, "glowstick is spent!")
@@ -818,7 +878,7 @@
. = ..()
if(.)
user.visible_message(span_notice("[user] cracks and shakes [src]."), span_notice("You crack and shake [src], turning it on!"))
- START_PROCESSING(SSobj, src)
+ turn_on()
/obj/item/flashlight/glowstick/suicide_act(mob/living/carbon/human/user)
if(!fuel)
@@ -829,7 +889,7 @@
user.visible_message(span_suicide("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!"))
return SHAME
user.visible_message(span_suicide("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!"))
- fuel = 0
+ burn_loop(fuel)
return FIRELOSS
/obj/item/flashlight/glowstick/red
@@ -862,7 +922,7 @@
icon_state = null
light_system = OVERLAY_LIGHT
light_range = 4
- light_power = 10
+ light_power = 2
alpha = 0
plane = FLOOR_PLANE
anchored = TRUE
@@ -907,9 +967,6 @@
/obj/item/flashlight/eyelight
name = "eyelight"
desc = "This shouldn't exist outside of someone's head, how are you seeing this?"
- light_system = OVERLAY_LIGHT
- light_range = 15
- light_power = 1
obj_flags = CONDUCTS_ELECTRICITY
item_flags = DROPDEL
actions_types = list()
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index 08bf47dcc073ee1..4d6b8edbd2dd20b 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -250,6 +250,14 @@ GLOBAL_LIST_INIT(channel_tokens, list(
worn_icon_state = "com_headset"
keyslot = /obj/item/encryptionkey/heads/hos
+/obj/item/radio/headset/heads/hos/advisor
+ name = "\proper the veteran security advisor headset"
+ desc = "The headset of the man who was in charge of keeping order and protecting the station..."
+ icon_state = "com_headset"
+ worn_icon_state = "com_headset"
+ keyslot = /obj/item/encryptionkey/heads/hos
+ command = FALSE
+
/obj/item/radio/headset/heads/hos/alt
name = "\proper the head of security's bowman headset"
desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs."
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 85a027935fd6404..72e50014ca2799a 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -120,6 +120,8 @@
slapcraft_recipes = list(/datum/crafting_recipe/improv_explosive)\
)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
+
/obj/item/radio/Destroy()
remove_radio_all(src) //Just to be sure
QDEL_NULL(wires)
@@ -127,6 +129,12 @@
QDEL_NULL(keyslot)
return ..()
+/obj/item/radio/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(broadcasting) //no broadcasting but it can still be used to send radio messages.
+ set_broadcasting(FALSE)
+ return COMSIG_SABOTEUR_SUCCESS
+
/obj/item/radio/proc/set_frequency(new_frequency)
SEND_SIGNAL(src, COMSIG_RADIO_NEW_FREQUENCY, args)
remove_radio(src, frequency)
diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm
index 8c90569bb91d0cc..7f672e7d32a53fc 100644
--- a/code/game/objects/items/devices/scanners/health_analyzer.dm
+++ b/code/game/objects/items/devices/scanners/health_analyzer.dm
@@ -33,7 +33,8 @@
/obj/item/healthanalyzer/examine(mob/user)
. = ..()
- . += span_notice("Alt-click [src] to toggle the limb damage readout.")
+ if(src.mode != SCANNER_NO_MODE)
+ . += span_notice("Alt-click [src] to toggle the limb damage readout.")
/obj/item/healthanalyzer/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] begins to analyze [user.p_them()]self with [src]! The display shows that [user.p_theyre()] dead!"))
diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm
index b571688f1a877e1..5a6dc49b09b6eb9 100644
--- a/code/game/objects/items/extinguisher.dm
+++ b/code/game/objects/items/extinguisher.dm
@@ -279,13 +279,8 @@
/obj/item/extinguisher/proc/EmptyExtinguisher(mob/user)
if(loc == user && reagents.total_volume)
+ reagents.expose(user.loc, TOUCH)
reagents.clear_reagents()
-
- var/turf/T = get_turf(loc)
- if(isopenturf(T))
- var/turf/open/theturf = T
- theturf.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
-
user.visible_message(span_notice("[user] empties out \the [src] onto the floor using the release valve."), span_info("You quietly empty out \the [src] using its release valve."))
//firebot assembly
@@ -297,3 +292,10 @@
user.put_in_hands(new /obj/item/bot_assembly/firebot)
else
..()
+
+/obj/item/extinguisher/anti
+ name = "fire extender"
+ desc = "A traditional red fire extinguisher. Made in Britain... wait, what?"
+ chem = /datum/reagent/fuel
+ tanktype = /obj/structure/reagent_dispensers/fueltank
+ cooling_power = 0
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index 1a31c5d58a219d7..1564e4e40298add 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -16,6 +16,9 @@
resistance_flags = FIRE_PROOF
trigger_guard = TRIGGER_GUARD_NORMAL
light_system = OVERLAY_LIGHT
+ light_color = LIGHT_COLOR_FLARE
+ light_range = 2
+ light_power = 2
light_on = FALSE
var/status = FALSE
var/lit = FALSE //on or off
@@ -82,12 +85,13 @@
return // too close
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, span_warning("You can't bring yourself to fire \the [src]! You don't want to risk harming anyone..."))
+ log_combat(user, target, "attempted to flamethrower", src, "with gas mixture: {[print_gas_mixture(ptank.return_analyzable_air())]}, flamethrower: \"[name]\" ([src]), igniter: \"[igniter.name]\", tank: \"[ptank.name]\" and tank distribution pressure: \"[siunit(1000 * ptank.distribute_pressure, unit = "Pa", maxdecimals = INFINITY)]\"" + lit ? " while lit" : "" + " but failed due to pacifism.")
return
if(user && user.get_active_held_item() == src) // Make sure our user is still holding us
var/turf/target_turf = get_turf(target)
if(target_turf)
var/turflist = get_line(user, target_turf)
- log_combat(user, target, "flamethrowered", src)
+ log_combat(user, target, "flamethrowered", src, "with gas mixture: {[print_gas_mixture(ptank.return_analyzable_air())]}, flamethrower: \"[name]\", igniter: \"[igniter.name]\", tank: \"[ptank.name]\" and tank distribution pressure: \"[siunit(1000 * ptank.distribute_pressure, unit = "Pa", maxdecimals = INFINITY)]\"" + lit ? " while lit." : ".")
flame_turf(turflist)
/obj/item/flamethrower/wrench_act(mob/living/user, obj/item/tool)
diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm
index ba1845bb40da4a1..0f95aac6d852855 100644
--- a/code/game/objects/items/food/bread.dm
+++ b/code/game/objects/items/food/bread.dm
@@ -481,6 +481,7 @@
foodtypes = GRAIN | DAIRY
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_2
+ custom_price = PAYCHECK_CREW
/obj/item/food/butterdog/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/items/food/frozen.dm b/code/game/objects/items/food/frozen.dm
index 0bd0cd3a60620d8..27052507bfd4a88 100644
--- a/code/game/objects/items/food/frozen.dm
+++ b/code/game/objects/items/food/frozen.dm
@@ -12,6 +12,7 @@
foodtypes = GRAIN | DAIRY | SUGAR
food_flags = FOOD_FINGER_FOOD
crafting_complexity = FOOD_COMPLEXITY_2
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/strawberryicecreamsandwich
name = "strawberry ice cream sandwich"
@@ -27,7 +28,7 @@
foodtypes = FRUIT | DAIRY | SUGAR
food_flags = FOOD_FINGER_FOOD
crafting_complexity = FOOD_COMPLEXITY_3
-
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/spacefreezy
name = "space freezy"
@@ -43,6 +44,7 @@
tastes = list("blue cherries" = 2, "ice cream" = 2)
foodtypes = FRUIT | DAIRY | SUGAR
crafting_complexity = FOOD_COMPLEXITY_3
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/spacefreezy/make_edible()
. = ..()
@@ -62,6 +64,7 @@
tastes = list("ice cream" = 1, "banana" = 1)
foodtypes = FRUIT | DAIRY | SUGAR
crafting_complexity = FOOD_COMPLEXITY_3
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/sundae/make_edible()
. = ..()
@@ -81,6 +84,7 @@
tastes = list("ice cream" = 1, "banana" = 1, "a bad joke" = 1)
foodtypes = FRUIT | DAIRY | SUGAR
crafting_complexity = FOOD_COMPLEXITY_4
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/honkdae/make_edible()
. = ..()
@@ -104,6 +108,7 @@
foodtypes = SUGAR //We use SUGAR as a base line to act in as junkfood, other wise we use fruit
food_flags = FOOD_FINGER_FOOD
crafting_complexity = FOOD_COMPLEXITY_2
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/snowcones/lime
name = "lime snowcone"
@@ -330,6 +335,7 @@
foodtypes = DAIRY | SUGAR
food_flags = FOOD_FINGER_FOOD
crafting_complexity = FOOD_COMPLEXITY_3
+ crafted_food_buff = /datum/status_effect/food/chilling
var/overlay_state = "creamsicle_o" //This is the edible part of the popsicle.
var/bite_states = 4 //This value value is used for correctly setting the bite_consumption to ensure every bite changes the sprite. Do not set to zero.
@@ -434,6 +440,7 @@
foodtypes = DAIRY | SUGAR
venue_value = FOOD_PRICE_NORMAL
crafting_complexity = FOOD_COMPLEXITY_3
+ crafted_food_buff = /datum/status_effect/food/chilling
/obj/item/food/popsicle/meatsicle
name = "Meatsicle"
diff --git a/code/game/objects/items/food/lizard.dm b/code/game/objects/items/food/lizard.dm
index 729ad4d38a97154..47b5ff751091685 100644
--- a/code/game/objects/items/food/lizard.dm
+++ b/code/game/objects/items/food/lizard.dm
@@ -34,6 +34,7 @@
foodtypes = MEAT
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_2
+ custom_price = PAYCHECK_CREW
/obj/item/food/raw_headcheese
name = "raw headcheese block"
diff --git a/code/game/objects/items/food/martian.dm b/code/game/objects/items/food/martian.dm
index 2441ac0f67478c6..7ceaf1878176c6b 100644
--- a/code/game/objects/items/food/martian.dm
+++ b/code/game/objects/items/food/martian.dm
@@ -732,6 +732,7 @@
foodtypes = MEAT | VEGETABLES | FRUIT | PINEAPPLE
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_4
+ custom_price = PAYCHECK_CREW * 1.2
/obj/item/food/salt_chilli_fries
name = "salt n' chilli fries"
@@ -1210,6 +1211,7 @@
foodtypes = FRUIT | MEAT | PINEAPPLE | VEGETABLES | GRAIN
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_4 //Uses Sambal
+ custom_price = PAYCHECK_CREW * 2
/obj/item/food/frickles
name = "frickles"
diff --git a/code/game/objects/items/food/meatdish.dm b/code/game/objects/items/food/meatdish.dm
index b9a6c34df04edd1..537c7688d2dd4ac 100644
--- a/code/game/objects/items/food/meatdish.dm
+++ b/code/game/objects/items/food/meatdish.dm
@@ -554,6 +554,7 @@
w_class = WEIGHT_CLASS_SMALL
venue_value = FOOD_PRICE_CHEAP
crafting_complexity = FOOD_COMPLEXITY_2
+ custom_price = PAYCHECK_CREW * 0.6
/obj/item/food/sausage/make_processable()
AddElement(/datum/element/processable, TOOL_KNIFE, /obj/item/food/salami, 6, 3 SECONDS, table_required = TRUE, screentip_verb = "Slice")
@@ -734,6 +735,7 @@
foodtypes = MEAT | DAIRY | GRAIN
w_class = WEIGHT_CLASS_TINY
crafting_complexity = FOOD_COMPLEXITY_3
+ custom_price = PAYCHECK_CREW
/obj/item/food/bbqribs
name = "bbq ribs"
@@ -750,19 +752,6 @@
foodtypes = MEAT | SUGAR
crafting_complexity = FOOD_COMPLEXITY_2
-///Special private component to handle how bbq is grilled, not meant to be used anywhere else
-/datum/component/grillable/bbq
-
-/datum/component/grillable/bbq/finish_grilling(atom/grill_source)
- //when on a grill allow it to roast without deleting itself
- if(istype(grill_source, /obj/machinery/grill))
- grill_source.visible_message(span_notice("[parent] is grilled to perfection!"))
- else //when on a girddle allow it to burn into an mouldy mess
- return ..()
-
-/obj/item/food/bbqribs/make_grillable()
- AddComponent(/datum/component/grillable/bbq, /obj/item/food/badrecipe, rand(30 SECONDS, 40 SECONDS), FALSE)
-
/obj/item/food/meatclown
name = "meat clown"
desc = "A delicious, round piece of meat clown. How horrifying."
diff --git a/code/game/objects/items/food/pastries.dm b/code/game/objects/items/food/pastries.dm
index 277e6efaff8950b..e1449007b71d482 100644
--- a/code/game/objects/items/food/pastries.dm
+++ b/code/game/objects/items/food/pastries.dm
@@ -77,6 +77,10 @@
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_2
+/obj/item/food/waffles/make_edible()
+ . = ..()
+ AddComponent(/datum/component/ice_cream_holder, max_scoops = 1, x_offset = -2)
+
/obj/item/food/soylentgreen
name = "\improper Soylent Green"
desc = "Not made of people. Honest." //Totally people.
@@ -123,6 +127,10 @@
w_class = WEIGHT_CLASS_SMALL
crafting_complexity = FOOD_COMPLEXITY_3
+/obj/item/food/rofflewaffles/make_edible()
+ . = ..()
+ AddComponent(/datum/component/ice_cream_holder, max_scoops = 1, x_offset = -2)
+
////////////////////////////////////////////OTHER////////////////////////////////////////////
/obj/item/food/cookie
@@ -356,7 +364,7 @@
bite_consumption = 4
foodtypes = DAIRY | SUGAR
food_flags = FOOD_FINGER_FOOD
- crafting_complexity = FOOD_COMPLEXITY_3
+ crafting_complexity = FOOD_COMPLEXITY_2
max_volume = 10 //The max volumes scales up with the number of scoops of ice cream served.
/// These two variables are used by the ice cream vat. Latter is the one that shows on the UI.
var/list/ingredients = list(
@@ -371,14 +379,10 @@
*/
var/list/prefill_flavours
-/obj/item/food/icecream/New(loc, list/prefill_flavours)
+/obj/item/food/icecream/Initialize(mapload, list/prefill_flavours)
if(ingredients)
ingredients_text = "Requires: [reagent_paths_list_to_text(ingredients)]"
- return ..()
-
-/obj/item/food/icecream/Initialize(mapload, list/prefill_flavours)
- if(prefill_flavours)
- src.prefill_flavours = prefill_flavours
+ src.prefill_flavours = prefill_flavours
return ..()
/obj/item/food/icecream/make_edible()
@@ -398,7 +402,6 @@
/datum/reagent/consumable/sugar,
/datum/reagent/consumable/coco,
)
- crafting_complexity = FOOD_COMPLEXITY_3
/obj/item/food/icecream/korta
name = "korta cone"
diff --git a/code/game/objects/items/food/sandwichtoast.dm b/code/game/objects/items/food/sandwichtoast.dm
index c6488f67a1ed501..e440a1039e6d177 100644
--- a/code/game/objects/items/food/sandwichtoast.dm
+++ b/code/game/objects/items/food/sandwichtoast.dm
@@ -152,6 +152,7 @@
w_class = WEIGHT_CLASS_SMALL
venue_value = FOOD_PRICE_CHEAP
crafting_complexity = FOOD_COMPLEXITY_3
+ custom_price = PAYCHECK_CREW * 0.7
// Used for unit tests, do not delete
/obj/item/food/hotdog/debug
@@ -174,6 +175,7 @@
w_class = WEIGHT_CLASS_SMALL
venue_value = FOOD_PRICE_NORMAL
crafting_complexity = FOOD_COMPLEXITY_4
+ custom_price = PAYCHECK_CREW
/obj/item/food/sandwich/blt
name = "\improper BLT"
diff --git a/code/game/objects/items/implants/implant_explosive.dm b/code/game/objects/items/implants/implant_explosive.dm
index 25966a24c68ead9..51564799f6e44fc 100644
--- a/code/game/objects/items/implants/implant_explosive.dm
+++ b/code/game/objects/items/implants/implant_explosive.dm
@@ -19,6 +19,8 @@
var/active = FALSE
///The final countdown (delay before we explode)
var/delay = MICROBOMB_DELAY
+ ///If the delay is equal or lower to MICROBOMB_DELAY (0.7 sec), the explosion will be instantaneous.
+ var/instant_explosion = TRUE
///Radius of weak devastation explosive impact
var/explosion_light = MICROBOMB_EXPLOSION_LIGHT
///Radius of medium devastation explosive impact
@@ -33,7 +35,8 @@
var/no_paralyze = FALSE
///Do we override other explosive implants?
var/master_implant = FALSE
-
+ ///Will this implant notify ghosts when activated?
+ var/notify_ghosts = TRUE
/obj/item/implant/explosive/proc/on_death(datum/source, gibbed)
SIGNAL_HANDLER
@@ -73,7 +76,7 @@
var/turf/boomturf = get_turf(imp_in)
message_admins("[ADMIN_LOOKUPFLW(imp_in)] has activated their [name] at [ADMIN_VERBOSEJMP(boomturf)], with cause of [cause].")
//If the delay is shorter or equal to the default delay, just blow up already jeez
- if(delay <= MICROBOMB_DELAY)
+ if(delay <= MICROBOMB_DELAY && instant_explosion)
explode()
return
timed_explosion()
@@ -119,14 +122,15 @@
/obj/item/implant/explosive/proc/timed_explosion()
imp_in.visible_message(span_warning("[imp_in] starts beeping ominously!"))
- notify_ghosts(
- "[imp_in] is about to detonate their explosive implant!",
- source = src,
- header = "Tick Tick Tick...",
- notify_flags = NOTIFY_CATEGORY_NOFLASH,
- ghost_sound = 'sound/machines/warning-buzzer.ogg',
- notify_volume = 75,
- )
+ if(notify_ghosts)
+ notify_ghosts(
+ "[imp_in] is about to detonate their explosive implant!",
+ source = src,
+ header = "Tick Tick Tick...",
+ notify_flags = NOTIFY_CATEGORY_NOFLASH,
+ ghost_sound = 'sound/machines/warning-buzzer.ogg',
+ notify_volume = 75,
+ )
playsound(loc, 'sound/items/timer.ogg', 30, FALSE)
if(!panic_beep_sound)
@@ -203,6 +207,13 @@
if(source.health < source.crit_threshold)
INVOKE_ASYNC(src, PROC_REF(activate), "deniability")
+/obj/item/implant/explosive/deathmatch
+ name = "deathmatch microbomb implant"
+ delay = 0.5 SECONDS
+ actions_types = null
+ instant_explosion = FALSE
+ notify_ghosts = FALSE
+
/obj/item/implanter/explosive
name = "implanter (microbomb)"
imp_type = /obj/item/implant/explosive
diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm
index d79714c1b68d8e0..907176bc9b90824 100644
--- a/code/game/objects/items/melee/baton.dm
+++ b/code/game/objects/items/melee/baton.dm
@@ -454,6 +454,7 @@
else
cell = new preload_cell_type(src)
RegisterSignal(src, COMSIG_ATOM_ATTACKBY, PROC_REF(convert))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
update_appearance()
/obj/item/melee/baton/security/get_cell()
@@ -488,6 +489,14 @@
qdel(item)
qdel(src)
+/obj/item/melee/baton/security/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(!active)
+ return
+ toggle_light()
+ active = FALSE
+ update_appearance()
+ return COMSIG_SABOTEUR_SUCCESS
/obj/item/melee/baton/security/Exited(atom/movable/mov_content)
. = ..()
if(mov_content == cell)
diff --git a/code/game/objects/items/piggy_bank.dm b/code/game/objects/items/piggy_bank.dm
new file mode 100644
index 000000000000000..012ff91b0216c84
--- /dev/null
+++ b/code/game/objects/items/piggy_bank.dm
@@ -0,0 +1,129 @@
+/**
+ * Piggy banks. They store your hard-earned money until you or someone destroys it.
+ * If the persistence id is set, money will be carried between rounds until broken.
+ */
+/obj/item/piggy_bank
+ name = "piggy bank"
+ desc = "A pig-shaped money container made of porkelain, oink. Do not throw." //pun very intended.
+ icon = 'icons/obj/fluff/general.dmi'
+ icon_state = "piggy_bank"
+ max_integrity = 8
+ w_class = WEIGHT_CLASS_NORMAL
+ force = 12
+ throwforce = 15
+ throw_speed = 3
+ throw_range = 7
+ greyscale_config = /datum/greyscale_config/piggy_bank
+ ///Some piggy banks are persistent, meaning they carry dosh between rounds.
+ var/persistence_id
+ ///Callback to execute upon roundend to save the current amount of cash it has stored, IF persistent.
+ var/datum/callback/persistence_cb
+ ///How much dosh can this piggy bank hold.
+ var/maximum_value = PAYCHECK_COMMAND * 20
+ ///How much dosh this piggy bank spawns with.
+ var/initial_value = 0
+
+/obj/item/piggy_bank/Initialize(mapload)
+ if(!greyscale_colors)
+ greyscale_colors = pick(COLOR_PINK,
+ COLOR_LIGHT_ORANGE,
+ COLOR_GREEN_GRAY,
+ COLOR_PALE_BLUE_GRAY,
+ COLOR_DARK_MODERATE_LIME_GREEN,
+ COLOR_OFF_WHITE,
+ )
+
+ . = ..()
+
+ AddElement(/datum/element/can_shatter, shattering_sound = SFX_SHATTER, shatters_as_weapon = TRUE)
+ AddElement(/datum/element/beauty, 500)
+ if(!persistence_id)
+ if(initial_value)
+ new /obj/item/holochip(src, initial_value)
+ return
+
+ SSpersistence.load_piggy_bank(src)
+ persistence_cb = CALLBACK(src, PROC_REF(save_cash))
+ SSticker.OnRoundend(persistence_cb)
+
+ if(initial_value && initial_value + calculate_dosh_amount() <= maximum_value)
+ new /obj/item/holochip(src, initial_value)
+
+/obj/item/piggy_bank/proc/save_cash()
+ SSpersistence.save_piggy_bank(src)
+
+/obj/item/piggy_bank/Destroy()
+ if(persistence_cb)
+ LAZYREMOVE(SSticker.round_end_events, persistence_cb) //cleanup the callback.
+ persistence_cb = null
+ return ..()
+
+/obj/item/piggy_bank/deconstruct(disassembled = TRUE)
+ for(var/obj/item/thing as anything in contents)
+ thing.forceMove(loc)
+ //Smashing the piggy after the round is over doesn't count.
+ if(persistence_id && SSticker.current_state < GAME_STATE_FINISHED)
+ LAZYADD(SSpersistence.queued_broken_piggy_ids, persistence_id)
+ return ..()
+
+/obj/item/piggy_bank/attack_self(mob/user, modifiers)
+ . = ..()
+ if(DOING_INTERACTION_WITH_TARGET(user, src))
+ return
+ balloon_alert(user, "rattle rattle...")
+ if(!do_after(user, 0.5 SECONDS, src))
+ return
+ var/percentile = round(calculate_dosh_amount()/maximum_value * 100, 1)
+ if(percentile >= 10)
+ playsound(src, SFX_RATTLE, percentile * 0.5, FALSE, FALSE)
+ switch(percentile)
+ if(0)
+ balloon_alert(user, "it's empty")
+ if(1 to 9)
+ balloon_alert(user, "it's almost empty")
+ if(10 to 25)
+ balloon_alert(user, "it's some cash")
+ if(25 to 45)
+ balloon_alert(user, "it's plenty of cash")
+ if(45 to 70)
+ balloon_alert(user, "it feels almost full")
+ if(70 to 95)
+ balloon_alert(user, "it feels full")
+ if(95 to INFINITY)
+ balloon_alert(user, "brimming with cash")
+
+/obj/item/piggy_bank/attackby(obj/item/item, mob/user, params)
+ var/creds_value = item.get_item_credit_value()
+ if(isnull(creds_value))
+ return ..()
+
+ var/dosh_amount = calculate_dosh_amount()
+
+ if(dosh_amount >= maximum_value)
+ balloon_alert(user, "it's full!")
+ else if(dosh_amount + creds_value > maximum_value)
+ balloon_alert(user, "too much cash!")
+ else if(!user.transferItemToLoc(item, src))
+ balloon_alert(user, "stuck in your hands!")
+ else
+ balloon_alert(user, "inserted [creds_value] creds")
+ return TRUE
+
+///Returns the total amount of credits that its contents amount to.
+/obj/item/piggy_bank/proc/calculate_dosh_amount()
+ var/total_value = 0
+ for(var/obj/item/item in contents)
+ total_value += item.get_item_credit_value()
+ return total_value
+
+/obj/item/piggy_bank/museum
+ name = "Pigston Swinelord VI"
+ desc = "The museum's mascot piggy bank and favorite embezzler, known to carry donations between shifts without paying taxes. The space IRS hates him."
+ persistence_id = "museum_piggy"
+ greyscale_colors = COLOR_PINK
+ maximum_value = PAYCHECK_COMMAND * 100
+ initial_value = PAYCHECK_COMMAND * 4
+
+/obj/item/piggy_bank/museum/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/areabound) //do not steal.
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index ca669a9733e1221..90790a75ea20beb 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -44,7 +44,7 @@
/obj/item/toy/plush/Initialize(mapload)
. = ..()
AddComponent(/datum/component/squeak, squeak_override)
- AddElement(/datum/element/bed_tuckable, 6, -5, 90)
+ AddElement(/datum/element/bed_tuckable, mapload, 6, -5, 90)
//have we decided if Pinocchio goes in the blue or pink aisle yet?
if(gender == NEUTER)
diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm
index 7ac22d00897eac5..dca3fe172159a64 100644
--- a/code/game/objects/items/puzzle_pieces.dm
+++ b/code/game/objects/items/puzzle_pieces.dm
@@ -289,28 +289,47 @@
// literally just buttons
//
-/obj/machinery/puzzle_button
- name = "control panel"
- desc = "A panel that controls something nearby. I'm sure it being covered in hazard stripes is fine."
+/obj/machinery/puzzle
+ name = "abstract puzzle gizmo"
icon = 'icons/obj/machines/wallmounts.dmi'
- icon_state = "lockdown0"
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
- base_icon_state = "lockdown"
/// have we been pressed already?
var/used = FALSE
/// can we be pressed only once?
var/single_use = TRUE
/// puzzle id we send on press
- var/id = "0" //null would literally open every puzzle door without an id
+ var/id //null would literally open every puzzle door without an id
/// queue size, must match count of objects this activates!
var/queue_size = 2
+ /// should the puzzle machinery perform the final step of the queue link on LateInitialize? An alternative to queue size
+ var/late_initialize_pop = FALSE
-/obj/machinery/puzzle_button/Initialize(mapload)
+/obj/machinery/puzzle/Initialize(mapload)
. = ..()
if(!isnull(id))
- SSqueuelinks.add_to_queue(src, id, queue_size)
+ SSqueuelinks.add_to_queue(src, id, late_initialize_pop ? 0 : queue_size)
+ return late_initialize_pop ? INITIALIZE_HINT_LATELOAD : .
+
+/obj/machinery/puzzle/LateInitialize()
+ . = ..()
+ if(late_initialize_pop && id && SSqueuelinks.queues[id])
+ SSqueuelinks.pop_link(id)
+
+/obj/machinery/puzzle/proc/on_puzzle_complete() //incase someone wants to make this do something else for some reason
+ SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED)
+
+/obj/machinery/puzzle/update_icon_state()
+ icon_state = "[base_icon_state][used]"
+ return ..()
+
+/obj/machinery/puzzle/button
+ name = "control panel"
+ desc = "A panel that controls something nearby. I'm sure it being covered in hazard stripes is fine."
+ icon = 'icons/obj/machines/wallmounts.dmi'
+ icon_state = "lockdown0"
+ base_icon_state = "lockdown"
-/obj/machinery/puzzle_button/attack_hand(mob/user, list/modifiers)
+/obj/machinery/puzzle/button/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
@@ -320,37 +339,17 @@
update_icon_state()
visible_message(span_notice("[user] presses a button on [src]."), span_notice("You press a button on [src]."))
playsound(src, 'sound/machines/terminal_button07.ogg', 45, TRUE)
- open_doors()
+ on_puzzle_complete()
-/obj/machinery/puzzle_button/proc/open_doors() //incase someone wants to make this do something else for some reason
- SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED)
-
-/obj/machinery/puzzle_button/update_icon_state()
- icon_state = "[base_icon_state][used]"
- return ..()
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/button, 32)
-MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_button, 32)
-
-/obj/machinery/puzzle_keycardpad
+/obj/machinery/puzzle/keycardpad
name = "keycard panel"
desc = "A panel that controls something nearby. Accepts keycards."
- icon = 'icons/obj/machines/wallmounts.dmi'
icon_state = "keycardpad0"
- resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
base_icon_state = "keycardpad"
- /// were we used successfully?
- var/used = FALSE
- /// puzzle id we send if the correct card is swiped
- var/id = "0"
- /// queue size, must match count of objects this activates!
- var/queue_size = 2
-
-/obj/machinery/puzzle_keycardpad/Initialize(mapload)
- . = ..()
- if(!isnull(id))
- SSqueuelinks.add_to_queue(src, id, queue_size)
-/obj/machinery/puzzle_keycardpad/attackby(obj/item/attacking_item, mob/user, params)
+/obj/machinery/puzzle/keycardpad/attackby(obj/item/attacking_item, mob/user, params)
. = ..()
if(!istype(attacking_item, /obj/item/keycard) || used)
return
@@ -363,13 +362,75 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_button, 32)
used = TRUE
update_icon_state()
playsound(src, 'sound/machines/beep.ogg', 45, TRUE)
- SEND_SIGNAL(src, COMSIG_PUZZLE_COMPLETED)
+ on_puzzle_complete()
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/keycardpad, 32)
+
+/obj/machinery/puzzle/password
+ name = "password panel"
+ desc = "A panel that controls something nearby. This one requires a (case-sensitive) password, and it's not \"Swordfish\"."
+ icon_state = "passpad0"
+ base_icon_state = "passpad"
+ ///The password to this door.
+ var/password = ""
+ ///The text shown in the tgui input popup
+ var/tgui_text = "Please enter the password."
+ ///The title of the tgui input popup
+ var/tgui_title = "What's the password?"
+ ///Decides whether the max length of the input is MAX_NAME_LEN or the length of the password.
+ var/input_max_len_is_pass = FALSE
+
+/obj/machinery/puzzle/password/interact(mob/user, list/modifiers)
+ if(used && single_use)
+ return
+ if(!user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src))
+ return
+ var/pass_input = tgui_input_text(user, tgui_text, tgui_title, max_length = input_max_len_is_pass ? length(password) : MAX_NAME_LEN)
+ if(isnull(pass_input) || !user.can_perform_action(src, ALLOW_SILICON_REACH) || !user.can_interact_with(src))
+ return
+ var/correct = pass_input == password
+ balloon_alert_to_viewers("[correct ? "correct" : "wrong"] password[correct ? "" : "!"]")
+ if(!correct)
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 45, TRUE)
+ return
+ used = single_use
+ update_icon_state()
+ playsound(src, 'sound/machines/terminal_button07.ogg', 45, TRUE)
+ on_puzzle_complete()
-/obj/machinery/puzzle_keycardpad/update_icon_state()
- icon_state = "[base_icon_state][used]"
- return ..()
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password, 32)
-MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32)
+/obj/machinery/puzzle/password/pin
+ desc = "A panel that controls something nearby. This one requires a PIN password, so let's start by typing in 1234..."
+ tgui_text = "Please enter the PIN code."
+ tgui_title = "What's the PIN code?"
+ input_max_len_is_pass = TRUE
+ ///The length of the PIN. Suggestion: something between 4 and 12.
+ var/pin_length = 6
+ ///associate a color to each digit that may be found in the password.
+ var/list/digit_to_color = list()
+
+/obj/machinery/puzzle/password/pin/Initialize(mapload)
+ . = ..()
+
+ for(var/iteration in 1 to pin_length)
+ password += "[rand(1, 9)]"
+
+ var/list/possible_colors = list(
+ "white",
+ "black",
+ "red",
+ "green",
+ "blue",
+ "yellow",
+ "orange",
+ "brown",
+ "gray",
+ )
+ for(var/digit in 0 to 9)
+ digit_to_color["[digit]"] = pick_n_take(possible_colors)
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle/password/pin, 32)
//
// blockade
@@ -445,7 +506,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32)
if(isnull(id) || isnull(queue_id))
log_mapping("[src] id:[id] has no id or door id and has been deleted")
return INITIALIZE_HINT_QDEL
-
+
SSqueuelinks.add_to_queue(src, queue_id)
/obj/effect/puzzle_poddoor_open/MatchedLinks(id, list/partners)
@@ -461,3 +522,99 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32)
if(isnull(openclose))
openclose = door.density
INVOKE_ASYNC(door, openclose ? TYPE_PROC_REF(/obj/machinery/door/poddoor, open) : TYPE_PROC_REF(/obj/machinery/door/poddoor, close))
+
+#define MAX_PUZZLE_DOTS_PER_ROW 4
+#define PUZZLE_DOTS_VERTICAL_OFFSET 7
+#define PUZZLE_DOTS_HORIZONTAL_OFFSET 7
+
+///A dotted board that can be used as clue for PIN puzzle machinery
+/obj/effect/decal/puzzle_dots
+ name = "dotted board"
+ desc = "A board filled with colored dots. What could this mean?"
+ icon = 'icons/obj/fluff/puzzle_small.dmi'
+ icon_state = "puzzle_dots"
+ plane = GAME_PLANE //visible over walls
+ resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | UNACIDABLE | LAVA_PROOF
+ flags_1 = UNPAINTABLE_1
+ ///The id of the puzzle we're linked to.
+ var/id
+
+/obj/effect/decal/puzzle_dots/Initialize(mapload)
+ . = ..()
+ if(id)
+ SSqueuelinks.add_to_queue(src, id)
+
+/obj/effect/decal/puzzle_dots/MatchedLinks(id, partners)
+ var/obj/machinery/puzzle/password/pin/pad = locate() in partners
+ var/list/pass_digits = splittext(pad.password, "")
+ var/pass_len = length(pass_digits)
+ var/extra_rows = CEILING((pass_len/MAX_PUZZLE_DOTS_PER_ROW)-1, 1)
+ if(extra_rows)
+ pixel_y += round(extra_rows*(PUZZLE_DOTS_VERTICAL_OFFSET*0.5))
+ for(var/i in 1 to extra_rows)
+ var/mutable_appearance/row = mutable_appearance(icon, icon_state)
+ row.pixel_y = -i*PUZZLE_DOTS_VERTICAL_OFFSET
+ add_overlay(row)
+ for(var/i in 1 to pass_len)
+ var/mutable_appearance/colored_dot = mutable_appearance(icon, "puzzle_dot_single")
+ colored_dot.color = pad.digit_to_color[pass_digits[i]]
+ colored_dot.pixel_x = PUZZLE_DOTS_HORIZONTAL_OFFSET * ((i-1)%MAX_PUZZLE_DOTS_PER_ROW)
+ colored_dot.pixel_y -= CEILING((i/MAX_PUZZLE_DOTS_PER_ROW)-1, 1)*PUZZLE_DOTS_VERTICAL_OFFSET
+ add_overlay(colored_dot)
+
+#undef MAX_PUZZLE_DOTS_PER_ROW
+#undef PUZZLE_DOTS_VERTICAL_OFFSET
+#undef PUZZLE_DOTS_HORIZONTAL_OFFSET
+
+
+/obj/effect/decal/cleanable/crayon/puzzle
+ name = "Password character"
+ icon_state = "0"
+ ///The id of the puzzle we're linked to.
+ var/puzzle_id
+
+/obj/effect/decal/cleanable/crayon/puzzle/Initialize(mapload, main, type, e_name, graf_rot, alt_icon = null)
+ . = ..()
+ name = "number"
+ if(puzzle_id)
+ SSqueuelinks.add_to_queue(src, puzzle_id)
+
+/obj/effect/decal/cleanable/crayon/puzzle/MatchedLinks(id, partners)
+ var/obj/machinery/puzzle/password/pad = locate() in partners
+ var/list/pass_character = splittext(pad.password, "")
+ var/chosen_character = icon_state
+ if(!findtext(chosen_character, GLOB.is_alphanumeric))
+ qdel(src)
+ return FALSE
+ icon_state = pick(pass_character)
+ if(!text2num(icon_state))
+ name = "letter"
+ desc = "A letter vandalizing the station."
+ return TRUE
+
+/obj/effect/decal/cleanable/crayon/puzzle/pin
+ name = "PIN number"
+
+/obj/effect/decal/cleanable/crayon/puzzle/pin/MatchedLinks(id, partners)
+ . = ..()
+ var/obj/machinery/puzzle/password/pin/pad = locate() in partners
+ add_atom_colour(pad.digit_to_color[icon_state], FIXED_COLOUR_PRIORITY)
+
+/obj/item/paper/fluff/scrambled_pass
+ name = "gibberish note"
+ icon_state = "scrap"
+ ///The ID associated to the puzzle we're part of.
+ var/puzzle_id
+
+/obj/item/paper/fluff/scrambled_pass/Initialize(mapload)
+ . = ..()
+ if(mapload && puzzle_id)
+ SSqueuelinks.add_to_queue(src, puzzle_id)
+
+/obj/item/paper/fluff/scrambled_pass/MatchedLinks(id, partners)
+ var/obj/machinery/puzzle/password/pad = locate() in partners
+ var/scrambled_text = ""
+ var/list/pass_characters = splittext(pad.password, "")
+ for(var/i in 1 to rand(200, 300))
+ scrambled_text += pick(pass_characters)
+ add_raw_text(scrambled_text)
diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm
index 9570614b400b3b8..025571a90a78269 100644
--- a/code/game/objects/items/rcd/RCD.dm
+++ b/code/game/objects/items/rcd/RCD.dm
@@ -219,7 +219,10 @@
delay *= FREQUENT_USE_DEBUFF_MULTIPLIER
current_active_effects += 1
- _rcd_create_effect(target, user, delay, rcd_results)
+ var/target_name = target.name //Store the name before it gets mutated due to deconstruction.
+ var/target_path = target.type
+ if(_rcd_create_effect(target, user, delay, rcd_results))
+ log_tool("used RCD with design path: \"[rcd_results["[RCD_DESIGN_MODE]"] == RCD_DECONSTRUCT ? "deconstruction" : rcd_results["[RCD_DESIGN_PATH]"]]\" with delay: \"[delay / (1 SECONDS)]s\" at target: \"[target_name] ([target_path])\" in location: \"[AREACOORD(target)]\".", user)
current_active_effects -= 1
/**
diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm
index a747f813ace869f..0ecfefa589d10e3 100644
--- a/code/game/objects/items/robot/items/food.dm
+++ b/code/game/objects/items/robot/items/food.dm
@@ -60,10 +60,7 @@
if(DISPENSE_LOLLIPOP_MODE)
food_item = new /obj/item/food/lollipop/cyborg(turf_to_dispense_to)
if(DISPENSE_ICECREAM_MODE)
- food_item = new /obj/item/food/icecream(
- loc = turf_to_dispense_to,
- prefill_flavours = list(ICE_CREAM_VANILLA),
- )
+ food_item = new /obj/item/food/icecream(turf_to_dispense_to, list(ICE_CREAM_VANILLA))
food_item.desc = "Eat the ice cream."
var/into_hands = FALSE
diff --git a/code/game/objects/items/shrapnel.dm b/code/game/objects/items/shrapnel.dm
index cdc786fc8db5633..c07bc780c9128a1 100644
--- a/code/game/objects/items/shrapnel.dm
+++ b/code/game/objects/items/shrapnel.dm
@@ -35,6 +35,9 @@
wound_bonus = 30
embedding = list(embed_chance=70, ignore_throwspeed_threshold=TRUE, fall_chance=1)
+/obj/projectile/bullet/shrapnel/short_range
+ range = 5
+
/obj/projectile/bullet/shrapnel/mega
name = "flying shrapnel hunk"
range = 45
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 19b518157c8c286..75c35eabb181841 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -74,7 +74,7 @@
attack_verb_simple = list("bluespace polybash", "bluespace polybatter", "bluespace polybludgeon", "bluespace polythrash", "bluespace polysmash")
novariants = TRUE
grind_results = list(/datum/reagent/bluespace = 20)
- point_value = 30
+ point_value = 90
merge_type = /obj/item/stack/sheet/bluespace_crystal
material_type = /datum/material/bluespace
var/crystal_type = /obj/item/stack/ore/bluespace_crystal/refined
diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
index 514ab36ed66d3cb..43cd135904f8764 100644
--- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
+++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
@@ -433,15 +433,14 @@
var/glow_range = 3
var/glow_power = 1
var/glow_color = LIGHT_COLOR_DEFAULT
- var/datum/component/overlay_lighting/lightbulb
+ var/obj/effect/dummy/lighting_obj/moblight/lightbulb
/datum/status_effect/golem_lightbulb/on_apply()
. = ..()
if (!.)
return
to_chat(owner, span_notice("You start to emit a healthy glow."))
- owner.light_system = OVERLAY_LIGHT
- lightbulb = owner.AddComponent(/datum/component/overlay_lighting, _range = glow_range, _power = glow_power, _color = glow_color)
+ lightbulb = owner.mob_light(glow_range, glow_power, glow_color)
owner.add_filter(LIGHTBULB_FILTER, 2, list("type" = "outline", "color" = glow_color, "alpha" = 60, "size" = 1))
/datum/status_effect/golem_lightbulb/on_remove()
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index 5a789d34350e297..d6bd65afe31e185 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -159,7 +159,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
resistance_flags = ACID_PROOF
merge_type = /obj/item/stack/sheet/rglass
grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/iron = 10)
- point_value = 4
+ point_value = 12
matter_amount = 6
tableVariant = /obj/structure/table/reinforced/rglass
@@ -197,7 +197,7 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
material_flags = NONE
merge_type = /obj/item/stack/sheet/plasmarglass
grind_results = list(/datum/reagent/silicon = 20, /datum/reagent/toxin/plasma = 10, /datum/reagent/iron = 10)
- point_value = 23
+ point_value = 69
matter_amount = 8
tableVariant = /obj/structure/table/reinforced/plasmarglass
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index 8eafe6d52e5ae65..0620d82cd9bf4f4 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(sandbag_recipes, list ( \
sheettype = "diamond"
mats_per_unit = list(/datum/material/diamond=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/carbon = 20)
- point_value = 25
+ point_value = 75
merge_type = /obj/item/stack/sheet/mineral/diamond
material_type = /datum/material/diamond
walltype = /turf/closed/wall/mineral/diamond
@@ -124,7 +124,7 @@ GLOBAL_LIST_INIT(diamond_recipes, list ( \
sheettype = "uranium"
mats_per_unit = list(/datum/material/uranium=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/uranium = 20)
- point_value = 20
+ point_value = 60
merge_type = /obj/item/stack/sheet/mineral/uranium
material_type = /datum/material/uranium
walltype = /turf/closed/wall/mineral/uranium
@@ -157,7 +157,7 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \
max_integrity = 100
mats_per_unit = list(/datum/material/plasma=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/toxin/plasma = 20)
- point_value = 20
+ point_value = 60
merge_type = /obj/item/stack/sheet/mineral/plasma
material_type = /datum/material/plasma
walltype = /turf/closed/wall/mineral/plasma
@@ -192,7 +192,7 @@ GLOBAL_LIST_INIT(plasma_recipes, list ( \
sheettype = "gold"
mats_per_unit = list(/datum/material/gold=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/gold = 20)
- point_value = 20
+ point_value = 60
merge_type = /obj/item/stack/sheet/mineral/gold
material_type = /datum/material/gold
walltype = /turf/closed/wall/mineral/gold
@@ -219,7 +219,7 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \
sheettype = "silver"
mats_per_unit = list(/datum/material/silver=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/silver = 20)
- point_value = 20
+ point_value = 60
merge_type = /obj/item/stack/sheet/mineral/silver
material_type = /datum/material/silver
tableVariant = /obj/structure/table/optable
@@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(silver_recipes, list ( \
sheettype = "bananium"
mats_per_unit = list(/datum/material/bananium=SHEET_MATERIAL_AMOUNT)
grind_results = list(/datum/reagent/consumable/banana = 20)
- point_value = 50
+ point_value = 150
merge_type = /obj/item/stack/sheet/mineral/bananium
material_type = /datum/material/bananium
walltype = /turf/closed/wall/mineral/bananium
@@ -276,7 +276,7 @@ GLOBAL_LIST_INIT(bananium_recipes, list ( \
throw_range = 3
sheettype = "titanium"
mats_per_unit = list(/datum/material/titanium=SHEET_MATERIAL_AMOUNT)
- point_value = 20
+ point_value = 60
merge_type = /obj/item/stack/sheet/mineral/titanium
material_type = /datum/material/titanium
walltype = /turf/closed/wall/mineral/titanium
@@ -308,7 +308,7 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \
throw_range = 3
sheettype = "plastitanium"
mats_per_unit = list(/datum/material/alloy/plastitanium=SHEET_MATERIAL_AMOUNT)
- point_value = 45
+ point_value = 135
material_type = /datum/material/alloy/plastitanium
merge_type = /obj/item/stack/sheet/mineral/plastitanium
material_flags = NONE
@@ -482,7 +482,7 @@ GLOBAL_LIST_INIT(metalhydrogen_recipes, list(
singular_name = "metal hydrogen sheet"
w_class = WEIGHT_CLASS_NORMAL
resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
- point_value = 100
+ point_value = 300
mats_per_unit = list(/datum/material/metalhydrogen = SHEET_MATERIAL_AMOUNT)
material_type = /datum/material/metalhydrogen
merge_type = /obj/item/stack/sheet/mineral/metal_hydrogen
@@ -497,7 +497,7 @@ GLOBAL_LIST_INIT(metalhydrogen_recipes, list(
inhand_icon_state = "sheet-zaukerite"
singular_name = "zaukerite crystal"
w_class = WEIGHT_CLASS_NORMAL
- point_value = 120
+ point_value = 360
mats_per_unit = list(/datum/material/zaukerite = SHEET_MATERIAL_AMOUNT)
merge_type = /obj/item/stack/sheet/mineral/zaukerite
material_type = /datum/material/zaukerite
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 8b5fa16e5800eb6..4d02057d30a0527 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -48,6 +48,11 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
new /datum/stack_recipe("bench (left)", /obj/structure/chair/sofa/bench/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
new /datum/stack_recipe("bench (right)", /obj/structure/chair/sofa/bench/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
new /datum/stack_recipe("bench (corner)", /obj/structure/chair/sofa/bench/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
+ new /datum/stack_recipe("tram bench (solo)", /obj/structure/chair/sofa/bench/tram/solo, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
+ new /datum/stack_recipe("tram bench (middle)", /obj/structure/chair/sofa/bench/tram, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
+ new /datum/stack_recipe("tram bench (left)", /obj/structure/chair/sofa/bench/tram/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
+ new /datum/stack_recipe("tram bench (right)", /obj/structure/chair/sofa/bench/tram/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
+ new /datum/stack_recipe("tram bench (corner)", /obj/structure/chair/sofa/bench/tram/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \
)), \
new /datum/stack_recipe_list("chess pieces", list( \
new /datum/stack_recipe("White Pawn", /obj/structure/chess/whitepawn, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \
@@ -145,7 +150,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
resistance_flags = FIRE_PROOF
merge_type = /obj/item/stack/sheet/iron
grind_results = list(/datum/reagent/iron = 20)
- point_value = 2
+ point_value = 6
tableVariant = /obj/structure/table
material_type = /datum/material/iron
matter_amount = 4
@@ -272,7 +277,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \
resistance_flags = FIRE_PROOF
merge_type = /obj/item/stack/sheet/plasteel
grind_results = list(/datum/reagent/iron = 20, /datum/reagent/toxin/plasma = 20)
- point_value = 23
+ point_value = 69
tableVariant = /obj/structure/table/reinforced
material_flags = NONE
matter_amount = 12
@@ -780,7 +785,8 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \
)
GLOBAL_LIST_INIT(plastic_recipes, list(
new /datum/stack_recipe("plastic floor tile", /obj/item/stack/tile/plastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \
- new /datum/stack_recipe("thermoplastic tram tile", /obj/item/stack/thermoplastic, 1, 2, time = 4 SECONDS, check_density = FALSE, placement_checks = STACK_CHECK_TRAM_EXCLUSIVE, category = CAT_TILES), \
+ new /datum/stack_recipe("light tram tile", /obj/item/stack/thermoplastic/light, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \
+ new /datum/stack_recipe("dark tram tile", /obj/item/stack/thermoplastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \
new /datum/stack_recipe("folding plastic chair", /obj/structure/chair/plastic, 2, check_density = FALSE, category = CAT_FURNITURE), \
new /datum/stack_recipe("plastic flaps", /obj/structure/plasticflaps, 5, one_per_turf = TRUE, on_solid_ground = TRUE, time = 4 SECONDS, category = CAT_FURNITURE), \
new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/empty, check_density = FALSE, category = CAT_CONTAINERS), \
diff --git a/code/game/objects/items/sticker.dm b/code/game/objects/items/sticker.dm
deleted file mode 100644
index 459c8d211e4d949..000000000000000
--- a/code/game/objects/items/sticker.dm
+++ /dev/null
@@ -1,131 +0,0 @@
-/// parent type for all other stickers. do not spawn directly
-/obj/item/sticker
- name = "sticker"
- desc = "A sticker with some strong adhesive on the back, sticks to stuff!"
- item_flags = NOBLUDGEON | XENOMORPH_HOLDABLE //funny
- resistance_flags = FLAMMABLE
- icon = 'icons/obj/toys/stickers.dmi'
- w_class = WEIGHT_CLASS_TINY
- throw_range = 3
- vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE | VIS_INHERIT_LAYER
- ///If not null, pick an icon_state from this list
- var/icon_states
- /// If the sticker should be disincluded from normal sticker boxes.
- var/contraband = FALSE
-
-/obj/item/sticker/Initialize(mapload)
- . = ..()
- if(icon_states)
- icon_state = pick(icon_states)
- pixel_y = rand(-3,3)
- pixel_x = rand(-3,3)
- AddElement(/datum/element/sticker)
-
-/obj/item/sticker/smile
- name = "smiley sticker"
- icon_state = "smile"
-
-/obj/item/sticker/frown
- name = "frowny sticker"
- icon_state = "frown"
-
-/obj/item/sticker/left_arrow
- name = "left arrow sticker"
- icon_state = "larrow"
-
-/obj/item/sticker/right_arrow
- name = "right arrow sticker"
- icon_state = "rarrow"
-
-/obj/item/sticker/star
- name = "star sticker"
- icon_state = "star1"
- icon_states = list("star1","star2")
-
-/obj/item/sticker/heart
- name = "heart sticker"
- icon_state = "heart"
-
-/obj/item/sticker/googly
- name = "googly eye sticker"
- icon_state = "googly1"
- icon_states = list("googly1","googly2")
-
-/obj/item/sticker/rev
- name = "blue R sticker"
- desc = "A sticker of FUCK THE SYSTEM, the galaxy's premiere hardcore punk band."
- icon_state = "revhead"
-
-/obj/item/sticker/pslime
- name = "slime plushie sticker"
- icon_state = "pslime"
-
-/obj/item/sticker/pliz
- name = "lizard plushie sticker"
- icon_state = "plizard"
-
-/obj/item/sticker/pbee
- name = "bee plushie sticker"
- icon_state = "pbee"
-
-/obj/item/sticker/psnake
- name = "snake plushie sticker"
- icon_state = "psnake"
-
-/obj/item/sticker/robot
- name = "bot sticker"
- icon_state = "tile"
- icon_states = list("tile","medbot","clean")
-
-/obj/item/sticker/toolbox
- name = "toolbox sticker"
- icon_state = "toolbox"
-
-/obj/item/sticker/clown
- name = "clown sticker"
- icon_state = "honkman"
-
-/obj/item/sticker/mime
- name = "mime sticker"
- icon_state = "silentman"
-
-/obj/item/sticker/assistant
- name = "assistant sticker"
- icon_state = "tider"
-
-/obj/item/sticker/syndicate
- name = "syndicate sticker"
- icon_state = "synd"
- contraband = TRUE
-
-/obj/item/sticker/syndicate/c4
- name = "C-4 sticker"
- icon_state = "c4"
-
-/obj/item/sticker/syndicate/bomb
- name = "syndicate bomb sticker"
- icon_state = "sbomb"
-
-/obj/item/sticker/syndicate/apc
- name = "broken APC sticker"
- icon_state = "milf"
-
-/obj/item/sticker/syndicate/larva
- name = "larva sticker"
- icon_state = "larva"
-
-/obj/item/sticker/syndicate/cult
- name = "bloody paper sticker"
- icon_state = "cult"
-
-/obj/item/sticker/syndicate/flash
- name = "flash sticker"
- icon_state = "flash"
-
-/obj/item/sticker/syndicate/op
- name = "operative sticker"
- icon_state = "newcop"
-
-/obj/item/sticker/syndicate/trap
- name = "bear trap sticker"
- icon_state = "trap"
diff --git a/code/game/objects/items/stickers.dm b/code/game/objects/items/stickers.dm
new file mode 100644
index 000000000000000..c7288bed2dd5fac
--- /dev/null
+++ b/code/game/objects/items/stickers.dm
@@ -0,0 +1,195 @@
+#define MAX_STICKER_COUNT 15
+
+/**
+ * What stickers can do?
+ *
+ * - They can be attached to any object.
+ * - They inherit cursor position when attached.
+ * - They are unclickable by mouse, I suppose?
+ * - They can be washed off.
+ * - They can be burnt off.
+ * - They can be attached to the object they collided with.
+ * - They play "attack" animation when attached.
+ *
+ */
+
+/obj/item/sticker
+ name = "sticker"
+ desc = "A sticker with some strong adhesive on the back, sticks to stuff!"
+
+ icon = 'icons/obj/toys/stickers.dmi'
+
+ max_integrity = 50
+ resistance_flags = FLAMMABLE
+
+ throw_range = 3
+ pressure_resistance = 0
+
+ item_flags = NOBLUDGEON | XENOMORPH_HOLDABLE //funny ~Jimmyl
+ w_class = WEIGHT_CLASS_TINY
+
+ /// `list` or `null`, contains possible alternate `icon_states`.
+ var/list/icon_states
+ /// Whether sticker is legal and allowed to generate inside non-syndicate boxes.
+ var/contraband = FALSE
+
+/obj/item/sticker/Initialize(mapload)
+ . = ..()
+
+ if(length(icon_states))
+ icon_state = pick(icon_states)
+
+/obj/item/sticker/Bump(atom/bumped_atom)
+ if(prob(50) && attempt_attach(bumped_atom))
+ bumped_atom.balloon_alert_to_viewers("sticker landed on sticky side!")
+
+/obj/item/sticker/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ if(!isatom(interacting_with))
+ return NONE
+
+ var/cursor_x = text2num(LAZYACCESS(modifiers, ICON_X))
+ var/cursor_y = text2num(LAZYACCESS(modifiers, ICON_Y))
+
+ if(isnull(cursor_x) || isnull(cursor_y))
+ return NONE
+
+ if(attempt_attach(interacting_with, user, cursor_x, cursor_y))
+ return ITEM_INTERACT_SUCCESS
+
+ return NONE
+
+/**
+ * Attempts to attach sticker to an object. Returns `FALSE` if atom has more than
+ * `MAX_STICKER_COUNT` stickers, `TRUE` otherwise. If no `px` or `py` were passed
+ * picks random coordinates based on a `target`'s icon.
+ */
+/obj/item/sticker/proc/attempt_attach(atom/target, mob/user, px, py)
+ if(COUNT_TRAIT_SOURCES(target, TRAIT_STICKERED) >= MAX_STICKER_COUNT)
+ balloon_alert_to_viewers("sticker won't stick!")
+ return FALSE
+
+ if(isnull(px) || isnull(py))
+ var/icon/target_mask = icon(target.icon, target.icon_state)
+
+ if(isnull(px))
+ px = rand(1, target_mask.Width())
+
+ if(isnull(py))
+ py = rand(1, target_mask.Height())
+
+ if(!isnull(user))
+ user.do_attack_animation(target, used_item = src)
+ target.balloon_alert(user, "sticker sticked")
+
+ target.AddComponent(/datum/component/sticker, src, user, get_dir(target, src), px, py)
+ return TRUE
+
+#undef MAX_STICKER_COUNT
+
+/obj/item/sticker/smile
+ name = "smiley sticker"
+ icon_state = "smile"
+
+/obj/item/sticker/frown
+ name = "frowny sticker"
+ icon_state = "frown"
+
+/obj/item/sticker/left_arrow
+ name = "left arrow sticker"
+ icon_state = "arrow-left"
+
+/obj/item/sticker/right_arrow
+ name = "right arrow sticker"
+ icon_state = "arrow-right"
+
+/obj/item/sticker/star
+ name = "star sticker"
+ icon_state = "star"
+
+/obj/item/sticker/heart
+ name = "heart sticker"
+ icon_state = "heart"
+
+/obj/item/sticker/googly
+ name = "googly eye sticker"
+ icon_state = "googly"
+ icon_states = list("googly", "googly-alt")
+
+/obj/item/sticker/rev
+ name = "blue R sticker"
+ desc = "A sticker of FUCK THE SYSTEM, the galaxy's premiere hardcore punk band."
+ icon_state = "revhead"
+
+/obj/item/sticker/pslime
+ name = "slime plushie sticker"
+ icon_state = "pslime"
+
+/obj/item/sticker/pliz
+ name = "lizard plushie sticker"
+ icon_state = "plizard"
+
+/obj/item/sticker/pbee
+ name = "bee plushie sticker"
+ icon_state = "pbee"
+
+/obj/item/sticker/psnake
+ name = "snake plushie sticker"
+ icon_state = "psnake"
+
+/obj/item/sticker/robot
+ name = "bot sticker"
+ icon_state = "tile"
+ icon_states = list("tile", "medbot", "clean")
+
+/obj/item/sticker/toolbox
+ name = "toolbox sticker"
+ icon_state = "soul"
+
+/obj/item/sticker/clown
+ name = "clown sticker"
+ icon_state = "honkman"
+
+/obj/item/sticker/mime
+ name = "mime sticker"
+ icon_state = "silentman"
+
+/obj/item/sticker/assistant
+ name = "assistant sticker"
+ icon_state = "tider"
+
+/obj/item/sticker/syndicate
+ name = "syndicate sticker"
+ icon_state = "synd"
+ contraband = TRUE
+
+/obj/item/sticker/syndicate/c4
+ name = "C-4 sticker"
+ icon_state = "c4"
+
+/obj/item/sticker/syndicate/bomb
+ name = "syndicate bomb sticker"
+ icon_state = "sbomb"
+
+/obj/item/sticker/syndicate/apc
+ name = "broken APC sticker"
+ icon_state = "milf"
+
+/obj/item/sticker/syndicate/larva
+ name = "larva sticker"
+ icon_state = "larva"
+
+/obj/item/sticker/syndicate/cult
+ name = "bloody paper sticker"
+ icon_state = "cult"
+
+/obj/item/sticker/syndicate/flash
+ name = "flash sticker"
+ icon_state = "flash"
+
+/obj/item/sticker/syndicate/op
+ name = "operative sticker"
+ icon_state = "newcop"
+
+/obj/item/sticker/syndicate/trap
+ name = "bear trap sticker"
+ icon_state = "trap"
diff --git a/code/game/objects/items/storage/boxes/clothes_boxes.dm b/code/game/objects/items/storage/boxes/clothes_boxes.dm
index 18a6ec31d87c927..ce4dfb6c5e535af 100644
--- a/code/game/objects/items/storage/boxes/clothes_boxes.dm
+++ b/code/game/objects/items/storage/boxes/clothes_boxes.dm
@@ -211,3 +211,11 @@
new /obj/item/clothing/gloves/combat/floortile(src)
new /obj/item/clothing/shoes/jackboots/floortile(src)
new /obj/item/storage/backpack/floortile(src)
+
+/obj/item/storage/box/collar_bomb
+ name = "collar bomb box"
+ desc = "A small print on the back reads 'For research purposes only. Handle with care. In case of emergency, call the following number:'... the rest is scratched out with a marker..."
+
+/obj/item/storage/box/collar_bomb/PopulateContents()
+ var/obj/item/collar_bomb_button/button = new(src)
+ new /obj/item/clothing/neck/collar_bomb(src, button)
diff --git a/code/game/objects/items/storage/boxes/service_boxes.dm b/code/game/objects/items/storage/boxes/service_boxes.dm
index 14656f0f5f71def..8dcc1f4f6b62f28 100644
--- a/code/game/objects/items/storage/boxes/service_boxes.dm
+++ b/code/game/objects/items/storage/boxes/service_boxes.dm
@@ -209,16 +209,21 @@
desc = "A box full of random stickers. Do give to the clown."
/obj/item/storage/box/stickers/proc/generate_non_contraband_stickers_list()
- . = list()
+ var/list/allowed_stickers = list()
+
for(var/obj/item/sticker/sticker_type as anything in subtypesof(/obj/item/sticker))
- if(!initial(sticker_type.contraband))
- . += sticker_type
- return .
+ if(!sticker_type::contraband)
+ allowed_stickers += sticker_type
+
+ return allowed_stickers
+
/obj/item/storage/box/stickers/PopulateContents()
var/static/list/non_contraband
- if(!non_contraband)
+
+ if(isnull(non_contraband))
non_contraband = generate_non_contraband_stickers_list()
- for(var/i in 1 to rand(4,8))
+
+ for(var/i in 1 to rand(4, 8))
var/type = pick(non_contraband)
new type(src)
diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm
index a7fbe6e7452f956..d83c80ed5651458 100644
--- a/code/game/objects/items/storage/fancy.dm
+++ b/code/game/objects/items/storage/fancy.dm
@@ -223,7 +223,7 @@
balloon_alert(user, "ooh, free coupon")
var/obj/item/coupon/attached_coupon = new
user.put_in_hands(attached_coupon)
- attached_coupon.generate(rigged_omen ? COUPON_OMEN : null)
+ attached_coupon.generate(rigged_omen ? COUPON_OMEN : null, null, user)
attached_coupon = null
spawn_coupon = FALSE
name = "discarded cigarette packet"
diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm
index cfdfef8a4590c80..8631d62e79efd21 100644
--- a/code/game/objects/items/storage/storage.dm
+++ b/code/game/objects/items/storage/storage.dm
@@ -55,7 +55,6 @@
max_total_storage,
list/canhold,
list/canthold,
- storage_type = /datum/storage,
storage_type,
)
// If no type was passed in, default to what we already have
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 271fa8b03150fe0..aacf45e239de92c 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -10,6 +10,7 @@
#define KIT_SNIPER "sniper"
#define KIT_NUKEOPS_METAGAME "metaops"
#define KIT_LORD_SINGULOTH "lordsingulo"
+#define KIT_REVOLUTIONARY "revolutionary"
#define KIT_JAMES_BOND "bond"
#define KIT_NINJA "ninja"
@@ -19,6 +20,9 @@
#define KIT_BEES "bee"
#define KIT_MR_FREEZE "mr_freeze"
#define KIT_TRAITOR_2006 "ancient"
+#define KIT_DEAD_MONEY "dead_money"
+#define KIT_SAM_FISHER "sam_fisher"
+#define KIT_PROP_HUNT "prop_hunt"
/// last audited december 2022
/obj/item/storage/box/syndicate
@@ -35,7 +39,8 @@
KIT_IMPLANTS = 1,
KIT_HACKER = 3,
KIT_SNIPER = 1,
- KIT_NUKEOPS_METAGAME = 1
+ KIT_NUKEOPS_METAGAME = 1,
+ KIT_REVOLUTIONARY = 2
)))
if(KIT_RECON)
new /obj/item/clothing/glasses/thermal/xray(src) // ~8 tc?
@@ -165,6 +170,18 @@
new /obj/item/card/emag(src) // 4 tc
new /obj/item/card/emag/doorjack(src) // 3 tc
+ if(KIT_REVOLUTIONARY)
+ new /obj/item/healthanalyzer/rad_laser(src) // 3 TC
+ new /obj/item/assembly/flash/hypnotic(src) // 7 TC
+ new /obj/item/storage/pill_bottle/lsd(src) // ~1 TC
+ new /obj/item/pen/sleepy(src) // 4 TC
+ new /obj/item/gun/ballistic/revolver/nagant(src) // 13 TC comparable to 357. revolvers
+ new /obj/item/megaphone(src)
+ new /obj/item/bedsheet/rev(src)
+ new /obj/item/clothing/suit/armor/vest/russian_coat(src)
+ new /obj/item/clothing/head/helmet/rus_ushanka(src)
+ new /obj/item/storage/box/syndie_kit/poster_box(src)
+
/obj/item/storage/box/syndicate/bundle_b/PopulateContents()
switch (pick_weight(list(
KIT_JAMES_BOND = 2,
@@ -174,7 +191,10 @@
KIT_MAD_SCIENTIST = 2,
KIT_BEES = 1,
KIT_MR_FREEZE = 2,
- KIT_TRAITOR_2006 = 1
+ KIT_DEAD_MONEY = 2,
+ KIT_TRAITOR_2006 = 1,
+ KIT_SAM_FISHER = 1,
+ KIT_PROP_HUNT = 1
)))
if(KIT_JAMES_BOND)
new /obj/item/gun/ballistic/automatic/pistol(src) // 7 tc
@@ -261,9 +281,37 @@
new /obj/item/gun/energy/laser/thermal/cryo(src) // ~6 tc
new /obj/item/melee/energy/sword/saber/blue(src) //see see it fits the theme bc its blue and ice is blue, 8 tc
- if(KIT_TRAITOR_2006) //A kit so old, it's probably older than you. //This bundle is filled with the entire unlink contents traitors had access to in 2006, from OpenSS13. Notably the esword was not a choice but existed in code.
+ if(KIT_TRAITOR_2006) //A kit so old, it's probably older than you. //This bundle is filled with the entire uplink contents traitors had access to in 2006, from OpenSS13. Notably the esword was not a choice but existed in code.
new /obj/item/storage/toolbox/emergency/old/ancientbundle(src) //Items fit neatly into a classic toolbox just to remind you what the theme is.
+ if(KIT_DEAD_MONEY)
+ for(var/i in 1 to 4)
+ new /obj/item/clothing/neck/collar_bomb(src) // These let you remotely kill people with a signaler, though you have to get them first.
+ new /obj/item/storage/box/syndie_kit/signaler(src)
+ new /obj/item/mod/control/pre_equipped/responsory/inquisitory/syndie(src) // basically a snowflake yet better elite modsuit, so like, 8 + 5 tc.
+ new /obj/item/card/id/advanced/chameleon(src) // 2 tc
+ new /obj/item/clothing/mask/chameleon(src)
+ new /obj/item/melee/baton/telescopic/contractor_baton(src) // 7 tc
+ new /obj/item/jammer(src) // 5 tc
+ new /obj/item/pinpointer/crew(src) //priceless
+
+ if(KIT_SAM_FISHER)
+ new /obj/item/clothing/under/syndicate/combat(src)
+ new /obj/item/clothing/suit/armor/vest/marine/pmc(src) //The armor kit is comparable to the infiltrator, 6 TC
+ new /obj/item/clothing/head/helmet/marine/pmc(src)
+ new /obj/item/clothing/mask/gas/sechailer(src)
+ new /obj/item/clothing/glasses/night(src) // 3~ TC
+ new /obj/item/clothing/gloves/krav_maga/combatglovesplus(src) //5TC
+ new /obj/item/clothing/shoes/jackboots(src)
+ new /obj/item/storage/belt/military/assault/fisher(src) //items in this belt easily costs 18 TC
+
+ if(KIT_PROP_HUNT)
+ new /obj/item/chameleon(src) // 7 TC
+ new /obj/item/card/emag/doorjack(src) // 3 TC
+ new /obj/item/storage/box/syndie_kit/imp_stealth(src) //8 TC
+ new /obj/item/gun/ballistic/automatic/pistol(src) // 7 TC
+ new /obj/item/clothing/glasses/thermal(src) // 4 TC
+
/obj/item/storage/toolbox/emergency/old/ancientbundle/ //So the subtype works
/obj/item/storage/toolbox/emergency/old/ancientbundle/PopulateContents()
@@ -276,6 +324,17 @@
new /obj/item/implanter/freedom(src) // 5 tc
new /obj/item/stack/telecrystal(src) //The failsafe/self destruct isn't an item we can physically include in the kit, but 1 TC is technically enough to buy the equivalent.
+/obj/item/storage/belt/military/assault/fisher
+
+/obj/item/storage/belt/military/assault/fisher/PopulateContents()
+ new /obj/item/gun/ballistic/automatic/pistol/clandestine(src) // 7 TC
+ new /obj/item/suppressor(src) // 3 TC
+ new /obj/item/ammo_box/magazine/m10mm(src) // 1 TC
+ new /obj/item/ammo_box/magazine/m10mm(src)
+ new /obj/item/gun/energy/recharge/fisher(src) // Acquirable through black market, shit utility item 1 TC
+ new /obj/item/card/emag/doorjack(src) // 3 TC
+ new /obj/item/knife/combat(src) //comparable to the e-dagger, 2 TC
+
/obj/item/storage/box/syndie_kit
name = "box"
desc = "A sleek, sturdy box."
@@ -628,6 +687,7 @@
/obj/item/storage/box/syndie_kit/stickers/PopulateContents()
var/list/types = subtypesof(/obj/item/sticker/syndicate)
+
for(var/i in 1 to atom_storage.max_slots)
var/type = pick(types)
new type(src)
@@ -807,6 +867,7 @@
#undef KIT_SNIPER
#undef KIT_NUKEOPS_METAGAME
#undef KIT_LORD_SINGULOTH
+#undef KIT_REVOLUTIONARY
#undef KIT_JAMES_BOND
#undef KIT_NINJA
@@ -816,3 +877,6 @@
#undef KIT_BEES
#undef KIT_MR_FREEZE
#undef KIT_TRAITOR_2006
+#undef KIT_DEAD_MONEY
+#undef KIT_SAM_FISHER
+#undef KIT_PROP_HUNT
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index a670a966805e780..e198f7d75d74de8 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -440,7 +440,7 @@
if(LAZYLEN(assembly.assemblies) == igniter_count)
return
-
+
if(isitem(loc)) // we are in a storage item
balloon_alert(user, "can't reach!")
return
@@ -553,7 +553,7 @@
var/turf/T = get_turf(src)
if(!T)
return
- log_atmos("[type] released its contents of ", air_contents)
+ log_atmos("[type] released its contents of ", removed)
T.assume_air(removed)
#undef ASSEMBLY_BOMB_BASE
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index c0fedb4380d6b7b..31efb0ef273f5cb 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -19,7 +19,7 @@
pickup_sound = 'sound/items/handling/weldingtool_pickup.ogg'
light_system = OVERLAY_LIGHT
light_range = 2
- light_power = 0.75
+ light_power = 1.5
light_color = LIGHT_COLOR_FIRE
light_on = FALSE
throw_speed = 3
diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm
index 0383767ce66e8e9..7084b313dff5930 100644
--- a/code/game/objects/items/vending_items.dm
+++ b/code/game/objects/items/vending_items.dm
@@ -19,8 +19,10 @@
w_class = WEIGHT_CLASS_BULKY
armor_type = /datum/armor/item_vending_refill
- // Built automatically from the corresponding vending machine.
- // If null, considered to be full. Otherwise, is list(/typepath = amount).
+ /**
+ * Built automatically from the corresponding vending machine.
+ * If null, considered to be full. Otherwise, is list(/typepath = amount).
+ */
var/list/products
var/list/product_categories
var/list/contraband
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 059f78b80c90da9..c2f654f85530bcc 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -554,124 +554,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/ectoplasm/mystic
icon_state = "mysticplasm"
-/obj/item/statuebust
- name = "bust"
- desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it
- icon = 'icons/obj/art/statue.dmi'
- icon_state = "bust"
- force = 15
- throwforce = 10
- throw_speed = 5
- throw_range = 2
- attack_verb_continuous = list("busts")
- attack_verb_simple = list("bust")
- var/impressiveness = 45
-
-/obj/item/statuebust/Initialize(mapload)
- . = ..()
- AddElement(/datum/element/art, impressiveness)
- AddElement(/datum/element/beauty, 1000)
-
-/obj/item/statuebust/hippocratic
- name = "hippocrates bust"
- desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine."
- icon_state = "hippocratic"
- impressiveness = 50
- // If it hits the prob(reference_chance) chance, this is set to TRUE. Adds medical HUD when wielded, but has a 10% slower attack speed and is too bloody to make an oath with.
- var/reference = FALSE
- // Chance for above.
- var/reference_chance = 1
- // Minimum time inbetween oaths.
- COOLDOWN_DECLARE(oath_cd)
-
-/obj/item/statuebust/hippocratic/evil
- reference_chance = 100
-
-/obj/item/statuebust/hippocratic/Initialize(mapload)
- . = ..()
- if(prob(reference_chance))
- name = "Solemn Vow"
- desc = "Art lovers will cherish the bust of Hippocrates, commemorating a time when medics still thought doing no harm was a good idea."
- attack_speed = CLICK_CD_SLOW
- reference = TRUE
-
-/obj/item/statuebust/hippocratic/examine(mob/user)
- . = ..()
- if(reference)
- . += span_notice("You could activate the bust in-hand to swear or forswear a Hippocratic Oath... but it seems like somebody decided it was more of a Hippocratic Suggestion. This thing is caked with bits of blood and gore.")
- return
- . += span_notice("You can activate the bust in-hand to swear or forswear a Hippocratic Oath! This has no effects except pacifism or bragging rights. Does not remove other sources of pacifism. Do not eat.")
-
-/obj/item/statuebust/hippocratic/equipped(mob/living/carbon/human/user, slot)
- ..()
- if(!(slot & ITEM_SLOT_HANDS))
- return
- var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- our_hud.show_to(user)
- ADD_TRAIT(user, TRAIT_MEDICAL_HUD, type)
-
-/obj/item/statuebust/hippocratic/dropped(mob/living/carbon/human/user)
- ..()
- if(HAS_TRAIT_NOT_FROM(user, TRAIT_MEDICAL_HUD, type))
- return
- var/datum/atom_hud/our_hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- our_hud.hide_from(user)
- REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, type)
-
-/obj/item/statuebust/hippocratic/attack_self(mob/user)
- if(!iscarbon(user))
- to_chat(user, span_warning("You remember how the Hippocratic Oath specifies 'my fellow human beings' and realize that it's completely meaningless to you."))
- return
-
- if(reference)
- to_chat(user, span_warning("As you prepare yourself to swear the Oath, you realize that doing so on a blood-caked bust is probably not a good idea."))
- return
-
- if(!COOLDOWN_FINISHED(src, oath_cd))
- to_chat(user, span_warning("You've sworn or forsworn an oath too recently to undo your decisions. The bust looks at you with disgust."))
- return
-
- COOLDOWN_START(src, oath_cd, 5 MINUTES)
-
- if(HAS_TRAIT_FROM(user, TRAIT_PACIFISM, type))
- to_chat(user, span_warning("You've already sworn a vow. You start preparing to rescind it..."))
- if(do_after(user, 5 SECONDS, target = user))
- user.say("Yeah this Hippopotamus thing isn't working out. I quit!", forced = "hippocratic hippocrisy")
- REMOVE_TRAIT(user, TRAIT_PACIFISM, type)
-
- // they can still do it for rp purposes
- if(HAS_TRAIT_NOT_FROM(user, TRAIT_PACIFISM, type))
- to_chat(user, span_warning("You already don't want to harm people, this isn't going to do anything!"))
-
-
- to_chat(user, span_notice("You remind yourself of the Hippocratic Oath's contents and prepare to swear yourself to it..."))
- if(do_after(user, 4 SECONDS, target = user))
- user.say("I swear to fulfill, to the best of my ability and judgment, this covenant:", forced = "hippocratic oath")
- else
- return fuck_it_up(user)
- if(do_after(user, 2 SECONDS, target = user))
- user.say("I will apply, for the benefit of the sick, all measures that are required, avoiding those twin traps of overtreatment and therapeutic nihilism.", forced = "hippocratic oath")
- else
- return fuck_it_up(user)
- if(do_after(user, 3 SECONDS, target = user))
- user.say("I will remember that I remain a member of society, with special obligations to all my fellow human beings, those sound of mind and body as well as the infirm.", forced = "hippocratic oath")
- else
-
- return fuck_it_up(user)
- if(do_after(user, 3 SECONDS, target = user))
- user.say("If I do not violate this oath, may I enjoy life and art, respected while I live and remembered with affection thereafter. May I always act so as to preserve the finest traditions of my calling and may I long experience the joy of healing those who seek my help.", forced = "hippocratic oath")
- else
- return fuck_it_up(user)
-
- to_chat(user, span_notice("Contentment, understanding, and purpose washes over you as you finish the oath. You consider for a second the concept of harm and shudder."))
- ADD_TRAIT(user, TRAIT_PACIFISM, type)
-
-// Bully the guy for fucking up.
-/obj/item/statuebust/hippocratic/proc/fuck_it_up(mob/living/carbon/user)
- to_chat(user, span_warning("You forget what comes next like a dumbass. The Hippocrates bust looks down on you, disappointed."))
- user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
- COOLDOWN_RESET(src, oath_cd)
-
/obj/item/tailclub
name = "tail club"
desc = "For the beating to death of lizards with their own tails."
@@ -909,26 +791,28 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
hitsound = 'sound/effects/snap.ogg'
w_class = WEIGHT_CLASS_SMALL
/// Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc.
- var/list/splattable
+ var/static/list/splattable
/// Things in this list which take a lot more damage from the fly swatter, but not be necessarily killed by it.
- var/list/strong_against
+ var/static/list/strong_against
/// How much extra damage the fly swatter does against mobs it is strong against
var/extra_strength_damage = 24
/obj/item/melee/flyswatter/Initialize(mapload)
. = ..()
- splattable = typecacheof(list(
- /mob/living/basic/ant,
- /mob/living/basic/butterfly,
- /mob/living/basic/cockroach,
- /mob/living/basic/spider/growing/spiderling,
- /mob/living/basic/bee,
- /obj/effect/decal/cleanable/ants,
- /obj/item/queen_bee,
- ))
- strong_against = typecacheof(list(
- /mob/living/basic/spider/giant,
- ))
+ if (isnull(splattable))
+ splattable = typecacheof(list(
+ /mob/living/basic/ant,
+ /mob/living/basic/butterfly,
+ /mob/living/basic/cockroach,
+ /mob/living/basic/spider/growing/spiderling,
+ /mob/living/basic/bee,
+ /obj/effect/decal/cleanable/ants,
+ /obj/item/queen_bee,
+ ))
+ if (isnull(strong_against))
+ strong_against = typecacheof(list(
+ /mob/living/basic/spider,
+ ))
/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag)
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index f80042f5679a758..50fcae8dec1382c 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -35,7 +35,7 @@ LINEN BINS
/obj/item/bedsheet/Initialize(mapload)
. = ..()
AddComponent(/datum/component/surgery_initiator)
- AddElement(/datum/element/bed_tuckable, 0, 0, 0)
+ AddElement(/datum/element/bed_tuckable, mapload, 0, 0, 0)
if(bedsheet_type == BEDSHEET_DOUBLE)
stack_amount *= 2
dying_key = DYE_REGISTRY_DOUBLE_BEDSHEET
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index b3d3c7085bd50da..d2df088e06f7306 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -94,6 +94,11 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
/// Volume of the internal air
var/air_volume = TANK_STANDARD_VOLUME * 3
+ /// How many pixels the closet can shift on the x axis when shaking
+ var/x_shake_pixel_shift = 2
+ /// how many pixels the closet can shift on the y axes when shaking
+ var/y_shake_pixel_shift = 1
+
/datum/armor/structure_closet
melee = 20
bullet = 10
@@ -1031,6 +1036,9 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
user.visible_message(span_warning("[src] begins to shake violently!"), \
span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \
span_hear("You hear banging from [src]."))
+
+ addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 1 SECONDS)
+
if(do_after(user,(breakout_time), target = src))
if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) )
return
@@ -1045,6 +1053,23 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets)
/obj/structure/closet/relay_container_resist_act(mob/living/user, obj/container)
container.container_resist_act()
+/// Check if someone is still resisting inside, and choose to either keep shaking or stop shaking the closet
+/obj/structure/closet/proc/check_if_shake()
+ // Assuming we decide to shake again, how long until we check to shake again
+ var/next_check_time = 1 SECONDS
+
+ // How long we shake between different calls of Shake(), so that it starts shaking and stops, instead of a steady shake
+ var/shake_duration = 0.3 SECONDS
+
+ for(var/mob/living/mob in contents)
+ if(DOING_INTERACTION_WITH_TARGET(mob, src))
+ // Shake and queue another check_if_shake
+ Shake(x_shake_pixel_shift, y_shake_pixel_shift, shake_duration, shake_interval = 0.1 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(check_if_shake)), next_check_time)
+ return TRUE
+
+ // If we reach here, nobody is resisting, so dont shake
+ return FALSE
/obj/structure/closet/proc/bust_open()
SIGNAL_HANDLER
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 89b6245a6a0b127..c081804d36ba37c 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -18,6 +18,8 @@
drag_slowdown = 0
door_anim_time = 0 // no animation
pass_flags_self = PASSSTRUCTURE | LETPASSTHROW
+ x_shake_pixel_shift = 1
+ y_shake_pixel_shift = 2
/// Mobs standing on it are nudged up by this amount.
var/elevation = 14
/// The same, but when the crate is open
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index d5cc2cb177fc76d..ada42ff40d55a48 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -409,11 +409,10 @@
max_integrity = 350
/obj/structure/girder/tram
- name = "tram frame"
+ name = "tram girder"
desc = "Titanium framework to construct tram walls. Can be plated with titanium glass or other wall materials."
icon_state = "tram"
state = GIRDER_TRAM
- density = FALSE
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
/obj/structure/girder/tram/corner
diff --git a/code/game/objects/structures/gym/punching_bag.dm b/code/game/objects/structures/gym/punching_bag.dm
index 9c67bd89cfc09bc..f441887f2c191c2 100644
--- a/code/game/objects/structures/gym/punching_bag.dm
+++ b/code/game/objects/structures/gym/punching_bag.dm
@@ -44,17 +44,22 @@
flick("[icon_state]-punch", src)
playsound(loc, pick(hit_sounds), 25, TRUE, -1)
+ if(!iscarbon(user))
+ return
+
+ var/is_heavy_gravity = user.has_gravity() > STANDARD_GRAVITY
+
var/stamina_exhaustion = 3
if(ishuman(user))
var/mob/living/carbon/human/boxer = user
var/obj/item/clothing/gloves/boxing/boxing_gloves = boxer.get_item_by_slot(ITEM_SLOT_GLOVES)
if(istype(boxing_gloves))
stamina_exhaustion = 2
+ if (is_heavy_gravity)
+ stamina_exhaustion *= 1.5
- if(!iscarbon(user))
- return
user.adjustStaminaLoss(stamina_exhaustion)
- user.mind?.adjust_experience(/datum/skill/fitness, 0.1)
+ user.mind?.adjust_experience(/datum/skill/fitness, is_heavy_gravity ? 0.2 : 0.1)
user.apply_status_effect(/datum/status_effect/exercised)
/obj/structure/punching_bag/wrench_act_secondary(mob/living/user, obj/item/tool)
diff --git a/code/game/objects/structures/gym/weight_machine.dm b/code/game/objects/structures/gym/weight_machine.dm
index 055c9788c95da08..d3614ee6815bc91 100644
--- a/code/game/objects/structures/gym/weight_machine.dm
+++ b/code/game/objects/structures/gym/weight_machine.dm
@@ -133,8 +133,9 @@
user.adjust_nutrition(-5) // feel the burn
if(iscarbon(user))
+ var/gravity_modifier = user.has_gravity() > STANDARD_GRAVITY ? 2 : 1
// remember the real xp gain is from sleeping after working out
- user.mind.adjust_experience(/datum/skill/fitness, WORKOUT_XP)
+ user.mind.adjust_experience(/datum/skill/fitness, WORKOUT_XP * gravity_modifier)
user.apply_status_effect(/datum/status_effect/exercised, EXERCISE_STATUS_DURATION)
end_workout()
@@ -161,8 +162,13 @@
if(!iscarbon(user) || isnull(user.mind))
return TRUE
+
+ var/affected_gravity = user.has_gravity()
+ if (!affected_gravity)
+ return TRUE // No weight? I could do this all day
+ var/gravity_modifier = affected_gravity > STANDARD_GRAVITY ? 0.75 : 1
// the amount of workouts you can do before you hit stamcrit
- var/workout_reps = total_workout_reps[user.mind.get_skill_level(/datum/skill/fitness)]
+ var/workout_reps = total_workout_reps[user.mind.get_skill_level(/datum/skill/fitness)] * gravity_modifier
// total stamina drain of 1 workout calculated based on the workout length
var/stamina_exhaustion = FLOOR(user.maxHealth / workout_reps / WORKOUT_LENGTH, 0.1)
user.adjustStaminaLoss(stamina_exhaustion * seconds_per_tick)
diff --git a/code/game/objects/structures/lavaland/ore_vent.dm b/code/game/objects/structures/lavaland/ore_vent.dm
index 08a4394346af945..70ab15427b75987 100644
--- a/code/game/objects/structures/lavaland/ore_vent.dm
+++ b/code/game/objects/structures/lavaland/ore_vent.dm
@@ -21,6 +21,8 @@
var/discovered = FALSE
/// Is this type of vent exempt from the map's vent budget/limit? Think the free iron/glass vent or boss vents. This also causes it to not roll for random mineral breakdown.
var/unique_vent = FALSE
+ /// Does this vent spawn a node drone when tapped? Currently unique to boss vents so try not to VV it.
+ var/spawn_drone_on_tap = TRUE
/// What icon_state do we use when the ore vent has been tapped?
var/icon_state_tapped = "ore_vent_active"
/// A weighted list of what minerals are contained in this vent, with weight determining how likely each mineral is to be picked in produced boulders.
@@ -40,9 +42,7 @@
/// What string do we use to warn the player about the excavation event?
var/excavation_warning = "Are you ready to excavate this ore vent?"
- ///Are we currently spawning mobs?
- var/spawning_mobs = FALSE
- /// A list of mobs that can be spawned by this vent during a wave defense event.
+ /// A list of mobs that can be spawned by this vent during a wave defense event.
var/list/defending_mobs = list(
/mob/living/basic/mining/goliath,
/mob/living/basic/mining/legion/spawner_made,
@@ -198,6 +198,34 @@
/obj/structure/ore_vent/proc/ore_quantity_function(ore_floor)
return SHEET_MATERIAL_AMOUNT * round(boulder_size * (log(rand(1 + ore_floor, 4 + ore_floor)) ** -1))
+/**
+ * This confirms that the user wants to start the wave defense event, and that they can start it.
+ */
+/obj/structure/ore_vent/proc/pre_wave_defense(mob/user, spawn_drone = TRUE)
+ if(tgui_alert(user, excavation_warning, "Begin defending ore vent?", list("Yes", "No")) != "Yes")
+ return FALSE
+ if(!can_interact(user))
+ return FALSE
+ if(!COOLDOWN_FINISHED(src, wave_cooldown) || node)
+ return FALSE
+ //This is where we start spitting out mobs.
+ Shake(duration = 3 SECONDS)
+ if(spawn_drone)
+ node = new /mob/living/basic/node_drone(loc)
+ node.arrive(src)
+ RegisterSignal(node, COMSIG_QDELETING, PROC_REF(handle_wave_conclusion))
+ particles = new /particles/smoke/ash()
+ for(var/i in 1 to 5) // Clears the surroundings of the ore vent before starting wave defense.
+ for(var/turf/closed/mineral/rock in oview(i))
+ if(istype(rock, /turf/open/misc/asteroid) && prob(35)) // so it's too common
+ new /obj/effect/decal/cleanable/rubble(rock)
+ if(prob(100 - (i * 15)))
+ rock.gets_drilled(user, FALSE)
+ if(prob(50))
+ new /obj/effect/decal/cleanable/rubble(rock)
+ sleep(0.6 SECONDS)
+ return TRUE
+
/**
* Starts the wave defense event, which will spawn a number of lavaland mobs based on the size of the ore vent.
* Called after the vent has been tapped by a scanning device.
@@ -221,7 +249,6 @@
wave_timer = 150 SECONDS
COOLDOWN_START(src, wave_cooldown, wave_timer)
addtimer(CALLBACK(src, PROC_REF(handle_wave_conclusion)), wave_timer)
- spawning_mobs = TRUE
icon_state = icon_state_tapped
update_appearance(UPDATE_ICON_STATE)
@@ -272,7 +299,7 @@
if(tapped)
balloon_alert_to_viewers("vent tapped!")
return
- if(!COOLDOWN_FINISHED(src, wave_cooldown))
+ if(!COOLDOWN_FINISHED(src, wave_cooldown) || node) //We're already defending the vent, so don't scan it again.
if(!scan_only)
balloon_alert_to_viewers("protect the node drone!")
return
@@ -302,27 +329,9 @@
return
if(scan_only)
return
- if(tgui_alert(user, excavation_warning, "Begin defending ore vent?", list("Yes", "No")) != "Yes")
- return
- if(!COOLDOWN_FINISHED(src, wave_cooldown))
- return
- //This is where we start spitting out mobs.
- Shake(duration = 3 SECONDS)
- node = new /mob/living/basic/node_drone(loc)
- node.arrive(src)
- RegisterSignal(node, COMSIG_QDELETING, PROC_REF(handle_wave_conclusion))
- particles = new /particles/smoke/ash()
-
- for(var/i in 1 to 5) // Clears the surroundings of the ore vent before starting wave defense.
- for(var/turf/closed/mineral/rock in oview(i))
- if(istype(rock, /turf/open/misc/asteroid) && prob(35)) // so it's too common
- new /obj/effect/decal/cleanable/rubble(rock)
- if(prob(100 - (i * 15)))
- rock.gets_drilled(user, FALSE)
- if(prob(50))
- new /obj/effect/decal/cleanable/rubble(rock)
- sleep(0.6 SECONDS)
+ if(!pre_wave_defense(user, spawn_drone_on_tap))
+ return
start_wave_defense()
/**
@@ -416,15 +425,15 @@
if(LARGE_VENT_TYPE)
boulder_size = BOULDER_SIZE_LARGE
if(mapload)
- SSore_generation.ore_vent_sizes["large"] += 1
+ GLOB.ore_vent_sizes["large"] += 1
if(MEDIUM_VENT_TYPE)
boulder_size = BOULDER_SIZE_MEDIUM
if(mapload)
- SSore_generation.ore_vent_sizes["medium"] += 1
+ GLOB.ore_vent_sizes["medium"] += 1
if(SMALL_VENT_TYPE)
boulder_size = BOULDER_SIZE_SMALL
if(mapload)
- SSore_generation.ore_vent_sizes["small"] += 1
+ GLOB.ore_vent_sizes["small"] += 1
else
boulder_size = BOULDER_SIZE_SMALL //Might as well set a default value
name = initial(name)
@@ -437,8 +446,8 @@
defending_mobs = list(
/mob/living/basic/mining/lobstrosity,
/mob/living/basic/mining/legion/snow/spawner_made,
+ /mob/living/basic/mining/wolf,
/mob/living/simple_animal/hostile/asteroid/polarbear,
- /mob/living/simple_animal/hostile/asteroid/wolf,
)
ore_vent_options = list(
SMALL_VENT_TYPE,
@@ -450,8 +459,8 @@
/mob/living/basic/mining/lobstrosity,
/mob/living/basic/mining/legion/snow/spawner_made,
/mob/living/basic/mining/ice_demon,
+ /mob/living/basic/mining/wolf,
/mob/living/simple_animal/hostile/asteroid/polarbear,
- /mob/living/simple_animal/hostile/asteroid/wolf,
)
ore_vent_options = list(
SMALL_VENT_TYPE = 3,
@@ -463,6 +472,7 @@
name = "menacing ore vent"
desc = "An ore vent, brimming with underground ore. This one has an evil aura about it. Better be careful."
unique_vent = TRUE
+ spawn_drone_on_tap = FALSE
boulder_size = BOULDER_SIZE_LARGE
mineral_breakdown = list( // All the riches of the world, eeny meeny boulder room.
/datum/material/iron = 1,
@@ -481,7 +491,7 @@
/mob/living/simple_animal/hostile/megafauna/dragon,
/mob/living/simple_animal/hostile/megafauna/colossus,
)
- excavation_warning = "Something big is nearby. Are you ABSOLUTELY ready to excavate this ore vent?"
+ excavation_warning = "Something big is nearby. Are you ABSOLUTELY ready to excavate this ore vent? A NODE drone will be deployed after threat is neutralized."
///What boss do we want to spawn?
var/summoned_boss = null
@@ -506,6 +516,8 @@
. += span_notice("[boss_string] is etched onto the side of the vent.")
/obj/structure/ore_vent/boss/start_wave_defense()
+ if(!COOLDOWN_FINISHED(src, wave_cooldown))
+ return
// Completely override the normal wave defense, and just spawn the boss.
var/mob/living/simple_animal/hostile/megafauna/boss = new summoned_boss(loc)
RegisterSignal(boss, COMSIG_LIVING_DEATH, PROC_REF(handle_wave_conclusion))
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 1935d6ba4179021..500060850b7f961 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -450,14 +450,10 @@ GLOBAL_LIST_EMPTY(crematoriums)
/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user)
var/list/icecreams = list()
for(var/mob/living/i_scream as anything in get_all_contents_type(/mob/living))
- var/obj/item/food/icecream/IC = new /obj/item/food/icecream(
- loc = null,
- prefill_flavours = list(ICE_CREAM_MOB = list(null, i_scream.name))
- )
- icecreams += IC
+ icecreams += new /obj/item/food/icecream(null, list(ICE_CREAM_MOB = list(null, i_scream.name)))
. = ..()
- for(var/obj/IC in icecreams)
- IC.forceMove(src)
+ for(var/obj/ice_cream as anything in icecreams)
+ ice_cream.forceMove(src)
/*
* Generic Tray
diff --git a/code/game/objects/structures/tank_holder.dm b/code/game/objects/structures/tank_holder.dm
index 9b5b33d8417eb76..5b7c9d2ed553481 100644
--- a/code/game/objects/structures/tank_holder.dm
+++ b/code/game/objects/structures/tank_holder.dm
@@ -142,3 +142,7 @@
/obj/structure/tank_holder/extinguisher/advanced
icon_state = "holder_foam_extinguisher"
tank = /obj/item/extinguisher/advanced
+
+/obj/structure/tank_holder/extinguisher/anti
+ icon_state = "holder_extinguisher"
+ tank = /obj/item/extinguisher/anti
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index b343cd85f50b193..edf7f2fc803c192 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -760,11 +760,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sink/kitchen, (-16))
open = !open
if(open)
layer = SIGN_LAYER
- set_density(FALSE)
set_opacity(FALSE)
else
layer = WALL_OBJ_LAYER
- set_density(TRUE)
if(opaque_closed)
set_opacity(TRUE)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 393cf79a3458efb..d98c1aface5c574 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -425,4 +425,10 @@
'sound/items/reel4.ogg',
'sound/items/reel5.ogg',
)
+ if(SFX_RATTLE)
+ soundin = pick(
+ 'sound/items/rattle1.ogg',
+ 'sound/items/rattle2.ogg',
+ 'sound/items/rattle3.ogg',
+ )
return soundin
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index 0d8f505e62337e7..c7bc4a76d58475c 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -143,15 +143,15 @@
return rand(1,5)
if(distance < VENT_PROX_VERY_HIGH)
- return 5
+ return ORE_WALL_VERY_HIGH
if(distance < VENT_PROX_HIGH)
- return 4
+ return ORE_WALL_HIGH
if(distance < VENT_PROX_MEDIUM)
- return 3
+ return ORE_WALL_MEDIUM
if(distance < VENT_PROX_LOW)
- return 2
+ return ORE_WALL_LOW
if(distance < VENT_PROX_FAR)
- return 1
+ return ORE_WALL_FAR
return 0
/turf/closed/mineral/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
@@ -323,7 +323,7 @@
var/turf/closed/mineral/M = T
M.turf_type = src.turf_type
M.mineralAmt = scale_ore_to_vent()
- SSore_generation.post_ore_random["[M.mineralAmt]"] += 1
+ GLOB.post_ore_random["[M.mineralAmt]"] += 1
src = M
M.levelupdate()
else
@@ -334,7 +334,7 @@
Change_Ore(path, FALSE)
Spread_Vein(path)
mineralAmt = scale_ore_to_vent()
- SSore_generation.post_ore_manual["[mineralAmt]"] += 1
+ GLOB.post_ore_manual["[mineralAmt]"] += 1
/turf/closed/mineral/random/high_chance
icon_state = "rock_highchance"
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 4e79d835271eb87..13bc0a43da69dbb 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -140,7 +140,7 @@
for(var/obj/O in src.contents) //Eject contents!
if(istype(O, /obj/structure/sign/poster))
var/obj/structure/sign/poster/P = O
- P.roll_and_drop(src)
+ INVOKE_ASYNC(P, TYPE_PROC_REF(/obj/structure/sign/poster, roll_and_drop), src)
if(decon_type)
ChangeTurf(decon_type, flags = CHANGETURF_INHERIT_AIR)
else
diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm
index 8f9e7b44aa600eb..8165b35a256b8b0 100644
--- a/code/game/turfs/open/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -249,7 +249,7 @@
/turf/open/lava/proc/can_burn_stuff(atom/movable/burn_target)
if(QDELETED(burn_target))
return LAVA_BE_IGNORING
- if(burn_target.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) //you're flying over it.
+ if(burn_target.movement_type & MOVETYPES_NOT_TOUCHING_GROUND || !burn_target.has_gravity()) //you're flying over it.
return LAVA_BE_IGNORING
if(isobj(burn_target))
@@ -268,7 +268,7 @@
var/mob/living/burn_living = burn_target
var/atom/movable/burn_buckled = burn_living.buckled
if(burn_buckled)
- if(burn_buckled.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
+ if(burn_buckled.movement_type & MOVETYPES_NOT_TOUCHING_GROUND || !burn_buckled.has_gravity())
return LAVA_BE_PROCESSING
if(isobj(burn_buckled))
var/obj/burn_buckled_obj = burn_buckled
diff --git a/code/game/world.dm b/code/game/world.dm
index 47e92316d942152..09ae2f810641fc1 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -119,7 +119,7 @@ GLOBAL_VAR(restart_counter)
// From a really fucking old commit (91d7150)
// I wanted to move it but I think this needs to be after /world/New is called but before any sleeps?
// - Dominion/Cyberboss
- GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
+ GLOB.timezoneOffset = world.timezone * 36000
// First possible sleep()
InitTgs()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index f2b7fb1aa3cedbe..9ca2ea8ff7dfb60 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -309,6 +309,10 @@ GLOBAL_PROTECT(admin_verbs_poll)
add_verb(src, /client/proc/play_web_sound)
if(rights & R_SPAWN)
add_verb(src, GLOB.admin_verbs_spawn)
+#ifdef MAP_TEST
+ remove_verb(src, /client/proc/enable_mapping_verbs)
+ add_verb(src, list(/client/proc/disable_mapping_verbs, GLOB.admin_verbs_debug_mapping))
+#endif
/client/proc/remove_admin_verbs()
remove_verb(src, list(
@@ -1226,4 +1230,3 @@ GLOBAL_PROTECT(admin_verbs_poll)
QDEL_NULL(segment.ai_controller)
segment.AddComponent(/datum/component/mob_chain, front = previous)
previous = segment
-
diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm
index b8f6737b1552e55..a54aafde916d3f5 100644
--- a/code/modules/admin/antag_panel.dm
+++ b/code/modules/admin/antag_panel.dm
@@ -100,6 +100,7 @@ GLOBAL_VAR(antag_prototypes)
out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"] "
out += "Assigned role: [assigned_role.title]. Edit "
out += "Faction and special role: [special_role] "
+ out += "Show Teams
"
var/special_statuses = get_special_statuses()
if(length(special_statuses))
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index e98016df74f3dd3..9d2525ed8fa2d5f 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -232,7 +232,7 @@ GLOBAL_PROTECT(href_token)
return VALID_2FA_CONNECTION
if (!SSdbcore.Connect())
- if (verify_backup_data(client))
+ if (verify_backup_data(client) || (client.ckey in GLOB.protected_admins))
return VALID_2FA_CONNECTION
else
return list(FALSE, null)
diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm
index 2938e5f4fd2f06a..3ed57df1dcacdaa 100644
--- a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm
+++ b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm
@@ -17,6 +17,12 @@
return
return ..()
+/obj/structure/sign/poster/abductor/attackby(obj/item/tool, mob/user, params)
+ if(tool.toolspeed >= 0.2)
+ balloon_alert(user, "tool too weak!")
+ return FALSE
+ return ..()
+
/obj/structure/sign/poster/abductor/random
name = "random abductor poster"
icon_state = "random_abductor"
diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm
index 38349dce2ac410d..214825f4c73a28b 100644
--- a/code/modules/antagonists/brother/brother.dm
+++ b/code/modules/antagonists/brother/brother.dm
@@ -77,22 +77,23 @@
flashed.balloon_alert(source, "[flashed.p_they()] resist!")
return
- flashed.mind.add_antag_datum(/datum/antagonist/brother, team)
+ if (!team.add_brother(flashed, key_name(source))) // Shouldn't happen given the former, more specific checks but just in case
+ flashed.balloon_alert(source, "failed!")
+ return
+
source.log_message("converted [key_name(flashed)] to blood brother", LOG_ATTACK)
flashed.log_message("was converted by [key_name(source)] to blood brother", LOG_ATTACK)
- log_game("[key_name(flashed)] converted [key_name(source)] to blood brother", list(
- "flashed" = flashed,
- "victim" = source,
+ log_game("[key_name(flashed)] was made into a blood brother by [key_name(source)]", list(
+ "converted" = flashed,
+ "converted by" = source,
))
-
- flashed.balloon_alert(source, "converted")
- to_chat(source, span_notice("[span_bold("[flashed]")] has been converted to aide you as your Brother!"))
flash.burn_out()
flashed.mind.add_memory( \
/datum/memory/recruited_by_blood_brother, \
protagonist = flashed, \
antagonist = owner.current, \
)
+ flashed.balloon_alert(source, "converted")
UnregisterSignal(source, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON)
source.RemoveComponentSource(REF(src), /datum/component/can_flash_from_behind)
@@ -172,6 +173,34 @@
if (prob(10))
brothers_left += 1
+/datum/team/brother_team/add_member(datum/mind/new_member)
+ . = ..()
+ if (!new_member.has_antag_datum(/datum/antagonist/brother))
+ add_brother(new_member.current)
+
+/datum/team/brother_team/remove_member(datum/mind/member)
+ if (!(member in members))
+ return
+ . = ..()
+ member.remove_antag_datum(/datum/antagonist/brother)
+ if (isnull(member.current))
+ return
+ for (var/datum/mind/brother_mind as anything in members)
+ to_chat(brother_mind, span_warning("[span_bold("[member.current.real_name]")] is no longer your brother!"))
+ update_name()
+
+/// Adds a new brother to the team
+/datum/team/brother_team/proc/add_brother(mob/living/new_brother, source)
+ if (isnull(new_brother) || isnull(new_brother.mind) || !GET_CLIENT(new_brother) || new_brother.mind.has_antag_datum(/datum/antagonist/brother))
+ return FALSE
+
+ for (var/datum/mind/brother_mind as anything in members)
+ if (brother_mind == new_brother.mind)
+ continue
+ to_chat(brother_mind, span_notice("[span_bold("[new_brother.real_name]")] has been converted to aid you as your brother!"))
+ new_brother.mind.add_antag_datum(/datum/antagonist/brother, src)
+ return TRUE
+
/datum/team/brother_team/proc/update_name()
var/list/last_names = list()
for(var/datum/mind/team_minds as anything in members)
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 5fd8b5d9ee4e998..16b3def11223e69 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -405,7 +405,7 @@
if(IS_CULTIST(user))
user.visible_message(span_warning("[user] holds up [user.p_their()] hand, which explodes in a flash of red light!"), \
span_cultitalic("You attempt to stun [target] with the spell!"))
- user.mob_light(range = 3, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS)
+ user.mob_light(range = 1.1, power = 2, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS)
if(IS_HERETIC(target))
to_chat(user, span_warning("Some force greater than you intervenes! [target] is protected by the Forgotten Gods!"))
to_chat(target, span_warning("You are protected by your faith to the Forgotten Gods."))
diff --git a/code/modules/antagonists/heretic/items/eldritch_painting.dm b/code/modules/antagonists/heretic/items/eldritch_painting.dm
index 5aa63407dc6ef6d..5302fc1c9c148d8 100644
--- a/code/modules/antagonists/heretic/items/eldritch_painting.dm
+++ b/code/modules/antagonists/heretic/items/eldritch_painting.dm
@@ -92,7 +92,7 @@
if(!IS_HERETIC(examiner))
to_chat(examiner, span_hypnophrase("Respite, for now...."))
examiner.mob_mood.mood_events.Remove("eldritch_weeping")
- examiner.add_mood_event("weeping_withdrawl", /datum/mood_event/eldritch_painting/weeping_withdrawl)
+ examiner.add_mood_event("weeping_withdrawal", /datum/mood_event/eldritch_painting/weeping_withdrawal)
return
to_chat(examiner, span_notice("Oh, what arts! Just gazing upon it clears your mind."))
diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
index ff150f0ac0fb4e7..1124d9a44403e95 100644
--- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
@@ -42,7 +42,7 @@
/datum/heretic_knowledge/ashen_grasp
name = "Grasp of Ash"
- desc = "Your Mansus Grasp will burn the eyes of the victim, causing damage and blindness."
+ desc = "Your Mansus Grasp will burn the eyes of the victim, damaging them and blurring their vision."
gain_text = "The Nightwatcher was the first of them, his treason started it all. \
Their lantern, expired to ash - their watch, absent."
next_knowledge = list(/datum/heretic_knowledge/spell/ash_passage)
@@ -70,7 +70,7 @@
/datum/heretic_knowledge/spell/ash_passage
name = "Ashen Passage"
- desc = "Grants you Ashen Passage, a silent but short range jaunt."
+ desc = "Grants you Ashen Passage, a spell that lets you phase out of reality and traverse a short distance, passing though any walls."
gain_text = "He knew how to walk between the planes."
next_knowledge = list(
/datum/heretic_knowledge/mark/ash_mark,
@@ -181,6 +181,7 @@
When completed, you become a harbinger of flames, gaining two abilites. \
Cascade, which causes a massive, growing ring of fire around you, \
and Oath of Flame, causing you to passively create a ring of flames as you walk. \
+ Some ashen spells you already knew will be empowered as well. \
You will also become immune to flames, space, and similar environmental hazards."
gain_text = "The Watch is dead, the Nightwatcher burned with it. Yet his fire burns evermore, \
for the Nightwatcher brought forth the rite to mankind! His gaze continues, as now I am one with the flames, \
diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
index 1a16f2e9f9321ce..09efb5c2eb8f44f 100644
--- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
@@ -42,7 +42,8 @@
/datum/heretic_knowledge/cosmic_grasp
name = "Grasp of Cosmos"
- desc = "Your Mansus Grasp will give people a star mark (cosmic ring) and create a cosmic field where you stand."
+ desc = "Your Mansus Grasp will give people a star mark (cosmic ring) and create a cosmic field where you stand. \
+ People with a star mark can not pass cosmic fields."
gain_text = "Some stars dimmed, others' magnitude increased. \
With newfound strength I could channel the nebula's power into myself."
next_knowledge = list(/datum/heretic_knowledge/spell/cosmic_runes)
@@ -82,7 +83,8 @@
/datum/heretic_knowledge/mark/cosmic_mark
name = "Mark of Cosmos"
desc = "Your Mansus Grasp now applies the Mark of Cosmos. The mark is triggered from an attack with your Cosmic Blade. \
- When triggered, the victim is returned to the location where the mark was originally applied to them. \
+ When triggered, the victim is returned to the location where the mark was originally applied to them, \
+ leaving a cosmic field in their place. \
They will then be paralyzed for 2 seconds."
gain_text = "The Beast now whispered to me occasionally, only small tidbits of their circumstances. \
I can help them, I have to help them."
@@ -98,8 +100,7 @@
name = "Star Touch"
desc = "Grants you Star Touch, a spell which places a star mark upon your target \
and creates a cosmic field at your feet and to the turfs next to you. Targets which already have a star mark \
- will be forced to sleep for 4 seconds. When the victim is hit it also creates a beam that \
- deals a bit of fire damage and damages the cells. \
+ will be forced to sleep for 4 seconds. When the victim is hit it also creates a beam that burns them. \
The beam lasts a minute, until the beam is obstructed or until a new target has been found."
gain_text = "After waking in a cold sweat I felt a palm on my scalp, a sigil burned onto me. \
My veins now emitted a strange purple glow, the Beast knows I will surpass its expectations."
@@ -110,7 +111,7 @@
/datum/heretic_knowledge/spell/star_blast
name = "Star Blast"
- desc = "Fires a projectile that moves very slowly and creates cosmic fields on impact. \
+ desc = "Fires a projectile that moves very slowly, raising a short-lived wall of cosmic fields where it goes. \
Anyone hit by the projectile will receive burn damage, a knockdown, and give people in a three tile range a star mark."
gain_text = "The Beast was behind me now at all times, with each sacrifice words of affirmation coursed through me."
next_knowledge = list(
@@ -243,7 +244,8 @@
You can also give it commands through speech. \
The Star Gazer is a strong ally who can even break down reinforced walls. \
The Star Gazer has an aura that will heal you and damage opponents. \
- Star Touch can now teleport you to the Star Gazer when activated in your hand."
+ Star Touch can now teleport you to the Star Gazer when activated in your hand. \
+ Your cosmic expansion spell and your blades also become greatly empowered."
gain_text = "The Beast held out its hand, I grabbed hold and they pulled me to them. Their body was towering, but it seemed so small and feeble after all their tales compiled in my head. \
I clung on to them, they would protect me, and I would protect it. \
I closed my eyes with my head laid against their form. I was safe. \
diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
index d6fcb2f43a25708..96d67f680adbb19 100644
--- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
@@ -122,6 +122,7 @@
/datum/heretic_knowledge/limited_amount/flesh_ghoul
name = "Imperfect Ritual"
desc = "Allows you to transmute a corpse and a poppy to create a Voiceless Dead. \
+ The corpse does not need to have a soul. \
Voiceless Dead are mute ghouls and only have 50 health, but can use Bloody Blades effectively. \
You can only create two at a time."
gain_text = "I found notes of a dark ritual, unfinished... yet still, I pushed forward."
diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
index 9f80f47b0ae4878..0727b86bb668e4f 100644
--- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
@@ -92,9 +92,12 @@
/datum/heretic_knowledge/key_ring
name = "Key Keeper’s Burden"
desc = "Allows you to transmute a wallet, an iron rod, and an ID card to create an Eldritch Card. \
- It functions the same as an ID Card, but attacking it with an ID card fuses it and gains its access. \
- You can use it in-hand to change its form to a card you fused. \
- Does not preserve the card used in the ritual."
+ Hit a pair of airlocks with it to create a pair of portals, which will teleport you between them, but teleport non-heretics randomly. \
+ You can ctrl-click the card to invert this behavior for created portals. \
+ Each card may only sustain a single pair of portals at the same time. \
+ It also functions and appears the same as a regular ID Card. \
+ Attacking it with a normal ID card consumes it and gains its access, and you can use it in-hand to change its appearance to a card you fused. \
+ Does not preserve the card originally used in the ritual."
gain_text = "The Keeper sneered. \"These plastic rectangles are a mockery of keys, and I curse every door that desires them.\""
required_atoms = list(
/obj/item/storage/wallet = 1,
@@ -186,7 +189,8 @@
desc = "The ascension ritual of the Path of Knock. \
Bring 3 corpses without organs in their torso to a transmutation rune to complete the ritual. \
When completed, you gain the ability to transform into empowered eldritch creatures \
- and in addition, create a tear to the Labyrinth's heart; \
+ and your keyblades will become even deadlier. \
+ In addition, you will create a tear to the Labyrinth's heart; \
a tear in reality located at the site of this ritual. \
Eldritch creatures will endlessly pour from this rift \
who are bound to obey your instructions."
diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
index d7d1bd3bf22a751..723599ad262f5ae 100644
--- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
@@ -45,8 +45,8 @@
/datum/heretic_knowledge/moon_grasp
name = "Grasp of Lunacy"
- desc = "Your Mansus Grasp will cause them to hallucinate everyone as lunar mass, \
- and hides your identity for a short dur ation."
+ desc = "Your Mansus Grasp will cause your victims to hallucinate everyone as lunar mass, \
+ and hides your identity for a short duration."
gain_text = "The troupe on the side of the moon showed me truth, and I took it."
next_knowledge = list(/datum/heretic_knowledge/spell/moon_smile)
cost = 1
@@ -85,9 +85,8 @@
/datum/heretic_knowledge/mark/moon_mark
name = "Mark of Moon"
- desc = "Your Mansus Grasp now applies the Mark of Moon. The mark is triggered from an attack with your Moon Blade. \
- When triggered, the victim is confused, and when the mark is applied they are pacified \
- until attacked."
+ desc = "Your Mansus Grasp now applies the Mark of Moon, pacifying the victim until attacked. \
+ The mark can also be triggered from an attack with your Moon Blade, leaving the victim confused."
gain_text = "The troupe on the moon would dance all day long \
and in that dance the moon would smile upon us \
but when the night came its smile would dull forced to gaze on the earth."
@@ -112,9 +111,9 @@
/datum/heretic_knowledge/moon_amulette
name = "Moonlight Amulette"
- desc = "Allows you to transmute 2 sheets of glass, a heart and a tie \
- if the item is used on someone with low sanity they go berserk attacking everyone \
- , if their sanity isnt low enough it decreases their mood."
+ desc = "Allows you to transmute 2 sheets of glass, a heart and a tie to create a Moonlight Amulette. \
+ If the item is used on someone with low sanity they go berserk attacking everyone, \
+ if their sanity isn't low enough it decreases their mood."
gain_text = "At the head of the parade he stood, the moon condensed into one mass, a reflection of the soul."
next_knowledge = list(
/datum/heretic_knowledge/blade_upgrade/moon,
@@ -153,9 +152,9 @@
/datum/heretic_knowledge/spell/moon_ringleader
name = "Ringleaders Rise"
- desc = "Grants you Ringleaders Rise, an aoe spell that deals more brain damage the lower the sanity of everyone in the AoE,\
- causes hallucinations with those who have less sanity getting more. \
- If their sanity is low enough turns them insane, the spell then halves their sanity."
+ desc = "Grants you Ringleaders Rise, an AoE spell that deals more brain damage the lower the sanity of everyone in the AoE \
+ and causes hallucinations, with those who have less sanity getting more. \
+ If their sanity is low enough this turns them insane, the spell then halves their sanity."
gain_text = "I grabbed his hand and we rose, those who saw the truth rose with us. \
The ringleader pointed up and the dim light of truth illuminated us further."
next_knowledge = list(
@@ -170,8 +169,8 @@
name = "The Last Act"
desc = "The ascension ritual of the Path of Moon. \
Bring 3 corpses with more than 50 brain damage to a transmutation rune to complete the ritual. \
- When completed, you become a harbinger of madness gaining and aura of passive sanity decrease \
- , confusion increase and if their sanity is low enough brain damage and blindness. \
+ When completed, you become a harbinger of madness gaining and aura of passive sanity decrease, \
+ confusion increase and, if their sanity is low enough, brain damage and blindness. \
1/5th of the crew will turn into acolytes and follow your command, they will all recieve moonlight amulettes."
gain_text = "We dived down towards the crowd, his soul splitting off in search of greater venture \
for where the Ringleader had started the parade, I shall continue it unto the suns demise \
diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
index 4ce8f9de9c93601..a4810c706c1186f 100644
--- a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
@@ -21,7 +21,7 @@
name = "Curse of Paralysis"
desc = "Allows you to transmute a hatchet and both a left and right leg to cast a curse of immobility on a crew member. \
While cursed, the victim will be unable to walk. You can additionally supply an item that a victim has touched \
- or is covered in the victim's blood to empower the curse."
+ or is covered in the victim's blood to make the curse last longer."
gain_text = "The flesh of humanity is weak. Make them bleed. Show them their fragility."
next_knowledge = list(
/datum/heretic_knowledge/mad_mask,
@@ -58,8 +58,8 @@
/datum/heretic_knowledge/summon/ashy
name = "Ashen Ritual"
- desc = "Allows you to transmute a head, a pile of ash, and a book to create an Ash Man. \
- Ash Men have a short range jaunt and the ability to cause bleeding in foes at range. \
+ desc = "Allows you to transmute a head, a pile of ash, and a book to create an Ash Spirit. \
+ Ash Spirits have a short range jaunt and the ability to cause bleeding in foes at range. \
They also have the ability to create a ring of fire around themselves for a length of time."
gain_text = "I combined my principle of hunger with my desire for destruction. The Marshal knew my name, and the Nightwatcher gazed on."
next_knowledge = list(
diff --git a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
index 470d98e178b7e8b..14a003ce11c0b70 100644
--- a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
@@ -36,8 +36,9 @@
/datum/heretic_knowledge/eldritch_coin
name = "Eldritch Coin"
desc = "Allows you to transmute a sheet of plasma and a diamond to create an Eldritch Coin. \
- The coin will open or close nearby doors when landing on heads and bolt or unbolt nearby doors \
- when landing on tails. If the coin gets inserted into an airlock it emags the door destroying the coin."
+ The coin will open or close nearby doors when landing on heads and toggle their bolts \
+ when landing on tails. If you insert the coin into an airlock, it will be consumed \
+ to fry its electronics, opening the airlock permanently unless bolted. "
gain_text = "The Mansus is a place of all sorts of sins. But greed held a special role."
next_knowledge = list(
/datum/heretic_knowledge/spell/cosmic_expansion,
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
index e2825c6db2869cc..74013f2b0bd1da6 100644
--- a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
@@ -2,7 +2,8 @@
/datum/heretic_knowledge/spell/opening_blast
name = "Wave Of Desperation"
desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \
- It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby."
+ It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby. \
+ However, you will fall unconscious a short time after casting this spell."
gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power."
next_knowledge = list(
/datum/heretic_knowledge/summon/raw_prophet,
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
index 737e0f08f40a1cc..f1dd564310be502 100644
--- a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
@@ -16,10 +16,10 @@
/datum/heretic_knowledge/unfathomable_curio
name = "Unfathomable Curio"
- desc = "Allows you to transmute 3 rods, a brain and a belt into an Unfathomable Curio\
- , a belt that can hold blades and items for rituals. Whilst worn will also \
+ desc = "Allows you to transmute 3 rods, lungs and any belt into an Unfathomable Curio\
+ , a belt that can hold blades and items for rituals. Whilst worn it will also \
veil you, allowing you to take 5 hits without suffering damage, this veil will recharge very slowly \
- outside of combat. When examined the examiner will suffer brain damage and blindness."
+ outside of combat."
gain_text = "The mansus holds many a curio, some are not meant for the mortal eye."
next_knowledge = list(
/datum/heretic_knowledge/spell/burglar_finesse,
@@ -38,12 +38,12 @@
name = "Unsealed Arts"
desc = "Allows you to transmute a canvas and an additional item to create a piece of art, these paintings \
have different effects depending on the additional item added. Possible paintings: \
- The sister and He Who Wept: Eyes. When a non-heretic looks at the painting they will begin to hallucinate everyone as heretics. \
- The First Desire: Any bodypart. Increases the hunger of non-heretics, when examined drops an organ or body part at your feet. \
- Great chaparral over rolling hills: Any grown food. Spreads kudzu when placed, when examined grants a flower. \
- Lady out of gates: Gloves. Causes non-heretics to scratch themselves, when examined removes all your mutations. \
- Climb over the rusted mountain: Trash. Causes non-heretics to rust the floor they walk on. \
- These effects are mitigated for a few minutes when a non-heretic suffering an effect examines the painting that caused the effect."
+ The sister and He Who Wept: Eyes. Clears your own mind, but curses non-heretics with hallucinations. \
+ The First Desire: Any bodypart. Supplies you with random organs, but curses non-heretics with a hunger for flesh. \
+ Great chaparral over rolling hills: Any grown food. Spreads kudzu when placed and examined by non-heretics. Also supplies you with poppies and harebells. \
+ Lady out of gates: Gloves. Clears your mutations, but mutates non-heretics and curses them with scratching. \
+ Climb over the rusted mountain: Trash. Curses non-heretics to rust the floor they walk on. \
+ Non-heretics can counter most of these effects by examining one of these paintings."
gain_text = "A wind of inspiration blows through me, past the walls and past the gate inspirations lie, yet to be depicted. \
They yearn for mortal eyes again, and I shall grant that wish."
next_knowledge = list(
diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
index 2dbb44ea4eb7ed9..3d326b4a9af452d 100644
--- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
@@ -44,7 +44,7 @@
name = "Curse of Corrosion"
desc = "Allows you to transmute wirecutters, a pool of vomit, and a heart to cast a curse of sickness on a crew member. \
While cursed, the victim will repeatedly vomit while their organs will take constant damage. You can additionally supply an item \
- that a victim has touched or is covered in the victim's blood to empower the curse."
+ that a victim has touched or is covered in the victim's blood to make the curse last longer."
gain_text = "The body of humanity is temporary. Their weaknesses cannot be stopped, like iron falling to rust. Show them all."
next_knowledge = list(
/datum/heretic_knowledge/spell/area_conversion,
diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
index 643fd434af7b5a0..e044eee8619ef8b 100644
--- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
@@ -144,7 +144,8 @@
name = "Maid in the Mirror"
desc = "Allows you to transmute five sheets of titanium, a flash, a suit of armor, and a pair of lungs \
to create a Maid in the Mirror. Maid in the Mirrors are decent combatants that can become incorporeal by \
- phasing in and out of the mirror realm, serving as powerful scouts and ambushers."
+ phasing in and out of the mirror realm, serving as powerful scouts and ambushers. \
+ However, they are weak to mortal gaze and take damage by being examined."
gain_text = "Within each reflection, lies a gateway into an unimaginable world of colors never seen and \
people never met. The ascent is glass, and the walls are knives. Each step is blood, if you do not have a guide."
next_knowledge = list(
diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm
index 801104dddf9fc29..e80d08911848cb5 100644
--- a/code/modules/antagonists/heretic/magic/apetravulnera.dm
+++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm
@@ -5,7 +5,7 @@
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
- button_icon_state = "cleave"
+ button_icon_state = "apetra_vulnera"
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
@@ -23,7 +23,7 @@
/datum/action/cooldown/spell/pointed/apetra_vulnera/cast(mob/living/carbon/human/cast_on)
. = ..()
-
+
if(IS_HERETIC_OR_MONSTER(cast_on))
return FALSE
@@ -44,7 +44,7 @@
a_limb_got_damaged = TRUE
var/datum/wound/slash/crit_wound = new wound_type()
crit_wound.apply_wound(bodypart)
-
+
if(!a_limb_got_damaged)
var/datum/wound/slash/crit_wound = new wound_type()
crit_wound.apply_wound(pick(cast_on.bodyparts))
@@ -53,7 +53,7 @@
span_danger("[cast_on]'s scratches and bruises are torn open by an unholy force!"),
span_danger("Your scratches and bruises are torn open by some horrible unholy force!")
)
-
+
new /obj/effect/temp_visual/cleave(get_turf(cast_on))
return TRUE
diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
index 18e8d26fecc60fe..e792dc116da6fdc 100644
--- a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
+++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
@@ -6,6 +6,8 @@
cooldown_time = 20 SECONDS
convert_damage = FALSE
die_with_shapeshifted_form = FALSE
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "lock_ascension"
possible_shapes = list(
/mob/living/basic/heretic_summon/ash_spirit,
/mob/living/basic/heretic_summon/raw_prophet/ascended,
diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm
index 29fcecf076fb059..86ff285001917b9 100644
--- a/code/modules/antagonists/heretic/magic/caretaker.dm
+++ b/code/modules/antagonists/heretic/magic/caretaker.dm
@@ -6,8 +6,8 @@
and you can be removed from it upon contact with antimagical artifacts."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
- button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- button_icon_state = "ninja_cloak"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "caretaker"
sound = 'sound/effects/curse2.ogg'
school = SCHOOL_FORBIDDEN
diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
index 5115a2181fa9110..4af3b94b44f34d9 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
@@ -1,6 +1,7 @@
/datum/action/cooldown/spell/cosmic_rune
name = "Cosmic Rune"
- desc = "Creates a cosmic rune at your position, only two can exist at a time. Invoking one rune transports you to the other."
+ desc = "Creates a cosmic rune at your position, only two can exist at a time. Invoking one rune transports you to the other. \
+ Anyone with a star mark gets transported along with you."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm
index 409e55bf9261acc..3b7f1d007cd6e18 100644
--- a/code/modules/antagonists/heretic/magic/moon_parade.dm
+++ b/code/modules/antagonists/heretic/magic/moon_parade.dm
@@ -1,6 +1,6 @@
/datum/action/cooldown/spell/pointed/projectile/moon_parade
name = "Lunar parade"
- desc = "This unleashes the parade towards a target."
+ desc = "This unleashes the parade, making everyone in its way join it and suffer hallucinations."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/heretic/magic/moon_ringleader.dm b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
index 45d3285a876dade..3c0b1d2aedb52cd 100644
--- a/code/modules/antagonists/heretic/magic/moon_ringleader.dm
+++ b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
@@ -1,7 +1,8 @@
/datum/action/cooldown/spell/aoe/moon_ringleader
name = "Ringleaders Rise"
- desc = "Big AoE spell that deals more brain damage the lower the sanity of everyone in the AoE and it also causes hallucinations with those who have less sanity getting more. \
- If their sanity is low enough they snap and go insane, the spell then halves their sanity."
+ desc = "Big AoE spell that deals brain damage and causes hallucinations to everyone in the AoE. \
+ The worse their sanity, the stronger this spell becomes. \
+ If their sanity is low enough, they even snap and go insane, and the spell then further halves their sanity."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
index 64638d7103b17b7..4e37f5db17fed5e 100644
--- a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
+++ b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
@@ -1,6 +1,6 @@
/datum/action/cooldown/spell/aoe/fiery_rebirth
name = "Nightwatcher's Rebirth"
- desc = "A spell that extinguishes you drains nearby heathens engulfed in flames of their life force, \
+ desc = "A spell that extinguishes you and drains nearby heathens engulfed in flames of their life force, \
healing you for each victim drained. Those in critical condition \
will have the last of their vitality drained, killing them."
background_icon_state = "bg_heretic"
diff --git a/code/modules/antagonists/heretic/magic/rust_charge.dm b/code/modules/antagonists/heretic/magic/rust_charge.dm
index d5427cf3762622d..56054bd56fdd851 100644
--- a/code/modules/antagonists/heretic/magic/rust_charge.dm
+++ b/code/modules/antagonists/heretic/magic/rust_charge.dm
@@ -1,7 +1,9 @@
// Rust charge, a charge action that can only be started on rust (and only destroys rust tiles)
/datum/action/cooldown/mob_cooldown/charge/rust
name = "Rust Charge"
- desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge. As it is the rust that empoweres you for this ability, no focus is needed"
+ desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, \
+ will deal high damage to others and rust around you during the charge. \
+ As it is the rust that empowers you with this ability, no focus is needed."
charge_distance = 10
charge_damage = 50
cooldown_time = 45 SECONDS
diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm
index 212e90535d6c7ce..48fdf2f26934b06 100644
--- a/code/modules/antagonists/heretic/magic/star_blast.dm
+++ b/code/modules/antagonists/heretic/magic/star_blast.dm
@@ -1,6 +1,6 @@
/datum/action/cooldown/spell/pointed/projectile/star_blast
name = "Star Blast"
- desc = "This spell fires a disk with cosmic energies at a target."
+ desc = "This spell fires a disk with cosmic energies at a target, spreading the star mark."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/heretic/magic/star_touch.dm b/code/modules/antagonists/heretic/magic/star_touch.dm
index 9037d07295a9446..89c5d02e7d4981c 100644
--- a/code/modules/antagonists/heretic/magic/star_touch.dm
+++ b/code/modules/antagonists/heretic/magic/star_touch.dm
@@ -1,7 +1,8 @@
/datum/action/cooldown/spell/touch/star_touch
name = "Star Touch"
- desc = "Marks someone with a star mark or puts someone with a star mark to sleep for 4 seconds, removing the star mark. \
- You and your target are linked with a cosmic ray, burning them for up to a minute, or \
+ desc = "Manifests cosmic fields on tiles next to you while marking the victim with a star mark \
+ or consuming an already present star mark to put them to sleep for 4 seconds. \
+ They will then be linked to you with a cosmic ray, burning them for up to a minute, or \
until they can escape your sight. Star Touch can also remove Cosmic Runes, or teleport you \
to your Star Gazer when used on yourself."
background_icon_state = "bg_heretic"
diff --git a/code/modules/antagonists/heretic/status_effects/debuffs.dm b/code/modules/antagonists/heretic/status_effects/debuffs.dm
index 08839fa8f10583c..7037d1cc3778b6f 100644
--- a/code/modules/antagonists/heretic/status_effects/debuffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/debuffs.dm
@@ -280,7 +280,7 @@
/datum/status_effect/moon_converted/on_remove()
// Span warning and unconscious so they realize they aren't evil anymore
- to_chat(owner, span_warning("Your mind is cleared from the effect of the manus, your alligiences are as they were before"))
+ to_chat(owner, span_warning("Your mind is cleared from the effect of the mansus, your alligiences are as they were before"))
REMOVE_TRAIT(owner, TRAIT_MUTE, REF(src))
owner.AdjustUnconscious(5 SECONDS, ignore_canstun = FALSE)
owner.log_message("[owner] is no longer insane.", LOG_GAME)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm
index 72c51f14b2b9985..c318679b4f6febf 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm
@@ -26,7 +26,7 @@
/obj/item/disk/nuclear/Initialize(mapload)
. = ..()
- AddElement(/datum/element/bed_tuckable, 6, -6, 0)
+ AddElement(/datum/element/bed_tuckable, mapload, 6, -6, 0)
AddComponent(/datum/component/stationloving, !fake)
if(!fake)
diff --git a/code/modules/antagonists/nukeop/outfits.dm b/code/modules/antagonists/nukeop/outfits.dm
index a3c97a764688bc5..e6a3fe6623d7f9d 100644
--- a/code/modules/antagonists/nukeop/outfits.dm
+++ b/code/modules/antagonists/nukeop/outfits.dm
@@ -77,6 +77,7 @@
backpack_contents = list(
/obj/item/gun/ballistic/automatic/pistol/clandestine = 1,
/obj/item/pen/edagger = 1,
+ /obj/item/ammo_box/magazine/m12g = 3,
)
/datum/outfit/syndicate/full/plasmaman
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm
index 92489145fda9740..85267c0333c451a 100644
--- a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm
@@ -148,6 +148,7 @@
color = COLOR_PALE_GREEN
light_range = 2
light_color = COLOR_PALE_GREEN
+ resistance_flags = parent_type::resistance_flags | SHUTTLE_CRUSH_PROOF
/// Who are we reviving?
var/mob/living/corpse
/// Who if anyone is playing as them?
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index 5319d4465e2ab9a..659bcec50cf9765 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -171,7 +171,7 @@
visible_message(span_danger("[user] blinds [flashed] with the flash!"), span_userdanger("[user] blinds you with the flash!"))
//easy way to make sure that you can only long stun someone who is facing in your direction
flashed.adjustStaminaLoss(rand(80, 120) * (1 - (deviation * 0.5)))
- flashed.Paralyze(rand(25, 50) * (1 - (deviation * 0.5)))
+ flashed.Knockdown(rand(25, 50) * (1 - (deviation * 0.5)))
SEND_SIGNAL(user, COMSIG_MOB_SUCCESSFUL_FLASHED_CARBON, flashed, src, deviation)
else if(user)
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index 87523034f3ecc1c..cfb87ce7cb3fed0 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -184,7 +184,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
if(amount <= 0)
return null
var/ratio = amount / sum
- var/datum/gas_mixture/removed = new type
+ var/datum/gas_mixture/removed = new type(volume)
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
@@ -206,7 +206,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
ratio = min(ratio, 1)
var/list/cached_gases = gases
- var/datum/gas_mixture/removed = new type
+ var/datum/gas_mixture/removed = new type(volume)
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index be455ea6d47093d..d42618e9ee99dab 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -54,6 +54,10 @@
///Whether it can be painted
var/paintable = TRUE
+ ///Whether it will generate cap sprites when hidden
+ var/has_cap_visuals = FALSE
+ ///Cap overlay that is being added to turf's `vis_contents`, `null` if pipe was never hidden or has no valid connections
+ var/obj/effect/overlay/cap_visual/cap_overlay
///Is the thing being rebuilt by SSair or not. Prevents list bloat
var/rebuilding = FALSE
@@ -106,6 +110,10 @@
if(isturf(loc))
turf_loc = loc
turf_loc.add_blueprints_preround(src)
+
+ if(hide)
+ RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(on_hide))
+
SSspatial_grid.add_grid_awareness(src, SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
SSspatial_grid.add_grid_membership(src, turf_loc, SPATIAL_GRID_CONTENTS_TYPE_ATMOS)
if(init_processing)
@@ -119,11 +127,22 @@
SSair.stop_processing_machine(src)
SSair.rebuild_queue -= src
- if(pipe_vision_img)
- qdel(pipe_vision_img)
+ QDEL_NULL(pipe_vision_img)
+ QDEL_NULL(cap_overlay)
return ..()
- //return QDEL_HINT_FINDREFERENCE
+
+/**
+ * Handler for `COMSIG_OBJ_HIDE`, connects only if `hide` is set to `TRUE`. Calls `update_cap_visuals` on pipe and its connected nodes
+ */
+/obj/machinery/atmospherics/proc/on_hide(datum/source, underfloor_accessibility)
+ SHOULD_CALL_PARENT(TRUE)
+ SIGNAL_HANDLER
+
+ for(var/obj/machinery/atmospherics/node in nodes)
+ node.update_cap_visuals()
+
+ update_cap_visuals()
/**
* Run when you update the conditions in which an /atom might want to start reacting to its turf's air
@@ -205,8 +224,9 @@
update_appearance()
/obj/machinery/atmospherics/update_icon()
- . = ..()
update_layer()
+ update_cap_visuals()
+ return ..()
/**
* Find a connecting /obj/machinery/atmospherics in specified direction, called by relaymove()
@@ -616,6 +636,52 @@
/obj/machinery/atmospherics/proc/update_layer()
return
+/**
+ * Handles cap overlay addition and removal, won't do anything if `has_cap_visuals` is set to `FALSE`
+ */
+/obj/machinery/atmospherics/proc/update_cap_visuals()
+ if(!has_cap_visuals)
+ return
+
+ cap_overlay?.moveToNullspace()
+
+ if(!HAS_TRAIT(src, TRAIT_UNDERFLOOR))
+ return
+
+ var/connections = NONE
+ for(var/obj/machinery/atmospherics/node in nodes)
+ if(HAS_TRAIT(node, TRAIT_UNDERFLOOR))
+ continue
+
+ if(isplatingturf(get_turf(node)))
+ continue
+
+ var/connected_dir = get_dir(src, node)
+ connections |= connected_dir
+
+ if(connections == NONE)
+ return
+
+ var/bitfield = CARDINAL_TO_PIPECAPS(connections) | (~connections) & ALL_CARDINALS
+ var/turf/our_turf = get_turf(src)
+
+ if(isnull(cap_overlay))
+ cap_overlay = new
+
+ SET_PLANE_EXPLICIT(cap_overlay, initial(plane), our_turf)
+
+ cap_overlay.color = pipe_color
+ cap_overlay.layer = layer
+ cap_overlay.icon_state = "[bitfield]_[piping_layer]"
+
+ cap_overlay.forceMove(our_turf)
+
+/obj/effect/overlay/cap_visual
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ icon = 'icons/obj/pipes_n_cables/!pipes_bitmask.dmi'
+ vis_flags = NONE
+ anchored = TRUE
+
/**
* Called by the RPD.dm pre_attack()
* Arguments:
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index e72d72b3d595512..b4e5d88d62c7186 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -32,12 +32,6 @@
component_mixture.volume = 200
airs[i] = component_mixture
-/obj/machinery/atmospherics/components/Initialize(mapload)
- . = ..()
-
- if(hide)
- RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(hide_pipe))
-
// Iconnery
/**
@@ -46,11 +40,14 @@
/obj/machinery/atmospherics/components/proc/update_icon_nopipes()
return
+/obj/machinery/atmospherics/components/on_hide(datum/source, underfloor_accessibility)
+ hide_pipe(underfloor_accessibility)
+ return ..()
+
/**
- * Called in Initialize(), set the showpipe var to true or false depending on the situation, calls update_icon()
+ * Called in on_hide(), set the showpipe var to true or false depending on the situation, calls update_icon()
*/
-/obj/machinery/atmospherics/components/proc/hide_pipe(datum/source, underfloor_accessibility)
- SIGNAL_HANDLER
+/obj/machinery/atmospherics/components/proc/hide_pipe(underfloor_accessibility)
showpipe = !!underfloor_accessibility
if(showpipe)
REMOVE_TRAIT(src, TRAIT_UNDERFLOOR, REF(src))
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
index ea20f2eeb66a8a9..4161a30ed7d723c 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
@@ -10,6 +10,7 @@
hide = TRUE
layer = GAS_SCRUBBER_LAYER
pipe_state = "injector"
+ has_cap_visuals = TRUE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF //really helpful in building gas chambers for xenomorphs
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.25
@@ -74,6 +75,8 @@
if(showpipe)
// everything is already shifted so don't shift the cap
add_overlay(get_pipe_image(icon, "inje_cap", initialize_directions, pipe_color))
+ else
+ PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT)
if(!nodes[1] || !on || !is_operational)
icon_state = "inje_off"
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
index f461cbe8988f8ec..17f6c761f129dc7 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
@@ -10,6 +10,7 @@
shift_underlay_only = FALSE
pipe_state = "pvent"
+ has_cap_visuals = TRUE
vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
/obj/machinery/atmospherics/components/unary/passive_vent/update_icon_nopipes()
@@ -17,6 +18,8 @@
if(showpipe)
var/image/cap = get_pipe_image(icon, "vent_cap", initialize_directions, pipe_color)
add_overlay(cap)
+ else
+ PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT)
icon_state = "passive_vent"
/obj/machinery/atmospherics/components/unary/passive_vent/process_atmos()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
index d5eada4e73f8946..f47d6d5b069ca3a 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
@@ -13,6 +13,8 @@
pipe_flags = PIPING_ONE_PER_TURF
pipe_state = "connector"
+ has_cap_visuals = TRUE
+
custom_reconcilation = TRUE
///Reference to the connected device
@@ -29,11 +31,13 @@
return ..()
/obj/machinery/atmospherics/components/unary/portables_connector/update_icon_nopipes()
- icon_state = "connector"
+ cut_overlays()
if(showpipe)
- cut_overlays()
var/image/cap = get_pipe_image(icon, "connector_cap", initialize_directions, pipe_color)
add_overlay(cap)
+ else
+ PIPING_LAYER_SHIFT(src, PIPING_LAYER_DEFAULT)
+ icon_state = "connector"
/obj/machinery/atmospherics/components/unary/portables_connector/process_atmos()
if(!connected_device)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 46adfee054e6e65..aa890b0b574a0a1 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -16,8 +16,6 @@
layer = OBJ_LAYER
circuit = /obj/item/circuitboard/machine/thermomachine
- hide = TRUE
-
move_resist = MOVE_RESIST_DEFAULT
vent_movement = NONE
pipe_flags = PIPING_ONE_PER_TURF
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index f3c5563fd3afd30..bece67572b6f569 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -14,6 +14,7 @@
hide = TRUE
shift_underlay_only = FALSE
pipe_state = "uvent"
+ has_cap_visuals = TRUE
vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
// vents are more complex machinery and so are less resistant to damage
max_integrity = 100
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
index 051299950bc68c9..0eadf783e5e9fb1 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
@@ -12,6 +12,7 @@
hide = TRUE
shift_underlay_only = FALSE
pipe_state = "scrubber"
+ has_cap_visuals = TRUE
vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
processing_flags = NONE
diff --git a/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm
index 9642442a9733f51..6b1997cc3c718b4 100644
--- a/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipe_spritesheet_helper.dm
@@ -18,6 +18,13 @@
"[WEST]"=icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "damage_mask", WEST),
)
+ var/static/list/icon/cap_masks = list(
+ "[NORTH]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", NORTH),
+ "[EAST]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", EAST),
+ "[SOUTH]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", SOUTH),
+ "[WEST]" = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "cap_mask", WEST),
+ )
+
var/icon/generated_icons
/datum/pipe_icon_generator/proc/Start(icon_state_suffix="")
@@ -85,6 +92,34 @@
outputs[damaged] = "[icon_state_dirs]_[layer]"
return outputs
+/datum/pipe_icon_generator/proc/generate_capped(icon/working, layer, dirs, x_offset=1, y_offset=1)
+ var/list/outputs = list()
+ var/list/completed = list()
+ for(var/combined_dirs in 1 to 15)
+ combined_dirs &= dirs
+
+ var/completion_key = "[combined_dirs]"
+ if(completed[completion_key] || (combined_dirs == NONE))
+ continue
+
+ completed[completion_key] = TRUE
+
+ var/icon/capped_mask = icon('icons/obj/pipes_n_cables/pipe_template_pieces.dmi', "blank_mask")
+ for(var/i in 0 to 3)
+ var/dir = 1 << i
+ if(!(combined_dirs & dir))
+ continue
+
+ var/icon/cap_mask = cap_masks["[dir]"]
+ capped_mask.Blend(cap_mask, ICON_OVERLAY, x_offset, y_offset)
+
+ var/icon/capped = icon(working)
+ capped.Blend(capped_mask, ICON_MULTIPLY)
+
+ var/icon_state_dirs = (dirs & ~combined_dirs) | CARDINAL_TO_PIPECAPS(combined_dirs)
+ outputs[capped] = "[icon_state_dirs]_[layer]"
+
+ return outputs
/datum/pipe_icon_generator/proc/GeneratePipeStraight(icon_state_suffix, layer, combined_dirs)
var/list/output = list()
@@ -97,8 +132,10 @@
switch(combined_dirs)
if(NORTH | SOUTH)
output += GenerateDamaged(working, layer, combined_dirs, y_offset=offset)
+ output += generate_capped(working, layer, combined_dirs, y_offset=offset)
if(EAST | WEST)
output += GenerateDamaged(working, layer, combined_dirs, x_offset=offset)
+ output += generate_capped(working, layer, combined_dirs, x_offset=offset)
return output
@@ -117,6 +154,7 @@
output[working] = "[combined_dirs]_[layer]"
output += GenerateDamaged(working, layer, combined_dirs)
+ output += generate_capped(working, layer, combined_dirs)
return output
@@ -135,6 +173,7 @@
output[working] = "[combined_dirs]_[layer]"
output += GenerateDamaged(working, layer, combined_dirs)
+ output += generate_capped(working, layer, combined_dirs)
return output
@@ -144,5 +183,6 @@
output[working] = "[combined_dirs]_[layer]"
output += GenerateDamaged(working, layer, combined_dirs)
+ output += generate_capped(working, layer, combined_dirs)
return output
diff --git a/code/modules/atmospherics/machinery/pipes/smart.dm b/code/modules/atmospherics/machinery/pipes/smart.dm
index 365d48e41213e49..ee2efd699cdb034 100644
--- a/code/modules/atmospherics/machinery/pipes/smart.dm
+++ b/code/modules/atmospherics/machinery/pipes/smart.dm
@@ -10,6 +10,8 @@ GLOBAL_LIST_INIT(atmos_components, typecacheof(list(/obj/machinery/atmospherics)
device_type = QUATERNARY
construction_type = /obj/item/pipe/quaternary
pipe_state = "manifold4w"
+ has_cap_visuals = TRUE
+
///Current active connections
var/connections = NONE
///Was this pipe created during map load
diff --git a/code/modules/cargo/coupon.dm b/code/modules/cargo/coupon.dm
index f654db448872eac..8eefcc8676613e6 100644
--- a/code/modules/cargo/coupon.dm
+++ b/code/modules/cargo/coupon.dm
@@ -63,7 +63,7 @@
update_name()
/// Choose what our prize is :D
-/obj/item/coupon/proc/generate(discount, datum/supply_pack/discounted_pack)
+/obj/item/coupon/proc/generate(discount, datum/supply_pack/discounted_pack, mob/user)
src.discounted_pack = discounted_pack || pick(GLOB.discountable_packs[pick_weight(GLOB.pack_discount_odds)])
var/static/list/chances = list("0.10" = 4, "0.15" = 8, "0.20" = 10, "0.25" = 8, "0.50" = 4, COUPON_OMEN = 1)
discount_pct_off = discount || pick_weight(chances)
@@ -77,14 +77,14 @@
name = "coupon - fuck you"
desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?"
- if(!ismob(loc))
+ var/mob/cursed = user || loc
+ if(!ismob(cursed))
return FALSE
- var/mob/cursed = loc
to_chat(cursed, span_warning("The coupon reads 'fuck you' in large, bold text... is- is that a prize, or?"))
if(!cursed.GetComponent(/datum/component/omen))
- cursed.AddComponent(/datum/component/omen)
+ cursed.AddComponent(/datum/component/omen, src, 1)
return TRUE
if(HAS_TRAIT(cursed, TRAIT_CURSED))
to_chat(cursed, span_warning("What a horrible night... To have a curse!"))
@@ -98,6 +98,7 @@
/obj/item/coupon/proc/curse_heart(mob/living/cursed)
if(!iscarbon(cursed))
cursed.gib(DROP_ALL_REMAINS)
+ burn_evilly()
return TRUE
var/mob/living/carbon/player = cursed
@@ -105,6 +106,11 @@
to_chat(player, span_mind_control("What could that coupon mean?"))
to_chat(player, span_userdanger("...The suspense is killing you!"))
player.set_heartattack(status = TRUE)
+ burn_evilly()
+
+/obj/item/coupon/proc/burn_evilly()
+ visible_message(span_warning("[src] burns up in a sinister flash, taking an evil energy with it..."))
+ burn()
/obj/item/coupon/attack_atom(obj/O, mob/living/user, params)
if(!istype(O, /obj/machinery/computer/cargo))
diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm
index 840d40f18371291..fc8c9656fea78ba 100644
--- a/code/modules/cargo/exports/parts.dm
+++ b/code/modules/cargo/exports/parts.dm
@@ -33,3 +33,9 @@
unit_name = "data disk"
export_types = list(/obj/item/computer_disk)
include_subtypes = TRUE
+
+/datum/export/refill_canister
+ cost = CARGO_CRATE_VALUE * 0.5 //If someone want to make this worth more as it empties, go ahead
+ unit_name = "vending refill canister"
+ message = "Thank you for restocking the station!"
+ export_types = list(/obj/item/vending_refill)
diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm
index fd30973d24107ad..c39fa59c200605c 100644
--- a/code/modules/cargo/goodies.dm
+++ b/code/modules/cargo/goodies.dm
@@ -308,3 +308,10 @@
desc = "A less cheap imported climbing hook. Absolutely no use outside of planetary stations."
cost = PAYCHECK_CREW * 5
contains = list(/obj/item/climbing_hook)
+
+/datum/supply_pack/goody/double_barrel
+ name = "Double Barrel Shotgun"
+ desc = "Lost your beloved bunny to a demonic invasion? Clown broke in and stole your beloved gun? No worries! Get a new gun so long as you can pay the absurd fees."
+ cost = PAYCHECK_COMMAND * 18
+ access_view = ACCESS_WEAPONS
+ contains = list(/obj/item/gun/ballistic/shotgun/doublebarrel)
diff --git a/code/modules/cargo/markets/market_items/clothing.dm b/code/modules/cargo/markets/market_items/clothing.dm
index 8af34e22916575d..bf705e8b5725100 100644
--- a/code/modules/cargo/markets/market_items/clothing.dm
+++ b/code/modules/cargo/markets/market_items/clothing.dm
@@ -93,5 +93,11 @@
stock_max = 3
availability_prob = 40
-
-
+/datum/market_item/clothing/collar_bomb
+ name = "Collar Bomb Kit"
+ desc = "An unpatented and questionably ethical kit consisting of a low-yield explosive collar and a remote to trigger it."
+ item = /obj/item/storage/box/collar_bomb
+ price_min = CARGO_CRATE_VALUE * 3.5
+ price_max = CARGO_CRATE_VALUE * 4.5
+ stock_max = 3
+ availability_prob = 60
diff --git a/code/modules/cargo/markets/market_items/weapons.dm b/code/modules/cargo/markets/market_items/weapons.dm
index 052074a30b38d26..11f242d57c87460 100644
--- a/code/modules/cargo/markets/market_items/weapons.dm
+++ b/code/modules/cargo/markets/market_items/weapons.dm
@@ -66,7 +66,7 @@
/datum/market_item/weapon/fisher
name = "SC/FISHER Saboteur Handgun"
- desc = "A self-recharging, compact pistol that disrupts flashlights and security cameras, if only temporarily. Also usable in melee."
+ desc = "A self-recharging, compact pistol that disrupts lights, cameras, APCs, turrets and more, if only temporarily. Also usable in melee."
item = /obj/item/gun/energy/recharge/fisher
price_min = CARGO_CRATE_VALUE * 2
diff --git a/code/modules/cargo/markets/market_uplink.dm b/code/modules/cargo/markets/market_uplink.dm
index 19c1a049a1be061..a82218082e90dfa 100644
--- a/code/modules/cargo/markets/market_uplink.dm
+++ b/code/modules/cargo/markets/market_uplink.dm
@@ -150,6 +150,7 @@
icon_state = "uplink"
//The original black market uplink
accessible_markets = list(/datum/market/blackmarket)
+ custom_premium_price = PAYCHECK_CREW * 2.5
/datum/crafting_recipe/blackmarket_uplink
diff --git a/code/modules/cargo/materials_market.dm b/code/modules/cargo/materials_market.dm
index 92d83d5d0a14134..947197d16f298a0 100644
--- a/code/modules/cargo/materials_market.dm
+++ b/code/modules/cargo/materials_market.dm
@@ -166,12 +166,14 @@
var/min_value_override = initial(traded_mat.minimum_value_override)
if(min_value_override)
minimum_value_threshold = min_value_override
-
+ else
+ minimum_value_threshold = round(initial(traded_mat.value_per_unit) * SHEET_MATERIAL_AMOUNT * 0.5)
//send data
material_data += list(list(
"name" = initial(traded_mat.name),
"price" = SSstock_market.materials_prices[traded_mat],
+ "rarity" = initial(traded_mat.value_per_unit),
"threshold" = minimum_value_threshold,
"quantity" = SSstock_market.materials_quantity[traded_mat],
"trend" = trend_string,
@@ -205,6 +207,7 @@
.["orderBalance"] = current_cost
.["orderingPrive"] = ordering_private
.["canOrderCargo"] = can_buy_via_budget
+ .["updateTime"] = SSstock_market.next_fire - world.time
/obj/machinery/materials_market/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
@@ -348,8 +351,8 @@
/obj/item/stock_block/Initialize(mapload)
. = ..()
- addtimer(CALLBACK(src, PROC_REF(value_warning)), 2.5 MINUTES, TIMER_DELETE_ME)
- addtimer(CALLBACK(src, PROC_REF(update_value)), 5 MINUTES, TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(value_warning)), 1.5 MINUTES, TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(update_value)), 3 MINUTES, TIMER_DELETE_ME)
/obj/item/stock_block/examine(mob/user)
. = ..()
diff --git a/code/modules/cargo/packs/costumes_toys.dm b/code/modules/cargo/packs/costumes_toys.dm
index 51fb4686038f561..e23e6112a4bfc6e 100644
--- a/code/modules/cargo/packs/costumes_toys.dm
+++ b/code/modules/cargo/packs/costumes_toys.dm
@@ -250,8 +250,9 @@
discountable = SUPPLY_PACK_STD_DISCOUNTABLE
/datum/supply_pack/costumes_toys/stickers/fill(obj/structure/closet/crate/crate)
- for(var/i in 1 to rand(1,2))
+ for(var/i in 1 to rand(1, 2))
new /obj/item/storage/box/stickers(crate)
+
if(prob(30)) // a pair of googly eyes because funny
new /obj/item/storage/box/stickers/googly(crate)
diff --git a/code/modules/cargo/packs/organic.dm b/code/modules/cargo/packs/organic.dm
index d806ce51d45a9d7..f405fc7de3a8a9a 100644
--- a/code/modules/cargo/packs/organic.dm
+++ b/code/modules/cargo/packs/organic.dm
@@ -298,7 +298,7 @@
name = "Grilling Starter Kit"
desc = "Hey dad I'm Hungry. Hi Hungry I'm THE NEW GRILLING STARTER KIT \
ONLY 5000 BUX GET NOW! Contains a grill and fuel."
- cost = CARGO_CRATE_VALUE * 8
+ cost = CARGO_CRATE_VALUE * 4
crate_type = /obj/structure/closet/crate
contains = list(
/obj/item/stack/sheet/mineral/coal/five,
diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm
index f0de463c4490f87..7fa9013c686cf69 100644
--- a/code/modules/cargo/packs/science.dm
+++ b/code/modules/cargo/packs/science.dm
@@ -104,14 +104,16 @@
name = "Robotics Assembly Crate"
desc = "The tools you need to replace those finicky humans with a loyal robot army! \
Contains four proximity sensors, two empty first aid kits, two health analyzers, \
- two red hardhats, two mechanical toolboxes, and two cleanbot assemblies!"
+ two red hardhats, two toolboxes, and two cleanbot assemblies!"
cost = CARGO_CRATE_VALUE * 3
access = ACCESS_ROBOTICS
access_view = ACCESS_ROBOTICS
- contains = list(/obj/item/assembly/prox_sensor = 5,
+ contains = list(/obj/item/assembly/prox_sensor = 4,
/obj/item/healthanalyzer = 2,
/obj/item/clothing/head/utility/hardhat/red = 2,
- /obj/item/storage/medkit = 2)
+ /obj/item/storage/medkit = 2,
+ /obj/item/storage/toolbox = 2,
+ /obj/item/bot_assembly/cleanbot = 2)
crate_name = "robotics assembly crate"
crate_type = /obj/structure/closet/crate/secure/science/robo
diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm
index 0b0073258298b1c..b8e93f2815c0d85 100644
--- a/code/modules/cargo/packs/security.dm
+++ b/code/modules/cargo/packs/security.dm
@@ -330,8 +330,8 @@
/datum/supply_pack/security/armory/thermal
name = "Thermal Pistol Crate"
desc = "Contains a pair of holsters each with two experimental thermal pistols, \
- using nanites as the basis for their ammo."
- cost = CARGO_CRATE_VALUE * 7
+ using nanites as the basis for their ammo. Can be shaken to reload."
+ cost = CARGO_CRATE_VALUE * 10
contains = list(/obj/item/storage/belt/holster/energy/thermal = 2)
crate_name = "thermal pistol crate"
diff --git a/code/modules/cargo/packs/vending_restock.dm b/code/modules/cargo/packs/vending_restock.dm
index cfe9961cc3a4362..10ae874d5d6c927 100644
--- a/code/modules/cargo/packs/vending_restock.dm
+++ b/code/modules/cargo/packs/vending_restock.dm
@@ -4,7 +4,7 @@
/datum/supply_pack/vending/bartending
name = "Booze-o-mat and Coffee Supply Crate"
desc = "Bring on the booze and coffee vending machine refills."
- cost = CARGO_CRATE_VALUE * 4
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/boozeomat,
/obj/item/vending_refill/coffee,
)
@@ -14,7 +14,7 @@
name = "Cigarette Supply Crate"
desc = "Don't believe the reports - smoke today! Contains a \
cigarette vending machine refill."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/cigarette)
crate_name = "cigarette supply crate"
crate_type = /obj/structure/closet/crate
@@ -62,7 +62,7 @@
/datum/supply_pack/vending/imported
name = "Imported Vending Machines"
desc = "Vending machines famous in other parts of the galaxy."
- cost = CARGO_CRATE_VALUE * 8
+ cost = CARGO_CRATE_VALUE * 5
contains = list(/obj/item/vending_refill/sustenance,
/obj/item/vending_refill/robotics,
/obj/item/vending_refill/sovietsoda,
@@ -74,7 +74,7 @@
name = "Medical Vending Crate"
desc = "Contains one NanoMed Plus refill, one NanoDrug Plus refill, \
and one wall-mounted NanoMed refill."
- cost = CARGO_CRATE_VALUE * 5
+ cost = CARGO_CRATE_VALUE * 3.5
contains = list(/obj/item/vending_refill/medical,
/obj/item/vending_refill/drugs,
/obj/item/vending_refill/wallmed,
@@ -85,7 +85,7 @@
name = "PTech Supply Crate"
desc = "Not enough cartridges after half the crew lost their PDA \
to explosions? This may fix it."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2.5
contains = list(/obj/item/vending_refill/cart)
crate_name = "\improper PTech supply crate"
@@ -103,7 +103,7 @@
name = "Snack Supply Crate"
desc = "One vending machine refill of cavity-bringin' goodness! \
The number one dentist recommended order!"
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/snack)
crate_name = "snacks supply crate"
@@ -111,14 +111,14 @@
name = "Softdrinks Supply Crate"
desc = "Got whacked by a toolbox, but you still have those pesky teeth? \
Get rid of those pearly whites with this soda machine refill, today!"
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/cola)
crate_name = "soft drinks supply crate"
/datum/supply_pack/vending/vendomat
name = "Part-Mart & YouTool Supply Crate"
desc = "More tools for your IED testing facility."
- cost = CARGO_CRATE_VALUE * 2
+ cost = CARGO_CRATE_VALUE * 3
contains = list(/obj/item/vending_refill/assist,
/obj/item/vending_refill/youtool,
)
@@ -138,7 +138,7 @@
name = "Autodrobe Supply Crate"
desc = "Autodrobe missing your favorite dress? Solve that issue today \
with this autodrobe refill."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 2
contains = list(/obj/item/vending_refill/autodrobe)
crate_name = "autodrobe supply crate"
@@ -200,7 +200,7 @@
name = "Science Wardrobe Supply Crate"
desc = "This crate contains refills for the SciDrobe, \
GeneDrobe, and RoboDrobe."
- cost = CARGO_CRATE_VALUE * 3
+ cost = CARGO_CRATE_VALUE * 4.5
contains = list(/obj/item/vending_refill/wardrobe/robo_wardrobe,
/obj/item/vending_refill/wardrobe/gene_wardrobe,
/obj/item/vending_refill/wardrobe/science_wardrobe,
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 20ea6fec8ba5088..15850f13e590d84 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -70,6 +70,16 @@
bluespace = TRUE
explosionSize = list(0,0,0,0)
+/obj/structure/closet/supplypod/podspawn/deathmatch
+ desc = "A blood-red styled drop pod."
+ specialised = TRUE
+
+/obj/structure/closet/supplypod/podspawn/deathmatch/Entered(atom/movable/arrived)
+ . = ..()
+ if(isliving(arrived))
+ var/mob/living/critter = arrived
+ critter.faction = list(FACTION_HOSTILE) //No infighting, but also KILL!!
+
/obj/structure/closet/supplypod/extractionpod
name = "Syndicate Extraction Pod"
desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery."
@@ -90,6 +100,28 @@
delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+/obj/structure/closet/supplypod/back_to_station
+ name = "blood-red supply pod"
+ desc = "An intimidating supply pod, covered in the blood-red markings"
+ bluespace = TRUE
+ explosionSize = list(0,0,0,0)
+ style = STYLE_SYNDICATE
+ specialised = TRUE
+
+/obj/structure/closet/supplypod/deadmatch_missile
+ name = "cruise missile"
+ desc = "A big ass missile, likely launched from some far-off deep space missile silo."
+ decal = null
+ door = null
+ fin_mask = null
+ explosionSize = list(0,1,2,2)
+ effectShrapnel = TRUE
+ rubble_type = RUBBLE_THIN
+ specialised = TRUE
+ delays = list(POD_TRANSIT = 2.6 SECONDS, POD_FALLING = 0.4 SECONDS)
+ effectMissile = TRUE
+ shrapnel_type = /obj/projectile/bullet/shrapnel/short_range
+
/datum/armor/closet_supplypod
melee = 30
bullet = 50
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index a197c5d3cdf9e39..29eec0802a80dbd 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1196,8 +1196,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!CONFIG_GET(flag/use_age_restriction_for_jobs))
return 0
- if(!isnum(player_age))
- return 0 //This is only a number if the db connection is established, otherwise it is text: "Requires database", meaning these restrictions cannot be enforced
+ if(!isnum(player_age) || player_age < 0)
+ return 0
if(!isnum(days_needed))
return 0
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 5df802b9f608936..dc55b72b09d39de 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -10,12 +10,15 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
to_chat(usr, span_danger("Speech is currently admin-disabled."))
return
- if(!mob)
- return
+ var/client_initalized = VALIDATE_CLIENT_INITIALIZATION(src)
+ if(isnull(mob) || !client_initalized)
+ if(!client_initalized)
+ unvalidated_client_error() // we only want to throw this warning message when it's directly related to client failure.
- VALIDATE_CLIENT(src)
+ to_chat(usr, span_warning("Failed to send your OOC message. You attempted to send the following message:\n[span_big(msg)]"))
+ return
- if(!holder)
+ if(isnull(holder))
if(!GLOB.ooc_allowed)
to_chat(src, span_danger("OOC is globally muted."))
return
diff --git a/code/modules/clothing/belts/polymorph_belt.dm b/code/modules/clothing/belts/polymorph_belt.dm
index 73959d6d415195e..fb09b2e68c8f119 100644
--- a/code/modules/clothing/belts/polymorph_belt.dm
+++ b/code/modules/clothing/belts/polymorph_belt.dm
@@ -63,10 +63,9 @@
if (target_mob.mob_biotypes & (MOB_HUMANOID|MOB_ROBOTIC|MOB_SPECIAL|MOB_SPIRIT|MOB_UNDEAD))
balloon_alert(user, "incompatible!")
return TRUE
- if (isanimal_or_basicmob(target_mob))
- if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC))
- balloon_alert(user, "target too intelligent!")
- return TRUE
+ if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC))
+ balloon_alert(user, "target too intelligent!")
+ return TRUE
if (stored_mob_type == target_mob.type)
balloon_alert(user, "already scanned!")
return TRUE
diff --git a/code/modules/clothing/head/cakehat.dm b/code/modules/clothing/head/cakehat.dm
index 57369ac24f89ccc..1fc0fa0b05b5009 100644
--- a/code/modules/clothing/head/cakehat.dm
+++ b/code/modules/clothing/head/cakehat.dm
@@ -9,8 +9,10 @@
lefthand_file = 'icons/mob/inhands/clothing/hats_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing/hats_righthand.dmi'
armor_type = /datum/armor/none
- light_range = 2 //luminosity when on
light_system = OVERLAY_LIGHT
+ light_range = 2 //luminosity when on
+ light_power = 1.3
+ light_color = "#FF964E"
flags_cover = HEADCOVERSEYES
heat = 999
wound_bonus = 10
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index 3550690b42626ca..c5b25166c09e0b6 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -16,6 +16,7 @@
light_system = OVERLAY_LIGHT_DIRECTIONAL
light_range = 4
light_power = 0.8
+ light_color = "#ffcc99"
light_on = FALSE
dog_fashion = /datum/dog_fashion/head
@@ -39,9 +40,7 @@
/obj/item/clothing/head/utility/hardhat/Initialize(mapload)
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
-
-/obj/item/clothing/head/utility/hardhat/attack_self(mob/living/user)
- toggle_helmet_light(user)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/obj/item/clothing/head/utility/hardhat/proc/toggle_helmet_light(mob/living/user)
on = !on
@@ -61,6 +60,15 @@
/obj/item/clothing/head/utility/hardhat/proc/turn_off(mob/user)
set_light_on(FALSE)
+/obj/item/clothing/head/utility/hardhat/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(on)
+ toggle_helmet_light()
+ return COMSIG_SABOTEUR_SUCCESS
+
+/obj/item/clothing/head/utility/hardhat/attack_self(mob/living/user)
+ toggle_helmet_light(user)
+
/obj/item/clothing/head/utility/hardhat/orange
icon_state = "hardhat0_orange"
inhand_icon_state = null
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index e8338f4c95b19fc..0b0a6fb4d50cb31 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -135,6 +135,15 @@
strip_delay = 60
dog_fashion = null
+/obj/item/clothing/head/soft/veteran
+ name = "veteran cap"
+ desc = "It's a robust baseball hat in tasteful black colour with a golden connotation to \"REMEMBER\"."
+ icon_state = "veteransoft"
+ soft_type = "veteran"
+ armor_type = /datum/armor/cosmetic_sec
+ strip_delay = 60
+ dog_fashion = null
+
/obj/item/clothing/head/soft/paramedic
name = "paramedic cap"
desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top."
diff --git a/code/modules/clothing/neck/collar_bomb.dm b/code/modules/clothing/neck/collar_bomb.dm
new file mode 100644
index 000000000000000..72f5b20320db361
--- /dev/null
+++ b/code/modules/clothing/neck/collar_bomb.dm
@@ -0,0 +1,118 @@
+///Special neckwear that kills its wearer if triggered, by either its specific remote or assemblies.
+/obj/item/clothing/neck/collar_bomb
+ name = "collar bomb"
+ desc = "A cumbersome collar of some sort, filled with just enough explosive to rip one's head off... at least that's what it reads on the front tag."
+ icon_state = "collar_bomb"
+ icon = 'icons/obj/clothing/neck.dmi'
+ inhand_icon_state = "reverse_bear_trap"
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ clothing_flags = INEDIBLE_CLOTHING
+ armor_type = /datum/armor/collar_bomb
+ equip_delay_self = 6 SECONDS
+ equip_delay_other = 8 SECONDS
+ ///The button it's associated with
+ var/obj/item/collar_bomb_button/button
+ ///Whether the collar countdown has been triggered.
+ var/active = FALSE
+
+/datum/armor/collar_bomb
+ fire = 97
+ bomb = 97
+ acid = 97
+
+/obj/item/clothing/neck/collar_bomb/Initialize(mapload, obj/item/collar_bomb_button/button)
+ . = ..()
+ src.button = button
+ button?.collar = src
+ set_wires(new /datum/wires/collar_bomb(src))
+
+/obj/item/clothing/neck/collar_bomb/Destroy()
+ button?.collar = null
+ button = null
+ return ..()
+
+/obj/item/clothing/neck/collar_bomb/examine(mob/user)
+ . = ..()
+ if(user.get_item_by_slot(ITEM_SLOT_NECK) == src)
+ return
+ . += span_tinynotice("It has a [EXAMINE_HINT("wire")] panel that could be interacted with...")
+
+/obj/item/clothing/neck/collar_bomb/attackby(obj/item/item, mob/user, params)
+ if(is_wire_tool(item))
+ wires.interact(user)
+ else
+ return ..()
+
+/obj/item/clothing/neck/collar_bomb/equipped(mob/user, slot, initial = FALSE)
+ . = ..()
+ if(slot == ITEM_SLOT_NECK)
+ ADD_TRAIT(src, TRAIT_NODROP, INNATE_TRAIT)
+
+/obj/item/clothing/neck/collar_bomb/dropped(mob/user, silent = FALSE)
+ . = ..()
+ REMOVE_TRAIT(src, TRAIT_NODROP, INNATE_TRAIT)
+
+/obj/item/clothing/neck/collar_bomb/proc/explosive_countdown(ticks_left)
+ active = TRUE
+ if(ticks_left > 0)
+ playsound(src, 'sound/items/timer.ogg', 30, FALSE)
+ balloon_alert_to_viewers("[ticks_left]")
+ ticks_left--
+ addtimer(CALLBACK(src, PROC_REF(explosive_countdown), ticks_left), 1 SECONDS)
+ return
+
+ playsound(src, 'sound/effects/snap.ogg', 75, TRUE)
+ if(!ishuman(loc))
+ balloon_alert_to_viewers("dud...")
+ active = FALSE
+ return
+ var/mob/living/carbon/human/brian = loc
+ if(brian.get_item_by_slot(ITEM_SLOT_NECK) != src)
+ balloon_alert_to_viewers("dud...")
+ active = FALSE
+ return
+ visible_message(span_warning("[src] goes off, outright decapitating [brian]!"), span_hear("You hear a fleshy boom!"))
+ playsound(src, SFX_EXPLOSION, 30, TRUE)
+ brian.apply_damage(200, BRUTE, BODY_ZONE_HEAD)
+ var/obj/item/bodypart/head/myhead = brian.get_bodypart(BODY_ZONE_HEAD)
+ myhead?.dismember()
+ brian.investigate_log("has been decapitated by [src].", INVESTIGATE_DEATHS)
+ flash_color(brian, flash_color = "#FF0000", flash_time = 1 SECONDS)
+ qdel(src)
+
+///The button that detonates the collar.
+/obj/item/collar_bomb_button
+ name = "big yellow button"
+ desc = "It looks like a big red button, except it's yellow. It comes with a heavy trigger, to avoid accidents."
+ icon = 'icons/obj/devices/assemblies.dmi'
+ icon_state = "bigyellow"
+ inhand_icon_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ ///The collar bomb it's associated with.
+ var/obj/item/clothing/neck/collar_bomb/collar
+
+/obj/item/collar_bomb_button/attack_self(mob/user)
+ . = ..()
+ if(DOING_INTERACTION_WITH_TARGET(user, src))
+ return
+ balloon_alert_to_viewers("pushing the button...")
+ if(!do_after(user, 1.2 SECONDS, target = src))
+ return
+ playsound(user, 'sound/machines/click.ogg', 25, TRUE)
+ if(!collar|| collar.active)
+ return
+ collar.explosive_countdown(ticks_left = 5)
+ if(!ishuman(collar.loc))
+ return
+ var/mob/living/carbon/human/brian = collar.loc
+ if(brian.get_item_by_slot(ITEM_SLOT_NECK) == collar)
+ brian.investigate_log("has has their [collar] triggered by [user] via yellow button.", INVESTIGATE_DEATHS)
+
+
+/obj/item/collar_bomb_button/Destroy()
+ collar?.button = null
+ collar = null
+ return ..()
diff --git a/code/modules/clothing/shoes/clown.dm b/code/modules/clothing/shoes/clown.dm
index 549d2a40f152e82..2f4b973f9a9f9e9 100644
--- a/code/modules/clothing/shoes/clown.dm
+++ b/code/modules/clothing/shoes/clown.dm
@@ -58,4 +58,4 @@
name = "moffers"
desc = "No moths were harmed in the making of these slippers."
icon_state = "moffers"
- squeak_sound = list('sound/voice/moth/scream_moth.ogg'=1) //like sweet music to my ears
+ squeak_sound = list('sound/effects/footstep/moffstep01.ogg'=1) //like sweet music to my ears
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index 90141d281dd3162..860ac12d2020996 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -54,6 +54,8 @@
resistance_flags = FIRE_PROOF
light_system = OVERLAY_LIGHT_DIRECTIONAL
light_range = 4
+ light_power = 0.8
+ light_color = "#ffcc99"
light_on = FALSE
var/helmet_on = FALSE
var/smile = FALSE
@@ -76,6 +78,7 @@
. = ..()
visor_toggling()
update_appearance()
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/obj/item/clothing/head/helmet/space/plasmaman/examine()
. = ..()
@@ -106,6 +109,11 @@
playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing
update_appearance()
+/obj/item/clothing/head/helmet/space/plasmaman/update_icon_state()
+ . = ..()
+ icon_state = "[initial(icon_state)][helmet_on ? "-light":""]"
+ inhand_icon_state = icon_state
+
/obj/item/clothing/head/helmet/space/plasmaman/update_overlays()
. = ..()
. += visor_icon
@@ -137,6 +145,7 @@
hitting_clothing.forceMove(src)
update_appearance()
+///By the by, helmets have the update_icon_updates_onmob element, so we don't have to call mob.update_worn_head()
/obj/item/clothing/head/helmet/space/plasmaman/worn_overlays(mutable_appearance/standing, isinhands)
. = ..()
if(!isinhands && smile)
@@ -159,9 +168,7 @@
/obj/item/clothing/head/helmet/space/plasmaman/attack_self(mob/user)
helmet_on = !helmet_on
- icon_state = "[initial(icon_state)][helmet_on ? "-light":""]"
- inhand_icon_state = icon_state
- user.update_worn_head() //So the mob overlay updates
+ update_appearance()
if(helmet_on)
if(!up)
@@ -174,6 +181,14 @@
update_item_action_buttons()
+/obj/item/clothing/head/helmet/space/plasmaman/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(!helmet_on)
+ return
+ helmet_on = FALSE
+ update_appearance()
+ return COMSIG_SABOTEUR_SUCCESS
+
/obj/item/clothing/head/helmet/space/plasmaman/attack_hand_secondary(mob/user)
..()
. = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
diff --git a/code/modules/clothing/suits/costume.dm b/code/modules/clothing/suits/costume.dm
index 8c1815f3a01a338..e4fb756e2980a15 100644
--- a/code/modules/clothing/suits/costume.dm
+++ b/code/modules/clothing/suits/costume.dm
@@ -360,6 +360,48 @@
clothing_flags = THICKMATERIAL
flags_inv = HIDEHAIR|HIDEEARS
+/obj/item/clothing/suit/hooded/shark_costume // Blahaj
+ name = "Shark costume"
+ desc = "Finally, a costume to match your favorite plush."
+ icon_state = "shark"
+ icon = 'icons/obj/clothing/suits/costume.dmi'
+ worn_icon = 'icons/mob/clothing/suits/costume.dmi'
+ inhand_icon_state = "shark"
+ body_parts_covered = CHEST|GROIN|ARMS
+ clothing_flags = THICKMATERIAL
+ hoodtype = /obj/item/clothing/head/hooded/shark_hood
+
+/obj/item/clothing/head/hooded/shark_hood
+ name = "shark hood"
+ desc = "A hood attached to a shark costume."
+ icon = 'icons/obj/clothing/head/costume.dmi'
+ worn_icon = 'icons/mob/clothing/head/costume.dmi'
+ icon_state = "shark"
+ body_parts_covered = HEAD
+ clothing_flags = THICKMATERIAL
+ flags_inv = HIDEHAIR|HIDEEARS
+
+/obj/item/clothing/suit/hooded/shork_costume // Oh God Why
+ name = "shork costume"
+ desc = "Why would you ever do this?"
+ icon_state = "sharkcursed"
+ icon = 'icons/obj/clothing/suits/costume.dmi'
+ worn_icon = 'icons/mob/clothing/suits/costume.dmi'
+ inhand_icon_state = "sharkcursed"
+ body_parts_covered = CHEST|GROIN|ARMS
+ clothing_flags = THICKMATERIAL
+ hoodtype = /obj/item/clothing/head/hooded/shork_hood
+
+/obj/item/clothing/head/hooded/shork_hood
+ name = "shork hood"
+ desc = "A hood attached to a shork costume."
+ icon = 'icons/obj/clothing/head/costume.dmi'
+ worn_icon = 'icons/mob/clothing/head/costume.dmi'
+ icon_state = "sharkcursed"
+ body_parts_covered = HEAD
+ clothing_flags = THICKMATERIAL
+ flags_inv = HIDEHAIR|HIDEEARS
+
/obj/item/clothing/suit/hooded/bloated_human //OH MY GOD WHAT HAVE YOU DONE!?!?!?
name = "bloated human suit"
desc = "A horribly bloated suit made from human skins."
diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm
index 283878339fe3232..aaa233f0d35ff89 100644
--- a/code/modules/clothing/suits/wintercoats.dm
+++ b/code/modules/clothing/suits/wintercoats.dm
@@ -521,6 +521,9 @@
/obj/item/pipe_dispenser,
/obj/item/storage/bag/construction,
/obj/item/t_scanner,
+ /obj/item/construction/rld,
+ /obj/item/construction/rtd,
+ /obj/item/gun/ballistic/rifle/rebarxbow
)
armor_type = /datum/armor/wintercoat_engineering
hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering
diff --git a/code/modules/deathmatch/deathmatch_controller.dm b/code/modules/deathmatch/deathmatch_controller.dm
index c288daefd4e1ce2..45b5f087c5bb5a2 100644
--- a/code/modules/deathmatch/deathmatch_controller.dm
+++ b/code/modules/deathmatch/deathmatch_controller.dm
@@ -5,6 +5,8 @@
var/list/datum/lazy_template/deathmatch/maps = list()
/// All loadouts
var/list/datum/outfit/loadouts
+ /// All modifiers
+ var/list/datum/deathmatch_modifier/modifiers
/datum/deathmatch_controller/New()
. = ..()
@@ -17,6 +19,7 @@
var/map_name = initial(template.name)
maps[map_name] = new template
loadouts = subtypesof(/datum/outfit/deathmatch_loadout)
+ modifiers = sortTim(init_subtypes_w_path_keys(/datum/deathmatch_modifier), GLOBAL_PROC_REF(cmp_deathmatch_mods), associative = TRUE)
/datum/deathmatch_controller/proc/create_new_lobby(mob/host)
lobbies[host.ckey] = new /datum/deathmatch_lobby(host)
@@ -87,7 +90,7 @@
var/datum/deathmatch_lobby/chosen_lobby = lobbies[params["id"]]
if (!isnull(playing_lobby) && playing_lobby != chosen_lobby)
playing_lobby.leave(usr.ckey)
-
+
if(isnull(playing_lobby))
log_game("[usr.ckey] joined deathmatch lobby [params["id"]] as a player.")
chosen_lobby.join(usr)
diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm
index f994987427e86dd..341b85fb926806e 100644
--- a/code/modules/deathmatch/deathmatch_loadouts.dm
+++ b/code/modules/deathmatch/deathmatch_loadouts.dm
@@ -17,7 +17,7 @@
if(!isnull(species_override))
user.set_species(species_override)
- else if (istype(user.dna.species.outfit_important_for_life)) //plasmamen get lit on fire and die
+ else if (!isnull(user.dna.species.outfit_important_for_life)) //plasmamen get lit on fire and die
user.set_species(/datum/species/human)
for(var/datum/action/act as anything in granted_spells)
var/datum/action/new_ability = new act(user)
diff --git a/code/modules/deathmatch/deathmatch_lobby.dm b/code/modules/deathmatch/deathmatch_lobby.dm
index e498f662b5e397e..fb8077b491698d6 100644
--- a/code/modules/deathmatch/deathmatch_lobby.dm
+++ b/code/modules/deathmatch/deathmatch_lobby.dm
@@ -12,13 +12,15 @@
/// Whether players hear deadchat and people through walls
var/global_chat = FALSE
/// Whether the lobby is currently playing
- var/playing = FALSE
+ var/playing = DEATHMATCH_NOT_PLAYING
/// Number of total ready players
var/ready_count
/// List of loadouts, either gotten from the deathmatch controller or the map
var/list/loadouts
/// Current map player spawn locations, cleared after spawning
var/list/player_spawns = list()
+ /// A list of paths of modifiers enabled for the match.
+ var/list/modifiers = list()
/datum/deathmatch_lobby/New(mob/player)
. = ..()
@@ -48,11 +50,12 @@
map = null
location = null
loadouts = null
+ modifiers = null
/datum/deathmatch_lobby/proc/start_game()
if (playing)
return
- playing = TRUE
+ playing = DEATHMATCH_PRE_PLAYING
RegisterSignal(map, COMSIG_LAZY_TEMPLATE_LOADED, PROC_REF(find_spawns_and_start_delay))
location = map.lazy_load()
@@ -78,6 +81,9 @@
playing = FALSE
return FALSE
+ for(var/modpath in modifiers)
+ GLOB.deathmatch_game.modifiers[modpath].on_start_game(src)
+
for (var/key in players)
var/mob/dead/observer/observer = players[key]["mob"]
if (isnull(observer) || !observer.client)
@@ -97,19 +103,26 @@
var/mob/observer = observers[observer_key]["mob"]
observer.forceMove(pick(location.reserved_turfs))
+ playing = DEATHMATCH_PLAYING
addtimer(CALLBACK(src, PROC_REF(game_took_too_long)), initial(map.automatic_gameend_time))
log_game("Deathmatch game [host] started.")
announce(span_reallybig("GO!"))
+ if(length(modifiers))
+ var/list/modifier_names = list()
+ for(var/datum/deathmatch_modifier/modifier in modifiers)
+ modifier_names += uppertext(initial(modifier.name))
+ announce(span_boldnicegreen("THIS MATCH MODIFIERS: [english_list(modifier_names, and_text = " ,")]."))
return TRUE
/datum/deathmatch_lobby/proc/spawn_observer_as_player(ckey, loc)
- var/mob/dead/observer/observer = players[ckey]["mob"]
+ var/list/players_info = players[ckey]
+ var/mob/dead/observer/observer = players_info["mob"]
if (isnull(observer) || !observer.client)
remove_ckey_from_play(ckey)
return
// equip player
- var/datum/outfit/deathmatch_loadout/loadout = players[ckey]["loadout"]
+ var/datum/outfit/deathmatch_loadout/loadout = players_info["loadout"]
if (!(loadout in loadouts))
loadout = loadouts[1]
@@ -126,7 +139,10 @@
)
new_player.equipOutfit(loadout) // Loadout
new_player.key = ckey
- players[ckey]["mob"] = new_player
+ players_info["mob"] = new_player
+
+ for(var/datum/deathmatch_modifier/modifier as anything in modifiers)
+ GLOB.deathmatch_game.modifiers[modifier].apply(new_player, src)
// register death handling.
RegisterSignals(new_player, list(COMSIG_LIVING_DEATH, COMSIG_MOB_GHOSTIZED, COMSIG_QDELETING), PROC_REF(player_died))
@@ -158,6 +174,9 @@
loser.ghostize()
qdel(loser)
+ for(var/datum/deathmatch_modifier/modifier in modifiers)
+ GLOB.deathmatch_game.modifiers[modifier].on_end_game(src)
+
clear_reservation()
GLOB.deathmatch_game.remove_lobby(host)
log_game("Deathmatch game [host] ended.")
@@ -187,7 +206,8 @@
announce(span_reallybig("[player.real_name] HAS DIED. [players.len] REMAIN."))
if(!gibbed && !QDELING(player)) // for some reason dusting or deleting in chasm storage messes up tgui bad
- player.dust(TRUE, TRUE, TRUE)
+ if(!HAS_TRAIT(src, TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS))
+ player.dust(TRUE, TRUE, TRUE)
if (players.len <= 1)
end_game()
@@ -277,6 +297,9 @@
continue
players[player_key]["loadout"] = loadouts[1]
+ for(var/deathmatch_mod in modifiers)
+ GLOB.deathmatch_game.modifiers[deathmatch_mod].on_map_changed(src)
+
/datum/deathmatch_lobby/proc/clear_reservation()
if(isnull(location) || isnull(map))
return
@@ -312,11 +335,15 @@
for (var/map_key in GLOB.deathmatch_game.maps)
.["maps"] += map_key
+
/datum/deathmatch_lobby/ui_data(mob/user)
. = list()
+ var/is_player = !isnull(players[user.ckey])
+ var/is_host = (user.ckey == host)
+ var/is_admin = check_rights_for(user.client, R_ADMIN)
.["self"] = user.ckey
- .["host"] = (user.ckey == host)
- .["admin"] = check_rights_for(user.client, R_ADMIN)
+ .["host"] = is_host
+ .["admin"] = is_admin
.["global_chat"] = global_chat
.["playing"] = playing
.["loadouts"] = list("Randomize")
@@ -328,7 +355,27 @@
.["map"]["time"] = map.automatic_gameend_time
.["map"]["min_players"] = map.min_players
.["map"]["max_players"] = map.max_players
- if(!isnull(players[user.ckey]) && !isnull(players[user.ckey]["loadout"]))
+
+ .["mod_menu_open"] = FALSE
+ if((is_host || is_admin) && players[user.ckey]["mod_menu_open"])
+ .["mod_menu_open"] = TRUE
+ for(var/modpath in GLOB.deathmatch_game.modifiers)
+ var/datum/deathmatch_modifier/mod = GLOB.deathmatch_game.modifiers[modpath]
+ .["modifiers"] += list(list(
+ "name" = mod.name,
+ "desc" = mod.description,
+ "modpath" = "[modpath]",
+ "selected" = (modpath in modifiers),
+ "selectable" = is_host && mod.selectable(src),
+ ))
+ .["active_mods"] = "No modifiers selected"
+ if(length(modifiers))
+ var/list/mod_names = list()
+ for(var/datum/deathmatch_modifier/modpath as anything in modifiers)
+ mod_names += initial(modpath.name)
+ .["active_mods"] = "Selected modifiers: [english_list(mod_names)]"
+
+ if(is_player && !isnull(players[user.ckey]["loadout"]))
var/datum/outfit/deathmatch_loadout/loadout = players[user.ckey]["loadout"]
.["loadoutdesc"] = initial(loadout.desc)
else
@@ -437,6 +484,31 @@
if ("global_chat")
global_chat = !global_chat
return TRUE
+ if("open_mod_menu")
+ players[usr.ckey]["mod_menu_open"] = TRUE
+ return TRUE
+ if("exit_mod_menu")
+ players[usr.ckey] -= "mod_menu_open"
+ return TRUE
+ if("toggle_modifier")
+ var/datum/deathmatch_modifier/modpath = text2path(params["modpath"])
+ if(!ispath(modpath))
+ return TRUE
+ var/global_mod = params["global_mod"]
+ if(global_mod)
+ if(usr.ckey != host && !check_rights(R_ADMIN))
+ return TRUE
+ else if(!(usr.ckey in players))
+ return TRUE
+ var/datum/deathmatch_modifier/chosen_modifier = GLOB.deathmatch_game.modifiers[modpath]
+ if(modpath in modifiers)
+ chosen_modifier.unselect(src)
+ modifiers -= modpath
+ return TRUE
+ else if(chosen_modifier.selectable(src))
+ chosen_modifier.on_select(src)
+ modifiers += modpath
+ return TRUE
if ("admin") // Admin functions
if (!check_rights(R_ADMIN))
message_admins("[usr.key] has attempted to use admin functions in a deathmatch lobby!")
@@ -447,4 +519,7 @@
log_admin("[key_name(usr)] force started deathmatch lobby [host].")
start_game()
-
+/datum/deathmatch_lobby/ui_close(mob/user)
+ . = ..()
+ if(players[user.ckey])
+ players[user.ckey] -= "mod_menu_open"
diff --git a/code/modules/deathmatch/deathmatch_modifier.dm b/code/modules/deathmatch/deathmatch_modifier.dm
new file mode 100644
index 000000000000000..e654db3d392c9f5
--- /dev/null
+++ b/code/modules/deathmatch/deathmatch_modifier.dm
@@ -0,0 +1,478 @@
+///Deathmatch modifiers are little options the host can choose to spice the match a bit.
+/datum/deathmatch_modifier
+ ///The name of the modifier
+ var/name = "Unnamed Modifier"
+ ///A small description/tooltip shown in the UI
+ var/description = "What the heck does this do?"
+ ///The color of the button shown in the UI
+ var/color = "blue"
+ ///A list of modifiers this is incompatible with.
+ var/list/blacklisted_modifiers
+ ///Is this trait exempted from the "Random Modifiers" modifier.
+ var/random_exempted = FALSE
+
+///Whether or not this modifier can be selected, for both host and player-selected modifiers.
+/datum/deathmatch_modifier/proc/selectable(datum/deathmatch_lobby/lobby)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!random_exempted && (/datum/deathmatch_modifier/random in lobby.modifiers))
+ return FALSE
+ if(length(lobby.modifiers & blacklisted_modifiers))
+ return FALSE
+ for(var/modpath in lobby.modifiers)
+ if(src in GLOB.deathmatch_game.modifiers[modpath].blacklisted_modifiers)
+ return FALSE
+ return TRUE
+
+///Called when selecting the deathmatch modifier.
+/datum/deathmatch_modifier/proc/on_select(datum/deathmatch_lobby/lobby)
+ return
+
+///When the host changes his mind and unselects it.
+/datum/deathmatch_modifier/proc/unselect(datum/deathmatch_lobby/lobby)
+ return
+
+///Called when the host chooses to change map.
+/datum/deathmatch_modifier/proc/on_map_changed(datum/deathmatch_lobby/lobby)
+ return
+
+///Called as the game is about to start.
+/datum/deathmatch_modifier/proc/on_start_game(datum/deathmatch_lobby/lobby)
+ return
+
+///Called as the game has ended, right before the reservation is deleted.
+/datum/deathmatch_modifier/proc/on_end_game(datum/deathmatch_lobby/lobby)
+ return
+
+///Apply the modifier to the newly spawned player as the game is about to start
+/datum/deathmatch_modifier/proc/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ return
+
+/datum/deathmatch_modifier/health
+ name = "Double-Health"
+ description = "Doubles your starting health"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/health/triple)
+ var/multiplier = 2
+
+/datum/deathmatch_modifier/health/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.maxHealth *= multiplier
+ player.health *= multiplier
+
+/datum/deathmatch_modifier/health/triple
+ name = "Triple-Health"
+ description = "When \"Double-Health\" isn't enough..."
+ multiplier = 3
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/health)
+
+/datum/deathmatch_modifier/tenacity
+ name = "Tenacity"
+ description = "Unaffected by critical condition and pain"
+
+/datum/deathmatch_modifier/tenacity/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.add_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_ANALGESIA), DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/no_wounds
+ name = "No Wounds"
+ description = "Ah, the good ol' days when people did't have literal dents in their skulls..."
+
+/datum/deathmatch_modifier/no_wounds/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ ADD_TRAIT(player, TRAIT_NEVER_WOUNDED, DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/no_knockdown
+ name = "No Knockdowns"
+ description = "I'M FUCKING INVINCIBLE!"
+
+/datum/deathmatch_modifier/no_knockdown/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.add_traits(list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE), DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/xray
+ name = "X-Ray Vision"
+ description = "See through the cordons of the deathmatch arena!"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/thermal, /datum/deathmatch_modifier/echolocation)
+
+/datum/deathmatch_modifier/xray/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ ADD_TRAIT(player, TRAIT_XRAY_VISION, DEATHMATCH_TRAIT)
+ player.update_sight()
+
+/datum/deathmatch_modifier/thermal
+ name = "Thermal Vision"
+ description = "See mobs through walls"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/xray, /datum/deathmatch_modifier/echolocation)
+
+/datum/deathmatch_modifier/thermal/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ ADD_TRAIT(player, TRAIT_THERMAL_VISION, DEATHMATCH_TRAIT)
+ player.update_sight()
+
+/datum/deathmatch_modifier/regen
+ name = "Health Regen"
+ description = "The closest thing to free health insurance you can get"
+
+/datum/deathmatch_modifier/regen/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.AddComponent(/datum/component/regenerator, regeneration_delay = 4 SECONDS, brute_per_second = 2.5, burn_per_second = 2.5, tox_per_second = 2.5)
+
+/datum/deathmatch_modifier/nearsightness
+ name = "Nearsightness"
+ description = "Oops, I forgot my glasses at home"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/echolocation)
+
+/datum/deathmatch_modifier/nearsightness/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.become_nearsighted(DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/echolocation
+ name = "Echolocation"
+ description = "On one hand, you're blind, but on the other..."
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/nearsightness, /datum/deathmatch_modifier/xray, /datum/deathmatch_modifier/thermal)
+
+/datum/deathmatch_modifier/echolocation/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.AddComponent(/datum/component/echolocation)
+
+/datum/deathmatch_modifier/ocelot
+ name = "Ocelot"
+ description = "Shoot faster, with extra ricochet and less spread. You're pretty good!"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/stormtrooper)
+
+/datum/deathmatch_modifier/ocelot/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.add_traits(list(TRAIT_NICE_SHOT, TRAIT_DOUBLE_TAP), DEATHMATCH_TRAIT)
+ RegisterSignal(player, COMSIG_MOB_FIRED_GUN, PROC_REF(reduce_spread))
+ RegisterSignal(player, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, PROC_REF(apply_ricochet))
+
+/datum/deathmatch_modifier/ocelot/proc/reduce_spread(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
+ SIGNAL_HANDLER
+ bonus_spread_values[MIN_BONUS_SPREAD_INDEX] -= 50
+ bonus_spread_values[MAX_BONUS_SPREAD_INDEX] -= 50
+
+/datum/deathmatch_modifier/ocelot/proc/apply_ricochet(mob/user, obj/projectile/projectile, datum/fired_from, atom/clicked_atom)
+ SIGNAL_HANDLER
+ projectile.ricochets_max += 2
+ projectile.min_ricochets += 2
+ projectile.ricochet_incidence_leeway = 0
+ ADD_TRAIT(projectile, TRAIT_ALWAYS_HIT_ZONE, DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/stormtrooper
+ name = "Stormtrooper Aim"
+ description = "Fresh out of the 'I Can't Aim For Shit' School"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/ocelot)
+
+/datum/deathmatch_modifier/stormtrooper/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ RegisterSignal(player, COMSIG_MOB_FIRED_GUN, PROC_REF(increase_spread))
+
+/datum/deathmatch_modifier/stormtrooper/proc/increase_spread(mob/user, obj/item/gun/gun_fired, target, params, zone_override, list/bonus_spread_values)
+ SIGNAL_HANDLER
+ bonus_spread_values[MIN_BONUS_SPREAD_INDEX] += 10
+ bonus_spread_values[MAX_BONUS_SPREAD_INDEX] += 35
+
+/datum/deathmatch_modifier/four_hands
+ name = "Four Hands"
+ description = "When one pair isn't enough..."
+
+/datum/deathmatch_modifier/four_hands/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.change_number_of_hands(4)
+
+/datum/deathmatch_modifier/paraplegic
+ name = "Paraplegic"
+ description = "Wheelchairs. For. Everyone."
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/mounts)
+
+/datum/deathmatch_modifier/paraplegic/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic, TRAUMA_RESILIENCE_ABSOLUTE)
+ var/obj/vehicle/ridden/wheelchair/motorized/improved/wheels = new (player.loc)
+ wheels.setDir(player.dir)
+ wheels.buckle_mob(player)
+
+/datum/deathmatch_modifier/mounts
+ name = "Mounts"
+ description = "A horse! A horse! My kingdom for a horse!"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/paraplegic)
+
+/datum/deathmatch_modifier/mounts/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ ///We do a bit of fun over balance here, some mounts may be better than others.
+ var/mount_path = pick(list(
+ /mob/living/basic/carp,
+ /mob/living/basic/pony,
+ /mob/living/basic/pony/syndicate,
+ /mob/living/basic/pig,
+ /mob/living/basic/cow,
+ /mob/living/basic/cow/moonicorn,
+ /mob/living/basic/mining/wolf,
+ /mob/living/basic/mining/goldgrub,
+ /mob/living/basic/mining/goliath/saddled,
+ ))
+ var/mob/living/basic/mount = new mount_path (player.loc)
+ mount.tamed(player, null)
+ mount.befriend(player)
+ mount.buckle_mob(player)
+ if(HAS_TRAIT(lobby, TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS))
+ var/obj/item/implant/explosive/deathmatch/implant = new()
+ implant.implant(mount, silent = TRUE, force = TRUE)
+
+/datum/deathmatch_modifier/no_gravity
+ name = "No Gravity"
+ description = "Hone your robusting skills in zero g"
+ blacklisted_modifiers = list(/datum/deathmatch_modifier/mounts, /datum/deathmatch_modifier/paraplegic, /datum/deathmatch_modifier/minefield)
+
+/datum/deathmatch_modifier/no_gravity/on_start_game(datum/deathmatch_lobby/lobby)
+ ASYNC
+ for(var/turf/turf as anything in lobby.location.reserved_turfs)
+ turf.AddElement(/datum/element/forced_gravity, 0)
+ CHECK_TICK
+
+/datum/deathmatch_modifier/no_gravity/on_end_game(datum/deathmatch_lobby/lobby)
+ for(var/turf/turf as anything in lobby.location.reserved_turfs)
+ turf.RemoveElement(/datum/element/forced_gravity, 0)
+
+/datum/deathmatch_modifier/drop_pod
+ name = "Drop Pod: Syndies"
+ description = "Steel Rain: Syndicate Edition"
+ ///A lazylist of lobbies that have this modifier enabled
+ var/list/signed_lobbies
+ ///The type of drop pod that'll periodically fall from the sky
+ var/drop_pod_type = /obj/structure/closet/supplypod/podspawn/deathmatch
+ ///A (weighted) list of possible contents of the drop pod. Only one is picked at a time
+ var/list/contents
+ ///An interval representing the min and max cooldown between each time it's fired.
+ var/interval = list(7 SECONDS, 12 SECONDS)
+ ///How many (a number or a two keyed list) drop pods can be dropped at a time.
+ var/amount = list(1, 2)
+ ///The cooldown for dropping pods into every affected deathmatch arena.
+ COOLDOWN_DECLARE(drop_pod_cd)
+
+/datum/deathmatch_modifier/drop_pod/New()
+ . = ..()
+ populate_contents()
+
+/datum/deathmatch_modifier/drop_pod/on_select(datum/deathmatch_lobby/lobby)
+ if(isnull(signed_lobbies))
+ START_PROCESSING(SSprocessing, src)
+ LAZYADD(signed_lobbies, lobby)
+ RegisterSignal(lobby, COMSIG_QDELETING, PROC_REF(remove_lobby))
+
+/datum/deathmatch_modifier/drop_pod/unselect(datum/deathmatch_lobby/lobby)
+ remove_lobby(lobby)
+
+/datum/deathmatch_modifier/drop_pod/proc/remove_lobby(datum/deathmatch_lobby/lobby)
+ SIGNAL_HANDLER
+ LAZYREMOVE(signed_lobbies, lobby)
+ UnregisterSignal(lobby, COMSIG_QDELETING)
+ if(isnull(signed_lobbies))
+ STOP_PROCESSING(SSprocessing, src)
+
+/datum/deathmatch_modifier/drop_pod/process(seconds_per_tick)
+ if(!COOLDOWN_FINISHED(src, drop_pod_cd))
+ return
+ var/pod_spawned = FALSE
+ for(var/datum/deathmatch_lobby/lobby as anything in signed_lobbies)
+ if(lobby.playing != DEATHMATCH_PLAYING || isnull(lobby.location))
+ continue
+ var/yet_to_spawn = islist(amount) ? rand(amount[1], amount[2]) : amount
+ for(var/attempt in 1 to 10)
+ var/turf/to_strike = pick(lobby.location.reserved_turfs)
+ if(!isopenturf(to_strike) || isgroundlessturf(to_strike))
+ continue
+ var/atom/movable/to_spawn
+ if(length(contents))
+ var/spawn_path = pick_weight(contents)
+ to_spawn = new spawn_path (to_strike)
+ if(isliving(to_spawn) && HAS_TRAIT(lobby, TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS))
+ var/obj/item/implant/explosive/deathmatch/implant = new()
+ implant.implant(to_spawn, silent = TRUE, force = TRUE)
+ podspawn(list(
+ "path" = drop_pod_type,
+ "target" = to_strike,
+ "spawn" = to_spawn,
+ ))
+ pod_spawned = TRUE
+ yet_to_spawn--
+ if(yet_to_spawn == 0)
+ break
+
+ if(pod_spawned)
+ COOLDOWN_START(src, drop_pod_cd, rand(interval[1], interval[2]))
+
+/datum/deathmatch_modifier/drop_pod/proc/populate_contents()
+ contents = typesof(/mob/living/basic/trooper/syndicate)
+
+/datum/deathmatch_modifier/drop_pod/monsters
+ name = "Drop Pod: Monsters"
+ description = "Monsters are raining from the sky!"
+
+/datum/deathmatch_modifier/drop_pod/monsters/populate_contents()
+ contents = list(
+ /mob/living/basic/ant = 2,
+ /mob/living/basic/construct/proteon = 2,
+ /mob/living/basic/flesh_spider = 2,
+ /mob/living/basic/garden_gnome = 2,
+ /mob/living/basic/killer_tomato = 2,
+ /mob/living/basic/leaper = 1,
+ /mob/living/basic/mega_arachnid = 1,
+ /mob/living/basic/mining/goliath = 1,
+ /mob/living/basic/mining/ice_demon = 1,
+ /mob/living/basic/mining/ice_whelp = 1,
+ /mob/living/basic/mining/lobstrosity = 1,
+ /mob/living/basic/mining/mook = 2,
+ /mob/living/basic/mouse/rat = 2,
+ /mob/living/basic/spider/giant/nurse/scrawny = 2,
+ /mob/living/basic/spider/giant/tarantula/scrawny = 2,
+ /mob/living/basic/spider/giant/hunter/scrawny = 2,
+ /mob/living/simple_animal/hostile/dark_wizard = 2,
+ /mob/living/simple_animal/hostile/retaliate/goose = 2,
+ /mob/living/simple_animal/hostile/ooze = 1,
+ /mob/living/simple_animal/hostile/vatbeast = 1,
+ )
+
+/datum/deathmatch_modifier/drop_pod/missiles
+ name = "Drop Pod: Cruise Missiles"
+ description = "You're going to get shelled hard"
+ drop_pod_type = /obj/structure/closet/supplypod/deadmatch_missile
+ interval = list(3 SECONDS, 5 SECONDS)
+ amount = list(1, 3)
+
+/datum/deathmatch_modifier/drop_pod/missiles/populate_contents()
+ return
+
+/datum/deathmatch_modifier/explode_on_death
+ name = "Explosive Death"
+ description = "Everyone gets a microbomb that cannot be manually activated."
+
+/datum/deathmatch_modifier/explode_on_death/on_start_game(datum/deathmatch_lobby/lobby)
+ ADD_TRAIT(lobby, TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS, DEATHMATCH_TRAIT)
+
+/datum/deathmatch_modifier/explode_on_death/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ var/obj/item/implant/explosive/deathmatch/implant = new()
+ implant.implant(player, silent = TRUE, force = TRUE)
+
+/datum/deathmatch_modifier/helgrasp
+ name = "Helgrasped"
+ description = "Cursed hands are being thrown at you!"
+
+/datum/deathmatch_modifier/helgrasp/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ var/metabolism_rate = /datum/reagent/inverse/helgrasp/heretic::metabolization_rate
+ player.reagents.add_reagent(/datum/reagent/inverse/helgrasp/heretic, initial(lobby.map.automatic_gameend_time) / metabolism_rate)
+
+/datum/deathmatch_modifier/wasted
+ name = "Wasted"
+ description = "You've had one drink too many"
+
+/datum/deathmatch_modifier/wasted/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.adjust_drunk_effect(rand(30, 35))
+ var/metabolism_rate = /datum/reagent/consumable/ethanol/kahlua::metabolization_rate
+ player.reagents.add_reagent(/datum/reagent/consumable/ethanol/kahlua, initial(lobby.map.automatic_gameend_time) * 0.35 / metabolism_rate)
+
+/datum/deathmatch_modifier/monkeys
+ name = "Monkeyfication"
+ description = "Go back, I want to be monkey!"
+
+/datum/deathmatch_modifier/monkeys/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ //we don't call monkeyize(), because it'd set the player name to a generic "monkey(number)".
+ player.set_species(/datum/species/monkey)
+
+/datum/deathmatch_modifier/inverted_movement
+ name = "Inverted Movement"
+ description = "Up is down, left is right"
+
+/datum/deathmatch_modifier/inverted_movement/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.AddElement(/datum/element/inverted_movement)
+
+/datum/deathmatch_modifier/minefield
+ name = "Minefield"
+ description = "Oh, it seems you've trotted on a mine!"
+
+/datum/deathmatch_modifier/minefield/on_start_game(datum/deathmatch_lobby/lobby)
+ var/list/mines = subtypesof(/obj/effect/mine)
+ mines -= list(
+ /obj/effect/mine/explosive, //too lethal.
+ /obj/effect/mine/kickmine, //will kick the client, lol
+ /obj/effect/mine/gas, //Just spawns oxygen.
+ )
+
+ ///1 every 10 turfs, but it will actually spawn fewer mines since groundless and closed turfs are skipped.
+ var/mines_to_spawn = length(lobby.location.reserved_turfs) * 0.1
+ for(var/iteration in 1 to mines_to_spawn)
+ var/turf/target_turf = pick(lobby.location.reserved_turfs)
+ if(!isopenturf(target_turf) || isgroundlessturf(target_turf))
+ continue
+ ///don't spawn mine next to player spawns.
+ if(locate(/obj/effect/landmark/deathmatch_player_spawn) in range(1, target_turf))
+ continue
+ var/mine_path = pick(mines)
+ new mine_path (target_turf)
+
+/datum/deathmatch_modifier/flipping
+ name = "Perma-Flipping"
+ description = "You're constantly flipping, however it's purely cosmetic"
+
+/datum/deathmatch_modifier/flipping/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ player.SpinAnimation(speed = 0.9 SECONDS, loops = -1)
+
+/datum/deathmatch_modifier/screen_flipping
+ name = "Rotating Screen"
+ description = "♪ You spin me right round, baby right round ♪"
+
+/datum/deathmatch_modifier/screen_flipping/apply(mob/living/carbon/player, datum/deathmatch_lobby/lobby)
+ var/atom/movable/plane_master_controller/pm_controller = player.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
+ var/clockwise = prob(50)
+ for(var/atom/movable/screen/plane_master/plane as anything in pm_controller.get_planes())
+ plane.SpinAnimation(4.5 SECONDS, clockwise = clockwise)
+
+/datum/deathmatch_modifier/random
+ name = "Random Modifiers"
+ description = "Picks 3 to 5 random modifiers as the game is about to start"
+
+/datum/deathmatch_modifier/random/on_select(datum/deathmatch_lobby/lobby)
+ ///remove any other global modifier if chosen. It'll pick random ones when the time comes.
+ for(var/modpath in lobby.modifiers)
+ var/datum/deathmatch_modifier/modifier = GLOB.deathmatch_game.modifiers[modpath]
+ if(modifier.random_exempted)
+ continue
+ modifier.unselect(lobby)
+ lobby -= modpath
+
+/datum/deathmatch_modifier/random/on_start_game(datum/deathmatch_lobby/lobby)
+ lobby.modifiers -= type //remove it before attempting to select other modifiers, or they'll fail.
+
+ var/static/list/static_pool
+ if(!static_pool)
+ static_pool = subtypesof(/datum/deathmatch_modifier)
+ for(var/datum/deathmatch_modifier/modpath as anything in static_pool)
+ if(initial(modpath.random_exempted))
+ static_pool -= modpath
+ var/list/modifiers_pool = static_pool.Copy()
+
+ ///Pick global modifiers at random.
+ for(var/iteration in rand(3, 5))
+ var/mod_len = length(modifiers_pool)
+ if(!mod_len)
+ break
+ var/datum/deathmatch_modifier/modifier
+ if(mod_len > 1)
+ modifier = GLOB.deathmatch_game.modifiers[pick_n_take(modifiers_pool)]
+ else //pick() throws errors if the list has only one element iirc.
+ modifier = GLOB.deathmatch_game.modifiers[modifiers_pool[1]]
+ modifiers_pool = null
+ if(!modifier.selectable(lobby))
+ continue
+ modifier.on_select(lobby)
+ modifier.on_start_game(lobby)
+ lobby += modifier
+ modifiers_pool -= modifier.blacklisted_modifiers
+
+/datum/deathmatch_modifier/any_loadout
+ name = "Any Loadout Allowed"
+ description = "Watch players pick Instagib everytime"
+ random_exempted = TRUE
+
+/datum/deathmatch_modifier/any_loadout/selectable(datum/deathmatch_lobby/lobby)
+ . = ..()
+ if(!.)
+ return
+ return lobby.map.allowed_loadouts
+
+/datum/deathmatch_modifier/any_loadout/on_select(datum/deathmatch_lobby/lobby)
+ lobby.loadouts = GLOB.deathmatch_game.loadouts
+
+/datum/deathmatch_modifier/any_loadout/unselect(datum/deathmatch_lobby/lobby)
+ lobby.loadouts = lobby.map.allowed_loadouts
+
+/datum/deathmatch_modifier/any_loadout/on_map_changed(datum/deathmatch_lobby/lobby)
+ if(lobby.loadouts == GLOB.deathmatch_game.loadouts) //This arena already allows any loadout for some reason.
+ lobby.modifiers -= type
+ else
+ lobby.loadouts = GLOB.deathmatch_game.loadouts
diff --git a/code/modules/events/ghost_role/fugitive_event.dm b/code/modules/events/ghost_role/fugitive_event.dm
index 4b86e751c0b981d..687dde2dcee0f34 100644
--- a/code/modules/events/ghost_role/fugitive_event.dm
+++ b/code/modules/events/ghost_role/fugitive_event.dm
@@ -105,7 +105,7 @@
/datum/round_event/ghost_role/fugitives/proc/check_spawn_hunters(backstory, remaining_time)
//if the emergency shuttle has been called, spawn hunters now to give them a chance
- if(remaining_time == 0 || SSshuttle.emergency.mode != EMERGENCY_IDLE_OR_RECALLED)
+ if(remaining_time == 0 || !EMERGENCY_IDLE_OR_RECALLED)
spawn_hunters(backstory)
return
addtimer(CALLBACK(src, PROC_REF(check_spawn_hunters), backstory, remaining_time - 1 MINUTES), 1 MINUTES)
diff --git a/code/modules/fishing/aquarium/aquarium_kit.dm b/code/modules/fishing/aquarium/aquarium_kit.dm
index 3aafa178f263677..e22ccb3755879a2 100644
--- a/code/modules/fishing/aquarium/aquarium_kit.dm
+++ b/code/modules/fishing/aquarium/aquarium_kit.dm
@@ -85,7 +85,7 @@
)
return pick_weight(weighted_list)
-/obj/item/storage/fish_cas/blackmarket/Initialize(mapload)
+/obj/item/storage/fish_case/blackmarket/Initialize(mapload)
. = ..()
for(var/obj/item/fish/fish as anything in contents)
fish.set_status(FISH_DEAD)
diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm
index f247d11ef8484d8..8dee817f3e57fb5 100644
--- a/code/modules/fishing/fish/fish_traits.dm
+++ b/code/modules/fishing/fish/fish_traits.dm
@@ -213,9 +213,10 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
/datum/fish_trait/revival/proc/check_status(obj/item/fish/source)
SIGNAL_HANDLER
if(source.status == FISH_DEAD)
- addtimer(CALLBACK(src, PROC_REF(revive), source), rand(1 MINUTES, 2 MINUTES))
+ addtimer(CALLBACK(src, PROC_REF(revive), WEAKREF(source)), rand(1 MINUTES, 2 MINUTES))
-/datum/fish_trait/revival/proc/revive(obj/item/fish/source)
+/datum/fish_trait/revival/proc/revive(datum/weakref/fish_ref)
+ var/obj/item/fish/source = fish_ref.resolve()
if(QDELETED(source) || source.status != FISH_DEAD)
return
source.set_status(FISH_ALIVE)
diff --git a/code/modules/food_and_drinks/machinery/grill.dm b/code/modules/food_and_drinks/machinery/grill.dm
index bfa740a6fb23ba3..3d9acdde0baf170 100644
--- a/code/modules/food_and_drinks/machinery/grill.dm
+++ b/code/modules/food_and_drinks/machinery/grill.dm
@@ -1,33 +1,88 @@
-//I JUST WANNA GRILL FOR GOD'S SAKE
-
+///The fuel amount wasted as heat
#define GRILL_FUELUSAGE_IDLE 0.5
+///The fuel amount used to actually grill the item
#define GRILL_FUELUSAGE_ACTIVE 5
-/obj/machinery/grill//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE
- name = "grill"
- desc = "Just like the old days."
+/obj/machinery/grill //SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE
+ name = "Barbeque grill"
+ desc = "Just like the old days. Smokes items over a light heat"
icon = 'icons/obj/machines/kitchen.dmi'
icon_state = "grill_open"
density = TRUE
pass_flags_self = PASSMACHINE | LETPASSTHROW
- layer = BELOW_OBJ_LAYER
+ processing_flags = START_PROCESSING_MANUALLY
use_power = NO_POWER_USE
+
+ ///The amount of fuel gained from stacks or reagents
var/grill_fuel = 0
+ ///The item we are trying to grill
var/obj/item/food/grilled_item
+ ///The amount of time the food item has spent on the grill
var/grill_time = 0
+ ///Sound loop for the sizzling sound
var/datum/looping_sound/grill/grill_loop
/obj/machinery/grill/Initialize(mapload)
. = ..()
+ create_reagents(30, NO_REACT)
grill_loop = new(src, FALSE)
+ register_context()
/obj/machinery/grill/Destroy()
- grilled_item = null
+ QDEL_NULL(grilled_item)
QDEL_NULL(grill_loop)
return ..()
+/obj/machinery/grill/on_deconstruction(disassembled)
+ if(!QDELETED(grilled_item))
+ grilled_item.forceMove(drop_location())
+
+ new /obj/item/assembly/igniter(loc)
+ new /obj/item/stack/sheet/iron(loc, 5)
+ new /obj/item/stack/rods(loc, 5)
+
+ if(grill_fuel > 0)
+ var/datum/effect_system/fluid_spread/smoke/bad/smoke = new
+ smoke.set_up(1, holder = src, location = loc)
+ smoke.start()
+
+/obj/machinery/grill/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = NONE
+ if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1) || (held_item.resistance_flags & INDESTRUCTIBLE))
+ return
+
+ if(istype(held_item, /obj/item/stack/sheet/mineral/coal) || istype(held_item, /obj/item/stack/sheet/mineral/wood))
+ context[SCREENTIP_CONTEXT_LMB] = "Add fuel"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(is_reagent_container(held_item) && held_item.is_open_container() && held_item.reagents.total_volume)
+ context[SCREENTIP_CONTEXT_LMB] = "Add fuel"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(IS_EDIBLE(held_item) && !HAS_TRAIT(held_item, TRAIT_NODROP))
+ context[SCREENTIP_CONTEXT_LMB] = "Add item"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(held_item.tool_behaviour == TOOL_WRENCH)
+ context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(!anchored && held_item.tool_behaviour == TOOL_CROWBAR)
+ context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/grill/examine(mob/user)
+ . = ..()
+
+ . += span_notice("Add fuel via wood/coal stacks or any open container having a good fuel source")
+ . += span_notice("Monkey energy > Oil > Welding fuel > Ethanol. Others cause bad effects")
+ . += span_notice("Place any food item on top via hand to start grilling")
+
+ if(!anchored)
+ . += span_notice("It can be [EXAMINE_HINT("pried")] apart.")
+ if(anchored)
+ . += span_notice("Its [EXAMINE_HINT("anchored")] in place.")
+ else
+ . += span_warning("It needs to be [EXAMINE_HINT("anchored")] to work.")
+
/obj/machinery/grill/update_icon_state()
- if(grilled_item)
+ if(!QDELETED(grilled_item))
icon_state = "grill"
return ..()
if(grill_fuel > 0)
@@ -36,115 +91,216 @@
icon_state = "grill_open"
return ..()
-/obj/machinery/grill/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/sheet/mineral/coal) || istype(I, /obj/item/stack/sheet/mineral/wood))
- var/obj/item/stack/S = I
- var/stackamount = S.get_amount()
- to_chat(user, span_notice("You put [stackamount] [I]s in [src]."))
- if(istype(I, /obj/item/stack/sheet/mineral/coal))
- grill_fuel += (500 * stackamount)
- else
- grill_fuel += (50 * stackamount)
- S.use(stackamount)
- update_appearance()
- return
- if(I.resistance_flags & INDESTRUCTIBLE)
- to_chat(user, span_warning("You don't feel it would be wise to grill [I]..."))
- return ..()
- if(istype(I, /obj/item/reagent_containers/cup/glass))
- if(I.reagents.has_reagent(/datum/reagent/consumable/monkey_energy))
- grill_fuel += (20 * (I.reagents.get_reagent_amount(/datum/reagent/consumable/monkey_energy)))
- to_chat(user, span_notice("You pour the Monkey Energy in [src]."))
- I.reagents.remove_reagent(/datum/reagent/consumable/monkey_energy, I.reagents.get_reagent_amount(/datum/reagent/consumable/monkey_energy))
- update_appearance()
- return
- else if(IS_EDIBLE(I))
- if(HAS_TRAIT(I, TRAIT_NODROP) || (I.item_flags & (ABSTRACT | DROPDEL)))
- return ..()
- else if(HAS_TRAIT(I, TRAIT_FOOD_GRILLED))
- to_chat(user, span_notice("[I] has already been grilled!"))
- return
- else if(grill_fuel <= 0)
- to_chat(user, span_warning("There is not enough fuel!"))
- return
- else if(!grilled_item && user.transferItemToLoc(I, src))
- grilled_item = I
- RegisterSignal(grilled_item, COMSIG_ITEM_GRILLED, PROC_REF(GrillCompleted))
- to_chat(user, span_notice("You put the [grilled_item] on [src]."))
- update_appearance()
- grill_loop.start()
- return
-
- ..()
-
-/obj/machinery/grill/process(seconds_per_tick)
- update_appearance()
- if(grill_fuel <= 0)
- return
- else
- grill_fuel -= GRILL_FUELUSAGE_IDLE * seconds_per_tick
- if(SPT_PROB(0.5, seconds_per_tick))
- var/datum/effect_system/fluid_spread/smoke/bad/smoke = new
- smoke.set_up(1, holder = src, location = loc)
- smoke.start()
- if(grilled_item)
- SEND_SIGNAL(grilled_item, COMSIG_ITEM_GRILL_PROCESS, src, seconds_per_tick)
- if(QDELETED(grilled_item))
- grilled_item = null
- finish_grill()
- return
- grill_time += seconds_per_tick * 10 //convert to deciseconds
- grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 0.5 * seconds_per_tick)
- grill_fuel -= GRILL_FUELUSAGE_ACTIVE * seconds_per_tick
- grilled_item.AddComponent(/datum/component/sizzle)
-
/obj/machinery/grill/Exited(atom/movable/gone, direction)
. = ..()
if(gone == grilled_item)
- finish_grill()
+ grill_time = 0
+ grill_loop.stop()
grilled_item = null
-/obj/machinery/grill/wrench_act(mob/living/user, obj/item/I)
- . = ..()
- if(default_unfasten_wrench(user, I) != CANT_UNFASTEN)
+/obj/machinery/grill/attack_hand(mob/living/user, list/modifiers)
+ if(!QDELETED(grilled_item))
+ balloon_alert(user, "item removed")
+ grilled_item.forceMove(drop_location())
+ update_appearance(UPDATE_ICON_STATE)
return TRUE
-/obj/machinery/grill/on_deconstruction(disassembled)
- if(grilled_item)
- finish_grill()
- new /obj/item/stack/sheet/iron(loc, 5)
- new /obj/item/stack/rods(loc, 5)
+ return ..()
/obj/machinery/grill/attack_ai(mob/user)
- return
+ return //the ai can't physically flip the lid for the grill
+
+
+/// Makes grill fuel from a unit of stack
+/obj/machinery/grill/proc/burn_stack()
+ PRIVATE_PROC(TRUE)
+
+ //compute boost from wood or coal
+ var/boost
+ for(var/obj/item/stack in contents)
+ boost = 5 * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE)
+ if(istype(stack, /obj/item/stack/sheet/mineral/coal))
+ boost *= 2
+ if(stack.use(1))
+ grill_fuel += boost
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/machinery/grill/item_interaction(mob/living/user, obj/item/weapon, list/modifiers, is_right_clicking)
+ if(user.combat_mode || (weapon.item_flags & ABSTRACT) || (weapon.flags_1 & HOLOGRAM_1) || (weapon.resistance_flags & INDESTRUCTIBLE))
+ return ..()
+
+ if(istype(weapon, /obj/item/stack/sheet/mineral/coal) || istype(weapon, /obj/item/stack/sheet/mineral/wood))
+ if(!QDELETED(grilled_item))
+ return ..()
+ if(!anchored)
+ balloon_alert(user, "anchor first!")
+ return ITEM_INTERACT_BLOCKING
+
+ //required for amount subtypes
+ var/target_type
+ if(istype(weapon, /obj/item/stack/sheet/mineral/coal))
+ target_type = /obj/item/stack/sheet/mineral/coal
+ else
+ target_type = /obj/item/stack/sheet/mineral/wood
+
+ //transfer or merge stacks if we have enough space
+ var/merged = FALSE
+ var/obj/item/stack/target = weapon
+ for(var/obj/item/stack/stored in contents)
+ if(!istype(stored, target_type))
+ continue
+ if(stored.amount == MAX_STACK_SIZE)
+ to_chat(user, span_warning("No space for [weapon]"))
+ return ITEM_INTERACT_BLOCKING
+ target.merge(stored)
+ merged = TRUE
+ break
+ if(!merged)
+ weapon.forceMove(src)
+
+ to_chat(user, span_notice("You add [src] to the fuel stack"))
+ if(!grill_fuel)
+ burn_stack()
+ begin_processing()
+ return ITEM_INTERACT_SUCCESS
+
+ if(is_reagent_container(weapon) && weapon.is_open_container())
+ var/obj/item/reagent_containers/container = weapon
+ if(!QDELETED(grilled_item))
+ return ..()
+ if(!anchored)
+ balloon_alert(user, "anchor first!")
+ return ITEM_INTERACT_BLOCKING
+
+ var/transfered_amount = weapon.reagents.trans_to(src, container.amount_per_transfer_from_this)
+ if(transfered_amount)
+ //reagents & their effects on fuel
+ var/static/list/fuel_map = list(
+ /datum/reagent/consumable/monkey_energy = 4,
+ /datum/reagent/fuel/oil = 3,
+ /datum/reagent/fuel = 2,
+ /datum/reagent/consumable/ethanol = 1
+ )
+
+ //compute extra fuel to be obtained from everything transfered
+ var/boost
+ var/additional_fuel = 0
+ for(var/datum/reagent/stored as anything in reagents.reagent_list)
+ boost = fuel_map[stored.type]
+ if(!boost) //anything we don't recognize as fuel has inverse effects
+ boost = -1
+ boost = boost * stored.volume * (GRILL_FUELUSAGE_IDLE + GRILL_FUELUSAGE_ACTIVE)
+ additional_fuel += boost
+
+ //add to fuel source
+ reagents.clear_reagents()
+ grill_fuel += additional_fuel
+ if(grill_fuel <= 0) //can happen if you put water or something
+ grill_fuel = 0
+ else
+ begin_processing()
+ update_appearance(UPDATE_ICON_STATE)
+
+ //feedback
+ to_chat(user, span_notice("You transfer [transfered_amount]u to the fuel source"))
+ return ITEM_INTERACT_SUCCESS
+ else
+ to_chat(user, span_warning("No fuel was transfered"))
+ return ITEM_INTERACT_BLOCKING
+
+ if(IS_EDIBLE(weapon))
+ //sanity checks
+ if(!anchored)
+ balloon_alert(user, "anchor first!")
+ return ITEM_INTERACT_BLOCKING
+ if(HAS_TRAIT(weapon, TRAIT_NODROP))
+ return ..()
+ if(!QDELETED(grilled_item))
+ balloon_alert(user, "remove item first!")
+ return ITEM_INTERACT_BLOCKING
+ else if(grill_fuel <= 0)
+ balloon_alert(user, "no fuel!")
+ return ITEM_INTERACT_BLOCKING
+ else if(!user.transferItemToLoc(weapon, src))
+ balloon_alert(user, "[weapon] is stuck in your hand!")
+ return ITEM_INTERACT_BLOCKING
+
+ //add the item on the grill
+ grill_time = 0
+ grilled_item = weapon
+ var/datum/component/sizzle/sizzle = grilled_item.GetComponent(/datum/component/sizzle)
+ if(!isnull(sizzle))
+ grill_time = sizzle.time_elapsed()
+ to_chat(user, span_notice("You put the [grilled_item] on [src]."))
+ update_appearance(UPDATE_ICON_STATE)
+ grill_loop.start()
+ return ITEM_INTERACT_SUCCESS
-/obj/machinery/grill/attack_hand(mob/user, list/modifiers)
- if(grilled_item)
- to_chat(user, span_notice("You take out [grilled_item] from [src]."))
- grilled_item.forceMove(drop_location())
- update_appearance()
- return
return ..()
-/obj/machinery/grill/proc/finish_grill()
+/obj/machinery/grill/wrench_act(mob/living/user, obj/item/tool)
+ if(user.combat_mode)
+ return NONE
+
+ . = ITEM_INTERACT_BLOCKING
+ if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/grill/crowbar_act(mob/living/user, obj/item/tool)
+ if(user.combat_mode)
+ return NONE
+
+ . = ITEM_INTERACT_BLOCKING
+ if(anchored)
+ balloon_alert(user, "unanchor first!")
+ return
+
+ if(default_deconstruction_crowbar(tool, ignore_panel = TRUE))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/grill/process(seconds_per_tick)
+ if(!anchored)
+ return PROCESS_KILL
+
+ var/fuel_usage = GRILL_FUELUSAGE_IDLE * seconds_per_tick
+ if(grill_fuel < fuel_usage)
+ grill_fuel = 0
+ burn_stack()
+ if(grill_fuel < fuel_usage) //could not make any fuel
+ return PROCESS_KILL
+
+ //use fuel, create smoke puffs for immersion
+ grill_fuel -= fuel_usage
+ if(SPT_PROB(0.5, seconds_per_tick))
+ var/datum/effect_system/fluid_spread/smoke/bad/smoke = new
+ smoke.set_up(1, holder = src, location = loc)
+ smoke.start()
+
+ fuel_usage = GRILL_FUELUSAGE_ACTIVE * seconds_per_tick
if(!QDELETED(grilled_item))
+ //check to see if we need to burn more fuel
+ if(grill_fuel < fuel_usage)
+ burn_stack()
+ if(grill_fuel < fuel_usage) //could not make any fuel
+ return
+
+ //grill the item
+ var/last_grill_time = grill_time
+ grill_time += seconds_per_tick * 10 //convert to deciseconds
+ grilled_item.reagents.add_reagent(/datum/reagent/consumable/char, 0.5 * seconds_per_tick)
+ grilled_item.AddComponent(/datum/component/sizzle, grill_time)
+
+ //check to see if we have grilled our item to perfection
var/time_limit = 20 SECONDS
- //when items grill themselves in their own unique ways we want to follow their constraints
var/datum/component/grillable/custom_grilling = grilled_item.GetComponent(/datum/component/grillable)
if(!isnull(custom_grilling))
time_limit = custom_grilling.required_cook_time
-
if(grill_time >= time_limit)
+ grilled_item.RemoveElement(/datum/element/grilled_item, last_grill_time)
grilled_item.AddElement(/datum/element/grilled_item, grill_time)
- UnregisterSignal(grilled_item, COMSIG_ITEM_GRILLED)
-
- grill_time = 0
- grill_loop.stop()
-///Called when a food is transformed by the grillable component
-/obj/machinery/grill/proc/GrillCompleted(obj/item/source, atom/grilled_result)
- SIGNAL_HANDLER
- grilled_item = grilled_result //use the new item!!
+ //use fuel
+ grill_fuel -= fuel_usage
/obj/machinery/grill/unwrenched
anchored = FALSE
diff --git a/code/modules/food_and_drinks/machinery/icecream_vat.dm b/code/modules/food_and_drinks/machinery/icecream_vat.dm
index e5742418c140f09..d4de59919954482 100644
--- a/code/modules/food_and_drinks/machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/machinery/icecream_vat.dm
@@ -249,6 +249,12 @@
reagents.remove_reagent(reagents_used, CONE_REAGENET_NEEDED)
balloon_alert_to_viewers("scoops [selected_flavour]", "scoops [selected_flavour]")
+ if(istype(cone))
+ if(isnull(cone.crafted_food_buff))
+ cone.crafted_food_buff = /datum/status_effect/food/chilling
+ if(user.mind)
+ ADD_TRAIT(cone, TRAIT_FOOD_CHEF_MADE, REF(user.mind))
+
///Swaps the mode to the next one meant to be selected, then tells the user who changed it.
/obj/machinery/icecream_vat/proc/swap_modes(mob/user)
if(!user.can_perform_action(src))
diff --git a/code/modules/food_and_drinks/plate.dm b/code/modules/food_and_drinks/plate.dm
index a0d24dec8dacbaa..1df1d7c24bb9175 100644
--- a/code/modules/food_and_drinks/plate.dm
+++ b/code/modules/food_and_drinks/plate.dm
@@ -22,7 +22,7 @@
. = ..()
if(fragile)
- AddElement(/datum/element/shatters_when_thrown)
+ AddElement(/datum/element/can_shatter)
/obj/item/plate/attackby(obj/item/I, mob/user, params)
if(!IS_EDIBLE(I))
diff --git a/code/modules/forensics/_forensics.dm b/code/modules/forensics/_forensics.dm
index 40b480182537e17..5c43b9da0995c6a 100644
--- a/code/modules/forensics/_forensics.dm
+++ b/code/modules/forensics/_forensics.dm
@@ -8,8 +8,8 @@
* * List of clothing fibers on the atom
*/
/datum/forensics
- /// Weakref to the parent owning this datum
- var/datum/weakref/parent
+ /// Ref to the parent owning this datum
+ var/atom/parent
/**
* List of fingerprints on this atom
*
@@ -39,7 +39,7 @@
*/
var/list/fibers
-/datum/forensics/New(atom/parent, fingerprints, hiddenprints, blood_DNA, fibers)
+/datum/forensics/New(atom/parent, list/fingerprints, list/hiddenprints, list/blood_DNA, list/fibers)
if(!isatom(parent))
stack_trace("We tried adding a forensics datum to something that isnt an atom. What the hell are you doing?")
qdel(src)
@@ -47,7 +47,7 @@
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_act))
- src.parent = WEAKREF(parent)
+ src.parent = parent
src.fingerprints = fingerprints
src.hiddenprints = hiddenprints
src.blood_DNA = blood_DNA
@@ -67,9 +67,7 @@
check_blood()
/datum/forensics/Destroy(force)
- var/atom/parent_atom = parent.resolve()
- if (!isnull(parent_atom))
- UnregisterSignal(parent_atom, list(COMSIG_COMPONENT_CLEAN_ACT))
+ UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
parent = null
return ..()
@@ -148,10 +146,7 @@
/// Adds a single fiber
/datum/forensics/proc/add_fibers(mob/living/carbon/human/suspect)
var/fibertext
- var/atom/actual_parent = parent?.resolve()
- if(isnull(actual_parent))
- parent = null
- var/item_multiplier = isitem(actual_parent) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER
+ var/item_multiplier = isitem(parent) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER
if(suspect.wear_suit)
fibertext = "Material from \a [suspect.wear_suit]."
if(prob(10 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
@@ -217,11 +212,7 @@
if(last_stamp_pos)
LAZYSET(hiddenprints, suspect.key, copytext(hiddenprints[suspect.key], 1, last_stamp_pos))
hiddenprints[suspect.key] += "\nLast: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]" //made sure to be existing by if(!LAZYACCESS);else
- var/atom/parent_atom = parent?.resolve()
- if(!isnull(parent_atom))
- parent_atom.fingerprintslast = suspect.ckey
- else
- parent = null
+ parent.fingerprintslast = suspect.ckey
return TRUE
/// Adds the given list into blood_DNA
@@ -236,12 +227,8 @@
/// Updates the blood displayed on parent
/datum/forensics/proc/check_blood()
- var/obj/item/the_thing = parent?.resolve()
- if(isnull(the_thing))
- parent = null
- return
- if(!istype(the_thing) || isorgan(the_thing)) // organs don't spawn with blood decals by default
+ if(!isitem(parent) || isorgan(parent)) // organs don't spawn with blood decals by default
return
if(!length(blood_DNA))
return
- the_thing.AddElement(/datum/element/decal/blood)
+ parent.AddElement(/datum/element/decal/blood)
diff --git a/code/modules/forensics/forensics_helpers.dm b/code/modules/forensics/forensics_helpers.dm
index d71d1ebe1539c74..8cb7f721842101d 100644
--- a/code/modules/forensics/forensics_helpers.dm
+++ b/code/modules/forensics/forensics_helpers.dm
@@ -1,5 +1,7 @@
/// Adds a list of fingerprints to the atom
/atom/proc/add_fingerprint_list(list/fingerprints_to_add) //ASSOC LIST FINGERPRINT = FINGERPRINT
+ if (QDELETED(src))
+ return
if (isnull(fingerprints_to_add))
return
if (forensics)
@@ -10,7 +12,7 @@
/// Adds a single fingerprint to the atom
/atom/proc/add_fingerprint(mob/suspect, ignoregloves = FALSE) //Set ignoregloves to add prints irrespective of the mob having gloves on.
- if (QDELING(src))
+ if (QDELETED(src))
return
if (isnull(forensics))
forensics = new(src)
@@ -19,6 +21,8 @@
/// Add a list of fibers to the atom
/atom/proc/add_fiber_list(list/fibers_to_add) //ASSOC LIST FIBERTEXT = FIBERTEXT
+ if (QDELETED(src))
+ return
if (isnull(fibers_to_add))
return
if (forensics)
@@ -29,6 +33,8 @@
/// Adds a single fiber to the atom
/atom/proc/add_fibers(mob/living/carbon/human/suspect)
+ if (QDELETED(src))
+ return
var/old = 0
if(suspect.gloves && istype(suspect.gloves, /obj/item/clothing))
var/obj/item/clothing/gloves/suspect_gloves = suspect.gloves
@@ -47,6 +53,8 @@
/// Adds a list of hiddenprints to the atom
/atom/proc/add_hiddenprint_list(list/hiddenprints_to_add) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM
+ if (QDELETED(src))
+ return
if (isnull(hiddenprints_to_add))
return
if (forensics)
@@ -57,6 +65,8 @@
/// Adds a single hiddenprint to the atom
/atom/proc/add_hiddenprint(mob/suspect)
+ if (QDELETED(src))
+ return
if (isnull(forensics))
forensics = new(src)
forensics.add_hiddenprint(suspect)
@@ -67,6 +77,8 @@
return FALSE
/obj/add_blood_DNA(list/blood_DNA_to_add)
+ if (QDELETED(src))
+ return
. = ..()
if (isnull(blood_DNA_to_add))
return .
@@ -98,6 +110,8 @@
return FALSE
/mob/living/carbon/human/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
+ if (QDELETED(src))
+ return
if(wear_suit)
wear_suit.add_blood_DNA(blood_DNA_to_add)
update_worn_oversuit()
diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm
index 97f75f95061064e..9281bc6534361ea 100644
--- a/code/modules/hallucination/stray_bullet.dm
+++ b/code/modules/hallucination/stray_bullet.dm
@@ -34,6 +34,7 @@
damage = 0
projectile_type = /obj/projectile/hallucination
log_override = TRUE
+ do_not_log = TRUE
/// Our parent hallucination that's created us
var/datum/hallucination/parent
/// The image that represents our projectile itself
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index f41f607dabe120f..24e5274f7fbfeb1 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -456,8 +456,8 @@
/datum/holiday/france/greet()
return "Do you hear the people sing?"
-/datum/holiday/hotdogday //I have plans for this.
- name = "National Hot Dog Day"
+/datum/holiday/hotdogday
+ name = HOTDOG_DAY
begin_day = 17
begin_month = JULY
diff --git a/code/modules/hydroponics/grown/ambrosia.dm b/code/modules/hydroponics/grown/ambrosia.dm
index aa02ba7efa1bca8..2becc390f39802a 100644
--- a/code/modules/hydroponics/grown/ambrosia.dm
+++ b/code/modules/hydroponics/grown/ambrosia.dm
@@ -74,6 +74,8 @@
icon_state = "ambrosia_gaia"
light_system = OVERLAY_LIGHT
light_range = 3
+ light_power = 1.2
+ light_color = "#ffff00"
seed = /obj/item/seeds/ambrosia/gaia
wine_power = 70
wine_flavor = "the earthmother's blessing"
diff --git a/code/modules/hydroponics/grown/cereals.dm b/code/modules/hydroponics/grown/cereals.dm
index 2bcc2860458bbc0..744c0dc5b023cf4 100644
--- a/code/modules/hydroponics/grown/cereals.dm
+++ b/code/modules/hydroponics/grown/cereals.dm
@@ -25,6 +25,8 @@
grind_results = list(/datum/reagent/consumable/flour = 0)
tastes = list("wheat" = 1)
distill_reagent = /datum/reagent/consumable/ethanol/beer
+ slot_flags = ITEM_SLOT_MASK
+ worn_icon = 'icons/mob/clothing/head/hydroponics.dmi'
// Oat
/obj/item/seeds/wheat/oat
@@ -93,6 +95,8 @@
grind_results = list(/datum/reagent/consumable/flour = 0, /datum/reagent/blood = 0)
tastes = list("meatwheat" = 1)
can_distill = FALSE
+ slot_flags = ITEM_SLOT_MASK
+ worn_icon = 'icons/mob/clothing/head/hydroponics.dmi'
/obj/item/food/grown/meatwheat/attack_self(mob/living/user)
user.visible_message(span_notice("[user] crushes [src] into meat."), span_notice("You crush [src] into something that resembles meat."))
diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm
index d9a7e2f3e8b18ea..dcc4ef8daba3d8b 100644
--- a/code/modules/instruments/items.dm
+++ b/code/modules/instruments/items.dm
@@ -34,20 +34,7 @@
user.visible_message(span_suicide("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
-/obj/item/instrument/attack_self(mob/user)
- if(!ISADVANCEDTOOLUSER(user))
- to_chat(user, span_warning("You don't have the dexterity to do this!"))
- return TRUE
- interact(user)
-
-/obj/item/instrument/interact(mob/user)
- ui_interact(user)
-
-/obj/item/instrument/ui_interact(mob/living/user)
- if(!isliving(user) || user.stat != CONSCIOUS || (HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) && !ispAI(user)))
- return
-
- user.set_machine(src)
+/obj/item/instrument/ui_interact(mob/user, datum/tgui/ui)
song.ui_interact(user)
/obj/item/instrument/violin
@@ -130,9 +117,9 @@
. = ..()
AddElement(/datum/element/spooky)
-/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (src, 'sound/runtime/instruments/trombone/En4.mid', 100,1,-1)
- ..()
+/obj/item/instrument/trumpet/spectral/attack(mob/living/target_mob, mob/living/user, params)
+ playsound(src, 'sound/runtime/instruments/trombone/En4.mid', 1000, 1, -1)
+ return ..()
/obj/item/instrument/saxophone
name = "saxophone"
@@ -154,9 +141,9 @@
. = ..()
AddElement(/datum/element/spooky)
-/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (src, 'sound/runtime/instruments/saxophone/En4.mid', 100,1,-1)
- ..()
+/obj/item/instrument/saxophone/spectral/attack(mob/living/target_mob, mob/living/user, params)
+ playsound(src, 'sound/runtime/instruments/trombone/En4.mid', 1000, 1, -1)
+ return ..()
/obj/item/instrument/trombone
name = "trombone"
@@ -178,9 +165,9 @@
. = ..()
AddElement(/datum/element/spooky)
-/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
- playsound (src, 'sound/runtime/instruments/trombone/Cn4.mid', 100,1,-1)
- ..()
+/obj/item/instrument/trombone/spectral/attack(mob/living/target_mob, mob/living/user, params)
+ playsound(src, 'sound/runtime/instruments/trombone/Cn4.mid', 1000, 1, -1)
+ return ..()
/obj/item/instrument/recorder
name = "recorder"
@@ -201,19 +188,24 @@
w_class = WEIGHT_CLASS_SMALL
actions_types = list(/datum/action/item_action/instrument)
-/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
- SIGNAL_HANDLER
- if(song.playing && ismob(loc))
- to_chat(loc, span_warning("You stop playing the harmonica to talk..."))
- song.playing = FALSE
-
-/obj/item/instrument/harmonica/equipped(mob/M, slot)
+/obj/item/instrument/harmonica/equipped(mob/user, slot, initial = FALSE)
. = ..()
- RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ if(!(slot & slot_flags))
+ return
+ RegisterSignal(user, COMSIG_MOB_SAY, PROC_REF(handle_speech))
-/obj/item/instrument/harmonica/dropped(mob/M)
+/obj/item/instrument/harmonica/dropped(mob/user, silent = FALSE)
. = ..()
- UnregisterSignal(M, COMSIG_MOB_SAY)
+ UnregisterSignal(user, COMSIG_MOB_SAY)
+
+/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+ if(!song.playing)
+ return
+ if(!ismob(loc))
+ CRASH("[src] was still registered to listen in on [source] but was not found to be on their mob.")
+ to_chat(loc, span_warning("You stop playing the harmonica to talk..."))
+ song.playing = FALSE
/datum/action/item_action/instrument
name = "Use Instrument"
diff --git a/code/modules/instruments/piano_synth.dm b/code/modules/instruments/piano_synth.dm
index 71d0d96ef560dfc..8e107d494c779f8 100644
--- a/code/modules/instruments/piano_synth.dm
+++ b/code/modules/instruments/piano_synth.dm
@@ -148,7 +148,7 @@
stopped_playing.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/synth/proc/import_song()
- synth.song.ParseSong(song.value)
+ synth.song.ParseSong(new_song = song.value)
/obj/item/circuit_component/synth/proc/set_repetitions()
synth.song.set_repeats(repetitions.value)
@@ -169,7 +169,9 @@
synth.song.note_shift = clamp(note_shift.value, synth.song.note_shift_min, synth.song.note_shift_max)
/obj/item/circuit_component/synth/proc/set_sustain_mode()
- synth.song.sustain_mode = SSinstruments.note_sustain_modes[sustain_mode.value]
+ if(!(sustain_mode.value in SSinstruments.note_sustain_modes))
+ return
+ synth.song.sustain_mode = sustain_mode.value
/obj/item/circuit_component/synth/proc/set_sustain_value()
switch(synth.song.sustain_mode)
diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm
index 68039df21468ee3..fb0e4f087449a35 100644
--- a/code/modules/instruments/songs/_song.dm
+++ b/code/modules/instruments/songs/_song.dm
@@ -23,11 +23,6 @@
/// Are we currently playing?
var/playing = FALSE
- /// Are we currently editing?
- var/editing = TRUE
- /// Is the help screen open?
- var/help = FALSE
-
/// Repeats left
var/repeat = 0
/// Maximum times we can repeat
@@ -107,7 +102,6 @@
var/note_shift = 0
var/note_shift_min = -100
var/note_shift_max = 100
- var/can_noteshift = TRUE
/// The kind of sustain we're using
var/sustain_mode = SUSTAIN_LINEAR
/// When a note is considered dead if it is below this in volume
@@ -129,7 +123,7 @@
tempo = sanitize_tempo(tempo, TRUE)
src.parent = parent
if(instrument_ids)
- allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids)
+ allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids)
if(length(allowed_instrument_ids))
set_instrument(allowed_instrument_ids[1])
hearing_mobs = list()
@@ -217,8 +211,6 @@
delay_by = 0
current_chord = 1
music_player = user
- if(ismob(music_player))
- updateDialog(music_player)
START_PROCESSING(SSinstruments, src)
/**
@@ -328,12 +320,6 @@
/datum/song/proc/set_bpm(bpm)
tempo = sanitize_tempo(600 / bpm)
-/**
- * Updates the window for our users. Override down the line.
- */
-/datum/song/proc/updateDialog(mob/user)
- ui_interact(user)
-
/datum/song/process(wait)
if(!playing)
return PROCESS_KILL
@@ -359,7 +345,6 @@
/datum/song/proc/set_volume(volume)
src.volume = clamp(round(volume, 1), max(0, min_volume), min(100, max_volume))
update_sustain()
- updateDialog()
/**
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
@@ -367,7 +352,6 @@
/datum/song/proc/set_dropoff_volume(volume)
sustain_dropoff_volume = clamp(round(volume, 0.01), INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
- updateDialog()
/**
* Setter for setting exponential falloff factor.
@@ -375,7 +359,6 @@
/datum/song/proc/set_exponential_drop_rate(drop)
sustain_exponential_dropoff = clamp(round(drop, 0.00001), INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
- updateDialog()
/**
* Setter for setting linear falloff duration.
@@ -383,7 +366,6 @@
/datum/song/proc/set_linear_falloff_duration(duration)
sustain_linear_duration = clamp(round(duration * 10, world.tick_lag), world.tick_lag, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
- updateDialog()
/datum/song/vv_edit_var(var_name, var_value)
. = ..()
@@ -401,9 +383,6 @@
// subtype for handheld instruments, like violin
/datum/song/handheld
-/datum/song/handheld/updateDialog(mob/user)
- parent.ui_interact(user || usr)
-
/datum/song/handheld/should_stop_playing(atom/player)
. = ..()
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
@@ -414,9 +393,6 @@
// subtype for stationary structures, like pianos
/datum/song/stationary
-/datum/song/stationary/updateDialog(mob/user)
- parent.ui_interact(user || usr)
-
/datum/song/stationary/should_stop_playing(atom/player)
. = ..()
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm
index 58c0562c9b04d1a..927e03d055dcf27 100644
--- a/code/modules/instruments/songs/editor.dm
+++ b/code/modules/instruments/songs/editor.dm
@@ -1,96 +1,189 @@
-/**
- * Returns the HTML for the status UI for this song datum.
- */
-/datum/song/proc/instrument_status_ui()
- . = list()
- . += "