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) && ( + <> + +