#define WHITE_TEAM "White" #define RED_TEAM "Red" #define BLUE_TEAM "Blue" #define GREEN_TEAM "Green" #define YELLOW_TEAM "Yellow" #define FLAG_RETURN_TIME 200 // 20 seconds #define INSTAGIB_RESPAWN 50 //5 seconds #define DEFAULT_RESPAWN 150 //15 seconds /obj/item/ctf name = "banner" icon = 'icons/obj/banner.dmi' icon_state = "banner" inhand_icon_state = "banner" lefthand_file = 'icons/mob/inhands/equipment/banners_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/banners_righthand.dmi' desc = "A banner with Nanotrasen's logo on it." slowdown = 2 throw_speed = 0 throw_range = 1 force = 200 armour_penetration = 1000 resistance_flags = INDESTRUCTIBLE anchored = TRUE item_flags = SLOWS_WHILE_IN_HAND var/team = WHITE_TEAM var/reset_cooldown = 0 var/anyonecanpickup = TRUE var/obj/effect/ctf/flag_reset/reset var/reset_path = /obj/effect/ctf/flag_reset /// Which area we announce updates on the flag to. Should just generally be the area of the arena. var/game_area = /area/ctf /obj/item/ctf/Destroy() QDEL_NULL(reset) return ..() /obj/item/ctf/Initialize(mapload) . = ..() if(!reset) reset = new reset_path(get_turf(src)) reset.flag = src RegisterSignal(src, COMSIG_PARENT_PREQDELETED, .proc/reset_flag) //just in case CTF has some map hazards (read: chasms). /obj/item/ctf/process() if(is_ctf_target(loc)) //pickup code calls temporary drops to test things out, we need to make sure the flag doesn't reset from return PROCESS_KILL if(world.time > reset_cooldown) reset_flag() /obj/item/ctf/proc/reset_flag(capture = FALSE) SIGNAL_HANDLER STOP_PROCESSING(SSobj, src) var/turf/our_turf = get_turf(src.reset) if(!our_turf) return TRUE forceMove(our_turf) for(var/mob/M in GLOB.player_list) var/area/mob_area = get_area(M) if(istype(mob_area, game_area)) if(!capture) to_chat(M, span_userdanger("[src] has been returned to the base!")) return TRUE //so if called by a signal, it doesn't delete //working with attack hand feels like taking my brain and putting it through an industrial pill press so i'm gonna be a bit liberal with the comments /obj/item/ctf/attack_hand(mob/living/user, list/modifiers) //pre normal check item stuff, this is for our special flag checks if(!is_ctf_target(user) && !anyonecanpickup) to_chat(user, span_warning("Non-players shouldn't be moving the flag!")) return if(team in user.faction) to_chat(user, span_warning("You can't move your own flag!")) return if(loc == user) if(!user.dropItemToGround(src)) return for(var/mob/M in GLOB.player_list) var/area/mob_area = get_area(M) if(istype(mob_area, game_area)) to_chat(M, span_userdanger("\The [initial(src.name)] has been taken!")) STOP_PROCESSING(SSobj, src) anchored = FALSE // Hacky usage that bypasses set_anchored(), because normal checks need this to be FALSE to pass . = ..() //this is the actual normal item checks if(.) //only apply these flag passives anchored = TRUE // Avoid directly assigning to anchored and prefer to use set_anchored() on normal circumstances. return //passing means the user picked up the flag so we can now apply this user.set_anchored(TRUE) user.status_flags &= ~CANPUSH /obj/item/ctf/dropped(mob/user) ..() user.anchored = FALSE // Hacky usage that bypasses set_anchored() user.status_flags |= CANPUSH reset_cooldown = world.time + 20 SECONDS START_PROCESSING(SSobj, src) for(var/mob/M in GLOB.player_list) var/area/mob_area = get_area(M) if(istype(mob_area, game_area)) to_chat(M, span_userdanger("\The [initial(name)] has been dropped!")) anchored = TRUE // Avoid directly assigning to anchored and prefer to use set_anchored() on normal circumstances. /obj/item/ctf/red name = "red flag" icon_state = "banner-red" inhand_icon_state = "banner-red" desc = "A red banner used to play capture the flag." team = RED_TEAM reset_path = /obj/effect/ctf/flag_reset/red /obj/item/ctf/blue name = "blue flag" icon_state = "banner-blue" inhand_icon_state = "banner-blue" desc = "A blue banner used to play capture the flag." team = BLUE_TEAM reset_path = /obj/effect/ctf/flag_reset/blue /obj/item/ctf/green name = "green flag" icon_state = "banner-green" inhand_icon_state = "banner-green" desc = "A green banner used to play capture the flag." team = GREEN_TEAM reset_path = /obj/effect/ctf/flag_reset/green /obj/item/ctf/yellow name = "yellow flag" icon_state = "banner-yellow" inhand_icon_state = "banner-yellow" desc = "A yellow banner used to play capture the flag." team = YELLOW_TEAM reset_path = /obj/effect/ctf/flag_reset/yellow /obj/effect/ctf/flag_reset name = "banner landmark" icon = 'icons/obj/items_and_weapons.dmi' icon_state = "banner" desc = "This is where a banner with Nanotrasen's logo on it would go." layer = LOW_ITEM_LAYER var/obj/item/ctf/flag /obj/effect/ctf/flag_reset/Destroy() if(flag) flag.reset = null flag = null return ..() /obj/effect/ctf/flag_reset/red name = "red flag landmark" icon_state = "banner-red" desc = "This is where a red banner used to play capture the flag \ would go." /obj/effect/ctf/flag_reset/blue name = "blue flag landmark" icon_state = "banner-blue" desc = "This is where a blue banner used to play capture the flag \ would go." /obj/effect/ctf/flag_reset/green name = "green flag landmark" icon_state = "banner" desc = "This is where a green banner used to play capture the flag \ would go." /obj/effect/ctf/flag_reset/yellow name = "yellow flag landmark" icon_state = "banner" desc = "This is where a yellow banner used to play capture the flag \ would go." /proc/toggle_id_ctf(user, activated_id, automated = FALSE) var/ctf_enabled = FALSE var/area/A for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(activated_id != CTF.game_id) continue ctf_enabled = CTF.toggle_ctf() A = get_area(CTF) for(var/obj/machinery/power/emitter/E in A) E.active = ctf_enabled if(user) message_admins("[key_name_admin(user)] has [ctf_enabled ? "enabled" : "disabled"] CTF!") else if(automated) message_admins("CTF has finished a round and automatically restarted.") notify_ghosts("CTF has automatically restarted after a round finished in [A]!",'sound/effects/ghost2.ogg') else message_admins("The players have spoken! Voting has enabled CTF!") if(!automated) notify_ghosts("CTF has been [ctf_enabled? "enabled" : "disabled"] in [A]!",'sound/effects/ghost2.ogg') /obj/machinery/capture_the_flag name = "CTF Controller" desc = "Used for running friendly games of capture the flag." icon = 'icons/obj/device.dmi' icon_state = "syndbeacon" resistance_flags = INDESTRUCTIBLE var/game_id = "centcom" var/victory_rejoin_text = "Teams have been cleared. Click on the machines to vote to begin another round." var/team = WHITE_TEAM var/team_span = "" //Capture the Flag scoring var/points = 0 var/points_to_win = 3 var/respawn_cooldown = DEFAULT_RESPAWN //Capture Point/King of the Hill scoring var/control_points = 0 var/control_points_to_win = 180 var/list/team_members = list() ///assoc list: mob = outfit datum (class) var/list/spawned_mobs = list() var/list/recently_dead_ckeys = list() var/ctf_enabled = FALSE ///assoc list for classes. If there's only one, it'll just equip. Otherwise, it lets you pick which outfit! var/list/ctf_gear = list("Rifleman" = /datum/outfit/ctf, "Assaulter" = /datum/outfit/ctf/assault, "Marksman" = /datum/outfit/ctf/marksman) var/instagib_gear = /datum/outfit/ctf/instagib var/ammo_type = /obj/effect/powerup/ammo/ctf // Fast paced gameplay, no real time for burn infections. var/player_traits = list(TRAIT_NEVER_WOUNDED) var/list/dead_barricades = list() var/static/arena_reset = FALSE var/static/list/people_who_want_to_play = list() var/game_area = /area/ctf /// This variable is needed because of ctf shitcode + we need to make sure we're deleting the current ctf landmark that spawned us in and not a new one. var/obj/effect/landmark/ctf/ctf_landmark /obj/machinery/capture_the_flag/Initialize(mapload) . = ..() SSpoints_of_interest.make_point_of_interest(src) ctf_landmark = GLOB.ctf_spawner /obj/machinery/capture_the_flag/Destroy() ctf_landmark = null return ..() /obj/machinery/capture_the_flag/process(delta_time) for(var/i in spawned_mobs) if(!i) spawned_mobs -= i continue // Anyone in crit, automatically reap var/mob/living/living_participant = i if(HAS_TRAIT(living_participant, TRAIT_CRITICAL_CONDITION) || living_participant.stat == DEAD || !living_participant.client) // If they're critted, dead or no longer in their body, dust them ctf_dust_old(living_participant) else // The changes that you've been hit with no shield but not // instantly critted are low, but have some healing. living_participant.adjustBruteLoss(-2.5 * delta_time) living_participant.adjustFireLoss(-2.5 * delta_time) /obj/machinery/capture_the_flag/red name = "Red CTF Controller" icon_state = "syndbeacon" team = RED_TEAM team_span = "redteamradio" ctf_gear = list("Rifleman" = /datum/outfit/ctf/red, "Assaulter" = /datum/outfit/ctf/assault/red, "Marksman" = /datum/outfit/ctf/marksman/red) instagib_gear = /datum/outfit/ctf/red/instagib /obj/machinery/capture_the_flag/blue name = "Blue CTF Controller" icon_state = "bluebeacon" team = BLUE_TEAM team_span = "blueteamradio" ctf_gear = list("Rifleman" = /datum/outfit/ctf/blue, "Assaulter" = /datum/outfit/ctf/assault/blue, "Marksman" = /datum/outfit/ctf/marksman/blue) instagib_gear = /datum/outfit/ctf/blue/instagib /obj/machinery/capture_the_flag/green name = "Green CTF Controller" icon_state = "greenbeacon" team = GREEN_TEAM team_span = "greenteamradio" ctf_gear = list("Rifleman" = /datum/outfit/ctf/green, "Assaulter" = /datum/outfit/ctf/assault/green, "Marksman" = /datum/outfit/ctf/marksman/green) instagib_gear = /datum/outfit/ctf/green/instagib /obj/machinery/capture_the_flag/yellow name = "Yellow CTF Controller" icon_state = "yellowbeacon" team = YELLOW_TEAM team_span = "yellowteamradio" ctf_gear = list("Rifleman" = /datum/outfit/ctf/yellow, "Assaulter" = /datum/outfit/ctf/assault/yellow, "Marksman" = /datum/outfit/ctf/marksman/yellow) instagib_gear = /datum/outfit/ctf/yellow/instagib //ATTACK GHOST IGNORING PARENT RETURN VALUE /obj/machinery/capture_the_flag/attack_ghost(mob/user) if(ctf_enabled == FALSE) if(user.client && user.client.holder) var/response = tgui_alert(usr,"Enable this CTF game?", "CTF", list("Yes", "No")) if(response == "Yes") toggle_id_ctf(user, game_id) return if(!(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME)) to_chat(user, span_warning("CTF has been temporarily disabled by admins.")) return for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id && CTF.ctf_enabled) to_chat(user, span_warning("There is already an ongoing game in the [get_area(CTF)]!")) return people_who_want_to_play |= user.ckey var/num = people_who_want_to_play.len var/remaining = CTF_REQUIRED_PLAYERS - num if(remaining <= 0) people_who_want_to_play.Cut() toggle_id_ctf(null, game_id) else to_chat(user, span_notice("CTF has been requested. [num]/[CTF_REQUIRED_PLAYERS] have readied up.")) return if(!SSticker.HasRoundStarted()) return if(user.ckey in team_members) if(user.ckey in recently_dead_ckeys) to_chat(user, span_warning("It must be more than [DisplayTimeText(respawn_cooldown)] from your last death to respawn!")) return var/client/new_team_member = user.client if(user.mind && user.mind.current) ctf_dust_old(user.mind.current) spawn_team_member(new_team_member) return for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id || CTF == src || CTF.ctf_enabled == FALSE) continue if(user.ckey in CTF.team_members) to_chat(user, span_warning("No switching teams while the round is going!")) return if(CTF.team_members.len < src.team_members.len) to_chat(user, span_warning("[src.team] has more team members than [CTF.team]! Try joining [CTF.team] team to even things up.")) return var/client/new_team_member = user.client team_members |= new_team_member.ckey to_chat(user, "You are now a member of [src.team]. Get the enemy flag and bring it back to your team's controller!") spawn_team_member(new_team_member) //does not add to recently dead, because it dusts and that triggers ctf_qdelled_player /obj/machinery/capture_the_flag/proc/ctf_dust_old(mob/living/body) if(isliving(body) && (team in body.faction)) var/turf/T = get_turf(body) if(ammo_type) new ammo_type(T) body.dust() /obj/machinery/capture_the_flag/proc/ctf_qdelled_player(mob/living/body) SIGNAL_HANDLER recently_dead_ckeys += body.ckey spawned_mobs -= body addtimer(CALLBACK(src, .proc/clear_cooldown, body.ckey), respawn_cooldown, TIMER_UNIQUE) /obj/machinery/capture_the_flag/proc/clear_cooldown(ckey) recently_dead_ckeys -= ckey /obj/machinery/capture_the_flag/proc/spawn_team_member(client/new_team_member) var/datum/outfit/chosen_class if(ctf_gear.len == 1) //no choices to make for(var/key in ctf_gear) chosen_class = ctf_gear[key] else //there's a choice to make, present a radial menu var/list/display_classes = list() for(var/key in ctf_gear) var/datum/outfit/ctf/class = ctf_gear[key] var/datum/radial_menu_choice/option = new option.image = image(icon = initial(class.icon), icon_state = initial(class.icon_state)) option.info = "[initial(class.class_description)]" display_classes[key] = option sortList(display_classes) var/choice = show_radial_menu(new_team_member.mob, src, display_classes, radius = 38) if(!choice || !(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME) || (new_team_member.ckey in recently_dead_ckeys) || !isobserver(new_team_member.mob) || src.ctf_enabled == FALSE || !(new_team_member.ckey in src.team_members)) return //picked nothing, admin disabled it, cheating to respawn faster, cheating to respawn... while in game?, //there isn't a game going on any more, you are no longer a member of this team (perhaps a new match already started?) chosen_class = ctf_gear[choice] var/mob/living/carbon/human/M = new /mob/living/carbon/human(get_turf(src)) new_team_member.prefs.safe_transfer_prefs_to(M, is_antag = TRUE) M.set_species(/datum/species/synth) M.key = new_team_member.key M.faction += team M.equipOutfit(chosen_class) RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/ctf_qdelled_player) //just in case CTF has some map hazards (read: chasms). bit shorter than dust for(var/trait in player_traits) ADD_TRAIT(M, trait, CAPTURE_THE_FLAG_TRAIT) spawned_mobs[M] = chosen_class return M //used in medisim.dm /obj/machinery/capture_the_flag/Topic(href, href_list) if(href_list["join"]) var/mob/dead/observer/ghost = usr if(istype(ghost)) attack_ghost(ghost) /obj/machinery/capture_the_flag/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/ctf)) var/obj/item/ctf/flag = I if(flag.team != src.team) points++ flag.reset_flag(capture = TRUE) for(var/mob/ctf_player in GLOB.player_list) var/area/mob_area = get_area(ctf_player) if(istype(mob_area, game_area)) to_chat(ctf_player, "[user.real_name] has captured \the [flag], scoring a point for [team] team! They now have [points]/[points_to_win] points!") if(points >= points_to_win) victory() /obj/machinery/capture_the_flag/proc/victory() for(var/mob/_competitor in GLOB.mob_living_list) var/mob/living/competitor = _competitor var/area/mob_area = get_area(competitor) if(istype(mob_area, game_area)) to_chat(competitor, "[team] team wins!") to_chat(competitor, victory_rejoin_text) for(var/obj/item/ctf/W in competitor) competitor.dropItemToGround(W) competitor.dust() for(var/obj/machinery/control_point/control in GLOB.machines) control.icon_state = "dominator" control.controlling = null for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id) continue if(CTF.ctf_enabled == TRUE) CTF.points = 0 CTF.control_points = 0 CTF.ctf_enabled = FALSE CTF.team_members = list() CTF.arena_reset = FALSE /obj/machinery/capture_the_flag/proc/toggle_ctf() if(!ctf_enabled) start_ctf() . = TRUE else stop_ctf() . = FALSE /obj/machinery/capture_the_flag/proc/start_ctf() ctf_enabled = TRUE for(var/d in dead_barricades) var/obj/effect/ctf/dead_barricade/D = d D.respawn() dead_barricades.Cut() notify_ghosts("[name] has been activated!", source = src, action=NOTIFY_ORBIT, header = "CTF has been activated") /obj/machinery/capture_the_flag/proc/reset_the_arena() if(!ctf_landmark) return if(ctf_landmark == GLOB.ctf_spawner) new /obj/effect/landmark/ctf(get_turf(GLOB.ctf_spawner)) /obj/machinery/capture_the_flag/proc/stop_ctf() ctf_enabled = FALSE arena_reset = FALSE var/area/A = get_area(src) for(var/_competitor in GLOB.mob_living_list) var/mob/living/competitor = _competitor if((get_area(A) == A) && (competitor.ckey in team_members)) competitor.dust() team_members.Cut() spawned_mobs.Cut() recently_dead_ckeys.Cut() reset_the_arena() /obj/machinery/capture_the_flag/proc/instagib_mode() for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id) continue if(CTF.ctf_enabled == TRUE) CTF.ctf_gear = CTF.instagib_gear CTF.respawn_cooldown = INSTAGIB_RESPAWN /obj/machinery/capture_the_flag/proc/normal_mode() for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id) continue if(CTF.ctf_enabled == TRUE) CTF.ctf_gear = initial(ctf_gear) CTF.respawn_cooldown = DEFAULT_RESPAWN /obj/structure/trap/ctf name = "Spawn protection" desc = "Stay outta the enemy spawn!" icon_state = "trap" resistance_flags = INDESTRUCTIBLE var/team = WHITE_TEAM time_between_triggers = 1 anchored = TRUE alpha = 255 /obj/structure/trap/ctf/examine(mob/user) return /obj/structure/trap/ctf/trap_effect(mob/living/L) if(!is_ctf_target(L)) return if(!(src.team in L.faction)) to_chat(L, span_danger("Stay out of the enemy spawn!")) L.death() /obj/structure/trap/ctf/red team = RED_TEAM icon_state = "trap-fire" /obj/structure/trap/ctf/blue team = BLUE_TEAM icon_state = "trap-frost" /obj/structure/trap/ctf/green team = GREEN_TEAM icon_state = "trap-earth" /obj/structure/trap/ctf/yellow team = YELLOW_TEAM icon_state = "trap-shock" /obj/structure/barricade/security/ctf name = "barrier" desc = "A barrier. Provides cover in fire fights." deploy_time = 0 deploy_message = 0 /obj/structure/barricade/security/ctf/make_debris() new /obj/effect/ctf/dead_barricade(get_turf(src)) /obj/structure/table/reinforced/ctf resistance_flags = INDESTRUCTIBLE flags_1 = NODECONSTRUCT_1 /obj/effect/ctf density = FALSE anchored = TRUE invisibility = INVISIBILITY_OBSERVER alpha = 100 resistance_flags = INDESTRUCTIBLE /obj/effect/ctf/dead_barricade name = "dead barrier" desc = "It provided cover in fire fights. And now it's gone." icon = 'icons/obj/objects.dmi' icon_state = "barrier0" var/game_id = "centcom" /obj/effect/ctf/dead_barricade/Initialize(mapload) . = ..() for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id) continue CTF.dead_barricades += src /obj/effect/ctf/dead_barricade/Destroy() for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.game_id != game_id) continue CTF.dead_barricades -= src return ..() /obj/effect/ctf/dead_barricade/proc/respawn() if(!QDELETED(src)) new /obj/structure/barricade/security/ctf(get_turf(src)) qdel(src) //Control Point /obj/machinery/control_point name = "control point" desc = "You should capture this." icon = 'icons/obj/machines/dominator.dmi' icon_state = "dominator" resistance_flags = INDESTRUCTIBLE var/obj/machinery/capture_the_flag/controlling var/team = "none" var/point_rate = 0.5 var/game_area = /area/ctf /obj/machinery/control_point/process(delta_time) if(controlling) controlling.control_points += point_rate * delta_time if(controlling.control_points >= controlling.control_points_to_win) controlling.victory() /obj/machinery/control_point/attackby(mob/user, params) capture(user) /obj/machinery/control_point/attack_hand(mob/user, list/modifiers) . = ..() if(.) return capture(user) /obj/machinery/control_point/proc/capture(mob/user) if(do_after(user, 30, target = src)) for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(CTF.ctf_enabled && (user.ckey in CTF.team_members)) controlling = CTF icon_state = "dominator-[CTF.team]" for(var/mob/M in GLOB.player_list) var/area/mob_area = get_area(M) if(istype(mob_area, game_area)) to_chat(M, span_userdanger("[user.real_name] has captured \the [src], claiming it for [CTF.team]! Go take it back!")) break /proc/is_ctf_target(atom/target) . = FALSE if(istype(target, /obj/structure/barricade/security/ctf)) . = TRUE if(ishuman(target)) var/mob/living/carbon/human/H = target for(var/obj/machinery/capture_the_flag/CTF in GLOB.machines) if(H in CTF.spawned_mobs) . = TRUE break #undef WHITE_TEAM #undef RED_TEAM #undef BLUE_TEAM #undef GREEN_TEAM #undef YELLOW_TEAM #undef FLAG_RETURN_TIME #undef INSTAGIB_RESPAWN #undef DEFAULT_RESPAWN