From 34baba306d35f78b12c480fcdb6f59dd27981b48 Mon Sep 17 00:00:00 2001
From: Crystalic <39885003+blackcrystall@users.noreply.github.com>
Date: Fri, 28 Jun 2024 22:12:39 +0500
Subject: [PATCH 1/2] faction stage 1, point of no return
---
code/__DEFINES/factions.dm | 174 ++
code/_globalvars/global_lists.dm | 39 -
code/datums/event_info_text.dm | 40 +-
.../factions/__misc/autobalance_payload.dm | 320 ++++
code/datums/factions/__misc/faction_tasks.dm | 472 ++++++
code/datums/factions/__misc/helpers.dm | 39 +
.../factions/__misc/relations_actions.dm | 75 +
.../datums/factions/__misc/relations_datum.dm | 60 +
code/datums/factions/_modules/hive.dm | 1440 +++++++++++++++++
code/datums/factions/clf.dm | 16 +-
code/datums/factions/colonists.dm | 42 +
code/datums/factions/contractor.dm | 7 +-
code/datums/factions/dutch's_dozen.dm | 6 +
code/datums/factions/faction.dm | 1033 +++++++++++-
code/datums/factions/freelancer.dm | 7 +
code/datums/factions/gladiator.dm | 5 +
code/datums/factions/hefa_order.dm | 8 +
code/datums/factions/helpers.dm | 14 -
code/datums/factions/mercenary.dm | 5 +
code/datums/factions/pirate.dm | 5 +
code/datums/factions/pizza_delivery.dm | 7 +
code/datums/factions/ress.dm | 5 +
code/datums/factions/souto.dm | 5 +
code/datums/factions/twe.dm | 12 +
.../commando.dm} | 22 +-
code/datums/factions/upp.dm | 77 +-
code/datums/factions/uscm.dm | 11 +-
code/datums/factions/uscm/cmb.dm | 4 +
code/datums/factions/uscm/marine.dm | 117 ++
code/datums/factions/uscm/marsoc.dm | 6 +
code/datums/factions/wy.dm | 34 +
code/datums/factions/wy/deathsquad.dm | 6 +
code/datums/factions/{ => wy}/pmc.dm | 35 +-
code/datums/factions/xeno.dm | 390 +++++
code/datums/factions/xeno/alpha.dm | 8 +
code/datums/factions/xeno/bravo.dm | 8 +
code/datums/factions/xeno/charlie.dm | 8 +
code/datums/factions/xeno/corrupted.dm | 40 +
code/datums/factions/xeno/delta.dm | 8 +
code/datums/factions/xeno/feral.dm | 14 +
code/datums/factions/xeno/forsaken.dm | 14 +
code/datums/factions/xeno/mutated.dm | 10 +
code/datums/factions/xeno/normal.dm | 51 +
code/datums/factions/xeno/renegade.dm | 14 +
code/datums/factions/xeno/yautja.dm | 14 +
code/datums/factions/yautja.dm | 47 +
code/datums/factions/zombie.dm | 60 +
code/modules/admin/tabs/event_tab.dm | 31 +-
code/modules/admin/verbs/custom_event.dm | 29 +-
colonialmarines.dme | 40 +-
50 files changed, 4755 insertions(+), 179 deletions(-)
create mode 100644 code/__DEFINES/factions.dm
create mode 100644 code/datums/factions/__misc/autobalance_payload.dm
create mode 100644 code/datums/factions/__misc/faction_tasks.dm
create mode 100644 code/datums/factions/__misc/helpers.dm
create mode 100644 code/datums/factions/__misc/relations_actions.dm
create mode 100644 code/datums/factions/__misc/relations_datum.dm
create mode 100644 code/datums/factions/_modules/hive.dm
create mode 100644 code/datums/factions/colonists.dm
create mode 100644 code/datums/factions/dutch's_dozen.dm
create mode 100644 code/datums/factions/freelancer.dm
create mode 100644 code/datums/factions/gladiator.dm
create mode 100644 code/datums/factions/hefa_order.dm
delete mode 100644 code/datums/factions/helpers.dm
create mode 100644 code/datums/factions/mercenary.dm
create mode 100644 code/datums/factions/pirate.dm
create mode 100644 code/datums/factions/pizza_delivery.dm
create mode 100644 code/datums/factions/ress.dm
create mode 100644 code/datums/factions/souto.dm
create mode 100644 code/datums/factions/twe.dm
rename code/datums/factions/{royalmarinescommando.dm => twe/commando.dm} (90%)
create mode 100644 code/datums/factions/uscm/cmb.dm
create mode 100644 code/datums/factions/uscm/marine.dm
create mode 100644 code/datums/factions/uscm/marsoc.dm
create mode 100644 code/datums/factions/wy.dm
create mode 100644 code/datums/factions/wy/deathsquad.dm
rename code/datums/factions/{ => wy}/pmc.dm (72%)
create mode 100644 code/datums/factions/xeno.dm
create mode 100644 code/datums/factions/xeno/alpha.dm
create mode 100644 code/datums/factions/xeno/bravo.dm
create mode 100644 code/datums/factions/xeno/charlie.dm
create mode 100644 code/datums/factions/xeno/corrupted.dm
create mode 100644 code/datums/factions/xeno/delta.dm
create mode 100644 code/datums/factions/xeno/feral.dm
create mode 100644 code/datums/factions/xeno/forsaken.dm
create mode 100644 code/datums/factions/xeno/mutated.dm
create mode 100644 code/datums/factions/xeno/normal.dm
create mode 100644 code/datums/factions/xeno/renegade.dm
create mode 100644 code/datums/factions/xeno/yautja.dm
create mode 100644 code/datums/factions/yautja.dm
create mode 100644 code/datums/factions/zombie.dm
diff --git a/code/__DEFINES/factions.dm b/code/__DEFINES/factions.dm
new file mode 100644
index 000000000000..73d49539ecbb
--- /dev/null
+++ b/code/__DEFINES/factions.dm
@@ -0,0 +1,174 @@
+//FACTION NAMES
+#define FACTION_NEUTRAL "neutral"
+//USCM
+#define FACTION_USCM "uscm"
+#define FACTION_MARINE "cm"
+#define FACTION_CMB "cmb"
+#define FACTION_MARSOC "msoc"
+//CONTRACTOR
+#define FACTION_CONTRACTOR "contractor"
+//WY
+#define FACTION_WY "wey_yu"
+#define FACTION_PMC "pmc"
+#define FACTION_WY_DEATHSQUAD "wy_death_sqaud"
+//UPP
+#define FACTION_UPP "upp"
+//CLF
+#define FACTION_CLF "clf"
+//COLON
+#define FACTION_COLONIST "colonist"
+//OTHER
+#define FACTION_RESS "ress"
+#define FACTION_TWE "twe"
+#define FACTION_MERCENARY "mercenary"
+#define FACTION_FREELANCER "freelancer"
+#define FACTION_HEFA "hefa_order"
+#define FACTION_DUTCH "dutch's_dozen"
+#define FACTION_PIRATE "pirate"
+#define FACTION_GLADIATOR "gladiator"
+#define FACTION_PIZZA "pizza_delivery"
+#define FACTION_SOUTO "souto"
+//ZOMBIE
+#define FACTION_ZOMBIE "zombie"
+//YAUTJA
+#define FACTION_YAUTJA "yautja"
+//XENOS
+#define FACTION_XENOMORPH "xeno"
+#define FACTION_XENOMORPH_NORMAL "xenomorph"
+#define FACTION_XENOMORPH_CORRUPTED "corrupted_xenomoprh"
+#define FACTION_XENOMORPH_ALPHA "alpha_xenomorph"
+#define FACTION_XENOMORPH_BRAVO "bravo_xenomorph"
+#define FACTION_XENOMORPH_CHARLIE "charlie_xenomorph"
+#define FACTION_XENOMORPH_DELTA "delta_xenomorph"
+#define FACTION_XENOMORPH_FERAL "feral_xenomorph"
+#define FACTION_XENOMORPH_FORSAKEN "forsaken_xenomorph"
+#define FACTION_XENOMORPH_TAMED "tamed_xenomorph"
+#define FACTION_XENOMORPH_MUTATED "mutated_xenomorph"
+#define FACTION_XENOMORPH_YAUTJA "yautja_xenomorph"
+#define FACTION_XENOMORPH_RENEGADE "renegade_xenomorph"
+
+#define FACTION_LIST_MARINE list(FACTION_USCM, FACTION_MARINE, FACTION_CMB, FACTION_MARSOC)
+#define FACTION_LIST_WY list(FACTION_WY, FACTION_PMC, FACTION_WY_DEATHSQUAD)
+#define FACTION_LIST_HUMANOID list(FACTION_NEUTRAL, FACTION_CONTRACTOR, FACTION_CLF, FACTION_UPP, FACTION_FREELANCER, FACTION_COLONIST, FACTION_MERCENARY, FACTION_DUTCH, FACTION_HEFA, FACTION_GLADIATOR, FACTION_PIRATE, FACTION_PIZZA, FACTION_SOUTO, FACTION_YAUTJA) + FACTION_LIST_MARINE + FACTION_LIST_WY
+#define FACTION_LIST_XENOMORPH list(FACTION_XENOMORPH_NORMAL, FACTION_XENOMORPH_CORRUPTED, FACTION_XENOMORPH_ALPHA, FACTION_XENOMORPH_BRAVO, FACTION_XENOMORPH_CHARLIE, FACTION_XENOMORPH_DELTA, FACTION_XENOMORPH_FERAL, FACTION_XENOMORPH_FORSAKEN, FACTION_XENOMORPH_TAMED, FACTION_XENOMORPH_MUTATED, FACTION_XENOMORPH_YAUTJA, FACTION_XENOMORPH_RENEGADE)
+#define FACTION_LIST_ALL FACTION_LIST_HUMANOID + FACTION_LIST_XENOMORPH
+/// This is factions handle defcons
+#define FACTION_LIST_DEFCONED list(FACTION_USCM, FACTION_MARINE, FACTION_UPP)
+
+//FACTIONS RELATIONS
+#define RELATIONS_FACTION_NEUTRAL list(FACTION_USCM = RELATIONS_NEUTRAL, FACTION_WY = RELATIONS_NEUTRAL, FACTION_UPP = RELATIONS_NEUTRAL, FACTION_CLF = RELATIONS_NEUTRAL, FACTION_COLONIST = RELATIONS_NEUTRAL, FACTION_RESS = RELATIONS_NEUTRAL, FACTION_TWE = RELATIONS_NEUTRAL, FACTION_MERCENARY = RELATIONS_NEUTRAL, FACTION_FREELANCER = RELATIONS_NEUTRAL, FACTION_THREEWE = RELATIONS_NEUTRAL)
+#define RELATIONS_FACTION_USCM list(FACTION_WY = RELATIONS_FRIENDLY, FACTION_UPP = RELATIONS_HOSTILE, FACTION_CLF = RELATIONS_HOSTILE, FACTION_COLONIST = RELATIONS_NEUTRAL, FACTION_RESS = RELATIONS_FRIENDLY, FACTION_TWE = RELATIONS_FRIENDLY, FACTION_MERCENARY = RELATIONS_NEUTRAL, FACTION_FREELANCER = RELATIONS_NEUTRAL, FACTION_THREEWE = RELATIONS_TENSE, FACTION_NEUTRAL = RELATIONS_NEUTRAL)
+#define RELATIONS_FACTION_WY list(FACTION_USCM = RELATIONS_FRIENDLY, FACTION_UPP = RELATIONS_NEUTRAL, FACTION_CLF = RELATIONS_HOSTILE, FACTION_COLONIST = RELATIONS_NEUTRAL, FACTION_RESS = RELATIONS_FRIENDLY, FACTION_TWE = RELATIONS_HOSTILE, FACTION_MERCENARY = RELATIONS_NEUTRAL, FACTION_FREELANCER = RELATIONS_NEUTRAL, FACTION_THREEWE = RELATIONS_NEUTRAL, FACTION_NEUTRAL = RELATIONS_NEUTRAL)
+#define RELATIONS_FACTION_CLF list(FACTION_USCM = RELATIONS_HOSTILE, FACTION_WY = RELATIONS_NEUTRAL, FACTION_UPP = RELATIONS_HOSTILE, FACTION_COLONIST = RELATIONS_NEUTRAL, FACTION_RESS = RELATIONS_HOSTILE, FACTION_TWE = RELATIONS_HOSTILE, FACTION_MERCENARY = RELATIONS_HOSTILE, FACTION_FREELANCER = RELATIONS_HOSTILE, FACTION_THREEWE = RELATIONS_HOSTILE, FACTION_NEUTRAL = RELATIONS_NEUTRAL)
+#define RELATIONS_FACTION_UPP list(FACTION_USCM = RELATIONS_HOSTILE, FACTION_WY = RELATIONS_FRIENDLY, FACTION_CLF = RELATIONS_HOSTILE, FACTION_COLONIST = RELATIONS_NEUTRAL, FACTION_RESS = RELATIONS_HOSTILE, FACTION_TWE = RELATIONS_FRIENDLY, FACTION_MERCENARY = RELATIONS_HOSTILE, FACTION_FREELANCER = RELATIONS_HOSTILE, FACTION_THREEWE = RELATIONS_HOSTILE, FACTION_NEUTRAL = RELATIONS_NEUTRAL)
+#define RELATIONS_FACTION_XENOMORPH list(FACTION_XENOMORPH_NORMAL = RELATIONS_HOSTILE, FACTION_XENOMORPH_CORRUPTED = RELATIONS_HOSTILE, FACTION_XENOMORPH_ALPHA = RELATIONS_HOSTILE, FACTION_XENOMORPH_BRAVO = RELATIONS_HOSTILE, FACTION_XENOMORPH_CHARLIE = RELATIONS_HOSTILE, FACTION_XENOMORPH_DELTA = RELATIONS_HOSTILE, FACTION_XENOMORPH_FERAL = RELATIONS_HOSTILE, FACTION_XENOMORPH_FORSAKEN = RELATIONS_HOSTILE, FACTION_XENOMORPH_TAMED = RELATIONS_HOSTILE, FACTION_XENOMORPH_MUTATED = RELATIONS_HOSTILE, FACTION_XENOMORPH_YAUTJA = RELATIONS_HOSTILE, FACTION_XENOMORPH_RENEGADE = RELATIONS_HOSTILE, FACTION_NEUTRAL = RELATIONS_NEUTRAL)
+
+#define RELATIONS_MAP list(FACTION_NEUTRAL = null, FACTION_USCM = null, FACTION_MARINE = null, FACTION_CMB = null, FACTION_MARSOC = null, FACTION_CONTRACTOR = null, FACTION_WY = null, FACTION_PMC = null, FACTION_WY_DEATHSQUAD = null, FACTION_CLF = null, FACTION_UPP = null, FACTION_FREELANCER = null, FACTION_COLONIST = null, FACTION_MERCENARY = null, FACTION_DUTCH = null, FACTION_HEFA = null, FACTION_GLADIATOR = null, FACTION_PIRATE = null, FACTION_PIZZA = null, FACTION_SOUTO = null, FACTION_YAUTJA = null, FACTION_XENOMORPH_NORMAL = null, FACTION_XENOMORPH_CORRUPTED = null, FACTION_XENOMORPH_ALPHA = null, FACTION_XENOMORPH_BRAVO = null, FACTION_XENOMORPH_CHARLIE = null, FACTION_XENOMORPH_DELTA = null, FACTION_XENOMORPH_FERAL = null, FACTION_XENOMORPH_FORSAKEN = null, FACTION_XENOMORPH_TAMED = null, FACTION_XENOMORPH_MUTATED = null, FACTION_XENOMORPH_YAUTJA = null)
+#define RELATIONS_MAP_HOSTILE list(FACTION_USCM = RELATIONS_HOSTILE, FACTION_WY = RELATIONS_HOSTILE, FACTION_UPP = RELATIONS_HOSTILE, FACTION_CLF = RELATIONS_HOSTILE, FACTION_COLONIST = RELATIONS_HOSTILE, FACTION_RESS = RELATIONS_HOSTILE, FACTION_TWE = RELATIONS_HOSTILE, FACTION_MERCENARY = RELATIONS_HOSTILE, FACTION_FREELANCER = RELATIONS_HOSTILE, FACTION_THREEWE = RELATIONS_HOSTILE, FACTION_XENOMORPH_NORMAL = RELATIONS_HOSTILE, FACTION_XENOMORPH_CORRUPTED = RELATIONS_HOSTILE, FACTION_XENOMORPH_ALPHA = RELATIONS_HOSTILE, FACTION_XENOMORPH_BRAVO = RELATIONS_HOSTILE, FACTION_XENOMORPH_CHARLIE = RELATIONS_HOSTILE, FACTION_XENOMORPH_DELTA = RELATIONS_HOSTILE, FACTION_XENOMORPH_FERAL = RELATIONS_HOSTILE, FACTION_XENOMORPH_FORSAKEN = RELATIONS_HOSTILE, FACTION_XENOMORPH_TAMED = RELATIONS_HOSTILE, FACTION_XENOMORPH_MUTATED = RELATIONS_HOSTILE, FACTION_XENOMORPH_YAUTJA = RELATIONS_HOSTILE, FACTION_NEUTRAL = RELATIONS_HOSTILE)
+
+#define RELATIONS_UNKNOWN null
+#define RELATIONS_DISABLED list(0, 0)
+#define RELATIONS_WAR list(1, 200)
+#define RELATIONS_HOSTILE list(201, 400)
+#define RELATIONS_TENSE list(401, 500)
+#define RELATIONS_NEUTRAL list(501, 700)
+#define RELATIONS_FRIENDLY list(701, 900)
+#define RELATIONS_VERY_GOOD list(901, 1000)
+#define RELATIONS_SELF 1100
+#define RELATIONS_MAX 1000
+
+//FACTION TREES
+#define SIDE_FACTION_NEUTRAL "NEUTRAL_T"
+#define SIDE_FACTION_USCM "USCM_T"
+#define SIDE_FACTION_WY "W-Y_T"
+#define SIDE_FACTION_CLF "CLF_T"
+#define SIDE_FACTION_UPP "UPP_T"
+#define SIDE_FACTION_ZOMBIE "ZOMBIE_T"
+#define SIDE_FACTION_YAUTJA "YAUTJA_T"
+#define SIDE_FACTION_XENOMORPH "XENOMORPH_T"
+
+#define SIDE_ORGANICAL_DOM list(SIDE_FACTION_ZOMBIE, SIDE_FACTION_XENOMORPH)
+
+#define SITREP_INTERVAL 15 MINUTES
+
+//NAMES
+#define NAME_FACTION_NEUTRAL "Neutral Faction"
+//USCM
+#define NAME_FACTION_USCM "United States Colonial Marines"
+#define NAME_FACTION_MARINE "Colonial Marines"
+#define NAME_FACTION_CMB "Colonial Marshal Bureau"
+#define NAME_FACTION_MARSOC "Marine Special Operations Command"
+//CONTRACTOR
+#define NAME_FACTION_CONTRACTOR "Vanguard's Arrow Incorporated"
+//WY
+#define NAME_FACTION_WY "Weyland-Yutani"
+#define NAME_FACTION_PMC "Private Military Company"
+#define NAME_FACTION_WY_DEATHSQUAD "Corporate Commandos"
+//UPP
+#define NAME_FACTION_UPP "Union of Progressive Peoples"
+//CLF
+#define NAME_FACTION_CLF "Colonial Liberation Front"
+//COLON
+#define NAME_FACTION_COLONIST "Colonists"
+//OTHER
+#define NAME_FACTION_RESS "Royal Empire of the Shining Sun"
+#define NAME_FACTION_TWE "Royal Marines Commando"
+#define NAME_FACTION_MERCENARY "Mercenary Group"
+#define NAME_FACTION_FREELANCER "Freelancer Mercenaries"
+#define NAME_FACTION_HEFA "HEFA Knights"
+#define NAME_FACTION_DUTCH "Dutch's Dozen"
+#define NAME_FACTION_PIRATE "Pirates of Free Space"
+#define NAME_FACTION_GLADIATOR "Gladiators"
+#define NAME_FACTION_PIZZA "Pizza Galaxy"
+#define NAME_FACTION_SOUTO "Souto Space"
+#define NAME_FACTION_THREEWE "Three World Empire"
+//ZOMBIE
+#define NAME_FACTION_ZOMBIE "Zombie Horde"
+//YAUTJA
+#define NAME_FACTION_YAUTJA "Yautja Hanting Groop"
+//XENOS
+#define NAME_FACTION_XENOMORPH "Xenomorphs"
+#define NAME_FACTION_XENOMORPH_NORMAL "Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_CORRUPTED "Corrupted Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_ALPHA "Alpha Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_BRAVO "Bravo Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_CHARLIE "Charlie Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_DELTA "Delta Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_FERAL "Feral Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_FORSAKEN "Forsaken Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_TAMED "Tamed Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_MUTATED "Mutated Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_YAUTJA "Yautja Xenomorph Hive"
+#define NAME_FACTION_XENOMORPH_RENEGADE "Renegade Xenomorph Hive"
+
+#define NAME_FACTION_LIST_MARINE list(NAME_FACTION_USCM, NAME_FACTION_MARINE, NAME_FACTION_MARSOC)
+#define NAME_FACTION_LIST_WY list(NAME_FACTION_WY, NAME_FACTION_PMC, NAME_FACTION_WY_DEATHSQUAD)
+#define NAME_FACTION_LIST_HUMANOID list(NAME_FACTION_NEUTRAL, NAME_FACTION_CLF, NAME_FACTION_UPP, NAME_FACTION_FREELANCER, NAME_FACTION_COLONIST, NAME_FACTION_MERCENARY, NAME_FACTION_DUTCH, NAME_FACTION_HEFA, NAME_FACTION_GLADIATOR, NAME_FACTION_PIRATE, NAME_FACTION_PIZZA, NAME_FACTION_SOUTO, NAME_FACTION_ZOMBIE, NAME_FACTION_YAUTJA) + NAME_FACTION_LIST_MARINE + NAME_FACTION_LIST_WY
+#define NAME_FACTION_LIST_XENOMORPH list(NAME_FACTION_XENOMORPH_NORMAL, NAME_FACTION_XENOMORPH_CORRUPTED, NAME_FACTION_XENOMORPH_ALPHA, NAME_FACTION_XENOMORPH_BRAVO, NAME_FACTION_XENOMORPH_CHARLIE, NAME_FACTION_XENOMORPH_DELTA, NAME_FACTION_XENOMORPH_FERAL, NAME_FACTION_XENOMORPH_FORSAKEN, NAME_FACTION_XENOMORPH_TAMED, NAME_FACTION_XENOMORPH_MUTATED, NAME_FACTION_XENOMORPH_YAUTJA, NAME_FACTION_XENOMORPH_YAUTJA)
+#define NAME_FACTION_LIST_ALL NAME_FACTION_LIST_HUMANOID + NAME_FACTION_LIST_XENOMORPH
+
+//ANNOUNCES
+#define COMMAND_ANNOUNCE "Command Announcement"
+#define UPP_COMMAND_ANNOUNCE "UPP Command Announcement"
+#define CLF_COMMAND_ANNOUNCE "CLF Command Announcement"
+#define WY_COMMAND_ANNOUNCE "WY Command Announcement"
+#define QUEEN_ANNOUNCE "The words of the Queen reverberate in your head..."
+#define QUEEN_MOTHER_ANNOUNCE "Queen Mother Psychic Directive"
+#define XENO_GENERAL_ANNOUNCE "You sense something unusual..."
+#define YAUTJA_ANNOUNCE "You receive a message from your ship AI..."
+#define HIGHER_FORCE_ANNOUNCE SPAN_ANNOUNCEMENT_HEADER_BLUE("Unknown Higher Force")
+
+//TASKS
+#define FACTION_TASKS_DOMINATE "Dominate"
+#define FACTION_TASKS_DESTROY "Destroy"
+#define FACTION_TASKS_SECTOR_OCCUPY "Occupy Sector"
+#define FACTION_TASKS_SECTOR_PROTECT "Protect Sector"
+#define FACTION_TASKS_SECTOR_HOLD "Hold Sector"
+#define FACTION_TASKS_SECTOR_CONTROL "Sector Control"
+#define FACTION_TASKS_KILL "Kill"
+#define FACTION_TASKS_PROTECT "Protect"
+#define FACTION_TASKS_HOLD_TIME "Hold Time"
+#define FACTION_TASKS_LIST_ALL list(FACTION_TASKS_DOMINATE, FACTION_TASKS_DESTROY, FACTION_TASKS_SECTOR_OCCUPY, FACTION_TASKS_SECTOR_PROTECT, FACTION_TASKS_SECTOR_CONTROL, FACTION_TASKS_PROTECT, FACTION_TASKS_KILL, FACTION_TASKS_HOLD_TIME)
+
+// Faction allegiances within a certain faction.
+#define FACTION_ALLEGIANCE_USCM_COMMANDER list("Doves", "Hawks", "Magpies", "Unaligned")
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index e663bc287946..1df237b455d8 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -172,31 +172,12 @@ GLOBAL_LIST_INIT(language_keys, setup_language_keys()) //table of say codes for
GLOBAL_REFERENCE_LIST_INDEXED(origins, /datum/origin, name)
GLOBAL_LIST_INIT(player_origins, USCM_ORIGINS)
-//Xeno hives
-GLOBAL_LIST_INIT_TYPED(hive_datum, /datum/hive_status, list(
- XENO_HIVE_NORMAL = new /datum/hive_status(),
- XENO_HIVE_CORRUPTED = new /datum/hive_status/corrupted(),
- XENO_HIVE_ALPHA = new /datum/hive_status/alpha(),
- XENO_HIVE_BRAVO = new /datum/hive_status/bravo(),
- XENO_HIVE_CHARLIE = new /datum/hive_status/charlie(),
- XENO_HIVE_DELTA = new /datum/hive_status/delta(),
- XENO_HIVE_FERAL = new /datum/hive_status/feral(),
- XENO_HIVE_TAMED = new /datum/hive_status/corrupted/tamed(),
- XENO_HIVE_MUTATED = new /datum/hive_status/mutated(),
- XENO_HIVE_FORSAKEN = new /datum/hive_status/forsaken(),
- XENO_HIVE_YAUTJA = new /datum/hive_status/yautja(),
- XENO_HIVE_RENEGADE = new /datum/hive_status/corrupted/renegade(),
- XENO_HIVE_TUTORIAL = new /datum/hive_status/tutorial()
-))
-
GLOBAL_LIST_INIT(xeno_evolve_times, setup_xeno_evolve_times())
/proc/setup_xeno_evolve_times()
for(var/datum/caste_datum/caste as anything in subtypesof(/datum/caste_datum))
LAZYADDASSOCLIST(., num2text(initial(caste.minimum_evolve_time)), caste)
-GLOBAL_LIST_INIT(custom_event_info_list, setup_custom_event_info())
-
// Posters
GLOBAL_LIST_INIT(poster_designs, subtypesof(/datum/poster))
@@ -420,26 +401,6 @@ GLOBAL_LIST_INIT(hj_emotes, setup_hazard_joe_emotes())
mobtypes["[T]"] = typecacheof(T.target_mobtypes)
return mobtypes
-/proc/setup_custom_event_info()
- //faction event messages
- var/list/custom_event_info_list = list()
- var/datum/custom_event_info/CEI = new /datum/custom_event_info
- CEI.faction = "Global" //the old public one for whole server to see
- custom_event_info_list[CEI.faction] = CEI
- for(var/T in FACTION_LIST_HUMANOID)
- CEI = new /datum/custom_event_info
- CEI.faction = T
- custom_event_info_list[T] = CEI
-
- var/datum/hive_status/hive
- for(var/hivenumber in GLOB.hive_datum)
- hive = GLOB.hive_datum[hivenumber]
- CEI = new /datum/custom_event_info
- CEI.faction = hive.internal_faction
- custom_event_info_list[hive.name] = CEI
-
- return custom_event_info_list
-
/proc/setup_taskbar_icons()
var/list/png_list = flist("icons/taskbar")
for(var/png in png_list)
diff --git a/code/datums/event_info_text.dm b/code/datums/event_info_text.dm
index 5336c5abed9d..b73f7833132a 100644
--- a/code/datums/event_info_text.dm
+++ b/code/datums/event_info_text.dm
@@ -1,18 +1,13 @@
/datum/custom_event_info
- var/faction = "default" //here category/faction/hive name stored
- var/msg = "" //here is the message itself
-
+ var/name = "default"
+ var/faction_name = "default"
+ var/datum/faction/faction = null
+ var/msg = ""
//this shows event info to player. can pass clients and mobs
-/datum/custom_event_info/proc/show_player_event_info(user)
-
- if(!istype(user, /client))
- if(ismob(user))
- var/mob/M = user
- if(!M.client)
- return
- else
- return
+/datum/custom_event_info/proc/show_player_event_info(client/user)
+ if(!istype(user))
+ return
if(msg == "")
to_chat(user, SPAN_WARNING("No [faction] custom event message has been found. Either no custom event is taking place, admin hasn't properly set this or deemed it unnecessary to be set."))
@@ -27,7 +22,6 @@
//this shows changed event info to everyone in the category
/datum/custom_event_info/proc/handle_event_info_update()
-
if(!msg)
return
@@ -38,26 +32,16 @@
to_world(dat)
return
- else if(faction in FACTION_LIST_HUMANOID)
- for(var/mob/M in GLOB.human_mob_list)
- if(M && M.faction == faction)
- show_player_event_info(M)
+ else if(faction_name)
+ for(var/mob/M in faction.totalMobs)
+ show_player_event_info(M.client)
return
- else
- var/datum/hive_status/hive
- for(var/hivenumber in GLOB.hive_datum)
- hive = GLOB.hive_datum[hivenumber]
- if(hive.name == faction)
- for(var/mob/M in hive.totalXenos)
- show_player_event_info(M)
- return
-
message_admins("ERROR, ([faction ? faction : "name lost"]) faction is not found for event info.")
return
-/mob/proc/check_event_info(category = "Global")
+/proc/check_event_info(category = "Global", client/user)
if(GLOB.custom_event_info_list[category])
var/datum/custom_event_info/CEI = GLOB.custom_event_info_list[category]
if(CEI.msg)
- CEI.show_player_event_info(src)
+ CEI.show_player_event_info(user)
diff --git a/code/datums/factions/__misc/autobalance_payload.dm b/code/datums/factions/__misc/autobalance_payload.dm
new file mode 100644
index 000000000000..b9350cdae6c2
--- /dev/null
+++ b/code/datums/factions/__misc/autobalance_payload.dm
@@ -0,0 +1,320 @@
+/datum/autobalance_row_faction_info
+ var/datum/faction/faction
+ var/esteminated_power = 0
+ var/weight = 0
+ var/average_fires = 0
+ var/list/round_start_pop = list(0, 0, 0)
+ var/list/average_pop = list(0, 0, 0)
+ var/list/last_pop = list(0, 0, 0)
+
+/datum/autobalance_row_faction_info/New(datum/faction/faction_to_set)
+ ..()
+ faction = faction_to_set
+
+/datum/autobalance_row_faction_info/proc/esteminate_faction_info()
+ var/new_esteminated_power = 0
+ var/new_weight = 0
+ average_fires++
+ last_pop = list(0, 0, 0)
+ for(var/potantial_row in SSautobalancer.balance_rows)
+ var/datum/autobalance_row_info/balance_row = SSautobalancer.balance_rows[potantial_row]
+ if(balance_row.faction_to_set() == faction && balance_row.player_entity.player.owning_client?.mob)
+ var/role_coeff = faction.get_role_coeff(balance_row.player_entity.player.owning_client.mob.job)
+ new_esteminated_power += balance_row.get_player_rating() * role_coeff
+ new_weight += role_coeff
+ average_pop[balance_row.active]++
+ last_pop[balance_row.active]++
+
+// for(var/i = 1; i <= length(round_start_pop); i++)
+// new_esteminated_power += (last_pop[i] - average_pop[i] / average_fires - round_start_pop[i]) * 100
+
+ esteminated_power = new_esteminated_power
+ weight = new_weight
+ return esteminated_power
+
+/datum/autobalance_row_faction_info/proc/round_start()
+ for(var/potantial_row in SSautobalancer.balance_rows)
+ var/datum/autobalance_row_info/balance_row = SSautobalancer.balance_rows[potantial_row]
+ if(balance_row.faction_to_set() == faction)
+ round_start_pop[balance_row.active]++
+
+/datum/autobalance_formula_row
+ var/statistic_type = BALANCE_FORMULA_MISC
+
+/datum/autobalance_formula_row/proc/calculate(mob/calculationg_mob, datum/player_entity/entity)
+ var/datum/statistic_groups/group = entity.statistics[calculationg_mob.faction.faction_name]
+ if(group)
+ var/list/stats = list()
+ for(var/group_subtype in group.statistic_info)
+ var/datum/player_statistic/player_statistic = group.statistic_info[group_subtype]
+ stats += player_statistic.total
+
+ return formula_calculate(calculationg_mob, entity, stats)
+
+/datum/autobalance_formula_row/proc/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/total_encounters = 0
+ var/total_value = 0
+ for(var/potential_stat in stats)
+ if(potential_stat in STATISTIC_MISC_ALL)
+ total_encounters++
+ total_value += stats[potential_stat]
+ if(total_value)
+ final_calculations += total_value / total_encounters
+ return final_calculations
+
+/datum/autobalance_formula_row/commanding
+ statistic_type = BALANCE_FORMULA_COMMANDING
+
+/datum/autobalance_formula_row/commanding/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/total_value = 0
+ for(var/potential_stat in stats)
+ if(potential_stat in STATISTIC_ALL)
+ total_value += stats[potential_stat]
+ if(total_value)
+ final_calculations += total_value / length(STATISTIC_ALL)
+ return final_calculations
+
+/datum/autobalance_formula_row/field
+ statistic_type = BALANCE_FORMULA_FIELD
+
+/datum/autobalance_formula_row/field/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/list/kda = new(2)
+ var/list/shots = new(2)
+ var/damage = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTICS_KILL)
+ kda[1] += stats[potential_stat]
+ if(STATISTICS_KILL_FF)
+ kda[1] -= stats[potential_stat]
+ if(STATISTICS_DEATH)
+ kda[2] += stats[potential_stat]
+ if(STATISTICS_DEATH_FF)
+ kda[2] -= stats[potential_stat]
+ if(STATISTICS_SHOT)
+ shots[1] += stats[potential_stat]
+ if(STATISTICS_SHOT_HIT)
+ shots[2] += stats[potential_stat]
+ if(STATISTICS_FF_SHOT_HIT)
+ shots[2] -= stats[potential_stat]
+ if(STATISTICS_DAMAGE)
+ damage += stats[potential_stat]
+ if(STATISTICS_FF_DAMAGE)
+ damage -= stats[potential_stat]
+
+ if(kda[1] && kda[2])
+ final_calculations += kda[1] / kda[2] * 100
+ if(shots[1] && shots[2])
+ final_calculations += shots[2] / shots[1] * 100
+ if(damage && kda[1])
+ final_calculations += damage / kda[1]
+ return final_calculations
+
+
+/datum/autobalance_formula_row/support
+ statistic_type = BALANCE_FORMULA_SUPPORT
+
+/datum/autobalance_formula_row/support/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/stats_assists = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTIC_ASSIST_ALL)
+ stats_assists += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(stats_assists)
+ final_calculations += stats_assists / rounds
+ return final_calculations
+
+/datum/autobalance_formula_row/support/medic
+ statistic_type = BALANCE_FORMULA_MEDIC
+
+/datum/autobalance_formula_row/support/medic/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/healed = 0
+ var/revives = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTICS_HEALED_DAMAGE)
+ healed += stats[potential_stat]
+ if(STATISTICS_REVIVE)
+ revives += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(healed)
+ final_calculations += healed / rounds
+ if(revives)
+ final_calculations += revives / rounds * 100
+ return final_calculations
+
+/datum/autobalance_formula_row/support/operations
+ statistic_type = BALANCE_FORMULA_OPERATIONS
+
+/datum/autobalance_formula_row/support/operations/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/surgeries = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTIC_SURGERY_ALL)
+ surgeries += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(surgeries)
+ final_calculations += surgeries / rounds
+ return final_calculations
+
+/datum/autobalance_formula_row/support/engineer
+ statistic_type = BALANCE_FORMULA_ENGINEER
+
+/datum/autobalance_formula_row/support/engineer/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/engi_stats = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ if(potential_stat in STATISTIC_ENGINEERING_ALL)
+ engi_stats += stats[potential_stat]
+ else if(potential_stat == STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(engi_stats)
+ final_calculations += engi_stats / rounds * 100
+ return final_calculations
+
+/datum/autobalance_formula_row/xeno_fighter
+ statistic_type = BALANCE_FORMULA_XENO_FIGHTER
+
+/datum/autobalance_formula_row/xeno_fighter/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/list/kda = new(2)
+ var/slashes = 0
+ var/damage = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTICS_KILL)
+ kda[1] += stats[potential_stat]
+ if(STATISTICS_KILL_FF)
+ kda[1] -= stats[potential_stat]
+ if(STATISTICS_DEATH)
+ kda[2] += stats[potential_stat]
+ if(STATISTICS_DEATH_FF)
+ kda[2] -= stats[potential_stat]
+ if(STATISTICS_SLASH)
+ slashes += stats[potential_stat]
+ if(STATISTICS_DAMAGE)
+ damage += stats[potential_stat]
+ if(STATISTICS_FF_DAMAGE)
+ damage -= stats[potential_stat]
+
+ if(kda[1] && kda[2])
+ final_calculations += kda[1] / kda[2] * 100
+ if(slashes && kda[1])
+ final_calculations += slashes / kda[1]
+ if(damage && kda[1])
+ final_calculations += damage / kda[1]
+ return final_calculations
+
+/datum/autobalance_formula_row/xeno_healer
+ statistic_type = BALANCE_FORMULA_XENO_HEALER
+
+/datum/autobalance_formula_row/xeno_healer/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/healed = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTICS_HEALED_DAMAGE)
+ healed += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(healed)
+ final_calculations += healed / rounds
+ return final_calculations
+
+/datum/autobalance_formula_row/xeno_builder
+ statistic_type = BALANCE_FORMULA_XENO_BUILDER
+
+/datum/autobalance_formula_row/xeno_builder/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/build = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTIC_XENO_STRUCTURES_BUILD)
+ build += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(build)
+ final_calculations += build / rounds
+ return final_calculations
+
+/datum/autobalance_formula_row/xeno_abiliter
+ statistic_type = BALANCE_FORMULA_XENO_ABILITER
+
+/datum/autobalance_formula_row/xeno_abiliter/formula_calculate(mob/calculationg_mob, datum/player_entity/entity, list/stats = list())
+ var/final_calculations = 0
+ var/abilities = 0
+ var/rounds = 0
+ for(var/potential_stat in stats)
+ switch(potential_stat)
+ if(STATISTICS_ABILITES)
+ abilities += stats[potential_stat]
+ if(STATISTICS_ROUNDS_PLAYED)
+ rounds += stats[potential_stat]
+
+ if(abilities)
+ final_calculations += abilities / rounds
+ return final_calculations
+
+/datum/autobalance_row_info
+ var/rating = 0
+ var/active = 3
+ var/datum/player_entity/player_entity = null
+ var/datum/entity/player/player = null
+
+/datum/autobalance_row_info/New(datum/player_entity/entity_to_set)
+ ..()
+ player_entity = entity_to_set
+
+/datum/autobalance_row_info/proc/get_player_rating()
+ rating = 0
+ var/mob/living = player_entity.player.owning_client?.mob
+ if(istype(living) && istype(living.job, /datum/job))
+ var/datum/job/player_job = GET_MAPPED_ROLE(living.job)
+ for(var/balance_formula in player_job.balance_formulas + living.balance_formulas)
+ var/datum/autobalance_formula_row/formula = GLOB.balance_formulas[balance_formula]
+ var/additional_rating = formula.calculate(living, player_entity)
+ if(additional_rating)
+ rating += additional_rating / length(player_job.balance_formulas + living.balance_formulas)
+ return rating
+
+/datum/autobalance_row_info/proc/faction_to_set()
+ var/mob/living = player_entity.player.owning_client?.mob
+ if(living)
+ return living.faction
+ return FALSE
+
+/datum/autobalance_row_info/proc/status_change(action)
+ switch(action)
+ if("login")
+ active = 3
+ if("logout")
+ active = 2
+ if("death")
+ active = 1
+ if("revive")
+ if(player_entity.player.owning_client?.mob?.client)
+ active = 3
+ else
+ active = 2
diff --git a/code/datums/factions/__misc/faction_tasks.dm b/code/datums/factions/__misc/faction_tasks.dm
new file mode 100644
index 000000000000..774dcdc7b5fe
--- /dev/null
+++ b/code/datums/factions/__misc/faction_tasks.dm
@@ -0,0 +1,472 @@
+/datum/faction_task
+ var/name = "INSERT"
+ var/desc = "INSERT"
+ var/list/announce_desc = list("complete" = "Good job, sector taken, ", "failed" = "Sector control failed")
+ var/state = OBJECTIVE_INACTIVE
+
+ var/score = 0
+
+ var/game_ender = FALSE
+ var/rand_total_time = FALSE
+ var/total_time = 0
+ COOLDOWN_DECLARE(remaining_time)
+
+ var/task_type
+ var/task_color
+ var/datum/faction/faction_owner
+
+/datum/faction_task/New(datum/faction/faction_to_set)
+ . = ..()
+
+ if(rand_total_time)
+ total_time += rand(-total_time, total_time) / 2
+
+ faction_owner = faction_to_set
+ SSfactions.add_task(src)
+ activate()
+
+/datum/faction_task/Destroy()
+ SSfactions.stop_processing_task(src)
+ SSfactions.remove_task(src)
+ return ..()
+
+/datum/faction_task/proc/activate()
+ if(total_time)
+ COOLDOWN_START(src, total_time, total_time)
+ SSfactions.start_processing_task(src)
+
+/datum/faction_task/proc/deactivate()
+ SSfactions.stop_processing_task(src)
+
+/datum/faction_task/proc/check_completion()
+ return
+
+/datum/faction_task/proc/complete(end_state)
+ state = end_state
+
+ if(state & OBJECTIVE_COMPLETE)
+ faction_owner.faction_victory_points += score
+ announce_faction()
+
+/datum/faction_task/proc/announce_faction()
+ faction_announcement(state & OBJECTIVE_COMPLETE ? announce_desc["complete"] : announce_desc["failed"], name, null, faction_owner)
+
+/datum/faction_task/proc/get_completion_status()
+ if(state & OBJECTIVE_IN_PROGRESS)
+ return "In Progress!"
+ else if(state & OBJECTIVE_FAILED)
+ return "Failed!"
+ else if(state & OBJECTIVE_COMPLETE)
+ return "Succeeded!"
+ return "Awaiting"
+
+/datum/faction_task/proc/get_readable_progress()
+ var/dat = "[name]: "
+ return dat + get_completion_status() + "
"
+
+////////////////////////////////////////////////////////////////////////////////////////
+/obj/structure/prop/sector_center
+ name = FACTION_TASKS_SECTOR_CONTROL
+ desc = "To seize this object is to take victory in one hand"
+ icon = 'icons/obj/structures/sector_control.dmi'
+ icon_state = "tower"
+ breakable = FALSE
+ indestructible = TRUE
+ unacidable = TRUE
+ unslashable = TRUE
+ density = TRUE
+
+ var/capture_progress = 0
+ var/req_capture_progress = 100
+ var/zone_range = 8
+ var/home_sector = FALSE
+
+ var/sector_id = "0"
+ var/list/sector_connections = list()
+
+ faction_to_get = null
+
+ var/datum/faction_task/sector_control/owner
+
+ var/list/connected_task = list()
+ var/list/bordered_sectors = list()
+ var/list/linked_turfs = list()
+
+ var/obj/effect/decal/fog //fog
+ var/datum/shape/rectangle/range_bounds
+
+/obj/structure/prop/sector_center/Initialize()
+ . = ..()
+ name = "Sector [pick(operation_prefixes)]-[pick(operation_postfixes)]"
+ owner = new(faction, src)
+ range_bounds = RECT(loc.x, loc.y, zone_range * 2, zone_range * 2)
+ update_icon()
+ if(home_sector)
+ capture_progress = req_capture_progress
+
+ fog = new(owner)
+
+ for(var/turf/turf in range(round(zone_range*COVERAGE_MULT), loc))
+ LAZYADD(turf.linked_sectors, src)
+ linked_turfs += turf
+
+ START_PROCESSING(SSslowobj, src)
+
+/obj/structure/prop/sector_center/Destroy()
+ . = ..()
+ for(var/turf/turf as anything in linked_turfs)
+ LAZYREMOVE(turf.linked_sectors, src)
+ range_bounds = null
+ STOP_PROCESSING(SSslowobj, src)
+
+/obj/structure/prop/sector_center/get_examine_text(mob/user)
+ . = ..()
+ . += "Capture progress [capture_progress/req_capture_progress*100]% ([capture_progress]/[req_capture_progress]),"
+ if(faction)
+ . += " by faction [faction],"
+ . += "
Zone radius [zone_range] m.
"
+
+/obj/structure/prop/sector_center/process()
+ var/turf/turf = get_turf(src)
+ if(!istype(turf))
+ return
+
+ if(!range_bounds)
+ range_bounds = RECT(turf.x, turf.y, zone_range * 2, zone_range * 2)
+
+ var/list/candidates = SSquadtree.players_in_range(range_bounds, turf.z, QTREE_EXCLUDE_OBSERVER | QTREE_SCAN_MOBS)
+
+ var/total_ammount = 0
+ var/list/sorted_in_range = list()
+ for(var/atom in candidates)
+ var/mob/living/carbon/carbon = atom
+ if(carbon.faction)
+ if(!length(sorted_in_range[carbon.faction.faction_name]))
+ sorted_in_range[carbon.faction.faction_name] = list()
+ total_ammount++
+ sorted_in_range[carbon.faction.faction_name] += carbon
+
+ var/datum/faction/potential_faction
+ var/potential_number = 0
+ var/faction_mobs_number = 0
+ for(var/faction_to_get in sorted_in_range)
+ faction_mobs_number = length(sorted_in_range[faction_to_get])
+ if(potential_number < faction_mobs_number)
+ potential_faction = GLOB.faction_datum[faction_to_get]
+ potential_number = faction_mobs_number
+
+ var/have_task = FALSE
+ if(potential_faction && length(get_faction_tasks(potential_faction)))
+ have_task = TRUE
+
+ var/overhelming = potential_number * 2 - total_ammount
+ var/overhelming_to_add = potential_number * 2 - total_ammount
+ if(!have_task)
+ overhelming_to_add = 1
+
+ if(!length(candidates) && capture_progress != req_capture_progress)
+ capture_progress--
+
+ else if(capture_progress != req_capture_progress)
+ if(faction != potential_faction)
+ if(overhelming)
+ if(capture_progress > 1)
+ capture_progress -= overhelming_to_add
+ else if(capture_progress < 1 && !faction)
+ get_sector(potential_faction)
+ capture_progress = 1
+ else
+ capture_progress--
+ else
+ if(overhelming)
+ capture_progress += overhelming_to_add
+ else
+ capture_progress--
+ else
+ if(faction != potential_faction)
+ if(overhelming)
+ capture_progress -= overhelming_to_add
+
+ if(capture_progress == req_capture_progress && faction)
+ faction.faction_victory_points += home_sector ? 5 : 1
+ capture_progress = Clamp(capture_progress, 0, req_capture_progress)
+ if(!capture_progress && faction)
+ get_sector(null)
+
+/obj/structure/prop/sector_center/proc/get_sector(datum/faction/check_faction)
+ if(!check_faction)
+ faction_announcement("[name] lost!", FACTION_TASKS_SECTOR_CONTROL, null, faction)
+ faction = null
+// fog.faction = null
+ return
+ var/announce_text = "[name] new controlled by [check_faction]"
+ if(home_sector)
+ var/datum/faction/initial_faction = GLOB.faction_datum[faction_to_get]
+ if(initial_faction == faction)
+ faction_announcement("[name] new controlled by [check_faction], home sector occupied, now for us that big problem!", FACTION_TASKS_SECTOR_CONTROL, null, faction)
+ faction_announcement("[name] new controlled by [check_faction], home sector returned back, now your enemy don't can do anything!", FACTION_TASKS_SECTOR_CONTROL, null, initial_faction)
+ initial_faction.latejoin_enabled = FALSE
+ else if(initial_faction == check_faction)
+ faction_announcement("[name] new controlled by [check_faction], home sector occupied, enemy now can comeback!", FACTION_TASKS_SECTOR_CONTROL, null, faction)
+ faction_announcement("[name] new controlled by [check_faction], home sector returned back, rise and destory our enemies!", FACTION_TASKS_SECTOR_CONTROL, null, initial_faction)
+ initial_faction.latejoin_enabled = TRUE
+ else
+ faction_announcement(announce_text, FACTION_TASKS_SECTOR_CONTROL, null, faction)
+ faction_announcement(announce_text, FACTION_TASKS_SECTOR_CONTROL, null, check_faction)
+ else
+ faction_announcement(announce_text, FACTION_TASKS_SECTOR_CONTROL, null, faction)
+ faction_announcement(announce_text, FACTION_TASKS_SECTOR_CONTROL, null, check_faction)
+ faction = check_faction
+// fog.faction = faction
+
+/obj/structure/prop/sector_center/proc/captured(datum/faction/check_faction)
+ if(faction == check_faction && capture_progress == req_capture_progress)
+ return TRUE
+ return FALSE
+
+/obj/structure/prop/sector_center/proc/get_faction_tasks(datum/faction/check_faction)
+ var/list/tasks_list = list()
+ var/list/potential_tasks = connected_task[check_faction.faction_name]
+ for(var/datum/faction_task/task in potential_tasks)
+ if(task.faction_owner == check_faction)
+ tasks_list += task
+ return tasks_list
+
+/obj/structure/prop/sector_center/base
+ req_capture_progress = 600
+ zone_range = 16
+ home_sector = TRUE
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/sector_control
+ name = FACTION_TASKS_SECTOR_CONTROL
+ desc = "You need to control ###SECTOR### sector as long as can"
+ announce_desc = list("complete" = "Good job, sector taken", "failed" = "Sector control failed")
+
+ task_type = FACTION_TASKS_SECTOR_CONTROL
+ task_color = "#1616a0"
+
+ var/obj/structure/prop/sector_center/sector_center
+ var/datum/faction_task/sector_control/grooped_task
+
+/datum/faction_task/sector_control/New(datum/faction/faction_to_set, obj/structure/prop/sector_center/sector)
+ . = ..()
+ sector_center = sector
+ desc = replacetext(desc, "###SECTOR###", sector.name)
+ sector_center.connected_task += src
+
+/datum/faction_task/sector_control/process()
+ sector_center.process()
+
+/datum/faction_task/sector_control/announce_faction()
+ faction_announcement("[sector_center] [state & OBJECTIVE_COMPLETE ? announce_desc["complete"] : announce_desc["failed"]]", name, null, faction_owner)
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/sector_control/protect
+ name = FACTION_TASKS_SECTOR_PROTECT
+ desc = "Defend ###SECTOR### sector"
+ announce_desc = list("complete" = "Good job, sector taken", "failed" = "Sector control failed")
+
+ score = 1500
+
+ total_time = 0
+
+/datum/faction_task/sector_control/protect/process()
+ return
+
+/datum/faction_task/sector_control/protect/check_completion()
+ if(!sector_center.captured(faction_owner))
+ complete(OBJECTIVE_FAILED)
+ else if(COOLDOWN_FINISHED(src, remaining_time))
+ complete(OBJECTIVE_COMPLETE)
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/sector_control/occupy
+ name = FACTION_TASKS_SECTOR_OCCUPY
+ desc = "Capture ###SECTOR### sector"
+ announce_desc = list("complete" = "Good job, sector taken", "failed" = "Sector control failed")
+
+ score = 2250
+
+ total_time = 15 MINUTES
+
+/datum/faction_task/sector_control/occupy/New(datum/faction/faction_to_set, obj/structure/prop/sector_center/sector)
+ . = ..()
+ if(sector.faction)
+ grooped_task = new /datum/faction_task/sector_control/protect(sector.faction, sector)
+ SSfactions.active_tasks += grooped_task
+
+/datum/faction_task/sector_control/occupy/check_completion()
+ if(!sector_center.captured(faction_owner) || COOLDOWN_FINISHED(src, remaining_time))
+ complete(OBJECTIVE_FAILED)
+ grooped_task.complete(OBJECTIVE_COMPLETE)
+ else if(sector_center.captured(faction_owner))
+ complete(OBJECTIVE_COMPLETE)
+ grooped_task.complete(OBJECTIVE_FAILED)
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/sector_control/occupy/hold
+ name = FACTION_TASKS_SECTOR_HOLD
+ desc = "Dig in and hold ###SECTOR### sector"
+ announce_desc = list("complete" = "Good job, sector taken", "failed" = "Sector control failed")
+
+ total_time = 30 MINUTES
+
+/datum/faction_task/sector_control/occupy/hold/process()
+ if(sector_center.captured(faction_owner))
+ score++
+
+/datum/faction_task/sector_control/occupy/hold/check_completion()
+ if(COOLDOWN_FINISHED(src, remaining_time))
+ if(score)
+ complete(OBJECTIVE_COMPLETE)
+ else
+ complete(OBJECTIVE_FAILED)
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/dominate
+ name = FACTION_TASKS_DOMINATE
+ desc = "Destroy all enemy forces and capture all strategic points"
+ announce_desc = list("complete" = "All enemy forces destroyed!", "failed" = "Enemy overhelmed and destroyed our forces")
+
+ task_type = "Game Ender"
+ task_color = "#b23131"
+
+/datum/faction_task/dominate/check_completion()
+ faction_owner.faction_victory_points -= score
+ faction_owner.homes_sector_occupation = TRUE
+ score = 0
+
+ var/list/faction_stats = list("dead_enemy_factions" = 0, "total_friendly_factions" = 0)
+ for(var/faction_name in SSticker.mode.factions_pool)
+ var/datum/faction/faction = GLOB.faction_datum[SSticker.mode.factions_pool[faction_name]]
+ if(faction_owner.relations_datum.allies[faction.faction_name])
+ faction_stats["total_friendly_factions"]++
+
+ else if(!length(faction.totalMobs))
+ faction_stats["dead_enemy_factions"]++
+ score += length(faction.totalDeadMobs)
+
+ faction_owner.faction_victory_points += score
+
+ if(faction_stats["dead_enemy_factions"] >= length(SSticker.mode.factions_pool) - length(faction_stats["total_friendly_factions"]))
+ complete(OBJECTIVE_COMPLETE)
+
+/datum/faction_task/dominate/complete(end_state)
+ . = ..()
+ if(state == OBJECTIVE_COMPLETE)
+ SSticker.mode.round_finished = SSticker.mode.faction_round_end_state[faction_owner.faction_name]
+ SSticker.mode.faction_won = faction_owner
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/hold
+ name = FACTION_TASKS_HOLD_TIME
+ desc = "Hold positions until enemy reserves are depleted"
+ announce_desc = list("complete" = "You survived, await main army, we already there!", "failed" = "This is cvaudrant lost, you failed your one small job, idiots!")
+
+ rand_total_time = TRUE
+ total_time = 2 HOURS
+
+ task_type = "Game Ender"
+ task_color = "#b23131"
+
+/datum/faction_task/hold/process()
+ faction_owner.faction_victory_points++
+
+/datum/faction_task/hold/check_completion()
+ if(!length(faction_owner.totalDeadMobs))
+ complete(OBJECTIVE_FAILED)
+ else if(COOLDOWN_FINISHED(src, remaining_time))
+ complete(OBJECTIVE_COMPLETE)
+
+/datum/faction_task/hold/complete(end_state)
+ . = ..()
+ if(state == OBJECTIVE_COMPLETE)
+ SSticker.mode.round_finished = SSticker.mode.faction_round_end_state[faction_owner.faction_name]
+ SSticker.mode.faction_won = faction_owner
+
+/datum/faction_task/hold/get_completion_status()
+ if(state & OBJECTIVE_IN_PROGRESS)
+ return "Remaining time: [COOLDOWN_TIMELEFT(src, remaining_time)]"
+ else if(state & OBJECTIVE_FAILED)
+ return "Failed! Everyone's dead!"
+ else if(state & OBJECTIVE_COMPLETE)
+ return "Succeeded! Time's up!"
+ return "Awaiting"
+
+////////////////////////////////////////////////////////////////////////////////////////
+/datum/faction_task/destroy
+ name = FACTION_TASKS_DESTROY
+ desc = "Destroy a target"
+ announce_desc = list("complete" = "Good job, sector taken, ", "failed" = "Sector control failed")
+
+ score = 700
+
+ total_time = 25 MINUTES
+
+ task_type = "Objectives"
+ task_color = "#1eb641"
+
+ var/datum/faction/destroyed_faction
+
+/datum/faction_task/destroy/New(datum/faction/faction_to_set, obj/target)
+ . = ..()
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(object_destroyed), override = TRUE)
+
+/datum/faction_task/destroy/proc/object_destroyed(obj/target, datum/faction/faction)
+ SIGNAL_HANDLER
+ destroyed_faction = faction
+
+/datum/faction_task/destroy/check_completion()
+ if(COOLDOWN_FINISHED(src, remaining_time))
+ complete(OBJECTIVE_FAILED)
+
+ else if(destroyed_faction)
+ if(destroyed_faction != faction_owner)
+ complete(OBJECTIVE_FAILED)
+ else
+ complete(OBJECTIVE_COMPLETE)
+
+/*
+#define FACTION_TASKS_KILL "Kill Task"
+#define FACTION_TASKS_PROTECT "Protect Task"
+*/
+#define TASK_STATUS_LIST list(OBJECTIVE_COMPLETE = "complete", OBJECTIVE_FAILED = "failed", OBJECTIVE_IN_PROGRESS = "in progress", OBJECTIVE_ACTIVE = "active", OBJECTIVE_INACTIVE = "inactive")
+#define TGUI_TASK_COLORS list("#b23131" = "red", "#1eb641" = "green", "#1616a0" = "blue")
+
+/datum/faction_task_ui
+ var/datum/faction/faction
+
+/datum/faction_task_ui/New(datum/faction/faction_to_set)
+ faction = faction_to_set
+
+/datum/faction_task_ui/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "FactionTask", "[faction] Tasks")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+
+/datum/faction_task_ui/ui_data(mob/user)
+ . = list()
+ var/list/task_payload = list()
+ for(var/datum/faction_task/task in SSfactions.active_tasks)
+ if(task.faction_owner != faction)
+ continue
+ task_payload += list(
+ "name" = task.name,
+ "desc" = task.desc,
+ "status" = TASK_STATUS_LIST[task.state],
+ "status_desc" = task.get_completion_status(),
+ "type" = task.task_type,
+ "color" = TGUI_TASK_COLORS[task.task_color]
+ )
+ .["tasks"] = task_payload
+ .["points"] = faction.faction_victory_points
+ .["req_points"] = 10000
+
+/datum/faction_task_ui/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+
+/datum/faction_task_ui/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
diff --git a/code/datums/factions/__misc/helpers.dm b/code/datums/factions/__misc/helpers.dm
new file mode 100644
index 000000000000..b36af84e0ba6
--- /dev/null
+++ b/code/datums/factions/__misc/helpers.dm
@@ -0,0 +1,39 @@
+
+GLOBAL_LIST_INIT_TYPED(objectives_reward_list, /datum/objectives_reward, create_objectives_rewards_list())
+GLOBAL_LIST_EMPTY_TYPED(objective_controller, /datum/objectives_datum)
+GLOBAL_LIST_INIT(objectives_links, list(
+ "objective" = list(/obj/item/storage/fancy/vials/random, /obj/item/disk/objective, /obj/item/document_objective/technical_manual, /obj/item/document_objective/paper),
+ "close" = list(/obj/item/disk/objective, /obj/item/document_objective/technical_manual, /obj/item/document_objective/folder, /obj/item/document_objective/report, /obj/item/document_objective/paper),
+ "medium" = list(/obj/item/device/mass_spectrometer/adv/objective, /obj/item/device/reagent_scanner/adv/objective, /obj/item/device/healthanalyzer/objective, /obj/item/device/autopsy_scanner/objective, /obj/item/document_objective/paper),
+ "far" = list(/obj/item/storage/fancy/vials/random, /obj/item/paper/research_notes, /obj/item/disk/objective,/obj/item/document_objective/folder, /obj/item/document_objective/report, /obj/item/document_objective/paper),
+ "science" = list(/obj/item/storage/fancy/vials/random, /obj/item/paper/research_notes, /obj/item/document_objective/paper)
+))
+
+GLOBAL_LIST_INIT(task_gen_list, list("sector_control" = list(/datum/faction_task/sector_control/occupy, /datum/faction_task/sector_control/occupy/hold)))
+GLOBAL_LIST_INIT(task_gen_list_game_enders, list("game_enders" = list(/datum/faction_task/dominate, /datum/faction_task/hold)))
+
+GLOBAL_LIST_INIT_TYPED(faction_datum, /datum/faction, setup_faction_list())
+
+/proc/setup_faction_list()
+ . = list()
+ for(var/faction_to_get in FACTION_LIST_DEFCONED)
+ var/datum/objectives_datum/objectives_datum = new (faction_to_get)
+ GLOB.objective_controller[faction_to_get] = objectives_datum
+ for(var/path in typesof(/datum/faction))
+ var/datum/faction/faction = new path
+ .[faction.faction_name] = faction
+ faction.relations_datum.generate_relations_helper()
+
+GLOBAL_LIST_INIT_TYPED(custom_event_info_list, /datum/custom_event_info, setup_custom_event_info())
+
+/proc/setup_custom_event_info()
+ . = list()
+ var/datum/custom_event_info/CEI = new()
+ CEI.faction_name = "Global"
+ .[CEI.faction_name] = CEI
+ for(var/faction_to_get in FACTION_LIST_ALL)
+ var/datum/faction/faction = GLOB.faction_datum[faction_to_get]
+ CEI = new()
+ CEI.faction_name = faction.name
+ CEI.faction = faction
+ .[CEI.faction_name] = CEI
diff --git a/code/datums/factions/__misc/relations_actions.dm b/code/datums/factions/__misc/relations_actions.dm
new file mode 100644
index 000000000000..5642c221b4fc
--- /dev/null
+++ b/code/datums/factions/__misc/relations_actions.dm
@@ -0,0 +1,75 @@
+/datum/relations_action
+ var/name = "TELL A DEV"
+ var/desc = "TELL A DEV"
+
+ var/act_name = "Set name"
+ var/act_desc = "Set description"
+ var/act_additional_info = "Set additional info"
+
+ var/accepted = FALSE
+ var/datum/faction/target_faction
+ var/confirmed = FALSE
+ var/datum/faction/initiator_faction
+
+ var/time_delegated
+ var/time_given = 5 MINUTES
+
+ var/completion_initialy = FALSE
+
+ var/expirable = FALSE
+ var/start_acting_time
+ var/expiring
+
+/datum/relations_action/New(datum/faction/target_faction_to_set, datum/faction/initiator_faction_to_set)
+ target_faction = target_faction_to_set
+ initiator_faction = initiator_faction_to_set
+ time_delegated = world.time
+
+/datum/relations_action/Destroy()
+ target_faction = null
+ initiator_faction = null
+ . = ..()
+
+/datum/relations_action/proc/offer()
+
+/datum/relations_action/proc/check_completion()
+
+/datum/relations_action/proc/can_accept()
+
+/datum/relations_action/proc/accept()
+
+/datum/relations_action/proc/can_confirm()
+
+/datum/relations_action/proc/confirmation()
+
+/datum/relations_action/proc/start_acting()
+ if(expirable)
+ start_acting_time = world.time
+
+ if(completion_initialy)
+ completion()
+
+/datum/relations_action/proc/completion()
+
+/datum/relations_action/proc/expire()
+
+//////////////
+/datum/relations_action/alliance_request
+ name = "Пакт для создания союза"
+ desc = "Две стороны становятся союзниками, тем самым до момента разрыва договора не мешают друг другу"
+
+ completion_initialy = TRUE
+
+/datum/relations_action/alliance_request/completion()
+ initiator_faction.relations_datum.allies[target_faction.faction_name] = target_faction
+ target_faction.relations_datum.allies[initiator_faction.faction_name] = initiator_faction
+ initiator_faction.relations_datum.gain_opinion(target_faction, 200)
+ target_faction.relations_datum.gain_opinion(initiator_faction, 200)
+
+/datum/relations_action/
+
+
+/*
+оскорблеения
+
+*/
diff --git a/code/datums/factions/__misc/relations_datum.dm b/code/datums/factions/__misc/relations_datum.dm
new file mode 100644
index 000000000000..1ab9ddf4ac9b
--- /dev/null
+++ b/code/datums/factions/__misc/relations_datum.dm
@@ -0,0 +1,60 @@
+/datum/faction_relations
+ var/relations[] = RELATIONS_MAP
+ var/list/allies = list()
+ var/list/datum/relations_action/relation_actions = list()
+ var/datum/faction/faction
+ var/atom/source
+
+/datum/faction_relations/New(datum/faction/faction_to_set, atom/referenced_source)
+ faction = faction_to_set
+ if(referenced_source)
+ source = referenced_source
+ else
+ source = src
+
+/datum/faction_relations/proc/generate_relations_helper()
+ spawn(30 SECONDS)
+ for(var/i in FACTION_LIST_ALL)
+ if(i == faction.faction_name)
+ relations[i] = RELATIONS_SELF
+ continue
+ if(i in faction.relations_pregen)
+ relations[i] = rand(faction.relations_pregen[i][1], faction.relations_pregen[i][2])
+ if(RELATIONS_FRIENDLY[2] < faction.relations_pregen[i])
+ allies += GLOB.faction_datum[i]
+ continue
+ relations[i] = RELATIONS_UNKNOWN
+
+/datum/faction_relations/proc/can_acting(datum/faction/target_faction)
+ if(isnull(relations[target_faction.faction_name]) || relations[target_faction.faction_name] < RELATIONS_WAR[1] || relations[target_faction.faction_name] > RELATIONS_MAX)
+ return FALSE
+ return TRUE
+
+/datum/faction_relations/proc/gain_opinion(datum/faction/target_faction, opinion)
+ relations[target_faction.faction_name] = clamp(relations[target_faction.faction_name] + opinion, RELATIONS_WAR[1], RELATIONS_MAX)
+
+/datum/faction_relations/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, source, ui)
+ if(!ui)
+ ui = new(user, source, "FactionRelations", "[faction] Relations")
+ ui.open()
+ ui.set_autoupdate(TRUE)
+
+/datum/faction_relations/ui_data(mob/user)
+ . = list()
+ var/list/relations_mapping = list()
+ for(var/i in relations)
+ if(relations[i] == null || relations[i] > 1000)
+ continue
+ relations_mapping += list(list("name" = GLOB.faction_datum[i].name, "desc" = GLOB.faction_datum[i].desc, "color" = GLOB.faction_datum[i].ui_color, "value" = relations[i]))
+
+ .["actions"] = source != src ? TRUE : FALSE
+
+ .["faction_color"] = faction.ui_color
+ .["faction_relations"] = relations_mapping
+
+/datum/faction_relations/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+
+/datum/faction_relations/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
diff --git a/code/datums/factions/_modules/hive.dm b/code/datums/factions/_modules/hive.dm
new file mode 100644
index 000000000000..9cc45fae7058
--- /dev/null
+++ b/code/datums/factions/_modules/hive.dm
@@ -0,0 +1,1440 @@
+/*
+ * This module handle hive status, please be calm and don't be pathetic, best solution.
+ * :CLUELES_FACE:
+*/
+
+/datum/faction_module/hive
+ var/name = "Normal Hive"
+
+ // Used for the faction of the xenomorph. Not recommended to modify.
+ var/internal_faction
+
+ /// Short Hive ID as string used in stats reporting
+ var/reporting_id = "normal"
+
+ var/hivenumber = XENO_HIVE_NORMAL
+ var/mob/living/carbon/xenomorph/queen/living_xeno_queen
+ var/egg_planting_range = 15
+ var/slashing_allowed = XENO_SLASH_ALLOWED //This initial var allows the queen to turn on or off slashing. Slashing off means harm intent does much less damage.
+ var/construction_allowed = NORMAL_XENO //Who can place construction nodes for special structures
+ var/destruction_allowed = NORMAL_XENO //Who can destroy special structures
+ var/unnesting_allowed = TRUE
+ var/hive_orders = "" //What orders should the hive have
+ var/color = null
+ var/ui_color = null // Color for hive status collapsible buttons and xeno count list
+ var/prefix = ""
+ var/queen_leader_limit = 2
+ var/list/open_xeno_leader_positions = list(1, 2) // Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied
+ var/list/xeno_leader_list[2] // Ordered list (i.e. index n holds the nth xeno leader)
+ var/stored_larva = 0
+
+ ///used by /datum/hive_status/proc/increase_larva_after_burst() to support non-integer increases to larva
+ var/partial_larva = 0
+ /// Assoc list of free slots available to specific castes
+ var/list/free_slots = list(
+ /datum/caste_datum/burrower = 1,
+ /datum/caste_datum/hivelord = 1,
+ /datum/caste_datum/carrier = 1
+ )
+ /// Assoc list of slots currently used by specific castes (for calculating free_slot usage)
+ var/list/used_slots = list()
+ /// list of living tier2 xenos
+ var/list/tier_2_xenos = list()
+ /// list of living tier3 xenos
+ var/list/tier_3_xenos = list()
+ /// list of living xenos
+ var/list/totalXenos = list()
+ /// list of previously living xenos (hardrefs currently)
+ var/list/total_dead_xenos = list()
+ var/xeno_queen_timer
+ var/isSlotOpen = TRUE //Set true for starting alerts only after the hive has reached its full potential
+ var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles.
+ var/obj/effect/alien/resin/special/pylon/core/hive_location = null //Set to ref every time a core is built, for defining the hive location
+
+ var/tier_slot_multiplier = 1
+ var/larva_gestation_multiplier = 1
+ var/bonus_larva_spawn_chance = 1
+ var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed
+ /// how many burrowed is going to spawn during larva surge
+ var/hijack_burrowed_left = 0
+
+ var/dynamic_evolution = TRUE
+ var/evolution_rate = 3 // Only has use if dynamic_evolution is false
+ var/evolution_bonus = 0
+
+ var/allow_no_queen_actions = FALSE
+ var/allow_no_queen_evo = FALSE
+ var/evolution_without_ovipositor = TRUE //Temporary for the roundstart.
+ /// Set to false if you want to prevent evolutions into Queens
+ var/allow_queen_evolve = TRUE
+ /// Set to true if you want to prevent bursts and spawns of new xenos. Will also prevent healing if the queen no longer exists
+ var/hardcore = FALSE
+ /// Set to false if you want to prevent getting burrowed larva from latejoin marines
+ var/latejoin_burrowed = TRUE
+ /// If hit limit of larva from pylons
+ var/hit_larva_pylon_limit = FALSE
+
+ var/see_humans_on_tacmap = FALSE
+
+ var/list/hive_inherant_traits
+
+ // Cultist Info
+ var/mob/living/carbon/leading_cult_sl
+
+ //List of how many maximum of each special structure you can have
+ var/list/hive_structures_limit = list(
+ XENO_STRUCTURE_CORE = 1,
+ XENO_STRUCTURE_CLUSTER = 8,
+ XENO_STRUCTURE_EGGMORPH = 6,
+ XENO_STRUCTURE_RECOVERY = 6,
+ XENO_STRUCTURE_PYLON = 2,
+ )
+
+ var/global/list/hive_structure_types = list(
+ XENO_STRUCTURE_CORE = /datum/construction_template/xenomorph/core,
+ XENO_STRUCTURE_CLUSTER = /datum/construction_template/xenomorph/cluster,
+ XENO_STRUCTURE_EGGMORPH = /datum/construction_template/xenomorph/eggmorph,
+ XENO_STRUCTURE_RECOVERY = /datum/construction_template/xenomorph/recovery
+ )
+
+ var/list/list/hive_structures = list() //Stringref list of structures that have been built
+ var/list/list/hive_constructions = list() //Stringref list of structures that are being built
+
+ var/datum/hive_status_ui/hive_ui
+ var/datum/mark_menu_ui/mark_ui
+ var/datum/hive_faction_ui/faction_ui
+
+ var/list/tunnels = list()
+
+ var/list/allies = list()
+
+ var/list/resin_marks = list()
+
+ var/list/banished_ckeys = list()
+
+ var/hivecore_cooldown = FALSE
+
+ var/need_round_end_check = FALSE
+
+ //Joining as Facehugger vars
+ /// When can huggers join the round
+ var/hugger_timelock = 15 MINUTES
+ /// How many huggers can the hive support
+ var/playable_hugger_limit = 0
+ /// Minimum number of huggers available at any hive size
+ var/playable_hugger_minimum = 2
+ /// This number divides the total xenos counted for slots to give the max number of facehuggers
+ var/playable_hugger_max_divisor = 4
+
+ /// How many lesser drones the hive can support
+ var/lesser_drone_limit = 0
+ /// Slots available for lesser drones will never go below this number
+ var/lesser_drone_minimum = 2
+ /// This number divides the total xenos counted for slots to give the max number of lesser drones
+ var/playable_lesser_drones_max_divisor = 3
+
+ var/datum/tacmap/drawing/xeno/tacmap
+ var/minimap_type = MINIMAP_FLAG_XENO
+
+ var/list/available_nicknumbers = list()
+
+ /*Stores the image()'s for the xeno evolution radial menu
+ To add an image for your caste - add an icon to icons/mob/xenos/radial_xenos.dmi
+ Icon size should be 32x32, to make them fit within the radial menu border size your icon 22x22 and leave 10px transparent border.
+ The name of the icon should be the same as the XENO_CASTE_ define for that caste eg. #define XENO_CASTE_DRONE "Drone"
+ */
+ var/static/list/evolution_menu_images
+
+/datum/hive_status/New()
+ hive_ui = new(src)
+ mark_ui = new(src)
+ faction_ui = new(src)
+ minimap_type = get_minimap_flag_for_faction(hivenumber)
+ tacmap = new(src, minimap_type)
+ if(!internal_faction)
+ internal_faction = name
+ for(var/number in 1 to 999)
+ available_nicknumbers += number
+ if(hivenumber != XENO_HIVE_NORMAL)
+ return
+
+ if(!evolution_menu_images)
+ evolution_menu_images = list()
+ generate_evo_menu_images()
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_POST_SETUP, PROC_REF(post_setup))
+
+///Generate the image()'s requried for the evolution radial menu.
+/datum/hive_status/proc/generate_evo_menu_images()
+ for(var/datum/caste_datum/caste as anything in subtypesof(/datum/caste_datum))
+ evolution_menu_images[initial(caste.caste_type)] = image('icons/mob/xenos/radial_xenos.dmi', initial(caste.caste_type))
+
+/datum/hive_status/proc/post_setup()
+ SIGNAL_HANDLER
+
+ setup_evolution_announcements()
+ setup_pylon_limits()
+
+/datum/hive_status/proc/setup_evolution_announcements()
+ for(var/time in GLOB.xeno_evolve_times)
+ if(time == "0")
+ continue
+
+ addtimer(CALLBACK(src, PROC_REF(announce_evolve_available), GLOB.xeno_evolve_times[time]), text2num(time))
+
+/// Sets up limits on pylons in New() for potential futureproofing with more static comms
+/datum/hive_status/proc/setup_pylon_limits()
+ hive_structures_limit[XENO_STRUCTURE_PYLON] = length(GLOB.all_static_telecomms_towers) || 2
+
+/datum/hive_status/proc/announce_evolve_available(list/datum/caste_datum/available_castes)
+
+ var/list/castes_available = list()
+ for(var/datum/caste_datum/current_caste as anything in available_castes)
+ castes_available += initial(current_caste.caste_type)
+
+ var/castes = castes_available.Join(", ")
+ xeno_message(SPAN_XENOANNOUNCE("The Hive is now strong enough to support: [castes]"))
+ xeno_maptext("The Hive can now support: [castes]", "Hive Strengthening")
+
+
+// Adds a xeno to this hive
+/datum/hive_status/proc/add_xeno(mob/living/carbon/xenomorph/X)
+ if(!X || !istype(X))
+ return
+
+ // If the xeno is part of another hive, they should be removed from that one first
+ if(X.hive && X.hive != src)
+ X.hive.remove_xeno(X, TRUE)
+
+ // Already in the hive
+ if(X in totalXenos)
+ return
+
+ // Can only have one queen.
+ if(isqueen(X))
+ if(!living_xeno_queen && !should_block_game_interaction(X)) // Don't consider xenos in admin level
+ set_living_xeno_queen(X)
+
+ X.hivenumber = hivenumber
+ X.hive = src
+
+ X.set_faction(internal_faction)
+
+ if(X.hud_list)
+ X.hud_update()
+
+ var/area/A = get_area(X)
+ if(!should_block_game_interaction(X) || (A.flags_atom & AREA_ALLOW_XENO_JOIN))
+ totalXenos += X
+ if(X.tier == 2)
+ tier_2_xenos += X
+ else if(X.tier == 3)
+ tier_3_xenos += X
+
+ // Xenos are a fuckfest of cross-dependencies of different datums that are initialized at different times
+ // So don't even bother trying updating UI here without large refactors
+
+// Removes the xeno from the hive
+/datum/hive_status/proc/remove_xeno(mob/living/carbon/xenomorph/xeno, hard = FALSE, light_mode = FALSE)
+ if(!xeno || !istype(xeno))
+ return
+
+ // Make sure the xeno was in the hive in the first place
+ if(!(xeno in totalXenos))
+ return
+
+ // This might be a redundant check now that Queen/Destroy() checks, but doesn't hurt to double check
+ if(living_xeno_queen == xeno)
+ var/mob/living/carbon/xenomorph/queen/next_queen = null
+ for(var/mob/living/carbon/xenomorph/queen/queen in totalXenos)
+ if(!should_block_game_interaction(queen) && queen != src && !QDELETED(queen))
+ next_queen = queen
+ break
+
+ set_living_xeno_queen(next_queen) // either null or a queen
+
+ // We allow "soft" removals from the hive (the xeno still retains information about the hive)
+ // This is so that xenos can add themselves back to the hive if they should die or otherwise go "on leave" from the hive
+ if(hard)
+ xeno.hivenumber = 0
+ xeno.hive = null
+#ifndef UNIT_TESTS // Since this is a hard ref, we shouldn't confuse create_and_destroy
+ else
+ total_dead_xenos += xeno
+#endif
+
+ totalXenos -= xeno
+ if(xeno.tier == 2)
+ tier_2_xenos -= xeno
+ else if(xeno.tier == 3)
+ tier_3_xenos -= xeno
+
+ // Only handle free slots if the xeno is not in tdome
+ if(!should_block_game_interaction(xeno))
+ var/selected_caste = GLOB.xeno_datum_list[xeno.caste_type]?.type
+ if(used_slots[selected_caste])
+ used_slots[selected_caste]--
+
+ if(!light_mode)
+ hive_ui.update_xeno_counts()
+ hive_ui.xeno_removed(xeno)
+
+/datum/hive_status/proc/set_living_xeno_queen(mob/living/carbon/xenomorph/queen/queen)
+ if(!queen)
+ SStracking.delete_leader("hive_[hivenumber]")
+ SStracking.stop_tracking("hive_[hivenumber]", living_xeno_queen)
+ SShive_status.wait = 10 SECONDS
+ else
+ SStracking.set_leader("hive_[hivenumber]", queen)
+ SShive_status.wait = 2 SECONDS
+
+ SEND_SIGNAL(src, COMSIG_HIVE_NEW_QUEEN, queen)
+ living_xeno_queen = queen
+
+ recalculate_hive()
+
+/datum/hive_status/proc/recalculate_hive()
+ //No leaders for a Hive without a Queen!
+ queen_leader_limit = living_xeno_queen ? 4 : 0
+
+ if (xeno_leader_list.len > queen_leader_limit)
+ var/diff = 0
+ for (var/i in queen_leader_limit + 1 to xeno_leader_list.len)
+ if(!open_xeno_leader_positions.Remove(i))
+ remove_hive_leader(xeno_leader_list[i])
+ diff++
+ xeno_leader_list.len -= diff // Changing the size of xeno_leader_list needs to go at the end or else it won't iterate through the list properly
+ else if (xeno_leader_list.len < queen_leader_limit)
+ for (var/i in xeno_leader_list.len + 1 to queen_leader_limit)
+ open_xeno_leader_positions += i
+ xeno_leader_list.len++
+
+ hive_ui.update_all_data()
+
+/datum/hive_status/proc/add_hive_leader(mob/living/carbon/xenomorph/xeno)
+ if(!xeno)
+ return FALSE //How did this even happen?
+ if(!open_xeno_leader_positions.len)
+ return FALSE //Too many leaders already (no available xeno leader positions)
+ if(xeno.hive_pos != NORMAL_XENO)
+ return FALSE //Already on the list
+ var/leader_num = open_xeno_leader_positions[1]
+ xeno_leader_list[leader_num] = xeno
+ xeno.hive_pos = XENO_LEADER_HIVE_POS(leader_num)
+ xeno.handle_xeno_leader_pheromones()
+ xeno.hud_update() // To add leader star
+ open_xeno_leader_positions -= leader_num
+
+ xeno.update_minimap_icon()
+
+ give_action(xeno, /datum/action/xeno_action/activable/info_marker)
+
+ hive_ui.update_xeno_keys()
+ return TRUE
+
+/datum/hive_status/proc/remove_hive_leader(mob/living/carbon/xenomorph/xeno, light_mode = FALSE)
+ if(!istype(xeno) || !IS_XENO_LEADER(xeno))
+ return FALSE
+
+ var/leader_num = GET_XENO_LEADER_NUM(xeno)
+
+ xeno_leader_list[leader_num] = null
+
+ if(!light_mode) // Don't run side effects during deletions. Better yet, replace all this by signals someday
+ xeno.hive_pos = NORMAL_XENO
+ xeno.handle_xeno_leader_pheromones()
+ xeno.hud_update() // To remove leader star
+
+ // Need to maintain ascending order of open_xeno_leader_positions
+ for (var/i in 1 to queen_leader_limit)
+ if (i > open_xeno_leader_positions.len || open_xeno_leader_positions[i] > leader_num)
+ open_xeno_leader_positions.Insert(i, leader_num)
+ break
+
+ if(!light_mode)
+ hive_ui.update_xeno_keys()
+
+ for(var/obj/effect/alien/resin/marker/leaderless_mark in resin_marks) //no resin_mark limit abuse
+ if(leaderless_mark.createdby == xeno.nicknumber)
+ qdel(leaderless_mark)
+
+ xeno.update_minimap_icon()
+
+ remove_action(xeno, /datum/action/xeno_action/activable/info_marker)
+
+ return TRUE
+
+/datum/hive_status/proc/replace_hive_leader(mob/living/carbon/xenomorph/original, mob/living/carbon/xenomorph/replacement)
+ if(!replacement || replacement.hive_pos != NORMAL_XENO)
+ return remove_hive_leader(original)
+
+ var/leader_num = GET_XENO_LEADER_NUM(original)
+
+ xeno_leader_list[leader_num] = replacement
+
+ original.hive_pos = NORMAL_XENO
+ original.handle_xeno_leader_pheromones()
+ original.hud_update() // To remove leader star
+ remove_action(original, /datum/action/xeno_action/activable/info_marker)
+
+ replacement.hive_pos = XENO_LEADER_HIVE_POS(leader_num)
+ replacement.handle_xeno_leader_pheromones()
+ replacement.hud_update() // To add leader star
+ give_action(replacement, /datum/action/xeno_action/activable/info_marker)
+
+ hive_ui.update_xeno_keys()
+
+/datum/hive_status/proc/handle_xeno_leader_pheromones()
+ for(var/mob/living/carbon/xenomorph/L in xeno_leader_list)
+ L.handle_xeno_leader_pheromones()
+
+/*
+ * Helper procs for the Hive Status UI
+ * These are all called by the hive status UI manager to update its data
+ */
+
+// Returns a list of how many of each caste of xeno there are, sorted by tier
+/datum/hive_status/proc/get_xeno_counts()
+ // Every caste is manually defined here so you get
+ var/list/xeno_counts = list(
+ // Yes, Queen is technically considered to be tier 0
+ list(XENO_CASTE_LARVA = 0, "Queen" = 0),
+ list(XENO_CASTE_DRONE = 0, XENO_CASTE_RUNNER = 0, XENO_CASTE_SENTINEL = 0, XENO_CASTE_DEFENDER = 0),
+ list(XENO_CASTE_HIVELORD = 0, XENO_CASTE_BURROWER = 0, XENO_CASTE_CARRIER = 0, XENO_CASTE_LURKER = 0, XENO_CASTE_SPITTER = 0, XENO_CASTE_WARRIOR = 0),
+ list(XENO_CASTE_BOILER = 0, XENO_CASTE_CRUSHER = 0, XENO_CASTE_PRAETORIAN = 0, XENO_CASTE_RAVAGER = 0)
+ )
+
+ for(var/mob/living/carbon/xenomorph/X in totalXenos)
+ //don't show xenos in the thunderdome when admins test stuff.
+ if(should_block_game_interaction(X))
+ var/area/A = get_area(X)
+ if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN))
+ continue
+
+ if(X.caste && X.counts_for_slots)
+ xeno_counts[X.caste.tier+1][X.caste.caste_type]++
+
+ return xeno_counts
+
+// Returns a sorted list of some basic info (stuff that's needed for sorting) about all the xenos in the hive
+// The idea is that we sort this list, and use it as a "key" for all the other information (especially the nicknumber)
+// in the hive status UI. That way we can minimize the amount of sorts performed by only calling this when xenos are created/disposed
+/datum/hive_status/proc/get_xeno_keys()
+ var/list/xenos = list()
+
+ for(var/mob/living/carbon/xenomorph/X in totalXenos)
+ if(should_block_game_interaction(X))
+ var/area/A = get_area(X)
+ if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN))
+ continue
+
+ if(!(X in GLOB.living_xeno_list))
+ continue
+
+ // This looks weird, but in DM adding List A to List B actually adds each item in List B to List A, not List B itself.
+ // Having a nested list like this sort of tricks it into adding the list instead.
+ // In this case this results in an array of different 'xeno' dictionaries, rather than just a dictionary.
+ xenos += list(list(
+ "nicknumber" = X.nicknumber,
+ "tier" = X.tier, // This one is only important for sorting
+ "is_leader" = (IS_XENO_LEADER(X)),
+ "is_queen" = istype(X.caste, /datum/caste_datum/queen),
+ "caste_type" = X.caste_type
+ ))
+
+ // Make it all nice and fancy by sorting the list before returning it
+ var/list/sorted_keys = sort_xeno_keys(xenos)
+ if(length(sorted_keys))
+ return sorted_keys
+ return xenos
+
+// This sorts the xeno info list by multiple criteria. Prioritized in order:
+// 1. Queen
+// 2. Leaders
+// 3. Tier
+// It uses a slightly modified insertion sort to accomplish this
+/datum/hive_status/proc/sort_xeno_keys(list/xenos)
+ if(!length(xenos))
+ return
+
+ var/list/sorted_list = xenos.Copy()
+
+ if(!length(sorted_list))
+ return
+
+ for(var/index in 2 to length(sorted_list))
+ var/j = index
+
+ while(j > 1)
+ var/current = sorted_list[j]
+ var/prev = sorted_list[j-1]
+
+ // Queen comes first, always
+ if(current["is_queen"])
+ sorted_list.Swap(j-1, j)
+ j--
+ continue
+
+ // don't muck up queen's slot
+ if(prev["is_queen"])
+ j--
+ continue
+
+ // Leaders before normal xenos
+ if(!prev["is_leader"] && current["is_leader"])
+ sorted_list.Swap(j-1, j)
+ j--
+ continue
+
+ // Make sure we're only comparing leaders to leaders and non-leaders to non-leaders when sorting
+ // This means we get leaders sorted first, then non-leaders sorted
+ // Sort by tier first, higher tiers over lower tiers, and then by name alphabetically
+
+ // Could not think of an elegant way to write this
+ if(!(current["is_leader"]^prev["is_leader"])\
+ && (prev["tier"] < current["tier"]\
+ || prev["tier"] == current["tier"] && prev["caste_type"] > current["caste_type"]\
+ ))
+ sorted_list.Swap(j-1, j)
+
+ j--
+
+ return sorted_list
+
+// Returns a list with some more info about all xenos in the hive
+/datum/hive_status/proc/get_xeno_info()
+ var/list/xenos = list()
+
+ for(var/mob/living/carbon/xenomorph/X in totalXenos)
+ if(should_block_game_interaction(X))
+ var/area/A = get_area(X)
+ if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN))
+ continue
+
+ var/xeno_name = X.name
+ // goddamn fucking larvas with their weird ass maturing system
+ // its name updates with its icon, unlike other castes which only update the mature/elder, etc. prefix on evolve
+ if(istype(X, /mob/living/carbon/xenomorph/larva))
+ xeno_name = "Larva ([X.nicknumber])"
+ xenos["[X.nicknumber]"] = list(
+ "name" = xeno_name,
+ "strain" = X.get_strain_name(),
+ "ref" = "\ref[X]"
+ )
+
+ return xenos
+
+/datum/hive_status/proc/set_hive_location(obj/effect/alien/resin/special/pylon/core/C)
+ if(!C || C == hive_location)
+ return
+ var/area/A = get_area(C)
+ xeno_message(SPAN_XENOANNOUNCE("The Queen has set the hive location as \the [A]."), 3, hivenumber)
+ hive_location = C
+ hive_ui.update_hive_location()
+
+// Returns a list of xeno healths and locations
+/datum/hive_status/proc/get_xeno_vitals()
+ var/list/xenos = list()
+
+ for(var/mob/living/carbon/xenomorph/X in totalXenos)
+ if(should_block_game_interaction(X))
+ var/area/A = get_area(X)
+ if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN))
+ continue
+
+ if(!(X in GLOB.living_xeno_list))
+ continue
+
+ var/area/A = get_area(X)
+ var/area_name = "Unknown"
+ if(A)
+ area_name = A.name
+
+ xenos["[X.nicknumber]"] = list(
+ "health" = round((X.health / X.maxHealth) * 100, 1),
+ "area" = area_name,
+ "is_ssd" = (!X.client)
+ )
+
+ return xenos
+
+#define TIER_3 "3"
+#define TIER_2 "2"
+#define OPEN_SLOTS "open_slots"
+#define GUARANTEED_SLOTS "guaranteed_slots"
+
+// Returns an assoc list of open slots and guaranteed slots left
+/datum/hive_status/proc/get_tier_slots()
+ var/list/slots = list(
+ TIER_3 = list(
+ OPEN_SLOTS = 0,
+ GUARANTEED_SLOTS = list(),
+ ),
+ TIER_2 = list(
+ OPEN_SLOTS = 0,
+ GUARANTEED_SLOTS = list(),
+ ),
+ )
+
+ var/used_tier_2_slots = length(tier_2_xenos)
+ var/used_tier_3_slots = length(tier_3_xenos)
+
+ for(var/caste_path in free_slots)
+ var/slots_free = free_slots[caste_path]
+ var/slots_used = used_slots[caste_path]
+ var/datum/caste_datum/current_caste = caste_path
+ if(slots_used)
+ // Don't count any free slots in use
+ switch(initial(current_caste.tier))
+ if(2)
+ used_tier_2_slots -= min(slots_used, slots_free)
+ if(3)
+ used_tier_3_slots -= min(slots_used, slots_free)
+ if(slots_free <= slots_used)
+ continue
+ // Display any free slots available
+ switch(initial(current_caste.tier))
+ if(2)
+ slots[TIER_2][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used
+ if(3)
+ slots[TIER_3][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used
+
+ var/burrowed_factor = min(stored_larva, sqrt(4*stored_larva))
+ var/effective_total = floor(burrowed_factor)
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos)
+ if(xeno.counts_for_slots)
+ effective_total++
+
+ // Tier 3 slots are always 20% of the total xenos in the hive
+ slots[TIER_3][OPEN_SLOTS] = max(0, ceil(0.20*effective_total/tier_slot_multiplier) - used_tier_3_slots)
+ // Tier 2 slots are between 30% and 50% of the hive, depending
+ // on how many T3s there are.
+ slots[TIER_2][OPEN_SLOTS] = max(0, ceil(0.5*effective_total/tier_slot_multiplier) - used_tier_2_slots - used_tier_3_slots)
+
+ return slots
+
+#undef TIER_3
+#undef TIER_2
+#undef OPEN_SLOTS
+#undef GUARANTEED_SLOTS
+
+/datum/hive_status/proc/can_build_structure(structure_name)
+ if(!structure_name || !hive_structures_limit[structure_name])
+ return FALSE
+ if(get_structure_count(structure_name) >= hive_structures_limit[structure_name])
+ return FALSE
+ return TRUE
+
+/datum/hive_status/proc/get_structure_count(structure_name)
+ return length(hive_structures[structure_name]) + length(hive_constructions[structure_name])
+
+/datum/hive_status/proc/has_structure(structure_name)
+ if(!structure_name)
+ return FALSE
+ if(hive_structures[structure_name] && hive_structures[structure_name].len)
+ return TRUE
+ return FALSE
+
+/datum/hive_status/proc/add_construction(obj/effect/alien/resin/construction/S)
+ if(!S || !S.template)
+ return FALSE
+ var/name_ref = initial(S.template.name)
+ if(!hive_constructions[name_ref])
+ hive_constructions[name_ref] = list()
+ if(hive_constructions[name_ref].len >= hive_structures_limit[name_ref])
+ return FALSE
+ hive_constructions[name_ref] += src
+ return TRUE
+
+/datum/hive_status/proc/remove_construction(obj/effect/alien/resin/construction/S)
+ if(!S || !S.template)
+ return FALSE
+ var/name_ref = initial(S.template.name)
+ hive_constructions[name_ref] -= src
+ return TRUE
+
+/datum/hive_status/proc/add_special_structure(obj/effect/alien/resin/special/S)
+ if(!S)
+ return FALSE
+ var/name_ref = initial(S.name)
+ if(!hive_structures[name_ref])
+ hive_structures[name_ref] = list()
+ if(hive_structures[name_ref].len >= hive_structures_limit[name_ref])
+ return FALSE
+ hive_structures[name_ref] += S
+ return TRUE
+
+/datum/hive_status/proc/remove_special_structure(obj/effect/alien/resin/special/S)
+ if(!S)
+ return FALSE
+ var/name_ref = initial(S.name)
+ hive_structures[name_ref] -= S
+ return TRUE
+
+/datum/hive_status/proc/has_special_structure(name_ref)
+ if(!name_ref || !hive_structures[name_ref] || !hive_structures[name_ref].len)
+ return 0
+ return hive_structures[name_ref].len
+
+/datum/hive_status/proc/abandon_on_hijack()
+ var/area/hijacked_dropship = get_area(living_xeno_queen)
+ var/shipside_humans_weighted_count = 0
+ var/xenos_count = 0
+ for(var/name_ref in hive_structures)
+ for(var/obj/effect/alien/resin/special/S in hive_structures[name_ref])
+ if(get_area(S) == hijacked_dropship)
+ continue
+ S.hijack_delete = TRUE
+ hive_structures[name_ref] -= S
+ qdel(S)
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos)
+ if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z))
+ if(isfacehugger(xeno) || islesserdrone(xeno))
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind."))
+ if(xeno.stomach_contents.len)
+ xeno.devour_timer = 0
+ xeno.handle_stomach_contents()
+ qdel(xeno)
+ continue
+ if(xeno.hunter_data.hunted && !isqueen(xeno))
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, seperating you from her hive! You must defend yourself from the headhunter before you can enter hibernation..."))
+ xeno.set_hive_and_update(XENO_HIVE_FORSAKEN)
+ else
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind."))
+ if(xeno.stomach_contents.len)
+ xeno.devour_timer = 0
+ xeno.handle_stomach_contents()
+ qdel(xeno)
+ stored_larva++
+ continue
+ if(xeno.tier >= 1)
+ xenos_count++
+ for(var/i in GLOB.alive_mob_list)
+ var/mob/living/potential_host = i
+ if(!(potential_host.status_flags & XENO_HOST))
+ continue
+ if(!is_ground_level(potential_host.z) || get_area(potential_host) == hijacked_dropship)
+ continue
+ var/obj/item/alien_embryo/A = locate() in potential_host
+ if(A && A.hivenumber != hivenumber)
+ continue
+ for(var/obj/item/alien_embryo/embryo in potential_host)
+ embryo.hivenumber = XENO_HIVE_FORSAKEN
+ potential_host.update_med_icon()
+ for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list)
+ if(!(isspecieshuman(current_human) || isspeciessynth(current_human)))
+ continue
+ var/datum/job/job = GLOB.RoleAuthority.roles_for_mode[current_human.job]
+ if(!job)
+ continue
+ var/turf/turf = get_turf(current_human)
+ if(is_mainship_level(turf?.z))
+ shipside_humans_weighted_count += GLOB.RoleAuthority.calculate_role_weight(job)
+ hijack_burrowed_surge = TRUE
+ hijack_burrowed_left = max(ceil(shipside_humans_weighted_count * 0.5) - xenos_count, 5)
+ hivecore_cooldown = FALSE
+ xeno_message(SPAN_XENOBOLDNOTICE("The weeds have recovered! A new hive core can be built!"),3,hivenumber)
+
+/datum/hive_status/proc/free_respawn(client/C)
+ stored_larva++
+ if(!hive_location || !hive_location.spawn_burrowed_larva(C.mob))
+ stored_larva--
+ else
+ hive_ui.update_burrowed_larva()
+
+/datum/hive_status/proc/respawn_on_turf(client/xeno_client, turf/spawning_turf)
+ var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber)
+ if(isnull(new_xeno))
+ return FALSE
+
+ if(!SSticker.mode.transfer_xeno(xeno_client.mob, new_xeno))
+ qdel(new_xeno)
+ return FALSE
+
+ new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly emerges from a dead husk!"),
+ SPAN_XENOANNOUNCE("The hive has no core! You manage to emerge from your old husk as a larva!"))
+ msg_admin_niche("[key_name(new_xeno)] respawned at \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]")
+ playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1)
+ if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN)
+ window_flash(new_xeno.client)
+
+ hive_ui.update_burrowed_larva()
+
+/datum/hive_status/proc/do_buried_larva_spawn(mob/xeno_candidate)
+ var/spawning_area
+ if(hive_location)
+ spawning_area = hive_location
+ else if(living_xeno_queen)
+ spawning_area = living_xeno_queen
+ else for(var/mob/living/carbon/xenomorpheus as anything in totalXenos)
+ if(islarva(xenomorpheus) || isxeno_builder(xenomorpheus)) //next to xenos that should be in a safe spot
+ spawning_area = xenomorpheus
+ if(!spawning_area)
+ spawning_area = pick(totalXenos) // FUCK IT JUST GO ANYWHERE
+ var/list/turf_list
+ for(var/turf/open/open_turf in orange(3, spawning_area))
+ if(istype(open_turf, /turf/open/space))
+ continue
+ LAZYADD(turf_list, open_turf)
+ // just on the off-chance
+ if(!LAZYLEN(turf_list))
+ return FALSE
+ var/turf/open/spawning_turf = pick(turf_list)
+
+ var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber)
+ if(isnull(new_xeno))
+ return FALSE
+
+ if(!SSticker.mode.transfer_xeno(xeno_candidate, new_xeno))
+ qdel(new_xeno)
+ return FALSE
+ new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of \the [spawning_turf]!"),
+ SPAN_XENODANGER("You burrow out of \the [spawning_turf] and awaken from your slumber. For the Hive!"))
+ msg_admin_niche("[key_name(new_xeno)] burrowed out from \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]")
+ playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1)
+ to_chat(new_xeno, SPAN_XENOANNOUNCE("You are a xenomorph larva awakened from slumber!"))
+ if(new_xeno.client)
+ if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN)
+ window_flash(new_xeno.client)
+
+ stored_larva--
+ hive_ui.update_burrowed_larva()
+
+/mob/living/proc/ally_of_hivenumber(hivenumber)
+ var/datum/hive_status/indexed_hive = GLOB.hive_datum[hivenumber]
+ if(!indexed_hive)
+ return FALSE
+
+ return indexed_hive.is_ally(src)
+
+/datum/hive_status/proc/is_ally(mob/living/living_mob)
+ if(isxeno(living_mob))
+ var/mob/living/carbon/xenomorph/zenomorf = living_mob
+ if(zenomorf.hivenumber == hivenumber)
+ return !zenomorf.banished
+
+ if(!living_mob.faction)
+ return FALSE
+
+ return faction_is_ally(living_mob.faction)
+
+/datum/hive_status/proc/faction_is_ally(faction, ignore_queen_check = FALSE)
+ if(faction == internal_faction)
+ return TRUE
+ if(!ignore_queen_check && !living_xeno_queen)
+ return FALSE
+
+ return allies[faction]
+
+/datum/hive_status/proc/can_delay_round_end(mob/living/carbon/xenomorph/xeno)
+ if(HAS_TRAIT(src, TRAIT_NO_HIVE_DELAY))
+ return FALSE
+ return TRUE
+
+/datum/hive_status/proc/update_hugger_limit()
+ var/countable_xeno_iterator = 0
+ for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos)
+ if(cycled_xeno.counts_for_slots)
+ countable_xeno_iterator++
+
+ playable_hugger_limit = max(floor(countable_xeno_iterator / playable_hugger_max_divisor), playable_hugger_minimum)
+
+/datum/hive_status/proc/can_spawn_as_hugger(mob/dead/observer/user)
+ if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber])
+ return FALSE
+ if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned
+ to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph."))
+ return FALSE
+ if(world.time < hugger_timelock)
+ to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet..."))
+ return FALSE
+ if(world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY)
+ var/time_left = floor((user.timeofdeath + JOIN_AS_FACEHUGGER_DELAY - world.time) / 10)
+ to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a facehugger until 3 minutes have passed ([time_left] seconds remaining)."))
+ return FALSE
+ if(totalXenos.len <= 0)
+ //This is to prevent people from joining as Forsaken Huggers on the pred ship
+ to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!"))
+ return FALSE
+ for(var/mob_name in banished_ckeys)
+ if(banished_ckeys[mob_name] == user.ckey)
+ to_chat(user, SPAN_WARNING("You are banished from the [name], you may not rejoin unless the Queen re-admits you or dies."))
+ return FALSE
+
+ update_hugger_limit()
+
+ var/current_hugger_count = 0
+ for(var/mob/mob as anything in totalXenos)
+ if(isfacehugger(mob))
+ current_hugger_count++
+ if(playable_hugger_limit <= current_hugger_count)
+ to_chat(user, SPAN_WARNING("\The [GLOB.hive_datum[hivenumber]] cannot support more facehuggers! Limit: [current_hugger_count]/[playable_hugger_limit]"))
+ return FALSE
+
+ if(tgui_alert(user, "Are you sure you want to become a facehugger?", "Confirmation", list("Yes", "No")) != "Yes")
+ return FALSE
+
+ if(!user.client)
+ return FALSE
+
+ return TRUE
+
+/datum/hive_status/proc/get_current_playable_facehugger_count()
+ var/count = 0
+ for(var/mob/mob as anything in totalXenos)
+ if(isfacehugger(mob))
+ count++
+ return count
+
+/datum/hive_status/proc/spawn_as_hugger(mob/dead/observer/user, atom/A)
+ var/mob/living/carbon/xenomorph/facehugger/hugger = new /mob/living/carbon/xenomorph/facehugger(A.loc, null, hivenumber)
+ user.mind.transfer_to(hugger, TRUE)
+ hugger.visible_message(SPAN_XENODANGER("A facehugger suddenly emerges out of \the [A]!"), SPAN_XENODANGER("You emerge out of \the [A] and awaken from your slumber. For the Hive!"))
+ playsound(hugger, 'sound/effects/xeno_newlarva.ogg', 25, TRUE)
+ hugger.generate_name()
+ hugger.timeofdeath = user.timeofdeath // Keep old death time
+
+/datum/hive_status/proc/update_lesser_drone_limit()
+ var/countable_xeno_iterator = 0
+ for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos)
+ if(cycled_xeno.counts_for_slots)
+ countable_xeno_iterator++
+
+ lesser_drone_limit = max(floor(countable_xeno_iterator / playable_lesser_drones_max_divisor), lesser_drone_minimum)
+
+/datum/hive_status/proc/can_spawn_as_lesser_drone(mob/dead/observer/user, obj/effect/alien/resin/special/pylon/spawning_pylon)
+ if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber])
+ return FALSE
+
+ if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned
+ to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph."))
+ return FALSE
+
+ if(world.time - user.timeofdeath < JOIN_AS_LESSER_DRONE_DELAY)
+ var/time_left = floor((user.timeofdeath + JOIN_AS_LESSER_DRONE_DELAY - world.time) / 10)
+ to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a lesser drone until 30 seconds have passed ([time_left] seconds remaining)."))
+ return FALSE
+
+ if(totalXenos.len <= 0)
+ to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!"))
+ return FALSE
+
+ if(!living_xeno_queen)
+ to_chat(user, SPAN_WARNING("The selected hive does not have a Queen!"))
+ return FALSE
+
+ if(spawning_pylon.lesser_drone_spawns < 1)
+ to_chat(user, SPAN_WARNING("The selected core or pylon does not have enough power for a lesser drone!"))
+ return FALSE
+
+ update_lesser_drone_limit()
+
+ var/current_lesser_drone_count = 0
+ for(var/mob/mob as anything in totalXenos)
+ if(islesserdrone(mob))
+ current_lesser_drone_count++
+
+ if(lesser_drone_limit <= current_lesser_drone_count)
+ to_chat(user, SPAN_WARNING("[GLOB.hive_datum[hivenumber]] cannot support more lesser drones! Limit: [current_lesser_drone_count]/[lesser_drone_limit]"))
+ return FALSE
+
+ if(!user.client)
+ return FALSE
+
+ return TRUE
+
+// Get amount of real xenos, don't count lessers/huggers
+/datum/hive_status/proc/get_real_total_xeno_count()
+ var/count = 0
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos)
+ if(xeno.counts_for_slots)
+ count++
+ return count
+
+// Checks if we hit larva limit
+/datum/hive_status/proc/check_if_hit_larva_from_pylon_limit()
+ var/groundside_humans_weighted_count = 0
+ for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list)
+ if(!(isspecieshuman(current_human) || isspeciessynth(current_human)))
+ continue
+ var/datum/job/job = GLOB.RoleAuthority.roles_for_mode[current_human.job]
+ if(!job)
+ continue
+ var/turf/turf = get_turf(current_human)
+ if(is_ground_level(turf?.z))
+ groundside_humans_weighted_count += GLOB.RoleAuthority.calculate_role_weight(job)
+ hit_larva_pylon_limit = (get_real_total_xeno_count() + stored_larva) > (groundside_humans_weighted_count * ENDGAME_LARVA_CAP_MULTIPLIER)
+ hive_ui.update_pylon_status()
+ return hit_larva_pylon_limit
+
+///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst
+/datum/hive_status/proc/increase_larva_after_burst()
+ var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst)
+ partial_larva += extra_per_burst
+ convert_partial_larva_to_full_larva()
+
+///Called after times when partial larva are added to process them to stored larva
+/datum/hive_status/proc/convert_partial_larva_to_full_larva()
+ for(var/i = 1 to partial_larva)
+ partial_larva--
+ stored_larva++
+
+/datum/hive_status/corrupted
+ name = "Corrupted Hive"
+ reporting_id = "corrupted"
+ hivenumber = XENO_HIVE_CORRUPTED
+ prefix = "Corrupted "
+ color = "#80ff80"
+ ui_color ="#4d994d"
+ latejoin_burrowed = FALSE
+
+ need_round_end_check = TRUE
+
+ var/list/defectors = list()
+ var/list/datum/weakref/personal_allies = list()
+
+/datum/hive_status/corrupted/add_xeno(mob/living/carbon/xenomorph/xeno)
+ . = ..()
+ xeno.add_language(LANGUAGE_ENGLISH)
+
+/datum/hive_status/corrupted/remove_xeno(mob/living/carbon/xenomorph/xeno, hard)
+ . = ..()
+ xeno.remove_language(LANGUAGE_ENGLISH)
+
+/datum/hive_status/corrupted/can_delay_round_end(mob/living/carbon/xenomorph/xeno)
+ if(!faction_is_ally(FACTION_MARINE, TRUE))
+ return TRUE
+ return FALSE
+
+/datum/hive_status/alpha
+ name = "Alpha Hive"
+ reporting_id = "alpha"
+ hivenumber = XENO_HIVE_ALPHA
+ prefix = "Alpha "
+ color = "#ff4040"
+ ui_color = "#992626"
+ latejoin_burrowed = FALSE
+
+ dynamic_evolution = FALSE
+
+/datum/hive_status/bravo
+ name = "Bravo Hive"
+ reporting_id = "bravo"
+ hivenumber = XENO_HIVE_BRAVO
+ prefix = "Bravo "
+ color = "#ffff80"
+ ui_color = "#99994d"
+ latejoin_burrowed = FALSE
+
+ dynamic_evolution = FALSE
+
+/datum/hive_status/charlie
+ name = "Charlie Hive"
+ reporting_id = "charlie"
+ hivenumber = XENO_HIVE_CHARLIE
+ prefix = "Charlie "
+ color = "#bb40ff"
+ ui_color = "#702699"
+ latejoin_burrowed = FALSE
+
+ dynamic_evolution = FALSE
+
+/datum/hive_status/delta
+ name = "Delta Hive"
+ reporting_id = "delta"
+ hivenumber = XENO_HIVE_DELTA
+ prefix = "Delta "
+ color = "#8080ff"
+ ui_color = "#4d4d99"
+ latejoin_burrowed = FALSE
+
+ dynamic_evolution = FALSE
+
+/datum/hive_status/feral
+ name = "Feral Hive"
+ reporting_id = "feral"
+ hivenumber = XENO_HIVE_FERAL
+ prefix = "Feral "
+ color = "#828296"
+ ui_color = "#828296"
+
+ construction_allowed = XENO_NOBODY
+ destruction_allowed = XENO_NOBODY
+ dynamic_evolution = FALSE
+ allow_no_queen_actions = TRUE
+ allow_no_queen_evo = TRUE
+ allow_queen_evolve = FALSE
+ latejoin_burrowed = FALSE
+
+/datum/hive_status/forsaken
+ name = "Forsaken Hive"
+ reporting_id = "forsaken"
+ hivenumber = XENO_HIVE_FORSAKEN
+ prefix = "Forsaken "
+ color = "#cc8ec4"
+ ui_color = "#cc8ec4"
+
+ dynamic_evolution = FALSE
+ allow_no_queen_actions = TRUE
+ allow_no_queen_evo = TRUE
+ allow_queen_evolve = FALSE
+ latejoin_burrowed = FALSE
+
+ need_round_end_check = TRUE
+
+/datum/hive_status/forsaken/can_delay_round_end(mob/living/carbon/xenomorph/xeno)
+ return FALSE
+
+/datum/hive_status/tutorial
+ name = "Tutorial Hive"
+ reporting_id = "tutorial"
+ hivenumber = XENO_HIVE_TUTORIAL
+ prefix = "Inquisitive "
+ latejoin_burrowed = FALSE
+
+ dynamic_evolution = FALSE
+ allow_queen_evolve = TRUE
+ evolution_without_ovipositor = FALSE
+ allow_no_queen_actions = TRUE
+
+ ///Can have many tutorials going at once.
+ hive_structures_limit = list(
+ XENO_STRUCTURE_CORE = 999,
+ XENO_STRUCTURE_CLUSTER = 999,
+ XENO_STRUCTURE_EGGMORPH = 999,
+ XENO_STRUCTURE_RECOVERY = 999,
+ )
+
+/datum/hive_status/tutorial/can_delay_round_end(mob/living/carbon/xenomorph/xeno)
+ return FALSE
+
+/datum/hive_status/yautja
+ name = "Hellhound Pack"
+ reporting_id = "hellhounds"
+ hivenumber = XENO_HIVE_YAUTJA
+ internal_faction = FACTION_YAUTJA
+
+ dynamic_evolution = FALSE
+ allow_no_queen_actions = TRUE
+ allow_no_queen_evo = TRUE
+ allow_queen_evolve = FALSE
+ latejoin_burrowed = FALSE
+
+ need_round_end_check = TRUE
+
+/datum/hive_status/yautja/can_delay_round_end(mob/living/carbon/xenomorph/xeno)
+ return FALSE
+
+/datum/hive_status/mutated
+ name = "Mutated Hive"
+ reporting_id = "mutated"
+ hivenumber = XENO_HIVE_MUTATED
+ prefix = "Mutated "
+ color = "#6abd99"
+ ui_color = "#6abd99"
+
+ hive_inherant_traits = list(TRAIT_XENONID, TRAIT_NO_COLOR)
+ latejoin_burrowed = FALSE
+
+/datum/hive_status/corrupted/tamed
+ name = "Tamed Hive"
+ reporting_id = "tamed"
+ hivenumber = XENO_HIVE_TAMED
+ prefix = "Tamed "
+ color = "#80ff80"
+
+ dynamic_evolution = FALSE
+ allow_no_queen_actions = TRUE
+ allow_no_queen_evo = TRUE
+ allow_queen_evolve = FALSE
+ latejoin_burrowed = FALSE
+
+ var/mob/living/carbon/human/leader
+ var/list/allied_factions
+
+/datum/hive_status/corrupted/tamed/New()
+ . = ..()
+ hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0
+
+/datum/hive_status/corrupted/tamed/proc/make_leader(mob/living/carbon/human/H)
+ if(!istype(H))
+ return
+
+ if(leader)
+ UnregisterSignal(leader, COMSIG_PARENT_QDELETING)
+
+ leader = H
+ RegisterSignal(leader, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdelete))
+
+/datum/hive_status/corrupted/tamed/proc/handle_qdelete(mob/living/carbon/human/H)
+ SIGNAL_HANDLER
+
+ if(H == leader)
+ leader = null
+
+ var/list/faction_groups = H.faction_group
+ if(faction_groups)
+ allied_factions = faction_groups.Copy()
+ if(!(H.faction in allied_factions))
+ allied_factions += H.faction
+
+/datum/hive_status/corrupted/tamed/add_xeno(mob/living/carbon/xenomorph/X)
+ . = ..()
+ X.faction_group = allied_factions
+
+/datum/hive_status/corrupted/tamed/remove_xeno(mob/living/carbon/xenomorph/X, hard)
+ . = ..()
+ X.faction_group = list(X.faction)
+
+/datum/hive_status/corrupted/tamed/is_ally(mob/living/carbon/C)
+ if(leader)
+ if(C.faction in leader.faction_group)
+ return TRUE
+
+ if(C.faction == leader.faction)
+ return TRUE
+ else
+ if(C.faction in allied_factions)
+ return TRUE
+
+ return ..()
+
+/datum/hive_status/corrupted/renegade
+ name = "Renegade Hive"
+ reporting_id = "renegade"
+ hivenumber = XENO_HIVE_RENEGADE
+ prefix = "Renegade "
+ color = "#9c7a4d"
+ ui_color ="#80705c"
+
+ dynamic_evolution = FALSE
+ allow_queen_evolve = FALSE
+ allow_no_queen_evo = TRUE
+ latejoin_burrowed = FALSE
+
+/datum/hive_status/corrupted/renegade/New()
+ . = ..()
+ hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0
+ for(var/faction in FACTION_LIST_HUMANOID) //renegades allied to all humanoids, but it mostly affects structures. Their ability to attack humanoids and other xenos (including of the same hive) depends on iff settings
+ allies[faction] = TRUE
+
+/datum/hive_status/corrupted/renegade/can_spawn_as_hugger(mob/dead/observer/user)
+ to_chat(user, SPAN_WARNING("The [name] cannot support facehuggers."))
+ return FALSE
+
+/datum/hive_status/corrupted/renegade/proc/iff_protection_check(mob/living/carbon/xenomorph/xeno, mob/living/carbon/attempt_harm_mob)
+ if(xeno == attempt_harm_mob)
+ return TRUE //you cannot hurt yourself...
+ if(!xeno.iff_tag)
+ return FALSE //can attack anyone if you don't have iff tag
+ if(isxeno(attempt_harm_mob))
+ var/mob/living/carbon/xenomorph/target_xeno = attempt_harm_mob
+ if(!target_xeno.iff_tag)
+ return FALSE //can attack any xeno who don't have iff tag
+ for(var/faction in xeno.iff_tag.faction_groups)
+ if(faction in target_xeno.iff_tag.faction_groups)
+ return TRUE //cannot attack xenos with same iff setting
+ return FALSE
+ for(var/faction in xeno.iff_tag.faction_groups)
+ if(faction in attempt_harm_mob.faction_group)
+ return TRUE //cannot attack mob if iff is set to at least one of its factions
+ return FALSE
+
+/datum/hive_status/corrupted/renegade/faction_is_ally(faction, ignore_queen_check = TRUE)
+ return ..()
+
+/datum/hive_status/proc/on_queen_death() //break alliances on queen's death
+ if(allow_no_queen_actions || living_xeno_queen)
+ return
+ var/broken_alliances = FALSE
+ for(var/faction in allies)
+ if(!allies[faction])
+ continue
+ change_stance(faction, FALSE)
+ broken_alliances = TRUE
+
+
+ if(broken_alliances)
+ xeno_message(SPAN_XENOANNOUNCE("With the death of the Queen, all alliances have been broken."), 3, hivenumber)
+
+/datum/hive_status/proc/change_stance(faction, should_ally)
+ if(faction == name)
+ return
+ if(allies[faction] == should_ally)
+ return
+ allies[faction] = should_ally
+
+ if(living_xeno_queen)
+ if(allies[faction])
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen set up an alliance with [faction]!"), 3, hivenumber)
+ else
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen broke the alliance with [faction]!"), 3, hivenumber)
+
+ for(var/number in GLOB.hive_datum)
+ var/datum/hive_status/target_hive = GLOB.hive_datum[number]
+ if(target_hive.name != faction)
+ continue
+ if(!target_hive.living_xeno_queen && !target_hive.allow_no_queen_actions)
+ return
+ if(allies[faction])
+ xeno_message(SPAN_XENOANNOUNCE("We sense that [name] [living_xeno_queen ? "Queen " : ""]set up an alliance with us!"), 3, target_hive.hivenumber)
+ return
+
+ xeno_message(SPAN_XENOANNOUNCE("We sense that [name] [living_xeno_queen ? "Queen " : ""]broke the alliance with us!"), 3, target_hive.hivenumber)
+ if(target_hive.allies[name]) //autobreak alliance on betrayal
+ target_hive.change_stance(name, FALSE)
+
+/datum/hive_status/corrupted/change_stance(faction, should_ally)
+ . = ..()
+ if(allies[faction])
+ return
+ if(!(faction in FACTION_LIST_HUMANOID))
+ return
+
+ for(var/mob/living/carbon/xenomorph/xeno in totalXenos) // handle defecting xenos on betrayal
+ if(!xeno.iff_tag)
+ continue
+ if(!(faction in xeno.iff_tag.faction_groups))
+ continue
+ if(xeno in defectors)
+ continue
+ if(xeno.caste_type == XENO_CASTE_QUEEN)
+ continue
+ INVOKE_ASYNC(src, PROC_REF(give_defection_choice), xeno, faction)
+ addtimer(CALLBACK(src, PROC_REF(handle_defectors), faction), 11 SECONDS)
+
+/datum/hive_status/corrupted/proc/give_defection_choice(mob/living/carbon/xenomorph/xeno, faction)
+ if(tgui_alert(xeno, "Our Queen has broken the alliance with the [faction]. The device inside our carapace begins to suppress our connection with the Hive. Do we remove it and stay loyal to her?", "Alliance broken!", list("Stay loyal", "Obey the talls"), 10 SECONDS) == "Obey the talls")
+ if(!xeno.iff_tag)
+ to_chat(xeno, SPAN_XENOWARNING("It's too late now. The device is gone and our service to the Queen continues."))
+ return
+ defectors += xeno
+ xeno.set_hive_and_update(XENO_HIVE_RENEGADE)
+ to_chat(xeno, SPAN_XENOANNOUNCE("You lost the connection with your Hive. Now you have no Queen, only your masters."))
+ to_chat(xeno, SPAN_NOTICE("Your instincts have changed, you seem compelled to protect [english_list(xeno.iff_tag.faction_groups, "no one")]."))
+ return
+ xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("We rip out [xeno.iff_tag]! For the Hive!"))
+ xeno.adjustBruteLoss(50)
+ xeno.iff_tag.forceMove(get_turf(xeno))
+ xeno.iff_tag = null
+
+/datum/hive_status/corrupted/proc/handle_defectors(faction)
+ for(var/mob/living/carbon/xenomorph/xeno in totalXenos)
+ if(!xeno.iff_tag)
+ continue
+ if(xeno in defectors)
+ continue
+ if(!(faction in xeno.iff_tag.faction_groups))
+ continue
+ xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("We rip out [xeno.iff_tag]! For the hive!"))
+ xeno.adjustBruteLoss(50)
+ xeno.iff_tag.forceMove(get_turf(xeno))
+ xeno.iff_tag = null
+ if(!length(defectors))
+ return
+
+ xeno_message(SPAN_XENOANNOUNCE("We sense that [english_list(defectors)] turned their backs against their sisters and the Queen in favor of their slavemasters!"), 3, hivenumber)
+ defectors.Cut()
+
+/datum/hive_status/corrupted/proc/add_personal_ally(mob/living/ally)
+ personal_allies += WEAKREF(ally)
+ ally.status_flags |= CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen proclaimed [ally] our ally! We must not harm them."), 3, hivenumber)
+
+/datum/hive_status/corrupted/proc/remove_personal_ally(datum/weakref/ally_ref)
+ personal_allies -= ally_ref
+ var/mob/living/ally = ally_ref.resolve()
+ if(ally)
+ ally.status_flags &= ~CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen has declared that [ally] is no longer our ally!"), 3, hivenumber)
+
+/datum/hive_status/corrupted/proc/clear_personal_allies(death = FALSE)
+ for(var/datum/weakref/ally_ref in personal_allies)
+ var/mob/living/ally = ally_ref.resolve()
+ if(!ally)
+ continue
+ ally.status_flags &= ~CORRUPTED_ALLY
+ ally.med_hud_set_status()
+ personal_allies.Cut()
+ if(!death)
+ xeno_message(SPAN_XENOANNOUNCE("Our Queen has broken all personal alliances with the talls! Favoritism is no more."), 3, hivenumber)
+ return
+ xeno_message(SPAN_XENOWARNING("With the death of the Queen, her friends no longer matter to us."), 3, hivenumber)
+
+/datum/hive_status/corrupted/is_ally(mob/living/living_mob)
+ if(living_mob.status_flags & CORRUPTED_ALLY)
+ return TRUE
+ return ..()
+
+/datum/hive_status/proc/override_evilution(evil, override)
+ if(SSxevolution)
+ SSxevolution.override_power(hivenumber, evil, override)
+
+/datum/hive_status/corrupted/on_queen_death()
+ ..()
+ if(!length(personal_allies))
+ return
+ clear_personal_allies(TRUE)
+
+//Xeno Resin Mark Shit, the very best place for it too :0)
+//Defines at the bottom of this list here will show up at the top in the mark menu
+/datum/xeno_mark_define
+ var/name = "xeno_declare"
+ var/icon_state = "empty"
+ var/desc = "Xenos make psychic markers with this meaning as positional lasting communication to eachother"
+
+/datum/xeno_mark_define/fortify
+ name = "Fortify"
+ desc = "Fortify this area!"
+ icon_state = "fortify"
+
+/datum/xeno_mark_define/weeds
+ name = "Need Weeds"
+ desc = "Need weeds here!"
+ icon_state = "weed"
+
+/datum/xeno_mark_define/nest
+ name = "Nest"
+ desc = "Nest enemies here!"
+ icon_state = "nest"
+
+/datum/xeno_mark_define/hosts
+ name = "Hosts"
+ desc = "Hosts here!"
+ icon_state = "hosts"
+
+/datum/xeno_mark_define/aide
+ name = "Aide"
+ desc = "Aide here!"
+ icon_state = "aide"
+
+/datum/xeno_mark_define/defend
+ name = "Defend"
+ desc = "Defend the hive here!"
+ icon_state = "defend"
+
+/datum/xeno_mark_define/danger
+ name = "Danger Warning"
+ desc = "Caution, danger here!"
+ icon_state = "danger"
+
+/datum/xeno_mark_define/rally
+ name = "Rally"
+ desc = "Group up here!"
+ icon_state = "rally"
+
+/datum/xeno_mark_define/hold
+ name = "Hold"
+ desc = "Hold this area!"
+ icon_state = "hold"
+
+/datum/xeno_mark_define/ambush
+ name = "Ambush"
+ desc = "Ambush the enemy here!"
+ icon_state = "ambush"
+/datum/xeno_mark_define/attack
+ name = "Attack"
+ desc = "Attack the enemy here!"
+ icon_state = "attack"
diff --git a/code/datums/factions/clf.dm b/code/datums/factions/clf.dm
index ce53b505b352..d6d010ddee11 100644
--- a/code/datums/factions/clf.dm
+++ b/code/datums/factions/clf.dm
@@ -1,15 +1,21 @@
/datum/faction/clf
- name = "Colonial Liberation Front"
- faction_tag = FACTION_CLF
+ name = NAME_FACTION_CLF
+ desc = "The Colonial Liberation Front is a paramilitary group primarily located in the Neroid sector of United Americas space. Their stated goal is to secure the independence of all of the colonies in the Neroid sector. They are the largest and most active militant group pushing for the independence of the colonies. \
+ The United Americas government classifies the CLF as a terrorist organization, with membership in the organization or providing financial or material support for the CLF being prosecutable offenses. The CLF grew organically from several different groups that formed in the wake of the Slaughter of Xibou in 2164. Prior to the slaughter of Xibou the conflicts between the United Americas government and the colonists of the Neroid sector had remained mostly non-violent. \
+ The sudden increase in tensions after Xibou, combined with the images of slaughtered fighters and civilians alike, greatly increased the willingness of the colonists to take up arms against the United Americas. Several different militant groups formed in the years following the Slaughter, and as they negotiated with one another and found common cause the CLF was formed from their union."
+
+ faction_name = FACTION_CLF
+ faction_tag = SIDE_FACTION_CLF
+ relations_pregen = RELATIONS_FACTION_CLF
/datum/faction/clf/modify_hud_holder(image/holder, mob/living/carbon/human/human)
var/hud_icon_state
- var/obj/item/card/id/ID = human.get_idcard()
+ var/obj/item/card/id/id_card = human.get_idcard()
var/_role
if(human.mind)
_role = human.job
- else if(ID)
- _role = ID.rank
+ else if(id_card)
+ _role = id_card.rank
switch(_role)
if(JOB_CLF_ENGI)
hud_icon_state = "engi"
diff --git a/code/datums/factions/colonists.dm b/code/datums/factions/colonists.dm
new file mode 100644
index 000000000000..aeb0ac337e33
--- /dev/null
+++ b/code/datums/factions/colonists.dm
@@ -0,0 +1,42 @@
+/datum/faction/colonist
+ name = NAME_FACTION_COLONIST
+ desc = "Colonists, the most ordinary citizens of the colonies, ADDITIONAL INFORMATION IS CHANGED"
+
+ faction_name = FACTION_COLONIST
+ faction_iff_tag_type = /obj/item/faction_tag/colonist
+
+ role_mappings = list(
+ MODE_NAME_EXTENDED = list(),
+ MODE_NAME_DISTRESS_SIGNAL = list(),
+ MODE_NAME_FACTION_CLASH = list(),
+ MODE_NAME_CRASH = list(),
+ MODE_NAME_WISKEY_OUTPOST = list(),
+ MODE_NAME_HUNTER_GAMES = list(),
+ MODE_NAME_HIVE_WARS = list(),
+ MODE_NAME_INFECTION = list()
+ )
+ roles_list = list(
+ MODE_NAME_EXTENDED = ROLES_REGULAR_SURV,
+ MODE_NAME_DISTRESS_SIGNAL = ROLES_REGULAR_SURV,
+ MODE_NAME_FACTION_CLASH = list(),
+ MODE_NAME_CRASH = ROLES_REGULAR_SURV,
+ MODE_NAME_WISKEY_OUTPOST = list(),
+ MODE_NAME_HUNTER_GAMES = list(
+ JOB_SURVIVOR
+ ),
+ MODE_NAME_HIVE_WARS = list(),
+ MODE_NAME_INFECTION = list(
+ JOB_SURVIVOR
+ )
+ )
+
+ weight_act = list(
+ MODE_NAME_EXTENDED = FALSE,
+ MODE_NAME_DISTRESS_SIGNAL = FALSE,
+ MODE_NAME_FACTION_CLASH = FALSE,
+ MODE_NAME_CRASH = FALSE,
+ MODE_NAME_WISKEY_OUTPOST = FALSE,
+ MODE_NAME_HUNTER_GAMES = FALSE,
+ MODE_NAME_HIVE_WARS = FALSE,
+ MODE_NAME_INFECTION = FALSE
+ )
diff --git a/code/datums/factions/contractor.dm b/code/datums/factions/contractor.dm
index 5e0f125b06b0..7b5e8106d0d9 100644
--- a/code/datums/factions/contractor.dm
+++ b/code/datums/factions/contractor.dm
@@ -1,3 +1,6 @@
/datum/faction/contractor
- name = "Vanguard's Arrow Incorporated"
- faction_tag = FACTION_CONTRACTOR
+ name = NAME_FACTION_CONTRACTOR
+ desc = "There is no information"
+
+ faction_name = FACTION_CONTRACTOR
+ faction_iff_tag_type = /obj/item/faction_tag/contractor
diff --git a/code/datums/factions/dutch's_dozen.dm b/code/datums/factions/dutch's_dozen.dm
new file mode 100644
index 000000000000..455224371e13
--- /dev/null
+++ b/code/datums/factions/dutch's_dozen.dm
@@ -0,0 +1,6 @@
+/datum/faction/dutchs_dozen
+ name = NAME_FACTION_DUTCH
+ desc = "Like the Colonial Liberation Front and the better-equipped Freelancer Mercenaries before them, the Dozens start with non-standard UA weaponry, employing equipment of the United Americas such as the M16A2 assault rifle and the minigun. While the weapons on their own are outdated and cannot take most attachments, their automatic weaponry is equally deadly, meant for hunting their targets. \
+ The Dozens will also expect a guerrilla style of gameplay, as unlike most modern forces of this era, the Dozens use special techniques related to survival and can handle themselves on the ground without support. Whether it would take place in a jungle landscape or in urban grounds. One advantage of the Dutch's Mercenary Team is their speed and aggressiveness, which are primarily used to eliminate their target(s), due to the target's equal amount of speed, aggressiveness and robustness."
+
+ faction_name = FACTION_DUTCH
diff --git a/code/datums/factions/faction.dm b/code/datums/factions/faction.dm
index a6201da70467..c86dbb8d1eec 100644
--- a/code/datums/factions/faction.dm
+++ b/code/datums/factions/faction.dm
@@ -1,13 +1,1042 @@
/datum/faction
- var/name = "Neutral Faction"
- var/faction_tag = FACTION_NEUTRAL
+ var/name = NAME_FACTION_NEUTRAL
+ var/desc = "Neutral Faction"
+
+ var/faction_name = FACTION_NEUTRAL
+ var/faction_tag = SIDE_FACTION_NEUTRAL
+
+ var/organ_faction_iff_tag_type
+ var/faction_iff_tag_type
+
+ var/relations_pregen[] = RELATIONS_NEUTRAL
+ var/datum/faction_relations/relations_datum
+
var/hud_type = FACTION_HUD
+ var/orders = "Остаться в живых"
+ var/color = "#22888a"
+ var/ui_color = "#22888a"
+ var/prefix = ""
+ var/list/totalMobs = list()
+ var/list/totalDeadMobs = list()
+ var/list/faction_leaders = list()
+ var/list/late_join_landmarks = list()
+ var/mob/living/carbon/faction_leader
+ var/datum/tacmap/faction_datum/tcmp_faction_datum
+ var/datum/objectives_datum/objectives_controller
+ var/list/objectives = list()
+ var/faction_victory_points = 0
+ var/homes_sector_occupation = TRUE
+ var/objectives_active = FALSE
+ var/need_round_end_check = FALSE
+
+////////////////
+//BALANCE DEFS//
+////////////////
+ var/list/role_mappings = list()
+ var/list/roles_list = list()
+ var/list/coefficient_per_role = list()
+ var/list/affected_spawns // TODO: СДЕЛАТЬ СКЕЙЛИНГ СПАВНА ВЕЩЕЙ В ВЕНДОРАХ И УСЛОЖНЕНИЯ ЗАДАЧ ОТ ОНЛАЙНА НАПРИМЕР КАК ОЧКИ ДЕФКОНА
+ var/weight_act = list(
+ MODE_NAME_EXTENDED = TRUE,
+ MODE_NAME_DISTRESS_SIGNAL = TRUE,
+ MODE_NAME_FACTION_CLASH = TRUE,
+ MODE_NAME_CRASH = TRUE,
+ MODE_NAME_WISKEY_OUTPOST = TRUE,
+ MODE_NAME_HUNTER_GAMES = TRUE,
+ MODE_NAME_HIVE_WARS = TRUE,
+ MODE_NAME_INFECTION = TRUE
+ )
+
+ var/spawning_enabled = TRUE
+ var/latejoin_enabled = TRUE
+ var/force_spawning = FALSE
+
+/////////////
+//XENO DEFS//
+/////////////
+ var/mob/living/carbon/xenomorph/queen/living_xeno_queen
+ var/egg_planting_range = 15
+ var/slashing_allowed = XENO_SLASH_ALLOWED //This initial var allows the queen to turn on or off slashing. Slashing off means harm intent does much less damage.
+ var/construction_allowed = NORMAL_XENO //Who can place construction nodes for special structures
+ var/destruction_allowed = XENO_LEADER //Who can destroy special structures
+ var/unnesting_allowed = TRUE
+ var/queen_leader_limit = 2
+ var/lesser_drone_limit = 0 //How many lesser drones the hive can support
+ var/lesser_drone_minimum = 2 //Slots available for lesser drones will never go below this number
+ var/playable_lesser_drones_max_divisor = 3 //This number divides the total xenos counted for slots to give the max number of lesser drones
+ var/list/open_xeno_leader_positions = list(1, 2) //Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied
+ var/list/xeno_leader_list[2] //Ordered list (i.e. index n holds the nth xeno leader)
+ var/partial_larva = 0
+ var/stored_larva = 0
+ var/list/free_slots = list(
+ /datum/caste_datum/burrower = 1,
+ /datum/caste_datum/hivelord = 1,
+ /datum/caste_datum/carrier = 1
+ ) //Assoc list of free slots available to specific castes
+ var/list/used_slots = list() //Assoc list of slots currently used by specific castes (for calculating free_slot usage)
+ var/list/tier_2_xenos = list() //list of living tier2 xenos
+ var/list/tier_3_xenos = list() //list of living tier3 xenos
+ var/xeno_queen_timer
+ var/isSlotOpen = TRUE //Set true for starting alerts only after the hive has reached its full potential
+ var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles.
+ var/obj/effect/alien/resin/special/pylon/core/faction_location = null //Set to ref every time a core is built, for defining the hive location.
+ var/crystal_stored = 0 //How much stockpiled material is stored for the hive to use.
+ var/datum/mutator_set/hive_mutations/mutators = new
+ var/tier_slot_multiplier = 1.0
+ var/larva_gestation_multiplier = 1.0
+ var/bonus_larva_spawn_chance = 1.0
+ var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of pooled
+ var/hijack_burrowed_left = 0 //how many burrowed is going to spawn during larva surge
+ var/ignore_slots = FALSE
+ var/dynamic_evolution = TRUE
+ var/evolution_rate = 3 //Only has use if dynamic_evolution is false
+ var/evolution_bonus = 0
+ var/allow_no_queen_actions = FALSE
+ var/evolution_without_ovipositor = TRUE //Temporary for the roundstart.
+ var/allow_queen_evolve = TRUE //Set to true if you want to prevent evolutions into Queens
+ var/hardcore = FALSE //Set to true if you want to prevent bursts and spawns of new xenos. Will also prevent healing if the queen no longer exists
+ var/list/hive_inherant_traits
+ var/mob/living/carbon/leading_cult_sl //Cultist Info
+ var/list/faction_structures_limit = list(
+ XENO_STRUCTURE_CORE = 1,
+ XENO_STRUCTURE_PYLON = 2,
+ XENO_STRUCTURE_CLUSTER = 8,
+ XENO_STRUCTURE_POOL = 1,
+ XENO_STRUCTURE_EGGMORPH = 6,
+ XENO_STRUCTURE_EVOPOD = 2,
+ XENO_STRUCTURE_RECOVERY = 6,
+ ) //List of how many maximum of each special structure you can have
+ var/global/list/faction_structure_types = list(
+ XENO_STRUCTURE_CORE = /datum/construction_template/xenomorph/core,
+ XENO_STRUCTURE_PYLON = /datum/construction_template/xenomorph/pylon,
+ XENO_STRUCTURE_CLUSTER = /datum/construction_template/xenomorph/cluster,
+ XENO_STRUCTURE_EGGMORPH = /datum/construction_template/xenomorph/eggmorph,
+ XENO_STRUCTURE_RECOVERY = /datum/construction_template/xenomorph/recovery
+ )
+ var/list/list/faction_structures = list() //Stringref list of structures that have been built
+ var/list/list/faction_constructions = list() //Stringref list of structures that are being built
+ var/datum/mark_menu_ui/mark_ui
+ var/datum/hive_status_ui/faction_ui
+ var/datum/faction_task_ui/task_interface
+ var/datum/objective_memory_storage/objective_memory
+ var/datum/objective_memory_interface/objective_interface
+ var/datum/research_objective_memory_interface/research_objective_interface
+ var/list/tunnels = list()
+ var/list/resin_marks = list()
+ var/list/banished_ckeys = list()
+ var/hivecore_cooldown = FALSE
+ var/hugger_timelock = 15 MINUTES //When can huggers join the round
+ var/playable_hugger_limit = 0 //How many huggers can the hive support
+
+//////////////
+/datum/faction/New()
+ relations_datum = new(src)
+ tcmp_faction_datum = new(src)
+ objectives_controller = GLOB.objective_controller[faction_name]
+ task_interface = new(src)
+ objective_memory = new()
+ objective_interface = new()
+ research_objective_interface = new()
+
+/datum/faction/can_vv_modify()
+ return FALSE
/datum/faction/proc/modify_hud_holder(image/holder, mob/living/carbon/human/H)
return
+/datum/faction/proc/add_mob(mob/living/carbon/carbon)
+ if(!carbon || !istype(carbon))
+ return
+
+ if(carbon.faction && carbon.faction != src)
+ carbon.faction.remove_mob(carbon, TRUE)
+
+ if(carbon in totalMobs)
+ return
+
+ carbon.faction = src
+
+ if(carbon.hud_list)
+ carbon.hud_update()
+
+ if(!carbon.statistic_exempt)
+ totalMobs += carbon
+
+/datum/faction/proc/remove_mob(mob/living/carbon/carbon, hard = FALSE)
+ if(!carbon || !istype(carbon))
+ return
+
+ if(!(carbon in totalMobs))
+ return
+
+ if(hard)
+ carbon.faction = null
+ else
+ totalDeadMobs += carbon
+
+ totalMobs -= carbon
+
+/datum/faction/proc/can_delay_round_end(mob/living/carbon/carbon)
+ return TRUE
+
+/datum/faction/proc/update_hugger_limit()
+ playable_hugger_limit = 2 + Ceiling(totalMobs.len / 4)
+
+/datum/faction/proc/can_spawn_as_hugger(mob/dead/observer/user)
+ if(world.time < hugger_timelock)
+ to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet..."))
+ return FALSE
+ if(world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY)
+ var/time_left = round((user.timeofdeath + JOIN_AS_FACEHUGGER_DELAY - world.time) / 10)
+ to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a facehugger until 3 minutes have passed ([time_left] seconds remaining)."))
+ return FALSE
+ if(totalMobs.len <= 0)
+ //This is to prevent people from joining as Forsaken Huggers on the pred ship
+ to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!"))
+ return FALSE
+
+ update_hugger_limit()
+
+ var/current_hugger_count = 0
+ for(var/mob/mob as anything in totalMobs)
+ if(isfacehugger(mob))
+ current_hugger_count++
+ if(playable_hugger_limit <= current_hugger_count)
+ to_chat(user, SPAN_WARNING("\The [name] cannot support more facehuggers! Limit: [current_hugger_count]/[playable_hugger_limit]"))
+ return FALSE
+
+ if(alert(user, "Are you sure you want to become a facehugger?", usr.client.auto_lang(LANGUAGE_CONFIRM), usr.client.auto_lang(LANGUAGE_YES), usr.client.auto_lang(LANGUAGE_NO)) != usr.client.auto_lang(LANGUAGE_YES))
+ return FALSE
+ return TRUE
+
+/datum/faction/proc/spawn_as_hugger(mob/dead/observer/user, atom/A)
+ var/mob/living/carbon/xenomorph/facehugger/facehugger = new /mob/living/carbon/xenomorph/facehugger(A.loc, null, src)
+ user.mind.transfer_to(facehugger, TRUE)
+ facehugger.visible_message(SPAN_XENODANGER("A facehugger suddenly emerges out of \the [A]!"), SPAN_XENODANGER("You emerge out of \the [A] and awaken from your slumber. For the Hive!"))
+ playsound(facehugger, 'sound/effects/xeno_newlarva.ogg', 25, TRUE)
+ facehugger.generate_name()
+
+//Ally procs
+/atom/movable/proc/ally(datum/faction/ally_faction)
+ if(!ally_faction)
+ return FALSE
+
+ var/list/factions = list()
+ factions += ally_faction
+ for(var/datum/faction/i in ally_faction.relations_datum.allies)
+ factions += i
+ if(isnull(factions) || !faction)
+ return FALSE
+
+ return faction in factions
+
+/mob/ally(datum/faction/ally_faction)
+ if(!ally_faction)
+ return FALSE
+
+ var/list/factions = list()
+ factions += ally_faction
+ for(var/datum/faction/i in ally_faction.relations_datum.allies)
+ factions += i
+
+ if(isnull(factions) || !faction)
+ return FALSE
+
+ return faction in factions
+
+/mob/living/carbon/ally(datum/faction/ally_faction)
+ if(!ally_faction)
+ return FALSE
+
+ if(client in ally_faction.banished_ckeys)
+ return FALSE
+
+ if((organ_faction_tag || (faction.faction_tag in SIDE_ORGANICAL_DOM)) && (ally_faction.faction_tag in SIDE_ORGANICAL_DOM))
+ if(organ_faction_tag)
+ return ally_faction.organ_faction_tag_is_ally(organ_faction_tag)
+ else if(faction == ally_faction)
+ return TRUE
+ else if(faction_tag)
+ return ally_faction.faction_tag_is_ally(faction_tag)
+
+ return FALSE
+
+/datum/faction/proc/organ_faction_tag_is_ally(obj/item/faction_tag/organ/FT)
+ if(FT.faction == src)
+ return TRUE
+ for(var/datum/faction/faction in FT.factions + FT.faction)
+ if(relations_datum.allies[faction.faction_name])
+ return TRUE
+
+ return FALSE
+
+/datum/faction/proc/faction_tag_is_ally(obj/item/faction_tag/FT)
+ if(FT.faction == src)
+ return TRUE
+ for(var/datum/faction/faction in FT.factions + FT.faction)
+ if(relations_datum.allies[faction.faction_name])
+ return TRUE
+ else if(faction.faction_tag == faction_tag)
+ return TRUE
+
+ return FALSE
+
+/datum/faction/proc/faction_is_ally(datum/faction/faction_to_check)
+ if(faction_to_check.faction_tag == faction_tag)
+ return TRUE
+
+ if(relations_datum.allies[faction_to_check.faction_name])
+ return TRUE
+
+ return FALSE
+
/datum/faction/proc/get_antag_guns_snowflake_equipment()
return list()
/datum/faction/proc/get_antag_guns_sorted_equipment()
return list()
+
+
+
+
+//////////////
+//XENO PROCS//
+//////////////
+/datum/faction/proc/set_living_xeno_queen(mob/living/carbon/xenomorph/queen/XQ)
+ if(XQ == null)
+ mutators.reset_mutators()
+ SStracking.delete_leader("hive_[faction_name]")
+ SStracking.stop_tracking("hive_[faction_name]", living_xeno_queen)
+ SShive_status.wait = 10 SECONDS
+ else
+ SStracking.set_leader("hive_[faction_name]", XQ)
+ SShive_status.wait = 2 SECONDS
+
+ living_xeno_queen = XQ
+ mutators.xeno = XQ
+
+ recalculate_hive()
+
+/datum/faction/proc/recalculate_hive()
+ if(!living_xeno_queen)
+ queen_leader_limit = 0 //No leaders for a Hive without a Queen!
+ else
+ queen_leader_limit = 4 + mutators.leader_count_boost
+
+ if(xeno_leader_list.len > queen_leader_limit)
+ var/diff = 0
+ for(var/i in queen_leader_limit + 1 to xeno_leader_list.len)
+ if(!open_xeno_leader_positions.Remove(i))
+ remove_hive_leader(xeno_leader_list[i])
+ diff++
+ xeno_leader_list.len -= diff // Changing the size of xeno_leader_list needs to go at the end or else it won't iterate through the list properly
+ else if(xeno_leader_list.len < queen_leader_limit)
+ for (var/i in xeno_leader_list.len + 1 to queen_leader_limit)
+ open_xeno_leader_positions += i
+ xeno_leader_list.len++
+
+
+ tier_slot_multiplier = mutators.tier_slot_multiplier
+ larva_gestation_multiplier = mutators.larva_gestation_multiplier
+ bonus_larva_spawn_chance = mutators.bonus_larva_spawn_chance
+
+ faction_ui.update_all_data()
+
+/datum/faction/proc/add_hive_leader(mob/living/carbon/xenomorph/X)
+ if(!X)
+ return FALSE //How did this even happen?
+ if(!open_xeno_leader_positions.len)
+ return FALSE //Too many leaders already (no available xeno leader positions)
+ if(X.hive_pos != NORMAL_XENO)
+ return FALSE //Already on the list
+ var/leader_num = open_xeno_leader_positions[1]
+ xeno_leader_list[leader_num] = X
+ X.hive_pos = XENO_LEADER_HIVE_POS(leader_num)
+ X.handle_xeno_leader_pheromones()
+ X.hud_update() // To add leader star
+ open_xeno_leader_positions -= leader_num
+
+ faction_ui.update_xeno_keys()
+ return TRUE
+
+/datum/faction/proc/remove_hive_leader(mob/living/carbon/xenomorph/X, light_mode = FALSE)
+ if(!istype(X) || !IS_XENO_LEADER(X))
+ return FALSE
+
+ var/leader_num = GET_XENO_LEADER_NUM(X)
+
+ xeno_leader_list[leader_num] = null
+
+ if(!light_mode) // Don't run side effects during deletions. Better yet, replace all this by signals someday
+ X.hive_pos = NORMAL_XENO
+ X.handle_xeno_leader_pheromones()
+ X.hud_update() // To remove leader star
+
+ // Need to maintain ascending order of open_xeno_leader_positions
+ for (var/i in 1 to queen_leader_limit)
+ if(i > open_xeno_leader_positions.len || open_xeno_leader_positions[i] > leader_num)
+ open_xeno_leader_positions.Insert(i, leader_num)
+ break
+
+ if(!light_mode)
+ faction_ui.update_xeno_keys()
+
+ return TRUE
+
+/datum/faction/proc/replace_hive_leader(mob/living/carbon/xenomorph/original, mob/living/carbon/xenomorph/replacement)
+ if(!replacement || replacement.hive_pos != NORMAL_XENO)
+ return remove_hive_leader(original)
+
+ var/leader_num = GET_XENO_LEADER_NUM(original)
+
+ xeno_leader_list[leader_num] = replacement
+
+ original.hive_pos = NORMAL_XENO
+ original.handle_xeno_leader_pheromones()
+ original.hud_update() // To remove leader star
+
+ replacement.hive_pos = XENO_LEADER_HIVE_POS(leader_num)
+ replacement.handle_xeno_leader_pheromones()
+ replacement.hud_update() // To add leader star
+
+ faction_ui.update_xeno_keys()
+
+/datum/faction/proc/handle_xeno_leader_pheromones()
+ for(var/mob/living/carbon/xenomorph/L in xeno_leader_list)
+ L.handle_xeno_leader_pheromones()
+
+/*
+ * Helper procs for the Hive Status UI
+ * These are all called by the hive status UI manager to update its data
+ */
+
+// Returns a list of how many of each caste of xeno there are, sorted by tier
+/datum/faction/proc/get_xeno_counts()
+ // Every caste is manually defined here so you get
+ var/list/xeno_counts = list(
+ // Yes, Queen is technically considered to be tier 0
+ list(XENO_CASTE_LARVA = 0, "Queen" = 0),
+ list(XENO_CASTE_DRONE = 0, XENO_CASTE_RUNNER = 0, XENO_CASTE_SENTINEL = 0, XENO_CASTE_DEFENDER = 0),
+ list(XENO_CASTE_HIVELORD = 0, XENO_CASTE_BURROWER = 0, XENO_CASTE_CARRIER = 0, XENO_CASTE_LURKER = 0, XENO_CASTE_SPITTER = 0, XENO_CASTE_WARRIOR = 0),
+ list(XENO_CASTE_BOILER = 0, XENO_CASTE_CRUSHER = 0, XENO_CASTE_PRAETORIAN = 0, XENO_CASTE_RAVAGER = 0)
+ )
+
+ for(var/mob/living/carbon/xenomorph/X in totalMobs)
+ //don't show xenos in the thunderdome when admins test stuff.
+ if(X.statistic_exempt)
+ continue
+
+ if(X.caste)
+ xeno_counts[X.caste.tier+1][X.caste.caste_type]++
+
+ return xeno_counts
+
+// Returns a sorted list of some basic info (stuff that's needed for sorting) about all the xenos in the hive
+// The idea is that we sort this list, and use it as a "key" for all the other information (especially the nicknumber)
+// in the hive status UI. That way we can minimize the amount of sorts performed by only calling this when xenos are created/disposed
+/datum/faction/proc/get_xeno_keys()
+ var/list/xenos[totalMobs.len]
+
+ var/index = 1
+ var/useless_slots = 0
+ for(var/mob/living/carbon/xenomorph/X in totalMobs)
+ if(X.statistic_exempt)
+ useless_slots++
+ continue
+
+ // Insert without doing list merging
+ xenos[index++] = list(
+ "nicknumber" = X.nicknumber,
+ "tier" = X.tier, // This one is only important for sorting
+ "is_leader" = (IS_XENO_LEADER(X)),
+ "is_queen" = istype(X.caste, /datum/caste_datum/queen),
+ "caste_type" = X.caste_type
+ )
+
+ // Clear nulls from the xenos list
+ xenos.len -= useless_slots
+
+ // Make it all nice and fancy by sorting the list before returning it
+ var/list/sorted_keys = sort_xeno_keys(xenos)
+ if(length(sorted_keys))
+ return sorted_keys
+ return xenos
+
+// This sorts the xeno info list by multiple criteria. Prioritized in order:
+// 1. Queen
+// 2. Leaders
+// 3. Tier
+// It uses a slightly modified insertion sort to accomplish this
+/datum/faction/proc/sort_xeno_keys(list/xenos)
+ if(!length(xenos))
+ return
+
+ var/list/sorted_list = xenos.Copy()
+
+ if(!length(sorted_list))
+ return
+
+ for(var/index in 2 to length(sorted_list))
+ var/j = index
+
+ while(j > 1)
+ var/current = sorted_list[j]
+ var/prev = sorted_list[j-1]
+
+ // Queen comes first, always
+ if(current["is_queen"])
+ sorted_list.Swap(j-1, j)
+ j--
+ continue
+
+ // don't muck up queen's slot
+ if(prev["is_queen"])
+ j--
+ continue
+
+ // Leaders before normal xenos
+ if(!prev["is_leader"] && current["is_leader"])
+ sorted_list.Swap(j-1, j)
+ j--
+ continue
+
+ // Make sure we're only comparing leaders to leaders and non-leaders to non-leaders when sorting
+ // This means we get leaders sorted first, then non-leaders sorted
+ // Sort by tier first, higher tiers over lower tiers, and then by name alphabetically
+
+ // Could not think of an elegant way to write this
+ if(!(current["is_leader"]^prev["is_leader"])\
+ && (prev["tier"] < current["tier"]\
+ || prev["tier"] == current["tier"] && prev["caste_type"] > current["caste_type"]\
+ ))
+ sorted_list.Swap(j-1, j)
+
+ j--
+
+ return sorted_list
+
+// Returns a list with some more info about all xenos in the hive
+/datum/faction/proc/get_xeno_info()
+ var/list/xenomorphs = list()
+
+ for(var/mob/living/carbon/xenomorph/xenomorph in totalMobs)
+ if(xenomorph.statistic_exempt)
+ continue
+
+ var/xeno_name = xenomorph.name
+ // goddamn fucking larvas with their weird ass maturing system
+ // its name updates with its icon, unlike other castes which only update the mature/elder, etc. prefix on evolve
+ if(istype(xenomorph, /mob/living/carbon/xenomorph/larva))
+ xeno_name = "Larva ([xenomorph.nicknumber])"
+ xenomorphs["[xenomorph.nicknumber]"] = list(
+ "name" = xeno_name,
+ "strain" = xenomorph.mutation_type,
+ "ref" = "\ref[xenomorph]"
+ )
+
+ return xenomorphs
+
+/datum/faction/proc/set_faction_location(obj/effect/alien/resin/special/pylon/core/core)
+ if(!core || core == faction_location)
+ return
+ var/area/A = get_area(core)
+ xeno_message(SPAN_XENOANNOUNCE("The Queen has set the hive location as \the [A]."), 3, src)
+ faction_location = core
+ faction_ui.update_faction_location()
+
+// Returns a list of xeno healths and locations
+/datum/faction/proc/get_xeno_vitals()
+ var/list/xenomorphs = list()
+
+ for(var/mob/living/carbon/xenomorph/xenomorph in totalMobs)
+ if(xenomorph.statistic_exempt)
+ continue
+
+ if(!(xenomorph in GLOB.living_xeno_list))
+ continue
+
+ var/area/area = get_area(xenomorph)
+ var/area_name = "Unknown"
+ if(area)
+ area_name = area.name
+
+ xenomorphs["[xenomorph.nicknumber]"] = list(
+ "health" = round((xenomorph.health / xenomorph.maxHealth) * 100, 1),
+ "area" = area_name,
+ "is_ssd" = (!xenomorph.client)
+ )
+
+ return xenomorphs
+
+#define TIER_3 "3"
+#define TIER_2 "2"
+#define OPEN_SLOTS "open_slots"
+#define GUARANTEED_SLOTS "guaranteed_slots"
+
+// Returns an assoc list of open slots and guaranteed slots left
+/datum/faction/proc/get_tier_slots()
+ var/list/slots = list(
+ TIER_3 = list(
+ OPEN_SLOTS = 0,
+ GUARANTEED_SLOTS = list(),
+ ),
+ TIER_2 = list(
+ OPEN_SLOTS = 0,
+ GUARANTEED_SLOTS = list(),
+ ),
+ )
+
+ var/used_tier_2_slots = length(tier_2_xenos)
+ var/used_tier_3_slots = length(tier_3_xenos)
+
+ for(var/caste_path in free_slots)
+ var/slots_free = free_slots[caste_path]
+ var/slots_used = used_slots[caste_path]
+ var/datum/caste_datum/current_caste = caste_path
+ if(slots_used)
+ // Don't count any free slots in use
+ switch(initial(current_caste.tier))
+ if(2)
+ used_tier_2_slots -= min(slots_used, slots_free)
+ if(3)
+ used_tier_3_slots -= min(slots_used, slots_free)
+ if(slots_free <= slots_used)
+ continue
+ // Display any free slots available
+ switch(initial(current_caste.tier))
+ if(2)
+ slots[TIER_2][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used
+ if(3)
+ slots[TIER_3][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used
+
+ var/burrowed_factor = min(stored_larva, sqrt(4*stored_larva))
+ var/effective_total = round(burrowed_factor)
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalMobs)
+ if(xeno.counts_for_slots)
+ effective_total++
+
+ // Tier 3 slots are always 20% of the total xenos in the hive
+ slots[TIER_3][OPEN_SLOTS] = max(0, Ceiling(0.20*effective_total/tier_slot_multiplier) - used_tier_3_slots)
+ // Tier 2 slots are between 30% and 50% of the hive, depending
+ // on how many T3s there are.
+ slots[TIER_2][OPEN_SLOTS] = max(0, Ceiling(0.5*effective_total/tier_slot_multiplier) - used_tier_2_slots - used_tier_3_slots)
+
+ return slots
+
+#undef TIER_3
+#undef TIER_2
+#undef OPEN_SLOTS
+#undef GUARANTEED_SLOTS
+
+// returns if that location can be used to plant eggs
+/datum/faction/proc/in_egg_plant_range(turf/T)
+ if(!istype(living_xeno_queen))
+ return TRUE // xenos already dicked without queen. Let them plant whereever
+
+ if(!living_xeno_queen.ovipositor)
+ return FALSE // ovid queen only
+
+ return get_dist(living_xeno_queen, T) <= egg_planting_range
+
+/datum/faction/proc/can_build_structure(structure_name)
+ if(!structure_name || !faction_structures_limit[structure_name])
+ return FALSE
+ var/total_count = 0
+ if(faction_structures[structure_name])
+ total_count += faction_structures[structure_name].len
+ if(faction_constructions[structure_name])
+ total_count += faction_constructions[structure_name].len
+ if(total_count >= faction_structures_limit[structure_name])
+ return FALSE
+ return TRUE
+
+/datum/faction/proc/has_structure(structure_name)
+ if(!structure_name)
+ return FALSE
+ if(faction_structures[structure_name] && faction_structures[structure_name].len)
+ return TRUE
+ return FALSE
+
+/datum/faction/proc/add_construction(obj/effect/alien/resin/construction/resin)
+ if(!resin || !resin.template)
+ return FALSE
+ var/name_ref = initial(resin.template.name)
+ if(!faction_constructions[name_ref])
+ faction_constructions[name_ref] = list()
+ if(faction_constructions[name_ref].len >= faction_structures_limit[name_ref])
+ return FALSE
+ faction_constructions[name_ref] += src
+ return TRUE
+
+/datum/faction/proc/remove_construction(obj/effect/alien/resin/construction/resin)
+ if(!resin || !resin.template)
+ return FALSE
+ var/name_ref = initial(resin.template.name)
+ faction_constructions[name_ref] -= src
+ return TRUE
+
+/datum/faction/proc/add_special_structure(obj/effect/alien/resin/special/resin)
+ if(!resin)
+ return FALSE
+ var/name_ref = initial(resin.name)
+ if(!faction_structures[name_ref])
+ faction_structures[name_ref] = list()
+ if(faction_structures[name_ref].len >= faction_structures_limit[name_ref])
+ return FALSE
+ faction_structures[name_ref] += resin
+ return TRUE
+
+/datum/faction/proc/remove_special_structure(obj/effect/alien/resin/special/resin)
+ if(!resin)
+ return FALSE
+ var/name_ref = initial(resin.name)
+ faction_structures[name_ref] -= resin
+ return TRUE
+
+/datum/faction/proc/has_special_structure(name_ref)
+ if(!name_ref || !faction_structures[name_ref] || !faction_structures[name_ref].len)
+ return 0
+ return faction_structures[name_ref].len
+
+/datum/faction/proc/abandon_on_hijack()
+ var/area/hijacked_dropship = get_area(living_xeno_queen)
+ var/xenos_count = 0
+ var/shipside_humans_weighted_count = 0
+ for(var/name_ref in faction_structures)
+ for(var/obj/effect/alien/resin/special/resin in faction_structures[name_ref])
+ if(get_area(resin) == hijacked_dropship)
+ continue
+ resin.hijack_delete = TRUE
+ faction_structures[name_ref] -= resin
+ qdel(resin)
+ for(var/mob/living/carbon/xenomorph/xeno as anything in totalMobs)
+ if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z))
+ if(isfacehugger(xeno) || islesserdrone(xeno))
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind."))
+ if(xeno.stomach_contents.len)
+ xeno.devour_timer = 0
+ xeno.handle_stomach_contents()
+ qdel(xeno)
+ continue
+ if(xeno.hunter_data.hunted && !isqueen(xeno))
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, seperating you from her hive! You must defend yourself from the headhunter before you can enter hibernation..."))
+ xeno.set_hive_and_update(GLOB.faction_datum[FACTION_XENOMORPH_FORSAKEN])
+ else
+ to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind."))
+ if(xeno.stomach_contents.len)
+ xeno.devour_timer = 0
+ xeno.handle_stomach_contents()
+ qdel(xeno)
+ stored_larva++
+ continue
+ if(xeno.tier >= 1)
+ xenos_count++
+ for(var/i in GLOB.alive_mob_list)
+ var/mob/living/potential_host = i
+ if(!(potential_host.status_flags & XENO_HOST))
+ continue
+ if(!is_ground_level(potential_host.z) || get_area(potential_host) == hijacked_dropship)
+ continue
+ var/obj/item/alien_embryo/A = locate() in potential_host
+ if(A && A.faction != src)
+ continue
+ for(var/obj/item/alien_embryo/embryo in potential_host)
+ embryo.faction = GLOB.faction_datum[FACTION_XENOMORPH_FORSAKEN]
+ potential_host.update_med_icon()
+ for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list)
+ if(!(isspecieshuman(current_human) || isspeciessynth(current_human)))
+ continue
+ var/datum/job/job = SSticker.role_authority.roles_for_mode[current_human.job]
+ if(!job)
+ continue
+ var/turf/turf = get_turf(current_human)
+ if(is_mainship_level(turf?.z))
+ shipside_humans_weighted_count += current_human.faction.get_role_coeff(current_human.job)
+ hijack_burrowed_surge = TRUE
+ hijack_burrowed_left = max(n_ceil(shipside_humans_weighted_count * 0.5) - xenos_count, 5)
+ hivecore_cooldown = FALSE
+ xeno_message(SPAN_XENOBOLDNOTICE("The weeds have recovered! A new hive core can be built!"), 3, src)
+
+/datum/faction/proc/update_lesser_drone_limit()
+ var/countable_xeno_iterator = 0
+ for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalMobs)
+ if(cycled_xeno.counts_for_slots)
+ countable_xeno_iterator++
+
+ lesser_drone_limit = max(round(countable_xeno_iterator / playable_lesser_drones_max_divisor), lesser_drone_minimum)
+
+/datum/faction/proc/can_spawn_as_lesser_drone(mob/dead/observer/user, obj/effect/alien/resin/special/pylon/spawning_pylon)
+ if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned
+ to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph."))
+ return FALSE
+
+ if(world.time - user.timeofdeath < JOIN_AS_LESSER_DRONE_DELAY)
+ var/time_left = round((user.timeofdeath + JOIN_AS_LESSER_DRONE_DELAY - world.time) / 10)
+ to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a lesser drone until 30 seconds have passed ([time_left] seconds remaining)."))
+ return FALSE
+
+ if(totalMobs.len <= 0)
+ to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!"))
+ return FALSE
+
+ if(!living_xeno_queen)
+ to_chat(user, SPAN_WARNING("The selected hive does not have a Queen!"))
+ return FALSE
+
+ if(spawning_pylon.lesser_drone_spawns < 1)
+ to_chat(user, SPAN_WARNING("The selected core or pylon does not have enough power for a lesser drone!"))
+ return FALSE
+
+ update_lesser_drone_limit()
+
+ var/current_lesser_drone_count = 0
+ for(var/mob/mob as anything in totalMobs)
+ if(islesserdrone(mob))
+ current_lesser_drone_count++
+
+ if(lesser_drone_limit <= current_lesser_drone_count)
+ to_chat(user, SPAN_WARNING("[name] cannot support more lesser drones! Limit: [current_lesser_drone_count]/[lesser_drone_limit]"))
+ return FALSE
+
+ if(!user.client)
+ return FALSE
+
+ return TRUE
+
+/datum/faction/proc/on_leader_death() //break alliances on queen's death
+ return TRUE
+
+/datum/faction/proc/free_respawn(client/xeno_client)
+ if(!faction_location || !faction_location.spawn_burrowed_larva(xeno_client.mob))
+ stored_larva--
+ else
+ stored_larva++
+ faction_ui.update_burrowed_larva()
+
+/datum/faction/proc/respawn_on_turf(client/xeno_client, turf/spawning_turf)
+ var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_faction_larva(spawning_turf, src)
+ if(isnull(new_xeno))
+ return FALSE
+
+ if(!SSticker.mode.transfer_xenomorph(xeno_client.mob, new_xeno))
+ qdel(new_xeno)
+ return FALSE
+
+ new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly emerges from a dead husk!"),
+ SPAN_XENOANNOUNCE("The hive has no core! You manage to emerge from your old husk as a larva!"))
+ msg_admin_niche("[key_name(new_xeno)] respawned at \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]")
+ playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1)
+ if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN)
+ window_flash(new_xeno.client)
+
+ faction_ui.update_burrowed_larva()
+
+/datum/faction/proc/do_buried_larva_spawn(mob/spawn_candidate)
+ var/spawning_area
+ if(faction_location)
+ spawning_area = faction_location
+ else if(living_xeno_queen)
+ spawning_area = living_xeno_queen
+ else for(var/mob/living/carbon/mob as anything in totalMobs)
+ if(islarva(spawn_candidate) || isxeno_builder(mob)) //next to xenos that should be in a safe spot
+ spawning_area = spawn_candidate
+ if(!spawning_area)
+ return FALSE
+
+ var/list/turf_list
+ for(var/turf/open/open_turf in orange(3, spawning_area))
+ LAZYADD(turf_list, open_turf)
+
+ var/turf/open/spawning_turf = pick(turf_list)
+
+ var/mob/living/carbon/xenomorph/larva/new_xenomorph = new(spawning_turf, null, src)
+ if(isnull(new_xenomorph))
+ return FALSE
+
+ if(!SSticker.mode.transfer_xenomorph(spawn_candidate, new_xenomorph))
+ qdel(new_xenomorph)
+ return FALSE
+ new_xenomorph.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of \the [spawning_turf]!"),
+ SPAN_XENODANGER("You burrow out of \the [spawning_turf] and awaken from your slumber. For the Hive!"))
+ msg_admin_niche("[key_name(new_xenomorph)] burrowed out from \a [spawning_turf]. (JMP)")
+ playsound(new_xenomorph, 'sound/effects/xeno_newlarva.ogg', 50, 1)
+ to_chat(new_xenomorph, SPAN_XENOANNOUNCE("You are a xenomorph larva awakened from slumber!"))
+ if(new_xenomorph.client)
+ if(new_xenomorph.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN)
+ window_flash(new_xenomorph.client)
+
+ stored_larva--
+ faction_ui.update_burrowed_larva()
+
+///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst
+/datum/faction/proc/increase_larva_after_burst()
+ var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst)
+ partial_larva += extra_per_burst
+ convert_partial_larva_to_full_larva()
+
+///Called after times when partial larva are added to process them to stored larva
+/datum/faction/proc/convert_partial_larva_to_full_larva()
+ for(var/i = 1 to partial_larva)
+ partial_larva--
+ stored_larva++
+
+/datum/faction/proc/make_leader(mob/living/carbon/mob)
+ if(!istype(mob))
+ return
+
+ if(mob.stat == DEAD)
+ return
+
+ if(faction_leader)
+ UnregisterSignal(faction_leader, COMSIG_PARENT_QDELETING)
+
+ faction_leader = mob
+ RegisterSignal(faction_leader, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdelete))
+
+/datum/faction/proc/handle_qdelete(mob/living/carbon/mob)
+ SIGNAL_HANDLER
+
+ if(mob == faction_leader)
+ faction_leader = null
+
+//Roles and join stuff
+/datum/faction/proc/get_role_coeff(role_name)
+ if(coefficient_per_role[role_name])
+ return coefficient_per_role[role_name]
+ return 1
+
+/datum/faction/proc/store_objective(datum/cm_objective/O)
+ if(objective_memory)
+ objective_memory.store_objective(O)
+
+//FACTION INFO PANEL
+/datum/faction/ui_state(mob/user)
+ return GLOB.not_incapacitated_state
+
+/datum/faction/ui_status(mob/user, datum/ui_state/state)
+ . = ..()
+ if(isobserver(user))
+ return UI_INTERACTIVE
+
+/datum/faction/ui_data(mob/user)
+ . = list()
+ .["faction_orders"] = orders
+
+/datum/faction/ui_static_data(mob/user)
+ . = list()
+ .["faction_color"] = ui_color
+ .["faction_name"] = name
+ .["faction_desc"] = desc
+ .["actions"] = get_faction_actions()
+
+/datum/faction/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "FactionStatus", "[name] Статус")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/faction/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("relations")
+ relations_datum.tgui_interact(usr)
+ if("tasks")
+ task_interface.tgui_interact(usr)
+ if("clues")
+ if(!skillcheck(usr, SKILL_INTEL, SKILL_INTEL_TRAINED))
+ to_chat(usr, SPAN_WARNING("You have no access to the [name] intel network."))
+ return
+ objective_interface.tgui_interact(usr)
+ if("researchs")
+ if(!skillcheck(usr, SKILL_RESEARCH, SKILL_RESEARCH_TRAINED))
+ to_chat(usr, SPAN_WARNING("You have no access to the [name] research network."))
+ return
+ research_objective_interface.tgui_interact(usr)
+ if("status")
+ get_faction_info(usr)
+
+/datum/faction/proc/get_faction_actions(mob/user)
+ . = list()
+ . += list(list("name" = "Faction Relations", "action" = "relations"))
+ . += list(list("name" = "Faction Tasks", "action" = "tasks"))
+ . += list(list("name" = "Faction Clues", "action" = "clues"))
+ . += list(list("name" = "Faction Researchs", "action" = "researchs"))
+ . += list(list("name" = "Faction Status", "action" = "status"))
+ return .
+
+/datum/faction/proc/get_faction_info(mob/user)
+ var/dat = GLOB.data_core.get_manifest(FALSE, src)
+ if(!dat)
+ return FALSE
+ show_browser(user, dat, "Список Экипажа [name]", "manifest", "size=450x750")
+ return TRUE
+
+/datum/faction/proc/get_join_status(mob/new_player/user, dat)
+ dat = "