diff --git a/_maps/map_files/Deltastation/DeltaStation2_skyrat.dmm b/_maps/map_files/Deltastation/DeltaStation2_skyrat.dmm index 173a2a8dfd8..d188e220f80 100644 --- a/_maps/map_files/Deltastation/DeltaStation2_skyrat.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2_skyrat.dmm @@ -8590,6 +8590,21 @@ }, /turf/open/floor/iron/grimy, /area/service/chapel/office) +"bwU" = ( +/obj/structure/destructible/cult/item_dispenser/archives, +/obj/item/book/codex_gigas, +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral, +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/turf/open/floor/iron/dark, +/area/service/library) "bwY" = ( /obj/structure/closet/wardrobe/botanist, /obj/effect/decal/cleanable/dirt, @@ -11505,24 +11520,6 @@ }, /turf/open/floor/iron/dark, /area/ai_monitored/security/armory) -"bPA" = ( -/obj/structure/closet/secure_closet/contraband/armory, -/obj/machinery/light/directional/east, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral, -/obj/effect/turf_decal/tile/neutral{ - dir = 4 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/obj/item/gun/ballistic/rifle/boltaction, -/obj/effect/spawner/random/maintenance/three, -/obj/effect/spawner/random/contraband/armory, -/turf/open/floor/iron/dark, -/area/ai_monitored/security/armory) "bPC" = ( /turf/closed/wall/r_wall, /area/ai_monitored/turret_protected/aisat_interior) @@ -50879,6 +50876,24 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/iron, /area/security/brig) +"iFP" = ( +/obj/structure/closet/secure_closet/contraband/armory, +/obj/machinery/light/directional/east, +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/effect/turf_decal/tile/neutral, +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/obj/item/gun/ballistic/rifle/boltaction, +/obj/effect/spawner/random/maintenance/three, +/obj/effect/spawner/random/contraband/armory, +/turf/open/floor/iron/dark, +/area/ai_monitored/security/armory) "iFY" = ( /obj/structure/chair/office{ dir = 1 @@ -55417,6 +55432,13 @@ /obj/machinery/bluespace_vendor/directional/north, /turf/open/floor/iron, /area/hallway/primary/central/aft) +"jZH" = ( +/obj/structure/destructible/cult/item_dispenser/archives, +/obj/effect/decal/cleanable/cobweb, +/obj/effect/decal/cleanable/dirt, +/obj/effect/decal/cleanable/blood/old, +/turf/open/floor/plating, +/area/service/library/abandoned) "jZK" = ( /obj/structure/window/reinforced{ dir = 8 @@ -66476,21 +66498,6 @@ }, /turf/open/floor/iron/dark, /area/command/heads_quarters/ce) -"nbt" = ( -/obj/structure/destructible/cult/item_dispenser/archives, -/obj/item/book/codex_gigas, -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/obj/effect/turf_decal/tile/neutral, -/obj/effect/turf_decal/tile/neutral{ - dir = 4 - }, -/obj/effect/turf_decal/tile/neutral{ - dir = 8 - }, -/turf/open/floor/iron/dark, -/area/service/library) "nbv" = ( /obj/structure/disposalpipe/segment{ dir = 4 @@ -81247,13 +81254,6 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/wood, /area/command/meeting_room/council) -"qXG" = ( -/obj/structure/destructible/cult/item_dispenser/archives, -/obj/effect/decal/cleanable/cobweb, -/obj/effect/decal/cleanable/dirt, -/obj/effect/decal/cleanable/blood/old, -/turf/open/floor/plating, -/area/service/library/abandoned) "qXO" = ( /obj/machinery/camera/directional/north{ c_tag = "Atmospherics - Aft"; @@ -138261,7 +138261,7 @@ lVS elW fHw ert -qXG +jZH oof ewK reb @@ -139492,7 +139492,7 @@ fVp qGH nJo ssD -nbt +bwU pAm nJo tHl @@ -161059,7 +161059,7 @@ aaa aad bLs bNt -bPA +iFP bRw bTk bVr diff --git a/code/__DEFINES/~skyrat_defines/banning.dm b/code/__DEFINES/~skyrat_defines/banning.dm index 5623762b86f..1b25b67f568 100644 --- a/code/__DEFINES/~skyrat_defines/banning.dm +++ b/code/__DEFINES/~skyrat_defines/banning.dm @@ -5,3 +5,5 @@ #define BAN_GHOST_ROLE_SPAWNER "Ghost role spawner ban" #define BAN_GHOST_TAKEOVER "Ghost candidate ban" #define BAN_EORG "EORG ban" +#define BAN_ANTAGONIST "General antagonist ban" +#define BAN_OPFOR "OPFOR ban" diff --git a/code/__DEFINES/~skyrat_defines/opposing_force_defines.dm b/code/__DEFINES/~skyrat_defines/opposing_force_defines.dm new file mode 100644 index 00000000000..0e998bbbf6a --- /dev/null +++ b/code/__DEFINES/~skyrat_defines/opposing_force_defines.dm @@ -0,0 +1,50 @@ +#define OPFOR_BACKSTORY_MIN_CHARS 100 +#define OPFOR_BACKSTORY_MAX_CHARS 500 + +#define OPFOR_MAX_OBJECTIVES 5 + +#define OPFOR_STATUS_NOT_SUBMITTED "Not submitted" +#define OPFOR_STATUS_APPROVED "Approved" +#define OPFOR_STATUS_DENIED "Denied" +#define OPFOR_STATUS_AWAITING_APPROVAL "Awaiting approval" +#define OPFOR_STATUS_CHANGES_REQUESTED "Changes requested" + +#define OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED "Not Reviewed" +#define OPFOR_OBJECTIVE_STATUS_APPROVED "Approved" +#define OPFOR_OBJECTIVE_STATUS_DENIED "Denied" + +#define OPFOR_OBJECTIVE_INTENSITY_1 "1 - Minor theft or basic antagonizing" +#define OPFOR_OBJECTIVE_INTENSITY_2 "2 - Kidnapping / Theft" +#define OPFOR_OBJECTIVE_INTENSITY_3 "3 - Assassination / Grand Theft" +#define OPFOR_OBJECTIVE_INTENSITY_4 "4 - Mass sabotage (engine delamination)" +#define OPFOR_OBJECTIVE_INTENSITY_5 "5 - Mass destruction or mass killings" + +#define OPFOR_SUBSYSTEM_REJECT_CAP "OPFOR application system has reached the maximum number of applications" +#define OPFOR_SUBSYSTEM_REJECT_CLOSED "OPFOR application system is not accepting new applications" +#define OPFOR_SUBSYSTEM_READY "OPFOR application system is ready to accept new applications" + +#define OPFOR_REQUEST_UPDATE_COOLDOWN 1 MINUTES +#define OPFOR_PING_COOLDOWN 1 MINUTES + +#define OPFOR_TEXT_LIMIT_TITLE 40 +#define OPFOR_TEXT_LIMIT_BACKSTORY 1000 +#define OPFOR_TEXT_LIMIT_DESCRIPTION 500 +#define OPFOR_TEXT_LIMIT_JUSTIFICATION 500 +#define OPFOR_TEXT_LIMIT_MESSAGE 300 + +#define OPFOR_EQUIPMENT_LIMIT 10 +#define OPFOR_EQUIPMENT_COUNT_LIMIT 5 + +#define OPFOR_EQUIPMENT_STATUS_NOT_REVIEWED "Not Reviewed" +#define OPFOR_EQUIPMENT_STATUS_APPROVED "Approved" +#define OPFOR_EQUIPMENT_STATUS_DENIED "Denied" + +// EQUIPMENT CATEGORIES +#define OPFOR_EQUIPMENT_CATEGORY_RANGED_WEAPONS "Ranged Weapons" +#define OPFOR_EQUIPMENT_CATEGORY_MELEE_WEAPONS "Melee Weapons" +#define OPFOR_EQUIPMENT_CATEGORY_AMMUNITION "Ammunition" +#define OPFOR_EQUIPMENT_CATEGORY_CLOTHING "Clothing" +#define OPFOR_EQUIPMENT_CATEGORY_UTILITY "Utility" +#define OPFOR_EQUIPMENT_CATEGORY_EXPLOSIVES "Explosives" +#define OPFOR_EQUIPMENT_CATEGORY_LOADOUT "Loadouts" +#define OPFOR_EQUIPMENT_CATEGORY_OTHER "Other" diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index a5c91d88fda..2c2f2a82547 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -311,6 +311,8 @@ //Antagonists parts += antag_report() + parts += opfor_report() //SKYRAT EDIT ADDITION + parts += hardcore_random_report() CHECK_TICK diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index e5e2cbfe836..896140985c3 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -206,6 +206,10 @@ if(!candidate_client.prefs?.read_preference(/datum/preference/toggle/be_antag)) candidates.Remove(candidate_player) continue + + if(is_banned_from(candidate_client.ckey, BAN_ANTAGONIST)) + candidates.Remove(candidate_player) + continue //SKYRAT EDIT END if(candidate_client.get_remaining_days(minimum_required_age) > 0) diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index b1b60bb261c..532ca7b6692 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -43,6 +43,14 @@ if (!M.client) // Are they connected? trimmed_list.Remove(M) continue + //SKYRAT EDIT ADDITION + if(is_banned_from(M.client.ckey, BAN_ANTAGONIST)) + trimmed_list.Remove(M) + continue + if(!M.client?.prefs?.read_preference(/datum/preference/toggle/be_antag)) + trimmed_list.Remove(M) + continue + //SKYRAT EDIT END if(M.client.get_remaining_days(minimum_required_age) > 0) trimmed_list.Remove(M) continue diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 7e26167dcff..8f99273d6d4 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -201,7 +201,7 @@ GLOBAL_LIST_EMPTY(objectives) //SKYRAT EDIT ADDITION /datum/objective/assassinate/update_explanation_text() ..() if(target?.current) - explanation_text = "Assassinate [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role]." + explanation_text = "Assassinate [target.name], the [!target_role_type ? target.assigned_role.title : target.special_role] ONCE." //SKYRAT EDIT CHANGE else explanation_text = "Free Objective" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index c2776b46581..f9e68770a04 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -95,6 +95,7 @@ GLOBAL_PROTECT(admin_verbs_sounds) GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/one_click_antag, // SKYRAT EDIT ADDITION - ONE CLICK ANTAG /client/proc/spawn_mob_spawner, // SKYRAT EDIT ADDITION + /client/proc/request_more_opfor, //SKYRAT EDIT ADDITION /client/proc/cmd_select_equipment, /client/proc/cmd_admin_gib_self, /client/proc/drop_bomb, diff --git a/code/modules/admin/check_antagonists.dm b/code/modules/admin/check_antagonists.dm index 1c0581c0282..358e85f79eb 100644 --- a/code/modules/admin/check_antagonists.dm +++ b/code/modules/admin/check_antagonists.dm @@ -191,5 +191,7 @@ dat += build_antag_listing() + dat += SSopposing_force.get_check_antag_listing() //SKYRAT EDIT ADDITION + dat += "" usr << browse(dat.Join(), "window=roundstatus;size=500x500") diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index d4f3fc94ec6..3a9e3e92a24 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -367,7 +367,9 @@ BAN_MOB_CONTROL, BAN_GHOST_ROLE_SPAWNER, BAN_GHOST_TAKEOVER, - BAN_EORG + BAN_EORG, + BAN_ANTAGONIST, + BAN_OPFOR, ),//SKYRAT EDIT ADDITION - EXTRA_BANS ) for(var/department in long_job_lists) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 8deb89e2199..29fbac6edb0 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -112,6 +112,11 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[closed_tickets.len]")]", null, REF(cstatclick)) L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[resolved_tickets.len]")]", null, REF(rstatclick)) + //SKYRAT EDIT ADDITION + if(LAZYLEN(SSopposing_force.submitted_applications)) + for(var/datum/opposing_force/opposing_force as anything in SSopposing_force.submitted_applications) + L[++L.len] = list("[opposing_force.handling_admin ? "H-[opposing_force.handling_admin]. " : ""]OPFOR: [opposing_force.ckey]", null, REF(opposing_force)) + //SKYRAT EDIT END return L //Reassociate still open ticket if one exists diff --git a/html/changelogs/AutoChangeLog-pr-10386.yml b/html/changelogs/AutoChangeLog-pr-10386.yml new file mode 100644 index 00000000000..444243dbce3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-10386.yml @@ -0,0 +1,6 @@ +author: "ErdinyoBarboza" +delete-after: True +changes: + - rscadd: "added new variations of uplink and rag and more sas14 ammo +expansion: Added a load of items to opfor list" + - balance: "rebalanced armory shotguns to be used in opfor" diff --git a/modular_skyrat/master_files/code/game/gamemodes/objective.dm b/modular_skyrat/master_files/code/game/gamemodes/objective.dm new file mode 100644 index 00000000000..fc7d654c80f --- /dev/null +++ b/modular_skyrat/master_files/code/game/gamemodes/objective.dm @@ -0,0 +1,9 @@ +// For modularity, we hook into the update_explanation_text to be sure we have a target to register. +/datum/objective/assassinate/update_explanation_text() + RegisterSignal(target, COMSIG_LIVING_DEATH, .proc/register_target_death) + return ..() + +/datum/objective/assassinate/proc/register_target_death(mob/living/dead_guy, gibbed) + SIGNAL_HANDLER + completed = TRUE + UnregisterSignal(dead_guy, COMSIG_LIVING_DEATH) diff --git a/modular_skyrat/modules/opposing_force/code/admin_procs.dm b/modular_skyrat/modules/opposing_force/code/admin_procs.dm new file mode 100644 index 00000000000..f81d98b0bd8 --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/admin_procs.dm @@ -0,0 +1,12 @@ +/client/proc/request_more_opfor() + set category = "Admin.Fun" + set name = "Request OPFOR" + set desc = "Request players sign up for opfor if they have antag on." + + var/asked = 0 + for(var/mob/living/carbon/human/human in GLOB.alive_player_list) + if(human.client?.prefs?.read_preference(/datum/preference/toggle/be_antag)) + to_chat(human, examine_block(span_greentext("The admins are looking for OPFOR players, if you're interested, sign up in the OOC tab!"))) + asked++ + message_admins("[ADMIN_LOOKUP(usr)] has requested more OPFOR players! (Asked: [asked] players)") + diff --git a/modular_skyrat/modules/opposing_force/code/items.dm b/modular_skyrat/modules/opposing_force/code/items.dm new file mode 100644 index 00000000000..4908c63b78e --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/items.dm @@ -0,0 +1,49 @@ +//Use this to add item variations + +/obj/item/uplink/opfor + name = "old radio" + desc = "A dusty and old looking radio." + +/obj/item/uplink/opfor/Initialize(mapload, owner, tc_amount = 0) + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.name = "old radio" + + + +/obj/item/reagent_containers/glass/rag/large + volume = 30 + amount_per_transfer_from_this = 30 + desc = "A damp rag made from a highly absorbant materials. Can hold up to 30u liquids. You can also clean up messes I guess." + + +/obj/item/storage/box/syndie_kit/gunman_outfit + name = "Gunman Clothing Bundle" + desc = "A box filled with armored and stylish clothing for the aspiring gunmans." + +/obj/item/clothing/suit/armor/vest/leather/gunman + name = "leather overcoat" + desc = "An armored leather overcoat, intended as the go-to wear for any aspiring gunman." + body_parts_covered = CHEST|GROIN|ARMS + armor = list(MELEE = 45, BULLET = 40, LASER = 40, ENERGY = 50, BOMB = 25, BIO = 0, FIRE = 50, ACID = 50, WOUND = 10) //makes it in line with the rest of the armor + +/obj/item/storage/box/syndie_kit/gunman_outfit/PopulateContents() // 45, 40 armor on general without a helmet. + new /obj/item/clothing/under/pants/black/robohand(src) + new /obj/item/clothing/glasses/sunglasses/robohand(src) + new /obj/item/clothing/suit/armor/vest/leather/gunman(src) + new /obj/item/clothing/shoes/combat(src) + +/obj/item/ammo_box/magazine/m16/extended //i will add custom sprites to this + name = "m4a1 magazine (5.56×45mm)" + icon = 'modular_skyrat/modules/gunsgalore/icons/guns/m16/m16.dmi' + icon_state = "5.56mm" + ammo_type = /obj/item/ammo_casing/a556 + caliber = "a556" + max_ammo = 30 + multiple_sprites = AMMO_BOX_FULL_EMPTY + + + + +/obj/item/autosurgeon/organ/syndicate/hackerman + starting_organ = /obj/item/organ/cyberimp/arm/hacker diff --git a/modular_skyrat/modules/opposing_force/code/mind.dm b/modular_skyrat/modules/opposing_force/code/mind.dm new file mode 100644 index 00000000000..bc150beccb5 --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/mind.dm @@ -0,0 +1,32 @@ +/datum/mind + var/datum/opposing_force/opposing_force + +/datum/mind/Destroy() + QDEL_NULL(opposing_force) + return ..() + +/mob/verb/opposing_force() + set name = "Opposing Force" + set category = "OOC" + set desc = "View your opposing force panel, or request one." + // Mind checks + if(!mind) + var/fail_message = "You have no mind!" + if(isobserver(src)) + fail_message += " You have to be in the current round at some point to have one." + to_chat(src, span_warning(fail_message)) + return + + if(is_banned_from(ckey, BAN_ANTAGONIST)) + to_chat(src, span_warning("You are antagonist banned!")) + return + + if(is_banned_from(ckey, BAN_OPFOR)) + to_chat(src, span_warning("You are OPFOR banned!")) + return + + if(!mind.opposing_force) + var/datum/opposing_force/opposing_force = new(mind) + mind.opposing_force = opposing_force + SSopposing_force.new_opfor(opposing_force) + mind.opposing_force.ui_interact(usr) diff --git a/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm b/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm new file mode 100644 index 00000000000..c20e54c0d1c --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/opposing_force_datum.dm @@ -0,0 +1,843 @@ +/datum/opposing_force_selected_equipment + /// Reference to the selected equipment datum. + var/datum/opposing_force_equipment/opposing_force_equipment + /// Why does the user need this? + var/reason = "" + /// What is the status of this item? + var/status = OPFOR_EQUIPMENT_STATUS_NOT_REVIEWED + /// If denied, why? + var/denied_reason = "" + /// How many does the user want? + var/count = 1 + +/datum/opposing_force_selected_equipment/New(datum/opposing_force_equipment/opfor_equipment) + if(opfor_equipment) + opposing_force_equipment = opfor_equipment + +/datum/opposing_force_selected_equipment/Destroy(force, ...) + opposing_force_equipment = null + return ..() + +/datum/opposing_force_objective + /// The name of the objective + var/title = "" + /// The actual objective. + var/description = "" + /// The reason for the objective. + var/justification = "" + /// Was this specific objective approved by the admins? + var/status = OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED + /// Why was this objective denied? If a reason was specified. + var/denied_reason = "" + /// How intense is this goal? + var/intensity = 1 + /// The text intensity of this goal + var/text_intensity = OPFOR_OBJECTIVE_INTENSITY_1 + +/datum/opposing_force + /// A list of objectives. + var/list/objectives = list() + /// A list of items they want spawned. + var/list/requested_items = list() + /// Justification for wanting to do bad things. + var/set_backstory = "" + /// Has this been approved? + var/status = OPFOR_STATUS_NOT_SUBMITTED + /// Hard ref to our mind. + var/datum/mind/mind_reference + /// For logging stuffs + var/list/modification_log = list() + /// Can we edit things? + var/can_edit = TRUE + /// The reason we were denied. + var/denied_reason = "" + /// Any changes required + var/requested_changes + /// Have we been request update muted by an admin? + var/request_updates_muted = FALSE + /// A text list of the admin chat. + var/list/admin_chat = list() + /// Have we issued the player their equipment? + var/equipment_issued = FALSE + /// A list of equipment that the user has requested. + var/list/selected_equipment = list() + /// Are we blocked from submitting a new request? + var/blocked = FALSE + /// What admin has this request been assigned to? + var/handling_admin = "" + /// What changes have the handling admin requested, if any? + var/admin_requested_changes = "" + /// The ckey of the person that made this application + var/ckey + + COOLDOWN_DECLARE(static/request_update_cooldown) + COOLDOWN_DECLARE(static/ping_cooldown) + +/datum/opposing_force/New(datum/mind/mind_reference) + src.mind_reference = mind_reference + ckey = ckey(mind_reference.key) + send_system_message("[ckey] created the application") + +/datum/opposing_force/Destroy(force) + mind_reference.opposing_force = null + mind_reference = null + SSopposing_force.remove_opfor(src) + QDEL_LIST(objectives) + QDEL_LIST(admin_chat) + QDEL_LIST(modification_log) + return ..() + +/datum/opposing_force/Topic(href, list/href_list) + if(href_list["admin_pref"]) + if(!check_rights(R_ADMIN)) + CRASH("Opposing_force TOPIC: Detected possible HREF exploit! ([usr])") + ui_interact(usr) + return TRUE + +/// Builds the HTML panel entry for the round end report +/datum/opposing_force/proc/build_html_panel_entry() + var/list/opfor_entry = list("[mind_reference.key] - ") + opfor_entry += "PM " + if(mind_reference.current) + opfor_entry += "FLW " + opfor_entry += "Show OPFOR Panel" + return opfor_entry.Join() + +/datum/opposing_force/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "OpposingForcePanel") + ui.open() + +/datum/opposing_force/ui_state(mob/user) + return GLOB.always_state + +/datum/opposing_force/ui_data(mob/user) + var/list/data = list() + + var/client/owner_client = GLOB.directory[ckey] + data["admin_mode"] = check_rights_for(user.client, R_DEFAULT) && user.client != owner_client + + data["creator_ckey"] = ckey + + data["owner_antag"] = (mind_reference.current in GLOB.current_living_antags) + + data["backstory"] = set_backstory + + data["raw_status"] = status + + data["status"] = get_status_string() + + data["can_submit"] = SSopposing_force.accepting_objectives && (status == OPFOR_STATUS_NOT_SUBMITTED || status == OPFOR_STATUS_CHANGES_REQUESTED) + + data["can_request_update"] = (status == OPFOR_STATUS_AWAITING_APPROVAL && COOLDOWN_FINISHED(src, request_update_cooldown)) + + data["request_updates_muted"] = request_updates_muted + + data["blocked"] = blocked + + data["can_edit"] = can_edit + + data["approved"] = status == OPFOR_STATUS_APPROVED ? TRUE : FALSE + + data["denied"] = status == OPFOR_STATUS_DENIED ? TRUE : FALSE + + data["handling_admin"] = handling_admin + + data["equipment_issued"] = equipment_issued + + data["owner_mob"] = mind_reference.current ? mind_reference.current.name : "No mob!" + + data["owner_role"] = mind_reference.assigned_role ? mind_reference.assigned_role.title : "No role!" + + var/list/messages = list() + for(var/message in admin_chat) + messages.Add(list(list( + "msg" = message + ))) + data["messages"] = messages + + data["objectives"] = list() + var/objective_num = 1 + for(var/datum/opposing_force_objective/opfor as anything in objectives) + var/list/objective_data = list( + "id" = objective_num, + "ref" = REF(opfor), + "title" = opfor.title, + "description" = opfor.description, + "intensity" = opfor.intensity, + "text_intensity" = opfor.text_intensity, + "justification" = opfor.justification, + "approved" = opfor.status == OPFOR_OBJECTIVE_STATUS_APPROVED ? TRUE : FALSE, + "status_text" = opfor.status, + "denied_text" = opfor.denied_reason, + ) + objective_num++ + data["objectives"] += list(objective_data) + + data["equipment_issued"] = equipment_issued + + data["equipment_list"] = list() + for(var/equipment_category in SSopposing_force.equipment_list) + var/category_items = list() + for(var/datum/opposing_force_equipment/opfor_equipment as anything in SSopposing_force.equipment_list[equipment_category]) + category_items += list(list( + "ref" = REF(opfor_equipment), + "name" = opfor_equipment.name, + "description" = opfor_equipment.description, + "equipment_category" = opfor_equipment.category, + )) + data["equipment_list"] += list(list( + "category" = equipment_category, + "items" = category_items, + )) + + data["selected_equipment"] = list() + for(var/datum/opposing_force_selected_equipment/equipment as anything in selected_equipment) + var/list/equipment_data = list( + "ref" = REF(equipment), + "name" = equipment.opposing_force_equipment.name, + "description" = equipment.opposing_force_equipment.description, + "item" = equipment.opposing_force_equipment.item_type, + "status" = equipment.status, + "approved" = equipment.status == OPFOR_EQUIPMENT_STATUS_APPROVED ? TRUE : FALSE, + "reason" = equipment.reason, + "denied_reason" = equipment.denied_reason, + "count" = equipment.count, + ) + data["selected_equipment"] += list(equipment_data) + + return data + +/datum/opposing_force/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + var/datum/opposing_force_objective/edited_objective + if(params["objective_ref"]) + edited_objective = locate(params["objective_ref"]) in objectives + if(!edited_objective) + return + + switch(action) + // General control + if("set_backstory") + set_backstory(usr, params["backstory"]) + if("request_update") + request_update(usr) + if("modify_request") + modify_request(usr) + if("close_application") + close_application(usr) + if("submit") + submit_to_subsystem(usr) + if("send_message") + send_message(usr, params["message"]) + // Objective control + if("add_objective") + add_objective(usr) + if("remove_objective") + remove_objective(usr, edited_objective) + if("set_objective_title") + set_objective_title(usr, edited_objective, params["title"]) + if("set_objective_description") + set_objective_description(usr, edited_objective, params["new_desciprtion"]) + if("set_objective_justification") + set_objective_justification(usr, edited_objective, params["new_justification"]) + if("set_objective_intensity") + set_objective_intensity(usr, edited_objective, params["new_intensity_level"]) + // Equipment control + if("select_equipment") + var/datum/opposing_force_equipment/equipment + for(var/category in SSopposing_force.equipment_list) + equipment = locate(params["equipment_ref"]) in SSopposing_force.equipment_list[category] + if(equipment) + break + if(!equipment) + return + select_equipment(usr, equipment) + if("remove_equipment") + var/datum/opposing_force_selected_equipment/equipment = locate(params["selected_equipment_ref"]) in selected_equipment + if(!equipment) + return + remove_equipment(usr, equipment) + if("set_equipment_reason") + var/datum/opposing_force_selected_equipment/equipment = locate(params["selected_equipment_ref"]) in selected_equipment + if(!equipment) + return + set_equipment_reason(usr, equipment, params["new_equipment_reason"]) + if("set_equipment_count") + var/datum/opposing_force_selected_equipment/equipment = locate(params["selected_equipment_ref"]) in selected_equipment + if(!equipment) + return + set_equipment_count(usr, equipment, params["new_equipment_count"]) + + //Admin protected procs + if("approve") + if(!check_rights(R_ADMIN)) + return + for(var/datum/opposing_force_objective/objective as anything in objectives) + if(objective.status == OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED) + to_chat(usr, examine_block(span_command_headset(span_pink("OPFOR: ERROR, some objectives have not been reviewed. Please approve/deny all objectives.")))) + return + for(var/datum/opposing_force_selected_equipment/equipment as anything in selected_equipment) + if(equipment.status == OPFOR_EQUIPMENT_STATUS_NOT_REVIEWED) + to_chat(usr, examine_block(span_command_headset(span_pink("OPFOR: ERROR, some equipment requests have not been reviewed. Please approve/deny all equipment requests.")))) + return + SSopposing_force.approve(src, usr) + if("approve_all") + if(!check_rights(R_ADMIN)) + return + approve_all(usr) + if("handle") + handle(usr) + if("issue_gear") + if(!check_rights(R_ADMIN)) + return + issue_gear(usr) + if("deny") + if(!check_rights(R_ADMIN)) + return + var/denied_reason = tgui_input_text(usr, "Denial Reason", "Enter a reason for denying this application:") + // Checking to see if the user is spamming the button, async and all. + if(status == OPFOR_STATUS_DENIED) + return + SSopposing_force.deny(src, denied_reason, usr) + if("mute_request_updates") + if(!check_rights(R_ADMIN)) + return + mute_request_updates(usr) + if("toggle_block") + if(!check_rights(R_ADMIN)) + return + toggle_block(usr) + if("approve_objective") + if(!check_rights(R_ADMIN)) + return + approve_objective(usr, edited_objective) + if("deny_objective") + if(!check_rights(R_ADMIN)) + return + var/denied_reason = tgui_input_text(usr, "Denial Reason", "Enter a reason for denying this objective:") + deny_objective(usr, edited_objective, denied_reason) + if("approve_equipment") + var/datum/opposing_force_selected_equipment/equipment = locate(params["selected_equipment_ref"]) in selected_equipment + if(!equipment) + return + if(!check_rights(R_ADMIN)) + return + approve_equipment(usr, equipment) + if("deny_equipment") + var/datum/opposing_force_selected_equipment/equipment = locate(params["selected_equipment_ref"]) in selected_equipment + if(!equipment) + return + if(!check_rights(R_ADMIN)) + return + var/denied_reason = tgui_input_text(usr, "Denial Reason", "Enter a reason for denying this objective:") + deny_equipment(usr, equipment, denied_reason) + if("flw_user") + if(!check_rights(R_ADMIN)) + return + flw_user(usr) + +/datum/opposing_force/proc/flw_user(mob/user) + user.client?.admin_follow(mind_reference.current) + +/datum/opposing_force/proc/set_equipment_count(mob/user, datum/opposing_force_selected_equipment/equipment, new_count) + var/sanitized_newcount = sanitize_integer(new_count, 1, OPFOR_EQUIPMENT_COUNT_LIMIT) + equipment.count = new_count + add_log(user.ckey, "Set equipment '[equipment.opposing_force_equipment.name] count to [sanitized_newcount]") + +/datum/opposing_force/proc/handle(mob/user) + if(handling_admin) + var/choice = tgui_alert(user, "Another admin is currently handling this application, do you want to override them?", "Admin Handling", list("Yes", "No")) + if(choice == "No") + return + handling_admin = get_admin_ckey(user) + to_chat(mind_reference.current, examine_block(span_nicegreen("Your OPFOR application is now being handled by [handling_admin]."))) + send_admins_opfor_message("HANDLE: [ADMIN_LOOKUPFLW(user)] is handling [mind_reference.key]'s OPFOR application.") + send_system_message("[handling_admin] has assigned themselves to this application") + add_log(user.ckey, "Assigned self to application") + +/datum/opposing_force/proc/mute_request_updates(mob/user, override = "none") + if(override != "none") + request_updates_muted = override + else + request_updates_muted = !request_updates_muted + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] [request_updates_muted ? "muted" : "unmuted"] the help requests function") + add_log(user.ckey, "[request_updates_muted ? "Muted" : "Unmuted"] user from opposing force help requests.") + +/datum/opposing_force/proc/toggle_block(mob/user, override = "none") + if(override != "none") + blocked = override + else + blocked = !blocked + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] blocked you from submitting new requests") + add_log(user.ckey, "Blocked user from opposing force requests.") + +/** + * Equipment procs + */ + +/datum/opposing_force/proc/deny_equipment(mob/user, datum/opposing_force_selected_equipment/incoming_equipment, denied_reason = "") + if(incoming_equipment.status == OPFOR_EQUIPMENT_STATUS_DENIED) + return + incoming_equipment.status = OPFOR_EQUIPMENT_STATUS_DENIED + incoming_equipment.denied_reason = denied_reason + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has denied equipment '[incoming_equipment.opposing_force_equipment.name]'[denied_reason ? " with the reason '[denied_reason]'" : ""]") + add_log(user.ckey, "Denied equipment: [incoming_equipment.opposing_force_equipment.name] with reason: [denied_reason]") + +/datum/opposing_force/proc/approve_equipment(mob/user, datum/opposing_force_selected_equipment/incoming_equipment) + if(incoming_equipment.status == OPFOR_EQUIPMENT_STATUS_APPROVED) + return + incoming_equipment.status = OPFOR_EQUIPMENT_STATUS_APPROVED + incoming_equipment.denied_reason = "" + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has approved equipment '[incoming_equipment.opposing_force_equipment.name]'") + add_log(user.ckey, "Approved equipment: [incoming_equipment.opposing_force_equipment.name]") + +/datum/opposing_force/proc/set_equipment_reason(mob/user, datum/opposing_force_selected_equipment/incoming_equipment, new_reason) + if(!can_edit) + return + if(!incoming_equipment) + CRASH("set_equipment_reason tried to update a non existent opfor equipment datum!") + var/sanitized_reason = STRIP_HTML_SIMPLE(new_reason, OPFOR_TEXT_LIMIT_DESCRIPTION) + add_log(user.ckey, "Updated equipment([incoming_equipment.opposing_force_equipment.name]) REASON from: [incoming_equipment.reason] to: [sanitized_reason]") + incoming_equipment.reason = sanitized_reason + return TRUE + +/datum/opposing_force/proc/remove_equipment(mob/user, datum/opposing_force_selected_equipment/incoming_equipment) + if(!can_edit) + return + add_log(user.ckey, "Removed equipment: [incoming_equipment.opposing_force_equipment.name]") + selected_equipment -= incoming_equipment + qdel(incoming_equipment) + +/datum/opposing_force/proc/select_equipment(mob/user, datum/opposing_force_equipment/incoming_equipment, reason) + if(!can_edit) + return + if(LAZYLEN(selected_equipment) >= OPFOR_EQUIPMENT_LIMIT) + to_chat(user, span_warning("You have too many items, please remove one!")) + return + var/datum/opposing_force_selected_equipment/new_selected = new(incoming_equipment) + selected_equipment += new_selected + add_log(user.ckey, "Selected equipment: [incoming_equipment.name]") + +/datum/opposing_force/proc/issue_gear(mob/user) + if(!selected_equipment.len || !isliving(mind_reference.current) || status != OPFOR_STATUS_APPROVED || equipment_issued) + return + var/mob/living/target = mind_reference.current + var/obj/item/storage/box/spawned_box = new(get_turf(target)) + for(var/datum/opposing_force_selected_equipment/iterating_equipment as anything in selected_equipment) + if(iterating_equipment.status != OPFOR_EQUIPMENT_STATUS_APPROVED) + continue + for(var/i in 1 to iterating_equipment.count) + new iterating_equipment.opposing_force_equipment.item_type(spawned_box) + if(ishuman(target)) + var/mob/living/carbon/human/human = target + human.put_in_hands(spawned_box) + + add_log(user.ckey, "Issued gear") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has issued all approved equipment") + equipment_issued = TRUE + +/** + * Control procs + */ + +/datum/opposing_force/proc/request_update(mob/user) + if(request_updates_muted) + to_chat(user, span_warning("You are currently blocked from requesting updates!")) + return + if(status != OPFOR_STATUS_AWAITING_APPROVAL || !COOLDOWN_FINISHED(src, request_update_cooldown)) + return + + send_admins_opfor_message(span_command_headset("UPDATE REQUEST: [ADMIN_LOOKUPFLW(user)] has requested an update on their OPFOR application!")) + add_log(user.ckey, "Requested an update") + + for(var/client/staff as anything in GLOB.admins) + if(staff?.prefs?.toggles & SOUND_ADMINHELP) + SEND_SOUND(staff, sound('sound/effects/adminhelp.ogg')) + window_flash(staff) + + COOLDOWN_START(src, request_update_cooldown, OPFOR_REQUEST_UPDATE_COOLDOWN) + +/datum/opposing_force/proc/submit_to_subsystem(mob/user) + if(blocked) + to_chat(user, span_warning("You are currently blocked from submitting new requests!")) + return + if(status != OPFOR_STATUS_NOT_SUBMITTED && status != OPFOR_STATUS_CHANGES_REQUESTED) + return FALSE + // Subsystem checks, no point in bloating the system if it's not accepting more. + var/availability = SSopposing_force.check_availability() + if(availability != OPFOR_SUBSYSTEM_READY) + to_chat(usr, span_warning("Error, the OPFOR subsystem rejected your request. Reason: [availability]")) + return FALSE + + var/queue_position = SSopposing_force.add_to_queue(src) + + for(var/client/staff as anything in GLOB.admins) + if(staff?.prefs?.toggles & SOUND_ADMINHELP) + SEND_SOUND(staff, sound('sound/effects/adminhelp.ogg')) + window_flash(staff, ignorepref = TRUE) + + status = OPFOR_STATUS_AWAITING_APPROVAL + can_edit = FALSE + add_log(user.ckey, "Submitted to the OPFOR subsystem") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has submitted the application for review") + send_admins_opfor_message(span_command_headset("SUBMISSION: [ADMIN_LOOKUPFLW(user)] has submitted their OPFOR application. They are number [queue_position] in the queue.")) + to_chat(usr, examine_block(span_nicegreen(("You have been added to the queue for the OPFOR subsystem. You are number [queue_position] in line.")))) + +/datum/opposing_force/proc/modify_request(mob/user) + if(status == OPFOR_STATUS_CHANGES_REQUESTED) + return + var/choice = tgui_alert(user, "Are you sure you want to request changes? This will unapprove all objectives.", "Confirm", list("Yes", "No")) + if(choice != "Yes") + return + if(status == OPFOR_STATUS_CHANGES_REQUESTED) // The alert is not async, so this could change, thus being spammed. + return + for(var/datum/opposing_force_objective/opfor in objectives) + opfor.status = OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED + status = OPFOR_STATUS_CHANGES_REQUESTED + SSopposing_force.modify_request(src) + can_edit = TRUE + + add_log(user.ckey, "Modify request submitted") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has requested modifications to the application") + send_admins_opfor_message("CHANGES REQUESTED: [ADMIN_LOOKUPFLW(user)] has submitted a modify request, their application has been reset.") + +/datum/opposing_force/proc/deny(mob/denier, reason = "") + if(status == OPFOR_STATUS_DENIED) + return + status = OPFOR_STATUS_DENIED + can_edit = FALSE + denied_reason = reason + + for(var/datum/opposing_force_selected_equipment/iterating_equipment as anything in selected_equipment) + iterating_equipment.status = OPFOR_EQUIPMENT_STATUS_DENIED + for(var/datum/opposing_force_objective/opfor in objectives) + opfor.status = OPFOR_OBJECTIVE_STATUS_DENIED + SEND_SOUND(mind_reference.current, sound('modular_skyrat/modules/opposing_force/sound/denied.ogg')) + add_log(denier.ckey, "Denied application") + to_chat(mind_reference.current, examine_block(span_redtext("Your OPFOR application has been denied by [denier ? get_admin_ckey(denier) : "the OPFOR subsystem"]!"))) + send_system_message(get_admin_ckey(denier) + " has denied the application with the following reason: [reason]") + send_admins_opfor_message("[span_red("DENIED")]: [ADMIN_LOOKUPFLW(denier)] has denied [ckey]'s application([reason ? reason : "No reason specified"])") + ticket_counter_add_handled(denier.key, 1) + +/datum/opposing_force/proc/approve(mob/approver) + if(status == OPFOR_STATUS_APPROVED) + return + status = OPFOR_STATUS_APPROVED + can_edit = FALSE + + SEND_SOUND(mind_reference.current, sound('modular_skyrat/modules/opposing_force/sound/approved.ogg')) + add_log(approver.ckey, "Approved application") + to_chat(mind_reference.current, examine_block(span_greentext("Your OPFOR application has been approved by [approver ? get_admin_ckey(approver) : "the OPFOR subsystem"]!"))) + send_system_message("[approver ? get_admin_ckey(approver) : "The OPFOR subsystem"] has approved the application") + send_admins_opfor_message("[span_green("APPROVED")]: [ADMIN_LOOKUPFLW(approver)] has approved [ckey]'s application") + ticket_counter_add_handled(approver.key, 1) + +/datum/opposing_force/proc/close_application(mob/user) + if(status == OPFOR_STATUS_NOT_SUBMITTED) + return + var/choice = tgui_alert(user, "Are you sure you want withdraw your application?", "Confirm", list("Yes", "No")) + if(choice != "Yes") + return + if(status == OPFOR_STATUS_NOT_SUBMITTED) // The alert is not async, so this could change, thus being spammed. + return + SSopposing_force.unsubmit_opfor(src) + status = OPFOR_STATUS_NOT_SUBMITTED + can_edit = TRUE + + for(var/datum/opposing_force_selected_equipment/iterating_equipment as anything in selected_equipment) + iterating_equipment.status = OPFOR_EQUIPMENT_STATUS_NOT_REVIEWED + for(var/datum/opposing_force_objective/opfor as anything in objectives) + opfor.status = OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED + + add_log(user.ckey, "Withdrew application") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has closed the application") + +/datum/opposing_force/proc/set_backstory(mob/user, incoming_backstory) + if(!can_edit) + return + var/sanitized_backstory = STRIP_HTML_SIMPLE(incoming_backstory, OPFOR_TEXT_LIMIT_BACKSTORY) + add_log(user.ckey, "Updated BACKSTORY from: [set_backstory] to: [sanitized_backstory]") + set_backstory = sanitized_backstory + return TRUE + +/datum/opposing_force/proc/approve_all(mob/user) + if(SSopposing_force.approve(src, user)) + for(var/datum/opposing_force_selected_equipment/iterating_equipment as anything in selected_equipment) + iterating_equipment.status = OPFOR_EQUIPMENT_STATUS_APPROVED + for(var/datum/opposing_force_objective/opfor as anything in objectives) + opfor.status = OPFOR_OBJECTIVE_STATUS_APPROVED + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has approved the application and ALL objectives and equipment") + add_log(user.ckey, "Approved application and all objectives and equipment") + + +/** + * Objective procs + */ + +/datum/opposing_force/proc/set_objective_intensity(mob/user, datum/opposing_force_objective/opposing_force_objective, new_intensity) + if(!can_edit) + return + if(!opposing_force_objective) + CRASH("set_objective_intensity tried to update a non existent opfor objective!") + var/sanitized_intensity = sanitize_integer(new_intensity, 1, 500) + switch(sanitized_intensity) + if(0 to 100) + opposing_force_objective.text_intensity = OPFOR_OBJECTIVE_INTENSITY_1 + if(101 to 200) + opposing_force_objective.text_intensity = OPFOR_OBJECTIVE_INTENSITY_2 + if(201 to 300) + opposing_force_objective.text_intensity = OPFOR_OBJECTIVE_INTENSITY_3 + if(301 to 400) + opposing_force_objective.text_intensity = OPFOR_OBJECTIVE_INTENSITY_4 + if(401 to 501) + opposing_force_objective.text_intensity = OPFOR_OBJECTIVE_INTENSITY_5 + add_log(user.ckey, "Set updated an objective intensity from [opposing_force_objective.intensity] to [sanitized_intensity].") + opposing_force_objective.intensity = sanitized_intensity + return TRUE + +/datum/opposing_force/proc/set_objective_description(mob/user, datum/opposing_force_objective/opposing_force_objective, new_description) + if(!can_edit) + return + if(!opposing_force_objective) + CRASH("set_objective_description tried to update a non existent opfor objective!") + var/sanitized_description = STRIP_HTML_SIMPLE(new_description, OPFOR_TEXT_LIMIT_DESCRIPTION) + opposing_force_objective.description = sanitized_description + add_log(user.ckey, "Updated objective([opposing_force_objective.title]) DESCRIPTION from: [opposing_force_objective.description] to: [sanitized_description]") + return TRUE + +/datum/opposing_force/proc/set_objective_justification(mob/user, datum/opposing_force_objective/opposing_force_objective, new_justification) + if(!can_edit) + return + if(!opposing_force_objective) + CRASH("set_objective_description tried to update a non existent opfor objective!") + var/sanitize_justification = STRIP_HTML_SIMPLE(new_justification, OPFOR_TEXT_LIMIT_JUSTIFICATION) + opposing_force_objective.justification = sanitize_justification + add_log(user.ckey, "Updated objective([opposing_force_objective.title]) JUSTIFICATION from: [opposing_force_objective.justification] to: [sanitize_justification]") + return TRUE + +/datum/opposing_force/proc/remove_objective(mob/user, datum/opposing_force_objective/opposing_force_objective) + if(!can_edit) + return + if(!opposing_force_objective) + CRASH("set_objective_description tried to remove a non existent opfor objective!") + objectives -= opposing_force_objective + add_log(user.ckey, "Removed the following objective from their OPFOR application: [opposing_force_objective.title]") + qdel(opposing_force_objective) + return TRUE + +/datum/opposing_force/proc/add_objective(mob/user) + if(!can_edit) + return + if(LAZYLEN(objectives) >= OPFOR_MAX_OBJECTIVES) + to_chat(user, span_warning("You have too many objectives, please remove one!")) + return + objectives += new /datum/opposing_force_objective + add_log(user.ckey, "Added a new blank objective") + return TRUE + +/datum/opposing_force/proc/set_objective_title(mob/user, datum/opposing_force_objective/opposing_force_objective, new_title) + if(!can_edit) + return + var/sanitized_title = STRIP_HTML_SIMPLE(new_title, OPFOR_TEXT_LIMIT_TITLE) + if(!opposing_force_objective) + CRASH("set_objective_description tried to update a non existent opfor objective!") + add_log(user.ckey, "Updated objective([opposing_force_objective.title]) TITLE from: [opposing_force_objective.title] to: [sanitized_title]") + opposing_force_objective.title = sanitized_title + return TRUE + +/datum/opposing_force/proc/deny_objective(mob/user, datum/opposing_force_objective/opposing_force_objective, deny_reason) + opposing_force_objective.status = OPFOR_OBJECTIVE_STATUS_DENIED + opposing_force_objective.denied_reason = deny_reason + add_log(user.ckey, "Denied objective([opposing_force_objective.title]) WITH REASON: [deny_reason]") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has denied objective '[opposing_force_objective.title]' with the reason '[deny_reason]'") + +/datum/opposing_force/proc/approve_objective(mob/user, datum/opposing_force_objective/opposing_force_objective) + opposing_force_objective.status = OPFOR_OBJECTIVE_STATUS_APPROVED + add_log(user.ckey, "Approved objective([opposing_force_objective.title])") + send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has approved objective '[opposing_force_objective.title]'") + +/** + * System procs + */ + +/datum/opposing_force/proc/add_log(logger_ckey, new_log) + var/msg = "OPFOR([ckey]): [logger_ckey ? logger_ckey : "SYSTEM"] - [new_log]" + modification_log += msg + log_admin(msg) + +/datum/opposing_force/proc/send_admins_opfor_message(message) + message = "[span_pink("OPFOR:")] [span_admin("[message] (Show Panel)")]" + to_chat(GLOB.admins, + type = MESSAGE_TYPE_ADMINLOG, + html = message, + confidential = TRUE) + +/datum/opposing_force/proc/get_status_string() + var/subsystem_status = SSopposing_force.check_availability() + if(subsystem_status != OPFOR_SUBSYSTEM_READY) + return subsystem_status + switch(status) + if(OPFOR_STATUS_AWAITING_APPROVAL) + return "Awaiting approval, you are number [SSopposing_force.get_queue_position(src)] in the queue" + if(OPFOR_STATUS_APPROVED) + return "Approved, please check your objectives for specific approval" + if(OPFOR_STATUS_DENIED) + return "Denied, do not attempt any of your objectives" + if(OPFOR_STATUS_CHANGES_REQUESTED) + return "Changes requested, please review your application" + if(OPFOR_STATUS_NOT_SUBMITTED) + return OPFOR_STATUS_NOT_SUBMITTED + else + return "ERROR" + +/datum/opposing_force/proc/get_admin_ckey(mob/user) + if(user.client?.holder?.fakekey) + return user.client.holder.fakekey + return user.ckey + +/datum/opposing_force/proc/broadcast_queue_change() + var/queue_number = SSopposing_force.get_queue_position(src) + to_chat(mind_reference.current, examine_block(span_nicegreen("Your OPFOR application is now number [queue_number] in the queue."))) + send_system_message("Application is now number [queue_number] in the queue") + +/datum/opposing_force/proc/send_message(mob/user, message) + if(!message) + return + message = STRIP_HTML_SIMPLE(message, OPFOR_TEXT_LIMIT_MESSAGE) + var/message_string + var/real_round_time = world.timeofday - SSticker.real_round_start_time + if(check_rights_for(user.client, R_ADMIN) && user != mind_reference) + message_string = "[time2text(real_round_time, "hh:mm:ss", 0)] (ADMIN) [get_admin_ckey(user)]: " + message + else + message_string = "[time2text(real_round_time, "hh:mm:ss", 0)] (USER) [user.ckey]: " + message + admin_chat += message_string + + // We support basic commands, see run_command for compatible commands, the operator is / + if(findtext(message, "/", 1, 2)) + // We remove the command indentifier before we try running the command. + var/command = replacetext(message, "/", "", 1, 2) + run_command(user, command) + + add_log(user.ckey, "Sent message: [message]") + + +/datum/opposing_force/proc/send_system_message(message) + var/real_round_time = world.timeofday - SSticker.real_round_start_time + var/message_string = "[time2text(real_round_time, "hh:mm:ss", 0)] SYSTEM: " + message + admin_chat += message_string + +/datum/opposing_force/proc/run_command(mob/user, message) + var/list/params = splittext(message, " ") + + var/command = params[1] + + switch(command) + if("item") + check_item(params[2]) + if("help") + print_help(user) + if("ping_admin") + ping_admin(user) + if("ping_user") + ping_user(user) + if("unlock_equipment") + unlock_equipment(user) // Admin only proc + else + send_system_message("Unknown command: [command]") + +/datum/opposing_force/proc/print_help(mob/user) + send_system_message("Available commands:") + send_system_message("/item 'item_name' - Check an items quick stats") + send_system_message("/ping_admin - Ping the handling admin, if there is one.") + send_system_message("/help - Print this help") + // Admin commands. + if(check_rights_for(user.client, R_ADMIN)) + send_system_message("Admin commands:") + send_system_message("/unlock_equipment - Unlock all equipment, useful if you need to give the user more stuff.") + send_system_message("/ping_user - Pings the user.") + +/** + * System commands + */ +/datum/opposing_force/proc/check_item(type) + var/obj/item/processed_item = text2path(type) + if(!processed_item) + send_system_message("Unknown type: [type]") + return + if(!ispath(processed_item, /obj/item)) + send_system_message("Error: [processed_item] is not an item") + return + + send_system_message("Here are the item specifications for [type]:") + send_system_message("Name: [initial(processed_item.name)]") + send_system_message("Description: [initial(processed_item.desc)]") + send_system_message("Weight class: [initial(processed_item.w_class)]") + send_system_message("Tool behaviour: [initial(processed_item.tool_behaviour)]") + send_system_message("Weak against armor: [initial(processed_item.weak_against_armour) ? "Yes" : "No"]") + send_system_message("Damage type: [initial(processed_item.damtype)]") + send_system_message("Wound bonus: [initial(processed_item.wound_bonus)]") + send_system_message("Bare wound bonus: [initial(processed_item.bare_wound_bonus)]") + send_system_message("Force: [initial(processed_item.force)]") + +/datum/opposing_force/proc/unlock_equipment(mob/user) + if(!check_rights_for(user.client, R_ADMIN)) + send_system_message("ERROR: You do not have permission to do that.") + return + if(!equipment_issued) + send_system_message("ERROR: Equipment not yet issued.") + return + equipment_issued = TRUE + send_system_message("Equipment unlocked.") + +/datum/opposing_force/proc/ping_admin(mob/user) + if(!handling_admin) + send_system_message("ERROR: No admin is handling the application.") + return + if(!COOLDOWN_FINISHED(src, ping_cooldown)) + send_system_message("ERROR: Ping is on cooldown.") + return + if(request_updates_muted) + send_system_message("ERROR: You are muted.") + return + if(user.ckey != handling_admin && GLOB.directory[handling_admin]) + to_chat(GLOB.directory[handling_admin], span_pink("OPFOR: [user] has pinged their OPFOR admin chat! (Show Panel)")) + SEND_SOUND(GLOB.directory[handling_admin], sound('sound/misc/bloop.ogg')) + send_system_message("Handling admin pinged.") + COOLDOWN_START(src, ping_cooldown, OPFOR_PING_COOLDOWN) + else + send_system_message("ERROR: Ping failed.") + +/datum/opposing_force/proc/ping_user(mob/user) + if(!check_rights_for(user.client, R_ADMIN)) + send_system_message("ERROR: You do not have permission to do that.") + return + send_system_message("User pinged.") + to_chat(mind_reference.current, span_pink("OPFOR: [get_admin_ckey(user)] has pinged your OPFOR chat, check it!")) + SEND_SOUND(mind_reference.current, sound('sound/misc/bloop.ogg')) + +/datum/opposing_force/proc/roundend_report() + var/list/report = list("
") + report += span_greentext(mind_reference.current.real_name) + if(objectives.len) + report += "Had an approved OPFOR appliation with the following objectives:
" + for(var/datum/opposing_force_objective/opfor_objective in objectives) + if(opfor_objective.status != OPFOR_OBJECTIVE_STATUS_APPROVED) + continue + report += "Title: [opfor_objective.title]
" + report += "
Description: [opfor_objective.description]
" + report += "
" + + if(selected_equipment.len) + report += "And had the following approved equipment:
" + for(var/datum/opposing_force_selected_equipment/opfor_equipment in selected_equipment) + if(opfor_equipment.status != OPFOR_EQUIPMENT_STATUS_APPROVED) + continue + report += "
[opfor_equipment.opposing_force_equipment.name]
" + report += "
" + + return report.Join("\n") diff --git a/modular_skyrat/modules/opposing_force/code/opposing_force_equipment.dm b/modular_skyrat/modules/opposing_force/code/opposing_force_equipment.dm new file mode 100644 index 00000000000..0b1814e1a83 --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/opposing_force_equipment.dm @@ -0,0 +1,560 @@ +/** + * OPFOR EQUIPMENT DATUM + * + * The OPFOR subsystem will gather all of these on initialisation and populate a list, which is then passed + * into the OPFOR UI, which can be selected by the user. + * + * The User will be equipped with whatever they have selected if the approving admin authorises it. + */ + + +/datum/opposing_force_equipment + /// The name of the equipment used in the listing, if not set, it will use the items name. + var/name + /// The description of the equipment used in the listing, if not set, it will use the items description. + var/description + /// The item path that we refer to when equipping. + var/obj/item_type + /// Category of the item. See opposing_force_defines.dm for a list of categories. + var/category = "" + +/** + * GUNS + */ + +/datum/opposing_force_equipment/gun + category = OPFOR_EQUIPMENT_CATEGORY_RANGED_WEAPONS +/datum/opposing_force_equipment/gun/m1911 + item_type = /obj/item/gun/ballistic/automatic/pistol/m1911 + description = "A semi-automatic .45 caliber pistol. Gold standard for sidearms." + +/datum/opposing_force_equipment/gun/m16 + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/m16 + +/datum/opposing_force_equipment/gun/m16m + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/m16/modern + +/datum/opposing_force_equipment/gun/m16patriot + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/m16/modern/v2 + +/datum/opposing_force_equipment/gun/akm + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/akm + +/datum/opposing_force_equipment/gun/akmm + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/akm/modern + +/datum/opposing_force_equipment/gun/mp40 + item_type = /obj/item/gun/ballistic/automatic/submachine_gun/mp40 + +/datum/opposing_force_equipment/gun/mp40m/modern + item_type = /obj/item/gun/ballistic/automatic/submachine_gun/mp40 + +/datum/opposing_force_equipment/gun/pps + item_type = /obj/item/gun/ballistic/automatic/submachine_gun/pps + description = "A very cheap, barely reliable reproduction of a personal defense weapon based on the original Soviet model. Not nearly as infamous as the Mosin. \ + Compact enough to fit in your backpack!" + +/datum/opposing_force_equipment/gun/ppsh + item_type = /obj/item/gun/ballistic/automatic/submachine_gun/ppsh + +/datum/opposing_force_equipment/gun/ppshm + item_type = /obj/item/gun/ballistic/automatic/submachine_gun/ppsh/modern + +/datum/opposing_force_equipment/gun/stg + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/stg + +/datum/opposing_force_equipment/gun/stgm + item_type = /obj/item/gun/ballistic/automatic/assault_rifle/stg/modern + +/datum/opposing_force_equipment/gun/fg42 + item_type = /obj/item/gun/ballistic/automatic/battle_rifle/fg42 + +/datum/opposing_force_equipment/gun/fg42m + item_type = /obj/item/gun/ballistic/automatic/battle_rifle/fg42/modern + +/datum/opposing_force_equipment/gun/uzi + item_type = /obj/item/gun/ballistic/automatic/mini_uzi + description = "The uzi nine millimeter, a timeless submachinegun for a warrior out of time." + +/datum/opposing_force_equipment/gun/mg34 + item_type = /obj/item/gun/ballistic/automatic/mg34 + +/datum/opposing_force_equipment/gun/cfa + item_type = /obj/item/gun/ballistic/automatic/cfa_rifle + +/datum/opposing_force_equipment/gun/m23 + item_type = /obj/item/gun/ballistic/shotgun/m23 + description = "An eight-round pump-action shotgun found in an old station. Comes loaded with beanbag shells but can take any 12 gauge load." + +/datum/opposing_force_equipment/gun/as2 + item_type = /obj/item/gun/ballistic/shotgun/automatic/as2 + description = "A compact version of the combat shotgun. Comes with a four-round tube and can be silenced as well as holstered to your belt or vest. \ + Comes loaded with slugs but can take any 12 gauge load." + +/datum/opposing_force_equipment/gun/sas14 + item_type = /obj/item/gun/ballistic/shotgun/sas14 + description = "A semi-automatic, magazine-fed shotgun chambered in 14 gauge. Standard mags can take five rounds. \ + Despite the lower damage of 14 gauge, it can load the highly praised taser shots." + +/datum/opposing_force_equipment/gun/g357 + item_type = /obj/item/gun/ballistic/revolver + description = "A .357 magnum revolver. Seven shots, more than enough to kill anything that moves." + +/datum/opposing_force_equipment/gun/mateba + item_type = /obj/item/gun/ballistic/revolver/mateba + +/datum/opposing_force_equipment/gun/nagant + item_type = /obj/item/gun/ballistic/revolver/nagant + +/datum/opposing_force_equipment/gun/snub + item_type = /obj/item/gun/ballistic/automatic/pistol/cfa_snub + +/datum/opposing_force_equipment/gun/ruby + item_type = /obj/item/gun/ballistic/automatic/pistol/cfa_ruby + +/datum/opposing_force_equipment/gun/wildcat + item_type = /obj/item/gun/ballistic/automatic/cfa_wildcat + +//oddities +/datum/opposing_force_equipment/gun/ninjastar + item_type = /obj/item/throwing_star + description = "Be the maintenance ninja you always wanted to be. Does not come with multi-throwing cybernetics" + +/datum/opposing_force_equipment/gun/throwing_weapons + item_type = /obj/item/storage/box/syndie_kit/throwing_weapons + description = "A box of shurikens and reinforced bolas from ancient Earth martial arts. They are highly effective \ + throwing weapons. The bolas can knock a target down and the shurikens will embed into limbs." + name = "Box of Throwing Weapons" + +/datum/opposing_force_equipment/gun/origami + item_type = /obj/item/storage/box/syndie_kit/origami_bundle + description = "A box containing a guide on how to craft masterful works of origami, allowing you to transform normal pieces of paper into \ + perfectly aerodynamic (and potentially lethal) paper airplanes." + + + +/** + * AMMO + */ +/datum/opposing_force_equipment/ammo + category = OPFOR_EQUIPMENT_CATEGORY_AMMUNITION + +/datum/opposing_force_equipment/ammo/m45 + item_type = /obj/item/ammo_box/magazine/m45 + description = "An eight-round magazine for the M1911 pistol. Uses .45 caliber ammunition." + +/datum/opposing_force_equipment/ammo/m16 + item_type = /obj/item/ammo_box/magazine/m16 + description = "A twenty-round magazine for the M16 assault rifle. Uses 5.56x45mm ammunition." + +/datum/opposing_force_equipment/ammo/akm + item_type = /obj/item/ammo_box/magazine/akm + +/datum/opposing_force_equipment/ammo/akmbanan + item_type = /obj/item/ammo_box/magazine/akm/banana + +/datum/opposing_force_equipment/ammo/mp40 + item_type = /obj/item/ammo_box/magazine/mp40 + description = "A thirty-two round magazine for the MP-40. Uses 9x19mm ammunition." + +/datum/opposing_force_equipment/ammo/pps + item_type = /obj/item/ammo_box/magazine/pps + +/datum/opposing_force_equipment/ammo/ppsh + item_type = /obj/item/ammo_box/magazine/ppsh + +/datum/opposing_force_equipment/ammo/stg + item_type = /obj/item/ammo_box/magazine/stg + +/datum/opposing_force_equipment/ammo/fg42 + item_type = /obj/item/ammo_box/magazine/fg42 + description = "A twenty round magazine for the FG-42. Uses 7.92×57mm ammunition." + +/datum/opposing_force_equipment/ammo/m9mm + item_type = /obj/item/ammo_box/magazine/uzim9mm + description = "A thirty-two round magazine for the mini uzi. Uses 9x19mm ammunition." + +/datum/opposing_force_equipment/ammo/mg34 + item_type = /obj/item/ammo_box/magazine/mg34 + +/datum/opposing_force_equipment/ammo/cm762 + item_type = /obj/item/ammo_box/magazine/cm762 + description = "7.62 bullets in a ten round magazine for Cantanheim 7.62 rifle." + +/datum/opposing_force_equipment/ammo/a357 + item_type = /obj/item/ammo_box/a357 + description = "A seven-round .357 magnum speedloader for a revolver." + +/datum/opposing_force_equipment/ammo/a357match + item_type = /obj/item/ammo_box/a357/match + description = "A seven-round .357 magnum speedloader for a revolver, loaded with match-grade ammunition that bounces off walls several times." + +/datum/opposing_force_equipment/ammo/cfa_snub + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_snub + +/datum/opposing_force_equipment/ammo/cfa_snubap + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_snub/ap + +/datum/opposing_force_equipment/ammo/cfa_snubrubber + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_snub/rubber + +/datum/opposing_force_equipment/ammo/cfa_snubincendiary + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_snub/incendiary + +/datum/opposing_force_equipment/ammo/cfa_ruby + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_ruby + +/datum/opposing_force_equipment/ammo/cfa_rubyap + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_ruby/ap + +/datum/opposing_force_equipment/ammo/cfa_rubyrubber + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_ruby/rubber + +/datum/opposing_force_equipment/ammo/cfa_rubyhp + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_ruby/hp + +/datum/opposing_force_equipment/ammo/cfa_rubyincendiary + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_ruby/incendiary + +/datum/opposing_force_equipment/ammo/wildcat + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_wildcat + +/datum/opposing_force_equipment/ammo/wildcatap + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_wildcat/ap + +/datum/opposing_force_equipment/ammo/wildcatrubber + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_wildcat/rubber + +/datum/opposing_force_equipment/ammo/wildcatincendiary + item_type = /obj/item/ammo_box/magazine/multi_sprite/cfa_wildcat/incendiary +/** + * MELEE WEAPONS + */ +/datum/opposing_force_equipment/melee + category = OPFOR_EQUIPMENT_CATEGORY_MELEE_WEAPONS + +/datum/opposing_force_equipment/melee/switchblade + item_type = /obj/item/switchblade + +/datum/opposing_force_equipment/melee/metalbat + item_type = /obj/item/melee/baseball_bat/ablative + description = "A highly reflective baseball bat for when you need to crack skulls and run away from security in the same night." + +/datum/opposing_force_equipment/melee/esword + item_type = /obj/item/melee/energy/sword + description = "The energy sword is an edged weapon with a blade of pure energy. The sword is small enough to be \ + pocketed when inactive. Activating it produces a loud, distinctive noise." + +/datum/opposing_force_equipment/melee/epirate + item_type = /obj/item/melee/energy/sword/pirate + description = "A variant of the energy sword styled as a cutlass. Pirate costume sold separately. Warranty void if bought." + +/datum/opposing_force_equipment/melee/edagger + item_type = /obj/item/pen/edagger + name = "Energy Dagger" + description = "A dagger made of energy that looks and functions as a pen when off." + +/datum/opposing_force_equipment/melee/cutlass + item_type = /obj/item/claymore/cutlass + +/datum/opposing_force_equipment/melee/claymore + item_type = /obj/item/claymore + description = "An extremely sharp and robust sword perfect to cleave through any opposition. Also highly illegal." + +/datum/opposing_force_equipment/melee/katana + item_type = /obj/item/katana + description = "An extremely sharp and robust sword folded over nine thousand times until perfection. Highly lethal and illegal." + +/datum/opposing_force_equipment/melee/ekatana + item_type = /obj/item/energy_katana + description = "An energy katana seized from a dead Spider Clan ninja. As well as being highly robust, it allows the \ + user to teleport short distances using right-click." + +/datum/opposing_force_equipment/melee/eswordarm + item_type = /obj/item/autosurgeon/organ/syndicate/esword_arm + name = "Energy Sword Arm Implant" + description = "It's an energy sword, in your arm. Pretty decent for getting past stop-searches and assassinating people. Comes loaded in a Syndicate brand autosurgeon to boot!" + +/datum/opposing_force_equipment/melee/powerfist + item_type = /obj/item/melee/powerfist + name = "Power Fist" + description = "The power-fist is a metal gauntlet with a built-in piston-ram powered by an external gas supply.\ + Upon hitting a target, the piston-ram will extend forward to make contact for some serious damage. \ + Using a wrench on the piston valve will allow you to tweak the amount of gas used per punch to \ + deal extra damage and hit targets further. Use a screwdriver to take out any attached tanks." + +//martialarts + +/datum/opposing_force_equipment/melee/cqc + item_type = /obj/item/book/granter/martial/cqc + description = "A manual that teaches a single user tactical Close-Quarters Combat before self-destructing." + +/datum/opposing_force_equipment/melee/carp + item_type = /obj/item/book/granter/martial/carp + description = "This scroll contains the secrets of an ancient martial arts technique. You will master unarmed combat \ + and gain the ability to swat bullets from the air, but you will also refuse to use dishonorable ranged weaponry." + +/datum/opposing_force_equipment/melee/kravmaga + item_type = /obj/item/clothing/gloves/krav_maga/combatglovesplus //yes its a glove but it exists to give krav maga martial arts so its here + +/** + * ARMOR AND CLOTHES + */ +/datum/opposing_force_equipment/clothing + category = OPFOR_EQUIPMENT_CATEGORY_CLOTHING + +/datum/opposing_force_equipment/clothing/syndiebag + item_type = /obj/item/storage/backpack/duffelbag/syndie + name = "Syndicate Brand Duffelbag" + description = "Thanks to being made from a lighter yet sturdier material, this duffelbag holds just the same as any other duffelbag without the speed penalty of its counterparts." + +/datum/opposing_force_equipment/clothing/vest + item_type = /obj/item/clothing/suit/armor/vest + description = "A basic armored vest, rated for all manner of protections." + +/datum/opposing_force_equipment/clothing/ballistic + item_type = /obj/item/clothing/suit/armor/bulletproof + description = "A bulletproof vest, for the aspiring warfighter." + +/datum/opposing_force_equipment/clothing/laser + item_type = /obj/item/clothing/suit/armor/laserproof + description = "A laserproof vest, for the aspiring bane of security." + +/datum/opposing_force_equipment/clothing/heavy + item_type = /obj/item/clothing/suit/armor/heavy + description = "A superheavy armor suit purpose-built to ensure all injuries are pushovers. WARNING: Not spaceproof." + +/datum/opposing_force_equipment/clothing/infiltrator + name = "Infiltrator Gear" + item_type = /obj/item/storage/toolbox/infiltrator + description = "A box of equipment specially made for an infiltration expert, including sound-insulated boots, nanochip apprehension gloves, \ + and a voice-masking balaclava. Too bad it's made in such a red color..." + +/datum/opposing_force_equipment/clothing/helmet + item_type = /obj/item/clothing/head/helmet/swat + description = "A red-striped SWAT helmet. More robust than the standard-issue Nanotrasen security issue helmet, and spaceproof to boot." + +/datum/opposing_force_equipment/clothing/syndierig + item_type = /obj/item/storage/belt/military + +/datum/opposing_force_equipment/clothing/assaultbelt + item_type = /obj/item/storage/belt/military/assault + +/datum/opposing_force_equipment/clothing/bandolier + item_type = /obj/item/storage/belt/bandolier + +/datum/opposing_force_equipment/clothing/grenades + item_type = /obj/item/storage/belt/grenade + description = "A belt for holding grenades. Does not come with grenades unfortunately." + +//NRI larping equipment here + +/datum/opposing_force_equipment/clothing/nrihelm + item_type = /obj/item/clothing/head/helmet/rus_helmet/nri + +/datum/opposing_force_equipment/clothing/nriberet + item_type = /obj/item/clothing/head/beret/sec/nri + name = "NRI Commander Beret" + description = "An armored beret worn by high-ranking NRI officers" + +/datum/opposing_force_equipment/clothing/reduthelm + item_type = /obj/item/clothing/head/helmet/nri_heavy + description = "A specialized ultra-heavy composite ballistic helmet stolen from space russians. Purpose-built for heavy duty combat, \ + or murder with a frying pan." + +/datum/opposing_force_equipment/clothing/nrivest + item_type = /obj/item/clothing/suit/armor/vest/russian/nri + +/datum/opposing_force_equipment/clothing/redut + item_type = /obj/item/clothing/suit/armor/heavy/nri + +/datum/opposing_force_equipment/clothing/nrijumpsuit + item_type = /obj/item/clothing/under/costume/nri + +/datum/opposing_force_equipment/clothing/nribelt + item_type = /obj/item/storage/belt/military/nri + +//end of NRI larp equipment + +/datum/opposing_force_equipment/clothing/holster + item_type = /obj/item/storage/belt/holster/chameleon + description = "A chameleon holster that fits into your belt. Designed to hold one sidearm and a spare load of ammunition, it also allows you to spin \ + your revolver, if you have one." + +/datum/opposing_force_equipment/clothing/holsternk + item_type = /obj/item/storage/belt/holster/nukie + description = "A holster retrieved from a nuclear operative. Able to hold any two types of weaponry or ammo as long as it is not extremely large. \ + Expect security to be not so friendly if they see you wearing one of these..." + +//modsuits in 2022 + +/datum/opposing_force_equipment/clothing/basicmod + item_type = /obj/item/mod/control/pre_equipped/advanced + +/datum/opposing_force_equipment/clothing/syndiemod + item_type = /obj/item/mod/control/pre_equipped/traitor + +/datum/opposing_force_equipment/clothing/nukiemod + item_type = /obj/item/mod/control/pre_equipped/nuclear + +/datum/opposing_force_equipment/clothing/elitemod + item_type = /obj/item/mod/control/pre_equipped/elite + +/** + * UTILITY ITEMS + */ +/datum/opposing_force_equipment/gear/ + category = OPFOR_EQUIPMENT_CATEGORY_UTILITY + +/datum/opposing_force_equipment/gear/stoolbox + item_type = /obj/item/storage/toolbox/syndicate + description = "A fully-kitted toolbox scavenged from maintenance by our highly-paid monkeys. The toolbox \ + itself is weighted especially to bash any head in and comes with a free pair of insulated combat gloves." + +/datum/opposing_force_equipment/gear/engichip + item_type = /obj/item/skillchip/job/engineer + description = "A skillchip that, when installed, allows the user to recognise airlock and APC wire layouts and understand their functionality at a glance. Highly valuable and sought after." + +/datum/opposing_force_equipment/gear/roboticist + item_type = /obj/item/skillchip/job/roboticist + description = "A skillchip that, when installed, allows the user to recognise cyborg wire layouts and understand their functionality at a glance." + +/datum/opposing_force_equipment/gear/tacticool + item_type = /obj/item/skillchip/chameleon/reload + +/datum/opposing_force_equipment/gear/thermalgoggles + item_type = /obj/item/clothing/glasses/thermal + description = "A pair of thermal goggles. Cannot be chameleon disguised." + +/datum/opposing_force_equipment/gear/xraygoggles + item_type = /obj/item/clothing/glasses/thermal/xray + description = "A pair of low-light x-ray goggles manufactured by the Syndicate. Cannot be chameleon disguised. Makes wearer more vulnerable to bright lights." + +/datum/opposing_force_equipment/gear/thermalgogglessyndi + item_type = /obj/item/clothing/glasses/thermal/syndi + description = "A Syndicate take on the classic thermal goggles, complete with chameleon disguise functionality." + +/datum/opposing_force_equipment/gear/cloakerbelt + item_type = /obj/item/shadowcloak + description = "A belt that allows its wearer to temporarily turn invisible. Only recharges in dark areas. Use wisely." + +/datum/opposing_force_equipment/gear/projector + item_type = /obj/item/chameleon + description = "A projector that allows its user to turn into any scanned object. Pairs well with a cluttered room and ambush weapon." + +/datum/opposing_force_equipment/gear/box + item_type = /obj/item/implanter/stealth + description = "An implanter that grants you the ability to wield the ultimate in invisible box technology. Best used in conjunction with \ + a tape recorder playing Snake Eater." + +/datum/opposing_force_equipment/gear/sechud + item_type = /obj/item/clothing/glasses/hud/security/chameleon + description = "A stolen Security HUD refitted with chameleon technology. Provides flash protection." + +/datum/opposing_force_equipment/gear/aidetector + item_type = /obj/item/multitool/ai_detect + description = "A multitool that lets you see the AI's vision cone with an overlaid HUD and know if you're being watched." + +/datum/opposing_force_equipment/gear/noslip + item_type = /obj/item/clothing/shoes/chameleon/noslip + description = "No-slip chameleon shoes, for when you plan on running through hell and back." + +/datum/opposing_force_equipment/gear/cloakmod + item_type = /obj/item/mod/module/stealth/ninja + description = "An upgraded MODsuit cloaking module stolen from the Spider Clan's finest. Consumes less power than the standard, but is obviously illegal." + +/datum/opposing_force_equipment/gear/suppressor + item_type = /obj/item/suppressor + +/datum/opposing_force_equipment/gear/extendedrag + item_type = /obj/item/reagent_containers/glass/rag/large + description = "A damp rag made with extra absorbant materials. The perfectly innocent tool to kidnap your local assistant. \ + Apply up to 30u liquids and use combat mode to smother anyone not covering their mouth." + +/datum/opposing_force_equipment/gear/nodrop + item_type = /obj/item/autosurgeon/organ/syndicate/nodrop + name = "Anti Drop Implant" + description = "An implant that prevents you from dropping items in your hand involuntarily. Comes loaded in a syndicate autosurgeon" + +/datum/opposing_force_equipment/gear/hackerman + item_type = /obj/item/autosurgeon/organ/syndicate/hackerman + name = "Hacking Arm Implant" + description = "An advanced arm implant that comes with cutting edge hacking tools. Perfect for the cybernetically enhanced wirerunners." + +/** + * EXPLOSIVES + */ +/datum/opposing_force_equipment/bomb + category = OPFOR_EQUIPMENT_CATEGORY_EXPLOSIVES + +/datum/opposing_force_equipment/bomb/henade + item_type = /obj/item/grenade/syndieminibomb/concussion + description = "A grenade intended to concuss and incapacitate enemies. Still rather explosive." + +/datum/opposing_force_equipment/bomb/fragnade + item_type = /obj/item/grenade/frag + description = "A fragmentation grenade that looses pieces of shrapnel after detonating for maximum injury." + +/datum/opposing_force_equipment/bomb/radnade + item_type = /obj/item/grenade/gluon + description = "A prototype grenade that freezes the target area and unleashes a wave of deadly radiation." + +/datum/opposing_force_equipment/bomb/c4 + item_type = /obj/item/grenade/c4 + description = "A brick of plastic explosives, for breaking open walls, doors, and optionally people." + +/datum/opposing_force_equipment/bomb/x4 + item_type = /obj/item/grenade/c4/x4 + description = "Similar to C4, but with a stronger blast that is directional instead of circular." + +/** + * LOADOUTS + */ +/datum/opposing_force_equipment/loadout + category = OPFOR_EQUIPMENT_CATEGORY_LOADOUT + +/datum/opposing_force_equipment/loadout/gunman + item_type = /obj/item/storage/box/syndie_kit/gunman_outfit + +/datum/opposing_force_equipment/loadout/grenadier + item_type = /obj/item/storage/belt/grenade/full + name = "Grenadier Kit" + description = "A belt full of grenades and bombs. May gods have mercy upon us if they approve this." + +/** + * OTHER + */ +/datum/opposing_force_equipment/other + category = OPFOR_EQUIPMENT_CATEGORY_OTHER + +/datum/opposing_force_equipment/other/uplink + item_type = /obj/item/uplink/opfor + name = "Syndicate Uplink" + description = "An old-school syndicate uplink without a password and an empty TC account. Perfect for the aspiring operatives." + +/datum/opposing_force_equipment/other/tc1 + item_type = /obj/item/stack/telecrystal + name = "1 Raw Telecrystal" + description = "A telecrystal in its rawest and purest form; can be utilized on active uplinks to increase their telecrystal count." + +/datum/opposing_force_equipment/other/tc5 + item_type = /obj/item/stack/telecrystal/five + name = "5 Raw Telecrystals" + description = "A bunch of telecrystals in their rawest and purest form; can be utilized on active uplinks to increase their telecrystal count." + +/datum/opposing_force_equipment/other/tc20 + item_type = /obj/item/stack/telecrystal/twenty + name = "20 Raw Telecrystals" + description = "A bundle of telecrystals in their rawest and purest form; can be utilized on active uplinks to increase their telecrystal count." + +/datum/opposing_force_equipment/other/cashcase + item_type = /obj/item/storage/secure/briefcase/syndie + name = "Syndicate Briefcase Full of Cash" + description = "A secure briefcase containing 5000 space credits. Useful for bribing personnel, or purchasing goods \ + and services at lucrative prices. The briefcase also feels a little heavier to hold; it has been \ + manufactured to pack a little bit more of a punch if your client needs some convincing." + +/datum/opposing_force_equipment/other/c10k + name = "10000 Space Cash Bill" + item_type = /obj/item/stack/spacecash/c10000 + description = "Cold hard cash. When you REALLY need to bribe or buy your way in. Or to payroll your gangmembers." diff --git a/modular_skyrat/modules/opposing_force/code/opposing_force_subsystem.dm b/modular_skyrat/modules/opposing_force/code/opposing_force_subsystem.dm new file mode 100644 index 00000000000..fa98138fdc2 --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/opposing_force_subsystem.dm @@ -0,0 +1,196 @@ +SUBSYSTEM_DEF(opposing_force) + name = "Opposing Force" + flags = SS_NO_FIRE + + /// A precompiled list of all equipment datums, processed on init + var/list/equipment_list = list() + /// A list of all currently active objectives + var/list/unsubmitted_applications = list() + /// A list of all currently submitted objectives + var/list/submitted_applications = list() + /// A list of all approved applications + var/list/approved_applications = list() + /// The max amount of objectives that can be tracked + var/max_objectives = 5 + /// Are we allowing players to make objectives? + var/accepting_objectives = TRUE + /// The status of the subsystem + var/status = OPFOR_SUBSYSTEM_READY + +/datum/controller/subsystem/opposing_force/stat_entry(msg) + msg = "UNSUB: [LAZYLEN(unsubmitted_applications)] | SUB: [LAZYLEN(submitted_applications)] | APPR: [LAZYLEN(approved_applications)]" + return ..() + +/datum/controller/subsystem/opposing_force/Initialize(start_timeofday) + for(var/datum/opposing_force_equipment/opfor_equipment as anything in subtypesof(/datum/opposing_force_equipment)) + // Set up our categories so we can add items to them + if(initial(opfor_equipment.category)) + var/category = initial(opfor_equipment.category) + if(!(category in equipment_list)) + // We instansiate the category list so we can add items to it later + equipment_list[category] = list() + // These can be considered abstract types, thus do not need to be added. + if(isnull(initial(opfor_equipment.item_type))) + continue + var/datum/opposing_force_equipment/spawned_opfor_equipment = new opfor_equipment() + // Datums without a name will assume the items name + spawned_opfor_equipment.name ||= initial(spawned_opfor_equipment.item_type.name) + // ditto for the description + spawned_opfor_equipment.description ||= initial(spawned_opfor_equipment.item_type.desc) + // Now that we've set up our datum, we can add it to the correct category + if(spawned_opfor_equipment.category) + // We have a category, let's add it to the associated list + equipment_list[spawned_opfor_equipment.category] += spawned_opfor_equipment + else + // Because of how the UI system works, categories cannot exist with nothing in them, so we + // only set the OTHER category if something can go inside it! + if(!(OPFOR_EQUIPMENT_CATEGORY_OTHER in equipment_list)) + equipment_list[OPFOR_EQUIPMENT_CATEGORY_OTHER] = list() + // We don't have home :( add us to the other category. + equipment_list[OPFOR_EQUIPMENT_CATEGORY_OTHER] += spawned_opfor_equipment + return ..() + +/datum/controller/subsystem/opposing_force/proc/check_availability() + if(get_current_applications() >= max_objectives) + status = OPFOR_SUBSYSTEM_REJECT_CAP + if(!accepting_objectives) + status = OPFOR_SUBSYSTEM_REJECT_CLOSED + status = OPFOR_SUBSYSTEM_READY + return status + +/datum/controller/subsystem/opposing_force/proc/close_objectives() + accepting_objectives = FALSE + +/datum/controller/subsystem/opposing_force/proc/get_queue_position(datum/opposing_force/opposing_force) + if(!(opposing_force in submitted_applications)) + return "ERROR" + var/position = 1 + for(var/opfor as anything in submitted_applications) + if(opposing_force == opfor) + break + position++ + return position + +/datum/controller/subsystem/opposing_force/proc/add_to_queue(datum/opposing_force/opposing_force) + if(!LAZYFIND(unsubmitted_applications, opposing_force)) + CRASH("Opposing_force_subsystem: Attempted to add an opposing force to the queue but it was not registered to the subsystem!") + + submitted_applications += opposing_force + unsubmitted_applications -= opposing_force + + return LAZYLEN(submitted_applications) + +/datum/controller/subsystem/opposing_force/proc/broadcast_queue_change(datum/opposing_force/updating_opposing_force) + for(var/datum/opposing_force/opposing_force in submitted_applications) + if(opposing_force == updating_opposing_force) + continue + opposing_force.broadcast_queue_change() + +/datum/controller/subsystem/opposing_force/proc/approve(datum/opposing_force/opposing_force, mob/approver) + if(!is_admin(approver.client)) + message_admins("Oppoding_force_subsystem: [ADMIN_LOOKUPFLW(approver)] attempted to approve an OPFOR application but was not an admin!") + CRASH("Opposing_force_subsystem: Attempted to approve an opposing force but the approver ([approver?.ckey]) was not an admin!") + + if(!LAZYFIND(unsubmitted_applications, opposing_force)) + unsubmitted_applications -= opposing_force + + if(LAZYFIND(submitted_applications, opposing_force)) + submitted_applications -= opposing_force + + if(LAZYFIND(approved_applications, opposing_force)) + return + + approved_applications += opposing_force + + opposing_force.approve(approver) + + broadcast_queue_change(opposing_force) + + return TRUE + +/datum/controller/subsystem/opposing_force/proc/deny(datum/opposing_force/opposing_force, reason, mob/denier) + if(!is_admin(denier.client)) + message_admins("Oppoding_force_subsystem: [ADMIN_LOOKUPFLW(denier)] attempted to deny an OPFOR application but was not an admin!") + CRASH("Opposing_force_subsystem: Attempted to deny an opposing force but the denier ([denier?.ckey]) was not an admin!") + + if(LAZYFIND(submitted_applications, opposing_force)) + submitted_applications -= opposing_force + + if(LAZYFIND(approved_applications, opposing_force)) + approved_applications -= opposing_force + + if(!LAZYFIND(unsubmitted_applications, opposing_force)) + unsubmitted_applications += opposing_force + + opposing_force.deny(denier, reason) + + broadcast_queue_change(opposing_force) + + return TRUE + +/datum/controller/subsystem/opposing_force/proc/modify_request(datum/opposing_force/opposing_force, changes) + if(LAZYFIND(submitted_applications, opposing_force)) + submitted_applications -= opposing_force + + if(LAZYFIND(approved_applications, opposing_force)) + approved_applications -= opposing_force + + if(!LAZYFIND(unsubmitted_applications, opposing_force)) + unsubmitted_applications += opposing_force + + broadcast_queue_change(opposing_force) + +/datum/controller/subsystem/opposing_force/proc/get_current_applications() + return LAZYLEN(submitted_applications) + LAZYLEN(approved_applications) + +/datum/controller/subsystem/opposing_force/proc/new_opfor(datum/opposing_force/opposing_force) + unsubmitted_applications += opposing_force + +/datum/controller/subsystem/opposing_force/proc/remove_opfor(datum/opposing_force/opposing_force) + if(LAZYFIND(unsubmitted_applications, opposing_force)) + unsubmitted_applications -= opposing_force + if(LAZYFIND(submitted_applications, opposing_force)) + submitted_applications -= opposing_force + if(LAZYFIND(approved_applications, opposing_force)) + approved_applications -= opposing_force + + broadcast_queue_change() + +/datum/controller/subsystem/opposing_force/proc/unsubmit_opfor(datum/opposing_force/opposing_force) + if(LAZYFIND(approved_applications, opposing_force)) + approved_applications -= opposing_force + if(LAZYFIND(submitted_applications, opposing_force)) + submitted_applications -= opposing_force + if(!LAZYFIND(unsubmitted_applications, opposing_force)) + unsubmitted_applications += opposing_force + + + broadcast_queue_change() + +/datum/controller/subsystem/opposing_force/proc/view_opfor(datum/opposing_force/opposing_force, mob/viewer) + if(!is_admin(viewer.client)) + message_admins("Oppoding_force_subsystem: [ADMIN_LOOKUPFLW(viewer)] attempted to view an OPFOR application but was not an admin!") + CRASH("Opposing_force_subsystem: Attempted to view an opposing force but the viewer was not an admin!") + + opposing_force.ui_interact(viewer) + +/datum/controller/subsystem/opposing_force/proc/get_check_antag_listing() + var/list/returned_html = list("
") + + returned_html += "OPFOR Applications" + + returned_html += "Submitted - FOLLOW QUEUE!" + var/queue_count = 1 + for(var/datum/opposing_force/opposing_force in submitted_applications) + returned_html += " - [queue_count]. [opposing_force.build_html_panel_entry()]" + queue_count++ + + returned_html += "Approved" + for(var/datum/opposing_force/opposing_force in approved_applications) + returned_html += " - [opposing_force.build_html_panel_entry()]" + + returned_html += "Unsubmitted" + for(var/datum/opposing_force/opposing_force in unsubmitted_applications) + returned_html += " - [opposing_force.build_html_panel_entry()]" + + return returned_html.Join("
") diff --git a/modular_skyrat/modules/opposing_force/code/roundend.dm b/modular_skyrat/modules/opposing_force/code/roundend.dm new file mode 100644 index 00000000000..07b81ff4e8a --- /dev/null +++ b/modular_skyrat/modules/opposing_force/code/roundend.dm @@ -0,0 +1,14 @@ +/datum/controller/subsystem/ticker/proc/opfor_report() + var/list/result = list() + + result += "
Opposing Force Report:
" + + if(!SSopposing_force.approved_applications.len) + result += span_red("No applications were approved.") + else + for(var/datum/opposing_force/opfor in SSopposing_force.approved_applications) + result += opfor.roundend_report() + + result += "
" + + return result.Join() diff --git a/modular_skyrat/modules/opposing_force/sound/application_recieved.ogg b/modular_skyrat/modules/opposing_force/sound/application_recieved.ogg new file mode 100644 index 00000000000..2ddef0c0b02 Binary files /dev/null and b/modular_skyrat/modules/opposing_force/sound/application_recieved.ogg differ diff --git a/modular_skyrat/modules/opposing_force/sound/approved.ogg b/modular_skyrat/modules/opposing_force/sound/approved.ogg new file mode 100644 index 00000000000..9b3a1fbf686 Binary files /dev/null and b/modular_skyrat/modules/opposing_force/sound/approved.ogg differ diff --git a/modular_skyrat/modules/opposing_force/sound/denied.ogg b/modular_skyrat/modules/opposing_force/sound/denied.ogg new file mode 100644 index 00000000000..627c1f3c847 Binary files /dev/null and b/modular_skyrat/modules/opposing_force/sound/denied.ogg differ diff --git a/modular_skyrat/modules/opposing_force/sound/update_requested.ogg b/modular_skyrat/modules/opposing_force/sound/update_requested.ogg new file mode 100644 index 00000000000..bf8b05099c4 Binary files /dev/null and b/modular_skyrat/modules/opposing_force/sound/update_requested.ogg differ diff --git a/modular_skyrat/modules/sec_haul/code/guns/guns.dm b/modular_skyrat/modules/sec_haul/code/guns/guns.dm index 4c0c07501cc..72d42b45c64 100644 --- a/modular_skyrat/modules/sec_haul/code/guns/guns.dm +++ b/modular_skyrat/modules/sec_haul/code/guns/guns.dm @@ -693,7 +693,7 @@ /obj/item/gun/ballistic/shotgun/m23 name = "\improper Model 23-37" - desc = "A common outdated police shotgun sporting a six-round tube." + desc = "An outdated police shotgun sporting an eight-round tube." icon_state = "riotshotgun" inhand_icon_state = "shotgun" mag_type = /obj/item/ammo_box/magazine/internal/shot/m23 @@ -704,11 +704,11 @@ name = "m23 shotgun internal magazine" caliber = CALIBER_SHOTGUN ammo_type = /obj/item/ammo_casing/shotgun/beanbag - max_ammo = 6 + max_ammo = 8 /obj/item/gun/ballistic/shotgun/automatic/as2 name = "\improper M2 Auto-Shotgun" - desc = "An automatic shotgun with a four round internal tube." + desc = "A semi-automatic shotgun with a four-round internal tube." icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns.dmi' icon_state = "as2" worn_icon = 'modular_skyrat/modules/aesthetics/guns/icons/guns_back.dmi' @@ -730,12 +730,12 @@ /obj/item/ammo_box/magazine/internal/shot/as2 name = "shotgun internal magazine" caliber = CALIBER_SHOTGUN - ammo_type = /obj/item/ammo_casing/shotgun/beanbag + ammo_type = /obj/item/ammo_casing/shotgun max_ammo = 4 /obj/item/gun/ballistic/shotgun/sas14 name = "\improper SAS-14" - desc = "A pump action shotgun with a five round box magazine." + desc = "A semi-automatic shotgun with a five-round box magazine." icon = 'modular_skyrat/modules/sec_haul/icons/guns/sas14.dmi' icon_state = "sas14" inhand_icon_state = "shotgun" @@ -752,7 +752,7 @@ weapon_weight = WEAPON_MEDIUM burst_size = 1 fire_select_modes = list(SELECT_SEMI_AUTOMATIC) - semi_auto = FALSE + semi_auto = TRUE internal_magazine = FALSE armadyne = TRUE @@ -764,3 +764,20 @@ caliber = CALIBER_14GAUGE ammo_type = /obj/item/ammo_casing/s14gauge max_ammo = 5 + +//New SAS14 mags beyond here + +/obj/item/ammo_box/magazine/sas14/empty + start_empty = TRUE + +/obj/item/ammo_box/magazine/sas14/beanbag + ammo_type = /obj/item/ammo_casing/s14gauge/beanbag + +/obj/item/ammo_box/magazine/sas14/buckshot + ammo_type = /obj/item/ammo_casing/s14gauge/buckshot + +/obj/item/ammo_box/magazine/sas14/rubbershot + ammo_type = /obj/item/ammo_casing/s14gauge/rubbershot + +/obj/item/ammo_box/magazine/sas14/stunslug + ammo_type = /obj/item/ammo_casing/s14gauge/stunslug diff --git a/modular_skyrat/modules/ticket_counter/code/counter.dm b/modular_skyrat/modules/ticket_counter/code/counter.dm index e8a6ca87cbc..a669b3b278d 100644 --- a/modular_skyrat/modules/ticket_counter/code/counter.dm +++ b/modular_skyrat/modules/ticket_counter/code/counter.dm @@ -10,13 +10,15 @@ GLOBAL_LIST_INIT(ticket_counter, list()) /datum/admin_help_tickets/stat_entry() var/list/L = ..() L[++L.len] = list(null, null, null, null) - L[++L.len] = list("Ticket statistics", null, null, null) - L[++L.len] = list("Admin:", "Tickets handled:", null, null) + L[++L.len] = list("Ticket/OPFOR statistics", null, null, null) + L[++L.len] = list("Admin:", "Tickets/OPFOR handled:", null, null) for(var/ckey in GLOB.ticket_counter) //assumption, that there's no keys with empty values L[++L.len] = list("[ckey]", "[GLOB.ticket_counter[ckey]]", null, null) + L[++L.len] = list("OPFOR:", "UNSUB: [LAZYLEN(SSopposing_force.unsubmitted_applications)] | SUB: [LAZYLEN(SSopposing_force.submitted_applications)] | APPR: [LAZYLEN(SSopposing_force.approved_applications)]", null, null) + return L /datum/admin_help diff --git a/tgstation.dme b/tgstation.dme index 84bed840408..8745fdee767 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -272,6 +272,7 @@ #include "code\__DEFINES\~skyrat_defines\mining_traits.dm" #include "code\__DEFINES\~skyrat_defines\mobs.dm" #include "code\__DEFINES\~skyrat_defines\obj_flags.dm" +#include "code\__DEFINES\~skyrat_defines\opposing_force_defines.dm" #include "code\__DEFINES\~skyrat_defines\pollution.dm" #include "code\__DEFINES\~skyrat_defines\projectiles.dm" #include "code\__DEFINES\~skyrat_defines\reskin_defines.dm" @@ -4260,6 +4261,7 @@ #include "modular_skyrat\master_files\code\datums\traits\negative.dm" #include "modular_skyrat\master_files\code\datums\traits\neutral.dm" #include "modular_skyrat\master_files\code\game\effects\spawners\random\structure.dm" +#include "modular_skyrat\master_files\code\game\gamemodes\objective.dm" #include "modular_skyrat\master_files\code\game\machinery\doors\firedoor.dm" #include "modular_skyrat\master_files\code\game\objects\items.dm" #include "modular_skyrat\master_files\code\game\objects\items\airlock_painter.dm" @@ -5121,6 +5123,13 @@ #include "modular_skyrat\modules\novaya_ert\code\uniform.dm" #include "modular_skyrat\modules\officestuff\code\officestuff.dm" #include "modular_skyrat\modules\oneclickantag\code\oneclickantag.dm" +#include "modular_skyrat\modules\opposing_force\code\admin_procs.dm" +#include "modular_skyrat\modules\opposing_force\code\items.dm" +#include "modular_skyrat\modules\opposing_force\code\mind.dm" +#include "modular_skyrat\modules\opposing_force\code\opposing_force_datum.dm" +#include "modular_skyrat\modules\opposing_force\code\opposing_force_equipment.dm" +#include "modular_skyrat\modules\opposing_force\code\opposing_force_subsystem.dm" +#include "modular_skyrat\modules\opposing_force\code\roundend.dm" #include "modular_skyrat\modules\organs\code\lungs.dm" #include "modular_skyrat\modules\oversized\code\door.dm" #include "modular_skyrat\modules\oversized\code\oversized_quirk.dm" diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss index 761cb45491a..845a49ea33c 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss @@ -997,3 +997,8 @@ em { color: #ff0000; font-weight: bold; } + +.pink { + color: #ff00ff; + font-weight: bold; +} diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/goon/chat-light.scss index 31986f469aa..f9291341392 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-light.scss @@ -953,3 +953,8 @@ h1.alert, h2.alert { color: #ff0000; font-weight: bold; } + +.pink { + color: #ff00ff; + font-weight: bold; +} diff --git a/tgui/packages/tgui/interfaces/OpposingForcePanel.js b/tgui/packages/tgui/interfaces/OpposingForcePanel.js new file mode 100644 index 00000000000..998297bd1a8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/OpposingForcePanel.js @@ -0,0 +1,777 @@ +import { round } from 'common/math'; +import { useBackend, useLocalState } from '../backend'; +import { Section, Stack, TextArea, Button, Tabs, Input, Slider, NoticeBox, LabeledList, Box, Collapsible, NumberInput } from '../components'; +import { Window } from '../layouts'; + +export const OpposingForcePanel = (props, context) => { + const [tab, setTab] = useLocalState(context, 'tab', 1); + const { act, data } = useBackend(context); + const { + admin_mode, + creator_ckey, + owner_antag, + } = data; + return ( + + + + + + {admin_mode ? ( + <> + setTab(1)}> + Admin Control + + setTab(2)}> + Admin Chat + + + ) : ( + <> + setTab(1)}> + Summary + + setTab(2)}> + Equipment + + setTab(3)}> + Admin Chat + + + )} + + + + {admin_mode ? ( + <> + {tab === 1 && ( + + )} + {tab === 2 && ( + + )} + + ) : ( + <> + {tab === 1 && ( + + )} + {tab === 2 && ( + + )} + {tab === 3 && ( + + )} + + )} + + + ); +}; + +export const OpposingForceTab = (props, context) => { + const { act, data } = useBackend(context); + const { + creator_ckey, + objectives = [], + can_submit, + status, + can_request_update, + request_updates_muted, + can_edit, + backstory, + handling_admin, + blocked, + approved, + denied, + } = data; + return ( + + +
+ + +
+
+ +
+