diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 33b7853cfd0..e97653e5022 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -385,3 +385,8 @@ GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
#define SHOVE_BLOCKED (1<<5)
///If the obstacle is an object at the border of the turf (so no signal from being sent to the other turf)
#define SHOVE_DIRECTIONAL_BLOCKED (1<<6)
+
+///Deathmatch lobby current status
+#define DEATHMATCH_NOT_PLAYING 0
+#define DEATHMATCH_PRE_PLAYING 1
+#define DEATHMATCH_PLAYING 2
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index df1a5c50293..e73297b684f 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -951,6 +951,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STATION_TRAIT_UNNATURAL_ATMOSPHERE "station_trait_unnatural_atmosphere"
#define STATION_TRAIT_VENDING_SHORTAGE "station_trait_vending_shortage"
+///Deathmatch traits
+#define TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS "deathmath_explosive_implants"
+
/// This atom is currently spinning.
#define TRAIT_SPINNING "spinning"
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index 7f4fbd92936..dcbfab7fe5f 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -88,6 +88,8 @@
#define MAFIA_TRAIT "mafia"
/// Trait associated with ctf
#define CTF_TRAIT "ctf"
+/// Trait associated with deathmatch
+#define DEATHMATCH_TRAIT "deathmatch"
/// Trait associated with highlander
#define HIGHLANDER_TRAIT "highlander"
/// Trait given from playing pretend with baguettes
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 0210c44ee57..f9957f4c739 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -192,3 +192,6 @@
/// Orders mobs by health
/proc/cmp_mob_health(mob/living/mob_a, mob/living/mob_b)
return mob_b.health - mob_a.health
+
+/proc/cmp_deathmatch_mods(datum/deathmatch_modifier/a, datum/deathmatch_modifier/b)
+ return sorttext(b.name, a.name)
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 5f4208a532e..6cc0707c195 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -100,6 +100,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"STATION_TRAIT_UNNATURAL_ATMOSPHERE" = STATION_TRAIT_UNNATURAL_ATMOSPHERE,
"STATION_TRAIT_VENDING_SHORTAGE" = STATION_TRAIT_VENDING_SHORTAGE,
),
+ /datum/deathmatch_lobby = list(
+ "TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS" = TRAIT_DEATHMATCH_EXPLOSIVE_IMPLANTS,
+ ),
/datum/wound = list(
"TRAIT_WOUND_SCANNED" = TRAIT_WOUND_SCANNED,
),
diff --git a/code/datums/components/echolocation.dm b/code/datums/components/echolocation.dm
index 888de7af758..13ef8f4ed24 100644
--- a/code/datums/components/echolocation.dm
+++ b/code/datums/components/echolocation.dm
@@ -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/tameable.dm b/code/datums/components/tameable.dm
index 67325b489d3..43f48005bf8 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/elements/inverted_movement.dm b/code/datums/elements/inverted_movement.dm
new file mode 100644
index 00000000000..537e7343893
--- /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/game/atom/_atom.dm b/code/game/atom/_atom.dm
index 97e6ce6bed9..6dd4db7fe6e 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/objects/items/implants/implant_explosive.dm b/code/game/objects/items/implants/implant_explosive.dm
index 25966a24c68..51564799f6e 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/shrapnel.dm b/code/game/objects/items/shrapnel.dm
index cdc786fc8db..c07bc780c91 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/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 8f814412fb3..1f9e7bea36f 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."
@@ -98,6 +108,20 @@
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/deathmatch/deathmatch_controller.dm b/code/modules/deathmatch/deathmatch_controller.dm
index c288daefd4e..45b5f087c5b 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_lobby.dm b/code/modules/deathmatch/deathmatch_lobby.dm
index e498f662b5e..fb8077b4916 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 00000000000..e654db3d392
--- /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/mob/living/basic/clown/clown.dm b/code/modules/mob/living/basic/clown/clown.dm
index 88c2b9496a9..a1a7014b263 100644
--- a/code/modules/mob/living/basic/clown/clown.dm
+++ b/code/modules/mob/living/basic/clown/clown.dm
@@ -403,7 +403,7 @@
GRANT_ACTION(/datum/action/cooldown/regurgitate)
AddElement(/datum/element/swabable, CELL_LINE_TABLE_GLUTTON, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheesiehonkers, /obj/item/food/cornchips), tame_chance = 30, bonus_tame_chance = 0)
AddElement(/datum/element/damage_threshold, 10) //lots of fat to cushion blows.
/mob/living/basic/clown/mutant/glutton/attacked_by(obj/item/item, mob/living/user)
@@ -461,7 +461,7 @@
playsound(loc,'sound/items/eatfood.ogg', rand(30,50), TRUE)
flick("glutton_mouth", src)
-/mob/living/basic/clown/mutant/glutton/proc/tamed(mob/living/tamer)
+/mob/living/basic/clown/mutant/glutton/tamed(mob/living/tamer, atom/food)
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/glutton)
diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
index e5d49aa867b..1472a64039a 100644
--- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
+++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm
@@ -61,10 +61,10 @@
var/static/list/food_types
if(!food_types)
food_types = src.food_types.Copy()
- AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
AddElement(/datum/element/basic_eating, food_types = food_types)
-/mob/living/basic/cow/proc/tamed(mob/living/tamer)
+/mob/living/basic/cow/tamed(mob/living/tamer, atom/food)
buckle_lying = 0
visible_message("[src] [tame_message] as it seems to bond with [tamer].", "You [self_tame_message], recognizing [tamer] as your new pal.")
AddElement(/datum/element/ridable, /datum/component/riding/creature/cow)
diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
index b3af3c59a12..f4c1849883f 100644
--- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
+++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm
@@ -35,9 +35,9 @@
if(!food_types)
food_types = src.food_types.Copy()
AddElement(/datum/element/basic_eating, food_types = food_types)
- AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 15)
-/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer)
+/mob/living/basic/cow/moonicorn/tamed(mob/living/tamer, atom/food)
. = ..()
///stop killing my FRIENDS
faction |= tamer.faction
diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm
index 94183d044c5..6f732f16844 100644
--- a/code/modules/mob/living/basic/farm_animals/pig.dm
+++ b/code/modules/mob/living/basic/farm_animals/pig.dm
@@ -37,9 +37,9 @@
///wrapper for the tameable component addition so you can have non tamable cow subtypes
/mob/living/basic/pig/proc/make_tameable()
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 25, bonus_tame_chance = 15)
-/mob/living/basic/pig/proc/tamed(mob/living/tamer)
+/mob/living/basic/pig/tamed(mob/living/tamer, atom/food)
can_buckle = TRUE
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/pig)
diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm
index 7795ec630e6..4c2c76e84c2 100644
--- a/code/modules/mob/living/basic/farm_animals/pony.dm
+++ b/code/modules/mob/living/basic/farm_animals/pony.dm
@@ -41,9 +41,9 @@
AddElement(/datum/element/ai_retaliate)
AddElement(/datum/element/ai_flee_while_injured)
AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = unique_tamer)
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, unique = unique_tamer)
-/mob/living/basic/pony/proc/tamed(mob/living/tamer)
+/mob/living/basic/pony/tamed(mob/living/tamer, atom/food)
can_buckle = TRUE
buckle_lying = 0
playsound(src, 'sound/creatures/pony/snort.ogg', 50)
@@ -151,4 +151,4 @@
ponycolors = list("#5d566f", pick_weight(mane_colors))
name = pick("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
// Only one person can tame these fellas, and they only need one apple
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 100, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = unique_tamer)
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 100, bonus_tame_chance = 15, unique = unique_tamer)
diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
index c657fa42843..e452c156474 100644
--- a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
+++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm
@@ -65,15 +65,9 @@
make_tameable()
/mob/living/basic/mining/wolf/proc/make_tameable()
- AddComponent(\
- /datum/component/tameable,\
- food_types = list(/obj/item/food/meat/slab),\
- tame_chance = 15,\
- bonus_tame_chance = 5,\
- after_tame = CALLBACK(src, PROC_REF(tame_wolf)),\
- )
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab), tame_chance = 15, bonus_tame_chance = 5)
-/mob/living/basic/mining/wolf/proc/tame_wolf()
+/mob/living/basic/mining/wolf/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
// ride wolf, life good
AddElement(/datum/element/ridable, /datum/component/riding/creature/wolf)
diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
index bc3ad96417e..59f7ffb249d 100644
--- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
+++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm
@@ -105,15 +105,9 @@
return ..()
/mob/living/basic/mining/goldgrub/proc/make_tameable()
- AddComponent(\
- /datum/component/tameable,\
- food_types = list(/obj/item/stack/ore),\
- tame_chance = 25,\
- bonus_tame_chance = 5,\
- after_tame = CALLBACK(src, PROC_REF(tame_grub)),\
- )
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/stack/ore), tame_chance = 25, bonus_tame_chance = 5)
-/mob/living/basic/mining/goldgrub/proc/tame_grub()
+/mob/living/basic/mining/goldgrub/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(src.loc)
AddElement(/datum/element/ridable, /datum/component/riding/creature/goldgrub)
AddComponent(/datum/component/obeys_commands, pet_commands)
diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
index 6d197d8853a..db47e91ad26 100644
--- a/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
+++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.dm
@@ -68,13 +68,7 @@
AddComponent(/datum/component/basic_mob_attack_telegraph)
AddComponentFrom(INNATE_TRAIT, /datum/component/shovel_hands)
if (tameable)
- AddComponent(\
- /datum/component/tameable,\
- food_types = list(/obj/item/food/grown/ash_flora),\
- tame_chance = 10,\
- bonus_tame_chance = 5,\
- after_tame = CALLBACK(src, PROC_REF(tamed)),\
- )
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/ash_flora), tame_chance = 10, bonus_tame_chance = 5)
tentacles = new (src)
tentacles.Grant(src)
@@ -120,6 +114,9 @@
return
balloon_alert(user, "ready to ride")
qdel(attacking_item)
+ make_rideable()
+
+/mob/living/basic/mining/goliath/proc/make_rideable()
saddled = TRUE
buckle_lying = 0
add_overlay("goliath_saddled")
@@ -149,7 +146,7 @@
icon_state = tentacle_warning_state
/// Get ready for mounting
-/mob/living/basic/mining/goliath/proc/tamed()
+/mob/living/basic/mining/goliath/tamed(mob/living/tamer, atom/food)
tamed = TRUE
// Copy entire faction rather than just placing user into faction, to avoid tentacle peril on station
@@ -163,6 +160,12 @@
/mob/living/basic/mining/goliath/ranged_secondary_attack(atom/atom_target, modifiers)
tentacle_line?.Trigger(target = atom_target)
+/mob/living/basic/mining/goliath/saddled
+
+/mob/living/basic/mining/goliath/saddled/Initialize(mapload)
+ . = ..()
+ make_rideable()
+
/// Legacy Goliath mob with different sprites, largely the same behaviour
/mob/living/basic/mining/goliath/ancient
name = "ancient goliath"
diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm
index 11b4530dd7a..30f486e9416 100644
--- a/code/modules/mob/living/basic/minebots/minebot.dm
+++ b/code/modules/mob/living/basic/minebots/minebot.dm
@@ -53,13 +53,7 @@
AddElement(/datum/element/death_drops, death_drops)
add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT)
AddElement(/datum/element/footstep, FOOTSTEP_OBJ_ROBOT, 1, -6, sound_vary = TRUE)
- AddComponent(\
- /datum/component/tameable,\
- food_types = list(/obj/item/stack/ore),\
- tame_chance = 100,\
- bonus_tame_chance = 5,\
- after_tame = CALLBACK(src, PROC_REF(activate_bot)),\
- )
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/stack/ore), tame_chance = 100, bonus_tame_chance = 5)
var/static/list/innate_actions = list(
/datum/action/cooldown/mob_cooldown/minedrone/toggle_light = BB_MINEBOT_LIGHT_ABILITY,
@@ -154,7 +148,7 @@
return ACCESS_ALLOWED
return ACCESS_DISALLOWED
-/mob/living/basic/mining_drone/proc/activate_bot()
+/mob/living/basic/mining_drone/tamed(mob/living/tamer, atom/food)
AddComponent(/datum/component/obeys_commands, pet_commands)
/mob/living/basic/mining_drone/death(gibbed)
diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm
index f835d7c5e17..f513795e7f6 100644
--- a/code/modules/mob/living/basic/pets/dog/_dog.dm
+++ b/code/modules/mob/living/basic/pets/dog/_dog.dm
@@ -50,7 +50,7 @@
AddElement(/datum/element/pet_bonus, "woofs happily!")
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/unfriend_attacker, untamed_reaction = "%SOURCE% fixes %TARGET% with a look of betrayal.")
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = FALSE)
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat/slab/human/mutant/skeleton, /obj/item/stack/sheet/bone), tame_chance = 30, bonus_tame_chance = 15, unique = FALSE)
AddComponent(/datum/component/obeys_commands, pet_commands)
var/dog_area = get_area(src)
for(var/obj/structure/bed/dogbed/dog_bed in dog_area)
@@ -64,7 +64,7 @@
speech.emote_see = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers."))
///Proc to run on a successful taming attempt
-/mob/living/basic/pet/dog/proc/tamed(mob/living/tamer)
+/mob/living/basic/pet/dog/tamed(mob/living/tamer, atom/food)
visible_message(span_notice("[src] licks at [tamer] in a friendly manner!"))
/// A dog bone fully heals a dog, and befriends it if it's not your friend.
diff --git a/code/modules/mob/living/basic/pets/parrot/_parrot.dm b/code/modules/mob/living/basic/pets/parrot/_parrot.dm
index 6dd5a9a4051..c23481d85a7 100644
--- a/code/modules/mob/living/basic/pets/parrot/_parrot.dm
+++ b/code/modules/mob/living/basic/pets/parrot/_parrot.dm
@@ -91,13 +91,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
AddElement(/datum/element/strippable, GLOB.strippable_parrot_items)
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/listen_and_repeat, desired_phrases = get_static_list_of_phrases(), blackboard_key = BB_PARROT_REPEAT_STRING)
- AddComponent(\
- /datum/component/tameable,\
- food_types = edibles,\
- tame_chance = 100,\
- bonus_tame_chance = 0,\
- after_tame = CALLBACK(src, PROC_REF(tamed)),\
- )
+ AddComponent(/datum/component/tameable, food_types = edibles, tame_chance = 100, bonus_tame_chance = 0)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attacking))
RegisterSignal(src, COMSIG_MOB_CLICKON, PROC_REF(on_click))
@@ -445,7 +439,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
return returnable_list
-/mob/living/basic/parrot/proc/tamed()
+/mob/living/basic/parrot/tamed(mob/living/tamer, atom/food)
new /obj/effect/temp_visual/heart(drop_location())
/mob/living/basic/parrot/proc/drop_item_on_signal(mob/living/user)
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
index ebb6f1d23f7..e0700892523 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
@@ -95,10 +95,10 @@
AddComponent(/datum/component/aggro_emote, emote_list = string_list(list("gnashes")))
AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour)
if (tamer)
- on_tamed(tamer, feedback = FALSE)
+ tamed(tamer, feedback = FALSE)
befriend(tamer)
else
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5, after_tame = CALLBACK(src, PROC_REF(on_tamed)))
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/meat), tame_chance = 10, bonus_tame_chance = 5)
teleport = new(src)
teleport.Grant(src)
@@ -119,7 +119,7 @@
set_greyscale(colors = list(pick_weight(GLOB.carp_colors)))
/// Called when another mob has forged a bond of friendship with this one, passed the taming mob as 'tamer'
-/mob/living/basic/carp/proc/on_tamed(mob/tamer, feedback = TRUE)
+/mob/living/basic/carp/tamed(mob/living/tamer, atom/food, feedback = TRUE)
buckle_lying = 0
AddElement(/datum/element/ridable, ridable_data)
AddComponent(/datum/component/obeys_commands, tamed_commands)
diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
index b72815d8325..50685e1ebe0 100644
--- a/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
+++ b/code/modules/mob/living/basic/space_fauna/eyeball/_eyeball.dm
@@ -58,7 +58,7 @@
grant_actions_by_list(innate_actions)
AddElement(/datum/element/simple_flying)
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100, after_tame = CALLBACK(src, PROC_REF(on_tame)))
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/carrot), tame_chance = 100)
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
on_hit_overlay = mutable_appearance(icon, "[icon_state]_crying")
@@ -119,7 +119,7 @@
befriend(target)
COOLDOWN_START(src, eye_healing, 15 SECONDS)
-/mob/living/basic/eyeball/proc/on_tame(mob/tamer)
+/mob/living/basic/eyeball/tamed(mob/living/tamer, atom/food)
spin(spintime = 2 SECONDS, speed = 1)
//become passive to the humens
faction |= tamer.faction
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index c1ba3c12e36..cf12c2b4083 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -63,7 +63,7 @@
if (tame)
faction |= FACTION_NEUTRAL
else
- AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheese), tame_chance = 100, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/cheese), tame_chance = 100)
/mob/living/basic/mouse/Destroy()
SSmobs.cheeserats -= src
@@ -148,7 +148,7 @@
to_chat(entered, span_notice("[icon2html(src, entered)] Squeak!"))
/// Called when a mouse is hand-fed some cheese, it will stop being afraid of humans
-/mob/living/basic/mouse/proc/tamed(mob/living/tamer, obj/item/food/cheese/cheese)
+/mob/living/basic/mouse/tamed(mob/living/tamer, obj/item/food/cheese/cheese)
new /obj/effect/temp_visual/heart(loc)
faction |= FACTION_NEUTRAL
tame = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
index f5184a85434..a5dfface811 100644
--- a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
+++ b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
@@ -31,9 +31,9 @@
GRANT_ACTION(/datum/action/cooldown/tentacle_slap)
add_cell_sample()
- AddComponent(/datum/component/tameable, list(/obj/item/food/fries, /obj/item/food/cheesyfries, /obj/item/food/cornchips, /obj/item/food/carrotfries), tame_chance = 30, bonus_tame_chance = 0, after_tame = CALLBACK(src, PROC_REF(tamed)))
+ AddComponent(/datum/component/tameable, list(/obj/item/food/fries, /obj/item/food/cheesyfries, /obj/item/food/cornchips, /obj/item/food/carrotfries), tame_chance = 30, bonus_tame_chance = 0)
-/mob/living/simple_animal/hostile/vatbeast/proc/tamed(mob/living/tamer)
+/mob/living/simple_animal/hostile/vatbeast/tamed(mob/living/tamer, obj/item/food)
buckle_lying = 0
AddElement(/datum/element/ridable, /datum/component/riding/creature/vatbeast)
faction = list(FACTION_NEUTRAL)
diff --git a/code/modules/vehicles/motorized_wheelchair.dm b/code/modules/vehicles/motorized_wheelchair.dm
index 7d3b70baf95..f85d6667e91 100644
--- a/code/modules/vehicles/motorized_wheelchair.dm
+++ b/code/modules/vehicles/motorized_wheelchair.dm
@@ -26,12 +26,15 @@
/obj/vehicle/ridden/wheelchair/motorized/Initialize(mapload)
. = ..()
+ add_component_parts()
+ refresh_parts()
+
+/obj/vehicle/ridden/wheelchair/motorized/proc/add_component_parts()
// Add tier 1 stock parts so that non-crafted wheelchairs aren't empty
component_parts += GLOB.stock_part_datums[/datum/stock_part/capacitor]
component_parts += GLOB.stock_part_datums[/datum/stock_part/servo]
component_parts += GLOB.stock_part_datums[/datum/stock_part/servo]
power_cell = new /obj/item/stock_parts/cell(src)
- refresh_parts()
/obj/vehicle/ridden/wheelchair/motorized/make_ridable()
AddElement(/datum/element/ridable, /datum/component/riding/vehicle/wheelchair/motorized)
@@ -221,3 +224,12 @@
visible_message(span_warning("A bomb appears in [src], what the fuck?"))
obj_flags |= EMAGGED
return TRUE
+
+///Version with slightly better components. Used by deathmatches.
+/obj/vehicle/ridden/wheelchair/motorized/improved
+
+/obj/vehicle/ridden/wheelchair/motorized/improved/add_component_parts()
+ component_parts += GLOB.stock_part_datums[/datum/stock_part/capacitor/tier2]
+ component_parts += GLOB.stock_part_datums[/datum/stock_part/servo/tier2]
+ component_parts += GLOB.stock_part_datums[/datum/stock_part/servo/tier2]
+ power_cell = new /obj/item/stock_parts/cell/upgraded/plus(src)
diff --git a/tgstation.dme b/tgstation.dme
index f89ed647142..bceac90b94e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1425,6 +1425,7 @@
#include "code\datums\elements\hostile_machine.dm"
#include "code\datums\elements\human_biter.dm"
#include "code\datums\elements\immerse.dm"
+#include "code\datums\elements\inverted_movement.dm"
#include "code\datums\elements\item_fov.dm"
#include "code\datums\elements\item_scaling.dm"
#include "code\datums\elements\kneecapping.dm"
@@ -3836,6 +3837,7 @@
#include "code\modules\deathmatch\deathmatch_lobby.dm"
#include "code\modules\deathmatch\deathmatch_mapping.dm"
#include "code\modules\deathmatch\deathmatch_maps.dm"
+#include "code\modules\deathmatch\deathmatch_modifier.dm"
#include "code\modules\detectivework\evidence.dm"
#include "code\modules\detectivework\scanner.dm"
#include "code\modules\discord\accountlink.dm"
diff --git a/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx b/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx
index 2e9b8018187..5d83a80492c 100644
--- a/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx
+++ b/tgui/packages/tgui/interfaces/DeathmatchLobby.tsx
@@ -8,6 +8,7 @@ import {
Dropdown,
Flex,
Icon,
+ Modal,
Section,
Table,
} from '../components';
@@ -21,6 +22,16 @@ type PlayerLike = {
};
};
+type Modifier = {
+ name: string;
+ desc: string;
+ modpath: string;
+ selected: BooleanLike;
+ selectable: BooleanLike;
+ player_selected: BooleanLike;
+ player_selectable: BooleanLike;
+};
+
type Data = {
self: string;
host: BooleanLike;
@@ -36,6 +47,9 @@ type Data = {
min_players: number;
max_players: number;
};
+ mod_menu_open: BooleanLike;
+ modifiers: Modifier[];
+ active_mods: string;
loadoutdesc: string;
players: PlayerLike[];
observers: PlayerLike[];
@@ -43,8 +57,10 @@ type Data = {
export const DeathmatchLobby = (props) => {
const { act, data } = useBackend();
+ const { modifiers = [] } = data;
return (
-
+
+
@@ -170,6 +186,19 @@ export const DeathmatchLobby = (props) => {
}
/>
+ {data.active_mods}
+ {(!!data.admin || !!data.host) && (
+ <>
+
+
);
};
+
+const ModSelector = (props) => {
+ const { act, data } = useBackend();
+ const { admin, host, mod_menu_open, modifiers = [] } = data;
+ if (!mod_menu_open || !host || !admin) {
+ return null;
+ }
+ return (
+
+
+ );
+};