diff --git a/code/__DEFINES/clans.dm b/code/__DEFINES/clans.dm index 576bbf6b76d5..312c6ac5b37a 100644 --- a/code/__DEFINES/clans.dm +++ b/code/__DEFINES/clans.dm @@ -43,6 +43,7 @@ #define CLAN_RANK_ELDER_INT 5 #define CLAN_RANK_LEADER_INT 6 #define CLAN_RANK_ADMIN_INT 7 +#define CLAN_RANK_SUPERADMIN_INT 10 /// Hard limit #define CLAN_LIMIT_NUMBER 1 @@ -91,3 +92,12 @@ ) #define CLAN_SHIP_PUBLIC -1 + +#define CLAN_ANCILLARY_NONE "None" +#define CLAN_ANCILLARY_ENFORCER "Enforcer" +#define CLAN_ANCILLARY_HOUND_MASTER "Hound Master" +#define CLAN_ANCILLARY_TASK_MASTER "Task Master" +#define CLAN_ANCILLARY_ADJUTANT "Adjutant" +#define CLAN_ANCILLARY_SHAMAN "Shaman" +#define CLAN_ANCILLARY_HIGH_ENFORCER "High Enforcer" +#define CLAN_ANCILLARY_HIGH_SHAMAN "High Shaman" diff --git a/code/_globalvars/lists/clans.dm b/code/_globalvars/lists/clans.dm index f04915a37435..8fe4fe3e23b1 100644 --- a/code/_globalvars/lists/clans.dm +++ b/code/_globalvars/lists/clans.dm @@ -17,3 +17,14 @@ GLOBAL_LIST_INIT(clan_ranks_ordered, list( CLAN_RANK_LEADER = CLAN_RANK_LEADER_INT, CLAN_RANK_ADMIN = CLAN_RANK_ADMIN_INT )) + +GLOBAL_LIST_INIT_TYPED(clan_ancillaries, /datum/yautja_ancillary, list( + CLAN_ANCILLARY_ENFORCER = new /datum/yautja_ancillary/enforcer(), + CLAN_ANCILLARY_ADJUTANT = new /datum/yautja_ancillary/adjutant(), + CLAN_ANCILLARY_HOUND_MASTER = new /datum/yautja_ancillary/hound_master(), + CLAN_ANCILLARY_TASK_MASTER = new /datum/yautja_ancillary/task_master(), + CLAN_ANCILLARY_SHAMAN = new /datum/yautja_ancillary/shaman(), + CLAN_ANCILLARY_HIGH_ENFORCER = new /datum/yautja_ancillary/high_enforcer(), + CLAN_ANCILLARY_HIGH_SHAMAN = new /datum/yautja_ancillary/high_shaman(), + CLAN_ANCILLARY_NONE = new /datum/yautja_ancillary/clear() +)) diff --git a/code/datums/entities/clans.dm b/code/datums/entities/clans.dm index 916afd18c178..076a24fd371a 100644 --- a/code/datums/entities/clans.dm +++ b/code/datums/entities/clans.dm @@ -5,6 +5,8 @@ var/clan_id var/honor + var/player_name + var/clan_ancillary /datum/entity/clan var/name @@ -40,12 +42,15 @@ BSQL_PROTECT_DATUM(/datum/entity/clan) "permissions" = DB_FIELDTYPE_BIGINT, "clan_id" = DB_FIELDTYPE_BIGINT, "honor" = DB_FIELDTYPE_BIGINT, + "player_name" = DB_FIELDTYPE_STRING_MEDIUM, + "clan_ancillary" = DB_FIELDTYPE_STRING_MEDIUM, ) key_field = "player_id" /datum/entity_meta/clan_player/on_insert(datum/entity/clan_player/player) player.honor = 0 player.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_UNBLOODED] + player.clan_ancillary = CLAN_ANCILLARY_NONE player.permissions = GLOB.clan_ranks[CLAN_RANK_UNBLOODED].permissions player.save() @@ -76,6 +81,8 @@ BSQL_PROTECT_DATUM(/datum/entity/clan) var/permissions var/clan_name var/honor + var/player_name + var/clan_ancillary /datum/entity_view_meta/clan_players_view root_record_type = /datum/entity/clan_player @@ -88,7 +95,9 @@ BSQL_PROTECT_DATUM(/datum/entity/clan) "honor", "clan_player_id" = "id", "clan_name" = "clan.name", - "ckey" = "player.ckey" + "ckey" = "player.ckey", + "player_name", + "clan_ancillary", ) order_by = list("clan_rank" = DB_ORDER_BY_DESC) diff --git a/code/modules/clans/clan_tgui.dm b/code/modules/clans/clan_tgui.dm new file mode 100644 index 000000000000..2acf0caa9699 --- /dev/null +++ b/code/modules/clans/clan_tgui.dm @@ -0,0 +1,486 @@ +/datum/yautja_panel + var/client/linked_client + var/current_clan_index = 0 + + /// A list of all clan names, in the order they were populated. + var/list/clan_name_to_index = list() + /// A list of clan IDs, tied to the order the clan was populated. + var/list/clan_index_to_id = list() + + /// The permissions of the panel user. + var/user_rights = 0 + /// The ID of the user's clan. + var/user_clan_id + /// The rank held within the user's clan. + var/user_clan_rank + + /// The currently selected clan's OD + var/current_clan_id + + /// The global data. + var/list/global_data + /// Whether an early update of the global data is queued. + var/early_queued = FALSE + +GLOBAL_DATUM_INIT(yautja_clan_data, /datum/yautja_panel, new(init_global = TRUE)) + +/datum/yautja_panel/New(client/origin_client, init_global = FALSE) + . = ..() + if(origin_client) + linked_client = origin_client + user_rights = linked_client.clan_info.permissions + user_clan_id = linked_client.clan_info.clan_id + user_clan_rank = linked_client.clan_info.clan_rank + if(init_global) + addtimer(CALLBACK(src, PROC_REF(populate_global_clan_data), TRUE), 30 SECONDS) + +/client + var/datum/yautja_panel/yautja_panel + +/client/verb/yautja_panel() + set name = "Yautja Clan Panel" + set category = "OOC.Records" + + if(!check_whitelist_status(WHITELIST_PREDATOR)) + return FALSE + + if(!GLOB.yautja_clan_data.global_data) + to_chat(usr, SPAN_WARNING("Clan Data has not populated yet, please wait for up to 30 seconds.")) + return FALSE + + if(yautja_panel) + qdel(yautja_panel) + yautja_panel = new(src) + yautja_panel.tgui_interact(mob) + +/datum/yautja_panel/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "YautjaClans", "Yautja Clan Panel") + ui.open() + +/datum/yautja_panel/ui_state(mob/user) + return GLOB.always_state + +/datum/yautja_panel/ui_close(mob/user) + . = ..() + if(user?.client.yautja_panel) + qdel(user.client.yautja_panel) + +/datum/yautja_panel/vv_edit_var(var_name, var_value) + return FALSE + +/datum/yautja_panel/ui_data(mob/user) + var/list/data = GLOB.yautja_clan_data.global_data + + data["current_clan_index"] = current_clan_index + data["user_is_clan_leader"] = verify_clan_leader(current_clan_id) + data["user_is_clan_elder"] = verify_clan_elder(current_clan_id) + + data["user_is_council"] = verify_council() + data["user_is_superadmin"] = verify_superadmin() + + return data + +/datum/yautja_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = ui.user + var/data_reloader = TRUE + + if(action == "change_clan_list") + var/selected_clan = params["selected_clan"] + current_clan_index = GLOB.yautja_clan_data.clan_name_to_index[selected_clan] + current_clan_id = index_to_id(current_clan_index) + return TRUE + + if(!verify_clan_elder(current_clan_id)) + to_chat(user, SPAN_WARNING("You are not authorized to do this.")) + return FALSE + + var/datum/entity/clan_player/target_yautja + var/datum/entity/player/target_player + var/datum/entity/clan/target_clan + var/target_ckey + if(params["target_player"]) + target_yautja = GET_CLAN_PLAYER(params["target_player"]) + target_yautja.sync() + + log_debug("Yautja Clans: Selected Player Clan ID: [target_yautja.clan_id]") + + target_player = DB_ENTITY(/datum/entity/player, target_yautja.player_id) + target_player.sync() + target_ckey = target_player.ckey + + if(!verify_superadmin() && (target_ckey == user.ckey)) + to_chat(user, SPAN_WARNING("You cannot edit yourself!")) + return FALSE + + + if(params["target_clan"]) + target_clan = GET_CLAN(params["target_clan"]) + target_clan.sync() + + // ------------- CLAN ELDER+ ONLY ACTION ------------- \\ + + if(action == "assign_ancillary") + if(!(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY))) + if(!(target_yautja.clan_id == current_clan_id)) + to_chat(user, SPAN_WARNING("You cannot assign an ancillary title to this player, they are not in your clan!")) + log_debug("Target Clan: [target_yautja.clan_id], Needed Clan: [current_clan_id]") + return + + var/list/datum/yautja_ancillary/ancillaries = GLOB.clan_ancillaries.Copy() + + var/datum/yautja_ancillary/chosen_ancillary + if(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY, warn = FALSE)) + var/input = tgui_input_list(user, "Select the ancillary to change this user to.", "Select Ancillary", ancillaries) + + if(!input) + log_debug("Yautja Clans: Input Check failed.") + return + + chosen_ancillary = ancillaries[input] + + else if(linked_client.has_clan_position(CLAN_RANK_ELDER_INT, current_clan_id, TRUE)) + for(var/ancillary in ancillaries) + if(!linked_client.has_clan_position(ancillaries[ancillary].granter_title_required, warn = FALSE)) + ancillaries -= ancillary + //if(!target_yautja.can_be_ancillary(ancillaries[ancillary].target_rank_required, current_clan_id)) + // ancillaries -= ancillary + + if(!ancillaries.len) + to_chat(user, SPAN_WARNING("No possible ancillaries to grant!")) + return + + var/input = tgui_input_list(user, "Select the ancillary to change this user to.", "Select Ancillary", ancillaries) + + if(!input) + log_debug("Yautja Clans: Input Check failed.") + return + + chosen_ancillary = ancillaries[input] + + if(chosen_ancillary.limit_type) + var/list/datum/view_record/clan_playerbase_view/CPV = DB_VIEW(/datum/view_record/clan_playerbase_view/, DB_AND(DB_COMP("clan_id", DB_EQUALS, target_yautja.clan_id), DB_COMP("rank", DB_EQUALS, GLOB.clan_ranks_ordered[input]))) + var/players_in_rank = length(CPV) + + switch(chosen_ancillary.limit_type) + if(CLAN_LIMIT_NUMBER) + if(players_in_rank >= chosen_ancillary.limit) + to_chat(user, SPAN_DANGER("This slot is full! (Maximum of [chosen_ancillary.limit] slots)")) + return + if(CLAN_LIMIT_SIZE) + var/list/datum/view_record/clan_playerbase_view/clan_players = DB_VIEW(/datum/view_record/clan_playerbase_view/, DB_COMP("clan_id", DB_EQUALS, target_yautja.clan_id)) + var/available_slots = ceil(length(clan_players) / chosen_ancillary.limit) + + if(players_in_rank >= available_slots) + to_chat(user, SPAN_DANGER("This slot is full! (Maximum of [chosen_ancillary.limit] per player in the clan, currently [available_slots])")) + return + else + log_debug("Yautja Clans: X Check failed.") + return // Doesn't have permission to do this + + if(!linked_client.has_clan_position(chosen_ancillary.granter_title_required, target_yautja.clan_id)) // Double check + log_debug("Yautja Clans: Double Check failed.") + return + + target_yautja.clan_ancillary = chosen_ancillary.name + message_admins("[key_name_admin(user)] has set the ancillary title of [target_ckey] to [chosen_ancillary.name] for their clan.") + to_chat(user, SPAN_NOTICE("Set [target_ckey]'s ancillary title to [chosen_ancillary.name]")) + + + // ------------- CLAN LEADER ONLY ACTIONS ------------- \\ + + if(!verify_clan_leader(current_clan_id)) + to_chat(user, SPAN_WARNING("You are not authorized to do this.")) + return FALSE + + switch(action) + if("clan_name") + var/input = input(user, "Input the new name", "Set Name", target_clan.name) as text|null + + if(!input || input == target_clan.name) + return + + message_admins("[key_name_admin(user)] has set the name of [target_clan.name] to [input].") + to_chat(user, SPAN_NOTICE("Set the name of [target_clan.name] to [input].")) + target_clan.name = trim(input) + + if("clan_desc") + var/input = input(user, "Input a new description", "Set Description", target_clan.description) as message|null + + if(!input || input == target_clan.description) + return + + message_admins("[key_name_admin(user)] has set the description of [target_clan.name].") + to_chat(user, SPAN_NOTICE("Set the description of [target_clan.name].")) + target_clan.description = trim(input) + data_reloader = FALSE + + + if("clan_color") + data_reloader = FALSE + var/color = input(user, "Input a new color", "Set Color", target_clan.color) as color|null + + if(!color) + return + + target_clan.color = color + message_admins("[key_name_admin(user)] has set the color of [target_clan.name] to [color].") + to_chat(user, SPAN_NOTICE("Set the color of [target_clan.name] to [color].")) + + if("change_rank") + if(!(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY)) && !(target_yautja.clan_id == current_clan_id)) + to_chat(user, SPAN_WARNING("You cannot change the rank of this player, they are not in your clan!")) + log_debug("Target Clan: [target_yautja.clan_id], Needed Clan: [current_clan_id]") + return + if(!verify_superadmin() && ((target_yautja.permissions & CLAN_PERMISSION_ADMIN_MANAGER) || (linked_client.clan_info.clan_rank <= target_yautja.clan_rank))) + to_chat(user, SPAN_WARNING("You can't target this person!")) + return + if(!target_yautja.clan_id) + to_chat(src, SPAN_WARNING("This player doesn't belong to a clan!")) + return + + var/list/datum/yautja_rank/ranks = GLOB.clan_ranks.Copy() + if(!verify_superadmin()) + ranks -= CLAN_RANK_ADMIN // Admin rank should not and cannot be obtained from here + + var/datum/yautja_rank/chosen_rank + if(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY, warn = FALSE)) + var/input = tgui_input_list(user, "Select the rank to change this user to.", "Select Rank", ranks) + + if(!input) + return + + chosen_rank = ranks[input] + + else if(linked_client.has_clan_permission(CLAN_PERMISSION_USER_MODIFY, target_yautja.clan_id)) + for(var/rank in ranks) + if(!linked_client.has_clan_permission(ranks[rank].permission_required, warn = FALSE)) + ranks -= rank + + var/input = tgui_input_list(user, "Select the rank to change this user to.", "Select Rank", ranks) + + if(!input) + return + + chosen_rank = ranks[input] + + if(chosen_rank.limit_type) + var/list/datum/view_record/clan_playerbase_view/CPV = DB_VIEW(/datum/view_record/clan_playerbase_view/, DB_AND(DB_COMP("clan_id", DB_EQUALS, target_yautja.clan_id), DB_COMP("rank", DB_EQUALS, GLOB.clan_ranks_ordered[input]))) + var/players_in_rank = length(CPV) + + switch(chosen_rank.limit_type) + if(CLAN_LIMIT_NUMBER) + if(players_in_rank >= chosen_rank.limit) + to_chat(user, SPAN_DANGER("This slot is full! (Maximum of [chosen_rank.limit] slots)")) + return + if(CLAN_LIMIT_SIZE) + var/list/datum/view_record/clan_playerbase_view/clan_players = DB_VIEW(/datum/view_record/clan_playerbase_view/, DB_COMP("clan_id", DB_EQUALS, target_yautja.clan_id)) + var/available_slots = ceil(length(clan_players) / chosen_rank.limit) + + if(players_in_rank >= available_slots) + to_chat(user, SPAN_DANGER("This slot is full! (Maximum of [chosen_rank.limit] per player in the clan, currently [available_slots])")) + return + else + return // Doesn't have permission to do this + + if(!linked_client.has_clan_permission(chosen_rank.permission_required)) // Double check + return + + target_yautja.clan_rank = GLOB.clan_ranks_ordered[chosen_rank.name] + target_yautja.permissions = chosen_rank.permissions + message_admins("[key_name_admin(user)] has set the rank of [target_ckey] to [chosen_rank.name] for their clan.") + to_chat(user, SPAN_NOTICE("Set [target_ckey]'s rank to [chosen_rank.name]")) + + if("kick_from_clan") + if(!(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY)) && !(target_yautja.clan_id == current_clan_id)) + to_chat(user, SPAN_WARNING("You cannot kick this player, they are not in your clan!")) + log_debug("Target Clan: [target_yautja.clan_id], Needed Clan: [current_clan_id]") + return + target_yautja.clan_id = null + target_yautja.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_BLOODED] + + to_chat(user, SPAN_NOTICE("Kicked [target_ckey] from your clan.")) + message_admins("Yautja Clans: [key_name_admin(user)] has kicked [target_ckey] from their clan.") + + if("banish_from_clan") + if(!(linked_client.has_clan_permission(CLAN_PERMISSION_ADMIN_MODIFY)) && !(target_yautja.clan_id == current_clan_id)) + to_chat(user, SPAN_WARNING("You cannot banish this player, they are not in your clan!")) + log_debug("Target Clan: [target_yautja.clan_id], Needed Clan: [current_clan_id]") + return + var/reason = tgui_input_text(user, "Why do you wish to Banish this Yautja?", "Reason") + if(!reason) + return + + target_yautja.clan_id = get_clan_id("The Banished") + target_yautja.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_BLOODED] + + to_chat(user, SPAN_NOTICE("You have banished [target_ckey] from your clan.")) + message_admins("Yautja Clans: [key_name_admin(user)] has banished [target_ckey] from their clan for '[reason]'.") + + if("move_to_clan") + if(!verify_council()) + to_chat(user, SPAN_WARNING("You are not authorized to do this.")) + return FALSE + + var/new_clan = tgui_input_list(user, "Choose the clan to put them in", "Change player's clan", GLOB.yautja_clan_data.clan_name_to_index) + if(!new_clan) + return FALSE + + target_yautja.clan_id = get_clan_id(new_clan) + + to_chat(user, SPAN_NOTICE("Moved [target_ckey] to [new_clan].")) + message_admins("Yautja Clans: [key_name_admin(user)] has moved [target_ckey] to clan [new_clan] ([target_yautja.clan_id]).") + + if(!(target_yautja.permissions & CLAN_PERMISSION_ADMIN_ANCIENT)) + target_yautja.permissions = GLOB.clan_ranks[CLAN_RANK_BLOODED].permissions + target_yautja.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_BLOODED] + target_yautja.clan_ancillary = "None" + + if("delete_player_data") + if(!verify_superadmin()) + to_chat(user, SPAN_WARNING("You are not authorized to do this.")) + return FALSE + to_chat(user, SPAN_WARNING("This command ([action]) is not yet functional.")) + + if("delete_clan") + if(!verify_superadmin()) + to_chat(user, SPAN_WARNING("You are not authorized to do this.")) + return FALSE + to_chat(user, SPAN_WARNING("This command ([action]) is not yet functional.")) + + if(data_reloader) + GLOB.yautja_clan_data.queue_early_repopulate() + else + to_chat(user, SPAN_WARNING("The UI data will not be reloaded for this change, updates happen automatically every 30 minutes.")) + + if(target_clan) + target_clan.save() + target_clan.sync() + if(target_yautja) + target_yautja.save() + target_yautja.sync() + + return TRUE + +/datum/yautja_panel/proc/get_clan_id(clan_name) + var/index_holder = GLOB.yautja_clan_data.clan_name_to_index[clan_name] + return index_to_id(index_holder) + +/datum/yautja_panel/proc/index_to_id(clan_index) + var/index_holder = "[clan_index]" + return GLOB.yautja_clan_data.clan_index_to_id[index_holder] + +/datum/yautja_panel/proc/verify_clan_leader(clan_id) + if(user_rights & CLAN_PERMISSION_ADMIN_ANCIENT) + return TRUE + if((user_rights & CLAN_PERMISSION_USER_MODIFY) && (user_clan_id == clan_id)) + return TRUE + return FALSE + +/datum/yautja_panel/proc/verify_clan_elder(clan_id) + if(verify_clan_leader(clan_id)) + return TRUE + if(user_clan_rank >= CLAN_RANK_ELDER_INT) + return TRUE + return FALSE + +/datum/yautja_panel/proc/verify_council() + if(user_rights & CLAN_PERMISSION_ADMIN_ANCIENT) + return TRUE + return FALSE + +/datum/yautja_panel/proc/verify_superadmin() + if(user_rights & CLAN_PERMISSION_ADMIN_MANAGER) + return TRUE + return FALSE + +/datum/yautja_panel/proc/queue_early_repopulate() + if(early_queued) + return FALSE + + early_queued = TRUE + addtimer(CALLBACK(src, PROC_REF(populate_global_clan_data), FALSE, "early"), 5 MINUTES) + message_admins("Yautja Clans: Global Data will early repopulate in 5 minutes.") + return TRUE + +/datum/yautja_panel/proc/populate_global_clan_data(start_timer = FALSE, type = "override") + if(type == "early") + early_queued = FALSE + + message_admins("Yautja Clans: Populating Global Data.") + global_data = populate_clan_data() + message_admins("Yautja Clans: Global Data has been populated.") + if(start_timer) + addtimer(CALLBACK(src, PROC_REF(populate_global_clan_data), TRUE, "regular"), 30 MINUTES) + message_admins("Yautja Clans: Clan Global Data will repopulate in 30 minutes.") + return "Populated" + +/datum/yautja_panel/proc/populate_clan_data() + clan_name_to_index = list("Clanless" = 0) + clan_index_to_id = list("0" = null) + var/list/clan_names = list("Clanless") + var/index = 1 + var/list/data = list() + data["clans"] = list() + + data["clans"] += list(populate_clan("Clanless", null)) + data["clans"] += list(populate_clan("Clanless", 0)) + var/list/datum/view_record/clan_view/clan_list = DB_VIEW(/datum/view_record/clan_view/) + for(var/datum/view_record/clan_view/viewed_clan in clan_list) + data["clans"] += list(populate_clan("[viewed_clan.name]", viewed_clan.clan_id)) + clan_index_to_id["[index]"] = viewed_clan.clan_id + clan_name_to_index[viewed_clan.name] = index + index++ + clan_names += viewed_clan.name + + data["clan_names"] = clan_names + return data + +/datum/yautja_panel/proc/populate_clan(clan_name, clan_to_format) + var/list/data = list() + + var/datum/entity/clan/formatting_clan + var/list/datum/view_record/clan_playerbase_view/clan_view + var/clan_desc = "This is a list of players without a clan" + if(clan_to_format) + formatting_clan = GET_CLAN(clan_to_format) + formatting_clan.sync() + clan_desc = html_encode(formatting_clan.description) + clan_view = DB_VIEW(/datum/view_record/clan_playerbase_view, DB_COMP("clan_id", DB_EQUALS, clan_to_format)) + else + clan_view = DB_VIEW(/datum/view_record/clan_playerbase_view, DB_COMP("clan_id", DB_IS, null)) + + var/list/members_list = list() + for(var/datum/view_record/clan_playerbase_view/CP in clan_view) + + var/yautja = list() + yautja["ckey"] = CP.ckey + yautja["player_label"] = CP.ckey + yautja["name"] = (CP.player_name? CP.player_name : "No Data") + yautja["player_id"] = CP.player_id + yautja["rank"] = GLOB.clan_ranks[CP.clan_rank] + yautja["ancillary"] = (CP.clan_ancillary? CP.clan_ancillary : "None") + yautja["clan_id"] = (CP.clan_id) + + var/datum/entity/player/player = get_player_from_key(CP.ckey) + if(player.check_whitelist_status(WHITELIST_YAUTJA_LEADER)) + yautja["player_label"] = "[CP.ckey] (SENATOR)" + else if(player.check_whitelist_status(WHITELIST_YAUTJA_COUNCIL)) + yautja["player_label"] = "[CP.ckey] (COUNCILLOR)" + else if(player.check_whitelist_status(WHITELIST_YAUTJA_LEGACY)) + yautja["player_label"] = "[CP.ckey] (LEGACY)" + + members_list += list(yautja) + + data["label"] = clan_name + data["desc"] = clan_desc + data["color"] = (formatting_clan?.color? formatting_clan.color : "#ffffff") + data["members"] = members_list + if(clan_to_format) + data["clan_id"] = clan_to_format + + return data diff --git a/code/modules/clans/client.dm b/code/modules/clans/client.dm index b31a2c06f9c9..2b8ef6d4fe4d 100644 --- a/code/modules/clans/client.dm +++ b/code/modules/clans/client.dm @@ -12,8 +12,22 @@ if(check_whitelist_status(WHITELIST_YAUTJA_LEADER)) clan_info.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_ADMIN] clan_info.permissions |= CLAN_PERMISSION_ALL + else if(check_whitelist_status(WHITELIST_YAUTJA_COUNCIL)) + clan_info.permissions |= CLAN_PERMISSION_ADMIN_ANCIENT else - clan_info.permissions &= ~CLAN_PERMISSION_ADMIN_MANAGER // Only the leader can manage the ancients + var/datum/yautja_rank/permission_rank = GLOB.clan_ranks[clan_info.clan_rank] + if(!istype(permission_rank)) + log_debug("Yautja Clans: Permission change attempt, got rank '[permission_rank]' for '[ckey]' instead of datum.") + permission_rank = GLOB.clan_ranks[permission_rank] + if(!istype(permission_rank)) + log_debug("Yautja Clans: Permission change attempt, got rank '[permission_rank]' for '[ckey]' instead of datum, twice.") + clan_info.permissions = CLAN_PERMISSION_USER_VIEW + else + clan_info.permissions = permission_rank.permissions + else + clan_info.permissions = permission_rank.permissions + + clan_info.player_name = prefs.predator_name clan_info.save() @@ -158,6 +172,7 @@ player_id = CP.player_id, name = CP.ckey, rank = GLOB.clan_ranks[CP.clan_rank], // rank_to_give not used here, because we need to get their visual rank, not their position + ancillary = CP.clan_ancillary, rank_pos = rank_to_give, honor = (CP.honor? CP.honor : 0) ) @@ -173,21 +188,67 @@ ui.set_auto_update(FALSE) ui.open() -/client/proc/has_clan_permission(permission_flag, clan_id, warn) +/client/proc/is_yautja_ancient(query_permissions) + if(query_permissions) + if(query_permissions & CLAN_PERMISSION_ADMIN_ANCIENT) + return TRUE + return FALSE + if(clan_info?.permissions & CLAN_PERMISSION_ADMIN_ANCIENT) + return TRUE + return FALSE + +/client/proc/is_authorized_clan_member(clan_id, warn) if(!clan_info) - if(warn) to_chat(src, "You do not have a yautja whitelist!") + if(warn) + to_chat(src, SPAN_WARNING("You do not have a yautja whitelist!")) return FALSE if(clan_id) + if(is_yautja_ancient()) + return TRUE if(clan_id != clan_info.clan_id) - if(warn) to_chat(src, "You do not have permission to perform actions on this clan!") + if(warn) + to_chat(src, SPAN_WARNING("You do not have permission to perform actions on this clan!")) return FALSE + return TRUE +/client/proc/has_clan_permission(permission_flag, clan_id, warn) + if(!is_authorized_clan_member(clan_id, warn)) + return FALSE if(!(clan_info.permissions & permission_flag)) - if(warn) to_chat(src, "You do not have the necessary permissions to perform this action!") + if(warn) + to_chat(src, SPAN_WARNING("You do not have the necessary permissions to perform this action!")) + return FALSE + + return TRUE + +/client/proc/has_clan_position(needed_position, clan_id, warn) + if(!is_authorized_clan_member(clan_id, warn)) + return FALSE + + if(is_yautja_ancient() || (clan_info.clan_rank >= CLAN_RANK_LEADER_INT)) + return TRUE + + if(clan_info.clan_ancillary == needed_position) + return TRUE + if(clan_info.clan_rank >= needed_position) + return TRUE + + if(warn) + log_debug("Needed Position: [needed_position], Needed Clan ID: [clan_id]") + log_debug("Clan Rank: [clan_info.clan_rank], Ancillary: [clan_info.clan_ancillary? clan_info.clan_ancillary : "N/A"]") + to_chat(src, SPAN_WARNING("You do not have the necessary position to perform this action!")) + return FALSE + +/datum/entity/clan_player/proc/can_be_ancillary(required_rank, needed_clan_id) + if(needed_clan_id != clan_id) + log_debug("Needed clan match fail.") return FALSE + if(clan_rank < required_rank) + log_debug("Clan rank match fail.") + return FALSE return TRUE /client/proc/add_honor(number) @@ -333,9 +394,14 @@ if(!has_clan_permission(CLAN_PERMISSION_ADMIN_MANAGER)) return - var/input = input(src, "Are you sure you want to purge this person? Type '[player_name]' to purge", "Confirm Purge") as text|null + var/typeout = player_name + if(!typeout) + typeout = "null" + + var/input = input(src, "Are you sure you want to purge this person? Type '[typeout]' to purge", "Confirm Purge") as text|null + - if(!input || input != player_name) + if(!input || input != typeout) return var/target_clan = target.clan_id diff --git a/code/modules/clans/rank.dm b/code/modules/clans/rank.dm index a6b78a0d95e9..2fd6d517ee00 100644 --- a/code/modules/clans/rank.dm +++ b/code/modules/clans/rank.dm @@ -42,3 +42,50 @@ permission_required = CLAN_PERMISSION_ADMIN_MANAGER permissions = CLAN_PERMISSION_ADMIN_ANCIENT + + + +/datum/yautja_ancillary + var/name + + var/limit_type = CLAN_LIMIT_SIZE + var/limit = 5 + + /// The rank INTEGER or Ancillary TEXT needed to grant. + var/granter_title_required = CLAN_RANK_LEADER_INT + /// The target rank INTEGER needed for the ancillary. + var/target_rank_required = CLAN_RANK_ELITE_INT + +/datum/yautja_ancillary/high_enforcer + name = CLAN_ANCILLARY_HIGH_ENFORCER + target_rank_required = CLAN_RANK_ELDER_INT + limit_type = CLAN_LIMIT_NUMBER + limit = 1 + +/datum/yautja_ancillary/enforcer + name = CLAN_ANCILLARY_ENFORCER + granter_title_required = CLAN_ANCILLARY_HIGH_ENFORCER + +/datum/yautja_ancillary/high_shaman + name = CLAN_ANCILLARY_HIGH_SHAMAN + target_rank_required = CLAN_RANK_ELDER_INT + limit_type = CLAN_LIMIT_NUMBER + limit = 1 + +/datum/yautja_ancillary/shaman + name = CLAN_ANCILLARY_SHAMAN + granter_title_required = CLAN_ANCILLARY_HIGH_SHAMAN + +/datum/yautja_ancillary/adjutant + name = CLAN_ANCILLARY_ADJUTANT + +/datum/yautja_ancillary/hound_master + name = CLAN_ANCILLARY_HOUND_MASTER + +/datum/yautja_ancillary/task_master + name = CLAN_ANCILLARY_TASK_MASTER + +/datum/yautja_ancillary/clear + name = CLAN_ANCILLARY_NONE + limit = null + limit_type = null diff --git a/colonialmarines.dme b/colonialmarines.dme index a4644f0ee77e..ead487c9f68e 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1555,6 +1555,7 @@ #include "code\modules\character_traits\robotic_limbs.dm" #include "code\modules\character_traits\skills.dm" #include "code\modules\clans\clan.dm" +#include "code\modules\clans\clan_tgui.dm" #include "code\modules\clans\client.dm" #include "code\modules\clans\rank.dm" #include "code\modules\clans\ship.dm" diff --git a/nano/templates/clan_menu.tmpl b/nano/templates/clan_menu.tmpl index cd987e9079f6..09161f56e16b 100644 --- a/nano/templates/clan_menu.tmpl +++ b/nano/templates/clan_menu.tmpl @@ -108,6 +108,7 @@ Name Rank + Ancillary {{if data.player_modify_ranks}} {{/if}} @@ -124,6 +125,7 @@ {{:keys.name}} {{:keys.rank}} + {{:keys.ancillary}} {{if data.player_rank_pos > keys.rank_pos}} {{if data.player_modify_ranks}}
{{:helper.link('Set Rank', '', { 'clan_target_href' : keys.player_id, 'clan_action': 'modifyrank' })}}
diff --git a/tgui/packages/tgui/interfaces/YautjaClans.jsx b/tgui/packages/tgui/interfaces/YautjaClans.jsx new file mode 100644 index 000000000000..e9dd3621c24c --- /dev/null +++ b/tgui/packages/tgui/interfaces/YautjaClans.jsx @@ -0,0 +1,198 @@ +import { useBackend } from '../backend'; +import { Box, Button, Dropdown, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const YautjaClans = (props) => { + return ( + + + + + + ); +}; + +const ViewClans = (props) => { + const { data, act } = useBackend(); + const { + clans, + clan_names, + current_clan_index, + user_is_clan_leader, + user_is_clan_elder, + user_is_council, + user_is_superadmin, + } = data; + + return ( +
+ + act('change_clan_list', { selected_clan: value }) + } + /> + +
+

{clans[current_clan_index].label}

+ + {clans[current_clan_index].desc} + + {clans[current_clan_index].clan_id && ( + <> + + act('clan_name', { + target_clan: clans[current_clan_index].clan_id, + }) + } + > + Rename Clan + + + act('clan_desc', { + target_clan: clans[current_clan_index].clan_id, + }) + } + > + Change Description + + + act('clan_color', { + target_clan: clans[current_clan_index].clan_id, + }) + } + > + Change Clan Color + + {!!user_is_superadmin && ( + + act('delete_clan', { + target_clan: clans[current_clan_index].clan_id, + }) + } + > + Delete Clan + + )} + + )} +
+ {clans[current_clan_index].members.map((yautja, i) => ( +
+ + {yautja.name} + {yautja.rank} + + {yautja.ancillary} + + + + act('assign_ancillary', { target_player: yautja.player_id }) + } + > + Assign Ancillary + + + act('change_rank', { target_player: yautja.player_id }) + } + > + Change Rank + + {!user_is_council && ( + <> + + act('kick_from_clan', { target_player: yautja.player_id }) + } + > + Remove From Clan + + + act('banish_from_clan', { target_player: yautja.player_id }) + } + > + Banish + + + )} + {!!user_is_council && ( + + act('move_to_clan', { target_player: yautja.player_id }) + } + > + Change Clan + + )} + {!!user_is_superadmin && ( + + act('delete_player_data', { target_player: yautja.player_id }) + } + > + Delete Player + + )} +
+ ))} +
+ ); +};