diff --git a/code/ATMOSPHERICS/components/trinary_devices/mixer.dm b/code/ATMOSPHERICS/components/trinary_devices/mixer.dm index be4bf969009..52845f1154d 100644 --- a/code/ATMOSPHERICS/components/trinary_devices/mixer.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/mixer.dm @@ -152,7 +152,7 @@ return 1 /obj/machinery/atmospherics/trinary/mixer/attack_ghost(mob/user) - ui_interact(user) + tgui_interact(user) /obj/machinery/atmospherics/trinary/mixer/attack_hand(mob/user) if(..()) @@ -163,62 +163,62 @@ return add_fingerprint(user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/atmospherics/trinary/mixer/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, var/master_ui = null, var/datum/topic_state/state = GLOB.default_state) +/obj/machinery/atmospherics/trinary/mixer/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) user.set_machine(src) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_mixer.tmpl", name, 370, 165, state = state) + ui = new(user, src, ui_key, "AtmosMixer", name, 330, 165, master_ui, state) ui.open() -/obj/machinery/atmospherics/trinary/mixer/ui_data(mob/user) - var/list/data = list() - data["on"] = on - data["pressure"] = round(target_pressure) - data["max_pressure"] = round(MAX_OUTPUT_PRESSURE) - data["node1_concentration"] = round(node1_concentration*100) - data["node2_concentration"] = round(node2_concentration*100) +/obj/machinery/atmospherics/trinary/mixer/tgui_data(mob/user) + var/list/data = list( + "on" = on, + "pressure" = round(target_pressure, 0.01), + "max_pressure" = MAX_OUTPUT_PRESSURE, + "node1_concentration" = round(node1_concentration * 100), + "node2_concentration" = round(node2_concentration * 100) + ) return data -/obj/machinery/atmospherics/trinary/mixer/Topic(href,href_list) + + +/obj/machinery/atmospherics/trinary/mixer/tgui_act(action, list/params) if(..()) - return 1 + return - if(href_list["power"]) - on = !on - investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos") - . = TRUE - if(href_list["pressure"]) - var/pressure = href_list["pressure"] - if(pressure == "max") - pressure = MAX_OUTPUT_PRESSURE - . = TRUE - else if(pressure == "input") - pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null - if(!isnull(pressure) && !..()) - . = TRUE - else if(text2num(pressure) != null) - pressure = text2num(pressure) - . = TRUE - if(.) - target_pressure = clamp(pressure, 0, MAX_OUTPUT_PRESSURE) - investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos") - if(href_list["node1"]) - var/value = text2num(href_list["node1"]) - node1_concentration = max(0, min(1, node1_concentration + value)) - node2_concentration = max(0, min(1, node2_concentration - value)) - investigate_log("was set to [node1_concentration] % on node 1 by [key_name(usr)]", "atmos") - . = TRUE - if(href_list["node2"]) - var/value = text2num(href_list["node2"]) - node2_concentration = max(0, min(1, node2_concentration + value)) - node1_concentration = max(0, min(1, node1_concentration - value)) - investigate_log("was set to [node2_concentration] % on node 2 by [key_name(usr)]", "atmos") - . = TRUE + switch(action) + if("power") + toggle() + investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos") + return TRUE - update_icon() - SSnanoui.update_uis(src) + if("set_node") + if(params["node_name"] == "Node 1") + node1_concentration = clamp(round(text2num(params["concentration"]), 0.01), 0, 1) + node2_concentration = round(1 - node1_concentration, 0.01) + investigate_log("was set to [node1_concentration] % on node 1 by [key_name(usr)]", "atmos") + return TRUE + else + node2_concentration = clamp(round(text2num(params["concentration"]), 0.01), 0, 1) + node1_concentration = round(1 - node2_concentration, 0.01) + investigate_log("was set to [node2_concentration] % on node 2 by [key_name(usr)]", "atmos") + return TRUE + + if("max_pressure") + target_pressure = MAX_OUTPUT_PRESSURE + . = TRUE + + if("min_pressure") + target_pressure = 0 + . = TRUE + + if("custom_pressure") + target_pressure = clamp(text2num(params["pressure"]), 0, MAX_OUTPUT_PRESSURE) + . = TRUE + if(.) + investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos") /obj/machinery/atmospherics/trinary/mixer/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/pen)) diff --git a/code/__DEFINES/martial_arts.dm b/code/__DEFINES/martial_arts.dm new file mode 100644 index 00000000000..4eb139cc44a --- /dev/null +++ b/code/__DEFINES/martial_arts.dm @@ -0,0 +1,16 @@ +#define MARTIAL_COMBO_FAIL 0 // If the combo failed +#define MARTIAL_COMBO_CONTINUE 1 // If the combo should continue +#define MARTIAL_COMBO_DONE 2 // If the combo is successful and done +#define MARTIAL_COMBO_DONE_NO_CLEAR 3 // If the combo is successful and done but the others should have a chance to finish +#define MARTIAL_COMBO_DONE_BASIC_HIT 4 // If the combo should do a basic hit after it's done +#define MARTIAL_COMBO_DONE_CLEAR_COMBOS 5 // If the combo should do a basic hit after it's done + +#define MARTIAL_ARTS_CANNOT_USE -1 + +#define MARTIAL_COMBO_STEP_HARM "Harm" +#define MARTIAL_COMBO_STEP_DISARM "Disarm" +#define MARTIAL_COMBO_STEP_GRAB "Grab" +#define MARTIAL_COMBO_STEP_HELP "Help" + +// A check used for all act types. Such as disarm_act +#define MARTIAL_ARTS_ACT_CHECK if((. = ..()) != FALSE) return . diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 719274857ec..7cd4cc7a918 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -208,6 +208,7 @@ #define isguardian(A) (istype((A), /mob/living/simple_animal/hostile/guardian)) #define isnymph(A) (istype((A), /mob/living/simple_animal/diona)) #define ishostile(A) (istype(A, /mob/living/simple_animal/hostile)) +#define isterrorspider(A) (istype((A), /mob/living/simple_animal/hostile/poison/terror_spider)) #define issilicon(A) (istype((A), /mob/living/silicon)) #define isAI(A) (istype((A), /mob/living/silicon/ai)) diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index 3a65da81382..9c6fa81c1a2 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -92,6 +92,7 @@ GLOBAL_VAR(map_name) // Self explanatory GLOBAL_DATUM_INIT(data_core, /datum/datacore, new) // Station datacore, manifest, etc GLOBAL_VAR_INIT(panic_bunker_enabled, FALSE) // Is the panic bunker enabled +GLOBAL_VAR_INIT(pending_server_update, FALSE) //Database connections //A connection is established on world creation. Ideally, the connection dies when the server restarts (After feedback logging.). diff --git a/code/datums/cache/air_alarm.dm b/code/datums/cache/air_alarm.dm index 2edc0792a34..fd9e529d70a 100644 --- a/code/datums/cache/air_alarm.dm +++ b/code/datums/cache/air_alarm.dm @@ -1,3 +1,5 @@ +#define AIR_ALARM_DATA_CACHE_DURATION 10 SECONDS + GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new()) /datum/repository/air_alarm/proc/air_alarm_data(var/list/monitored_alarms, var/refresh = 0, var/obj/machinery/alarm/passed_alarm) @@ -8,7 +10,7 @@ GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new()) cache_entry = new/datum/cache_entry cache_data = cache_entry - if(!refresh) + if(!refresh && cache_entry.timestamp + AIR_ALARM_DATA_CACHE_DURATION > world.time) return cache_entry.data if(SSticker && SSticker.current_state < GAME_STATE_PLAYING && istype(passed_alarm)) // Generating the list for the first time as the game hasn't started - no need to run through the machines list everything every time @@ -29,3 +31,5 @@ GLOBAL_DATUM_INIT(air_alarm_repository, /datum/repository/air_alarm, new()) /datum/repository/air_alarm/proc/update_cache(var/obj/machinery/alarm/alarm) return air_alarm_data(refresh = 1, passed_alarm = alarm) + +#undef AIR_ALARM_DATA_CACHE_DURATION diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm index 964fba34c9b..14705ae7375 100644 --- a/code/datums/dog_fashion.dm +++ b/code/datums/dog_fashion.dm @@ -204,3 +204,7 @@ D.mutations.Add(BREATHLESS) D.atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) D.minbodytemp = 0 + +/datum/dog_fashion/head/fried_vox_empty + name = "Colonel REAL_NAME" + desc = "Keep away from live vox." diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 8fa5b909cda..53230d1f83d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -35,6 +35,7 @@ var/list/restricted_roles = list() var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. + var/datum/martial_art/martial_art var/role_alt_title @@ -1102,8 +1103,8 @@ special_role = null to_chat(current,"Your infernal link has been severed! You are no longer a devil!") RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt) - RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish) - RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/fireball/hellish) + RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/summon_contract) RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork) RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater) RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended) diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm index 2b48d5da5f7..d5be5f50f35 100644 --- a/code/datums/outfits/outfit_admin.dm +++ b/code/datums/outfits/outfit_admin.dm @@ -242,7 +242,7 @@ /obj/item/organ/internal/cyberimp/eyes/shield, /obj/item/organ/internal/cyberimp/eyes/hud/security, /obj/item/organ/internal/cyberimp/eyes/xray, - /obj/item/organ/internal/cyberimp/brain/anti_stun, + /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened, /obj/item/organ/internal/cyberimp/chest/nutriment/plus, /obj/item/organ/internal/cyberimp/arm/combat/centcom ) diff --git a/code/datums/spell.dm b/code/datums/spell.dm index 94779ed67fb..abb9fdc37c9 100644 --- a/code/datums/spell.dm +++ b/code/datums/spell.dm @@ -24,6 +24,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) user.face_atom(A) return FALSE +/datum/click_intercept/proc_holder + var/obj/effect/proc_holder/spell + +/datum/click_intercept/proc_holder/New(client/C, obj/effect/proc_holder/spell_to_cast) + . = ..() + spell = spell_to_cast + +/datum/click_intercept/proc_holder/InterceptClickOn(user, params, atom/object) + spell.InterceptClickOn(user, params, object) + +/datum/click_intercept/proc_holder/quit() + spell.remove_ranged_ability(spell.ranged_ability_user) + return ..() + /obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg) if(!user || !user.client) return @@ -32,7 +46,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) user.ranged_ability.remove_ranged_ability(user) user.ranged_ability = src ranged_ability_user = user - user.client.click_intercept = user.ranged_ability + user.client.click_intercept = new /datum/click_intercept/proc_holder(user.client, user.ranged_ability) add_mousepointer(user.client) active = TRUE if(msg) @@ -48,15 +62,17 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) C.mouse_pointer_icon = initial(C.mouse_pointer_icon) /obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg) - if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability + if(!user || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability return user.ranged_ability = null ranged_ability_user = null - user.client.click_intercept = null - remove_mousepointer(user.client) active = FALSE - if(msg) - to_chat(user, msg) + if(user.client) + qdel(user.client.click_intercept) + user.client.click_intercept = null + remove_mousepointer(user.client) + if(msg) + to_chat(user, msg) update_icon() /obj/effect/proc_holder/spell @@ -114,10 +130,14 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) var/sound = null //The sound the spell makes when it is cast -/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell - if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) - to_chat(user, "You shouldn't have this spell! Something's wrong.") - return 0 +/* Checks if the user can cast the spell + * @param charge_check If the proc should do the cooldown check + * @param start_recharge If the proc should set the cooldown + * @param user The caster of the spell +*/ +/obj/effect/proc_holder/spell/proc/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell + if(!can_cast(user, charge_check, TRUE)) + return FALSE if(ishuman(user)) var/mob/living/carbon/human/caster = user @@ -126,49 +146,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) caster.reset_perspective(0) return 0 - if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel - return 0 - - if(!skipcharge) - switch(charge_type) - if("recharge") - if(charge_counter < charge_max) - to_chat(user, still_recharging_msg) - return 0 - if("charges") - if(!charge_counter) - to_chat(user, "[name] has no charges left.") - return 0 - - if(!ghost) - if(user.stat && !stat_allowed) - to_chat(user, "You can't cast this spell while incapacitated.") - return 0 - if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled()) - to_chat(user, "Mmmf mrrfff!") - return 0 - - var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list())) - if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check - var/mob/living/carbon/human/H = user - var/obj/item/clothing/robe = H.wear_suit - var/obj/item/clothing/hat = H.head - var/obj/item/clothing/shoes = H.shoes - if(!robe || !hat || !shoes) - to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.") - return 0 - if(!robe.magical || !hat.magical || !shoes.magical) - to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.") - return 0 - else if(!ishuman(user)) - if(clothes_req || human_req) - to_chat(user, "This spell can only be cast by humans!") - return 0 - if(nonabstract_req && (isbrain(user) || ispAI(user))) - to_chat(user, "This spell can only be cast by physical beings!") - return 0 - - if(!skipcharge) + if(start_recharge) switch(charge_type) if("recharge") charge_counter = 0 //doesn't start recharging until the targets selecting ends @@ -442,6 +420,100 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) return +/obj/effect/proc_holder/spell/targeted/click + var/click_radius = 1 // How big the radius around the clicked atom is to find a suitable target. -1 is only the selected atom is considered + var/selection_activated_message = "Click on a target to cast the spell." + var/selection_deactivated_message = "You choose to not cast this spell." + var/allowed_type = /mob/living // Which type the targets have to be + var/auto_target_single = TRUE // If the spell should auto select a target if only one is found + +/obj/effect/proc_holder/spell/targeted/click/Click() + var/mob/living/user = usr + if(!istype(user)) + return + + if(active) + remove_ranged_ability(user, selection_deactivated_message) + else + if(cast_check(TRUE, FALSE, user)) + if(auto_target_single && attempt_auto_target(user)) + return + + add_ranged_ability(user, selection_activated_message) + else + to_chat(user, "[src] is not ready to be used yet.") + +/obj/effect/proc_holder/spell/targeted/click/proc/attempt_auto_target(mob/user) + var/atom/target + for(var/atom/A in view_or_range(range, user, selection_type)) + if(valid_target(A, user)) + if(target) + return FALSE // Two targets found. ABORT + target = A + + if(target && cast_check(TRUE, TRUE, user)) // Singular target found. Cast it instantly + to_chat(user, "Only one target found. Casting [src] on [target]!") + perform(list(target), user = user) + return TRUE + return FALSE + +/obj/effect/proc_holder/spell/targeted/click/InterceptClickOn(mob/living/user, params, atom/A) + if(..() || !cast_check(TRUE, TRUE, user)) + remove_ranged_ability(user) + revert_cast(user) + return TRUE + + var/list/targets = list() + if(valid_target(A, user)) + targets.Add(A) + + if((!max_targets || max_targets > targets.len) && click_radius >= 0) + var/list/found_others = list() + for(var/atom/target in range(click_radius, A)) + if(valid_target(target, user)) + found_others |= target + if(!max_targets) + targets.Add(found_others) + else + if(max_targets <= found_others.len + targets.len) + targets.Add(found_others) + else + switch(random_target_priority) //Add in the rest + if(TARGET_RANDOM) + while(targets.len < max_targets && found_others.len) // Add the others + targets.Add(pick_n_take(found_others)) + if(TARGET_CLOSEST) + var/list/distances = list() + for(var/target in found_others) + distances[target] = get_dist(user, target) + sortTim(distances, /proc/cmp_numeric_asc, TRUE) // Sort on distance + for(var/target in distances) + targets.Add(target) + if(targets.len >= max_targets) + break + + + if(!targets.len) + to_chat(user, "No suitable target found.") + revert_cast(user) + return FALSE + + perform(targets, user = user) + remove_ranged_ability(user) + return TRUE + +/* Checks if a target is valid + * Should not include to_chats or other types of messages since this is used often on tons of targets. + * @param target The target to check + * @param user The user of the spell +*/ +/obj/effect/proc_holder/spell/targeted/click/proc/valid_target(target, user) + return istype(target, allowed_type) && (include_user || target != user) && \ + (target in view_or_range(range, user, selection_type)) + +/obj/effect/proc_holder/spell/targeted/click/choose_targets(mob/living/user, atom/A) // Not used + return + /obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) var/list/targets = list() @@ -475,30 +547,39 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) qdel(dummy) return 1 -/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr) +/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) + if(show_message) + to_chat(user, "You shouldn't have this spell! Something's wrong.") return 0 if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel return 0 - switch(charge_type) - if("recharge") - if(charge_counter < charge_max) - return 0 - if("charges") - if(!charge_counter) - return 0 - - if(user.stat && !stat_allowed) - return 0 + if(charge_check) + switch(charge_type) + if("recharge") + if(charge_counter < charge_max) + if(show_message) + to_chat(user, still_recharging_msg) + return 0 + if("charges") + if(!charge_counter) + if(show_message) + to_chat(user, "[name] has no charges left.") + return 0 + if(!ghost) + if(user.stat && !stat_allowed) + if(show_message) + to_chat(user, "You can't cast this spell while incapacitated.") + return 0 + if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled()) + if(show_message) + to_chat(user, "Mmmf mrrfff!") + return 0 if(ishuman(user)) var/mob/living/carbon/human/H = user - - if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled()) - return 0 - var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list) if(clothes_req && !clothcheck && !clothcheck2) //clothes check @@ -506,12 +587,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) var/obj/item/clothing/hat = H.head var/obj/item/clothing/shoes = H.shoes if(!robe || !hat || !shoes) + if(show_message) + to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.") return 0 if(!robe.magical || !hat.magical || !shoes.magical) + if(show_message) + to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.") return 0 else if(clothes_req || human_req) + if(show_message) + to_chat(user, "This spell can only be cast by humans!") return 0 if(nonabstract_req && (isbrain(user) || ispAI(user))) + if(show_message) + to_chat(user, "This spell can only be cast by physical beings!") return 0 return 1 diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm index 0d24a984014..5986da3a412 100644 --- a/code/datums/spells/area_teleport.dm +++ b/code/datums/spells/area_teleport.dm @@ -11,7 +11,7 @@ /obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr) var/thearea = before_cast(targets) - if(!thearea || !cast_check(1)) + if(!thearea || !cast_check(TRUE, FALSE, user)) revert_cast() return invocation(thearea) diff --git a/code/datums/spells/chaplain.dm b/code/datums/spells/chaplain.dm index bdb7bb3f551..f16a2a3fbac 100644 --- a/code/datums/spells/chaplain.dm +++ b/code/datums/spells/chaplain.dm @@ -1,25 +1,31 @@ -/obj/effect/proc_holder/spell/targeted/chaplain_bless +/obj/effect/proc_holder/spell/targeted/click/chaplain_bless name = "Bless" desc = "Blesses a single person." school = "transmutation" charge_max = 60 - clothes_req = 0 + clothes_req = FALSE invocation = "none" invocation_type = "none" max_targets = 1 - include_user = 0 - humans_only = 1 - + include_user = FALSE + allowed_type = /mob/living/carbon/human + selection_activated_message = "You prepare a blessing. Click on a target to start blessing." + selection_deactivated_message = "The crew will be blessed another time." range = 1 + click_radius = -1 // Only precision clicking cooldown_min = 20 action_icon_state = "shield" +/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE -/obj/effect/proc_holder/spell/targeted/chaplain_bless/cast(list/targets, mob/living/user = usr, distanceoverride) + return target.mind && target.ckey && !target.stat +/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/cast(list/targets, mob/living/user = usr) if(!istype(user)) to_chat(user, "Somehow, you are not a living mob. This should never happen. Report this bug.") revert_cast() @@ -35,32 +41,7 @@ revert_cast() return - var/mob/living/carbon/human/target = targets[range] - - if(!istype(target)) - to_chat(user, "No target.") - revert_cast() - return - - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - to_chat(user, "[target] is too far away!") - revert_cast() - return - - if(!target.mind) - to_chat(user, "[target] appears to be catatonic. Your blessing would have no effect.") - revert_cast() - return - - if(!target.ckey) - to_chat(user, "[target] appears to be too out of it to benefit from this.") - revert_cast() - return - - if(target.stat == DEAD) - to_chat(user, "[target] is already dead. There is no point.") - revert_cast() - return + var/mob/living/carbon/human/target = targets[1] spawn(0) // allows cast to complete even if recipient ignores the prompt if(alert(target, "[user] wants to bless you, in the name of [user.p_their()] religion. Accept?", "Accept Blessing?", "Yes", "No") == "Yes") // prevents forced conversions diff --git a/code/datums/spells/devil.dm b/code/datums/spells/devil.dm index 1a1069481cf..f9e075bdf81 100644 --- a/code/datums/spells/devil.dm +++ b/code/datums/spells/devil.dm @@ -21,13 +21,18 @@ action_background_icon_state = "bg_demon" -/obj/effect/proc_holder/spell/targeted/summon_contract +/obj/effect/proc_holder/spell/targeted/click/summon_contract name = "Summon infernal contract" desc = "Skip making a contract by hand, just do it by magic." invocation_type = "whisper" invocation = "Just sign on the dotted line." - include_user = 0 + selection_activated_message = "You prepare a detailed contract. Click on a target to summon the contract in his hands." + selection_deactivated_message = "You archive the contract for later use." + include_user = FALSE range = 5 + auto_target_single = FALSE // Prevent an accidental contract from summoning + click_radius = -1 // Precision clicking required + allowed_type = /mob/living/carbon clothes_req = FALSE school = "conjuration" charge_max = 150 @@ -35,8 +40,9 @@ action_icon_state = "spell_default" action_background_icon_state = "bg_demon" -/obj/effect/proc_holder/spell/targeted/summon_contract/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) +/obj/effect/proc_holder/spell/targeted/click/summon_contract/cast(list/targets, mob/user = usr) + for(var/target in targets) + var/mob/living/carbon/C = target if(C.mind && user.mind) if(C.stat == DEAD) if(user.drop_item()) @@ -63,7 +69,7 @@ to_chat(user,"[C] seems to not be sentient. You are unable to summon a contract for them.") -/obj/effect/proc_holder/spell/fireball/hellish +/obj/effect/proc_holder/spell/targeted/click/fireball/hellish name = "Hellfire" desc = "This spell launches hellfire at the target." school = "evocation" @@ -74,7 +80,7 @@ fireball_type = /obj/item/projectile/magic/fireball/infernal action_background_icon_state = "bg_demon" -/obj/effect/proc_holder/spell/fireball/hellish/cast(list/targets, mob/living/user = usr) +/obj/effect/proc_holder/spell/targeted/click/fireball/hellish/cast(list/targets, mob/living/user = usr) add_attack_logs(user, targets, "has fired a Hellfire ball", ATKLOG_FEW) .=..() diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm index 0140fbdf951..d5f686fb763 100644 --- a/code/datums/spells/horsemask.dm +++ b/code/datums/spells/horsemask.dm @@ -1,39 +1,31 @@ -/obj/effect/proc_holder/spell/targeted/horsemask +/obj/effect/proc_holder/spell/targeted/click/horsemask name = "Curse of the Horseman" desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes." school = "transmutation" charge_type = "recharge" charge_max = 150 charge_counter = 0 - clothes_req = 0 - stat_allowed = 0 + clothes_req = FALSE + stat_allowed = FALSE invocation = "KN'A FTAGHU, PUCK 'BTHNK!" invocation_type = "shout" range = 7 cooldown_min = 30 //30 deciseconds reduction per rank selection_type = "range" + selection_activated_message = "You start to quietly neigh an incantation. Click on or near a target to cast the spell." + selection_deactivated_message = "You stop neighing to yourself." + allowed_type = /mob/living/carbon/human + action_icon_state = "barn" sound = 'sound/magic/HorseHead_curse.ogg' -/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr) +/obj/effect/proc_holder/spell/targeted/click/horsemask/cast(list/targets, mob/user = usr) if(!targets.len) to_chat(user, "No target found in range.") return - var/mob/living/carbon/target = targets[1] - - if(!target) - return - - - if(!ishuman(target)) - to_chat(user, "It'd be stupid to curse [target] with a horse's head!") - return - - if(!(target in oview(range)))//If they are not in overview after selection. - to_chat(user, "They are too far away!") - return + var/mob/living/carbon/human/target = targets[1] var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead magichead.flags |= NODROP | DROPDEL //curses! diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm index ed6b7cd543e..20cd8dccdf5 100644 --- a/code/datums/spells/lightning.dm +++ b/code/datums/spells/lightning.dm @@ -25,10 +25,10 @@ /obj/effect/proc_holder/spell/targeted/lightning/Click() if(!ready && start_time == 0) - if(cast_check()) + if(cast_check(TRUE, FALSE, usr)) StartChargeup() else - if(ready && cast_check(skipcharge=1)) + if(ready && cast_check(TRUE, TRUE, usr)) choose_targets() return 1 diff --git a/code/datums/spells/magnet.dm b/code/datums/spells/magnet.dm index dfdac6f1b9c..155a3cb0b45 100644 --- a/code/datums/spells/magnet.dm +++ b/code/datums/spells/magnet.dm @@ -20,10 +20,10 @@ /obj/effect/proc_holder/spell/targeted/magnet/Click() if(!ready && start_time == 0) - if(cast_check()) + if(cast_check(TRUE, FALSE, usr)) StartChargeup() else - if(ready && cast_check(skipcharge=1)) + if(ready && cast_check(TRUE, TRUE, usr)) choose_targets() return 1 diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm index 3d9031a8e03..e4924ee1c2f 100644 --- a/code/datums/spells/mind_transfer.dm +++ b/code/datums/spells/mind_transfer.dm @@ -1,4 +1,4 @@ -/obj/effect/proc_holder/spell/targeted/mind_transfer +/obj/effect/proc_holder/spell/targeted/click/mind_transfer name = "Mind Transfer" desc = "This spell allows the user to switch bodies with a target." @@ -8,33 +8,29 @@ invocation = "GIN'YU CAPAN" invocation_type = "whisper" range = 1 + click_radius = 0 // Still gotta be pretty accurate + selection_activated_message = "You prepare to transfer your mind. Click on a target to cast the spell." + selection_deactivated_message = "You decide that your current form is good enough." cooldown_min = 200 //100 deciseconds reduction per rank var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell action_icon_state = "mindswap" +/obj/effect/proc_holder/spell/targeted/click/mind_transfer/valid_target(mob/living/target, user) + if(!..()) + return FALSE + return target.stat != DEAD && target.key && target.mind + /* Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering. Also, you never added distance checking after target is selected. I've went ahead and did that. */ -/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/user = usr, distanceoverride) +/obj/effect/proc_holder/spell/targeted/click/mind_transfer/cast(list/targets, mob/user = usr) var/mob/living/target = targets[range] - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - to_chat(user, "They are too far away!") - return - - if(target.stat == DEAD) - to_chat(user, "You don't particularly want to be dead.") - return - - if(!target.key || !target.mind) - to_chat(user, "[target.p_they(TRUE)] appear[target.p_s()] to be catatonic. Not even magic can affect [target.p_their()] vacant mind.") - return - if(user.suiciding) to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") return diff --git a/code/datums/spells/wizard.dm b/code/datums/spells/wizard.dm index 94383f1b1e2..a99655222a4 100644 --- a/code/datums/spells/wizard.dm +++ b/code/datums/spells/wizard.dm @@ -308,76 +308,50 @@ duration = 300 sound = 'sound/magic/blind.ogg' -/obj/effect/proc_holder/spell/fireball +/obj/effect/proc_holder/spell/targeted/click/fireball name = "Fireball" desc = "This spell fires a fireball at a target and does not require wizard garb." school = "evocation" charge_max = 60 - clothes_req = 0 + clothes_req = FALSE invocation = "ONI SOMA" invocation_type = "shout" + auto_target_single = FALSE // Having this true won't ever find a single target and is just lost processing power range = 20 cooldown_min = 20 //10 deciseconds reduction per rank + + click_radius = -1 + selection_activated_message = "Your prepare to cast your fireball spell! Left-click to cast at a target!" + selection_deactivated_message = "You extinguish your fireball...for now." + allowed_type = /atom // FIRE AT EVERYTHING + var/fireball_type = /obj/item/projectile/magic/fireball action_icon_state = "fireball0" sound = 'sound/magic/fireball.ogg' active = FALSE -/obj/effect/proc_holder/spell/fireball/Click() - var/mob/living/user = usr - if(!istype(user)) - return - - var/msg - - if(!can_cast(user)) - msg = "You can no longer cast Fireball." - remove_ranged_ability(user, msg) - return - - if(active) - msg = "You extinguish your fireball...for now." - remove_ranged_ability(user, msg) - else - msg = "Your prepare to cast your fireball spell! Left-click to cast at a target!" - add_ranged_ability(user, msg) - -/obj/effect/proc_holder/spell/fireball/update_icon() +/obj/effect/proc_holder/spell/targeted/click/fireball/update_icon() if(!action) return action.button_icon_state = "fireball[active]" action.UpdateButtonIcon() -/obj/effect/proc_holder/spell/fireball/InterceptClickOn(mob/living/user, params, atom/target) - if(..()) - return FALSE - - if(!cast_check(0, user)) - remove_ranged_ability(user) - return FALSE - - var/list/targets = list(target) - perform(targets, user = user) - - return TRUE - -/obj/effect/proc_holder/spell/fireball/cast(list/targets, mob/living/user = usr) +/obj/effect/proc_holder/spell/targeted/click/fireball/cast(list/targets, mob/living/user = usr) var/target = targets[1] //There is only ever one target for fireball var/turf/T = user.loc var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction if(!isturf(U) || !isturf(T)) - return 0 + return FALSE var/obj/item/projectile/magic/fireball/FB = new fireball_type(user.loc) FB.current = get_turf(user) FB.preparePixelProjectile(target, get_turf(target), user) FB.fire() user.newtonian_move(get_dir(U, T)) - remove_ranged_ability(user) - return 1 + return TRUE /obj/effect/proc_holder/spell/aoe_turf/repulse name = "Repulse" diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm index 8a29d2cb0d0..42f74e234a0 100644 --- a/code/datums/uplink_item.dm +++ b/code/datums/uplink_item.dm @@ -1563,11 +1563,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) cost = 10 /datum/uplink_item/cyber_implants/antistun - name = "CNS Rebooter Implant" - desc = "This implant will help you get back up on your feet faster after being stunned. \ + name = "Hardened CNS Rebooter Implant" + desc = "This implant will help you get back up on your feet faster after being stunned. It is invulnerable to EMPs. \ Comes with an automated implanting tool." reference = "CIAS" - item = /obj/item/organ/internal/cyberimp/brain/anti_stun + item = /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened cost = 12 /datum/uplink_item/cyber_implants/reviver diff --git a/code/game/dna/genes/goon_powers.dm b/code/game/dna/genes/goon_powers.dm index 87bbdf97c5c..77eb58e84b7 100644 --- a/code/game/dna/genes/goon_powers.dm +++ b/code/game/dna/genes/goon_powers.dm @@ -123,13 +123,13 @@ instability = GENE_INSTABILITY_MODERATE mutation = CRYO - spelltype = /obj/effect/proc_holder/spell/targeted/cryokinesis + spelltype = /obj/effect/proc_holder/spell/targeted/click/cryokinesis /datum/dna/gene/basic/grant_spell/cryo/New() ..() block = GLOB.cryoblock -/obj/effect/proc_holder/spell/targeted/cryokinesis +/obj/effect/proc_holder/spell/targeted/click/cryokinesis name = "Cryokinesis" desc = "Drops the bodytemperature of another person." panel = "Abilities" @@ -137,45 +137,44 @@ charge_type = "recharge" charge_max = 1200 - clothes_req = 0 - stat_allowed = 0 + clothes_req = FALSE + stat_allowed = FALSE + + click_radius = 0 + auto_target_single = FALSE // Give the clueless geneticists a way out and to have them not target themselves + selection_activated_message = "Your mind grow cold. Click on a target to cast the spell." + selection_deactivated_message = "Your mind returns to normal." + allowed_type = /mob/living/carbon invocation_type = "none" range = 7 selection_type = "range" - include_user = 1 + include_user = TRUE var/list/compatible_mobs = list(/mob/living/carbon/human) action_icon_state = "genetic_cryo" -/obj/effect/proc_holder/spell/targeted/cryokinesis/cast(list/targets, mob/user = usr) - if(!targets.len) - to_chat(user, "No target found in range.") - return +/obj/effect/proc_holder/spell/targeted/click/cryokinesis/cast(list/targets, mob/user = usr) var/mob/living/carbon/C = targets[1] - if(!iscarbon(C)) - to_chat(user, "This will only work on normal organic beings.") - return - if(COLDRES in C.mutations) C.visible_message("A cloud of fine ice crystals engulfs [C.name], but disappears almost instantly!") return - var/handle_suit = 0 + var/handle_suit = FALSE if(ishuman(C)) var/mob/living/carbon/human/H = C if(istype(H.head, /obj/item/clothing/head/helmet/space)) if(istype(H.wear_suit, /obj/item/clothing/suit/space)) - handle_suit = 1 + handle_suit = TRUE if(H.internal) H.visible_message("[user] sprays a cloud of fine ice crystals, engulfing [H]!", "[user] sprays a cloud of fine ice crystals over your [H.head]'s visor.") - add_attack_logs(user, C, "Cryokinesis") else H.visible_message("[user] sprays a cloud of fine ice crystals engulfing, [H]!", "[user] sprays a cloud of fine ice crystals cover your [H.head]'s visor and make it into your air vents!.") - add_attack_logs(user, C, "Cryokinesis") + H.bodytemperature = max(0, H.bodytemperature - 100) + add_attack_logs(user, C, "Cryokinesis") if(!handle_suit) C.bodytemperature = max(0, C.bodytemperature - 200) C.ExtinguishMob() @@ -454,7 +453,7 @@ name = "Polymorphism" desc = "Enables the subject to reconfigure their appearance to mimic that of others." - spelltype =/obj/effect/proc_holder/spell/targeted/polymorph + spelltype =/obj/effect/proc_holder/spell/targeted/click/polymorph //cooldown = 1800 activation_messages = list("You don't feel entirely like yourself somehow.") deactivation_messages = list("You feel secure in your identity.") @@ -465,34 +464,36 @@ ..() block = GLOB.polymorphblock -/obj/effect/proc_holder/spell/targeted/polymorph +/obj/effect/proc_holder/spell/targeted/click/polymorph name = "Polymorph" desc = "Mimic the appearance of others!" panel = "Abilities" charge_max = 1800 - clothes_req = 0 - human_req = 1 - stat_allowed = 0 + clothes_req = FALSE + stat_allowed = FALSE + + click_radius = -1 // Precision required + auto_target_single = FALSE // Safety to not turn into monkey (420) + selection_activated_message = "You body becomes unstable. Click on a target to cast transform into them." + selection_deactivated_message = "Your body calms down again." + allowed_type = /mob/living/carbon/human + invocation_type = "none" range = 1 selection_type = "range" action_icon_state = "genetic_poly" -/obj/effect/proc_holder/spell/targeted/polymorph/cast(list/targets, mob/user = usr) - var/mob/living/M = targets[1] - if(!ishuman(M)) - to_chat(usr, "You can only change your appearance to that of another human.") - return +/obj/effect/proc_holder/spell/targeted/click/polymorph/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/target = targets[1] user.visible_message("[user]'s body shifts and contorts.") spawn(10) - if(M && user) + if(target && user) playsound(user.loc, 'sound/goonstation/effects/gib.ogg', 50, 1) var/mob/living/carbon/human/H = user - var/mob/living/carbon/human/target = M H.UpdateAppearance(target.dna.UI) H.real_name = target.real_name H.name = target.name diff --git a/code/game/dna/genes/vg_powers.dm b/code/game/dna/genes/vg_powers.dm index 9092448e398..e08d35b681d 100644 --- a/code/game/dna/genes/vg_powers.dm +++ b/code/game/dna/genes/vg_powers.dm @@ -214,25 +214,21 @@ /obj/effect/proc_holder/spell/targeted/remotetalk/choose_targets(mob/user = usr) var/list/targets = new /list() - var/list/validtargets = new /list() - var/turf/T = get_turf(user) - for(var/mob/living/M in range(14, T)) - if(M && M.mind) - if(M == user) - continue - validtargets += M + var/list/validtargets = user.get_telepathic_targets() - if(!validtargets.len) + if(!length(validtargets)) to_chat(user, "There are no valid targets!") start_recharge() return - targets += input("Choose the target to talk to.", "Targeting") as null|mob in validtargets + var/target_name = input("Choose the target to talk to.", "Targeting") as null|anything in validtargets - if(!targets.len || !targets[1]) //doesn't waste the spell + var/mob/living/target + if(!target_name || !(target = validtargets[target_name])) revert_cast(user) return + targets += target perform(targets, user = user) /obj/effect/proc_holder/spell/targeted/remotetalk/cast(list/targets, mob/user = usr) @@ -249,7 +245,7 @@ target.show_message("You hear [user.real_name]'s voice: [say]") else target.show_message("You hear a voice that seems to echo around the room: [say]") - user.show_message("You project your mind into [target.name]: [say]") + user.show_message("You project your mind into [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"]: [say]") for(var/mob/dead/observer/G in GLOB.player_list) G.show_message("Telepathic message from [user] ([ghost_follow_link(user, ghost=G)]) to [target] ([ghost_follow_link(target, ghost=G)]): [say]") @@ -266,26 +262,22 @@ var/list/available_targets = list() /obj/effect/proc_holder/spell/targeted/mindscan/choose_targets(mob/user = usr) - var/list/targets = new /list() - var/list/validtargets = new /list() - var/turf/T = get_turf(user) - for(var/mob/living/M in range(14, T)) - if(M && M.mind) - if(M == user) - continue - validtargets += M + var/list/targets = list() + var/list/validtargets = user.get_telepathic_targets() - if(!validtargets.len) + if(!length(validtargets)) to_chat(user, "There are no valid targets!") start_recharge() return - targets += input("Choose the target to listen to.", "Targeting") as null|mob in validtargets + var/target_name = input("Choose the target to listen to.", "Targeting") as null|anything in validtargets - if(!targets.len || !targets[1]) //doesn't waste the spell + var/mob/living/target + if(!target_name || !(target = validtargets[target_name])) revert_cast(user) return + targets += target perform(targets, user = user) /obj/effect/proc_holder/spell/targeted/mindscan/cast(list/targets, mob/user = usr) @@ -295,7 +287,7 @@ var/message = "You feel your mind expand briefly... (Click to send a message.)" if(REMOTE_TALK in target.mutations) message = "You feel [user.real_name] request a response from you... (Click here to project mind.)" - user.show_message("You offer your mind to [target.name].") + user.show_message("You offer your mind to [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"].") target.show_message("[message]") available_targets += target addtimer(CALLBACK(src, .proc/removeAvailability, target), 100) diff --git a/code/game/gamemodes/devil/devilinfo.dm b/code/game/gamemodes/devil/devilinfo.dm index ecbeb54f7f7..22a5f4a6cde 100644 --- a/code/game/gamemodes/devil/devilinfo.dm +++ b/code/game/gamemodes/devil/devilinfo.dm @@ -92,7 +92,7 @@ GLOBAL_LIST_INIT(lawlorify, list ( var/form = BASIC_DEVIL var/exists = 0 var/static/list/dont_remove_spells = list( - /obj/effect/proc_holder/spell/targeted/summon_contract, + /obj/effect/proc_holder/spell/targeted/click/summon_contract, /obj/effect/proc_holder/spell/targeted/conjure_item/violin, /obj/effect/proc_holder/spell/targeted/summon_dancefloor) var/ascendable = FALSE @@ -326,12 +326,12 @@ GLOBAL_LIST_INIT(lawlorify, list ( owner.RemoveSpell(S) /datum/devilinfo/proc/give_summon_contract() - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_contract(null)) + owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/summon_contract(null)) /datum/devilinfo/proc/give_base_spells(give_summon_contract = 0) remove_spells() - owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null)) + owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null)) owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null)) if(give_summon_contract) give_summon_contract() @@ -343,13 +343,13 @@ GLOBAL_LIST_INIT(lawlorify, list ( /datum/devilinfo/proc/give_lizard_spells() remove_spells() owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null)) + owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null)) owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null)) /datum/devilinfo/proc/give_true_spells() remove_spells() owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null)) + owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null)) owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null)) owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch(null)) diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm index 91ed583efc7..47e5e759c77 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm @@ -153,7 +153,7 @@ else name = "[initial(name)] ([cast_amount]E)" -/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr) +/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr, charge_check = TRUE, show_message = FALSE) if(user.inhibited) return 0 if(charge_counter < charge_max) diff --git a/code/game/gamemodes/shadowling/shadowling_abilities.dm b/code/game/gamemodes/shadowling/shadowling_abilities.dm index 7692aa202b2..4be50a6cd59 100644 --- a/code/game/gamemodes/shadowling/shadowling_abilities.dm +++ b/code/game/gamemodes/shadowling/shadowling_abilities.dm @@ -18,80 +18,56 @@ return 0 -/obj/effect/proc_holder/spell/targeted/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling +/obj/effect/proc_holder/spell/targeted/click/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling name = "Glare" desc = "Stuns and mutes a target for a decent duration. Duration depends on the proximity to the target." panel = "Shadowling Abilities" charge_max = 300 - clothes_req = 0 + clothes_req = FALSE range = 10 //has no effect beyond this range, so setting this makes invalid/useless targets not show up in popup action_icon_state = "glare" - humans_only = 1 //useless since we override chose_targets, but might be used for other code later??? Might remove, idk -/obj/effect/proc_holder/spell/targeted/glare/choose_targets(mob/user) - var/list/possible_targets = list() - for(var/mob/living/carbon/human/target in view_or_range(range, user, "view")) - if(target.stat) - continue - if(is_shadow_or_thrall(target)) - continue - possible_targets += target - var/mob/living/carbon/human/M - var/list/targets = list() - if(possible_targets.len == 1)//no choice involved - targets = possible_targets - else - M = input("Choose the target for the spell.", "Targeting") as mob in possible_targets - if(M in view_or_range(range, user, "view")) - targets += M + selection_activated_message = "Your prepare to your eyes for a stunning glare! Left-click to cast at a target!" + selection_deactivated_message = "Your eyes relax... for now." + allowed_type = /mob/living/carbon/human +/obj/effect/proc_holder/spell/targeted/click/glare/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) + if(!shadowling_check(user)) + return FALSE + return ..() - if(!targets.len) //doesn't waste the spell - revert_cast(user) +/obj/effect/proc_holder/spell/targeted/click/glare/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + return !target.stat && !is_shadow_or_thrall(target) + +/obj/effect/proc_holder/spell/targeted/click/glare/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/H = targets[1] + + user.visible_message("[user]'s eyes flash a blinding red!") + var/distance = get_dist(H, user) + if (distance <= 1) //Melee glare + H.visible_message("[H] freezes in place, [H.p_their()] eyes glazing over...", \ + "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...") + H.Stun(10) + H.AdjustSilence(10) + else //Distant glare + var/loss = 10 - distance + var/duration = 10 - loss + if(loss <= 0) + to_chat(user, "Your glare had no effect over a such long distance!") + return + H.slowed = duration + H.AdjustSilence(10) + to_chat(H, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..") + addtimer(CALLBACK(src, .proc/do_stun, H, user, loss), duration SECONDS) + +/obj/effect/proc_holder/spell/targeted/click/glare/proc/do_stun(mob/living/carbon/human/target, user, stun_time) + if(!istype(target) || target.stat) return - - perform(targets, user = user) - return - - -/obj/effect/proc_holder/spell/targeted/glare/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/human/target in targets) - if(!ishuman(target)) - to_chat(user, "You may only glare at humans!") - charge_counter = charge_max - return - if(!shadowling_check(user)) - charge_counter = charge_max - return - if(target.stat) - to_chat(user, "[target] must be conscious!") - charge_counter = charge_max - return - if(is_shadow_or_thrall(target)) - to_chat(user, "You don't see why you would want to paralyze an ally.") - charge_counter = charge_max - return - var/mob/living/carbon/human/M = target - user.visible_message("[user]'s eyes flash a blinding red!") - var/distance = get_dist(target, user) - if (distance <= 1) //Melee glare - target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...") - to_chat(target, "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...") - target.Stun(10) - M.AdjustSilence(10) - else //Distant glare - var/loss = 10 - distance - var/duration = 10 - loss - if(loss <= 0) - to_chat(user, "Your glare had no effect over a such long distance!") - return - target.slowed = duration - M.AdjustSilence(10) - to_chat(target, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..") - sleep(duration*10) - target.Stun(loss) - target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...") - to_chat(target, "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...") + target.Stun(stun_time) + target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...",\ + "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...") /obj/effect/proc_holder/spell/aoe_turf/veil name = "Veil" @@ -228,96 +204,80 @@ M.reagents.add_reagent("frostoil", 15) //Half of a cryosting -/obj/effect/proc_holder/spell/targeted/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties +/obj/effect/proc_holder/spell/targeted/click/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties name = "Enthrall" desc = "Allows you to enslave a conscious, non-braindead, non-catatonic human to your will. This takes some time to cast." panel = "Shadowling Abilities" charge_max = 0 - clothes_req = 0 + clothes_req = FALSE range = 1 //Adjacent to user - var/enthralling = 0 + var/enthralling = FALSE action_icon_state = "enthrall" - humans_only = 1 -/obj/effect/proc_holder/spell/targeted/enthrall/cast(list/targets, mob/user = usr) + click_radius = -1 // Precision baby + selection_activated_message = "Your prepare your mind to entrall a mortal. Left-click to cast at a target!" + selection_deactivated_message = "Your mind relaxes." + allowed_type = /mob/living/carbon/human + +/obj/effect/proc_holder/spell/targeted/click/enthrall/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) + if(enthralling || !shadowling_check(user)) + return FALSE + return ..() + +/obj/effect/proc_holder/spell/targeted/click/enthrall/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + return target.key && target.mind && !target.stat && !is_shadow_or_thrall(target) && target.client + +/obj/effect/proc_holder/spell/targeted/click/enthrall/cast(list/targets, mob/user = usr) var/mob/living/carbon/human/ling = user listclearnulls(SSticker.mode.shadowling_thralls) if(!(ling.mind in SSticker.mode.shadows)) return - if(!isshadowling(ling)) - if(SSticker.mode.shadowling_thralls.len >= 5) - charge_counter = charge_max - return - for(var/mob/living/carbon/human/target in targets) - if(!in_range(user, target)) - to_chat(user, "You need to be closer to enthrall [target].") - charge_counter = charge_max - return - if(!target.key || !target.mind) - to_chat(user, "The target has no mind.") - charge_counter = charge_max - return - if(target.stat) - to_chat(user, "The target must be conscious.") - charge_counter = charge_max - return - if(is_shadow_or_thrall(target)) - to_chat(user, "You can not enthrall allies.") - charge_counter = charge_max - return - if(!ishuman(target)) - to_chat(user, "You can only enthrall humans.") - charge_counter = charge_max - return - if(enthralling) - to_chat(user, "You are already enthralling!") - charge_counter = charge_max - return - if(!target.client) - to_chat(user, "[target]'s mind is vacant of activity.") - enthralling = 1 - to_chat(user, "This target is valid. You begin the enthralling.") - to_chat(target, "[user] stares at you. You feel your head begin to pulse.") + var/mob/living/carbon/human/target = targets[1] + enthralling = TRUE + to_chat(user, "This target is valid. You begin the enthralling.") + to_chat(target, "[user] stares at you. You feel your head begin to pulse.") - for(var/progress = 0, progress <= 3, progress++) - switch(progress) - if(1) - to_chat(user, "You place your hands to [target]'s head...") - user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!") - if(2) - to_chat(user, "You begin preparing [target]'s mind as a blank slate...") - user.visible_message("[user]'s palms flare a bright red against [target]'s temples!") - to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.") - target.Weaken(12) - sleep(20) - if(ismindshielded(target)) - to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.") - user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!") - to_chat(target, "Your mindshield implant becomes hot as it comes under attack!") - sleep(100) //10 seconds - not spawn() so the enthralling takes longer - to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.") - user.visible_message("[user] relaxes again.") - for(var/obj/item/implant/mindshield/L in target) - if(L && L.implanted) - qdel(L) - to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.") - if(3) - to_chat(user, "You begin planting the tumor that will control the new thrall...") - user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!") - to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.") - if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant - to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.") - to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself") - enthralling = 0 - return + for(var/progress = 0, progress <= 3, progress++) + switch(progress) + if(1) + to_chat(user, "You place your hands to [target]'s head...") + user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!") + if(2) + to_chat(user, "You begin preparing [target]'s mind as a blank slate...") + user.visible_message("[user]'s palms flare a bright red against [target]'s temples!") + to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.") + target.Weaken(12) + sleep(20) + if(ismindshielded(target)) + to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.") + user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!") + to_chat(target, "Your mindshield implant becomes hot as it comes under attack!") + sleep(100) //10 seconds - not spawn() so the enthralling takes longer + to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.") + user.visible_message("[user] relaxes again.") + for(var/obj/item/implant/mindshield/L in target) + if(L && L.implanted) + qdel(L) + to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.") + if(3) + to_chat(user, "You begin planting the tumor that will control the new thrall...") + user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!") + to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.") + if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant + to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.") + to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself") + enthralling = FALSE + return - enthralling = 0 - to_chat(user, "You have enthralled [target]!") - target.visible_message("[target] looks to have experienced a revelation!", \ - "False faces all dark not real not real not--") - target.setOxyLoss(0) //In case the shadowling was choking them out - SSticker.mode.add_thrall(target.mind) - target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL + enthralling = FALSE + to_chat(user, "You have enthralled [target]!") + target.visible_message("[target] looks to have experienced a revelation!", \ + "False faces all dark not real not real not--") + target.setOxyLoss(0) //In case the shadowling was choking them out + SSticker.mode.add_thrall(target.mind) + target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL /obj/effect/proc_holder/spell/targeted/shadowling_regenarmor //Resets a shadowling's species to normal, removes genetic defects, and re-equips their armor name = "Rapid Re-Hatch" @@ -405,7 +365,7 @@ reviveThrallAcquired = 1 to_chat(target, "The power of your thralls has granted you the Black Recuperation ability. \ This will, after a short time, bring a dead thrall completely back to life with no bodily defects.") - target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/reviveThrall(null)) + target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/reviveThrall(null)) if(thralls < victory_threshold) to_chat(target, "You do not have the power to ascend. You require [victory_threshold] thralls, but only [thralls] living thralls are present.") @@ -571,169 +531,170 @@ -/obj/effect/proc_holder/spell/targeted/reviveThrall +/obj/effect/proc_holder/spell/targeted/click/reviveThrall name = "Black Recuperation" desc = "Revives or empowers a thrall." panel = "Shadowling Abilities" range = 1 charge_max = 600 - clothes_req = 0 - include_user = 0 + clothes_req = FALSE + include_user = FALSE action_icon_state = "revive_thrall" - humans_only = 1 + click_radius = -1 // Precision baby + selection_activated_message = "You start focusing your powers on mending wounds of allies. Left-click to cast at a target!" + selection_deactivated_message = "Your mind relaxes." + allowed_type = /mob/living/carbon/human -/obj/effect/proc_holder/spell/targeted/reviveThrall/cast(list/targets, mob/user = usr) +/obj/effect/proc_holder/spell/targeted/click/reviveThrall/can_cast(mob/user = usr) if(!shadowling_check(user)) - charge_counter = charge_max - return - for(var/mob/living/carbon/human/thrallToRevive in targets) - var/choice = alert(user,"Empower a living thrall or revive a dead one?",,"Empower","Revive","Cancel") - switch(choice) - if("Empower") - if(!is_thrall(thrallToRevive)) - to_chat(user, "[thrallToRevive] is not a thrall.") - charge_counter = charge_max - return - if(thrallToRevive.stat != CONSCIOUS) - to_chat(user, "[thrallToRevive] must be conscious to become empowered.") - charge_counter = charge_max - return - if(isshadowlinglesser(thrallToRevive)) - to_chat(user, "[thrallToRevive] is already empowered.") - charge_counter = charge_max - return - var/empowered_thralls = 0 - for(var/datum/mind/M in SSticker.mode.shadowling_thralls) - if(!ishuman(M.current)) - return - var/mob/living/carbon/human/H = M.current - if(isshadowlinglesser(H)) - empowered_thralls++ - if(empowered_thralls >= EMPOWERED_THRALL_LIMIT) - to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.") - charge_counter = charge_max - return - user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \ - "You place your hands on [thrallToRevive]'s face and begin gathering energy...") - to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...") - if(!do_mob(user, thrallToRevive, 80)) - to_chat(user, "Your concentration snaps. The flow of energy ebbs.") - charge_counter = charge_max - return - to_chat(user, "You release a massive surge of power into [thrallToRevive]!") - user.visible_message("Red lightning surges into [thrallToRevive]'s face!") - playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1) - playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1) - user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1) - thrallToRevive.Weaken(5) - thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \ - "AAAAAAAAAAAAAAAAAAAGH-") - sleep(20) - thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \ - "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \ - darkness but wither slowly in light. In addition, you now have glare and true shadow walk.") - thrallToRevive.set_species(/datum/species/shadow/ling/lesser) - thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk) - thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null)) - thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null)) - if("Revive") - if(!is_thrall(thrallToRevive)) - to_chat(user, "[thrallToRevive] is not a thrall.") - charge_counter = charge_max - return - if(thrallToRevive.stat != DEAD) - to_chat(user, "[thrallToRevive] is not dead.") - charge_counter = charge_max - return - user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \ - "You crouch over the body of your thrall and begin gathering energy...") - thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive) - if(!do_mob(user, thrallToRevive, 30)) - to_chat(user, "Your concentration snaps. The flow of energy ebbs.") - charge_counter = charge_max - return - to_chat(user, "You release a massive surge of power into [thrallToRevive]!") - user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!") - playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1) - playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1) - user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1) - sleep(10) - if(thrallToRevive.revive()) - thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \ - "You have returned. One of your masters has brought you from the darkness beyond.") - thrallToRevive.Weaken(4) - thrallToRevive.emote("gasp") - playsound(thrallToRevive, "bodyfall", 50, 1) - else - charge_counter = charge_max - return + return FALSE + return ..() -/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle +/obj/effect/proc_holder/spell/targeted/click/reviveThrall/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + + return is_thrall(target) + +/obj/effect/proc_holder/spell/targeted/click/reviveThrall/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/thrallToRevive = targets[1] + if(thrallToRevive.stat == CONSCIOUS) + if(isshadowlinglesser(thrallToRevive)) + to_chat(user, "[thrallToRevive] is already empowered.") + revert_cast(user) + return + var/empowered_thralls = 0 + for(var/datum/mind/M in SSticker.mode.shadowling_thralls) + if(!ishuman(M.current)) + return + var/mob/living/carbon/human/H = M.current + if(isshadowlinglesser(H)) + empowered_thralls++ + if(empowered_thralls >= EMPOWERED_THRALL_LIMIT) + to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.") + revert_cast(user) + return + user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \ + "You place your hands on [thrallToRevive]'s face and begin gathering energy...") + to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...") + if(!do_mob(user, thrallToRevive, 80)) + to_chat(user, "Your concentration snaps. The flow of energy ebbs.") + revert_cast(user) + return + to_chat(user, "You release a massive surge of power into [thrallToRevive]!") + user.visible_message("Red lightning surges into [thrallToRevive]'s face!") + playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1) + playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1) + user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1) + thrallToRevive.Weaken(5) + thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \ + "AAAAAAAAAAAAAAAAAAAGH-") + sleep(20) + thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \ + "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \ + darkness but wither slowly in light. In addition, you now have glare and true shadow walk.") + thrallToRevive.set_species(/datum/species/shadow/ling/lesser) + thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk) + thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null)) + thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null)) + else if(thrallToRevive.stat == DEAD) + user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \ + "You crouch over the body of your thrall and begin gathering energy...") + thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive) + if(!do_mob(user, thrallToRevive, 30)) + to_chat(user, "Your concentration snaps. The flow of energy ebbs.") + revert_cast(user) + return + to_chat(user, "You release a massive surge of power into [thrallToRevive]!") + user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!") + playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1) + playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1) + user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1) + sleep(10) + if(thrallToRevive.revive()) + thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \ + "You have returned. One of your masters has brought you from the darkness beyond.") + thrallToRevive.Weaken(4) + thrallToRevive.emote("gasp") + playsound(thrallToRevive, "bodyfall", 50, 1) + else + to_chat(user, "The target must be awake to empower or dead to revive.") + revert_cast(user) + +/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle name = "Destroy Engines" desc = "Extends the time of the emergency shuttle's arrival by ten minutes using a life force of our enemy. Shuttle will be unable to be recalled. This can only be used once." panel = "Shadowling Abilities" range = 1 - clothes_req = 0 + clothes_req = FALSE charge_max = 600 + click_radius = -1 // Precision baby + selection_activated_message = "You start gathering destructive powers to delay the shuttle. Left-click to cast at a target!" + selection_deactivated_message = "Your mind relaxes." + allowed_type = /mob/living/carbon/human action_icon_state = "extend_shuttle" var/global/extendlimit = 0 -/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle/cast(list/targets, mob/user = usr) +/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) if(!shadowling_check(user)) - charge_counter = charge_max - return + return FALSE if(extendlimit == 1) - to_chat(user, "Shuttle was already delayed.") - charge_counter = charge_max - return - for(var/mob/living/carbon/human/target in targets) - if(target.stat) - charge_counter = charge_max - return - if(is_shadow_or_thrall(target)) - to_chat(user, "[target] must not be an ally.") - charge_counter = charge_max - return - if(SSshuttle.emergency.mode != SHUTTLE_CALL) + if(show_message) + to_chat(user, "Shuttle was already delayed.") + return FALSE + if(SSshuttle.emergency.mode != SHUTTLE_CALL) + if(show_message) to_chat(user, "The shuttle must be inbound only to the station.") - charge_counter = charge_max - return - var/mob/living/carbon/human/M = target - user.visible_message("[user]'s eyes flash a bright red!", \ - "You begin to draw [M]'s life force.") - M.visible_message("[M]'s face falls slack, [M.p_their()] jaw slightly distending.", \ - "You are suddenly transported... far, far away...") - extendlimit = 1 - if(!do_after(user, 150, target = M)) - extendlimit = 0 - to_chat(M, "You are snapped back to reality, your haze dissipating!") - to_chat(user, "You have been interrupted. The draw has failed.") - return - to_chat(user, "You project [M]'s life force toward the approaching shuttle, extending its arrival duration!") - M.visible_message("[M]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \ - "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...") - M.death() - if(SSshuttle.emergency.mode == SHUTTLE_CALL) - var/more_minutes = 6000 - var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes - GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg') - SSshuttle.emergency.setTimer(timer) - SSshuttle.emergency.canRecall = FALSE - user.mind.spell_list.Remove(src) //Can only be used once! - qdel(src) + return FALSE + return ..() + +/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + return !target.stat && !is_shadow_or_thrall(target) + + +/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/target = targets[1] + + user.visible_message("[user]'s eyes flash a bright red!", \ + "You begin to draw [target]'s life force.") + target.visible_message("[target]'s face falls slack, [target.p_their()] jaw slightly distending.", \ + "You are suddenly transported... far, far away...") + extendlimit = 1 + if(!do_after(user, 150, target = target)) + extendlimit = 0 + to_chat(target, "You are snapped back to reality, your haze dissipating!") + to_chat(user, "You have been interrupted. The draw has failed.") + return + to_chat(user, "You project [target]'s life force toward the approaching shuttle, extending its arrival duration!") + target.visible_message("[target]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \ + "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...") + target.death() + if(SSshuttle.emergency.mode == SHUTTLE_CALL) + var/more_minutes = 6000 + var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes + GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg') + SSshuttle.emergency.setTimer(timer) + SSshuttle.emergency.canRecall = FALSE + user.mind.spell_list.Remove(src) //Can only be used once! + qdel(src) // ASCENDANT ABILITIES BEYOND THIS POINT // -/obj/effect/proc_holder/spell/targeted/annihilate +/obj/effect/proc_holder/spell/targeted/click/annihilate name = "Annihilate" desc = "Gibs someone instantly." panel = "Ascendant" range = 7 - charge_max = 0 - clothes_req = 0 + charge_max = FALSE + clothes_req = FALSE action_icon_state = "annihilate" + selection_activated_message = "You start thinking about gibs. Left-click to cast at a target!" + selection_deactivated_message = "Your mind relaxes." + allowed_type = /mob/living/carbon/human -/obj/effect/proc_holder/spell/targeted/annihilate/cast(list/targets, mob/user = usr) +/obj/effect/proc_holder/spell/targeted/click/annihilate/cast(list/targets, mob/user = usr) var/mob/living/simple_animal/ascendant_shadowling/SHA = user if(SHA.phasing) to_chat(user, "You are not in the same plane of existence. Unphase first.") @@ -756,45 +717,42 @@ -/obj/effect/proc_holder/spell/targeted/hypnosis +/obj/effect/proc_holder/spell/targeted/click/hypnosis name = "Hypnosis" desc = "Instantly enthralls a human." panel = "Ascendant" range = 7 - charge_max = 0 - clothes_req = 0 + charge_max = FALSE + clothes_req = FALSE action_icon_state = "enthrall" -/obj/effect/proc_holder/spell/targeted/hypnosis/cast(list/targets, mob/user = usr) - var/mob/living/simple_animal/ascendant_shadowling/SHA = user - if(SHA.phasing) - charge_counter = charge_max - to_chat(user, "You are not in the same plane of existence. Unphase first.") - return + click_radius = -1 + selection_activated_message = "You start preparing to mindwash over a mortal mind. Left-click to cast at a target!" + selection_deactivated_message = "Your mind relaxes." + allowed_type = /mob/living/carbon/human - for(var/mob/living/carbon/human/target in targets) - if(is_shadow_or_thrall(target)) - to_chat(user, "You cannot enthrall an ally.") - charge_counter = charge_max - return - if(!target.ckey || !target.mind) - to_chat(user, "The target has no mind.") - charge_counter = charge_max - return - if(target.stat) - to_chat(user, "The target must be conscious.") - charge_counter = charge_max - return - if(!ishuman(target)) - to_chat(user, "You can only enthrall humans.") - charge_counter = charge_max - return +/obj/effect/proc_holder/spell/targeted/click/hypnosis/can_cast(mob/living/simple_animal/ascendant_shadowling/user = usr, charge_check = TRUE, show_message = FALSE) + if(!istype(user)) + return FALSE + if(user.phasing) + if(show_message) + to_chat(user, "You are not in the same plane of existence. Unphase first.") + return FALSE + return ..() - to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.") - to_chat(target, "An agonizing spike of pain drives into your mind, and--") - SSticker.mode.add_thrall(target.mind) - target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL - target.add_language("Shadowling Hivemind") +/obj/effect/proc_holder/spell/targeted/click/hypnosis/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + return !is_shadow_or_thrall(target) && target.ckey && target.mind && !target.stat + +/obj/effect/proc_holder/spell/targeted/click/hypnosis/cast(list/targets, mob/user = usr) + var/mob/living/carbon/human/target = targets[1] + + to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.") + to_chat(target, "An agonizing spike of pain drives into your mind, and--") + SSticker.mode.add_thrall(target.mind) + target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL + target.add_language("Shadowling Hivemind") diff --git a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm index 83ca06fe4b7..28c934f7d38 100644 --- a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm +++ b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm @@ -99,14 +99,14 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u to_chat(H, "Your powers are awoken. You may now live to your fullest extent. Remember your goal. Cooperate with your thralls and allies.") H.ExtinguishMob() H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_vision(null)) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/enthrall(null)) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null)) + H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/enthrall(null)) + H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/veil(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/flashfreeze(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/collective_mind(null)) H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_regenarmor(null)) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle(null)) + H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle(null)) QDEL_NULL(H.hud_used) H.hud_used = new /datum/hud/human(H, ui_style2icon(H.client.prefs.UI_style), H.client.prefs.UI_style_color, H.client.prefs.UI_style_alpha) @@ -172,8 +172,8 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u H.mind.transfer_to(A) A.name = H.real_name A.languages = H.languages - A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/annihilate(null)) - A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/hypnosis(null)) + A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/annihilate(null)) + A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/hypnosis(null)) A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_phase_shift(null)) A.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/ascendant_storm(null)) A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowlingAscendantTransmit(null)) diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 0c3f148b106..e7c5b4541a8 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -119,10 +119,10 @@ if(traitorwin) - text += "
The [special_role_text] was successful!" + text += "
The [special_role_text] was successful!
" feedback_add_details("traitor_success","SUCCESS") else - text += "
The [special_role_text] has failed!" + text += "
The [special_role_text] has failed!
" feedback_add_details("traitor_success","FAIL") if(length(SSticker.mode.implanted)) diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm index 7906deeb718..406ab50b70d 100644 --- a/code/game/gamemodes/vampire/vampire_powers.dm +++ b/code/game/gamemodes/vampire/vampire_powers.dm @@ -15,7 +15,7 @@ if(!gain_desc) gain_desc = "You have gained \the [src] ability." -/obj/effect/proc_holder/spell/vampire/cast_check(skipcharge = 0, mob/living/user = usr) +/obj/effect/proc_holder/spell/vampire/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) if(!user.mind) return 0 if(!ishuman(user)) @@ -45,7 +45,7 @@ return 0 return ..() -/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr) +/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) if(!user.mind) return 0 if(!ishuman(user)) diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm index 442e9958fc7..cb0c277b1e6 100644 --- a/code/game/gamemodes/wizard/artefact.dm +++ b/code/game/gamemodes/wizard/artefact.dm @@ -61,7 +61,7 @@ switch(href_list["school"]) if("destruction") M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null)) - M.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball(null)) + M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball(null)) to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball.") if("bluespace") M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null)) @@ -74,7 +74,7 @@ to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.") if("robeless") M.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) - M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null)) + M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/mind_transfer(null)) to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.") M.equip_to_slot_or_del(new /obj/item/radio/headset(M), slot_l_ear) diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index d0773947c21..efa3303c26b 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -132,7 +132,7 @@ /datum/spellbook_entry/horseman name = "Curse of the Horseman" - spell_type = /obj/effect/proc_holder/spell/targeted/horsemask + spell_type = /obj/effect/proc_holder/spell/targeted/click/horsemask log_name = "HH" category = "Offensive" @@ -144,7 +144,7 @@ /datum/spellbook_entry/fireball name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/fireball + spell_type = /obj/effect/proc_holder/spell/targeted/click/fireball log_name = "FB" category = "Offensive" @@ -257,7 +257,7 @@ /datum/spellbook_entry/mindswap name = "Mindswap" - spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer + spell_type = /obj/effect/proc_holder/spell/targeted/click/mind_transfer log_name = "MT" category = "Mobility" @@ -877,7 +877,7 @@ return /obj/item/spellbook/oneuse/fireball - spell = /obj/effect/proc_holder/spell/fireball + spell = /obj/effect/proc_holder/spell/targeted/click/fireball spellname = "fireball" icon_state = "bookfireball" desc = "This book feels warm to the touch." @@ -910,7 +910,7 @@ user.EyeBlind(10) /obj/item/spellbook/oneuse/mindswap - spell = /obj/effect/proc_holder/spell/targeted/mind_transfer + spell = /obj/effect/proc_holder/spell/targeted/click/mind_transfer spellname = "mindswap" icon_state = "bookmindswap" desc = "This book's cover is pristine, though its pages look ragged and torn." @@ -934,8 +934,8 @@ to_chat(user, "You stare at the book some more, but there doesn't seem to be anything else to learn...") return - var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new - swapper.cast(user, stored_swap, 1) + var/obj/effect/proc_holder/spell/targeted/click/mind_transfer/swapper = new + swapper.cast(user, stored_swap) to_chat(stored_swap, "You're suddenly somewhere else... and someone else?!") to_chat(user, "Suddenly you're staring at [src] again... where are you, who are you?!") @@ -966,7 +966,7 @@ user.Weaken(20) /obj/item/spellbook/oneuse/horsemask - spell = /obj/effect/proc_holder/spell/targeted/horsemask + spell = /obj/effect/proc_holder/spell/targeted/click/horsemask spellname = "horses" icon_state = "bookhorses" desc = "This book is more horse than your mind has room for." diff --git a/code/game/gamemodes/wizard/wizloadouts.dm b/code/game/gamemodes/wizard/wizloadouts.dm index c050b27f273..518b9a68ca8 100644 --- a/code/game/gamemodes/wizard/wizloadouts.dm +++ b/code/game/gamemodes/wizard/wizloadouts.dm @@ -18,7 +18,7 @@ Care should be taken in hiding the item you choose as your phylactery after using Bind Soul, as you cannot revive if it destroyed or too far from your body!

\ Provides Bind Soul, Ethereal Jaunt, Fireball, Rod Form, Disable Tech, and Greater Forcewall." log_name = "DL" - spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/fireball, \ + spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/targeted/click/fireball, \ /obj/effect/proc_holder/spell/targeted/rod_form, /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech, /obj/effect/proc_holder/spell/targeted/forcewall/greater) is_ragin_restricted = TRUE diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm index f91460ba234..8684ca7d1b0 100644 --- a/code/game/jobs/job/central.dm +++ b/code/game/jobs/job/central.dm @@ -94,7 +94,7 @@ ) cybernetic_implants = list( /obj/item/organ/internal/cyberimp/eyes/xray, - /obj/item/organ/internal/cyberimp/brain/anti_stun, + /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened, /obj/item/organ/internal/cyberimp/chest/nutriment/plus, /obj/item/organ/internal/cyberimp/arm/combat/centcom ) diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm index 09328865b59..fda831242f9 100644 --- a/code/game/jobs/job/support.dm +++ b/code/game/jobs/job/support.dm @@ -458,6 +458,7 @@ total_positions = 0 spawn_positions = 0 supervisors = "the head of personnel" + department_head = list("Head of Personnel") selection_color = "#dddddd" access = list(ACCESS_MAINT_TUNNELS, ACCESS_GATEWAY, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS) minimal_access = list(ACCESS_MAINT_TUNNELS, ACCESS_GATEWAY, ACCESS_EVA, ACCESS_EXTERNAL_AIRLOCKS) diff --git a/code/game/jobs/job/support_chaplain.dm b/code/game/jobs/job/support_chaplain.dm index b39eed344ae..bfd9c4520f5 100644 --- a/code/game/jobs/job/support_chaplain.dm +++ b/code/game/jobs/job/support_chaplain.dm @@ -26,7 +26,7 @@ /obj/item/camera/spooky = 1, /obj/item/nullrod = 1 ) - + /datum/outfit/job/chaplain/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) . = ..() @@ -77,7 +77,7 @@ new_deity = deity_name B.deity_name = new_deity - H.AddSpell(new /obj/effect/proc_holder/spell/targeted/chaplain_bless(null)) + H.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/chaplain_bless(null)) var/accepted = 0 var/outoftime = 0 diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index ed3b3d95099..f676d861f96 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -46,7 +46,7 @@ return FALSE if(R.scrambledcodes) return FALSE - if(!atoms_share_level(src, R)) + if(!atoms_share_level(get_turf(src), get_turf(R))) return FALSE return TRUE diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 48b022a65f3..1ac341db260 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -1272,7 +1272,7 @@ ads_list = list("We like plants!","Don't you want some?","The greenest thumbs ever.","We like big plants.","Soft soil...") icon_state = "nutri" icon_deny = "nutri-deny" - products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 30,/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 20,/obj/item/reagent_containers/glass/bottle/nutrient/rh = 10,/obj/item/reagent_containers/spray/pestspray = 20, + products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 20,/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 13,/obj/item/reagent_containers/glass/bottle/nutrient/rh = 6,/obj/item/reagent_containers/spray/pestspray = 20, /obj/item/reagent_containers/syringe = 5,/obj/item/storage/bag/plants = 5,/obj/item/cultivator = 3,/obj/item/shovel/spade = 3,/obj/item/plant_analyzer = 4) contraband = list(/obj/item/reagent_containers/glass/bottle/ammonia = 10,/obj/item/reagent_containers/glass/bottle/diethylamine = 5) refill_canister = /obj/item/vending_refill/hydronutrients diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 639687a45e9..a1538b5fe92 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -42,7 +42,7 @@ /obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target, height=0) if(height == 0) return TRUE - if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider)) + if(istype(mover, /mob/living/simple_animal/hostile/poison/giant_spider) || isterrorspider(mover)) return TRUE else if(istype(mover, /mob/living)) if(prob(50)) diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index 3b3391b506b..7eb1dc9fd56 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -37,70 +37,83 @@ overlays.Cut() /obj/item/aicard/attack_self(mob/user) - ui_interact(user) + tgui_interact(user) -/obj/item/aicard/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.inventory_state) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/item/aicard/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_inventory_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "aicard.tmpl", "[name]", 600, 400, state = state) + ui = new(user, src, ui_key, "AICard", "[name]", 600, 394, master_ui, state) ui.open() - ui.set_auto_update(1) -/obj/item/aicard/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.inventory_state) +/obj/item/aicard/tgui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.inventory_state) var/data[0] var/mob/living/silicon/ai/AI = locate() in src if(istype(AI)) - data["has_ai"] = 1 + data["has_ai"] = TRUE data["name"] = AI.name - data["hardware_integrity"] = ((AI.health + 100) / 2) + data["integrity"] = ((AI.health + 100) / 2) data["radio"] = !AI.aiRadio.disabledAi data["wireless"] = !AI.control_disabled data["operational"] = AI.stat != DEAD data["flushing"] = flush var/laws[0] - for(var/datum/ai_law/AL in AI.laws.all_laws()) - laws[++laws.len] = list("index" = AL.get_index(), "law" = sanitize(AL.law)) + for(var/datum/ai_law/law in AI.laws.all_laws()) + if(law in AI.laws.ion_laws) // If we're an ion law, give it an ion index code + laws.Add(ionnum() + ". " + law.law) + else + laws.Add(num2text(law.get_index()) + ". " + law.law) data["laws"] = laws - data["has_laws"] = laws.len + data["has_laws"] = length(AI.laws.all_laws()) + + else + data["has_ai"] = FALSE // If this isn't passed to tgui, it won't show there isn't a AI in the card. return data -/obj/item/aicard/Topic(href, href_list, nowindow, state) +/obj/item/aicard/tgui_act(action, params) if(..()) - return 1 + return var/mob/living/silicon/ai/AI = locate() in src if(!istype(AI)) - return 1 + return var/user = usr + switch(action) + if("wipe") + if(flush) // Don't doublewipe. + to_chat(user, "You are already wiping this AI!") + return + var/confirm = alert("Are you sure you want to wipe this card's memory? This cannot be undone once started.", "Confirm Wipe", "Yes", "No") + if(confirm == "Yes" && (tgui_status(user, GLOB.tgui_inventory_state) == STATUS_INTERACTIVE)) // And make doubly sure they want to wipe (three total clicks) + msg_admin_attack("[key_name_admin(user)] wiped [key_name_admin(AI)] with \the [src].", ATKLOG_FEW) + add_attack_logs(user, AI, "Wiped with [src].") + INVOKE_ASYNC(src, .proc/wipe_ai) - if(href_list["wipe"]) - var/confirm = alert("Are you sure you want to wipe this card's memory? This cannot be undone once started.", "Confirm Wipe", "Yes", "No") - if(confirm == "Yes" && (CanUseTopic(user, state) == STATUS_INTERACTIVE)) - add_attack_logs(user, AI, "Wiped with [src].", ATKLOG_FEW) - flush = 1 - AI.suiciding = 1 - to_chat(AI, "Your core files are being wiped!") - while(AI && AI.stat != DEAD) - AI.adjustOxyLoss(2) - sleep(10) - flush = 0 + if("radio") + AI.aiRadio.disabledAi = !AI.aiRadio.disabledAi + to_chat(AI, "Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!") + to_chat(user, "You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.") - if(href_list["radio"]) - AI.aiRadio.disabledAi = text2num(href_list["radio"]) - to_chat(AI, "Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!") - to_chat(user, "You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.") + if("wireless") + AI.control_disabled = !AI.control_disabled + to_chat(AI, "Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!") + to_chat(user, "You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.") + update_icon() - if(href_list["wireless"]) - AI.control_disabled = text2num(href_list["wireless"]) - to_chat(AI, "Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!") - to_chat(user, "You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.") - update_icon() + return TRUE - return 1 +/obj/item/aicard/proc/wipe_ai() + var/mob/living/silicon/ai/AI = locate() in src + flush = TRUE + AI.suiciding = TRUE + to_chat(AI, "Your core files are being wiped!") + while(AI && AI.stat != DEAD) + AI.adjustOxyLoss(2) + sleep(10) + flush = FALSE diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index d9af70df391..2142dc9e50b 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -45,9 +45,6 @@ QDEL_NULL(keyslot2) return ..() -/obj/item/radio/headset/list_channels(var/mob/user) - return list_secure_channels() - /obj/item/radio/headset/examine(mob/user) . = ..() if(in_range(src, user) && radio_desc) diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index d9d3aa3c3c1..f9ba60ab380 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -245,14 +245,7 @@ usesound = 'sound/items/deconstruct.ogg' /obj/item/radio/intercom/locked - var/locked_frequency - -/obj/item/radio/intercom/locked/set_frequency(var/frequency) - if(frequency == locked_frequency) - ..(locked_frequency) - -/obj/item/radio/intercom/locked/list_channels() - return "" + freqlock = TRUE /obj/item/radio/intercom/locked/ai_private name = "\improper AI intercom" diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index cdcc2361937..ed6b0190502 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -129,7 +129,10 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( /obj/item/radio/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "Radio", name, 360, 150 + (length(channels) * 20), master_ui, state) + var/list/schannels = list_secure_channels(user) + var/list/ichannels = list_internal_channels(user) + var/calc_height = 150 + (schannels.len * 20) + (ichannels.len * 10) + ui = new(user, src, ui_key, "Radio", name, 400, calc_height, master_ui, state) ui.open() /obj/item/radio/tgui_data(mob/user) @@ -142,9 +145,8 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( data["maxFrequency"] = freerange ? RADIO_HIGH_FREQ : PUBLIC_HIGH_FREQ data["canReset"] = frequency == initial(frequency) ? FALSE : TRUE data["freqlock"] = freqlock - data["channels"] = list() - for(var/channel in channels) - data["channels"][channel] = channels[channel] & FREQ_LISTENING + data["schannels"] = list_secure_channels(user) + data["ichannels"] = list_internal_channels(user) data["has_loudspeaker"] = has_loudspeaker data["loudspeaker"] = loudspeaker @@ -173,6 +175,12 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( usr << browse(null, "window=radio") if(.) set_frequency(sanitize_frequency(tune, freerange)) + if("ichannel") // change primary frequency to an internal channel authorized by access + if(freqlock) + return + var/freq = params["ichannel"] + if(has_channel_access(usr, freq)) + set_frequency(text2num(freq)) if("listen") listening = !listening if("broadcast") @@ -198,34 +206,32 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( if(.) add_fingerprint(usr) -/obj/item/radio/proc/list_channels(var/mob/user) - return list_internal_channels(user) - -/obj/item/radio/proc/list_secure_channels(var/mob/user) - var/dat[0] - - for(var/ch_name in channels) - var/chan_stat = channels[ch_name] - var/listening = !!(chan_stat & FREQ_LISTENING) != 0 - - dat.Add(list(list("chan" = ch_name, "display_name" = ch_name, "secure_channel" = 1, "sec_channel_listen" = !listening, "chan_span" = SSradio.frequency_span_class(SSradio.radiochannels[ch_name])))) - +/obj/item/radio/proc/list_secure_channels(mob/user) + var/list/dat = list() + for(var/channel in channels) + dat[channel] = channels[channel] & FREQ_LISTENING return dat -/obj/item/radio/proc/list_internal_channels(var/mob/user) - var/dat[0] +/obj/item/radio/proc/list_internal_channels(mob/user) + var/list/dat = list() + if(freqlock) + return dat for(var/internal_chan in internal_channels) + var/freqnum = text2num(internal_chan) + var/freqname = get_frequency_name(freqnum) if(has_channel_access(user, internal_chan)) - dat.Add(list(list("chan" = internal_chan, "display_name" = get_frequency_name(text2num(internal_chan)), "chan_span" = SSradio.frequency_span_class(text2num(internal_chan))))) - + dat[freqname] = freqnum // unlike secure_channels, this is set to the freq number so Radio.js can use it as an arg return dat -/obj/item/radio/proc/has_channel_access(var/mob/user, var/freq) +/obj/item/radio/proc/has_channel_access(mob/user, freq) if(!user) - return 0 + return FALSE if(!(freq in internal_channels)) - return 0 + return FALSE + + if(isrobot(user)) + return FALSE // cyborgs and drones are not allowed to remotely re-tune intercomms, etc return user.has_internal_radio_channel_access(user, internal_channels[freq]) @@ -603,13 +609,14 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( /obj/item/radio/borg name = "Cyborg Radio" var/mob/living/silicon/robot/myborg = null // Cyborg which owns this radio. Used for power checks - var/obj/item/encryptionkey/keyslot = null//Borg radios can handle a single encryption key + var/obj/item/encryptionkey/keyslot // Borg radios can handle a single encryption key icon = 'icons/obj/robot_component.dmi' // Cyborgs radio icons should look like the component. icon_state = "radio" has_loudspeaker = TRUE loudspeaker = FALSE canhear_range = 0 dog_fashion = null + freqlock = TRUE // don't let cyborgs change the default channel of their internal radio away from common /obj/item/radio/borg/syndicate keyslot = new /obj/item/encryptionkey/syndicate/nukeops @@ -623,9 +630,6 @@ GLOBAL_LIST_INIT(default_medbay_channels, list( myborg = null return ..() -/obj/item/radio/borg/list_channels(var/mob/user) - return list_secure_channels(user) - /obj/item/radio/borg/syndicate/New() ..() syndiekey = keyslot diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 8508c741524..d369161127f 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -59,6 +59,14 @@ /obj/item/trash/fried_vox name = "Kentucky Fried Vox" icon_state = "fried_vox_empty" + item_state = "fried_vox_empty" + slot_flags = SLOT_HEAD + dog_fashion = /datum/dog_fashion/head/fried_vox_empty + sprite_sheets = list( + "Skrell" = 'icons/mob/species/skrell/head.dmi', + "Drask" = 'icons/mob/species/drask/head.dmi', + "Kidan" = 'icons/mob/species/kidan/head.dmi' + ) /obj/item/trash/pistachios name = "Pistachios pack" diff --git a/code/game/objects/items/weapons/highlander_swords.dm b/code/game/objects/items/weapons/highlander_swords.dm index 330da6beba0..568b023d051 100644 --- a/code/game/objects/items/weapons/highlander_swords.dm +++ b/code/game/objects/items/weapons/highlander_swords.dm @@ -25,14 +25,14 @@ return ..() /obj/item/claymore/highlander/equipped(mob/user, slot) - if(!ishuman(user)) + if(!ishuman(user) || !user.mind) return var/mob/living/carbon/human/H = user if(slot == slot_r_hand || slot == slot_l_hand) - if(H.martial_art && H.martial_art != style) - style.teach(H, 1) + if(H.mind.martial_art && H.mind.martial_art != style) + style.teach(H, TRUE) to_chat(H, "THERE CAN ONLY BE ONE!") - else if(H.martial_art && H.martial_art == style) + else if(H.mind.martial_art && H.mind.martial_art == style) style.remove(H) var/obj/item/claymore/highlander/sword = H.is_in_hands(/obj/item/claymore/highlander) if(sword) diff --git a/code/game/objects/items/weapons/implants/implant_krav_maga.dm b/code/game/objects/items/weapons/implants/implant_krav_maga.dm index 3c2666f3d66..9c33f43950a 100644 --- a/code/game/objects/items/weapons/implants/implant_krav_maga.dm +++ b/code/game/objects/items/weapons/implants/implant_krav_maga.dm @@ -17,12 +17,12 @@ /obj/item/implant/krav_maga/activate() var/mob/living/carbon/human/H = imp_in - if(!ishuman(H)) + if(!ishuman(H) || !H.mind) return - if(istype(H.martial_art, /datum/martial_art/krav_maga)) + if(istype(H.mind.martial_art, /datum/martial_art/krav_maga)) style.remove(H) else - style.teach(H,1) + style.teach(H, TRUE) /obj/item/implanter/krav_maga name = "implanter (krav maga)" diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index bdab5ca2466..e9abfca2dac 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -90,7 +90,7 @@ ..() if(climber) climber.Weaken(2) - climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You see [climber.name] get knocked off the table") + climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You hear [climber.name] get knocked off the table") else if(Adjacent(user) && user.pulling && user.pulling.pass_flags & PASSTABLE) user.Move_Pulled(src) if(user.pulling.loc == loc) diff --git a/code/game/world.dm b/code/game/world.dm index fbdcf12f9fc..d2828f9c04b 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -222,8 +222,12 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday) if(input["key"] != config.comms_password) return "Bad Key" else + var/prtext = input["announce"] + var/pr_substring = copytext(prtext, 1, 23) + if(pr_substring == "Pull Request merged by") + GLOB.pending_server_update = TRUE for(var/client/C in GLOB.clients) - to_chat(C, "PR: [input["announce"]]") + to_chat(C, "PR: [prtext]") else if("kick" in input) /* @@ -270,7 +274,7 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday) else if("hostannounce" in input) if(!key_valid) return keySpamProtect(addr) - + GLOB.pending_server_update = TRUE to_chat(world, "
Server Announcement: [input["message"]]
") /proc/keySpamProtect(var/addr) @@ -339,8 +343,12 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday) return #endif + var/secs_before_auto_reconnect = 10 + if(GLOB.pending_server_update) + secs_before_auto_reconnect = 60 + to_chat(world, "Reboot will take a little longer, due to pending updates.") for(var/client/C in GLOB.clients) - var/secs_before_auto_reconnect = 10 // TODO: make it higher if server is due for an update @AffectedArc07 + C << output(list2params(list(secs_before_auto_reconnect)), "browseroutput:reboot") if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite C << link("byond://[config.server]") diff --git a/code/modules/arcade/prize_datums.dm b/code/modules/arcade/prize_datums.dm index 1edad36b8f3..2b9f17c3d04 100644 --- a/code/modules/arcade/prize_datums.dm +++ b/code/modules/arcade/prize_datums.dm @@ -203,6 +203,12 @@ GLOBAL_DATUM_INIT(global_prizes, /datum/prizes, new()) typepath = /obj/item/toy/toy_xeno cost = 80 +/datum/prize_item/rubberducky + name = "Rubber Ducky" + desc = "Your favorite bathtime buddy, all squeaks and quacks quality assured." + typepath = /obj/item/bikehorn/rubberducky + cost = 80 + /datum/prize_item/tacticool name = "Tacticool Turtleneck" desc = "A cool-looking turtleneck." diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 72537f8ccc3..46ec605e71d 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -36,7 +36,10 @@ //////////// //SECURITY// //////////// - var/next_allowed_topic_time = 10 + + ///Used for limiting the rate of topic sends by the client to avoid abuse + var/list/topiclimiter + // comment out the line below when debugging locally to enable the options & messages menu //control_freak = 1 diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index ae503be111b..f8ef211d5de 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -11,6 +11,13 @@ #define SUGGESTED_CLIENT_VERSION 511 // only integers (e.g: 510, 511) useful here. Does not properly handle minor versions (e.g: 510.58, 511.848) #define SSD_WARNING_TIMER 30 // cycles, not seconds, so 30=60s +#define LIMITER_SIZE 5 +#define CURRENT_SECOND 1 +#define SECOND_COUNT 2 +#define CURRENT_MINUTE 3 +#define MINUTE_COUNT 4 +#define ADMINSWARNED_AT 5 + /* When somebody clicks a link in game, this Topic is called first. It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever] @@ -59,10 +66,38 @@ if(href_list["_src_"] == "chat") return chatOutput.Topic(href, href_list) - //Reduces spamming of links by dropping calls that happen during the delay period - if(next_allowed_topic_time > world.time) - return - next_allowed_topic_time = world.time + TOPIC_SPAM_DELAY + // Rate limiting + var/mtl = 100 // 100 topics per minute + if (!holder) // Admins are allowed to spam click, deal with it. + var/minute = round(world.time, 600) + if (!topiclimiter) + topiclimiter = new(LIMITER_SIZE) + if (minute != topiclimiter[CURRENT_MINUTE]) + topiclimiter[CURRENT_MINUTE] = minute + topiclimiter[MINUTE_COUNT] = 0 + topiclimiter[MINUTE_COUNT] += 1 + if (topiclimiter[MINUTE_COUNT] > mtl) + var/msg = "Your previous action was ignored because you've done too many in a minute." + if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them) + topiclimiter[ADMINSWARNED_AT] = minute + msg += " Administrators have been informed." + log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") + message_admins("[ADMIN_LOOKUPFLW(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") + to_chat(src, "[msg]") + return + + var/stl = 10 // 10 topics a second + if (!holder) // Admins are allowed to spam click, deal with it. + var/second = round(world.time, 10) + if (!topiclimiter) + topiclimiter = new(LIMITER_SIZE) + if (second != topiclimiter[CURRENT_SECOND]) + topiclimiter[CURRENT_SECOND] = second + topiclimiter[SECOND_COUNT] = 0 + topiclimiter[SECOND_COUNT] += 1 + if (topiclimiter[SECOND_COUNT] > stl) + to_chat(src, "Your previous action was ignored because you've done too many in a second") + return //search the href for script injection if( findtext(href,"UI resource files resent successfully. If you are still having issues, please try manually clearing your BYOND cache. This can be achieved by opening your BYOND launcher, pressing the cog in the top right, selecting preferences, going to the Games tab, and pressing 'Clear Cache'.") + +#undef LIMITER_SIZE +#undef CURRENT_SECOND +#undef SECOND_COUNT +#undef CURRENT_MINUTE +#undef MINUTE_COUNT +#undef ADMINSWARNED_AT diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 1f8cb7aaed1..62f740ad7b0 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -344,3 +344,10 @@ recharging_time = world.time + recharging_rate else to_chat(user, "Something prevents you from dashing forward!") + +/obj/item/clothing/shoes/ducky + name = "rubber ducky shoes" + desc = "These shoes are made for quacking, and thats just what they'll do." + icon_state = "ducky" + item_state = "ducky" + shoe_sound = "sound/items/squeaktoy.ogg" diff --git a/code/modules/crafting/tailoring.dm b/code/modules/crafting/tailoring.dm index 6973fd04ad3..38419f718c4 100644 --- a/code/modules/crafting/tailoring.dm +++ b/code/modules/crafting/tailoring.dm @@ -143,3 +143,12 @@ reqs = list(/obj/item/stack/sheet/animalhide/lizard = 1, /obj/item/stack/sheet/leather = 1) time = 60 category = CAT_CLOTHING + +/datum/crafting_recipe/rubberduckyshoes + name = "Rubber Ducky Shoes" + result = /obj/item/clothing/shoes/ducky + time = 45 + reqs = list(/obj/item/bikehorn/rubberducky = 2, + /obj/item/clothing/shoes/sandal = 1) + tools = list(TOOL_WIRECUTTER) + category = CAT_CLOTHING diff --git a/code/modules/events/spider_terror.dm b/code/modules/events/spider_terror.dm index 79acc49fe73..ae10f427506 100644 --- a/code/modules/events/spider_terror.dm +++ b/code/modules/events/spider_terror.dm @@ -43,8 +43,8 @@ spawncount = 2 if(4) // Pretty strong. - spider_type = /mob/living/simple_animal/hostile/poison/terror_spider/princess - spawncount = 2 + spider_type = /mob/living/simple_animal/hostile/poison/terror_spider/queen/princess + spawncount = 3 if(5) // Strongest, only used during highpop. spider_type = /mob/living/simple_animal/hostile/poison/terror_spider/queen diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 7d59bd3360b..d5c7744eb4a 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -27,10 +27,7 @@ w_class = WEIGHT_CLASS_SMALL throw_speed = 3 throw_range = 10 - -/obj/item/reagent_containers/spray/weedspray/New() - ..() - reagents.add_reagent("atrazine", 100) + list_reagents = list("atrazine" = 100) /obj/item/reagent_containers/spray/weedspray/suicide_act(mob/user) user.visible_message("[user] is huffing the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.") @@ -49,10 +46,7 @@ w_class = WEIGHT_CLASS_SMALL throw_speed = 3 throw_range = 10 - -/obj/item/reagent_containers/spray/pestspray/New() - ..() - reagents.add_reagent("pestkiller", 100) + list_reagents = list("pestkiller" = 100) /obj/item/reagent_containers/spray/pestspray/suicide_act(mob/user) user.visible_message("[user] is huffing the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.") @@ -222,79 +216,112 @@ /obj/item/reagent_containers/glass/bottle/nutrient - name = "bottle of nutrient" + name = "jug of nutrient" + desc = "A decent sized plastic jug." icon = 'icons/obj/chemical.dmi' - icon_state = "bottle16" - volume = 50 + icon_state = "plastic_jug" + item_state = "plastic_jug" w_class = WEIGHT_CLASS_TINY amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) + possible_transfer_amounts = list(1,2,5,10,20,40,80) + container_type = OPENCONTAINER + volume = 80 + hitsound = 'sound/weapons/jug_empty_impact.ogg' + throwhitsound = 'sound/weapons/jug_empty_impact.ogg' + force = 0.2 + throwforce = 0.2 /obj/item/reagent_containers/glass/bottle/nutrient/New() + ..() + add_lid() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + +/obj/item/reagent_containers/glass/bottle/nutrient/on_reagent_change() + . = ..() + update_icon() + if(reagents.total_volume) + hitsound = 'sound/weapons/jug_filled_impact.ogg' + throwhitsound = 'sound/weapons/jug_filled_impact.ogg' + else + hitsound = 'sound/weapons/jug_empty_impact.ogg' + throwhitsound = 'sound/weapons/jug_empty_impact.ogg' + +/obj/item/reagent_containers/glass/bottle/nutrient/update_icon() + cut_overlays() + + if(reagents.total_volume) + var/image/filling = image('icons/obj/reagentfillings.dmi', src, "plastic_jug10") + + var/percent = round((reagents.total_volume / volume) * 100) + switch(percent) + if(0 to 10) + filling.icon_state = "plastic_jug-10" + if(11 to 29) + filling.icon_state = "plastic_jug25" + if(30 to 45) + filling.icon_state = "plastic_jug40" + if(46 to 61) + filling.icon_state = "plastic_jug55" + if(62 to 77) + filling.icon_state = "plastic_jug70" + if(78 to 92) + filling.icon_state = "plastic_jug85" + if(93 to INFINITY) + filling.icon_state = "plastic_jug100" + + filling.icon += mix_color_from_reagents(reagents.reagent_list) + add_overlay(filling) + + if(!is_open_container()) + add_overlay("lid_jug") + + +/obj/item/reagent_containers/glass/bottle/nutrient/ez + name = "jug of E-Z-Nutrient" + desc = "Contains a fertilizer that causes mild mutations with each harvest." + icon = 'icons/obj/chemical.dmi' + icon_state = "plastic_jug_ez" + list_reagents = list("eznutriment" = 80) + +/obj/item/reagent_containers/glass/bottle/nutrient/l4z + name = "jug of Left 4 Zed" + desc = "Contains a fertilizer that limits plant yields to no more than one and causes significant mutations in plants." + icon = 'icons/obj/chemical.dmi' + icon_state = "plastic_jug_l4z" + list_reagents = list("left4zednutriment" = 80) + +/obj/item/reagent_containers/glass/bottle/nutrient/rh + name = "jug of Robust Harvest" + desc = "Contains a fertilizer that increases the yield of a plant by 30% while causing no mutations." + icon = 'icons/obj/chemical.dmi' + icon_state = "plastic_jug_rh" + list_reagents = list("robustharvestnutriment" = 80) + +/obj/item/reagent_containers/glass/bottle/nutrient/empty + icon = 'icons/obj/chemical.dmi' + icon_state = "plastic_jug" + +/obj/item/reagent_containers/glass/bottle/nutrient/killer + icon = 'icons/obj/chemical.dmi' + icon_state = "plastic_jug_k" + w_class = WEIGHT_CLASS_TINY + +/obj/item/reagent_containers/glass/bottle/nutrient/killer/New() ..() pixel_x = rand(-5, 5) pixel_y = rand(-5, 5) -/obj/item/reagent_containers/glass/bottle/nutrient/ez - name = "bottle of E-Z-Nutrient" - desc = "Contains a fertilizer that causes mild mutations with each harvest." - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle16" - -/obj/item/reagent_containers/glass/bottle/nutrient/ez/New() - ..() - reagents.add_reagent("eznutriment", 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/l4z - name = "bottle of Left 4 Zed" - desc = "Contains a fertilizer that limits plant yields to no more than one and causes significant mutations in plants." - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle18" - -/obj/item/reagent_containers/glass/bottle/nutrient/l4z/New() - ..() - reagents.add_reagent("left4zednutriment", 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/rh - name = "bottle of Robust Harvest" - desc = "Contains a fertilizer that increases the yield of a plant by 30% while causing no mutations." - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle15" - -/obj/item/reagent_containers/glass/bottle/nutrient/rh/New() - ..() - reagents.add_reagent("robustharvestnutriment", 50) - -/obj/item/reagent_containers/glass/bottle/nutrient/empty - name = "bottle" - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle16" - -/obj/item/reagent_containers/glass/bottle/killer - name = "bottle" - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle16" - volume = 50 - w_class = WEIGHT_CLASS_TINY - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) - -/obj/item/reagent_containers/glass/bottle/killer/weedkiller - name = "bottle of weed killer" +/obj/item/reagent_containers/glass/bottle/nutrient/killer/weedkiller + name = "jug of weed killer" desc = "Contains a herbicide." icon = 'icons/obj/chemical.dmi' - icon_state = "bottle19" + icon_state = "plastic_jug_wk" + list_reagents = list("atrazine" = 80) -/obj/item/reagent_containers/glass/bottle/killer/weedkiller/New() - ..() - reagents.add_reagent("atrazine", 50) - -/obj/item/reagent_containers/glass/bottle/killer/pestkiller - name = "bottle of pest spray" +/obj/item/reagent_containers/glass/bottle/nutrient/killer/pestkiller + name = "jug of pest spray" desc = "Contains a pesticide." icon = 'icons/obj/chemical.dmi' - icon_state = "bottle20" - -/obj/item/reagent_containers/glass/bottle/killer/pestkiller/New() - ..() - reagents.add_reagent("pestkiller", 50) + icon_state = "plastic_jug_pk" + list_reagents = list("pestkiller" = 80) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 1be8f11527d..d79a8030a59 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -744,6 +744,10 @@ to_chat(user, "[reagent_source] is empty.") return 1 + if(reagent_source.has_lid && !reagent_source.is_drainable()) //if theres a LID then cannot transfer reagents. + to_chat(user, "You need to open [O] first!") + return TRUE + var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc var/target = myseed ? myseed.plantname : src var/visi_msg = "" diff --git a/code/modules/martial_arts/adminfu.dm b/code/modules/martial_arts/adminfu.dm index 12436061e20..20f3c95f7b6 100644 --- a/code/modules/martial_arts/adminfu.dm +++ b/code/modules/martial_arts/adminfu.dm @@ -1,93 +1,37 @@ -///Adminfu -//Help act:Heal/revie GP //p is for help -//Disarm:Stun -//Grab:Neck -//Harm:Gib -#define HEAL_COMBO "GP" - /datum/martial_art/adminfu name = "Way of the Dancing Admin" - help_verb = /mob/living/carbon/human/proc/adminfu_help - -/datum/martial_art/adminfu/proc/check_streak(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(findtext(streak,HEAL_COMBO)) - streak = "" - healPalm(A,D) - return 1 - return 0 + has_explaination_verb = TRUE + combos = list(/datum/martial_combo/adminfu/healing_palm) /datum/martial_art/adminfu/harm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - + MARTIAL_ARTS_ACT_CHECK if(!D.stat)//do not kill what is dead... A.do_attack_animation(D) D.visible_message("[A] manifests a large glowing toolbox and shoves it in [D]'s chest!", \ "[A] shoves a mystical toolbox in your chest!") D.death() - return 1 + return TRUE /datum/martial_art/adminfu/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) + MARTIAL_ARTS_ACT_CHECK A.do_attack_animation(D) D.Weaken(25) D.Stun(25) - return 1 + return TRUE /datum/martial_art/adminfu/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("G",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK var/obj/item/grab/G = D.grabbedby(A,1) if(G) G.state = GRAB_NECK + return TRUE -/datum/martial_art/adminfu/help_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("P",D) - if(check_streak(A,D)) - return 1 - -/datum/martial_art/adminfu/proc/healPalm(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - A.do_attack_animation(D) - D.visible_message("[A] smacks [D] in the forehead!") - - //its the staff of healing code..hush - if(istype(D,/mob)) - var/old_stat = D.stat - if(isanimal(D) && D.stat == DEAD) - var/mob/living/simple_animal/O = D - var/mob/living/simple_animal/P = new O.type(O.loc) - P.real_name = O.real_name - P.name = O.name - if(O.mind) - O.mind.transfer_to(P) - else - P.key = O.key - qdel(O) - D = P - else - D.revive() - D.suiciding = 0 - if(!D.ckey) - for(var/mob/dead/observer/ghost in GLOB.player_list) - if(D.real_name == ghost.real_name) - ghost.reenter_corpse() - break - if(old_stat != DEAD) - to_chat(D, "You feel great!") - else - to_chat(D, "You rise with a start, you're alive!!!") - return 1 - -/mob/living/carbon/human/proc/adminfu_help() - set name = "Recall Teachings" - set desc = "Remember the way of the dancing admin." - set category = "Adminfu" - - to_chat(usr, "Grab: Automatic Neck Grab.") - to_chat(usr, "Disarm: Stun/weaken") - to_chat(usr, "Harm: Death.") - to_chat(usr, "Healing Palm::Combo:Grab,Help intent. Heals or revives a crature.") - +/datum/martial_art/adminfu/explaination_header(user) + to_chat(user, "Grab: Automatic Neck Grab.") + to_chat(user, "Disarm: Stun/weaken") + to_chat(user, "Harm: Death.") /obj/item/adminfu_scroll name = "frayed scroll" diff --git a/code/modules/martial_arts/combos/adminfu/healing_palm.dm b/code/modules/martial_arts/combos/adminfu/healing_palm.dm new file mode 100644 index 00000000000..c5bd34f1886 --- /dev/null +++ b/code/modules/martial_arts/combos/adminfu/healing_palm.dm @@ -0,0 +1,38 @@ +/datum/martial_combo/adminfu/healing_palm + name = "Healing Palm" + steps = list(MARTIAL_COMBO_STEP_GRAB, MARTIAL_COMBO_STEP_HELP) + explaination_text = "Heals or revives a creature." + combo_text_override = "Grab, switch hands, Help" + +/datum/martial_combo/adminfu/healing_palm/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + user.do_attack_animation(target) + target.visible_message("[user] smacks [target] in the forehead!") + + //its the staff of healing code..hush + if(istype(target,/mob)) + var/old_stat = target.stat + if(isanimal(target) && target.stat == DEAD) + var/mob/living/simple_animal/O = target + var/mob/living/simple_animal/P = new O.type(O.loc) + P.real_name = O.real_name + P.name = O.name + if(O.mind) + O.mind.transfer_to(P) + else + P.key = O.key + qdel(O) + target = P + else + target.revive() + target.suiciding = 0 + if(!target.ckey) + for(var/mob/dead/observer/ghost in GLOB.player_list) + if(target.real_name == ghost.real_name) + ghost.reenter_corpse() + break + if(old_stat != DEAD) + to_chat(target, "You feel great!") + else + to_chat(target, "You rise with a start, you're alive!!!") + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_FAIL diff --git a/code/modules/martial_arts/combos/cqc/consecutive.dm b/code/modules/martial_arts/combos/cqc/consecutive.dm new file mode 100644 index 00000000000..b513c8191e4 --- /dev/null +++ b/code/modules/martial_arts/combos/cqc/consecutive.dm @@ -0,0 +1,18 @@ +/datum/martial_combo/cqc/consecutive + name = "Consecutive CQC" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Mainly offensive move, huge damage and decent stamina damage." + +/datum/martial_combo/cqc/consecutive/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat) + target.visible_message("[user] strikes [target]'s abdomen, neck and back consecutively", \ + "[user] strikes your abdomen, neck and back consecutively!") + playsound(get_turf(target), 'sound/weapons/cqchit2.ogg', 50, 1, -1) + var/obj/item/I = target.get_active_hand() + if(I && target.drop_item()) + user.put_in_hands(I) + target.adjustStaminaLoss(50) + target.apply_damage(25, BRUTE) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Consecutive", ATKLOG_ALL) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_FAIL diff --git a/code/modules/martial_arts/combos/cqc/kick.dm b/code/modules/martial_arts/combos/cqc/kick.dm new file mode 100644 index 00000000000..887a51b049d --- /dev/null +++ b/code/modules/martial_arts/combos/cqc/kick.dm @@ -0,0 +1,24 @@ +/datum/martial_combo/cqc/kick + name = "CQC Kick" + steps = list(MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Knocks opponent away. Knocks out stunned or knocked down opponents." + +/datum/martial_combo/cqc/kick/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + . = MARTIAL_COMBO_FAIL + if(!target.stat || !target.IsWeakened()) + target.visible_message("[user] kicks [target] back!", \ + "[user] kicks you back!") + playsound(get_turf(user), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + var/atom/throw_target = get_edge_target_turf(target, user.dir) + target.throw_at(throw_target, 1, 14, user) + target.apply_damage(10, BRUTE) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Kick", ATKLOG_ALL) + . = MARTIAL_COMBO_DONE + if(target.IsWeakened() && !target.stat) + target.visible_message("[user] kicks [target]'s head, knocking [target.p_them()] out!", \ + "[user] kicks your head, knocking you out!") + playsound(get_turf(user), 'sound/weapons/genhit1.ogg', 50, 1, -1) + target.SetSleeping(15) + target.adjustBrainLoss(15) + add_attack_logs(user, target, "Knocked out with martial-art [src] : Kick", ATKLOG_ALL) + . = MARTIAL_COMBO_DONE diff --git a/code/modules/martial_arts/combos/cqc/pressure.dm b/code/modules/martial_arts/combos/cqc/pressure.dm new file mode 100644 index 00000000000..b19bfb6ddf7 --- /dev/null +++ b/code/modules/martial_arts/combos/cqc/pressure.dm @@ -0,0 +1,11 @@ +/datum/martial_combo/cqc/pressure + name = "Pressure" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_GRAB) + explaination_text = "Decent stamina damage." + +/datum/martial_combo/cqc/pressure/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + target.visible_message("[user] forces their arm on [target]'s neck!") + target.adjustStaminaLoss(60) + playsound(get_turf(user), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Pressure", ATKLOG_ALL) + return MARTIAL_COMBO_DONE diff --git a/code/modules/martial_arts/combos/cqc/restrain.dm b/code/modules/martial_arts/combos/cqc/restrain.dm new file mode 100644 index 00000000000..e9495c61dba --- /dev/null +++ b/code/modules/martial_arts/combos/cqc/restrain.dm @@ -0,0 +1,22 @@ +/datum/martial_combo/cqc/restrain + name = "Restrain" + steps = list(MARTIAL_COMBO_STEP_GRAB, MARTIAL_COMBO_STEP_GRAB) + explaination_text = "Locks opponents into a restraining position, disarm to knock them out with a choke hold." + combo_text_override = "Grab, switch hands, Grab" + +/datum/martial_combo/cqc/restrain/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + var/datum/martial_art/cqc/CQC = MA + if(!istype(CQC)) + return MARTIAL_COMBO_FAIL + if(CQC.restraining) + return MARTIAL_COMBO_FAIL + if(!target.stat) + target.visible_message("[user] locks [target] into a restraining position!", \ + "[user] locks you into a restraining position!") + target.adjustStaminaLoss(20) + target.Stun(5) + CQC.restraining = TRUE + addtimer(CALLBACK(CQC, /datum/martial_art/cqc/.proc/drop_restraining), 50, TIMER_UNIQUE) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Restrain", ATKLOG_ALL) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_FAIL diff --git a/code/modules/martial_arts/combos/cqc/slam.dm b/code/modules/martial_arts/combos/cqc/slam.dm new file mode 100644 index 00000000000..8c1a84d7817 --- /dev/null +++ b/code/modules/martial_arts/combos/cqc/slam.dm @@ -0,0 +1,16 @@ +/datum/martial_combo/cqc/slam + name = "Slam" + steps = list(MARTIAL_COMBO_STEP_GRAB, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Slam opponent into the ground, knocking them down." + combo_text_override = "Grab, switch hands, Harm" + +/datum/martial_combo/cqc/slam/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.IsWeakened() && !target.resting && !target.lying) + target.visible_message("[user] slams [target] into the ground!", \ + "[user] slams you into the ground!") + playsound(get_turf(user), 'sound/weapons/slam.ogg', 50, 1, -1) + target.apply_damage(10, BRUTE) + target.Weaken(6) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Slam", ATKLOG_ALL) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_FAIL diff --git a/code/modules/martial_arts/combos/krav_maga/leg_sweep.dm b/code/modules/martial_arts/combos/krav_maga/leg_sweep.dm new file mode 100644 index 00000000000..0defb3e9eff --- /dev/null +++ b/code/modules/martial_arts/combos/krav_maga/leg_sweep.dm @@ -0,0 +1,14 @@ +/datum/martial_combo/krav_maga/leg_sweep + name = "Leg Sweep" + explaination_text = "Trips the victim, rendering them prone and unable to move for a short time." + +/datum/martial_combo/krav_maga/leg_sweep/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(target.stat || target.IsWeakened()) + return FALSE + target.visible_message("[user] leg sweeps [target]!", \ + "[user] leg sweeps you!") + playsound(get_turf(user), 'sound/effects/hit_kick.ogg', 50, 1, -1) + target.apply_damage(5, BRUTE) + target.Weaken(2) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Leg Sweep", ATKLOG_ALL) + return MARTIAL_COMBO_DONE_CLEAR_COMBOS diff --git a/code/modules/martial_arts/combos/krav_maga/lung_punch.dm b/code/modules/martial_arts/combos/krav_maga/lung_punch.dm new file mode 100644 index 00000000000..e71289f9174 --- /dev/null +++ b/code/modules/martial_arts/combos/krav_maga/lung_punch.dm @@ -0,0 +1,12 @@ +/datum/martial_combo/krav_maga/lung_punch + name = "Lung Punch" + explaination_text = "Delivers a strong punch just above the victim's abdomen, constraining the lungs. The victim will be unable to breathe for a short time." + +/datum/martial_combo/krav_maga/lung_punch/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + target.visible_message("[user] pounds [target] on the chest!", \ + "[user] slams your chest! You can't breathe!") + playsound(get_turf(user), 'sound/effects/hit_punch.ogg', 50, 1, -1) + target.AdjustLoseBreath(5) + target.adjustOxyLoss(10) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Lung Punch", ATKLOG_ALL) + return MARTIAL_COMBO_DONE_CLEAR_COMBOS diff --git a/code/modules/martial_arts/combos/krav_maga/neck_chop.dm b/code/modules/martial_arts/combos/krav_maga/neck_chop.dm new file mode 100644 index 00000000000..c0e2f71b6a5 --- /dev/null +++ b/code/modules/martial_arts/combos/krav_maga/neck_chop.dm @@ -0,0 +1,12 @@ +/datum/martial_combo/krav_maga/neck_chop + name = "Neck Chop" + explaination_text = "Injures the neck, stopping the victim from speaking for a while." + +/datum/martial_combo/krav_maga/neck_chop/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + target.visible_message("[user] karate chops [target]'s neck!", \ + "[user] karate chops your neck, rendering you unable to speak for a short time!") + playsound(get_turf(user), 'sound/effects/hit_punch.ogg', 50, 1, -1) + target.apply_damage(5, BRUTE) + target.AdjustSilence(10) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Neck Chop", ATKLOG_ALL) + return MARTIAL_COMBO_DONE_CLEAR_COMBOS diff --git a/code/modules/martial_arts/combos/martial_combo.dm b/code/modules/martial_arts/combos/martial_combo.dm new file mode 100644 index 00000000000..6b4eedb97dc --- /dev/null +++ b/code/modules/martial_arts/combos/martial_combo.dm @@ -0,0 +1,36 @@ +/datum/martial_combo + /// Name used to explain the combo + var/name = "Code Fu" + /// Which steps need to be performed + var/list/steps + /// What index to check + var/current_step_index = 1 + /// Who is the target the combo is being executed on + var/current_combo_target = null + /// If you require to do the combo's on the same target + var/combos_require_same_target = TRUE + /// What does it do + var/explaination_text = "Ability to break shit" + /// How to do the combo. If null it'll auto generate it from the steps + var/combo_text_override + +/datum/martial_combo/proc/check_combo(step, mob/living/target) + if(!combos_require_same_target || current_combo_target == null || current_combo_target == target) + if(!length(steps) || step == steps[current_step_index]) + return TRUE + return FALSE + +/datum/martial_combo/proc/progress_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + current_combo_target = target + if(current_step_index++ >= LAZYLEN(steps)) + return perform_combo(user, target, MA) + return MARTIAL_COMBO_CONTINUE + +/datum/martial_combo/proc/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + return MARTIAL_COMBO_FAIL // Override this + +/datum/martial_combo/proc/give_explaination(user) + var/final_combo_text = combo_text_override + if(!final_combo_text) + final_combo_text = english_list(steps, and_text = " ", comma_text = " ") + to_chat(user, "[name]: [final_combo_text]. [explaination_text]") diff --git a/code/modules/martial_arts/combos/mimejutsu/mimechucks.dm b/code/modules/martial_arts/combos/mimejutsu/mimechucks.dm new file mode 100644 index 00000000000..b7f2d247b4b --- /dev/null +++ b/code/modules/martial_arts/combos/mimejutsu/mimechucks.dm @@ -0,0 +1,26 @@ +/datum/martial_combo/mimejutsu/mimechucks + name = "Mimechucks" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Hits the opponent with invisible nunchucks." + +/datum/martial_combo/mimejutsu/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat && !target.stunned && !target.IsWeakened()) + var/damage = rand(5, 8) + user.dna.species.punchdamagelow + if(!damage) + playsound(target.loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) + target.visible_message("[user] swings invisible nunchcuks at [target]..and misses?") + return MARTIAL_COMBO_DONE + + + var/obj/item/organ/external/affecting = target.get_organ(ran_zone(user.zone_selected)) + var/armor_block = target.run_armor_check(affecting, "melee") + + target.visible_message("[user] has hit [target] with invisible nunchucks!", \ + "[user] has hit [target] with a with invisible nunchuck!") + playsound(get_turf(user), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + + target.apply_damage(damage, STAMINA, affecting, armor_block) + add_attack_logs(user, target, "Melee attacked with [src] (mimechuck)") + + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/mimejutsu/silent_palm.dm b/code/modules/martial_arts/combos/mimejutsu/silent_palm.dm new file mode 100644 index 00000000000..eb8df68fac4 --- /dev/null +++ b/code/modules/martial_arts/combos/mimejutsu/silent_palm.dm @@ -0,0 +1,13 @@ +/datum/martial_combo/mimejutsu/silent_palm + name = "Silent Palm" + steps = list(MARTIAL_COMBO_STEP_GRAB, MARTIAL_COMBO_STEP_DISARM) + explaination_text = "Use mime energy to throw someone back." + +/datum/martial_combo/mimejutsu/silent_palm/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat && !target.stunned && !target.IsWeakened()) + target.visible_message("[user] has barely touched [target] with [user.p_their()] palm!", \ + "[user] hovers [user.p_their()] palm over your face!") + + var/atom/throw_target = get_edge_target_turf(target, get_dir(target, get_step_away(target, user))) + target.throw_at(throw_target, 200, 4, user) + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/mimejutsu/smokebomb.dm b/code/modules/martial_arts/combos/mimejutsu/smokebomb.dm new file mode 100644 index 00000000000..23277fa5f0d --- /dev/null +++ b/code/modules/martial_arts/combos/mimejutsu/smokebomb.dm @@ -0,0 +1,13 @@ +/datum/martial_combo/mimejutsu/smokebomb + name = "Smokebomb" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_DISARM) + explaination_text = "Drops a mime smokebomb." + +/datum/martial_combo/mimejutsu/smokebomb/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + target.visible_message("[user] throws an invisible smoke bomb!!") + + var/datum/effect_system/smoke_spread/bad/smoke = new + smoke.set_up(5, 0, target.loc) + smoke.start() + + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/plasma_fist/plasma_fist.dm b/code/modules/martial_arts/combos/plasma_fist/plasma_fist.dm new file mode 100644 index 00000000000..93c3e3ef480 --- /dev/null +++ b/code/modules/martial_arts/combos/plasma_fist/plasma_fist.dm @@ -0,0 +1,13 @@ +/datum/martial_combo/plasma_fist/plasma_fist + name = "The Plasma Fist" + steps = list(MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Knocks the brain out of the opponent and gibs their body." + +/datum/martial_combo/plasma_fist/plasma_fist/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + user.do_attack_animation(target, ATTACK_EFFECT_PUNCH) + playsound(target.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) + user.say("PLASMA FIST!") + target.visible_message("[user] has hit [target] with THE PLASMA FIST TECHNIQUE!", \ + "[user] has hit [target] with THE PLASMA FIST TECHNIQUE!") + target.gib() + return MARTIAL_COMBO_DONE diff --git a/code/modules/martial_arts/combos/plasma_fist/throwback.dm b/code/modules/martial_arts/combos/plasma_fist/throwback.dm new file mode 100644 index 00000000000..7057452a913 --- /dev/null +++ b/code/modules/martial_arts/combos/plasma_fist/throwback.dm @@ -0,0 +1,13 @@ +/datum/martial_combo/plasma_fist/throwback + name = "Throwback" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_DISARM) + explaination_text = "Throws the target and an item at them." + +/datum/martial_combo/plasma_fist/throwback/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + target.visible_message("[user] has hit [target] with Plasma Punch!", \ + "[user] has hit [target] with Plasma Punch!") + playsound(target.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) + var/atom/throw_target = get_edge_target_turf(target, get_dir(target, get_step_away(target, user))) + target.throw_at(throw_target, 200, 4, user) + user.say("HYAH!") + return MARTIAL_COMBO_DONE diff --git a/code/modules/martial_arts/combos/plasma_fist/tornado_sweep.dm b/code/modules/martial_arts/combos/plasma_fist/tornado_sweep.dm new file mode 100644 index 00000000000..41a7bd676d3 --- /dev/null +++ b/code/modules/martial_arts/combos/plasma_fist/tornado_sweep.dm @@ -0,0 +1,20 @@ +/datum/martial_combo/plasma_fist/tornado_sweep + name = "Tornado Sweep" + steps = list(MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_DISARM) + explaination_text = "Repulses target and everyone back." + +/datum/martial_combo/plasma_fist/tornado_sweep/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + user.say("TORNADO SWEEP!") + INVOKE_ASYNC(src, .proc/do_tornado_effect, user) + var/obj/effect/proc_holder/spell/aoe_turf/repulse/R = new(null) + var/list/turfs = list() + for(var/turf/T in range(1,user)) + turfs.Add(T) + R.cast(turfs) + return MARTIAL_COMBO_DONE + +/datum/martial_combo/plasma_fist/tornado_sweep/proc/do_tornado_effect(mob/living/carbon/human/user) + for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) + user.dir = i + playsound(user.loc, 'sound/weapons/punch1.ogg', 15, 1, -1) + sleep(1) diff --git a/code/modules/martial_arts/combos/sleeping_carp/back_kick.dm b/code/modules/martial_arts/combos/sleeping_carp/back_kick.dm new file mode 100644 index 00000000000..3ebaa191e23 --- /dev/null +++ b/code/modules/martial_arts/combos/sleeping_carp/back_kick.dm @@ -0,0 +1,18 @@ +/datum/martial_combo/sleeping_carp/back_kick + name = "Back Kick" + steps = list(MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_GRAB) + explaination_text = "Opponent must be facing away. Knocks down." + +/datum/martial_combo/sleeping_carp/back_kick/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(user.dir == target.dir && !target.stat && !target.IsWeakened()) + user.do_attack_animation(target, ATTACK_EFFECT_KICK) + target.visible_message("[user] kicks [target] in the back!", \ + "[user] kicks you in the back, making you stumble and fall!") + step_to(target,get_step(target,target.dir),1) + target.Weaken(4) + playsound(get_turf(target), 'sound/weapons/punch1.ogg', 50, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Back Kick", ATKLOG_ALL) + if(prob(80)) + user.say(pick("SURRPRIZU!","BACK STRIKE!","WOPAH!", "WATAAH", "ZOTA!", "Never turn your back to the enemy!")) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/sleeping_carp/elbow_drop.dm b/code/modules/martial_arts/combos/sleeping_carp/elbow_drop.dm new file mode 100644 index 00000000000..0269c9bed4d --- /dev/null +++ b/code/modules/martial_arts/combos/sleeping_carp/elbow_drop.dm @@ -0,0 +1,18 @@ +/datum/martial_combo/sleeping_carp/elbow_drop + name = "Elbow Drop" + steps = list(MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Opponent must be on the ground. Deals huge damage, instantly kills anyone in critical condition." + +/datum/martial_combo/sleeping_carp/elbow_drop/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(target.IsWeakened() || target.resting || target.stat) + user.do_attack_animation(target, ATTACK_EFFECT_PUNCH) + target.visible_message("[user] elbow drops [target]!", \ + "[user] piledrives you with [user.p_their()] elbow!") + target.death() //FINISH HIM! + target.apply_damage(50, BRUTE, "chest") + playsound(get_turf(target), 'sound/weapons/punch1.ogg', 75, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Elbow Drop", ATKLOG_ALL) + if(prob(80)) + user.say(pick("BANZAIII!", "KIYAAAA!", "OMAE WA MOU SHINDEIRU!", "YOU CAN'T SEE ME!", "MY TIME IS NOW!", "COWABUNGA")) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/sleeping_carp/head_kick.dm b/code/modules/martial_arts/combos/sleeping_carp/head_kick.dm new file mode 100644 index 00000000000..b45e10e73ce --- /dev/null +++ b/code/modules/martial_arts/combos/sleeping_carp/head_kick.dm @@ -0,0 +1,19 @@ +/datum/martial_combo/sleeping_carp/head_kick + name = "Head Kick" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_HARM, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Decent damage, forces opponent to drop item in hand." + +/datum/martial_combo/sleeping_carp/head_kick/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat && !target.IsWeakened()) + user.do_attack_animation(target, ATTACK_EFFECT_KICK) + target.visible_message("[user] kicks [target] in the head!", \ + "[user] kicks you in the jaw!") + target.apply_damage(20, BRUTE, "head") + target.drop_item() + playsound(get_turf(target), 'sound/weapons/punch1.ogg', 50, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Head Kick", ATKLOG_ALL) + if(prob(60)) + user.say(pick("OOHYOO!", "OOPYAH!", "HYOOAA!", "WOOAAA!", "SHURYUKICK!", "HIYAH!")) + target.Stun(4) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/sleeping_carp/stomach_knee.dm b/code/modules/martial_arts/combos/sleeping_carp/stomach_knee.dm new file mode 100644 index 00000000000..312e575003d --- /dev/null +++ b/code/modules/martial_arts/combos/sleeping_carp/stomach_knee.dm @@ -0,0 +1,20 @@ +/datum/martial_combo/sleeping_carp/stomach_knee + name = "Stomach Knee" + steps = list(MARTIAL_COMBO_STEP_GRAB, MARTIAL_COMBO_STEP_HARM) + explaination_text = "Knocks the wind out of opponent and stuns." + combo_text_override = "Grab, switch hands, Harm" + +/datum/martial_combo/sleeping_carp/stomach_knee/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat && !target.IsWeakened()) + user.do_attack_animation(target, ATTACK_EFFECT_KICK) + target.visible_message("[user] knees [target] in the stomach!", \ + "[user] winds you with a knee in the stomach!") + target.audible_message("[target] gags!") + target.AdjustLoseBreath(3) + target.Stun(2) + playsound(get_turf(target), 'sound/weapons/punch1.ogg', 50, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Stomach Knee", ATKLOG_ALL) + if(prob(80)) + user.say(pick("HWOP!", "KUH!", "YAKUUH!", "KYUH!", "KNEESTRIKE!")) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/combos/sleeping_carp/wrist_wrench.dm b/code/modules/martial_arts/combos/sleeping_carp/wrist_wrench.dm new file mode 100644 index 00000000000..38c21e7309e --- /dev/null +++ b/code/modules/martial_arts/combos/sleeping_carp/wrist_wrench.dm @@ -0,0 +1,20 @@ +/datum/martial_combo/sleeping_carp/wrist_wrench + name = "Wrist Wrench" + steps = list(MARTIAL_COMBO_STEP_DISARM, MARTIAL_COMBO_STEP_DISARM) + explaination_text = "Forces opponent to drop item in hand." + +/datum/martial_combo/sleeping_carp/wrist_wrench/perform_combo(mob/living/carbon/human/user, mob/living/target, datum/martial_art/MA) + if(!target.stat && !target.stunned && !target.IsWeakened()) + user.do_attack_animation(target, ATTACK_EFFECT_PUNCH) + target.visible_message("[user] grabs [target]'s wrist and wrenches it sideways!", \ + "[user] grabs your wrist and violently wrenches it to the side!") + playsound(get_turf(user), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + add_attack_logs(user, target, "Melee attacked with martial-art [src] : Wrist Wrench", ATKLOG_ALL) + if(prob(60)) + user.say(pick("WRISTY TWIRLY!", "WE FIGHT LIKE MEN!", "YOU DISHONOR YOURSELF!", "POHYAH!", "WHERE IS YOUR BATON NOW?", "SAY UNCLE!")) + target.emote("scream") + target.drop_item() + target.apply_damage(5, BRUTE, pick("l_arm", "r_arm")) + target.Stun(3) + return MARTIAL_COMBO_DONE + return MARTIAL_COMBO_DONE_BASIC_HIT diff --git a/code/modules/martial_arts/cqc.dm b/code/modules/martial_arts/cqc.dm index f0b5c016c5b..49a08c1bd3c 100644 --- a/code/modules/martial_arts/cqc.dm +++ b/code/modules/martial_arts/cqc.dm @@ -1,152 +1,27 @@ -#define SLAM_COMBO "GH" -#define KICK_COMBO "HH" -#define RESTRAIN_COMBO "GG" -#define PRESSURE_COMBO "DG" -#define CONSECUTIVE_COMBO "DDH" - /datum/martial_art/cqc name = "CQC" - help_verb = /mob/living/carbon/human/proc/CQC_help block_chance = 75 - var/just_a_cook = FALSE + has_explaination_verb = TRUE + combos = list(/datum/martial_combo/cqc/slam, /datum/martial_combo/cqc/kick, /datum/martial_combo/cqc/restrain, /datum/martial_combo/cqc/pressure, /datum/martial_combo/cqc/consecutive) + var/restraining = FALSE //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not var/static/list/areas_under_siege = typecacheof(list(/area/crew_quarters/kitchen, /area/crew_quarters/cafeteria, /area/crew_quarters/bar)) /datum/martial_art/cqc/under_siege name = "Close Quarters Cooking" - just_a_cook = TRUE + +/datum/martial_art/cqc/under_siege/can_use(mob/living/carbon/human/H) + var/area/A = get_area(H) + if(!(is_type_in_typecache(A, areas_under_siege))) + return FALSE + return ..() /datum/martial_art/cqc/proc/drop_restraining() restraining = FALSE -/datum/martial_art/cqc/can_use(mob/living/carbon/human/H) - var/area/A = get_area(H) - if(just_a_cook && !(is_type_in_typecache(A, areas_under_siege))) - return FALSE - return ..() - -/datum/martial_art/cqc/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - if(findtext(streak, SLAM_COMBO)) - streak = "" - Slam(A, D) - return TRUE - if(findtext(streak, KICK_COMBO)) - streak = "" - Kick(A, D) - return TRUE - if(findtext(streak, RESTRAIN_COMBO)) - streak = "" - Restrain(A, D) - return TRUE - if(findtext(streak, PRESSURE_COMBO)) - streak = "" - Pressure(A, D) - return TRUE - if(findtext(streak, CONSECUTIVE_COMBO)) - streak = "" - Consecutive(A, D) - return FALSE - -/datum/martial_art/cqc/proc/Slam(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - if(!D.IsWeakened() && !D.resting && !D.lying) - D.visible_message("[A] slams [D] into the ground!", \ - "[A] slams you into the ground!") - playsound(get_turf(A), 'sound/weapons/slam.ogg', 50, 1, -1) - D.apply_damage(10, BRUTE) - D.Weaken(6) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Slam", ATKLOG_ALL) - return TRUE - streak = "" - harm_act(A, D) - streak = "" - return TRUE - -/datum/martial_art/cqc/proc/Kick(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - var/success = FALSE - if(!D.stat || !D.IsWeakened()) - D.visible_message("[A] kicks [D] back!", \ - "[A] kicks you back!") - playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1) - var/atom/throw_target = get_edge_target_turf(D, A.dir) - D.throw_at(throw_target, 1, 14, A) - D.apply_damage(10, BRUTE) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Kick", ATKLOG_ALL) - success = TRUE - if(D.IsWeakened() && !D.stat) - D.visible_message("[A] kicks [D]'s head, knocking [D.p_them()] out!", \ - "[A] kicks your head, knocking you out!") - playsound(get_turf(A), 'sound/weapons/genhit1.ogg', 50, 1, -1) - D.SetSleeping(15) - D.adjustBrainLoss(15) - add_attack_logs(A, D, "Knocked out with martial-art [src] : Kick", ATKLOG_ALL) - success = TRUE - if(success) - return TRUE - streak = "" - harm_act(A, D) - streak = "" - return TRUE - -/datum/martial_art/cqc/proc/Pressure(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - D.visible_message("[A] forces their arm on [D]'s neck!") - D.adjustStaminaLoss(60) - playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Pressure", ATKLOG_ALL) - return TRUE - -/datum/martial_art/cqc/proc/Restrain(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(restraining) - return - if(!can_use(A)) - return FALSE - if(!D.stat) - D.visible_message("[A] locks [D] into a restraining position!", \ - "[A] locks you into a restraining position!") - D.adjustStaminaLoss(20) - D.Stun(5) - restraining = TRUE - addtimer(CALLBACK(src, .proc/drop_restraining), 50, TIMER_UNIQUE) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Restrain", ATKLOG_ALL) - return TRUE - streak = "" - harm_act(A, D) - streak = "" - return TRUE - -/datum/martial_art/cqc/proc/Consecutive(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - if(!D.stat) - D.visible_message("[A] strikes [D]'s abdomen, neck and back consecutively", \ - "[A] strikes your abdomen, neck and back consecutively!") - playsound(get_turf(D), 'sound/weapons/cqchit2.ogg', 50, 1, -1) - var/obj/item/I = D.get_active_hand() - if(I && D.drop_item()) - A.put_in_hands(I) - D.adjustStaminaLoss(50) - D.apply_damage(25, BRUTE) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Consecutive", ATKLOG_ALL) - return TRUE - streak = "" - harm_act(A, D) - streak = "" - return TRUE - /datum/martial_art/cqc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - add_to_streak("G", D) - if(check_streak(A, D)) - return TRUE + MARTIAL_ARTS_ACT_CHECK var/obj/item/grab/G = D.grabbedby(A, 1) if(G) G.state = GRAB_AGGRESSIVE //Instant aggressive grab @@ -155,11 +30,7 @@ return TRUE /datum/martial_art/cqc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - add_to_streak("H", D) - if(check_streak(A, D)) - return TRUE + MARTIAL_ARTS_ACT_CHECK add_attack_logs(A, D, "Melee attacked with martial-art [src]", ATKLOG_ALL) A.do_attack_animation(D) var/picked_hit_type = pick("CQC'd", "neck chopped", "gut punched", "Big Bossed") @@ -185,12 +56,7 @@ return TRUE /datum/martial_art/cqc/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) - if(!can_use(A)) - return FALSE - add_to_streak("D", D) - var/obj/item/I = null - if(check_streak(A, D)) - return TRUE + MARTIAL_ARTS_ACT_CHECK var/obj/item/grab/G = A.get_inactive_hand() if(restraining && istype(G) && G.affecting == D) D.visible_message("[A] puts [D] into a chokehold!", \ @@ -203,6 +69,8 @@ else restraining = FALSE + var/obj/item/I = null + if(prob(65)) if(!D.stat || !D.IsWeakened() || !restraining) I = D.get_active_hand() @@ -220,16 +88,8 @@ add_attack_logs(A, D, "Melee attacked with martial-art [src] : Disarmed [I ? " grabbing \the [I]" : ""]", ATKLOG_ALL) return TRUE -/mob/living/carbon/human/proc/CQC_help() - set name = "Remember The Basics" - set desc = "You try to remember some of the basics of CQC." - set category = "CQC" - to_chat(usr, "You try to remember some of the basics of CQC.") +/datum/martial_art/cqc/explaination_header(user) + to_chat(user, "You try to remember some of the basics of CQC.") - to_chat(usr, "Slam: Grab, switch hands, Harm. Slam opponent into the ground, knocking them down.") - to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.") - to_chat(usr, "Restrain: Grab, switch hands, Grab. Locks opponents into a restraining position, disarm to knock them out with a choke hold.") - to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.") - to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.") - - to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.") +/datum/martial_art/cqc/explaination_footer(user) + to_chat(user, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter attacks done to you.") diff --git a/code/modules/martial_arts/krav_maga.dm b/code/modules/martial_arts/krav_maga.dm index d348b3765fb..cd3a6214e83 100644 --- a/code/modules/martial_arts/krav_maga.dm +++ b/code/modules/martial_arts/krav_maga.dm @@ -15,7 +15,9 @@ to_chat(owner, "Your next attack will be a Neck Chop.") owner.visible_message("[owner] assumes the Neck Chop stance!") var/mob/living/carbon/human/H = owner - H.martial_art.streak = "neck_chop" + H.mind.martial_art.combos.Cut() + H.mind.martial_art.combos.Add(/datum/martial_combo/krav_maga/neck_chop) + H.mind.martial_art.reset_combos() /datum/action/leg_sweep name = "Leg Sweep - Trips the victim, rendering them prone and unable to move for a short time." @@ -28,7 +30,9 @@ to_chat(owner, "Your next attack will be a Leg Sweep.") owner.visible_message("[owner] assumes the Leg Sweep stance!") var/mob/living/carbon/human/H = owner - H.martial_art.streak = "leg_sweep" + H.mind.martial_art.combos.Cut() + H.mind.martial_art.combos.Add(/datum/martial_combo/krav_maga/leg_sweep) + H.mind.martial_art.reset_combos() /datum/action/lung_punch//referred to internally as 'quick choke' name = "Lung Punch - Delivers a strong punch just above the victim's abdomen, constraining the lungs. The victim will be unable to breathe for a short time." @@ -41,7 +45,9 @@ to_chat(owner, "Your next attack will be a Lung Punch.") owner.visible_message("[owner] assumes the Lung Punch stance!") var/mob/living/carbon/human/H = owner - H.martial_art.streak = "quick_choke"//internal name for lung punch + H.mind.martial_art.combos.Cut() + H.mind.martial_art.combos.Add(/datum/martial_combo/krav_maga/lung_punch) + H.mind.martial_art.reset_combos() /datum/martial_art/krav_maga/teach(var/mob/living/carbon/human/H,var/make_temporary=0) ..() @@ -58,59 +64,8 @@ legsweep.Remove(H) lungpunch.Remove(H) -/datum/martial_art/krav_maga/proc/check_streak(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - switch(streak) - if("neck_chop") - streak = "" - neck_chop(A,D) - return 1 - if("leg_sweep") - streak = "" - leg_sweep(A,D) - return 1 - if("quick_choke")//is actually lung punch - streak = "" - quick_choke(A,D) - return 1 - return 0 - -/datum/martial_art/krav_maga/proc/leg_sweep(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(D.stat || D.IsWeakened()) - return 0 - D.visible_message("[A] leg sweeps [D]!", \ - "[A] leg sweeps you!") - playsound(get_turf(A), 'sound/effects/hit_kick.ogg', 50, 1, -1) - D.apply_damage(5, BRUTE) - D.Weaken(2) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Leg Sweep", ATKLOG_ALL) - return 1 - -/datum/martial_art/krav_maga/proc/quick_choke(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D)//is actually lung punch - D.visible_message("[A] pounds [D] on the chest!", \ - "[A] slams your chest! You can't breathe!") - playsound(get_turf(A), 'sound/effects/hit_punch.ogg', 50, 1, -1) - D.AdjustLoseBreath(5) - D.adjustOxyLoss(10) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Lung Punch", ATKLOG_ALL) - return 1 - -/datum/martial_art/krav_maga/proc/neck_chop(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - D.visible_message("[A] karate chops [D]'s neck!", \ - "[A] karate chops your neck, rendering you unable to speak for a short time!") - playsound(get_turf(A), 'sound/effects/hit_punch.ogg', 50, 1, -1) - D.apply_damage(5, BRUTE) - D.AdjustSilence(10) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Neck Chop", ATKLOG_ALL) - return 1 - -/datum/martial_art/krav_maga/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(check_streak(A,D)) - return 1 - ..() - /datum/martial_art/krav_maga/harm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK add_attack_logs(A, D, "Melee attacked with [src]") var/picked_hit_type = pick("punches", "kicks") var/bonus_damage = 10 @@ -126,11 +81,10 @@ playsound(get_turf(D), 'sound/effects/hit_punch.ogg', 50, 1, -1) D.visible_message("[A] [picked_hit_type] [D]!", \ "[A] [picked_hit_type] you!") - return 1 + return TRUE /datum/martial_art/krav_maga/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK if(prob(60)) if(D.hand) if(istype(D.l_hand, /obj/item)) @@ -149,7 +103,7 @@ D.visible_message("[A] attempted to disarm [D]!", \ "[A] attempted to disarm [D]!") playsound(D, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - return 1 + return TRUE //Krav Maga Gloves diff --git a/code/modules/martial_arts/martial.dm b/code/modules/martial_arts/martial.dm index 8969463be1d..50ca634a22f 100644 --- a/code/modules/martial_arts/martial.dm +++ b/code/modules/martial_arts/martial.dm @@ -1,42 +1,87 @@ +#define HAS_COMBOS LAZYLEN(combos) +#define COMBO_ALIVE_TIME 5 SECONDS // How long the combo stays alive when no new attack is done + /datum/martial_art var/name = "Martial Art" var/streak = "" var/max_streak_length = 6 - var/current_target = null - var/temporary = 0 + var/temporary = FALSE var/datum/martial_art/base = null // The permanent style var/deflection_chance = 0 //Chance to deflect projectiles var/block_chance = 0 //Chance to block melee attacks using items while on throw mode. - var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not var/help_verb = null var/no_guns = FALSE //set to TRUE to prevent users of this style from using guns (sleeping carp, highlander). They can still pick them up, but not fire them. var/no_guns_message = "" //message to tell the style user if they try and use a gun while no_guns = TRUE (DISHONORABRU!) -/datum/martial_art/proc/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - return 0 + var/has_explaination_verb = FALSE // If the martial art has it's own explaination verb -/datum/martial_art/proc/harm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - return 0 + var/list/combos = list() // What combos can the user do? List of combo types + var/list/datum/martial_art/current_combos = list() // What combos are currently (possibly) being performed + var/last_hit = 0 // When the last hit happened -/datum/martial_art/proc/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - return 0 +/datum/martial_art/New() + . = ..() + reset_combos() -/datum/martial_art/proc/help_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - return 0 +/datum/martial_art/proc/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + return act(MARTIAL_COMBO_STEP_DISARM, A, D) + +/datum/martial_art/proc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + return act(MARTIAL_COMBO_STEP_HARM, A, D) + +/datum/martial_art/proc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + return act(MARTIAL_COMBO_STEP_GRAB, A, D) + +/datum/martial_art/proc/help_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + return act(MARTIAL_COMBO_STEP_HELP, A, D) /datum/martial_art/proc/can_use(mob/living/carbon/human/H) return TRUE -/datum/martial_art/proc/add_to_streak(var/element,var/mob/living/carbon/human/D) - if(D != current_target) - current_target = D - streak = "" - streak = streak+element - if(length(streak) > max_streak_length) - streak = copytext(streak,2) - return +/datum/martial_art/proc/act(step, mob/living/carbon/human/user, mob/living/carbon/human/target) + if(!can_use(user)) + return MARTIAL_ARTS_CANNOT_USE + if(last_hit + COMBO_ALIVE_TIME < world.time) + reset_combos() + last_hit = world.time -/datum/martial_art/proc/basic_hit(var/mob/living/carbon/human/A,var/mob/living/carbon/human/D) + if(HAS_COMBOS) + return check_combos(step, user, target) + return FALSE + +/datum/martial_art/proc/reset_combos() + current_combos.Cut() + for(var/combo_type in combos) + current_combos.Add(new combo_type()) + +/datum/martial_art/proc/check_combos(step, mob/living/carbon/human/user, mob/living/carbon/human/target) + . = FALSE + for(var/thing in current_combos) + var/datum/martial_combo/MC = thing + if(!MC.check_combo(step, target)) + current_combos -= MC // It failed so remove it + else + switch(MC.progress_combo(user, target, src)) + if(MARTIAL_COMBO_FAIL) + current_combos -= MC + if(MARTIAL_COMBO_DONE_NO_CLEAR) + . = TRUE + current_combos -= MC + if(MARTIAL_COMBO_DONE) + reset_combos() + return TRUE + if(MARTIAL_COMBO_DONE_BASIC_HIT) + basic_hit(user, target) + reset_combos() + return TRUE + if(MARTIAL_COMBO_DONE_CLEAR_COMBOS) + combos.Cut() + reset_combos() + return TRUE + if(!LAZYLEN(current_combos)) + reset_combos() + +/datum/martial_art/proc/basic_hit(mob/living/carbon/human/A, mob/living/carbon/human/D) var/damage = rand(A.dna.species.punchdamagelow, A.dna.species.punchdamagehigh) var/datum/unarmed_attack/attack = A.dna.species.unarmed @@ -54,7 +99,7 @@ if(!damage) playsound(D.loc, attack.miss_sound, 25, 1, -1) D.visible_message("[A] has attempted to [atk_verb] [D]!") - return 0 + return FALSE var/obj/item/organ/external/affecting = D.get_organ(ran_zone(A.zone_selected)) var/armor_block = D.run_armor_check(affecting, "melee") @@ -74,26 +119,60 @@ D.forcesay(GLOB.hit_appends) else if(D.lying) D.forcesay(GLOB.hit_appends) - return 1 + return TRUE -/datum/martial_art/proc/teach(var/mob/living/carbon/human/H,var/make_temporary=0) - if(help_verb) - H.verbs += help_verb +/datum/martial_art/proc/teach(mob/living/carbon/human/H, make_temporary = FALSE) + if(!H.mind) + return + if(has_explaination_verb) + H.verbs |= /mob/living/carbon/human/proc/martial_arts_help if(make_temporary) - temporary = 1 + temporary = TRUE if(temporary) - if(H.martial_art) - base = H.martial_art.base + if(H.mind.martial_art) + base = H.mind.martial_art.base else base = src - H.martial_art = src + H.mind.martial_art = src /datum/martial_art/proc/remove(var/mob/living/carbon/human/H) - if(H.martial_art != src) + if(!H.mind) return - H.martial_art = base - if(help_verb) - H.verbs -= help_verb + if(H.mind.martial_art != src) + return + H.mind.martial_art = base + if(has_explaination_verb && !(base && base.has_explaination_verb)) + H.verbs -= /mob/living/carbon/human/proc/martial_arts_help + +/mob/living/carbon/human/proc/martial_arts_help() + set name = "Show Info" + set desc = "Gives information about the martial arts you know." + set category = "Martial Arts" + var/mob/living/carbon/human/H = usr + if(!istype(H)) + to_chat(usr, "You shouldn't have access to this verb. Report this as a bug to the github please.") + return + H.mind.martial_art.give_explaination(H) + +/datum/martial_art/proc/give_explaination(user = usr) + explaination_header(user) + explaination_combos(user) + explaination_footer(user) + +// Put after the header and before the footer in the explaination text +/datum/martial_art/proc/explaination_combos(user) + if(HAS_COMBOS) + for(var/combo_type in combos) + var/datum/martial_combo/MC = new combo_type() + MC.give_explaination(user) + +// Put on top of the explaination text +/datum/martial_art/proc/explaination_header(user) + return + +// Put below the combos in the explaination text +/datum/martial_art/proc/explaination_footer(user) + return //ITEMS @@ -283,3 +362,6 @@ if(wielded) return ..() return 0 + +#undef HAS_COMBOS +#undef COMBO_ALIVE_TIME diff --git a/code/modules/martial_arts/mimejutsu.dm b/code/modules/martial_arts/mimejutsu.dm index 38904d407de..d57df486638 100644 --- a/code/modules/martial_arts/mimejutsu.dm +++ b/code/modules/martial_arts/mimejutsu.dm @@ -1,90 +1,16 @@ -#define MIMECHUCKS_COMBO "DH" -#define MIMESMOKE_COMBO "DD" -#define MIMEPALM_COMBO "GD" - /datum/martial_art/mimejutsu name = "Mimejutsu" - help_verb = /mob/living/carbon/human/proc/mimejutsu_help - -/datum/martial_art/mimejutsu/proc/check_streak(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(findtext(streak,MIMECHUCKS_COMBO)) - streak = "" - mimeChuck(A,D) - return 1 - if(findtext(streak,MIMESMOKE_COMBO)) - streak = "" - mimeSmoke(A,D) - return 1 - if(findtext(streak,MIMEPALM_COMBO)) - streak = "" - mimePalm(A,D) - return 1 - return 0 - -/datum/martial_art/mimejutsu/proc/mimeChuck(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(!D.stat && !D.stunned && !D.IsWeakened()) - var/damage = rand(5, 8) + A.dna.species.punchdamagelow - if(!damage) - playsound(D.loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) - D.visible_message("[A] swings invisible nunchcuks at [D]..and misses?") - return 0 - - - var/obj/item/organ/external/affecting = D.get_organ(ran_zone(A.zone_selected)) - var/armor_block = D.run_armor_check(affecting, "melee") - - D.visible_message("[A] has hit [D] with invisible nunchucks!", \ - "[A] has hit [D] with a with invisible nunchuck!") - playsound(get_turf(A), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - - D.apply_damage(damage, STAMINA, affecting, armor_block) - add_attack_logs(A, D, "Melee attacked with [src] (mimechuck)") - - return 1 - return basic_hit(A,D) - -/datum/martial_art/mimejutsu/proc/mimeSmoke(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - - D.visible_message("[A] throws an invisible smoke bomb!!") - - var/datum/effect_system/smoke_spread/bad/smoke = new - smoke.set_up(5, 0, D.loc) - smoke.start() - - return basic_hit(A,D) - -/datum/martial_art/mimejutsu/proc/mimePalm(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(!D.stat && !D.stunned && !D.IsWeakened()) - D.visible_message("[A] has barely touched [D] with [A.p_their()] palm!", \ - "[A] hovers [A.p_their()] palm over your face!") - - var/atom/throw_target = get_edge_target_turf(D, get_dir(D, get_step_away(D, A))) - D.throw_at(throw_target, 200, 4,A) - return basic_hit(A,D) - - -/datum/martial_art/mimejutsu/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("D",D) - if(check_streak(A,D)) - return 1 - - return ..() + has_explaination_verb = TRUE + combos = list(/datum/martial_combo/mimejutsu/mimechucks, /datum/martial_combo/mimejutsu/smokebomb, /datum/martial_combo/mimejutsu/silent_palm) /datum/martial_art/mimejutsu/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("G",D) - if(check_streak(A,D)) - return 1 - - return 1 + MARTIAL_ARTS_ACT_CHECK + return TRUE /datum/martial_art/mimejutsu/harm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("H",D) - if(check_streak(A,D)) - return 1 - + MARTIAL_ARTS_ACT_CHECK A.do_attack_animation(D) - - return 1 + return TRUE /obj/item/mimejutsu_scroll name = "Mimejutsu 'scroll'" @@ -106,13 +32,5 @@ name = "beret with staple" icon_state = "beret" -/mob/living/carbon/human/proc/mimejutsu_help() - set name = "Recall Ancient Mimeing" - set desc = "Remember the martial techniques of Mimejutsu." - set category = "Mimejutsu" - - to_chat(usr, "You make a invisible box around yourself and recall the teachings of Mimejutsu...") - - to_chat(usr, "Mimechucks: Disarm Harm. Hits the opponent with invisible nunchucks.") - to_chat(usr, "Smokebomb: Disarm Disarm. Drops a mime smokebomb.") - to_chat(usr, "Silent Palm: Grab Disarm. Using mime energy throw someone back.") +/datum/martial_art/mimejutsu/explaination_header(user) + to_chat(user, "You make a invisible box around yourself and recall the teachings of Mimejutsu...") diff --git a/code/modules/martial_arts/plasma_fist.dm b/code/modules/martial_arts/plasma_fist.dm index ea5aef5ea74..a216a5d730f 100644 --- a/code/modules/martial_arts/plasma_fist.dm +++ b/code/modules/martial_arts/plasma_fist.dm @@ -1,86 +1,22 @@ -#define TORNADO_COMBO "HHD" -#define THROWBACK_COMBO "DHD" -#define PLASMA_COMBO "HDDDH" - /datum/martial_art/plasma_fist name = "Plasma Fist" - help_verb = /mob/living/carbon/human/proc/plasma_fist_help - - -/datum/martial_art/plasma_fist/proc/check_streak(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(findtext(streak,TORNADO_COMBO)) - streak = "" - Tornado(A,D) - return 1 - if(findtext(streak,THROWBACK_COMBO)) - streak = "" - Throwback(A,D) - return 1 - if(findtext(streak,PLASMA_COMBO)) - streak = "" - Plasma(A,D) - return 1 - return 0 - -/datum/martial_art/plasma_fist/proc/Tornado(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - A.say("TORNADO SWEEP!") - spawn(0) - for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH)) - A.dir = i - playsound(A.loc, 'sound/weapons/punch1.ogg', 15, 1, -1) - sleep(1) - var/obj/effect/proc_holder/spell/aoe_turf/repulse/R = new(null) - var/list/turfs = list() - for(var/turf/T in range(1,A)) - turfs.Add(T) - R.cast(turfs) - return - -/datum/martial_art/plasma_fist/proc/Throwback(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - D.visible_message("[A] has hit [D] with Plasma Punch!", \ - "[A] has hit [D] with Plasma Punch!") - playsound(D.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) - var/atom/throw_target = get_edge_target_turf(D, get_dir(D, get_step_away(D, A))) - D.throw_at(throw_target, 200, 4,A) - A.say("HYAH!") - return - -/datum/martial_art/plasma_fist/proc/Plasma(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) - playsound(D.loc, 'sound/weapons/punch1.ogg', 50, 1, -1) - A.say("PLASMA FIST!") - D.visible_message("[A] has hit [D] with THE PLASMA FIST TECHNIQUE!", \ - "[A] has hit [D] with THE PLASMA FIST TECHNIQUE!") - D.gib() - return + combos = list(/datum/martial_combo/plasma_fist/tornado_sweep, /datum/martial_combo/plasma_fist/throwback, /datum/martial_combo/plasma_fist/plasma_fist) + has_explaination_verb = TRUE /datum/martial_art/plasma_fist/harm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("H",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK basic_hit(A,D) - return 1 + return TRUE /datum/martial_art/plasma_fist/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("D",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK basic_hit(A,D) - return 1 + return TRUE /datum/martial_art/plasma_fist/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("G",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK basic_hit(A,D) - return 1 + return TRUE -/mob/living/carbon/human/proc/plasma_fist_help() - set name = "Recall Teachings" - set desc = "Remember the martial techniques of the Plasma Fist." - set category = "Plasma Fist" - - to_chat(usr, "You clench your fists and have a flashback of knowledge...") - to_chat(usr, "Tornado Sweep: Harm Harm Disarm. Repulses target and everyone back.") - to_chat(usr, "Throwback: Disarm Harm Disarm. Throws the target and an item at them.") - to_chat(usr, "The Plasma Fist: Harm Disarm Disarm Disarm Harm. Knocks the brain out of the opponent and gibs their body.") +/datum/martial_art/plasma_fist/explaination_header(user) + to_chat(user, "You clench your fists and have a flashback of knowledge...") diff --git a/code/modules/martial_arts/sleeping_carp.dm b/code/modules/martial_arts/sleeping_carp.dm index 472fb6ca506..9ca070f8bfd 100644 --- a/code/modules/martial_arts/sleeping_carp.dm +++ b/code/modules/martial_arts/sleeping_carp.dm @@ -1,126 +1,21 @@ //Used by the gang of the same name. Uses combos. Basic attacks bypass armor and never miss -#define WRIST_WRENCH_COMBO "DD" -#define BACK_KICK_COMBO "HG" -#define STOMACH_KNEE_COMBO "GH" -#define HEAD_KICK_COMBO "DHH" -#define ELBOW_DROP_COMBO "HDHDH" /datum/martial_art/the_sleeping_carp name = "The Sleeping Carp" deflection_chance = 100 - help_verb = /mob/living/carbon/human/proc/sleeping_carp_help no_guns = TRUE no_guns_message = "Use of ranged weaponry would bring dishonor to the clan." - -/datum/martial_art/the_sleeping_carp/proc/check_streak(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(findtext(streak,WRIST_WRENCH_COMBO)) - streak = "" - wristWrench(A,D) - return 1 - if(findtext(streak,BACK_KICK_COMBO)) - streak = "" - backKick(A,D) - return 1 - if(findtext(streak,STOMACH_KNEE_COMBO)) - streak = "" - kneeStomach(A,D) - return 1 - if(findtext(streak,HEAD_KICK_COMBO)) - streak = "" - headKick(A,D) - return 1 - if(findtext(streak,ELBOW_DROP_COMBO)) - streak = "" - elbowDrop(A,D) - return 1 - return 0 - -/datum/martial_art/the_sleeping_carp/proc/wristWrench(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(!D.stat && !D.stunned && !D.IsWeakened()) - A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) - D.visible_message("[A] grabs [D]'s wrist and wrenches it sideways!", \ - "[A] grabs your wrist and violently wrenches it to the side!") - playsound(get_turf(A), 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Wrist Wrench", ATKLOG_ALL) - if(prob(60)) - A.say(pick("WRISTY TWIRLY!", "WE FIGHT LIKE MEN!", "YOU DISHONOR YOURSELF!", "POHYAH!", "WHERE IS YOUR BATON NOW?", "SAY UNCLE!")) - D.emote("scream") - D.drop_item() - D.apply_damage(5, BRUTE, pick("l_arm", "r_arm")) - D.Stun(3) - return 1 - return basic_hit(A,D) - -/datum/martial_art/the_sleeping_carp/proc/backKick(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(A.dir == D.dir && !D.stat && !D.IsWeakened()) - A.do_attack_animation(D, ATTACK_EFFECT_KICK) - D.visible_message("[A] kicks [D] in the back!", \ - "[A] kicks you in the back, making you stumble and fall!") - step_to(D,get_step(D,D.dir),1) - D.Weaken(4) - playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Back Kick", ATKLOG_ALL) - if(prob(80)) - A.say(pick("SURRPRIZU!","BACK STRIKE!","WOPAH!", "WATAAH", "ZOTA!", "Never turn your back to the enemy!")) - return 1 - return basic_hit(A,D) - -/datum/martial_art/the_sleeping_carp/proc/kneeStomach(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(!D.stat && !D.IsWeakened()) - A.do_attack_animation(D, ATTACK_EFFECT_KICK) - D.visible_message("[A] knees [D] in the stomach!", \ - "[A] winds you with a knee in the stomach!") - D.audible_message("[D] gags!") - D.AdjustLoseBreath(3) - D.Stun(2) - playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Stomach Knee", ATKLOG_ALL) - if(prob(80)) - A.say(pick("HWOP!", "KUH!", "YAKUUH!", "KYUH!", "KNEESTRIKE!")) - return 1 - return basic_hit(A,D) - -/datum/martial_art/the_sleeping_carp/proc/headKick(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(!D.stat && !D.IsWeakened()) - A.do_attack_animation(D, ATTACK_EFFECT_KICK) - D.visible_message("[A] kicks [D] in the head!", \ - "[A] kicks you in the jaw!") - D.apply_damage(20, BRUTE, "head") - D.drop_item() - playsound(get_turf(D), 'sound/weapons/punch1.ogg', 50, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Head Kick", ATKLOG_ALL) - if(prob(60)) - A.say(pick("OOHYOO!", "OOPYAH!", "HYOOAA!", "WOOAAA!", "SHURYUKICK!", "HIYAH!")) - D.Stun(4) - return 1 - return basic_hit(A,D) - -/datum/martial_art/the_sleeping_carp/proc/elbowDrop(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - if(D.IsWeakened() || D.resting || D.stat) - A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) - D.visible_message("[A] elbow drops [D]!", \ - "[A] piledrives you with [A.p_their()] elbow!") - if(D.stat) - D.death() //FINISH HIM! - D.apply_damage(50, BRUTE, "chest") - playsound(get_turf(D), 'sound/weapons/punch1.ogg', 75, 1, -1) - add_attack_logs(A, D, "Melee attacked with martial-art [src] : Elbow Drop", ATKLOG_ALL) - if(prob(80)) - A.say(pick("BANZAIII!", "KIYAAAA!", "OMAE WA MOU SHINDEIRU!", "YOU CAN'T SEE ME!", "MY TIME IS NOW!", "COWABUNGA")) - return 1 - return basic_hit(A,D) + has_explaination_verb = TRUE + combos = list(/datum/martial_combo/sleeping_carp/wrist_wrench, /datum/martial_combo/sleeping_carp/back_kick, /datum/martial_combo/sleeping_carp/stomach_knee, /datum/martial_combo/sleeping_carp/head_kick, /datum/martial_combo/sleeping_carp/elbow_drop) /datum/martial_art/the_sleeping_carp/grab_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("G",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK var/obj/item/grab/G = D.grabbedby(A,1) if(G) G.state = GRAB_AGGRESSIVE //Instant aggressive grab + return TRUE /datum/martial_art/the_sleeping_carp/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) - add_to_streak("H",D) - if(check_streak(A,D)) - return 1 + MARTIAL_ARTS_ACT_CHECK A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) var/atk_verb = pick("punches", "kicks", "chops", "hits", "slams") D.visible_message("[A] [atk_verb] [D]!", \ @@ -132,24 +27,7 @@ if(prob(D.getBruteLoss()) && !D.lying) D.visible_message("[D] stumbles and falls!", "The blow sends you to the ground!") D.Weaken(4) - return 1 - - -/datum/martial_art/the_sleeping_carp/disarm_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) - add_to_streak("D",D) - if(check_streak(A,D)) - return 1 - return ..() - -/mob/living/carbon/human/proc/sleeping_carp_help() - set name = "Recall Teachings" - set desc = "Remember the martial techniques of the Sleeping Carp clan." - set category = "Sleeping Carp" + return TRUE +/datum/martial_art/the_sleeping_carp/explaination_header(user) to_chat(usr, "You retreat inward and recall the teachings of the Sleeping Carp...") - - to_chat(usr, "Wrist Wrench: Disarm Disarm. Forces opponent to drop item in hand.") - to_chat(usr, "Back Kick: Harm Grab. Opponent must be facing away. Knocks down.") - to_chat(usr, "Stomach Knee: Grab Harm. Knocks the wind out of opponent and stuns.") - to_chat(usr, "Head Kick: Disarm Harm Harm. Decent damage, forces opponent to drop item in hand.") - to_chat(usr, "Elbow Drop: Harm Disarm Harm Disarm Harm. Opponent must be on the ground. Deals huge damage, instantly kills anyone in critical condition.") diff --git a/code/modules/martial_arts/wrestleing.dm b/code/modules/martial_arts/wrestling.dm similarity index 100% rename from code/modules/martial_arts/wrestleing.dm rename to code/modules/martial_arts/wrestling.dm diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index d495ca3d2f4..4cb846139f7 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -29,8 +29,6 @@ create_reagents(330) - martial_art = GLOB.default_martial_art - handcrafting = new() // Set up DNA. @@ -298,8 +296,8 @@ apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) /mob/living/carbon/human/bullet_act() - if(martial_art && martial_art.deflection_chance) //Some martial arts users can deflect projectiles! - if(!prob(martial_art.deflection_chance)) + if(mind && mind.martial_art && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles! + if(!prob(mind.martial_art.deflection_chance)) return ..() if(!src.lying && !(HULK in mutations)) //But only if they're not lying down, and hulks can't do it visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") @@ -1731,16 +1729,14 @@ Eyes need to have significantly high darksight to shine unless the mob has the X if(G.trigger_guard == TRIGGER_GUARD_NORMAL) if(HULK in mutations) to_chat(src, "Your meaty finger is much too large for the trigger guard!") - return 0 + return FALSE if(NOGUNS in dna.species.species_traits) to_chat(src, "Your fingers don't fit in the trigger guard!") - return 0 + return FALSE - if(martial_art && martial_art.no_guns) //great dishonor to famiry - to_chat(src, "[martial_art.no_guns_message]") - return 0 - - return . + if(mind && mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry + to_chat(src, "[mind.martial_art.no_guns_message]") + return FALSE /mob/living/carbon/human/proc/change_icobase(var/new_icobase, var/new_deform, var/owner_sensitive) for(var/obj/item/organ/external/O in bodyparts) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 958afaaef94..cf61162733e 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -214,7 +214,7 @@ emp_act return 0 /mob/living/carbon/human/proc/check_block() - if(martial_art && prob(martial_art.block_chance) && martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) + if(mind && mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) return TRUE /mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) //todo: update this to utilize check_obscured_slots() //and make sure it's check_obscured_slots(TRUE) to stop aciding through visors etc diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 3d365afa940..bc5c85e2929 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -1,4 +1,3 @@ -GLOBAL_DATUM_INIT(default_martial_art, /datum/martial_art, new()) /mob/living/carbon/human hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPMINDSHIELD_HUD,IMPCHEM_HUD,IMPTRACK_HUD,SPECIALROLE_HUD,GLAND_HUD) @@ -42,8 +41,6 @@ GLOBAL_DATUM_INIT(default_martial_art, /datum/martial_art, new()) var/datum/personal_crafting/handcrafting - var/datum/martial_art/martial_art = null - var/special_voice = "" // For changing our voice. Used by a symptom. var/hand_blood_color diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index a1be84f8163..c9cd99ad2d0 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -696,13 +696,14 @@ if(alcohol_strength >= slur_start) //slurring Slur(drunk) - if(alcohol_strength >= brawl_start) //the drunken martial art - if(!istype(martial_art, /datum/martial_art/drunk_brawling)) - var/datum/martial_art/drunk_brawling/F = new - F.teach(src, 1) - if(alcohol_strength < brawl_start) //removing the art - if(istype(martial_art, /datum/martial_art/drunk_brawling)) - martial_art.remove(src) + if(mind) + if(alcohol_strength >= brawl_start) //the drunken martial art + if(!istype(mind.martial_art, /datum/martial_art/drunk_brawling)) + var/datum/martial_art/drunk_brawling/F = new + F.teach(src, TRUE) + else if(alcohol_strength < brawl_start) //removing the art + if(istype(mind.martial_art, /datum/martial_art/drunk_brawling)) + mind.martial_art.remove(src) if(alcohol_strength >= confused_start && prob(33)) //confused walking if(!confused) Confused(1) diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm index a8e1eee5f52..1ba796c0435 100644 --- a/code/modules/mob/living/carbon/human/species/_species.dm +++ b/code/modules/mob/living/carbon/human/species/_species.dm @@ -516,7 +516,7 @@ playsound(target.loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1) target.visible_message("[user] attempted to disarm [target]!") -/datum/species/proc/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style = M.martial_art) //Handles any species-specific attackhand events. +/datum/species/proc/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) //Handles any species-specific attackhand events. if(!istype(M)) return @@ -528,6 +528,9 @@ to_chat(M, "You can't use your hand.") return + if(M.mind) + attacker_style = M.mind.martial_art + if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK)) add_attack_logs(M, H, "Melee attacked with fists (miss/block)") H.visible_message("[M] attempted to touch [H]!") diff --git a/code/modules/mob/living/carbon/superheroes.dm b/code/modules/mob/living/carbon/superheroes.dm index 74cf0bd37a7..32a562b212c 100644 --- a/code/modules/mob/living/carbon/superheroes.dm +++ b/code/modules/mob/living/carbon/superheroes.dm @@ -10,7 +10,7 @@ var/list/default_genes = list(REGEN, BREATHLESS, COLDRES) var/list/default_spells = list() var/activated = FALSE //for wishgranters to not give an option if someone already has it. - + /datum/superheroes/proc/create(var/mob/living/carbon/human/H) assign_genes(H) assign_spells(H) @@ -84,7 +84,7 @@ /datum/superheroes/griffin name = "The Griffin" - default_spells = list(/obj/effect/proc_holder/spell/targeted/recruit) + default_spells = list(/obj/effect/proc_holder/spell/targeted/click/recruit) class = "Supervillain" desc = "You are The Griffin, the ultimate supervillain. You thrive on chaos and have no respect for the supposed authority \ of the command staff of this station. Along with your gang of dim-witted yet trusty henchmen, you will be able to execute \ @@ -145,100 +145,102 @@ //The Griffin's special recruit abilitiy -/obj/effect/proc_holder/spell/targeted/recruit +/obj/effect/proc_holder/spell/targeted/click/recruit name = "Recruit Greyshirt" desc = "Allows you to recruit a conscious, non-braindead, non-catatonic human to be part of the Greyshirts, your personal henchmen. This works on Civilians only and you can recruit a maximum of 3!." charge_max = 450 - clothes_req = 0 + clothes_req = FALSE range = 1 //Adjacent to user action_icon_state = "spell_greytide" var/recruiting = 0 -/obj/effect/proc_holder/spell/targeted/recruit/cast(list/targets,mob/living/user = usr) - for(var/mob/living/carbon/human/target in targets) - var/obj/item/organ/external/head/head_organ = target.get_organ("head") - if(SSticker.mode.greyshirts.len >= 3) + click_radius = -1 + selection_activated_message = "You start preparing a mindblowing monologue. Left-click to cast at a target!" + selection_deactivated_message = "You decide to save your brilliance for another day." + allowed_type = /mob/living/carbon/human + +/obj/effect/proc_holder/spell/targeted/click/recruit/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE) + if(SSticker.mode.greyshirts.len >= 3) + if(show_message) to_chat(user, "You have already recruited the maximum number of henchmen.") - if(!in_range(user, target)) - to_chat(user, "You need to be closer to enthrall [target].") - charge_counter = charge_max - return - if(!target.ckey) - to_chat(user, "The target has no mind.") - charge_counter = charge_max - return - if(target.stat) - to_chat(user, "The target must be conscious.") - charge_counter = charge_max - return - if(!ishuman(target)) - to_chat(user, "You can only recruit humans.") - charge_counter = charge_max - return - if(target.mind.assigned_role != "Civilian") - to_chat(user, "You can only recruit Civilians.") - return - if(recruiting) + return FALSE + if(recruiting) + if(show_message) to_chat(user, "You are already recruiting!") - charge_counter = charge_max + return FALSE + return ..() + +/obj/effect/proc_holder/spell/targeted/click/recruit/valid_target(mob/living/carbon/human/target, user) + if(!..()) + return FALSE + + return target.ckey && !target.stat + +/obj/effect/proc_holder/spell/targeted/click/recruit/cast(list/targets,mob/living/user = usr) + var/mob/living/carbon/human/target = targets[1] + if(target.mind.assigned_role != "Civilian") + to_chat(user, "You can only recruit Civilians.") + revert_cast(user) + return + recruiting = TRUE + to_chat(user, "This target is valid. You begin the recruiting process.") + to_chat(target, "[user] focuses in concentration. Your head begins to ache.") + + for(var/progress = 0, progress <= 3, progress++) + switch(progress) + if(1) + to_chat(user, "You begin by introducing yourself and explaining what you're about.") + user.visible_message("[user] introduces [user.p_them()]self and explains [user.p_their()] plans.") + if(2) + to_chat(user, "You begin the recruitment of [target].") + user.visible_message("[user] leans over towards [target], whispering excitedly as [user.p_they()] give[user.p_s()] a speech.") + to_chat(target, "You feel yourself agreeing with [user], and a surge of loyalty begins building.") + target.Weaken(12) + sleep(20) + if(ismindshielded(target)) + to_chat(user, "[target.p_they(TRUE)] are enslaved by Nanotrasen. You feel [target.p_their()] interest in your cause wane and disappear.") + user.visible_message("[user] stops talking for a moment, then moves back away from [target].") + to_chat(target, "Your mindshield implant activates, protecting you from conversion.") + return + if(3) + to_chat(user, "You begin filling out the application form with [target].") + user.visible_message("[user] pulls out a pen and paper and begins filling an application form with [target].") + to_chat(target, "You are being convinced by [user] to fill out an application form to become a henchman.")//Ow the edge + + if(!do_mob(user, target, 100)) //around 30 seconds total for enthralling, 45 for someone with a mindshield implant + to_chat(user, "The enrollment process has been interrupted - you have lost the attention of [target].") + to_chat(target, "You move away and are no longer under the charm of [user]. The application form is null and void.") + recruiting = FALSE return - recruiting = 1 - to_chat(user, "This target is valid. You begin the recruiting process.") - to_chat(target, "[user] focuses in concentration. Your head begins to ache.") - for(var/progress = 0, progress <= 3, progress++) - switch(progress) - if(1) - to_chat(user, "You begin by introducing yourself and explaining what you're about.") - user.visible_message("[user] introduces [user.p_them()]self and explains [user.p_their()] plans.") - if(2) - to_chat(user, "You begin the recruitment of [target].") - user.visible_message("[user] leans over towards [target], whispering excitedly as [user.p_they()] give[user.p_s()] a speech.") - to_chat(target, "You feel yourself agreeing with [user], and a surge of loyalty begins building.") - target.Weaken(12) - sleep(20) - if(ismindshielded(target)) - to_chat(user, "[target.p_they(TRUE)] are enslaved by Nanotrasen. You feel [target.p_their()] interest in your cause wane and disappear.") - user.visible_message("[user] stops talking for a moment, then moves back away from [target].") - to_chat(target, "Your mindshield implant activates, protecting you from conversion.") - return - if(3) - to_chat(user, "You begin filling out the application form with [target].") - user.visible_message("[user] pulls out a pen and paper and begins filling an application form with [target].") - to_chat(target, "You are being convinced by [user] to fill out an application form to become a henchman.")//Ow the edge - - if(!do_mob(user, target, 100)) //around 30 seconds total for enthralling, 45 for someone with a mindshield implant - to_chat(user, "The enrollment process has been interrupted - you have lost the attention of [target].") - to_chat(target, "You move away and are no longer under the charm of [user]. The application form is null and void.") - recruiting = 0 - return - - recruiting = 0 - to_chat(user, "You have recruited [target] as your henchman!") - to_chat(target, "You have decided to enroll as a henchman for [user]. You are now part of the feared 'Greyshirts'.") - to_chat(target, "You must follow the orders of [user], and help [user.p_them()] succeed in [user.p_their()] dastardly schemes.") - to_chat(target, "You may not harm other Greyshirt or [user]. However, you do not need to obey other Greyshirts.") - SSticker.mode.greyshirts += target.mind - target.set_species(/datum/species/human) + recruiting = FALSE + to_chat(user, "You have recruited [target] as your henchman!") + to_chat(target, "You have decided to enroll as a henchman for [user]. You are now part of the feared 'Greyshirts'.") + to_chat(target, "You must follow the orders of [user], and help [user.p_them()] succeed in [user.p_their()] dastardly schemes.") + to_chat(target, "You may not harm other Greyshirt or [user]. However, you do not need to obey other Greyshirts.") + SSticker.mode.greyshirts += target.mind + target.set_species(/datum/species/human) + var/obj/item/organ/external/head/head_organ = target.get_organ("head") + if(head_organ) head_organ.h_style = "Bald" head_organ.f_style = "Shaved" - target.s_tone = 35 - // No `update_dna=0` here because the character is being over-written - target.change_eye_color(1,1,1) - for(var/obj/item/W in target.get_all_slots()) - target.unEquip(W) - target.rename_character(target.real_name, "Generic Henchman ([rand(1, 1000)])") - target.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey/greytide(target), slot_w_uniform) - target.equip_to_slot_or_del(new /obj/item/clothing/shoes/black/greytide(target), slot_shoes) - target.equip_to_slot_or_del(new /obj/item/storage/toolbox/mechanical/greytide(target), slot_l_hand) - target.equip_to_slot_or_del(new /obj/item/radio/headset(target), slot_l_ear) - var/obj/item/card/id/syndicate/W = new(target) - W.icon_state = "lifetimeid" - W.access = list(ACCESS_MAINT_TUNNELS) - W.assignment = "Greyshirt" - W.rank = "Greyshirt" - W.flags |= NODROP - W.SetOwnerInfo(target) - W.UpdateName() - target.equip_to_slot_or_del(W, slot_wear_id) - target.regenerate_icons() + target.s_tone = 35 + // No `update_dna=0` here because the character is being over-written + target.change_eye_color(1,1,1) + for(var/obj/item/W in target.get_all_slots()) + target.unEquip(W) + target.rename_character(target.real_name, "Generic Henchman ([rand(1, 1000)])") + target.equip_to_slot_or_del(new /obj/item/clothing/under/color/grey/greytide(target), slot_w_uniform) + target.equip_to_slot_or_del(new /obj/item/clothing/shoes/black/greytide(target), slot_shoes) + target.equip_to_slot_or_del(new /obj/item/storage/toolbox/mechanical/greytide(target), slot_l_hand) + target.equip_to_slot_or_del(new /obj/item/radio/headset(target), slot_l_ear) + var/obj/item/card/id/syndicate/W = new(target) + W.icon_state = "lifetimeid" + W.access = list(ACCESS_MAINT_TUNNELS) + W.assignment = "Greyshirt" + W.rank = "Greyshirt" + W.flags |= NODROP + W.SetOwnerInfo(target) + W.UpdateName() + target.equip_to_slot_or_del(W, slot_wear_id) + target.regenerate_icons() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 2f97001d3ae..dd0316f4177 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -122,8 +122,8 @@ Difficulty: Very Hard /mob/living/simple_animal/hostile/megafauna/colossus/proc/enrage(mob/living/L) if(ishuman(L)) var/mob/living/carbon/human/H = L - if(H.martial_art && prob(H.martial_art.deflection_chance)) - . = TRUE + if(H.mind && H.mind.martial_art && prob(H.mind.martial_art.deflection_chance)) + return TRUE /mob/living/simple_animal/hostile/megafauna/colossus/proc/alternating_dir_shots() ranged_cooldown = world.time + 40 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm index 2b15e584922..218e01af6a2 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm @@ -42,27 +42,6 @@ var/mob/living/simple_animal/hostile/poison/terror_spider/user = owner user.DoVentSmash() -// ---------- PRINCESS ACTIONS - -/datum/action/innate/terrorspider/evolvequeen - name = "Evolve Queen" - icon_icon = 'icons/mob/terrorspider.dmi' - button_icon_state = "terror_queen" - -/datum/action/innate/terrorspider/evolvequeen/Activate() - var/mob/living/simple_animal/hostile/poison/terror_spider/princess/user = owner - if(!istype(user)) - to_chat(user, "ERROR: attempt to use evolve queen ability on a non-princess") - return - var/feedings_left = user.feedings_to_evolve - user.fed - if(feedings_left > 0) - to_chat(user, "You must wrap [feedings_left] more humanoid prey before you can do this!") - return - for(var/mob/living/simple_animal/hostile/poison/terror_spider/queen/Q in GLOB.ts_spiderlist) - if(Q.spider_awaymission == user.spider_awaymission) - to_chat(user, "The presence of another Queen in the area is preventing you from maturing.") - return - user.evolve_to_queen() // ---------- QUEEN ACTIONS @@ -93,19 +72,11 @@ var/mob/living/simple_animal/hostile/poison/terror_spider/queen/user = owner user.LayQueenEggs() -/datum/action/innate/terrorspider/queen/queenfakelings - name = "Fake Spiderlings" - icon_icon = 'icons/effects/effects.dmi' - button_icon_state = "spiderling" - -/datum/action/innate/terrorspider/queen/queenfakelings/Activate() - var/mob/living/simple_animal/hostile/poison/terror_spider/queen/user = owner - user.QueenFakeLings() // ---------- EMPRESS /datum/action/innate/terrorspider/queen/empress/empresserase - name = "Erase Brood" + name = "Empress Erase Brood" icon_icon = 'icons/effects/blood.dmi' button_icon_state = "mgibbl1" @@ -113,6 +84,16 @@ var/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/user = owner user.EraseBrood() +/datum/action/innate/terrorspider/queen/empress/empresslings + name = "Empresss Spiderlings" + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "spiderling" + +/datum/action/innate/terrorspider/queen/empress/empresslings/Activate() + var/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/user = owner + user.EmpressLings() + + // ---------- WEB /mob/living/simple_animal/hostile/poison/terror_spider/proc/Web() diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/chem.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/chem.dm index 60e11de670a..b685fdd1959 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/chem.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/chem.dm @@ -22,7 +22,7 @@ else if(volume < 90) // bitten thrice, die quickly, severe muscle cramps make movement very difficult. Even calling for help probably won't save you. // total damage: 4, human health 150 until crit, = 37.5 ticks, = 75s = 1m15s until death - update_flags |= M.adjustToxLoss(4, FALSE) // a bit worse than coiine + update_flags |= M.adjustToxLoss(4, FALSE) update_flags |= M.EyeBlurry(6, FALSE) M.Confused(6) else diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm index 137d6739466..f011becf6b8 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress.dm @@ -19,7 +19,6 @@ ventcrawler = 1 idle_ventcrawl_chance = 0 ai_playercontrol_allowtype = 0 - rapid = 3 canlay = 1000 spider_tier = TS_TIER_5 projectiletype = /obj/item/projectile/terrorqueenspit/empress @@ -35,6 +34,8 @@ /mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/New() ..() + empresslings_action = new() + empresslings_action.Grant(src) empresserase_action = new() empresserase_action.Grant(src) @@ -44,7 +45,6 @@ /mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/NestMode() ..() queeneggs_action.button.name = "Empress Eggs" - queenfakelings_action.button.name = "Empress Lings" /mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/LayQueenEggs() var/eggtype = input("What kind of eggs?") as null|anything in list(TS_DESC_QUEEN, TS_DESC_MOTHER, TS_DESC_PRINCE, TS_DESC_PRINCESS, TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN, TS_DESC_BLACK, TS_DESC_PURPLE, TS_DESC_WHITE, TS_DESC_BROWN) @@ -70,7 +70,7 @@ if(TS_DESC_PRINCE) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/prince, numlings) if(TS_DESC_PRINCESS) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/princess, numlings) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess, numlings) if(TS_DESC_MOTHER) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/mother, numlings) if(TS_DESC_QUEEN) @@ -78,7 +78,7 @@ else to_chat(src, "Unrecognized egg type.") -/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/QueenFakeLings() +/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/proc/EmpressLings() var/numlings = input("How many?") as null|anything in list(10, 20, 30, 40, 50) var/sbpc = input("%chance to be stillborn?") as null|anything in list(0, 25, 50, 75, 100) for(var/i=0, iThrough the hivemind, the raw power of [src] floods into your body, burning it from the inside out!") @@ -106,8 +107,6 @@ qdel(T) to_chat(src, "All Terror Spiders, except yourself, will die off shortly.") - /obj/item/projectile/terrorqueenspit/empress - damage_type = BURN - damage = 30 - bonus_tox = 0 + damage = 90 + diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm index 1946f1a32ee..b07f34ec368 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/green.dm @@ -39,11 +39,10 @@ to_chat(src, "You must wrap more humanoid prey before you can do this!") return var/list/eggtypes = list(TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN) - var/num_brown = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/brown) - if(num_brown < 2) + var/list/spider_array = CountSpidersDetailed(FALSE) + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/brown] < 2) eggtypes += TS_DESC_BROWN - var/num_black = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/black) - if(num_black < 2) + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/black] < 2) eggtypes += TS_DESC_BLACK var/eggtype = pick(eggtypes) if(client) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm index 245ae95c5e9..407e6f3174e 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/hive.dm @@ -4,7 +4,8 @@ /mob/living/simple_animal/hostile/poison/terror_spider/proc/DoHiveSense() var/hsline = "" to_chat(src, "Your Brood: ") - for(var/mob/living/simple_animal/hostile/poison/terror_spider/T in GLOB.ts_spiderlist) + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing if(T.spider_awaymission != spider_awaymission) continue hsline = "* [T] in [get_area(T)], " @@ -20,21 +21,55 @@ /mob/living/simple_animal/hostile/poison/terror_spider/proc/CountSpiders() var/numspiders = 0 - for(var/mob/living/simple_animal/hostile/poison/terror_spider/T in GLOB.ts_spiderlist) + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing if(T.stat != DEAD && !T.spider_placed && spider_awaymission == T.spider_awaymission) numspiders += 1 return numspiders -/mob/living/simple_animal/hostile/poison/terror_spider/proc/CountSpidersType(specific_type) - var/numspiders = 0 - for(var/mob/living/simple_animal/hostile/poison/terror_spider/T in GLOB.ts_spiderlist) - if(T.stat != DEAD && !T.spider_placed && spider_awaymission == T.spider_awaymission) - if(T.type == specific_type) - numspiders += 1 - for(var/obj/structure/spider/eggcluster/terror_eggcluster/E in GLOB.ts_egg_list) - if(E.spiderling_type == specific_type && E.z == z) - numspiders += E.spiderling_number - for(var/obj/structure/spider/spiderling/terror_spiderling/L in GLOB.ts_spiderling_list) - if(!L.stillborn && L.grow_as == specific_type && L.z == z) - numspiders += 1 - return numspiders +/mob/living/simple_animal/hostile/poison/terror_spider/proc/CountSpidersDetailed(check_mine = FALSE, list/mytypes = list()) + var/list/spider_totals = list("all" = 0) + var/check_list = length(mytypes) > 0 + for(var/thistype in mytypes) + spider_totals[thistype] = 0 + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing + if(T.stat == DEAD || T.spider_placed || spider_awaymission != T.spider_awaymission) + continue + if(check_mine && T.spider_myqueen != src) + continue + if(check_list && !(T.type in mytypes)) + continue + if(T == src) + continue + if(spider_totals[T.type]) + spider_totals[T.type]++ + else + spider_totals[T.type] = 1 + spider_totals["all"]++ + for(var/thing in GLOB.ts_egg_list) + var/obj/structure/spider/eggcluster/terror_eggcluster/E = thing + if(check_mine && E.spider_myqueen != src) + continue + if(check_list && E.spiderling_type && !(E.spiderling_type in mytypes)) + continue + if(spider_totals[E.spiderling_type]) + spider_totals[E.spiderling_type] += E.spiderling_number + else + spider_totals[E.spiderling_type] = E.spiderling_number + spider_totals["all"] += E.spiderling_number + for(var/thing in GLOB.ts_spiderling_list) + var/obj/structure/spider/spiderling/terror_spiderling/L = thing + if(L.stillborn) + continue + if(check_mine && L.spider_myqueen != src) + continue + if(check_list && L.grow_as && !(L.grow_as in mytypes)) + continue + if(spider_totals[L.grow_as]) + spider_totals[L.grow_as]++ + else + spider_totals[L.grow_as] = 1 + spider_totals["all"]++ + return spider_totals + diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm index e486872abbb..7b900d8a881 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/prince.dm @@ -29,6 +29,7 @@ spider_opens_doors = 2 web_type = /obj/structure/spider/terrorweb/purple ai_spins_webs = FALSE + gender = MALE /mob/living/simple_animal/hostile/poison/terror_spider/prince/death(gibbed) if(can_die() && !hasdied && spider_uo71) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/princess.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/princess.dm index 6f33c51b246..7a840405cd0 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/princess.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/princess.dm @@ -2,58 +2,76 @@ // -------------------------------------------------------------------------------- // ----------------- TERROR SPIDERS: T3 PRINCESS OF TERROR -------------------------- // -------------------------------------------------------------------------------- -// -------------: ROLE: cutesy -// -------------: AI: as green, but will evolve to queen when it can -// -------------: SPECIAL: can evolve into a queen, if fed enough -// -------------: TO FIGHT IT: kill it before it evolves +// -------------: ROLE: mini-queen, maintains a smaller nest, but also more expendable +// -------------: AI: maintains a small group of spiders. Small fraction of a queen's nest. +// -------------: SPECIAL: lays eggs over time, like a queen +// -------------: TO FIGHT IT: hunt it before it lays eggs // -------------: SPRITES FROM: FoS, https://www.paradisestation.org/forum/profile/335-fos -/mob/living/simple_animal/hostile/poison/terror_spider/princess +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess name = "Princess of Terror spider" desc = "An enormous spider. It looks strangely cute and fluffy." - spider_role_summary = "Future Queen" + spider_role_summary = "Mini-Queen" ai_target_method = TS_DAMAGE_SIMPLE icon_state = "terror_princess1" icon_living = "terror_princess1" icon_dead = "terror_princess1_dead" maxHealth = 150 health = 150 - regen_points_per_hp = 1 // always regens very fast - force_threshold = 18 // outright immune to anything of force under 18, same as queen - melee_damage_lower = 10 - melee_damage_upper = 20 - idle_ventcrawl_chance = 5 spider_tier = TS_TIER_3 - spider_opens_doors = 2 - web_type = /obj/structure/spider/terrorweb/queen - var/feedings_to_evolve = 3 - var/datum/action/innate/terrorspider/ventsmash/ventsmash_action - var/datum/action/innate/terrorspider/evolvequeen/evolvequeen_action -/mob/living/simple_animal/hostile/poison/terror_spider/princess/New() - ..() - ventsmash_action = new() - ventsmash_action.Grant(src) - evolvequeen_action = new() - evolvequeen_action.Grant(src) + // Unlike queens, no ranged attack. + ranged = 0 + retreat_distance = 0 + minimum_distance = 0 + projectilesound = null + projectiletype = null -/mob/living/simple_animal/hostile/poison/terror_spider/princess/proc/evolve_to_queen() - var/mob/living/simple_animal/hostile/poison/terror_spider/queen/Q = new(loc) - if(mind) - mind.transfer_to(Q) - // Calling `transfer_to()` removes our new body (the Queen's) ability to see the med hud, so we have to re-add the queen here. - var/datum/atom_hud/U = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - U.add_hud_to(Q) - qdel(src) + canlay = 0 + hasnested = TRUE + spider_spawnfrequency = 300 // 30 seconds + var/grant_prob = 25 // 25% chance every spider_spawnfrequency seconds to gain 1 egg + var/spider_max_children = 8 -/mob/living/simple_animal/hostile/poison/terror_spider/princess/DoWrap() - . = ..() - if(fed == 0) + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/grant_queen_subtype_abilities() + // Queens start in movement mode, where they can ventcrawl but not lay eggs. Then they move to NestMode() where they can wallsmash and egglay, but not ventcrawl. + // Princesses are simpler, and can always lay eggs, always vent crawl, but never smash walls. Unlike queens, they don't have a "nesting" transformation. + queeneggs_action = new() + queeneggs_action.Grant(src) + queensense_action = new() + queensense_action.Grant(src) + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/ListAvailableEggTypes() + var/list/valid_types = list(TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN) + + // Each princess can also have ONE black/purple/brown. If it dies, they can pick a new spider from the 3 advanced types to lay. + var/list/spider_array = CountSpidersDetailed(TRUE, list(/mob/living/simple_animal/hostile/poison/terror_spider/black, /mob/living/simple_animal/hostile/poison/terror_spider/purple, /mob/living/simple_animal/hostile/poison/terror_spider/brown)) + if(spider_array["all"] < 1) + valid_types |= TS_DESC_BLACK + valid_types |= TS_DESC_PURPLE + valid_types |= TS_DESC_BROWN + + return valid_types + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/grant_eggs() + spider_lastspawn = world.time + + if(!prob(grant_prob)) + return + + var/list/spider_array = CountSpidersDetailed(TRUE) + var/brood_count = spider_array["all"] + + // Color shifts depending on how much of their brood capacity they have used. + if(brood_count == 0) icon_state = "terror_princess1" icon_living = "terror_princess1" icon_dead = "terror_princess1_dead" desc = "An enormous spider. It looks strangely cute and fluffy, with soft pink fur covering most of its body." - else if(fed == 1) + else if(brood_count < (spider_max_children /2)) icon_state = "terror_princess2" icon_living = "terror_princess2" icon_dead = "terror_princess2_dead" @@ -62,13 +80,45 @@ icon_state = "terror_princess3" icon_living = "terror_princess3" icon_dead = "terror_princess3_dead" - desc = "An enormous spider. Its entire body has turned an ominous blood red color, with actual blood dripping from its jaws. It stares around, hungrily." + desc = "An enormous spider. Its entire body looks to be the color of dried blood." -/mob/living/simple_animal/hostile/poison/terror_spider/princess/spider_special_action() - if(cocoon_target) - handle_cocoon_target() - else if(fed >= feedings_to_evolve) - evolve_to_queen() - else if(world.time > (last_cocoon_object + freq_cocoon_object)) - seek_cocoon_target() + if(!isturf(loc)) + to_chat(src, "You cannot generate eggs while hiding in [loc].") + return + + if((brood_count + canlay) >= spider_max_children) + return + canlay++ + if(canlay == 1) + to_chat(src, "You have an egg available to lay.") + else + to_chat(src, "You have [canlay] eggs available to lay.") + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/NestMode() + // Princesses don't nest. However, we still need to override this in case an AI princess calls it. + return + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/spider_special_action() + // Princess AI routine. GREATLY simplified version of queen routine. + if(!stat && !ckey) + // Utilize normal queen AI for finding a nest site (neststep=0), and activating NestMode() (neststep=1) + if(neststep != 2) + return ..() + // After that, simply lay an egg once per nestfrequency, until we have the max. + if(world.time < (lastnestsetup + nestfrequency)) + return + lastnestsetup = world.time + if(ai_nest_is_full()) + return + spider_lastspawn = world.time + DoLayTerrorEggs(pick(spider_types_standard), 1) + // Yes, this means NPC princesses won't create T2 spiders. + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess/ai_nest_is_full() + var/list/spider_array = CountSpidersDetailed(TRUE) + if(spider_array["all"] >= spider_max_children) + return TRUE + return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm index 737b6eba22a..af475b1a59a 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm @@ -5,7 +5,7 @@ // -------------: ROLE: gamma-level threat to the whole station, like a blob // -------------: AI: builds a nest, lays many eggs, attempts to take over the station // -------------: SPECIAL: spins webs, breaks lights, breaks cameras, webs objects, lays eggs, commands other spiders... -// -------------: TO FIGHT IT: bring an army, and take no prisoners. Mechs and/or decloner guns are a very good idea. +// -------------: TO FIGHT IT: bring an army, and take no prisoners. Mechs are a very good idea. // -------------: SPRITES FROM: IK3I /mob/living/simple_animal/hostile/poison/terror_spider/queen @@ -43,69 +43,94 @@ var/neststep = 0 var/hasnested = FALSE var/spider_max_per_nest = 35 // above this, AI queens become stable - var/canlay = 4 // main counter for egg-laying ability! # = num uses, incremented at intervals + var/canlay = 5 // main counter for egg-laying ability! # = num uses, incremented at intervals var/eggslaid = 0 - var/spider_can_fakelings = 3 // spawns defective spiderlings that don't grow up, used to freak out crew, atmosphere var/list/spider_types_standard = list(/mob/living/simple_animal/hostile/poison/terror_spider/red, /mob/living/simple_animal/hostile/poison/terror_spider/gray, /mob/living/simple_animal/hostile/poison/terror_spider/green, /mob/living/simple_animal/hostile/poison/terror_spider/black) var/datum/action/innate/terrorspider/queen/queennest/queennest_action var/datum/action/innate/terrorspider/queen/queensense/queensense_action var/datum/action/innate/terrorspider/queen/queeneggs/queeneggs_action - var/datum/action/innate/terrorspider/queen/queenfakelings/queenfakelings_action var/datum/action/innate/terrorspider/ventsmash/ventsmash_action + /mob/living/simple_animal/hostile/poison/terror_spider/queen/New() ..() - queennest_action = new() - queennest_action.Grant(src) ventsmash_action = new() ventsmash_action.Grant(src) + grant_queen_subtype_abilities() spider_myqueen = src if(spider_awaymission) - spider_growinstantly = 1 + spider_growinstantly = TRUE spider_spawnfrequency = 150 + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/grant_queen_subtype_abilities() + queennest_action = new() + queennest_action.Grant(src) + /mob/living/simple_animal/hostile/poison/terror_spider/queen/Life(seconds, times_fired) . = ..() if(stat != DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive. - if(ckey && canlay < 12 && hasnested) // max 12 eggs worth stored at any one time, realistically that's tons. + if(ckey && hasnested) if(world.time > (spider_lastspawn + spider_spawnfrequency)) - if(eggslaid >= 20) - canlay += 3 - else if(eggslaid >= 10) - canlay += 2 - else - canlay++ - spider_lastspawn = world.time - if(canlay == 1) - to_chat(src, "You have an egg available to lay.") - else if(canlay == 12) - to_chat(src, "You have [canlay] eggs available to lay. You won't grow any more eggs until you lay some of your existing ones.") - else - to_chat(src, "You have [canlay] eggs available to lay.") + grant_eggs() + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/grant_eggs() + spider_lastspawn = world.time + canlay += getSpiderLevel() + if(canlay == 1) + to_chat(src, "You have an egg available to lay.") + else if(canlay > 1) + to_chat(src, "You have [canlay] eggs available to lay.") + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/getSpiderLevel() + return 1 + round(MinutesAlive() / 10) + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/MinutesAlive() + return round((world.time - spider_creation_time) / 600) + /mob/living/simple_animal/hostile/poison/terror_spider/queen/death(gibbed) if(can_die() && !hasdied) if(spider_uo71) UnlockBlastDoors("UO71_Caves") - // When a queen dies, so do her player-controlled purple-type guardians. Intended as a motivator for purples to ensure they guard her. - for(var/mob/living/simple_animal/hostile/poison/terror_spider/purple/P in GLOB.ts_spiderlist) - if(ckey) - P.visible_message("\The [src] writhes in pain!") - to_chat(P,"\The [src] has died. Without her hivemind link, purple terrors like yourself cannot survive more than a few minutes!") - P.degenerate = 1 + // When a queen (or subtype!) dies, so do all of her spiderlings, and half of all her fully grown offspring + // This feature is intended to provide a way for crew to still win even if the queen has overwhelming numbers - by sniping the queen. + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing + if(!T.spider_myqueen) + continue + if(T.spider_myqueen != src) + continue + if(prob(50) || T.spider_tier >= spider_tier) + to_chat(T, "\The psychic backlash from the death of [src] crashes into your mind! Somehow... you find a way to keep going!") + continue + T.visible_message("[T] writhes in pain!") + to_chat(T, "\The psychic backlash from the death of [src] overwhelms you! You feel the life start to drain out of you...") + T.degenerate = TRUE + for(var/thing in GLOB.ts_spiderling_list) + var/obj/structure/spider/spiderling/terror_spiderling/T = thing + if(T.spider_myqueen && T.spider_myqueen == src) + qdel(T) return ..() + /mob/living/simple_animal/hostile/poison/terror_spider/queen/Retaliate() ..() - for(var/mob/living/simple_animal/hostile/poison/terror_spider/T in GLOB.ts_spiderlist) + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing T.enemies |= enemies + /mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/ai_nest_is_full() var/numspiders = CountSpiders() if(numspiders >= spider_max_per_nest) return TRUE return FALSE + /mob/living/simple_animal/hostile/poison/terror_spider/queen/spider_special_action() if(!stat && !ckey) switch(neststep) @@ -152,11 +177,13 @@ neststep = 2 NestMode() if(2) - // Create initial four purple nest guards. + // Create initial T2 spiders. if(world.time > (lastnestsetup + nestfrequency)) lastnestsetup = world.time spider_lastspawn = world.time - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/purple, 4) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/purple, 2) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/white, 2) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/brown, 2) neststep = 3 if(3) // Create spiders (random types) until nest is full. @@ -183,28 +210,26 @@ neststep = 4 else spider_lastspawn = world.time - var/num_purple = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/purple) - var/num_white = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/white) - var/num_brown = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/brown) - if(num_purple < 4) + var/list/spider_array = CountSpidersDetailed(FALSE) + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/purple] < 4) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/purple, 2) - else if(num_white < 2) + else if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/white] < 2) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/white, 2) - else if(num_brown < 4) + else if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/brown] < 4) DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/brown, 4) else DoLayTerrorEggs(pick(spider_types_standard), 5) + /mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/NestPrompt() var/confirm = alert(src, "Are you sure you want to nest? You will be able to lay eggs, and smash walls, but not ventcrawl.","Nest?","Yes","No") if(confirm == "Yes") NestMode() + /mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/NestMode() queeneggs_action = new() queeneggs_action.Grant(src) - queenfakelings_action = new() - queenfakelings_action.Grant(src) queensense_action = new() queensense_action.Grant(src) queennest_action.Remove(src) @@ -213,21 +238,8 @@ ai_ventcrawls = FALSE environment_smash = ENVIRONMENT_SMASH_RWALLS DoQueenScreech(8, 100, 8, 100) - MassFlicker() to_chat(src, "You have matured to your egglaying stage. You can now smash through walls, and lay eggs, but can no longer ventcrawl.") -/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/MassFlicker() - var/list/target_lights = list() - for(var/mob/living/carbon/human/H in GLOB.player_list) - if(H.z != z) - continue - if(H.stat == DEAD) - continue - for(var/obj/machinery/light/L in orange(7, H)) - if(L.on && prob(25)) - target_lights += L - for(var/obj/machinery/light/I in target_lights) - I.flicker() /mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/LayQueenEggs() if(stat == DEAD) @@ -242,42 +254,21 @@ else to_chat(src, "Too soon to attempt that again. Wait just a few more seconds...") return - var/list/eggtypes = list(TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN, TS_DESC_BLACK, TS_DESC_PURPLE) - if(canlay >= 4) - eggtypes |= TS_DESC_BROWN - if(canlay >= 12) - eggtypes |= TS_DESC_MOTHER - eggtypes |= TS_DESC_PRINCE - var/num_purples = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/purple) - if(num_purples >= 2) - eggtypes -= TS_DESC_PURPLE - var/num_blacks = CountSpidersType(/mob/living/simple_animal/hostile/poison/terror_spider/black) - if(num_blacks >= 2) - eggtypes -= TS_DESC_BLACK + var/list/eggtypes = ListAvailableEggTypes() + var/list/eggtypes_uncapped = list(TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN) + var/eggtype = input("What kind of eggs?") as null|anything in eggtypes + if(canlay < 1) + // this was checked before input() but we have to check again to prevent them spam-clicking the popup. + to_chat(src, "Too soon to lay another egg.") + return if(!(eggtype in eggtypes)) to_chat(src, "Unrecognized egg type.") return 0 - if(eggtype == TS_DESC_MOTHER || eggtype == TS_DESC_PRINCE) - if(canlay < 12) - to_chat(src, "Insufficient strength. It takes as much effort to lay one of those as it does to lay 12 normal eggs.") - else - if(eggtype == TS_DESC_MOTHER) - canlay -= 12 - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/mother, 1) - else if(eggtype == TS_DESC_PRINCE) - canlay -= 12 - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/prince, 1) - return - else if(eggtype == TS_DESC_BROWN) - if(canlay < 4) - to_chat(src, "Insufficient strength. It takes as much effort to lay one of those as it does to lay 4 normal eggs.") - else - canlay -= 4 - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/brown, 1) - return + + // Multiple of eggtypes_uncapped can be laid at once. Other types must be laid one at a time (to prevent exploits) var/numlings = 1 - if(eggtype != TS_DESC_PURPLE) + if(eggtype in eggtypes_uncapped) if(canlay >= 5) numlings = input("How many in the batch?") as null|anything in list(1, 2, 3, 4, 5) else if(canlay >= 3) @@ -287,27 +278,55 @@ if(eggtype == null || numlings == null) to_chat(src, "Cancelled.") return + // Actually lay the eggs. if(canlay < numlings) // We have to check this again after the popups, to account for people spam-clicking the button, then doing all the popups at once. to_chat(src, "Too soon to do this again!") return canlay -= numlings eggslaid += numlings - if(eggtype == TS_DESC_RED) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/red, numlings) - else if(eggtype == TS_DESC_GRAY) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/gray, numlings) - else if(eggtype == TS_DESC_GREEN) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/green, numlings) - else if(eggtype == TS_DESC_BLACK) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/black, numlings) - else if(eggtype == TS_DESC_PURPLE) - DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/purple, numlings) - else - to_chat(src, "Unrecognized egg type.") + switch(eggtype) + if(TS_DESC_RED) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/red, numlings) + if(TS_DESC_GRAY) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/gray, numlings) + if(TS_DESC_GREEN) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/green, numlings) + if(TS_DESC_BLACK) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/black, numlings) + if(TS_DESC_PURPLE) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/purple, numlings) + if(TS_DESC_BROWN) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/brown, numlings) + if(TS_DESC_MOTHER) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/mother, numlings) + if(TS_DESC_PRINCE) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/prince, numlings) + if(TS_DESC_PRINCESS) + DoLayTerrorEggs(/mob/living/simple_animal/hostile/poison/terror_spider/queen/princess, numlings) + else + to_chat(src, "Unrecognized egg type.") + + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/ListAvailableEggTypes() + if(MinutesAlive() >= 20) + var/list/spider_array = CountSpidersDetailed(TRUE, list(/mob/living/simple_animal/hostile/poison/terror_spider/mother, /mob/living/simple_animal/hostile/poison/terror_spider/prince, /mob/living/simple_animal/hostile/poison/terror_spider/queen/princess)) + if(spider_array["all"] == 0) + return list(TS_DESC_PRINCE, TS_DESC_PRINCESS) // Mother will be added to this list.... AFTER mothers are reworked. + + var/list/valid_types = list(TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN) + var/list/spider_array = CountSpidersDetailed(FALSE, list(/mob/living/simple_animal/hostile/poison/terror_spider/brown, /mob/living/simple_animal/hostile/poison/terror_spider/purple, /mob/living/simple_animal/hostile/poison/terror_spider/black)) + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/brown] < 2) + valid_types += TS_DESC_BROWN + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/purple] < 2) + valid_types += TS_DESC_PURPLE + if(spider_array[/mob/living/simple_animal/hostile/poison/terror_spider/black] < 2) + valid_types += TS_DESC_BLACK + return valid_types + /mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/DoQueenScreech(light_range, light_chance, camera_range, camera_chance) - visible_message("\The [src] emits a bone-chilling shriek!") + visible_message("[src] emits a bone-chilling shriek!") for(var/obj/machinery/light/L in orange(light_range, src)) if(L.on && prob(light_chance)) L.break_light_tube() @@ -315,46 +334,33 @@ if(C.status && prob(camera_chance)) C.toggle_cam(src, 0) -/mob/living/simple_animal/hostile/poison/terror_spider/queen/proc/QueenFakeLings() - if(eggslaid < 10) - to_chat(src, "You must lay at least 10 eggs before doing this.") + +/mob/living/simple_animal/hostile/poison/terror_spider/queen/examine(mob/user) + . = ..() + if(!key || stat == DEAD) return - if(spider_can_fakelings) - spider_can_fakelings-- - var/numlings = 25 - for(var/i in 1 to numlings) - var/obj/structure/spider/spiderling/terror_spiderling/S = new /obj/structure/spider/spiderling/terror_spiderling(get_turf(src)) - S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/red - S.stillborn = 1 - S.spider_mymother = src - if(!spider_can_fakelings) - queenfakelings_action.Remove(src) - else - to_chat(src, "You have run out of uses of this ability.") + if(!isobserver(user) && !isterrorspider(user)) + return + . += "[p_they(TRUE)] has laid [eggslaid] egg[eggslaid != 1 ? "s" : ""]." + . += "[p_they(TRUE)] has lived for [MinutesAlive()] minutes." + /obj/item/projectile/terrorqueenspit - name = "poisonous spit" - damage = 0 + name = "acid spit" + damage = 40 icon_state = "toxin" - damage_type = TOX - var/bonus_tox = 30 + damage_type = BURN -/obj/item/projectile/terrorqueenspit/on_hit(mob/living/carbon/target, blocked = 0, hit_zone) - if(ismob(target) && blocked < 100) - var/mob/living/L = target - if(L.reagents) - if(L.can_inject(null, FALSE, "chest", FALSE)) - L.Hallucinate(400) - if(!isterrorspider(L)) - L.adjustToxLoss(bonus_tox) /obj/structure/spider/terrorweb/queen - name = "shimmering web" - desc = "This web seems to shimmer all different colors in the light." + name = "airtight web" + desc = "This multi-layered web seems to be able to resist air pressure." + + +/obj/structure/spider/terrorweb/queen/New() + . = ..() + air_update_turf(TRUE) + +/obj/structure/spider/terrorweb/queen/CanAtmosPass(turf/T) + return FALSE -/obj/structure/spider/terrorweb/queen/web_special_ability(mob/living/carbon/C) - if(istype(C)) - var/inject_target = pick("chest","head") - if(C.can_inject(null, FALSE, inject_target, FALSE)) - C.Hallucinate(400) - C.adjustToxLoss(30) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm index a889bc6d328..8961fcdddb5 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/reproduction.dm @@ -188,7 +188,7 @@ C.enemies = enemies if(spider_growinstantly) C.amount_grown = 250 - C.spider_growinstantly = 1 + C.spider_growinstantly = TRUE spawn(10) stop_automated_movement = 0 @@ -196,7 +196,7 @@ name = "terror egg cluster" desc = "A cluster of tiny spider eggs. They pulse with a strong inner life, and appear to have sharp thorns on the sides." icon_state = "eggs" - var/spider_growinstantly = 0 + var/spider_growinstantly = FALSE var/spider_myqueen = null var/spider_mymother = null var/spiderling_type = null diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ai.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ai.dm index 11e4cac3e4a..c3e0c9a0511 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ai.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_ai.dm @@ -126,7 +126,7 @@ spider_steps_taken++ CreatePath(entry_vent) step_to(src,entry_vent) - if(spider_debug > 0) + if(spider_debug) visible_message("[src] moves towards the vent [entry_vent].") else path_to_vent = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm index ab36c6ca313..6c943b26f53 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm @@ -17,6 +17,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) // Name / Description name = "terror spider" desc = "The generic parent of all other terror spider types. If you see this in-game, it is a bug." + gender = FEMALE // Icons icon = 'icons/mob/terrorspider.dmi' @@ -141,29 +142,33 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) var/mylocation = null var/chasecycles = 0 var/web_infects = 0 + var/spider_creation_time = 0 var/datum/action/innate/terrorspider/web/web_action var/web_type = /obj/structure/spider/terrorweb var/datum/action/innate/terrorspider/wrap/wrap_action - // Breathing - require some oxygen, and no toxins, but take little damage from this requirement not being met (they can hold their breath) + // Breathing - require some oxygen, and no toxins atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 1 - // Temperature - can freeze in space and cook in plasma, but it takes extreme temperatures to do this. - minbodytemp = 100 - maxbodytemp = 500 - heat_damage_per_tick = 3 + // Temperature + heat_damage_per_tick = 5 // Takes 250% normal damage from being in a hot environment ("kill it with fire!") // DEBUG OPTIONS & COMMANDS - var/spider_growinstantly = 0 // DEBUG OPTION, DO NOT ENABLE THIS ON LIVE. IT IS USED TO TEST NEST GROWTH/SETUP AI. - var/spider_debug = 0 + var/spider_growinstantly = FALSE // DEBUG OPTION, DO NOT ENABLE THIS ON LIVE. IT IS USED TO TEST NEST GROWTH/SETUP AI. + var/spider_debug = FALSE // -------------------------------------------------------------------------------- // --------------------- TERROR SPIDERS: SHARED ATTACK CODE ----------------------- // -------------------------------------------------------------------------------- +/mob/living/simple_animal/hostile/poison/terror_spider/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) + // Forces terrors to use the 'bite' graphic when attacking something. Same as code/modules/mob/living/carbon/alien/larva/larva_defense.dm#L34 + if(!no_effect && !visual_effect_icon) + visual_effect_icon = ATTACK_EFFECT_BITE + ..() + /mob/living/simple_animal/hostile/poison/terror_spider/AttackingTarget() if(isterrorspider(target)) if(target in enemies) @@ -187,14 +192,13 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) if(F.welded) to_chat(src, "The fire door is welded shut.") else - visible_message("\The [src] pries open the firedoor!") + visible_message("[src] pries open the firedoor!") F.open() else to_chat(src, "Closing fire doors does not help.") else if(istype(target, /obj/machinery/door/airlock)) var/obj/machinery/door/airlock/A = target - if(A.density) - try_open_airlock(A) + try_open_airlock(A) else if(isliving(target) && (!client || a_intent == INTENT_HARM)) var/mob/living/G = target if(issilicon(G)) @@ -221,27 +225,23 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) /mob/living/simple_animal/hostile/poison/terror_spider/examine(mob/user) . = ..() - var/list/msgs = list() - if(stat == DEAD) - msgs += "It appears to be dead.\n" - else + if(stat != DEAD) if(key) - msgs += "Its eyes regard you with a curious intelligence." + . += "[p_they(TRUE)] regards [p_their()] surroundings with a curious intelligence." if(health > (maxHealth*0.95)) - msgs += "It is in excellent health." + . += "[p_they(TRUE)] is in excellent health." else if(health > (maxHealth*0.75)) - msgs += "It has a few injuries." + . += "[p_they(TRUE)] has a few injuries." else if(health > (maxHealth*0.55)) - msgs += "It has many injuries." + . += "[p_they(TRUE)] has many injuries." else if(health > (maxHealth*0.25)) - msgs += "It is barely clinging on to life!" + . += "[p_they(TRUE)] is barely clinging on to life!" if(degenerate) - msgs += "It appears to be dying." + . += "[p_they(TRUE)] appears to be dying." else if(health < maxHealth && regen_points > regen_points_per_kill) - msgs += "It appears to be regenerating quickly." + . += "[p_they(TRUE)] appears to be regenerating quickly." if(killcount >= 1) - msgs += "It has blood dribbling from its mouth." - . += msgs.Join("
") + . += "[p_they(TRUE)] has blood dribbling from [p_their()] mouth." /mob/living/simple_animal/hostile/poison/terror_spider/New() ..() @@ -254,9 +254,10 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) if(web_type) web_action = new() web_action.Grant(src) - wrap_action = new() - wrap_action.Grant(src) - + if(regen_points_per_tick < regen_points_per_hp) + // Only grant the Wrap action button to spiders who need to use it to regenerate their health + wrap_action = new() + wrap_action.Grant(src) name += " ([rand(1, 1000)])" real_name = name msg_terrorspiders("[src] has grown in [get_area(src)].") @@ -278,6 +279,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) addtimer(CALLBACK(src, .proc/announcetoghosts), 30) var/datum/atom_hud/U = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] U.add_hud_to(src) + spider_creation_time = world.time /mob/living/simple_animal/hostile/poison/terror_spider/proc/announcetoghosts() if(spider_awaymission) @@ -285,8 +287,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) if(stat == DEAD) return if(ckey) - var/image/alert_overlay = image('icons/mob/terrorspider.dmi', icon_state) - notify_ghosts("[src] has appeared in [get_area(src)]. (already player-controlled)", source = src, alert_overlay = alert_overlay) + notify_ghosts("[src] (player controlled) has appeared in [get_area(src)].") else if(ai_playercontrol_allowtype) var/image/alert_overlay = image('icons/mob/terrorspider.dmi', icon_state) notify_ghosts("[src] has appeared in [get_area(src)].", enter_link = "(Click to control)", source = src, alert_overlay = alert_overlay, action = NOTIFY_ATTACK) @@ -342,7 +343,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) /mob/living/simple_animal/hostile/poison/terror_spider/ObjBump(obj/O) if(istype(O, /obj/machinery/door/airlock)) var/obj/machinery/door/airlock/L = O - if(L.density) + if(L.density) // must check density here, to avoid rapid bumping of an airlock that is in the process of opening, instantly forcing it closed return try_open_airlock(L) if(istype(O, /obj/machinery/door/firedoor)) var/obj/machinery/door/firedoor/F = O @@ -352,7 +353,8 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) . = ..() /mob/living/simple_animal/hostile/poison/terror_spider/proc/msg_terrorspiders(msgtext) - for(var/mob/living/simple_animal/hostile/poison/terror_spider/T in GLOB.ts_spiderlist) + for(var/thing in GLOB.ts_spiderlist) + var/mob/living/simple_animal/hostile/poison/terror_spider/T = thing if(T.stat != DEAD) to_chat(T, "TerrorSense: [msgtext]") @@ -365,21 +367,54 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) /mob/living/simple_animal/hostile/poison/terror_spider/proc/try_open_airlock(obj/machinery/door/airlock/D) if(D.operating) return - if(!D.density) - to_chat(src, "Closing doors does not help us.") - else if(D.welded) - to_chat(src, "The door is welded shut.") + if(D.welded) + to_chat(src, "The door is welded.") else if(D.locked) - to_chat(src, "The door is bolted shut.") + to_chat(src, "The door is bolted.") else if(D.allowed(src)) - D.open(1) - return 1 + if(D.density) + D.open(TRUE) + else + D.close(TRUE) + return TRUE else if(D.arePowerSystemsOn() && (spider_opens_doors != 2)) to_chat(src, "The door's motors resist your efforts to force it.") else if(!spider_opens_doors) to_chat(src, "Your type of spider is not strong enough to force open doors.") else - visible_message("[src] pries open the door!") + visible_message("[src] forces the door!") playsound(src.loc, "sparks", 100, 1) - D.open(1) - return 1 + if(D.density) + D.open(TRUE) + else + D.close(TRUE) + return TRUE + + +/mob/living/simple_animal/hostile/poison/terror_spider/Stat() + ..() + // Determines what shows in the "Status" tab for player-controlled spiders. Used to help players understand spider health regeneration mechanics. + // Uses because the status panel does NOT accept . + if(statpanel("Status") && ckey && stat == CONSCIOUS) + if(degenerate) + stat(null, "Hivemind Connection Severed! Dying...") // color=red + return + if(health != maxHealth) + var/hp_points_per_second = 0 + var/ltext = "FAST" + var/lcolor = "#fcba03" // orange + var/secs_per_tick = (SSmobs.wait / 10) // This uses SSmobs.wait because it must use the same frequency as mobs are processed + if(regen_points < (regen_points_per_hp * 2)) + // Slow regen speed: using regen_points as we get them. Figure out regen_points/sec, then convert that to hp/sec. + var/regen_points_per_second = (regen_points_per_tick / secs_per_tick) + hp_points_per_second = (regen_points_per_second / regen_points_per_hp) + ltext = "SLOW (HUNGRY!)" + lcolor = "#eb4034" // red + else + // Fast regen speed: healing at full 1 hp / tick rate. Just divide 1hp/tick by seconds/tick to get healing/sec. + hp_points_per_second = 1 / secs_per_tick + if(hp_points_per_second > 0) + var/pc_of_max_per_second = round(((hp_points_per_second / maxHealth) * 100), 0.1) + stat(null, "Regeneration: [ltext]: [num2text(pc_of_max_per_second)]% of health per second") + + diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 848606be3ec..4e4bd99e54d 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -478,6 +478,41 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \ return 0 //Unsupported slot //END HUMAN +/mob/proc/get_visible_mobs() + var/list/seen_mobs = list() + for(var/mob/M in view(src)) + seen_mobs += M + + return seen_mobs + +/** + * Returns an assoc list which contains the mobs in range and their "visible" name. + * Mobs out of view but in range will be listed as unknown. Else they will have their visible name +*/ +/mob/proc/get_telepathic_targets() + var/list/validtargets = new /list() + var/turf/T = get_turf(src) + var/list/mobs_in_view = get_visible_mobs() + + for(var/mob/living/M in range(14, T)) + if(M && M.mind) + if(M == src) + continue + var/mob_name + if(M in mobs_in_view) + mob_name = M.name + else + mob_name = "Unknown entity" + var/i = 0 + var/result_name + do + result_name = mob_name + if(i++) + result_name += " ([i])" // Avoid dupes + while(validtargets[result_name]) + validtargets[result_name] = M + return validtargets + // If you're looking for `reset_perspective`, that's a synonym for this proc. /mob/proc/reset_perspective(atom/A) if(client) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index ae8cbd2d7ca..6acfb92a515 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -675,4 +675,3 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well return TRUE -#define isterrorspider(A) (istype((A), /mob/living/simple_animal/hostile/poison/terror_spider)) diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm index b1fe71db72e..d94e69133a7 100644 --- a/code/modules/paperwork/contract.dm +++ b/code/modules/paperwork/contract.dm @@ -314,7 +314,7 @@ /obj/item/paper/contract/infernal/magic/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0) if(!istype(user) || !user.mind) return -1 - user.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null)) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null)) user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) return ..() diff --git a/code/modules/reagents/chemistry/reagents/misc.dm b/code/modules/reagents/chemistry/reagents/misc.dm index f9a6f974dcf..2b65054f21f 100644 --- a/code/modules/reagents/chemistry/reagents/misc.dm +++ b/code/modules/reagents/chemistry/reagents/misc.dm @@ -603,7 +603,7 @@ name = "Left 4 Zed" id = "left4zednutriment" description = "Unstable nutriment that makes plants mutate more often than usual." - color = "#1A1E4D" // RBG: 26, 30, 77 + color = "#2A1680" // RBG: 42, 128, 22 tox_prob = 25 taste_description = "evolution" diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm index b015b75be2a..914fabbc752 100644 --- a/code/modules/reagents/chemistry/reagents/toxins.dm +++ b/code/modules/reagents/chemistry/reagents/toxins.dm @@ -1054,7 +1054,7 @@ id = "atrazine" description = "A herbicidal compound used for destroying unwanted plants." reagent_state = LIQUID - color = "#17002D" + color = "#773E73" //RGB: 47 24 45 lethality = 2 //Atrazine, however, is definitely toxic diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm index 1525ed42bd3..5da09309057 100644 --- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm +++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm @@ -146,7 +146,8 @@ /obj/item/reagent_containers/food/snacks/meat/slab, /obj/item/reagent_containers/food/snacks/grown, /obj/item/reagent_containers/food/snacks/grown/mushroom, - /obj/item/reagent_containers/food/snacks/deepfryholder + /obj/item/reagent_containers/food/snacks/deepfryholder, + /obj/item/reagent_containers/food/snacks/monstermeat ) blocked |= typesof(/obj/item/reagent_containers/food/snacks/customizable) diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 24a3e3b9e99..f5bc4a3274a 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -50,16 +50,25 @@ if(!QDELETED(src)) ..() + +/obj/item/reagent_containers/proc/add_lid() + if(has_lid) + container_type ^= REFILLABLE | DRAINABLE + update_icon() + +/obj/item/reagent_containers/proc/remove_lid() + if(has_lid) + container_type |= REFILLABLE | DRAINABLE + update_icon() + /obj/item/reagent_containers/attack_self(mob/user) if(has_lid) if(is_open_container()) to_chat(usr, "You put the lid on [src].") - container_type ^= REFILLABLE | DRAINABLE + add_lid() else to_chat(usr, "You take the lid off [src].") - container_type |= REFILLABLE | DRAINABLE - update_icon() - return + remove_lid() /obj/item/reagent_containers/attack(mob/M, mob/user, def_zone) if(user.a_intent == INTENT_HARM) diff --git a/code/modules/research/designs/biogenerator_designs.dm b/code/modules/research/designs/biogenerator_designs.dm index b70771cb8d3..f01fc5303a9 100644 --- a/code/modules/research/designs/biogenerator_designs.dm +++ b/code/modules/research/designs/biogenerator_designs.dm @@ -88,7 +88,7 @@ id = "weed_killer" build_type = BIOGENERATOR materials = list(MAT_BIOMASS = 50) - build_path = /obj/item/reagent_containers/glass/bottle/killer/weedkiller + build_path = /obj/item/reagent_containers/glass/bottle/nutrient/killer/weedkiller category = list("initial","Botany Chemicals") /datum/design/pest_spray @@ -96,12 +96,12 @@ id = "pest_spray" build_type = BIOGENERATOR materials = list(MAT_BIOMASS = 50) - build_path = /obj/item/reagent_containers/glass/bottle/killer/pestkiller + build_path = /obj/item/reagent_containers/glass/bottle/nutrient/killer/pestkiller category = list("initial","Botany Chemicals") /datum/design/botany_bottle - name = "Empty Bottle" - id = "botany_bottle" + name = "Empty Jug" + id = "botany_jug" build_type = BIOGENERATOR materials = list(MAT_BIOMASS = 5) build_path = /obj/item/reagent_containers/glass/bottle/nutrient/empty diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index b211d903e8a..8e007f60f89 100644 --- a/code/modules/surgery/organs/augments_internal.dm +++ b/code/modules/surgery/organs/augments_internal.dm @@ -128,6 +128,14 @@ origin_tech = "materials=5;programming=4;biotech=5" var/stun_max_amount = 2 +/obj/item/organ/internal/cyberimp/brain/anti_stun/hardened + name = "Hardened CNS Rebooter implant" + emp_proof = TRUE + +/obj/item/organ/internal/cyberimp/brain/anti_stun/hardened/Initialize(mapload) + . = ..() + desc += " The implant has been hardened. It is invulnerable to EMPs." + /obj/item/organ/internal/cyberimp/brain/anti_stun/on_life() ..() if(crit_fail) @@ -138,6 +146,7 @@ owner.SetWeakened(stun_max_amount) /obj/item/organ/internal/cyberimp/brain/anti_stun/emp_act(severity) + ..() if(crit_fail || emp_proof) return crit_fail = TRUE diff --git a/code/modules/surgery/organs/parasites.dm b/code/modules/surgery/organs/parasites.dm index b61a7c429bc..b9afa20b7d2 100644 --- a/code/modules/surgery/organs/parasites.dm +++ b/code/modules/surgery/organs/parasites.dm @@ -54,8 +54,6 @@ var/eggs_hatched = 0 // num of hatch events completed var/awaymission_checked = FALSE var/awaymission_infection = FALSE // TRUE if infection occurred inside gateway - var/list/types_basic = list(/mob/living/simple_animal/hostile/poison/terror_spider/red, /mob/living/simple_animal/hostile/poison/terror_spider/gray) - var/list/types_adv = list(/mob/living/simple_animal/hostile/poison/terror_spider/red, /mob/living/simple_animal/hostile/poison/terror_spider/gray, /mob/living/simple_animal/hostile/poison/terror_spider/green) /obj/item/organ/internal/body_egg/terror_eggs/on_life() @@ -104,12 +102,16 @@ var/infection_completed = FALSE var/obj/structure/spider/spiderling/terror_spiderling/S = new(get_turf(owner)) switch(eggs_hatched) - if(0) // First spiderling - S.grow_as = pick(types_basic) - if(1) // Second - S.grow_as = pick(types_adv) - if(2) // Last - S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/princess + if(0) // 1st spiderling + S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/gray + if(1) // 2nd + S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/red + if(2) // 3rd + S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/brown + if(3) // 4th + S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/green + if(4) // 5th + S.grow_as = /mob/living/simple_animal/hostile/poison/terror_spider/green infection_completed = TRUE S.immediate_ventcrawl = TRUE eggs_hatched++ diff --git a/icons/atmos/vent_scrubber.dmi b/icons/atmos/vent_scrubber.dmi index a062bc4675a..628a17e8565 100644 Binary files a/icons/atmos/vent_scrubber.dmi and b/icons/atmos/vent_scrubber.dmi differ diff --git a/icons/mob/corgi_head.dmi b/icons/mob/corgi_head.dmi index b80d695a090..7492b40833d 100644 Binary files a/icons/mob/corgi_head.dmi and b/icons/mob/corgi_head.dmi differ diff --git a/icons/mob/feet.dmi b/icons/mob/feet.dmi index 3892442c6ea..882e0a0f327 100644 Binary files a/icons/mob/feet.dmi and b/icons/mob/feet.dmi differ diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index 923dcf0200c..ddc5f3cf05c 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index d168010e92c..9fce742e0bf 100644 Binary files a/icons/mob/inhands/clothing_lefthand.dmi and b/icons/mob/inhands/clothing_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index 2fc6aa9c4c1..772f8af73e4 100644 Binary files a/icons/mob/inhands/clothing_righthand.dmi and b/icons/mob/inhands/clothing_righthand.dmi differ diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi index bcecfe6d17e..88cfce57ed9 100644 Binary files a/icons/mob/inhands/items_lefthand.dmi and b/icons/mob/inhands/items_lefthand.dmi differ diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi index e5b44069320..44934af9bfc 100644 Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ diff --git a/icons/mob/species/drask/head.dmi b/icons/mob/species/drask/head.dmi index f0c888e9f68..ca15dea1cb4 100644 Binary files a/icons/mob/species/drask/head.dmi and b/icons/mob/species/drask/head.dmi differ diff --git a/icons/mob/species/drask/shoes.dmi b/icons/mob/species/drask/shoes.dmi index e51947fdd35..53664dd3d70 100644 Binary files a/icons/mob/species/drask/shoes.dmi and b/icons/mob/species/drask/shoes.dmi differ diff --git a/icons/mob/species/kidan/head.dmi b/icons/mob/species/kidan/head.dmi new file mode 100644 index 00000000000..83818995f69 Binary files /dev/null and b/icons/mob/species/kidan/head.dmi differ diff --git a/icons/mob/species/skrell/head.dmi b/icons/mob/species/skrell/head.dmi index ec5e8dd62e4..5ffe836ffc2 100644 Binary files a/icons/mob/species/skrell/head.dmi and b/icons/mob/species/skrell/head.dmi differ diff --git a/icons/mob/species/vox/shoes.dmi b/icons/mob/species/vox/shoes.dmi index 99eac81e3a9..f04249274b3 100644 Binary files a/icons/mob/species/vox/shoes.dmi and b/icons/mob/species/vox/shoes.dmi differ diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi index f007687d568..05d04330c38 100644 Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ diff --git a/icons/obj/clothing/shoes.dmi b/icons/obj/clothing/shoes.dmi index aae2d9df6fb..f20ca2501a8 100644 Binary files a/icons/obj/clothing/shoes.dmi and b/icons/obj/clothing/shoes.dmi differ diff --git a/icons/obj/reagentfillings.dmi b/icons/obj/reagentfillings.dmi index 51e9b66ee30..c36fbf01876 100644 Binary files a/icons/obj/reagentfillings.dmi and b/icons/obj/reagentfillings.dmi differ diff --git a/nano/templates/aicard.tmpl b/nano/templates/aicard.tmpl deleted file mode 100644 index 8ebe6cff319..00000000000 --- a/nano/templates/aicard.tmpl +++ /dev/null @@ -1,98 +0,0 @@ - - - - - -{{if data.has_ai}} -
-
- Hardware Integrity: -
-
- {{:data.hardware_integrity}}% -
-
- - {{if data.has_laws}} - - - -
- Laws: -
- {{for data.laws}} - - {{/for}} -
IndexLaw
{{:value.index}}.{{:value.law}}
- {{else}} - No laws found. - {{/if}} - - {{if data.operational}} - - - - - - - - - - - {{if data.flushing}} - - {{else}} - - - - - {{/if}} -
Radio Subspace Transceiver{{:helper.link("Enabled", null, {'radio' : 0}, data.radio ? 'selected' : null)}}{{:helper.link("Disabled", null, {'radio' : 1}, data.radio ? null : 'redButton' )}}
Wireless Interface{{:helper.link("Enabled", null, {'wireless' : 0}, data.wireless ? 'selected' : null)}}{{:helper.link("Disabled", null, {'wireless' : 1}, data.wireless ? null : 'redButton' )}}
AI wipe in progress...
Wipe AI{{:helper.link("Wipe", 'exclamation-circle', {'wipe' : 1}, null, 'redButton')}}
- {{/if}} -{{else}} - Stored AI: No AI detected. -{{/if}} diff --git a/nano/templates/atmos_mixer.tmpl b/nano/templates/atmos_mixer.tmpl deleted file mode 100644 index 3bae7f993bd..00000000000 --- a/nano/templates/atmos_mixer.tmpl +++ /dev/null @@ -1,32 +0,0 @@ -
-
Power:
-
{{:helper.link(data.on ? 'On' : 'Off', data.on ? 'power-off' : 'times', {'power' : 1}, null, data.on ? 'selected' : null)}}
-
-
-
Output Pressure:
-
- {{:helper.link('Set', 'pencil', {'pressure' : 'input'})}} - {{:helper.link('Max', 'plus', {'pressure' : 'max'}, data.pressure == data.max_pressure ? 'disabled' : null)}} - {{:helper.smoothRound(data.pressure)}} kPa -
-
-
-
Node 1:
-
- {{:helper.link('', null, {'node1' : -0.1}, data.node1_concentration == 0 ? 'disabled' : null)}} - {{:helper.link('', null, {'node1' : -0.01}, data.node1_concentration == 0 ? 'disabled' : null)}} - {{:helper.link('', null, {'node1' : 0.01}, data.node1_concentration == 100 ? 'disabled' : null)}} - {{:helper.link('', null, {'node1' : 0.1}, data.node1_concentration == 100 ? 'disabled' : null)}} - {{:helper.smoothRound(data.node1_concentration)}}% -
-
-
-
Node 2:
-
- {{:helper.link('', null, {'node2' : -0.1}, data.node2_concentration == 0 ? 'disabled' : null)}} - {{:helper.link('', null, {'node2' : -0.01}, data.node2_concentration == 0 ? 'disabled' : null)}} - {{:helper.link('', null, {'node2' : 0.01}, data.node2_concentration == 100 ? 'disabled' : null)}} - {{:helper.link('', null, {'node2' : 0.1}, data.node2_concentration == 100 ? 'disabled' : null)}} - {{:helper.smoothRound(data.node2_concentration)}}% -
-
\ No newline at end of file diff --git a/paradise.dme b/paradise.dme index 5ed4f7e3bc5..7fe2674b7d8 100644 --- a/paradise.dme +++ b/paradise.dme @@ -50,6 +50,7 @@ #include "code\__DEFINES\lighting.dm" #include "code\__DEFINES\logs.dm" #include "code\__DEFINES\machines.dm" +#include "code\__DEFINES\martial_arts.dm" #include "code\__DEFINES\math.dm" #include "code\__DEFINES\MC.dm" #include "code\__DEFINES\mecha.dm" @@ -1664,7 +1665,28 @@ #include "code\modules\martial_arts\mimejutsu.dm" #include "code\modules\martial_arts\plasma_fist.dm" #include "code\modules\martial_arts\sleeping_carp.dm" -#include "code\modules\martial_arts\wrestleing.dm" +#include "code\modules\martial_arts\wrestling.dm" +#include "code\modules\martial_arts\combos\martial_combo.dm" +#include "code\modules\martial_arts\combos\adminfu\healing_palm.dm" +#include "code\modules\martial_arts\combos\cqc\consecutive.dm" +#include "code\modules\martial_arts\combos\cqc\kick.dm" +#include "code\modules\martial_arts\combos\cqc\pressure.dm" +#include "code\modules\martial_arts\combos\cqc\restrain.dm" +#include "code\modules\martial_arts\combos\cqc\slam.dm" +#include "code\modules\martial_arts\combos\krav_maga\leg_sweep.dm" +#include "code\modules\martial_arts\combos\krav_maga\lung_punch.dm" +#include "code\modules\martial_arts\combos\krav_maga\neck_chop.dm" +#include "code\modules\martial_arts\combos\mimejutsu\mimechucks.dm" +#include "code\modules\martial_arts\combos\mimejutsu\silent_palm.dm" +#include "code\modules\martial_arts\combos\mimejutsu\smokebomb.dm" +#include "code\modules\martial_arts\combos\plasma_fist\plasma_fist.dm" +#include "code\modules\martial_arts\combos\plasma_fist\throwback.dm" +#include "code\modules\martial_arts\combos\plasma_fist\tornado_sweep.dm" +#include "code\modules\martial_arts\combos\sleeping_carp\back_kick.dm" +#include "code\modules\martial_arts\combos\sleeping_carp\elbow_drop.dm" +#include "code\modules\martial_arts\combos\sleeping_carp\head_kick.dm" +#include "code\modules\martial_arts\combos\sleeping_carp\stomach_knee.dm" +#include "code\modules\martial_arts\combos\sleeping_carp\wrist_wrench.dm" #include "code\modules\mining\abandonedcrates.dm" #include "code\modules\mining\fulton.dm" #include "code\modules\mining\machine_processing.dm" diff --git a/sound/weapons/jug_empty_impact.ogg b/sound/weapons/jug_empty_impact.ogg new file mode 100644 index 00000000000..d78c1c5e554 Binary files /dev/null and b/sound/weapons/jug_empty_impact.ogg differ diff --git a/sound/weapons/jug_filled_impact.ogg b/sound/weapons/jug_filled_impact.ogg new file mode 100644 index 00000000000..05e2d364373 Binary files /dev/null and b/sound/weapons/jug_filled_impact.ogg differ diff --git a/tgui/packages/tgui/constants.js b/tgui/packages/tgui/constants.js index 94f32cd2697..024278e39b3 100644 --- a/tgui/packages/tgui/constants.js +++ b/tgui/packages/tgui/constants.js @@ -104,6 +104,11 @@ export const RADIO_CHANNELS = [ freq: 1355, color: '#57b8f0', }, + { + name: 'Medical(I)', + freq: 1485, + color: '#57b8f0', + }, { name: 'Engineering', freq: 1357, @@ -114,6 +119,11 @@ export const RADIO_CHANNELS = [ freq: 1359, color: '#dd3535', }, + { + name: 'Security(I)', + freq: 1475, + color: '#dd3535', + }, { name: 'AI Private', freq: 1343, diff --git a/tgui/packages/tgui/interfaces/AICard.js b/tgui/packages/tgui/interfaces/AICard.js new file mode 100644 index 00000000000..8ffe9906e2c --- /dev/null +++ b/tgui/packages/tgui/interfaces/AICard.js @@ -0,0 +1,94 @@ +import { useBackend } from "../backend"; +import { Button, ProgressBar, LabeledList, Box, Section } from "../components"; +import { Window } from "../layouts"; + +export const AICard = (props, context) => { + const { act, data } = useBackend(context); + if (data.has_ai === 0) { + return ( + + +
+ +

No AI detected.

+
+
+
+
+ ); + } else { + + let integrityColor = null; // Handles changing color of the integrity bar + if (data.integrity >= 75) { integrityColor = 'green'; } + else if (data.integrity >= 25) { integrityColor = 'yellow'; } + else { integrityColor = 'red'; } + + return ( + + +
+ +

{data.name}

+
+ + + + + + + + +

{data.flushing === 1 ? "Wipe of AI in progress..." : ""}

+
+
+ +
+ {!!data.has_laws && ( + + {data.laws.map((value, key) => ( + + {value} + + ))} + + ) || ( // Else, no laws. + +

No laws detected.

+
+ )} +
+ +
+ + +
+
+
+ ); + } +}; diff --git a/tgui/packages/tgui/interfaces/AtmosMixer.js b/tgui/packages/tgui/interfaces/AtmosMixer.js new file mode 100644 index 00000000000..66621ed8f78 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AtmosMixer.js @@ -0,0 +1,109 @@ +import { useBackend } from "../backend"; +import { Button, Section, NumberInput, LabeledList, Flex } from "../components"; +import { Window } from "../layouts"; + +export const AtmosMixer = (props, context) => { + const { act, data } = useBackend(context); + const { + on, + pressure, + max_pressure, + node1_concentration, + node2_concentration, + } = data; + + return ( + + +
+ + +
+
+
+ ); +}; + +const NodeControls = (props, context) => { + const { act, data } = useBackend(context); + const { + node_name, + node_ref, + } = props; + + return ( + +