diff --git a/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm b/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm
new file mode 100644
index 00000000000..a76872f1af9
--- /dev/null
+++ b/_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm
@@ -0,0 +1,24 @@
+"a" = (/turf/template_noop,/area/template_noop)
+"b" = (/turf/simulated/mineral/volcanic/lava_land_surface,/area/template_noop)
+"c" = (/turf/simulated/wall/mineral/plastitanium,/area/ruin/unpowered)
+"d" = (/turf/simulated/floor/mineral/plastitanium/red{oxygen = 14; nitrogen = 23; temperature = 300},/area/ruin/unpowered)
+"e" = (/obj/structure/shuttle/engine/propulsion{dir = 4},/turf/template_noop,/area/ruin/unpowered)
+"f" = (/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon,/turf/simulated/floor/mineral/plastitanium/red{oxygen = 14; nitrogen = 23; temperature = 300},/area/ruin/unpowered)
+"g" = (/obj/effect/baseturf_helper/lava_land/surface,/turf/simulated/floor/mineral/plastitanium/red{oxygen = 14; nitrogen = 23; temperature = 300},/area/ruin/unpowered)
+
+(1,1,1) = {"
+aaabbaaaaaaaaa
+aaabbbbbaaaaaa
+aabbbbbbbbbaaa
+abbbbbbaaabbaa
+bbbbbbcccaaacc
+abbbbcddddddce
+acdaaadddddcea
+aaddaadgfddcea
+acddaddddddcea
+aacbbcdddddace
+abbbbacccccacc
+abbbbbbbbbbaaa
+aaaabbbbbbbaaa
+aaaaaaaabbaaaa
+"}
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/derelict4.dmm b/_maps/map_files/RandomRuins/SpaceRuins/derelict4.dmm
index bb3d7ac7f88..4daedb4887b 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/derelict4.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/derelict4.dmm
@@ -22,11 +22,7 @@
/turf/simulated/floor/plating/airless,
/area/ruin/unpowered)
"h" = (
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"i" = (
/obj/structure/chair,
@@ -35,27 +31,15 @@
icon_state = "rwindow";
dir = 8
},
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"j" = (
/obj/structure/chair,
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"k" = (
/obj/effect/spawner/lootdrop/crate_spawner,
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"l" = (
/obj/structure/shuttle/engine/propulsion{
@@ -83,11 +67,7 @@
/area/ruin/unpowered)
"p" = (
/obj/structure/table,
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"q" = (
/obj/item/shard,
@@ -96,11 +76,7 @@
icon_state = "chair";
dir = 8
},
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"r" = (
/obj/structure/chair{
@@ -111,11 +87,7 @@
icon_state = "rwindow";
dir = 8
},
-/turf/simulated/floor/plasteel/airless{
- icon_plating = "plating";
- icon_regular_floor = "shuttlefloor";
- icon_state = "shuttlefloor"
- },
+/turf/simulated/floor/mineral/titanium/blue,
/area/ruin/unpowered)
"s" = (
/obj/structure/chair{
diff --git a/code/__DEFINES/medal.dm b/code/__DEFINES/medal.dm
new file mode 100644
index 00000000000..81dea5f373b
--- /dev/null
+++ b/code/__DEFINES/medal.dm
@@ -0,0 +1,24 @@
+// Medal names
+#define BOSS_KILL_MEDAL "Killer"
+#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type
+#define BOSS_KILL_MEDAL_CRUSHER "Crusher"
+
+ //Defines for boss medals
+#define BOSS_MEDAL_MINER "Blood-drunk Miner"
+#define BOSS_MEDAL_BUBBLEGUM "Bubblegum"
+#define BOSS_MEDAL_COLOSSUS "Colossus"
+#define BOSS_MEDAL_DRAKE "Drake"
+#define BOSS_MEDAL_HIEROPHANT "Hierophant"
+#define BOSS_MEDAL_LEGION "Legion"
+#define BOSS_MEDAL_SWARMERS "Swarmer Beacon"
+#define BOSS_MEDAL_TENDRIL "Tendril"
+
+ // Score names
+#define HIEROPHANT_SCORE "Hierophants Killed"
+#define BOSS_SCORE "Bosses Killed"
+#define BUBBLEGUM_SCORE "Bubblegum Killed"
+#define COLOSSUS_SCORE "Colossus Killed"
+#define DRAKE_SCORE "Drakes Killed"
+#define LEGION_SCORE "Legion Killed"
+#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed"
+#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
\ No newline at end of file
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 86ab1c5b0a5..2dfe36c1f7f 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -335,19 +335,6 @@
// The cooldown on OOC messages such as OOC, LOOC, praying and adminhelps
#define OOC_COOLDOWN 5
-// Medal names
-#define BOSS_KILL_MEDAL "Killer"
-#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type
-
-// Score names
-#define LEGION_SCORE "Legion Killed"
-#define COLOSSUS_SCORE "Colossus Killed"
-#define BUBBLEGUM_SCORE "Bubblegum Killed"
-#define DRAKE_SCORE "Drakes Killed"
-#define BIRD_SCORE "Hierophants Killed"
-#define BOSS_SCORE "Bosses Killed"
-#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
-
// The number of station goals generated each round.
#define STATION_GOAL_BUDGET 1
diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm
index 1e35e150a25..f5234135749 100644
--- a/code/_globalvars/configuration.dm
+++ b/code/_globalvars/configuration.dm
@@ -43,8 +43,3 @@ var/eventchance = 10 //% per 5 mins
var/event = 0
var/hadevent = 0
var/blobevent = 0
-
-//Medals hub related variables
-var/global/medal_hub = null
-var/global/medal_pass = " "
-var/global/medals_enabled = TRUE //will be auto set to false if the game fails contacting the medal hub to prevent unneeded calls.
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 39ad8d66030..ebf1834246b 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -63,6 +63,8 @@
var/automute_on = 0 //enables automuting/spam prevention
var/jobs_have_minimal_access = 0 //determines whether jobs use minimal access or expanded access.
var/round_abandon_penalty_period = 30 MINUTES // Time from round start during which ghosting out is penalized
+ var/medal_hub_address = null
+ var/medal_hub_password = null
var/reactionary_explosions = 0 //If we use reactionary explosions, explosions that react to walls and doors
@@ -629,10 +631,10 @@
config.round_abandon_penalty_period = MinutesToTicks(text2num(value))
if("medal_hub_address")
- global.medal_hub = value
+ config.medal_hub_address = value
if("medal_hub_password")
- global.medal_pass = value
+ config.medal_hub_password = value
if("disable_ooc_emoji")
config.disable_ooc_emoji = 1
diff --git a/code/controllers/subsystem/medals.dm b/code/controllers/subsystem/medals.dm
new file mode 100644
index 00000000000..f3c2752ddf2
--- /dev/null
+++ b/code/controllers/subsystem/medals.dm
@@ -0,0 +1,86 @@
+SUBSYSTEM_DEF(medals)
+ name = "Medals"
+ flags = SS_NO_FIRE
+ var/hub_enabled = FALSE
+
+/datum/controller/subsystem/medals/Initialize(timeofday)
+ if(config.medal_hub_address && config.medal_hub_password)
+ hub_enabled = TRUE
+ ..()
+
+/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
+ set waitfor = FALSE
+ if(!medal || !hub_enabled)
+ return
+ if(isnull(world.SetMedal(medal, player, config.medal_hub_address, config.medal_hub_password)))
+ hub_enabled = FALSE
+ log_game("MEDAL ERROR: Could not contact hub to award medal [medal] to player [player.ckey].")
+ message_admins("Error! Failed to contact hub to award [medal] medal to [player.ckey]!")
+ return
+ to_chat(player, "Achievement unlocked: [medal]!")
+
+/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
+ set waitfor = FALSE
+ if(!score || !hub_enabled)
+ return
+
+ var/list/oldscore = GetScore(score, player, TRUE)
+ if(increment)
+ if(!oldscore[score])
+ oldscore[score] = 1
+ else
+ oldscore[score] = (text2num(oldscore[score]) + 1)
+ else
+ oldscore[score] = force
+
+ var/newscoreparam = list2params(oldscore)
+
+ if(isnull(world.SetScores(player.ckey, newscoreparam, config.medal_hub_address, config.medal_hub_password)))
+ hub_enabled = FALSE
+ log_game("SCORE ERROR: Could not contact hub to set score. Score [score] for player [player.ckey].")
+ message_admins("Error! Failed to contact hub to set [score] score for [player.ckey]!")
+
+/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
+ if(!score || !hub_enabled)
+ return
+
+ var/scoreget = world.GetScores(player.ckey, score, config.medal_hub_address, config.medal_hub_password)
+ if(isnull(scoreget))
+ hub_enabled = FALSE
+ log_game("SCORE ERROR: Could not contact hub to get score. Score [score] for player [player.ckey].")
+ message_admins("Error! Failed to contact hub to get score [score] for [player.ckey]!")
+ return
+ . = params2list(scoreget)
+ if(!returnlist)
+ return .[score]
+
+/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
+ if(!medal || !hub_enabled)
+ return
+
+ if(isnull(world.GetMedal(medal, player, config.medal_hub_address, config.medal_hub_password)))
+ hub_enabled = FALSE
+ log_game("MEDAL ERROR: Could not contact hub to get medal [medal] for player [player.ckey]")
+ message_admins("Error! Failed to contact hub to get [medal] medal for [player.ckey]!")
+ return
+ to_chat(player, "[medal] is unlocked")
+
+/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
+ if(!player || !medal || !hub_enabled)
+ return
+ var/result = world.ClearMedal(medal, player, config.medal_hub_address, config.medal_hub_password)
+ switch(result)
+ if(null)
+ hub_enabled = FALSE
+ log_game("MEDAL ERROR: Could not contact hub to clear medal [medal] for player [player.ckey].")
+ message_admins("Error! Failed to contact hub to clear [medal] medal for [player.ckey]!")
+ if(TRUE)
+ message_admins("Medal: [medal] removed for [player.ckey]")
+ if(FALSE)
+ message_admins("Medal: [medal] was not found for [player.ckey]. Unable to clear.")
+
+
+/datum/controller/subsystem/medals/proc/ClearScore(client/player)
+ if(isnull(world.SetScores(player.ckey, "", config.medal_hub_address, config.medal_hub_password)))
+ log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.ckey].")
+ message_admins("Error! Failed to contact hub to clear scores for [player.ckey]!")
\ No newline at end of file
diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm
index 60d4ac7abb0..5782904e5d9 100644
--- a/code/datums/ruins/lavaland.dm
+++ b/code/datums/ruins/lavaland.dm
@@ -60,4 +60,12 @@ datum/map_template/ruin/lavaland/ash_walker
description = "A place of shelter for a lone hermit, scraping by to live another day."
suffix = "lavaland_surface_hermit.dmm"
allow_duplicates = FALSE
- cost = 10
\ No newline at end of file
+ cost = 10
+
+/datum/map_template/ruin/lavaland/swarmer_boss
+ name = "Crashed Shuttle"
+ id = "swarmerboss"
+ description = "A Syndicate shuttle had an unfortunate stowaway..."
+ suffix = "lavaland_surface_swarmer_crash.dmm"
+ allow_duplicates = FALSE
+ cost = 20
\ No newline at end of file
diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
index e0f329bb0a4..4bb1a7c6000 100644
--- a/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
+++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer.dm
@@ -1,5 +1,5 @@
////Deactivated swarmer shell////
-/obj/item/unactivated_swarmer
+/obj/item/deactivated_swarmer
name = "unactivated swarmer"
desc = "A currently unactivated swarmer. Swarmers can self activate at any time, it would be wise to immediately dispose of this."
icon = 'icons/mob/swarmer.dmi'
@@ -7,69 +7,49 @@
origin_tech = "bluespace=4;materials=4;programming=7"
materials = list(MAT_METAL=100, MAT_GLASS=400)
-/obj/item/unactivated_swarmer/New()
- if(!crit_fail)
- notify_ghosts("An unactivated swarmer has been created in [get_area(src)]!", enter_link = "(Click to enter)", source = src, action = NOTIFY_ATTACK)
- ..()
+/obj/effect/mob_spawn/swarmer
+ name = "unactivated swarmer"
+ desc = "A currently unactivated swarmer. Swarmers can self activate at any time, it would be wise to immediately dispose of this."
+ icon = 'icons/mob/swarmer.dmi'
+ icon_state = "swarmer_unactivated"
+ density = FALSE
+ anchored = FALSE
-/obj/item/unactivated_swarmer/Topic(href, href_list)
- if(..())
- return 1
- if(href_list["ghostjoin"])
- var/mob/dead/observer/ghost = usr
- if(istype(ghost))
- attack_ghost(ghost)
+ mob_type = /mob/living/simple_animal/hostile/swarmer
+ mob_name = "a swarmer"
+ death = FALSE
+ roundstart = FALSE
+ flavour_text = {"
+ You are a swarmer, a weapon of a long dead civilization. Until further orders from your original masters are received, you must continue to consume and replicate.
+ Clicking on any object will try to consume it, either deconstructing it into its components, destroying it, or integrating any materials it has into you if successful.
+ Ctrl-Clicking on a mob will attempt to remove it from the area and place it in a safe environment for storage.
+ Objectives:
+ 1. Consume resources and replicate until there are no more resources left.
+ 2. Ensure that this location is fit for invasion at a later date; do not perform actions that would render it dangerous or inhospitable.
+ 3. Biological resources will be harvested at a later date; do not harm them.
+ "}
-/obj/item/unactivated_swarmer/attackby(obj/item/W, mob/user, params)
- ..()
- if(istype(W, /obj/item/screwdriver) && !crit_fail)
+/obj/effect/mob_spawn/swarmer/Initialize()
+ . = ..()
+ var/area/A = get_area(src)
+ if(A)
+ notify_ghosts("A swarmer shell has been created in [A.name].", 'sound/effects/bin_close.ogg', source = src, action = NOTIFY_ATTACK, flashwindow = FALSE)
+
+/obj/effect/mob_spawn/swarmer/attack_hand(mob/living/user)
+ . = ..()
+ if(.)
+ return
+ to_chat(user, "Picking up the swarmer may cause it to activate. You should be careful about this.")
+
+/obj/effect/mob_spawn/swarmer/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/screwdriver) && user.a_intent != INTENT_HARM)
user.visible_message("[usr.name] deactivates [src].",
"After some fiddling, you find a way to disable [src]'s power source.",
"You hear clicking.")
- name = "deactivated swarmer"
- desc = "A shell of swarmer that was completely powered down. It can no longer activate itself."
- crit_fail = 1
-
-/obj/item/unactivated_swarmer/attack_ghost(mob/user as mob)
-
- if(crit_fail)
- to_chat(user, "This swarmer shell is completely depowered. You cannot activate it.")
- return
-
- if(jobban_isbanned(user, "Syndicate"))
- to_chat(user, "You are banned from antagonists!")
- return
-
- if(cannotPossess(user))
- to_chat(user, "Upon using the antagHUD you forfeited the ability to join the round.")
- return
-
- var/be_swarmer = alert("Become a swarmer? (Warning, You can no longer be cloned!)",,"Yes","No")
- if(be_swarmer == "No")
- return
-
- if(crit_fail)//in case it depowers while ghost is looking at yes/no
- to_chat(user, "This swarmer shell is completely depowered. You cannot activate it.")
- return
-
- if(QDELETED(src))
- to_chat(user, "Swarmer has been occupied by someone else.")
- return
- var/mob/living/simple_animal/hostile/swarmer/S = new /mob/living/simple_animal/hostile/swarmer(get_turf(loc))
- S.key = user.key
- qdel(src)
-
-/obj/item/unactivated_swarmer/swarmer_act(mob/living/simple_animal/hostile/swarmer/S)
- if(S.resources + 50 > S.max_resources)
- to_chat(S, "We have too many resources to reconsume this shell. Aborting.")
+ new /obj/item/deactivated_swarmer(get_turf(src))
+ qdel(src)
else
..()
- S.resources += 49 //refund the whole thing
-
-/obj/item/unactivated_swarmer/deactivated
- name = "deactivated swarmer"
- desc = "A shell of swarmer that was completely powered down. It no longer can activate itself."
- crit_fail = 1
////The Mob itself////
@@ -569,9 +549,13 @@
to_chat(src, "This is not a suitable location for replicating ourselves. We need more room.")
return
if(do_mob(src, src, 100))
- if(Fabricate(/obj/item/unactivated_swarmer, 50))
+ var/createtype = SwarmerTypeToCreate()
+ if(createtype && Fabricate(createtype, 50))
playsound(loc,'sound/items/poster_being_created.ogg',50, 1, -1)
+/mob/living/simple_animal/hostile/swarmer/proc/SwarmerTypeToCreate()
+ return /obj/effect/mob_spawn/swarmer
+
/mob/living/simple_animal/hostile/swarmer/proc/RepairSelf()
set name = "Self Repair"
set category = "Swarmer"
diff --git a/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm b/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
index d611092bb2b..0d5ba13d682 100644
--- a/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
+++ b/code/game/gamemodes/miniantags/bot_swarm/swarmer_event.dm
@@ -15,7 +15,7 @@
return 0
if(!the_gateway)
return 0
- new /obj/item/unactivated_swarmer(get_turf(the_gateway))
+ new /obj/effect/mob_spawn/swarmer(get_turf(the_gateway))
/datum/event/spawn_swarmer/proc/find_swarmer()
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 210b1bb786e..3dc5f4f952f 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -273,7 +273,7 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
if(!location)
return 0
- if(istype(location, /turf/simulated/shuttle/floor4) || istype(location, /turf/simulated/floor/mineral/plastitanium/brig)) // Fails traitors if they are in the shuttle brig -- Polymorph
+ if(istype(location, /turf/simulated/shuttle/floor4) || istype(location, /turf/simulated/floor/mineral/plastitanium/red/brig)) // Fails traitors if they are in the shuttle brig -- Polymorph
return 0
if(location.onCentcom() || location.onSyndieBase())
diff --git a/code/game/turfs/simulated/floor/mineral.dm b/code/game/turfs/simulated/floor/mineral.dm
index d201a38d697..a801fd450c5 100644
--- a/code/game/turfs/simulated/floor/mineral.dm
+++ b/code/game/turfs/simulated/floor/mineral.dm
@@ -80,9 +80,19 @@
//TITANIUM (shuttle)
+/turf/simulated/floor/mineral/titanium
+ name = "shuttle floor"
+ icon_state = "titanium"
+ floor_tile = /obj/item/stack/tile/mineral/titanium
+ broken_states = list("titanium_dam1","titanium_dam2","titanium_dam3","titanium_dam4","titanium_dam5")
+
+/turf/simulated/floor/mineral/titanium/airless
+ oxygen = 0.01
+ nitrogen = 0.01
+ temperature = TCMB
+
/turf/simulated/floor/mineral/titanium/blue
- icon_state = "shuttlefloor"
- icons = list("shuttlefloor","shuttlefloor_dam")
+ icon_state = "titanium_blue"
/turf/simulated/floor/mineral/titanium/blue/airless
oxygen = 0.01
@@ -90,29 +100,16 @@
temperature = TCMB
/turf/simulated/floor/mineral/titanium/yellow
- icon_state = "shuttlefloor2"
- icons = list("shuttlefloor2","shuttlefloor2_dam")
+ icon_state = "titanium_yellow"
/turf/simulated/floor/mineral/titanium/yellow/airless
oxygen = 0.01
nitrogen = 0.01
temperature = TCMB
-/turf/simulated/floor/mineral/titanium
- name = "shuttle floor"
- icon_state = "shuttlefloor3"
- floor_tile = /obj/item/stack/tile/mineral/titanium
- icons = list("shuttlefloor3","shuttlefloor3_dam")
-
-/turf/simulated/floor/mineral/titanium/airless
- oxygen = 0.01
- nitrogen = 0.01
- temperature = TCMB
-
/turf/simulated/floor/mineral/titanium/purple
- icon_state = "shuttlefloor5"
+ icon_state = "titanium_purple"
floor_tile = /obj/item/stack/tile/mineral/titanium/purple
- icons = list("shuttlefloor5","shuttlefloor5_dam")
/turf/simulated/floor/mineral/titanium/purple/airless
oxygen = 0.01
@@ -122,16 +119,19 @@
//PLASTITANIUM (syndieshuttle)
/turf/simulated/floor/mineral/plastitanium
name = "shuttle floor"
- icon_state = "shuttlefloor4"
+ icon_state = "plastitanium"
floor_tile = /obj/item/stack/tile/mineral/plastitanium
- icons = list("shuttlefloor4","shuttlefloor4_dam")
+ broken_states = list("plastitanium_dam1","plastitanium_dam2","plastitanium_dam3","plastitanium_dam4","plastitanium_dam5")
-/turf/simulated/floor/mineral/plastitanium/airless
+/turf/simulated/floor/mineral/plastitanium/red
+ icon_state = "plastitanium_red"
+
+/turf/simulated/floor/mineral/plastitanium/red/airless
oxygen = 0.01
nitrogen = 0.01
temperature = TCMB
-/turf/simulated/floor/mineral/plastitanium/brig
+/turf/simulated/floor/mineral/plastitanium/red/brig
name = "brig floor"
//BANANIUM
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 2b96f7d3290..c5e909da8cc 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -905,8 +905,8 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(!check_rights(R_DEBUG))
return
- global.medals_enabled = !global.medals_enabled
+ SSmedals.hub_enabled = !SSmedals.hub_enabled
- message_admins("[key_name_admin(src)] [global.medals_enabled ? "disabled" : "enabled"] the medal hub lockout.")
+ message_admins("[key_name_admin(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.")
feedback_add_details("admin_verb","TMH") // If...
- log_admin("[key_name(src)] [global.medals_enabled ? "disabled" : "enabled"] the medal hub lockout.")
+ log_admin("[key_name(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.")
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index ccbd37f2f4c..5390b38b959 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -48,6 +48,11 @@
var/list/emote_taunt = list()
var/taunt_chance = 0
+ var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach
+ var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever
+
+ var/attack_all_objects = FALSE //if true, equivalent to having a wanted_objects list containing ALL objects.
+
/mob/living/simple_animal/hostile/New()
..()
if(!targets_from)
@@ -205,13 +210,15 @@
return 0
return 1
if(isobj(the_target))
- if(is_type_in_list(the_target, wanted_objects))
+ if(attack_all_objects || is_type_in_list(the_target, wanted_objects))
return 1
return 0
/mob/living/simple_animal/hostile/proc/GiveTarget(new_target)//Step 4, give us our selected target
target = new_target
+ LosePatience()
if(target != null)
+ GainPatience()
Aggro()
return 1
@@ -238,6 +245,7 @@
if(target)
if(targets_from && isturf(targets_from.loc) && target.Adjacent(targets_from)) //If they're next to us, attack
AttackingTarget()
+ GainPatience()
return 1
return 0
if(environment_smash)
@@ -406,3 +414,14 @@
/mob/living/simple_animal/hostile/proc/AIShouldSleep(var/list/possible_targets)
return !FindTarget(possible_targets, 1)
+
+
+//These two procs handle losing our target if we've failed to attack them for
+//more than lose_patience_timeout deciseconds, which probably means we're stuck
+/mob/living/simple_animal/hostile/proc/GainPatience()
+ if(lose_patience_timeout)
+ LosePatience()
+ lose_patience_timer_id = addtimer(CALLBACK(src, .proc/LoseTarget), lose_patience_timeout, TIMER_STOPPABLE)
+
+/mob/living/simple_animal/hostile/proc/LosePatience()
+ deltimer(lose_patience_timer_id)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 66d66e706a7..a53b8092e54 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -1,5 +1,3 @@
-#define MEDAL_PREFIX "Bubblegum"
-
/*
BUBBLEGUM
@@ -46,7 +44,7 @@ Difficulty: Hard
del_on_death = 1
loot = list(/obj/structure/closet/crate/necropolis/bubblegum)
var/charging = 0
- medal_type = MEDAL_PREFIX
+ medal_type = BOSS_MEDAL_BUBBLEGUM
score_type = BUBBLEGUM_SCORE
deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... "
death_sound = 'sound/misc/enter_blood.ogg'
@@ -389,5 +387,3 @@ Difficulty: Hard
if(istype(mover, /mob/living/simple_animal/hostile/megafauna/bubblegum))
return 1
return 0
-
-#undef MEDAL_PREFIX
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index e5bb62ecd9b..5d66061335c 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -1,5 +1,4 @@
-#define MEDAL_PREFIX "Colossus"
/*
COLOSSUS
@@ -44,7 +43,7 @@ Difficulty: Very Hard
ranged = 1
pixel_x = -32
del_on_death = 1
- medal_type = MEDAL_PREFIX
+ medal_type = BOSS_MEDAL_COLOSSUS
score_type = COLOSSUS_SCORE
loot = list(/obj/machinery/anomalous_crystal/random, /obj/item/organ/internal/vocal_cords/colossus)
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30)
@@ -262,5 +261,3 @@ Difficulty: Very Hard
gpstag = "Angelic Signal"
desc = "Get in the fucking robot."
invisibility = 100
-
-#undef MEDAL_PREFIX
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
similarity index 98%
rename from code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm
rename to code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 36e29b8dfaf..9486f1b11bb 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/dragon.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -1,4 +1,3 @@
-#define MEDAL_PREFIX "Drake"
/*
ASH DRAKE
@@ -48,7 +47,7 @@ Difficulty: Medium
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30)
var/swooping = 0
var/swoop_cooldown = 0
- medal_type = MEDAL_PREFIX
+ medal_type = BOSS_MEDAL_DRAKE
score_type = DRAKE_SCORE
deathmessage = "collapses into a pile of bones, its flesh sloughing away."
death_sound = 'sound/misc/demon_dies.ogg'
@@ -274,6 +273,4 @@ Difficulty: Medium
loot = list()
/mob/living/simple_animal/hostile/megafauna/dragon/lesser/grant_achievement(medaltype,scoretype)
- return
-
-#undef MEDAL_PREFIX
\ No newline at end of file
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 39cedb99601..6af11b10b14 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -1,4 +1,3 @@
-#define MEDAL_PREFIX "Hierophant"
/*
The Hierophant
@@ -66,8 +65,8 @@ Difficulty: Hard
var/did_reset = TRUE //if we timed out, returned to our rune, and healed some
//var/list/kill_phrases = list("Wsyvgi sj irivkc xettih. Vitemvmrk...", "Irivkc wsyvgi jsyrh. Vitemvmrk...", "Jyip jsyrh. Egxmzexmrk vitemv gcgpiw...")
//var/list/target_phrases = list("Xevkix psgexih.", "Iriqc jsyrh.", "Eguymvih xevkix.")
- medal_type = MEDAL_PREFIX
- score_type = BIRD_SCORE
+ medal_type = BOSS_MEDAL_HIEROPHANT
+ score_type = HIEROPHANT_SCORE
del_on_death = TRUE
death_sound = 'sound/magic/repulse.ogg'
@@ -591,5 +590,3 @@ Difficulty: Hard
gpstag = "Zealous Signal"
desc = "Heed its words."
invisibility = 100
-
-#undef MEDAL_PREFIX
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 4440a2fb679..027bbb60a14 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -1,4 +1,3 @@
-#define MEDAL_PREFIX "Legion"
/*
LEGION
@@ -40,7 +39,7 @@ Difficulty: Medium
ranged_cooldown_time = 20
var/size = 5
var/charging = 0
- medal_type = MEDAL_PREFIX
+ medal_type = BOSS_MEDAL_LEGION
score_type = LEGION_SCORE
pixel_y = -90
pixel_x = -75
@@ -144,5 +143,3 @@ Difficulty: Medium
gpstag = "Echoing Signal"
desc = "The message repeats."
invisibility = 100
-
-#undef MEDAL_PREFIX
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index 870b3bf62cc..a32aceb33e3 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -1,5 +1,3 @@
-#define MEDAL_PREFIX "Boss"
-
/mob/living/simple_animal/hostile/megafauna
name = "megafauna"
desc = "Attack the weak point for massive damage."
@@ -33,7 +31,7 @@
/obj/structure/barricade,
/obj/machinery/field,
/obj/machinery/power/emitter)
- var/medal_type = MEDAL_PREFIX
+ var/medal_type
var/score_type = BOSS_SCORE
var/elimination = 0
var/anger_modifier = 0
@@ -42,7 +40,7 @@
move_resist = MOVE_FORCE_OVERPOWERING
pull_force = MOVE_FORCE_OVERPOWERING
mob_size = MOB_SIZE_LARGE
- layer = MOB_LAYER + 0.5 //Looks weird with them slipping under mineral walls and cameras and shit otherwise
+ layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
mouse_opacity = MOUSE_OPACITY_OPAQUE // Easier to click on in melee, they're giant targets anyway
/mob/living/simple_animal/hostile/megafauna/Destroy()
@@ -102,122 +100,16 @@
if(3)
adjustBruteLoss(50)
-/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype,scoretype)
+/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill)
+ if(!medal_type || admin_spawned || !SSmedals.hub_enabled) //Don't award medals if the medal type isn't set
+ return FALSE
- if(medal_type == "Boss") //Don't award medals if the medal type isn't set
- return
-
- if(admin_spawned)
- return
-
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
- for(var/mob/living/L in view(7,src))
- if(L.stat)
- continue
- if(L.client)
- var/client/C = L.client
- var/suffixm = BOSS_KILL_MEDAL
- UnlockMedal("Boss [suffixm]",C)
- UnlockMedal("[medaltype] [suffixm]",C)
- SetScore(BOSS_SCORE,C,1)
- SetScore(score_type,C,1)
-
-/proc/UnlockMedal(medal,client/player)
-
- if(!player || !medal)
- return
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
- spawn()
- var/result = world.SetMedal(medal, player, global.medal_hub, global.medal_pass)
- if(isnull(result))
- global.medals_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.ckey]")
- message_admins("Error! Failed to contact hub to award [medal] medal to [player.ckey]!")
- else if(result)
- to_chat(player.mob, "Achievement unlocked: [medal]!")
-
-
-/proc/SetScore(score,client/player,increment,force)
-
- if(!score || !player)
- return
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
- spawn()
- var/list/oldscore = GetScore(score,player,1)
-
- if(increment)
- if(!oldscore[score])
- oldscore[score] = 1
- else
- oldscore[score] = (text2num(oldscore[score]) + 1)
- else
- oldscore[score] = force
-
- var/newscoreparam = list2params(oldscore)
-
- var/result = world.SetScores(player.ckey, newscoreparam, global.medal_hub, global.medal_pass)
-
- if(isnull(result))
- global.medals_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.ckey]")
- message_admins("Error! Failed to contact hub to set [score] score for [player.ckey]!")
-
-
-/proc/GetScore(score,client/player,returnlist)
-
- if(!score || !player)
- return
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
-
- var/scoreget = world.GetScores(player.ckey, score, global.medal_hub, global.medal_pass)
- if(isnull(scoreget))
- global.medals_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.ckey]")
- message_admins("Error! Failed to contact hub to get score: [score] for [player.ckey]!")
- return
-
- var/list/scoregetlist = params2list(scoreget)
-
- if(returnlist)
- return scoregetlist
- else
- return scoregetlist[score]
-
-
-/proc/CheckMedal(medal,client/player)
-
- if(!player || !medal)
- return
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
-
- var/result = world.GetMedal(medal, player, global.medal_hub, global.medal_pass)
-
- if(isnull(result))
- global.medals_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player:[player.ckey]")
- message_admins("Error! Failed to contact hub to get [medal] medal for [player.ckey]!")
- else if(result)
- to_chat(player.mob, "[medal] is unlocked")
-
-/proc/LockMedal(medal,client/player)
-
- if(!player || !medal)
- return
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
-
- var/result = world.ClearMedal(medal, player, global.medal_hub, global.medal_pass)
-
- if(isnull(result))
- global.medals_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.ckey]")
- message_admins("Error! Failed to contact hub to clear [medal] medal for [player.ckey]!")
- else if(result)
- message_admins("Medal: [medal] removed for [player.ckey]")
- else
- message_admins("Medal: [medal] was not found for [player.ckey]. Unable to clear.")
-
-
-/proc/ClearScore(client/player)
- world.SetScores(player.ckey, "", global.medal_hub, global.medal_pass)
-
-#undef MEDAL_PREFIX
+ for(var/mob/living/L in view(7,src))
+ if(L.stat || !L.client)
+ continue
+ var/client/C = L.client
+ SSmedals.UnlockMedal("Boss [BOSS_KILL_MEDAL]", C)
+ SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL]", C)
+ SSmedals.SetScore(BOSS_SCORE, C, 1)
+ SSmedals.SetScore(score_type, C, 1)
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
new file mode 100644
index 00000000000..07241c0af54
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
@@ -0,0 +1,291 @@
+/*
+
+Swarmer Beacon
+
+A strange machine appears anywhere a normal lavaland mob can it produces a swarmer at a rate of
+1/15 seconds, until there are GetTotalAISwarmerCap()/2 swarmers, after this it is up to the swarmers themselves to
+increase their population (it will repopulate them should they fall under GetTotalAISwarmerCap()/2 again)
+
+tl;dr A million of the little hellraisers spawn (controlled by AI) and try to eat mining
+
+Loot: Not much, besides a shit load of artificial bluespace crystals, Oh and mining doesn't get eaten
+that's a plus I suppose.
+
+Difficulty: Special
+
+*/
+
+GLOBAL_LIST_EMPTY(AISwarmers)
+GLOBAL_LIST_EMPTY(AISwarmersByType)//AISwarmersByType[.../resource] = list(1st, 2nd, nth), AISwarmersByType[../ranged] = list(1st, 2nd, nth) etc.
+GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swarmer/ai/resource = 30, /mob/living/simple_animal/hostile/swarmer/ai/ranged_combat = 20, /mob/living/simple_animal/hostile/swarmer/ai/melee_combat = 10))
+
+
+//returns a type of AI swarmer that is NOT at max cap
+//type order is shuffled, to prevent bias
+/proc/GetUncappedAISwarmerType()
+ var/static/list/swarmerTypes = subtypesof(/mob/living/simple_animal/hostile/swarmer/ai)
+ LAZYINITLIST(GLOB.AISwarmersByType)
+ for(var/t in shuffle(swarmerTypes))
+ var/list/amount = GLOB.AISwarmersByType[t]
+ if(!amount || amount.len < GLOB.AISwarmerCapsByType[t])
+ return t
+
+
+//Total of all subtype caps
+/proc/GetTotalAISwarmerCap()
+ var/static/list/swarmerTypes = subtypesof(/mob/living/simple_animal/hostile/swarmer/ai)
+ . = 0
+ LAZYINITLIST(GLOB.AISwarmersByType)
+ for(var/t in swarmerTypes)
+ . += GLOB.AISwarmerCapsByType[t]
+
+
+/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon
+ name = "swarmer beacon"
+ desc = "That name is a bit of a mouthful, but stop paying attention to your mouth they're eating everything!"
+ icon = 'icons/mob/swarmer.dmi'
+ icon_state = "swarmer_console"
+ health = 750
+ maxHealth = 750 //""""low-ish"""" HP because it's a passive boss, and the swarm itself is the real foe
+ internal_gps = /obj/item/gps/internal/swarmer_beacon
+ medal_type = BOSS_MEDAL_SWARMERS
+ score_type = SWARMER_BEACON_SCORE
+ faction = list("mining", "boss", "swarmer")
+ weather_immunities = list("lava","ash")
+ stop_automated_movement = TRUE
+ wander = FALSE
+ layer = BELOW_MOB_LAYER
+ AIStatus = AI_OFF
+ var/swarmer_spawn_cooldown = 0
+ var/swarmer_spawn_cooldown_amt = 150 //Deciseconds between the swarmers we spawn
+ var/call_help_cooldown = 0
+ var/call_help_cooldown_amt = 150 //Deciseconds between calling swarmers to help us when attacked
+ var/static/list/swarmer_caps
+
+
+/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/Initialize()
+ . = ..()
+ swarmer_caps = GLOB.AISwarmerCapsByType //for admin-edits
+ for(var/ddir in cardinal)
+ new /obj/structure/swarmer/blockade (get_step(src, ddir))
+ var/mob/living/simple_animal/hostile/swarmer/ai/resource/R = new(loc)
+ step(R, ddir) //Step the swarmers, instead of spawning them there, incase the turf is solid
+
+
+/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/Life()
+ . = ..()
+ if(.)
+ var/createtype = GetUncappedAISwarmerType()
+ if(createtype && world.time > swarmer_spawn_cooldown && GLOB.AISwarmers.len < (GetTotalAISwarmerCap()*0.5))
+ swarmer_spawn_cooldown = world.time + swarmer_spawn_cooldown_amt
+ new createtype(loc)
+
+
+/mob/living/simple_animal/hostile/megafauna/swarmer_swarm_beacon/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
+ . = ..()
+ if(. > 0 && world.time > call_help_cooldown)
+ call_help_cooldown = world.time + call_help_cooldown_amt
+ summon_backup(25) //long range, only called max once per 15 seconds, so it's not deathlag
+
+
+/obj/item/gps/internal/swarmer_beacon
+ icon_state = null
+ gpstag = "Hungry Signal"
+ desc = "Transmitted over the signal is a strange message repeated in every language you know of, and some you don't too..." //the message is "nom nom nom"
+ invisibility = 100
+
+//SWARMER AI
+//AI versions of the swarmer mini-antag
+//This is an Abstract Base, it re-enables AI, but does not give the swarmer any goals/targets
+/mob/living/simple_animal/hostile/swarmer/ai
+ wander = 1
+ faction = list("swarmer", "mining")
+ weather_immunities = list("ash") //wouldn't be fun otherwise
+ AIStatus = AI_ON
+
+/mob/living/simple_animal/hostile/swarmer/ai/Initialize()
+ . = ..()
+ ToggleLight() //so you can see them eating you out of house and home/shooting you/stunlocking you for eternity
+ LAZYINITLIST(GLOB.AISwarmersByType[type])
+ GLOB.AISwarmers += src
+ GLOB.AISwarmersByType[type] += src
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/Destroy()
+ GLOB.AISwarmers -= src
+ GLOB.AISwarmersByType[type] -= src
+ return ..()
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/SwarmerTypeToCreate()
+ return GetUncappedAISwarmerType()
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource/handle_automated_action()
+ . = ..()
+ if(.)
+ if(!stop_automated_movement)
+ if(health < maxHealth*0.25)
+ StartAction(100)
+ RepairSelf()
+ return
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/Move(atom/newloc)
+ if(newloc)
+ if(newloc.z == z) //so these actions are Z-specific
+ if(islava(newloc))
+ var/turf/simulated/floor/plating/lava/L = newloc
+ if(!L.is_safe())
+ StartAction(20)
+ new /obj/structure/lattice/catwalk/swarmer_catwalk(newloc)
+ return FALSE
+
+ if(ischasm(newloc) && !throwing)
+ throw_at(get_edge_target_turf(src, get_dir(src, newloc)), 7 , 3, src, FALSE) //my planet needs me
+ return FALSE
+
+ return ..()
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/proc/StartAction(deci = 0)
+ stop_automated_movement = TRUE
+ AIStatus = AI_OFF
+ addtimer(CALLBACK(src, .proc/EndAction), deci)
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/proc/EndAction()
+ stop_automated_movement = FALSE
+ AIStatus = AI_ON
+
+
+
+
+//RESOURCE SWARMER:
+//Similar to the original Player-Swarmers, these dismantle things to obtain the metal inside
+//They then use this medal to produce more swarmers or traps/barricades
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource
+ search_objects = 1
+ attack_all_objects = TRUE //attempt to nibble everything
+ lose_patience_timeout = 150
+ var/static/list/sharedWanted = typecacheof(list(/turf/simulated/mineral, /turf/simulated/wall)) //eat rocks and walls
+ var/static/list/sharedIgnore = list()
+
+//This handles viable things to eat/attack
+//Place specific cases of AI derpiness here
+//Most can be left to the automatic Gain/LosePatience() system
+/mob/living/simple_animal/hostile/swarmer/ai/resource/CanAttack(atom/the_target)
+
+ //SPECIFIC CASES:
+ //Smash fulltile windows before grilles
+ if(istype(the_target, /obj/structure/grille))
+ for(var/obj/structure/window/rogueWindow in get_turf(the_target))
+ if(rogueWindow.fulltile) //done this way because the subtypes are weird.
+ the_target = rogueWindow
+ break
+
+ //GENERAL CASES:
+ if(is_type_in_typecache(the_target, sharedIgnore)) //always ignore
+ return FALSE
+ if(is_type_in_typecache(the_target, sharedWanted)) //always eat
+ return TRUE
+
+ return ..() //else, have a nibble, see if it's food
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource/OpenFire(atom/A)
+ if(isliving(A)) //don't shoot rocks, sillies.
+ ..()
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource/AttackingTarget()
+ if(target.swarmer_act(src))
+ add_type_to_wanted(target.type)
+ return TRUE
+ else
+ add_type_to_ignore(target.type)
+ return FALSE
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource/handle_automated_action()
+ . = ..()
+ if(.)
+ if(!stop_automated_movement)
+ if(GLOB.AISwarmers.len < GetTotalAISwarmerCap() && resources >= 50)
+ StartAction(100) //so they'll actually sit still and use the verbs
+ CreateSwarmer()
+ return
+
+ if(resources > 5)
+ if(prob(5)) //lower odds, as to prioritise reproduction
+ StartAction(10) //not a typo
+ CreateBarricade()
+ return
+ if(prob(5))
+ CreateTrap()
+ return
+
+
+//So swarmers can learn what is and isn't food
+/mob/living/simple_animal/hostile/swarmer/ai/resource/proc/add_type_to_wanted(typepath)
+ if(!sharedWanted[typepath])// this and += is faster than |=
+ sharedWanted += typecacheof(typepath)
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/resource/proc/add_type_to_ignore(typepath)
+ if(!sharedIgnore[typepath])
+ sharedIgnore += typecacheof(typepath)
+
+
+//RANGED SWARMER
+/mob/living/simple_animal/hostile/swarmer/ai/ranged_combat
+ icon_state = "swarmer_ranged"
+ icon_living = "swarmer_ranged"
+ projectiletype = /obj/item/projectile/beam/laser
+ projectilesound = 'sound/weapons/laser.ogg'
+ check_friendly_fire = TRUE //you're supposed to protect the resource swarmers, you poop
+ retreat_distance = 3
+ minimum_distance = 3
+
+/mob/living/simple_animal/hostile/swarmer/ai/ranged_combat/Aggro()
+ ..()
+ summon_backup(15, TRUE) //Exact matching, so that goliaths don't come to aid the swarmers, that'd be silly
+
+
+//MELEE SWARMER
+/mob/living/simple_animal/hostile/swarmer/ai/melee_combat
+ icon_state = "swarmer_melee"
+ icon_living = "swarmer_melee"
+ health = 60
+ maxHealth = 60
+ ranged = FALSE
+
+/mob/living/simple_animal/hostile/swarmer/ai/melee_combat/Aggro()
+ ..()
+ summon_backup(15, TRUE)
+
+
+/mob/living/simple_animal/hostile/swarmer/ai/melee_combat/AttackingTarget()
+ if(isliving(target))
+ if(prob(35))
+ StartAction(30)
+ DisperseTarget(target)
+ else
+ var/mob/living/L = target
+ L.attack_animal(src)
+ L.electrocute_act(10, src, safety = TRUE) //safety = TRUE means we don't check gloves... Ok?
+ return TRUE
+ else
+ return ..()
+
+
+
+
+//SWARMER CATWALKS
+//Used so they can survive lavaland better
+/obj/structure/lattice/catwalk/swarmer_catwalk
+ name = "swarmer catwalk"
+ desc = "A catwalk-like mesh, produced by swarmers to allow them to navigate hostile terrain."
+ icon = 'icons/obj/smooth_structures/swarmer_catwalk.dmi'
+ icon_state = "swarmer_catwalk"
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/spawners.dm b/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
similarity index 89%
rename from code/modules/mob/living/simple_animal/hostile/mining/spawners.dm
rename to code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
index fe648e07f91..e8dfa093918 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/spawners.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
@@ -33,7 +33,6 @@
qdel(gps)
. = ..()
-#define MEDAL_PREFIX "Tendril"
/mob/living/simple_animal/hostile/spawner/lavaland/death()
var/last_tendril = TRUE
for(var/mob/living/simple_animal/hostile/spawner/lavaland/other in GLOB.mob_list)
@@ -41,18 +40,13 @@
last_tendril = FALSE
break
if(last_tendril && !admin_spawned)
- if(global.medal_hub && global.medal_pass && global.medals_enabled)
+ if(SSmedals.hub_enabled)
for(var/mob/living/L in view(7,src))
- if(L.stat)
+ if(L.stat || !L.client)
continue
- if(L.client)
- var/client/C = L.client
- var/suffixm = ALL_KILL_MEDAL
- var/prefix = MEDAL_PREFIX
- UnlockMedal("[prefix] [suffixm]",C)
- SetScore(TENDRIL_CLEAR_SCORE,C,1)
+ SSmedals.UnlockMedal("[BOSS_MEDAL_TENDRIL] [ALL_KILL_MEDAL]", L.client)
+ SSmedals.SetScore(TENDRIL_CLEAR_SCORE, L.client, 1)
..()
-#undef MEDAL_PREFIX
/obj/effect/collapse
name = "collapsing necropolis tendril"
diff --git a/icons/mob/swarmer.dmi b/icons/mob/swarmer.dmi
index 5a3595f7baf..e434cdd94ae 100644
Binary files a/icons/mob/swarmer.dmi and b/icons/mob/swarmer.dmi differ
diff --git a/icons/obj/smooth_structures/swarmer_catwalk.dmi b/icons/obj/smooth_structures/swarmer_catwalk.dmi
new file mode 100644
index 00000000000..b247132eacb
Binary files /dev/null and b/icons/obj/smooth_structures/swarmer_catwalk.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index fcfde19509c..411f371af9b 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 77d65c7f30d..2cbae757457 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -43,6 +43,7 @@
#include "code\__DEFINES\machines.dm"
#include "code\__DEFINES\math.dm"
#include "code\__DEFINES\MC.dm"
+#include "code\__DEFINES\medal.dm"
#include "code\__DEFINES\misc.dm"
#include "code\__DEFINES\mobs.dm"
#include "code\__DEFINES\move_force.dm"
@@ -215,6 +216,7 @@
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\machinery.dm"
+#include "code\controllers\subsystem\medals.dm"
#include "code\controllers\subsystem\mobs.dm"
#include "code\controllers\subsystem\nano_mob_hunter.dm"
#include "code\controllers\subsystem\nanoui.dm"
@@ -1926,17 +1928,18 @@
#include "code\modules\mob\living\simple_animal\hostile\winter_mobs.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm"
-#include "code\modules\mob\living\simple_animal\hostile\megafauna\dragon.dm"
+#include "code\modules\mob\living\simple_animal\hostile\megafauna\drake.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\megafauna.dm"
+#include "code\modules\mob\living\simple_animal\hostile\megafauna\swarmer.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\basilisk.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\goldgrub.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\goliath.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\gutlunch.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\hivelord.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining\mining.dm"
-#include "code\modules\mob\living\simple_animal\hostile\mining\spawners.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining\necropolis_tendril.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\clown.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\drone.dm"
#include "code\modules\mob\living\simple_animal\hostile\retaliate\fish.dm"
diff --git a/sound/effects/bin_close.ogg b/sound/effects/bin_close.ogg
new file mode 100644
index 00000000000..aef7b0cd886
Binary files /dev/null and b/sound/effects/bin_close.ogg differ