Merge remote-tracking branch 'upstream/master' into tgui-portable-pump

This commit is contained in:
SteelSlayer
2020-09-18 12:35:00 -05:00
137 changed files with 2350 additions and 1964 deletions
@@ -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))
+16
View File
@@ -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 .
+1
View File
@@ -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))
+1
View File
@@ -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.).
+5 -1
View File
@@ -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
+4
View File
@@ -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."
+3 -2
View File
@@ -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,"<span class='userdanger'>Your infernal link has been severed! You are no longer a devil!</span>")
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)
+1 -1
View File
@@ -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
)
+157 -68
View File
@@ -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, "<span class='warning'>You shouldn't have this spell! Something's wrong.</span>")
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, "<span class='notice'>[name] has no charges left.</span>")
return 0
if(!ghost)
if(user.stat && !stat_allowed)
to_chat(user, "<span class='notice'>You can't cast this spell while incapacitated.</span>")
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, "<span class='notice'>Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.</span>")
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
to_chat(user, "<span class='notice'>Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.</span>")
return 0
else if(!ishuman(user))
if(clothes_req || human_req)
to_chat(user, "<span class='notice'>This spell can only be cast by humans!</span>")
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
to_chat(user, "<span class='notice'>This spell can only be cast by physical beings!</span>")
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 = "<span class='notice'>Click on a target to cast the spell.</span>"
var/selection_deactivated_message = "<span class='notice'>You choose to not cast this spell.</span>"
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, "<span class='warning'>[src] is not ready to be used yet.</span>")
/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, "<span class='warning'>Only one target found. Casting [src] on [target]!</span>")
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, "<span class='warning'>No suitable target found.</span>")
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, "<span class='warning'>You shouldn't have this spell! Something's wrong.</span>")
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, "<span class='notice'>[name] has no charges left.</span>")
return 0
if(!ghost)
if(user.stat && !stat_allowed)
if(show_message)
to_chat(user, "<span class='notice'>You can't cast this spell while incapacitated.</span>")
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, "<span class='notice'>Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.</span>")
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
if(show_message)
to_chat(user, "<span class='notice'>Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.</span>")
return 0
else
if(clothes_req || human_req)
if(show_message)
to_chat(user, "<span class='notice'>This spell can only be cast by humans!</span>")
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
if(show_message)
to_chat(user, "<span class='notice'>This spell can only be cast by physical beings!</span>")
return 0
return 1
+1 -1
View File
@@ -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)
+13 -32
View File
@@ -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 = "<span class='notice'>You prepare a blessing. Click on a target to start blessing.</span>"
selection_deactivated_message = "<span class='notice'>The crew will be blessed another time.</span>"
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
+12 -6
View File
@@ -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 = "<span class='notice'>You prepare a detailed contract. Click on a target to summon the contract in his hands.</span>"
selection_deactivated_message = "<span class='notice'>You archive the contract for later use.</span>"
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,"<span class='notice'>[C] seems to not be sentient. You are unable to summon a contract for them.</span>")
/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)
.=..()
+9 -17
View File
@@ -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 = "<span class='notice'>You start to quietly neigh an incantation. Click on or near a target to cast the spell.</span>"
selection_deactivated_message = "<span class='notice'>You stop neighing to yourself.</span>"
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, "<span class='notice'>No target found in range.</span>")
return
var/mob/living/carbon/target = targets[1]
if(!target)
return
if(!ishuman(target))
to_chat(user, "<span class='notice'>It'd be stupid to curse [target] with a horse's head!</span>")
return
if(!(target in oview(range)))//If they are not in overview after selection.
to_chat(user, "<span class='notice'>They are too far away!</span>")
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!
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+10 -14
View File
@@ -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 = "<span class='notice'>You prepare to transfer your mind. Click on a target to cast the spell.</span>"
selection_deactivated_message = "<span class='notice'>You decide that your current form is good enough.</span>"
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, "<span class='warning'>You're killing yourself! You can't concentrate enough to do this!</span>")
return
+13 -39
View File
@@ -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 = "<span class='notice'>Your prepare to cast your fireball spell! <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>You extinguish your fireball...for now.</span>"
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 = "<span class='warning'>You can no longer cast Fireball.</span>"
remove_ranged_ability(user, msg)
return
if(active)
msg = "<span class='notice'>You extinguish your fireball...for now.</span>"
remove_ranged_ability(user, msg)
else
msg = "<span class='notice'>Your prepare to cast your fireball spell! <B>Left-click to cast at a target!</B></span>"
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"
+3 -3
View File
@@ -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
+30 -29
View File
@@ -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 = "<span class='notice'>Your mind grow cold. Click on a target to cast the spell.</span>"
selection_deactivated_message = "<span class='notice'>Your mind returns to normal.</span>"
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, "<span class='notice'>No target found in range.</span>")
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, "<span class='warning'>This will only work on normal organic beings.</span>")
return
if(COLDRES in C.mutations)
C.visible_message("<span class='warning'>A cloud of fine ice crystals engulfs [C.name], but disappears almost instantly!</span>")
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("<span class='warning'>[user] sprays a cloud of fine ice crystals, engulfing [H]!</span>",
"<span class='notice'>[user] sprays a cloud of fine ice crystals over your [H.head]'s visor.</span>")
add_attack_logs(user, C, "Cryokinesis")
else
H.visible_message("<span class='warning'>[user] sprays a cloud of fine ice crystals engulfing, [H]!</span>",
"<span class='warning'>[user] sprays a cloud of fine ice crystals cover your [H.head]'s visor and make it into your air vents!.</span>")
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 = "<span class='notice'>You body becomes unstable. Click on a target to cast transform into them.</span>"
selection_deactivated_message = "<span class='notice'>Your body calms down again.</span>"
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, "<span class='warning'>You can only change your appearance to that of another human.</span>")
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("<span class='warning'>[user]'s body shifts and contorts.</span>")
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
+15 -23
View File
@@ -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, "<span class='warning'>There are no valid targets!</span>")
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("<span class='abductor'>You hear [user.real_name]'s voice: [say]</span>")
else
target.show_message("<span class='abductor'>You hear a voice that seems to echo around the room: [say]</span>")
user.show_message("<span class='abductor'>You project your mind into [target.name]: [say]</span>")
user.show_message("<span class='abductor'>You project your mind into [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"]: [say]</span>")
for(var/mob/dead/observer/G in GLOB.player_list)
G.show_message("<i>Telepathic message from <b>[user]</b> ([ghost_follow_link(user, ghost=G)]) to <b>[target]</b> ([ghost_follow_link(target, ghost=G)]): [say]</i>")
@@ -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, "<span class='warning'>There are no valid targets!</span>")
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("<span class='abductor'>You offer your mind to [target.name].</span>")
user.show_message("<span class='abductor'>You offer your mind to [(target in user.get_visible_mobs()) ? target.name : "the unknown entity"].</span>")
target.show_message("<span class='abductor'><A href='?src=[UID()];target=[target.UID()];user=[user.UID()]'>[message]</a></span>")
available_targets += target
addtimer(CALLBACK(src, .proc/removeAvailability, target), 100)
+5 -5
View File
@@ -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))
@@ -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)
@@ -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 = "<span class='notice'>Your prepare to your eyes for a stunning glare! <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your eyes relax... for now.</span>"
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("<span class='warning'><b>[user]'s eyes flash a blinding red!</b></span>")
var/distance = get_dist(H, user)
if (distance <= 1) //Melee glare
H.visible_message("<span class='danger'>[H] freezes in place, [H.p_their()] eyes glazing over...</span>", \
"<span class='userdanger'>Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...</span>")
H.Stun(10)
H.AdjustSilence(10)
else //Distant glare
var/loss = 10 - distance
var/duration = 10 - loss
if(loss <= 0)
to_chat(user, "<span class='danger'>Your glare had no effect over a such long distance!</span>")
return
H.slowed = duration
H.AdjustSilence(10)
to_chat(H, "<span class='userdanger'>A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..</span>")
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, "<span class='warning'>You may only glare at humans!</span>")
charge_counter = charge_max
return
if(!shadowling_check(user))
charge_counter = charge_max
return
if(target.stat)
to_chat(user, "<span class='warning'>[target] must be conscious!</span>")
charge_counter = charge_max
return
if(is_shadow_or_thrall(target))
to_chat(user, "<span class='danger'>You don't see why you would want to paralyze an ally.</span>")
charge_counter = charge_max
return
var/mob/living/carbon/human/M = target
user.visible_message("<span class='warning'><b>[user]'s eyes flash a blinding red!</b></span>")
var/distance = get_dist(target, user)
if (distance <= 1) //Melee glare
target.visible_message("<span class='danger'>[target] freezes in place, [target.p_their()] eyes glazing over...</span>")
to_chat(target, "<span class='userdanger'>Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...</span>")
target.Stun(10)
M.AdjustSilence(10)
else //Distant glare
var/loss = 10 - distance
var/duration = 10 - loss
if(loss <= 0)
to_chat(user, "<span class='danger'>Your glare had no effect over a such long distance!</span>")
return
target.slowed = duration
M.AdjustSilence(10)
to_chat(target, "<span class='userdanger'>A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..</span>")
sleep(duration*10)
target.Stun(loss)
target.visible_message("<span class='danger'>[target] freezes in place, [target.p_their()] eyes glazing over...</span>")
to_chat(target, "<span class='userdanger'>Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...</span>")
target.Stun(stun_time)
target.visible_message("<span class='danger'>[target] freezes in place, [target.p_their()] eyes glazing over...</span>",\
"<span class='userdanger'>Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...</span>")
/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 = "<span class='notice'>Your prepare your mind to entrall a mortal. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your mind relaxes.</span>"
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, "<span class='warning'>You need to be closer to enthrall [target].</span>")
charge_counter = charge_max
return
if(!target.key || !target.mind)
to_chat(user, "<span class='warning'>The target has no mind.</span>")
charge_counter = charge_max
return
if(target.stat)
to_chat(user, "<span class='warning'>The target must be conscious.</span>")
charge_counter = charge_max
return
if(is_shadow_or_thrall(target))
to_chat(user, "<span class='warning'>You can not enthrall allies.</span>")
charge_counter = charge_max
return
if(!ishuman(target))
to_chat(user, "<span class='warning'>You can only enthrall humans.</span>")
charge_counter = charge_max
return
if(enthralling)
to_chat(user, "<span class='warning'>You are already enthralling!</span>")
charge_counter = charge_max
return
if(!target.client)
to_chat(user, "<span class='warning'>[target]'s mind is vacant of activity.</span>")
enthralling = 1
to_chat(user, "<span class='danger'>This target is valid. You begin the enthralling.</span>")
to_chat(target, "<span class='userdanger'>[user] stares at you. You feel your head begin to pulse.</span>")
var/mob/living/carbon/human/target = targets[1]
enthralling = TRUE
to_chat(user, "<span class='danger'>This target is valid. You begin the enthralling.</span>")
to_chat(target, "<span class='userdanger'>[user] stares at you. You feel your head begin to pulse.</span>")
for(var/progress = 0, progress <= 3, progress++)
switch(progress)
if(1)
to_chat(user, "<span class='notice'>You place your hands to [target]'s head...</span>")
user.visible_message("<span class='warning'>[user] places [user.p_their()] hands onto the sides of [target]'s head!</span>")
if(2)
to_chat(user, "<span class='notice'>You begin preparing [target]'s mind as a blank slate...</span>")
user.visible_message("<span class='warning'>[user]'s palms flare a bright red against [target]'s temples!</span>")
to_chat(target, "<span class='danger'>A terrible red light floods your mind. You collapse as conscious thought is wiped away.</span>")
target.Weaken(12)
sleep(20)
if(ismindshielded(target))
to_chat(user, "<span class='notice'>They have a mindshield implant. You begin to deactivate it - this will take some time.</span>")
user.visible_message("<span class='warning'>[user] pauses, then dips [user.p_their()] head in concentration!</span>")
to_chat(target, "<span class='boldannounce'>Your mindshield implant becomes hot as it comes under attack!</span>")
sleep(100) //10 seconds - not spawn() so the enthralling takes longer
to_chat(user, "<span class='notice'>The nanobots composing the mindshield implant have been rendered inert. Now to continue.</span>")
user.visible_message("<span class='warning'>[user] relaxes again.</span>")
for(var/obj/item/implant/mindshield/L in target)
if(L && L.implanted)
qdel(L)
to_chat(target, "<span class='boldannounce'>Your mental protection implant unexpectedly falters, dims, dies.</span>")
if(3)
to_chat(user, "<span class='notice'>You begin planting the tumor that will control the new thrall...</span>")
user.visible_message("<span class='warning'>A strange energy passes from [user]'s hands into [target]'s head!</span>")
to_chat(target, "<span class='boldannounce'>You feel your memories twisting, morphing. A sense of horror dominates your mind.</span>")
if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
to_chat(user, "<span class='warning'>The enthralling has been interrupted - your target's mind returns to its previous state.</span>")
to_chat(target, "<span class='userdanger'>You wrest yourself away from [user]'s hands and compose yourself</span>")
enthralling = 0
return
for(var/progress = 0, progress <= 3, progress++)
switch(progress)
if(1)
to_chat(user, "<span class='notice'>You place your hands to [target]'s head...</span>")
user.visible_message("<span class='warning'>[user] places [user.p_their()] hands onto the sides of [target]'s head!</span>")
if(2)
to_chat(user, "<span class='notice'>You begin preparing [target]'s mind as a blank slate...</span>")
user.visible_message("<span class='warning'>[user]'s palms flare a bright red against [target]'s temples!</span>")
to_chat(target, "<span class='danger'>A terrible red light floods your mind. You collapse as conscious thought is wiped away.</span>")
target.Weaken(12)
sleep(20)
if(ismindshielded(target))
to_chat(user, "<span class='notice'>They have a mindshield implant. You begin to deactivate it - this will take some time.</span>")
user.visible_message("<span class='warning'>[user] pauses, then dips [user.p_their()] head in concentration!</span>")
to_chat(target, "<span class='boldannounce'>Your mindshield implant becomes hot as it comes under attack!</span>")
sleep(100) //10 seconds - not spawn() so the enthralling takes longer
to_chat(user, "<span class='notice'>The nanobots composing the mindshield implant have been rendered inert. Now to continue.</span>")
user.visible_message("<span class='warning'>[user] relaxes again.</span>")
for(var/obj/item/implant/mindshield/L in target)
if(L && L.implanted)
qdel(L)
to_chat(target, "<span class='boldannounce'>Your mental protection implant unexpectedly falters, dims, dies.</span>")
if(3)
to_chat(user, "<span class='notice'>You begin planting the tumor that will control the new thrall...</span>")
user.visible_message("<span class='warning'>A strange energy passes from [user]'s hands into [target]'s head!</span>")
to_chat(target, "<span class='boldannounce'>You feel your memories twisting, morphing. A sense of horror dominates your mind.</span>")
if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
to_chat(user, "<span class='warning'>The enthralling has been interrupted - your target's mind returns to its previous state.</span>")
to_chat(target, "<span class='userdanger'>You wrest yourself away from [user]'s hands and compose yourself</span>")
enthralling = FALSE
return
enthralling = 0
to_chat(user, "<span class='shadowling'>You have enthralled <b>[target]</b>!</span>")
target.visible_message("<span class='big'>[target] looks to have experienced a revelation!</span>", \
"<span class='warning'>False faces all d<b>ark not real not real not--</b></span>")
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, "<span class='shadowling'>You have enthralled <b>[target]</b>!</span>")
target.visible_message("<span class='big'>[target] looks to have experienced a revelation!</span>", \
"<span class='warning'>False faces all d<b>ark not real not real not--</b></span>")
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, "<span class='shadowling'><i>The power of your thralls has granted you the <b>Black Recuperation</b> ability. \
This will, after a short time, bring a dead thrall completely back to life with no bodily defects.</i></span>")
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, "<span class='shadowling'>You do not have the power to ascend. You require [victory_threshold] thralls, but only [thralls] living thralls are present.</span>")
@@ -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 = "<span class='notice'>You start focusing your powers on mending wounds of allies. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your mind relaxes.</span>"
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, "<span class='warning'>[thrallToRevive] is not a thrall.</span>")
charge_counter = charge_max
return
if(thrallToRevive.stat != CONSCIOUS)
to_chat(user, "<span class='warning'>[thrallToRevive] must be conscious to become empowered.</span>")
charge_counter = charge_max
return
if(isshadowlinglesser(thrallToRevive))
to_chat(user, "<span class='warning'>[thrallToRevive] is already empowered.</span>")
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, "<span class='warning'>You cannot spare this much energy. There are too many empowered thralls.</span>")
charge_counter = charge_max
return
user.visible_message("<span class='danger'>[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.</span>", \
"<span class='shadowling'>You place your hands on [thrallToRevive]'s face and begin gathering energy...</span>")
to_chat(thrallToRevive, "<span class='userdanger'>[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...</span>")
if(!do_mob(user, thrallToRevive, 80))
to_chat(user, "<span class='warning'>Your concentration snaps. The flow of energy ebbs.</span>")
charge_counter = charge_max
return
to_chat(user, "<span class='shadowling'><b><i>You release a massive surge of power into [thrallToRevive]!</b></i></span>")
user.visible_message("<span class='boldannounce'><i>Red lightning surges into [thrallToRevive]'s face!</i></span>")
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("<span class='warning'><b>[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!</span>", \
"<span class='userdanger'><i>AAAAAAAAAAAAAAAAAAAGH-</i></span>")
sleep(20)
thrallToRevive.visible_message("<span class='warning'>[thrallToRevive] slowly rises, no longer recognizable as human.</span>", \
"<span class='shadowling'><b>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.</b></span>")
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, "<span class='warning'>[thrallToRevive] is not a thrall.</span>")
charge_counter = charge_max
return
if(thrallToRevive.stat != DEAD)
to_chat(user, "<span class='warning'>[thrallToRevive] is not dead.</span>")
charge_counter = charge_max
return
user.visible_message("<span class='danger'>[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.</span>", \
"<span class='shadowling'>You crouch over the body of your thrall and begin gathering energy...</span>")
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, "<span class='warning'>Your concentration snaps. The flow of energy ebbs.</span>")
charge_counter = charge_max
return
to_chat(user, "<span class='shadowling'><b><i>You release a massive surge of power into [thrallToRevive]!</b></i></span>")
user.visible_message("<span class='boldannounce'><i>Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!</i></span>")
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("<span class='boldannounce'>[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.</span>", \
"<span class='shadowling'><b><i>You have returned. One of your masters has brought you from the darkness beyond.</b></i></span>")
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, "<span class='warning'>[thrallToRevive] is already empowered.</span>")
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, "<span class='warning'>You cannot spare this much energy. There are too many empowered thralls.</span>")
revert_cast(user)
return
user.visible_message("<span class='danger'>[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.</span>", \
"<span class='shadowling'>You place your hands on [thrallToRevive]'s face and begin gathering energy...</span>")
to_chat(thrallToRevive, "<span class='userdanger'>[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...</span>")
if(!do_mob(user, thrallToRevive, 80))
to_chat(user, "<span class='warning'>Your concentration snaps. The flow of energy ebbs.</span>")
revert_cast(user)
return
to_chat(user, "<span class='shadowling'><b><i>You release a massive surge of power into [thrallToRevive]!</b></i></span>")
user.visible_message("<span class='boldannounce'><i>Red lightning surges into [thrallToRevive]'s face!</i></span>")
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("<span class='warning'><b>[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!</span>", \
"<span class='userdanger'><i>AAAAAAAAAAAAAAAAAAAGH-</i></span>")
sleep(20)
thrallToRevive.visible_message("<span class='warning'>[thrallToRevive] slowly rises, no longer recognizable as human.</span>", \
"<span class='shadowling'><b>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.</b></span>")
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("<span class='danger'>[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.</span>", \
"<span class='shadowling'>You crouch over the body of your thrall and begin gathering energy...</span>")
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, "<span class='warning'>Your concentration snaps. The flow of energy ebbs.</span>")
revert_cast(user)
return
to_chat(user, "<span class='shadowling'><b><i>You release a massive surge of power into [thrallToRevive]!</b></i></span>")
user.visible_message("<span class='boldannounce'><i>Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!</i></span>")
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("<span class='boldannounce'>[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.</span>", \
"<span class='shadowling'><b><i>You have returned. One of your masters has brought you from the darkness beyond.</b></i></span>")
thrallToRevive.Weaken(4)
thrallToRevive.emote("gasp")
playsound(thrallToRevive, "bodyfall", 50, 1)
else
to_chat(user, "<span class='warning'>The target must be awake to empower or dead to revive.</span>")
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 = "<span class='notice'>You start gathering destructive powers to delay the shuttle. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your mind relaxes.</span>"
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, "<span class='warning'>Shuttle was already delayed.</span>")
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, "<span class='warning'>[target] must not be an ally.</span>")
charge_counter = charge_max
return
if(SSshuttle.emergency.mode != SHUTTLE_CALL)
if(show_message)
to_chat(user, "<span class='warning'>Shuttle was already delayed.</span>")
return FALSE
if(SSshuttle.emergency.mode != SHUTTLE_CALL)
if(show_message)
to_chat(user, "<span class='warning'>The shuttle must be inbound only to the station.</span>")
charge_counter = charge_max
return
var/mob/living/carbon/human/M = target
user.visible_message("<span class='warning'>[user]'s eyes flash a bright red!</span>", \
"<span class='notice'>You begin to draw [M]'s life force.</span>")
M.visible_message("<span class='warning'>[M]'s face falls slack, [M.p_their()] jaw slightly distending.</span>", \
"<span class='boldannounce'>You are suddenly transported... far, far away...</span>")
extendlimit = 1
if(!do_after(user, 150, target = M))
extendlimit = 0
to_chat(M, "<span class='warning'>You are snapped back to reality, your haze dissipating!</span>")
to_chat(user, "<span class='warning'>You have been interrupted. The draw has failed.</span>")
return
to_chat(user, "<span class='notice'>You project [M]'s life force toward the approaching shuttle, extending its arrival duration!</span>")
M.visible_message("<span class='warning'>[M]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.</span>", \
"<span class='warning'><b>...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...</span>")
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("<span class='warning'>[user]'s eyes flash a bright red!</span>", \
"<span class='notice'>You begin to draw [target]'s life force.</span>")
target.visible_message("<span class='warning'>[target]'s face falls slack, [target.p_their()] jaw slightly distending.</span>", \
"<span class='boldannounce'>You are suddenly transported... far, far away...</span>")
extendlimit = 1
if(!do_after(user, 150, target = target))
extendlimit = 0
to_chat(target, "<span class='warning'>You are snapped back to reality, your haze dissipating!</span>")
to_chat(user, "<span class='warning'>You have been interrupted. The draw has failed.</span>")
return
to_chat(user, "<span class='notice'>You project [target]'s life force toward the approaching shuttle, extending its arrival duration!</span>")
target.visible_message("<span class='warning'>[target]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.</span>", \
"<span class='warning'><b>...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...</span>")
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 = "<span class='notice'>You start thinking about gibs. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your mind relaxes.</span>"
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, "<span class='warning'>You are not in the same plane of existence. Unphase first.</span>")
@@ -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, "<span class='warning'>You are not in the same plane of existence. Unphase first.</span>")
return
click_radius = -1
selection_activated_message = "<span class='notice'>You start preparing to mindwash over a mortal mind. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>Your mind relaxes.</span>"
allowed_type = /mob/living/carbon/human
for(var/mob/living/carbon/human/target in targets)
if(is_shadow_or_thrall(target))
to_chat(user, "<span class='warning'>You cannot enthrall an ally.</span>")
charge_counter = charge_max
return
if(!target.ckey || !target.mind)
to_chat(user, "<span class='warning'>The target has no mind.</span>")
charge_counter = charge_max
return
if(target.stat)
to_chat(user, "<span class='warning'>The target must be conscious.</span>")
charge_counter = charge_max
return
if(!ishuman(target))
to_chat(user, "<span class='warning'>You can only enthrall humans.</span>")
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, "<span class='warning'>You are not in the same plane of existence. Unphase first.</span>")
return FALSE
return ..()
to_chat(user, "<span class='shadowling'>You instantly rearrange <b>[target]</b>'s memories, hyptonitizing [target.p_them()] into a thrall.</span>")
to_chat(target, "<span class='userdanger'><font size=3>An agonizing spike of pain drives into your mind, and--</font></span>")
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, "<span class='shadowling'>You instantly rearrange <b>[target]</b>'s memories, hyptonitizing [target.p_them()] into a thrall.</span>")
to_chat(target, "<span class='userdanger'><font size=3>An agonizing spike of pain drives into your mind, and--</font></span>")
SSticker.mode.add_thrall(target.mind)
target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
target.add_language("Shadowling Hivemind")
@@ -99,14 +99,14 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
to_chat(H, "<span class='shadowling'><b><i>Your powers are awoken. You may now live to your fullest extent. Remember your goal. Cooperate with your thralls and allies.</b></i></span>")
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))
+2 -2
View File
@@ -119,10 +119,10 @@
if(traitorwin)
text += "<br><font color='green'><B>The [special_role_text] was successful!</B></font>"
text += "<br><font color='green'><B>The [special_role_text] was successful!</B></font><br>"
feedback_add_details("traitor_success","SUCCESS")
else
text += "<br><font color='red'><B>The [special_role_text] has failed!</B></font>"
text += "<br><font color='red'><B>The [special_role_text] has failed!</B></font><br>"
feedback_add_details("traitor_success","FAIL")
if(length(SSticker.mode.implanted))
@@ -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))
+2 -2
View File
@@ -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, "<B>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, "<B>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, "<B>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)
+8 -8
View File
@@ -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, "<span class='notice'>You stare at the book some more, but there doesn't seem to be anything else to learn...</span>")
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, "<span class='warning'>You're suddenly somewhere else... and someone else?!</span>")
to_chat(user, "<span class='warning'>Suddenly you're staring at [src] again... where are you, who are you?!</span>")
@@ -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."
+1 -1
View File
@@ -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! <br><br> \
</i>Provides Bind Soul, Ethereal Jaunt, Fireball, Rod Form, Disable Tech, and Greater Forcewall.<i>"
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
+1 -1
View File
@@ -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
)
+1
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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))
+48 -35
View File
@@ -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, "<span class='warning'>You are already wiping this AI!</span>")
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, "<span class='warning'>Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!</span>")
to_chat(user, "<span class='notice'>You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.</span>")
if(href_list["radio"])
AI.aiRadio.disabledAi = text2num(href_list["radio"])
to_chat(AI, "<span class='warning'>Your Subspace Transceiver has been [AI.aiRadio.disabledAi ? "disabled" : "enabled"]!</span>")
to_chat(user, "<span class='notice'>You [AI.aiRadio.disabledAi ? "disable" : "enable"] the AI's Subspace Transceiver.</span>")
if("wireless")
AI.control_disabled = !AI.control_disabled
to_chat(AI, "<span class='warning'>Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!</span>")
to_chat(user, "<span class='notice'>You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.</span>")
update_icon()
if(href_list["wireless"])
AI.control_disabled = text2num(href_list["wireless"])
to_chat(AI, "<span class='warning'>Your wireless interface has been [AI.control_disabled ? "disabled" : "enabled"]!</span>")
to_chat(user, "<span class='notice'>You [AI.control_disabled ? "disable" : "enable"] the AI's wireless interface.</span>")
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
@@ -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)
@@ -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"
+31 -27
View File
@@ -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
+8
View File
@@ -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"
@@ -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, "<span class='notice'>THERE CAN ONLY BE ONE!</span>")
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)
@@ -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)"
+1 -1
View File
@@ -90,7 +90,7 @@
..()
if(climber)
climber.Weaken(2)
climber.visible_message("<span class='warning'>[climber.name] has been knocked off the table", "You've been knocked off the table", "You see [climber.name] get knocked off the table</span>")
climber.visible_message("<span class='warning'>[climber.name] has been knocked off the table", "You've been knocked off the table", "You hear [climber.name] get knocked off the table</span>")
else if(Adjacent(user) && user.pulling && user.pulling.pass_flags & PASSTABLE)
user.Move_Pulled(src)
if(user.pulling.loc == loc)
+11 -3
View File
@@ -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, "<span class='announce'>PR: [input["announce"]]</span>")
to_chat(C, "<span class='announce'>PR: [prtext]</span>")
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, "<hr><span style='color: #12A5F4'><b>Server Announcement:</b> [input["message"]]</span><hr>")
/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, "<span class='boldannounce'>Reboot will take a little longer, due to pending updates.</span>")
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]")
+6
View File
@@ -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."
+4 -1
View File
@@ -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
+46 -4
View File
@@ -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, "<span class='danger'>[msg]</span>")
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, "<span class='danger'>Your previous action was ignored because you've done too many in a second</span>")
return
//search the href for script injection
if( findtext(href,"<script",1,0) )
@@ -945,3 +980,10 @@
to_chat(usr, "<span class='notice'>UI resource files resent successfully. If you are still having issues, please try manually clearing your BYOND cache. <b>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'.</b></span>")
#undef LIMITER_SIZE
#undef CURRENT_SECOND
#undef SECOND_COUNT
#undef CURRENT_MINUTE
#undef MINUTE_COUNT
#undef ADMINSWARNED_AT
@@ -344,3 +344,10 @@
recharging_time = world.time + recharging_rate
else
to_chat(user, "<span class='warning'>Something prevents you from dashing forward!</span>")
/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"
+9
View File
@@ -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
+2 -2
View File
@@ -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
+97 -70
View File
@@ -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("<span class='suicide'>[user] is huffing the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.</span>")
@@ -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("<span class='suicide'>[user] is huffing the [src.name]! It looks like [user.p_theyre()] trying to commit suicide.</span>")
@@ -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)
+4
View File
@@ -744,6 +744,10 @@
to_chat(user, "<span class='notice'>[reagent_source] is empty.</span>")
return 1
if(reagent_source.has_lid && !reagent_source.is_drainable()) //if theres a LID then cannot transfer reagents.
to_chat(user, "<span class='warning'>You need to open [O] first!</span>")
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 = ""
+12 -68
View File
@@ -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("<span class='warning'>[A] manifests a large glowing toolbox and shoves it in [D]'s chest!</span>", \
"<spac class='userdanger'>[A] shoves a mystical toolbox in your chest!</span>")
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("<span class='warning'>[A] smacks [D] in the forehead!</span>")
//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, "<span class='notice'>You feel great!</span>")
else
to_chat(D, "<span class='notice'>You rise with a start, you're alive!!!</span>")
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, "<span class='notice'>Grab</span>: Automatic Neck Grab.")
to_chat(usr, "<span class='notice'>Disarm</span>: Stun/weaken")
to_chat(usr, "<span class='notice'>Harm</span>: Death.")
to_chat(usr, "<span class='notice'>Healing Palm:</span>:Combo:Grab,Help intent. Heals or revives a crature.")
/datum/martial_art/adminfu/explaination_header(user)
to_chat(user, "<span class='notice'>Grab</span>: Automatic Neck Grab.")
to_chat(user, "<span class='notice'>Disarm</span>: Stun/weaken")
to_chat(user, "<span class='notice'>Harm</span>: Death.")
/obj/item/adminfu_scroll
name = "frayed scroll"
@@ -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("<span class='warning'>[user] smacks [target] in the forehead!</span>")
//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, "<span class='notice'>You feel great!</span>")
else
to_chat(target, "<span class='notice'>You rise with a start, you're alive!!!</span>")
return MARTIAL_COMBO_DONE
return MARTIAL_COMBO_FAIL
@@ -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("<span class='warning'>[user] strikes [target]'s abdomen, neck and back consecutively</span>", \
"<span class='userdanger'>[user] strikes your abdomen, neck and back consecutively!</span>")
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
@@ -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("<span class='warning'>[user] kicks [target] back!</span>", \
"<span class='userdanger'>[user] kicks you back!</span>")
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("<span class='warning'>[user] kicks [target]'s head, knocking [target.p_them()] out!</span>", \
"<span class='userdanger'>[user] kicks your head, knocking you out!</span>")
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
@@ -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("<span class='warning'>[user] forces their arm on [target]'s neck!</span>")
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
@@ -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("<span class='warning'>[user] locks [target] into a restraining position!</span>", \
"<span class='userdanger'>[user] locks you into a restraining position!</span>")
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
@@ -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("<span class='warning'>[user] slams [target] into the ground!</span>", \
"<span class='userdanger'>[user] slams you into the ground!</span>")
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
@@ -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("<span class='warning'>[user] leg sweeps [target]!</span>", \
"<span class='userdanger'>[user] leg sweeps you!</span>")
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
@@ -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("<span class='warning'>[user] pounds [target] on the chest!</span>", \
"<span class='userdanger'>[user] slams your chest! You can't breathe!</span>")
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
@@ -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("<span class='warning'>[user] karate chops [target]'s neck!</span>", \
"<span class='userdanger'>[user] karate chops your neck, rendering you unable to speak for a short time!</span>")
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
@@ -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, "<span class='notice'>[name]</span>: [final_combo_text]. [explaination_text]")
@@ -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("<span class='warning'>[user] swings invisible nunchcuks at [target]..and misses?</span>")
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("<span class='danger'>[user] has hit [target] with invisible nunchucks!</span>", \
"<span class='userdanger'>[user] has hit [target] with a with invisible nunchuck!</span>")
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
@@ -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("<span class='danger'>[user] has barely touched [target] with [user.p_their()] palm!</span>", \
"<span class='userdanger'>[user] hovers [user.p_their()] palm over your face!</span>")
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
@@ -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("<span class='danger'>[user] throws an invisible smoke bomb!!</span>")
var/datum/effect_system/smoke_spread/bad/smoke = new
smoke.set_up(5, 0, target.loc)
smoke.start()
return MARTIAL_COMBO_DONE_BASIC_HIT
@@ -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("<span class='danger'>[user] has hit [target] with THE PLASMA FIST TECHNIQUE!</span>", \
"<span class='userdanger'>[user] has hit [target] with THE PLASMA FIST TECHNIQUE!</span>")
target.gib()
return MARTIAL_COMBO_DONE
@@ -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("<span class='danger'>[user] has hit [target] with Plasma Punch!</span>", \
"<span class='userdanger'>[user] has hit [target] with Plasma Punch!</span>")
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
@@ -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)
@@ -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("<span class='warning'>[user] kicks [target] in the back!</span>", \
"<span class='userdanger'>[user] kicks you in the back, making you stumble and fall!</span>")
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
@@ -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("<span class='warning'>[user] elbow drops [target]!</span>", \
"<span class='userdanger'>[user] piledrives you with [user.p_their()] elbow!</span>")
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
@@ -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("<span class='warning'>[user] kicks [target] in the head!</span>", \
"<span class='userdanger'>[user] kicks you in the jaw!</span>")
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
@@ -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("<span class='warning'>[user] knees [target] in the stomach!</span>", \
"<span class='userdanger'>[user] winds you with a knee in the stomach!</span>")
target.audible_message("<b>[target]</b> 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
@@ -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("<span class='warning'>[user] grabs [target]'s wrist and wrenches it sideways!</span>", \
"<span class='userdanger'>[user] grabs your wrist and violently wrenches it to the side!</span>")
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
+18 -158
View File
@@ -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("<span class='warning'>[A] slams [D] into the ground!</span>", \
"<span class='userdanger'>[A] slams you into the ground!</span>")
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("<span class='warning'>[A] kicks [D] back!</span>", \
"<span class='userdanger'>[A] kicks you back!</span>")
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("<span class='warning'>[A] kicks [D]'s head, knocking [D.p_them()] out!</span>", \
"<span class='userdanger'>[A] kicks your head, knocking you out!</span>")
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("<span class='warning'>[A] forces their arm on [D]'s neck!</span>")
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("<span class='warning'>[A] locks [D] into a restraining position!</span>", \
"<span class='userdanger'>[A] locks you into a restraining position!</span>")
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("<span class='warning'>[A] strikes [D]'s abdomen, neck and back consecutively</span>", \
"<span class='userdanger'>[A] strikes your abdomen, neck and back consecutively!</span>")
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("<span class='danger'>[A] puts [D] into a chokehold!</span>", \
@@ -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, "<b><i>You try to remember some of the basics of CQC.</i></b>")
/datum/martial_art/cqc/explaination_header(user)
to_chat(user, "<b><i>You try to remember some of the basics of CQC.</i></b>")
to_chat(usr, "<span class='notice'>Slam</span>: Grab, switch hands, Harm. Slam opponent into the ground, knocking them down.")
to_chat(usr, "<span class='notice'>CQC Kick</span>: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.")
to_chat(usr, "<span class='notice'>Restrain</span>: Grab, switch hands, Grab. Locks opponents into a restraining position, disarm to knock them out with a choke hold.")
to_chat(usr, "<span class='notice'>Pressure</span>: Disarm Grab. Decent stamina damage.")
to_chat(usr, "<span class='notice'>Consecutive CQC</span>: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.")
to_chat(usr, "<b><i>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.</i></b>")
/datum/martial_art/cqc/explaination_footer(user)
to_chat(user, "<b><i>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.</i></b>")
+13 -59
View File
@@ -15,7 +15,9 @@
to_chat(owner, "<b><i>Your next attack will be a Neck Chop.</i></b>")
owner.visible_message("<span class='danger'>[owner] assumes the Neck Chop stance!</span>")
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, "<b><i>Your next attack will be a Leg Sweep.</i></b>")
owner.visible_message("<span class='danger'>[owner] assumes the Leg Sweep stance!</span>")
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, "<b><i>Your next attack will be a Lung Punch.</i></b>")
owner.visible_message("<span class='danger'>[owner] assumes the Lung Punch stance!</span>")
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("<span class='warning'>[A] leg sweeps [D]!</span>", \
"<span class='userdanger'>[A] leg sweeps you!</span>")
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("<span class='warning'>[A] pounds [D] on the chest!</span>", \
"<span class='userdanger'>[A] slams your chest! You can't breathe!</span>")
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("<span class='warning'>[A] karate chops [D]'s neck!</span>", \
"<span class='userdanger'>[A] karate chops your neck, rendering you unable to speak for a short time!</span>")
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("<span class='danger'>[A] [picked_hit_type] [D]!</span>", \
"<span class='userdanger'>[A] [picked_hit_type] you!</span>")
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("<span class='danger'>[A] attempted to disarm [D]!</span>", \
"<span class='userdanger'>[A] attempted to disarm [D]!</span>")
playsound(D, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
return 1
return TRUE
//Krav Maga Gloves
+115 -33
View File
@@ -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("<span class='warning'>[A] has attempted to [atk_verb] [D]!</span>")
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, "<span class='warning'>You shouldn't have access to this verb. Report this as a bug to the github please.</span>")
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
+8 -90
View File
@@ -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("<span class='warning'>[A] swings invisible nunchcuks at [D]..and misses?</span>")
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("<span class='danger'>[A] has hit [D] with invisible nunchucks!</span>", \
"<span class='userdanger'>[A] has hit [D] with a with invisible nunchuck!</span>")
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("<span class='danger'>[A] throws an invisible smoke bomb!!</span>")
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("<span class='danger'>[A] has barely touched [D] with [A.p_their()] palm!</span>", \
"<span class='userdanger'>[A] hovers [A.p_their()] palm over your face!</span>")
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, "<b><i>You make a invisible box around yourself and recall the teachings of Mimejutsu...</i></b>")
to_chat(usr, "<span class='notice'>Mimechucks</span>: Disarm Harm. Hits the opponent with invisible nunchucks.")
to_chat(usr, "<span class='notice'>Smokebomb</span>: Disarm Disarm. Drops a mime smokebomb.")
to_chat(usr, "<span class='notice'>Silent Palm</span>: Grab Disarm. Using mime energy throw someone back.")
/datum/martial_art/mimejutsu/explaination_header(user)
to_chat(user, "<b><i>You make a invisible box around yourself and recall the teachings of Mimejutsu...</i></b>")
+10 -74
View File
@@ -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("<span class='danger'>[A] has hit [D] with Plasma Punch!</span>", \
"<span class='userdanger'>[A] has hit [D] with Plasma Punch!</span>")
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("<span class='danger'>[A] has hit [D] with THE PLASMA FIST TECHNIQUE!</span>", \
"<span class='userdanger'>[A] has hit [D] with THE PLASMA FIST TECHNIQUE!</span>")
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, "<b><i>You clench your fists and have a flashback of knowledge...</i></b>")
to_chat(usr, "<span class='notice'>Tornado Sweep</span>: Harm Harm Disarm. Repulses target and everyone back.")
to_chat(usr, "<span class='notice'>Throwback</span>: Disarm Harm Disarm. Throws the target and an item at them.")
to_chat(usr, "<span class='notice'>The Plasma Fist</span>: 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, "<b><i>You clench your fists and have a flashback of knowledge...</i></b>")
+7 -129
View File
@@ -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("<span class='warning'>[A] grabs [D]'s wrist and wrenches it sideways!</span>", \
"<span class='userdanger'>[A] grabs your wrist and violently wrenches it to the side!</span>")
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("<span class='warning'>[A] kicks [D] in the back!</span>", \
"<span class='userdanger'>[A] kicks you in the back, making you stumble and fall!</span>")
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("<span class='warning'>[A] knees [D] in the stomach!</span>", \
"<span class='userdanger'>[A] winds you with a knee in the stomach!</span>")
D.audible_message("<b>[D]</b> 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("<span class='warning'>[A] kicks [D] in the head!</span>", \
"<span class='userdanger'>[A] kicks you in the jaw!</span>")
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("<span class='warning'>[A] elbow drops [D]!</span>", \
"<span class='userdanger'>[A] piledrives you with [A.p_their()] elbow!</span>")
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("<span class='danger'>[A] [atk_verb] [D]!</span>", \
@@ -132,24 +27,7 @@
if(prob(D.getBruteLoss()) && !D.lying)
D.visible_message("<span class='warning'>[D] stumbles and falls!</span>", "<span class='userdanger'>The blow sends you to the ground!</span>")
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, "<b><i>You retreat inward and recall the teachings of the Sleeping Carp...</i></b>")
to_chat(usr, "<span class='notice'>Wrist Wrench</span>: Disarm Disarm. Forces opponent to drop item in hand.")
to_chat(usr, "<span class='notice'>Back Kick</span>: Harm Grab. Opponent must be facing away. Knocks down.")
to_chat(usr, "<span class='notice'>Stomach Knee</span>: Grab Harm. Knocks the wind out of opponent and stuns.")
to_chat(usr, "<span class='notice'>Head Kick</span>: Disarm Harm Harm. Decent damage, forces opponent to drop item in hand.")
to_chat(usr, "<span class='notice'>Elbow Drop</span>: Harm Disarm Harm Disarm Harm. Opponent must be on the ground. Deals huge damage, instantly kills anyone in critical condition.")
+7 -11
View File
@@ -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("<span class='danger'>[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!</span>", "<span class='userdanger'>You deflect the projectile!</span>")
@@ -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, "<span class='warning'>Your meaty finger is much too large for the trigger guard!</span>")
return 0
return FALSE
if(NOGUNS in dna.species.species_traits)
to_chat(src, "<span class='warning'>Your fingers don't fit in the trigger guard!</span>")
return 0
return FALSE
if(martial_art && martial_art.no_guns) //great dishonor to famiry
to_chat(src, "<span class='warning'>[martial_art.no_guns_message]</span>")
return 0
return .
if(mind && mind.martial_art && mind.martial_art.no_guns) //great dishonor to famiry
to_chat(src, "<span class='warning'>[mind.martial_art.no_guns_message]</span>")
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)
@@ -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
@@ -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
+8 -7
View File
@@ -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)
@@ -516,7 +516,7 @@
playsound(target.loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
target.visible_message("<span class='danger'>[user] attempted to disarm [target]!</span>")
/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, "<span class='warning'>You can't use your hand.</span>")
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("<span class='warning'>[M] attempted to touch [H]!</span>")
+88 -86
View File
@@ -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 = "<span class='notice'>You start preparing a mindblowing monologue. <B>Left-click to cast at a target!</B></span>"
selection_deactivated_message = "<span class='notice'>You decide to save your brilliance for another day.</span>"
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, "<span class='warning'>You have already recruited the maximum number of henchmen.</span>")
if(!in_range(user, target))
to_chat(user, "<span class='warning'>You need to be closer to enthrall [target].</span>")
charge_counter = charge_max
return
if(!target.ckey)
to_chat(user, "<span class='warning'>The target has no mind.</span>")
charge_counter = charge_max
return
if(target.stat)
to_chat(user, "<span class='warning'>The target must be conscious.</span>")
charge_counter = charge_max
return
if(!ishuman(target))
to_chat(user, "<span class='warning'>You can only recruit humans.</span>")
charge_counter = charge_max
return
if(target.mind.assigned_role != "Civilian")
to_chat(user, "<span class='warning'>You can only recruit Civilians.</span>")
return
if(recruiting)
return FALSE
if(recruiting)
if(show_message)
to_chat(user, "<span class='danger'>You are already recruiting!</span>")
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, "<span class='warning'>You can only recruit Civilians.</span>")
revert_cast(user)
return
recruiting = TRUE
to_chat(user, "<span class='danger'>This target is valid. You begin the recruiting process.</span>")
to_chat(target, "<span class='userdanger'>[user] focuses in concentration. Your head begins to ache.</span>")
for(var/progress = 0, progress <= 3, progress++)
switch(progress)
if(1)
to_chat(user, "<span class='notice'>You begin by introducing yourself and explaining what you're about.</span>")
user.visible_message("<span class='danger'>[user] introduces [user.p_them()]self and explains [user.p_their()] plans.</span>")
if(2)
to_chat(user, "<span class='notice'>You begin the recruitment of [target].</span>")
user.visible_message("<span class='danger'>[user] leans over towards [target], whispering excitedly as [user.p_they()] give[user.p_s()] a speech.</span>")
to_chat(target, "<span class='danger'>You feel yourself agreeing with [user], and a surge of loyalty begins building.</span>")
target.Weaken(12)
sleep(20)
if(ismindshielded(target))
to_chat(user, "<span class='notice'>[target.p_they(TRUE)] are enslaved by Nanotrasen. You feel [target.p_their()] interest in your cause wane and disappear.</span>")
user.visible_message("<span class='danger'>[user] stops talking for a moment, then moves back away from [target].</span>")
to_chat(target, "<span class='danger'>Your mindshield implant activates, protecting you from conversion.</span>")
return
if(3)
to_chat(user, "<span class='notice'>You begin filling out the application form with [target].</span>")
user.visible_message("<span class='danger'>[user] pulls out a pen and paper and begins filling an application form with [target].</span>")
to_chat(target, "<span class='danger'>You are being convinced by [user] to fill out an application form to become a henchman.</span>")//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, "<span class='danger'>The enrollment process has been interrupted - you have lost the attention of [target].</span>")
to_chat(target, "<span class='warning'>You move away and are no longer under the charm of [user]. The application form is null and void.</span>")
recruiting = FALSE
return
recruiting = 1
to_chat(user, "<span class='danger'>This target is valid. You begin the recruiting process.</span>")
to_chat(target, "<span class='userdanger'>[user] focuses in concentration. Your head begins to ache.</span>")
for(var/progress = 0, progress <= 3, progress++)
switch(progress)
if(1)
to_chat(user, "<span class='notice'>You begin by introducing yourself and explaining what you're about.</span>")
user.visible_message("<span class='danger'>[user] introduces [user.p_them()]self and explains [user.p_their()] plans.</span>")
if(2)
to_chat(user, "<span class='notice'>You begin the recruitment of [target].</span>")
user.visible_message("<span class='danger'>[user] leans over towards [target], whispering excitedly as [user.p_they()] give[user.p_s()] a speech.</span>")
to_chat(target, "<span class='danger'>You feel yourself agreeing with [user], and a surge of loyalty begins building.</span>")
target.Weaken(12)
sleep(20)
if(ismindshielded(target))
to_chat(user, "<span class='notice'>[target.p_they(TRUE)] are enslaved by Nanotrasen. You feel [target.p_their()] interest in your cause wane and disappear.</span>")
user.visible_message("<span class='danger'>[user] stops talking for a moment, then moves back away from [target].</span>")
to_chat(target, "<span class='danger'>Your mindshield implant activates, protecting you from conversion.</span>")
return
if(3)
to_chat(user, "<span class='notice'>You begin filling out the application form with [target].</span>")
user.visible_message("<span class='danger'>[user] pulls out a pen and paper and begins filling an application form with [target].</span>")
to_chat(target, "<span class='danger'>You are being convinced by [user] to fill out an application form to become a henchman.</span>")//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, "<span class='danger'>The enrollment process has been interrupted - you have lost the attention of [target].</span>")
to_chat(target, "<span class='warning'>You move away and are no longer under the charm of [user]. The application form is null and void.</span>")
recruiting = 0
return
recruiting = 0
to_chat(user, "<span class='notice'>You have recruited <b>[target]</b> as your henchman!</span>")
to_chat(target, "<span class='deadsay'><b>You have decided to enroll as a henchman for [user]. You are now part of the feared 'Greyshirts'.</b></span>")
to_chat(target, "<span class='deadsay'><b>You must follow the orders of [user], and help [user.p_them()] succeed in [user.p_their()] dastardly schemes.</span>")
to_chat(target, "<span class='deadsay'>You may not harm other Greyshirt or [user]. However, you do not need to obey other Greyshirts.</span>")
SSticker.mode.greyshirts += target.mind
target.set_species(/datum/species/human)
recruiting = FALSE
to_chat(user, "<span class='notice'>You have recruited <b>[target]</b> as your henchman!</span>")
to_chat(target, "<span class='deadsay'><b>You have decided to enroll as a henchman for [user]. You are now part of the feared 'Greyshirts'.</b></span>")
to_chat(target, "<span class='deadsay'><b>You must follow the orders of [user], and help [user.p_them()] succeed in [user.p_their()] dastardly schemes.</span>")
to_chat(target, "<span class='deadsay'>You may not harm other Greyshirt or [user]. However, you do not need to obey other Greyshirts.</span>")
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()
@@ -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
@@ -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, "<span class='warning'>ERROR: attempt to use evolve queen ability on a non-princess</span>")
return
var/feedings_left = user.feedings_to_evolve - user.fed
if(feedings_left > 0)
to_chat(user, "<span class='warning'>You must wrap [feedings_left] more humanoid prey before you can do this!</span>")
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, "<span class='warning'>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()
@@ -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
@@ -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, "<span class='danger'>Unrecognized egg type.</span>")
/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, i<numlings, i++)
@@ -96,7 +96,8 @@
S.amount_grown = 250
/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/proc/EraseBrood()
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_tier < spider_tier)
T.degenerate = 1
to_chat(T, "<span class='userdanger'>Through the hivemind, the raw power of [src] floods into your body, burning it from the inside out!</span>")
@@ -106,8 +107,6 @@
qdel(T)
to_chat(src, "<span class='userdanger'>All Terror Spiders, except yourself, will die off shortly.</span>")
/obj/item/projectile/terrorqueenspit/empress
damage_type = BURN
damage = 30
bonus_tox = 0
damage = 90
@@ -39,11 +39,10 @@
to_chat(src, "<span class='warning'>You must wrap more humanoid prey before you can do this!</span>")
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)
@@ -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
@@ -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)
@@ -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, "<span class='danger'>You cannot generate eggs while hiding in [loc].</span>")
return
if((brood_count + canlay) >= spider_max_children)
return
canlay++
if(canlay == 1)
to_chat(src, "<span class='notice'>You have an egg available to lay.</span>")
else
to_chat(src, "<span class='notice'>You have [canlay] eggs available to lay.</span>")
/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
@@ -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, "<span class='notice'>You have an egg available to lay.</span>")
else if(canlay == 12)
to_chat(src, "<span class='notice'>You have [canlay] eggs available to lay. You won't grow any more eggs until you lay some of your existing ones.</span>")
else
to_chat(src, "<span class='notice'>You have [canlay] eggs available to lay.</span>")
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, "<span class='notice'>You have an egg available to lay.</span>")
else if(canlay > 1)
to_chat(src, "<span class='notice'>You have [canlay] eggs available to lay.</span>")
/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("<span class='danger'>\The [src] writhes in pain!</span>")
to_chat(P,"<span class='userdanger'>\The [src] has died. Without her hivemind link, purple terrors like yourself cannot survive more than a few minutes!</span>")
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, "<span class='userdanger'>\The psychic backlash from the death of [src] crashes into your mind! Somehow... you find a way to keep going!</span>")
continue
T.visible_message("<span class='danger'>[T] writhes in pain!</span>")
to_chat(T, "<span class='userdanger'>\The psychic backlash from the death of [src] overwhelms you! You feel the life start to drain out of you...</span>")
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, "<span class='notice'>You have matured to your egglaying stage. You can now smash through walls, and lay eggs, but can no longer ventcrawl.</span>")
/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, "<span class='danger'>Too soon to attempt that again. Wait just a few more seconds...</span>")
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, "<span class='danger'>Too soon to lay another egg.</span>")
return
if(!(eggtype in eggtypes))
to_chat(src, "<span class='danger'>Unrecognized egg type.</span>")
return 0
if(eggtype == TS_DESC_MOTHER || eggtype == TS_DESC_PRINCE)
if(canlay < 12)
to_chat(src, "<span class='danger'>Insufficient strength. It takes as much effort to lay one of those as it does to lay 12 normal eggs.</span>")
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, "<span class='danger'>Insufficient strength. It takes as much effort to lay one of those as it does to lay 4 normal eggs.</span>")
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, "<span class='danger'>Cancelled.</span>")
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, "<span class='warning'>Too soon to do this again!</span>")
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, "<span class='danger'>Unrecognized egg type.</span>")
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, "<span class='danger'>Unrecognized egg type.</span>")
/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("<span class='userdanger'>\The [src] emits a bone-chilling shriek!</span>")
visible_message("<span class='userdanger'>[src] emits a bone-chilling shriek!</span>")
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, "<span class='danger'>You must lay at least 10 eggs before doing this.</span>")
/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, "<span class='danger'>You have run out of uses of this ability.</span>")
if(!isobserver(user) && !isterrorspider(user))
return
. += "<span class='notice'>[p_they(TRUE)] has laid [eggslaid] egg[eggslaid != 1 ? "s" : ""].</span>"
. += "<span class='notice'>[p_they(TRUE)] has lived for [MinutesAlive()] minutes.</span>"
/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)
@@ -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
@@ -126,7 +126,7 @@
spider_steps_taken++
CreatePath(entry_vent)
step_to(src,entry_vent)
if(spider_debug > 0)
if(spider_debug)
visible_message("<span class='notice'>[src] moves towards the vent [entry_vent].</span>")
else
path_to_vent = 0
@@ -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("<span class='danger'>\The [src] pries open the firedoor!</span>")
visible_message("<span class='danger'>[src] pries open the firedoor!</span>")
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 += "<span class='notice'>It appears to be dead.</span>\n"
else
if(stat != DEAD)
if(key)
msgs += "<span class='warning'>Its eyes regard you with a curious intelligence.</span>"
. += "<span class='warning'>[p_they(TRUE)] regards [p_their()] surroundings with a curious intelligence.</span>"
if(health > (maxHealth*0.95))
msgs += "<span class='notice'>It is in excellent health.</span>"
. += "<span class='notice'>[p_they(TRUE)] is in excellent health.</span>"
else if(health > (maxHealth*0.75))
msgs += "<span class='notice'>It has a few injuries.</span>"
. += "<span class='notice'>[p_they(TRUE)] has a few injuries.</span>"
else if(health > (maxHealth*0.55))
msgs += "<span class='warning'>It has many injuries.</span>"
. += "<span class='warning'>[p_they(TRUE)] has many injuries.</span>"
else if(health > (maxHealth*0.25))
msgs += "<span class='warning'>It is barely clinging on to life!</span>"
. += "<span class='warning'>[p_they(TRUE)] is barely clinging on to life!</span>"
if(degenerate)
msgs += "<span class='warning'>It appears to be dying.</span>"
. += "<span class='warning'>[p_they(TRUE)] appears to be dying.</span>"
else if(health < maxHealth && regen_points > regen_points_per_kill)
msgs += "<span class='notice'>It appears to be regenerating quickly.</span>"
. += "<span class='notice'>[p_they(TRUE)] appears to be regenerating quickly.</span>"
if(killcount >= 1)
msgs += "<span class='warning'>It has blood dribbling from its mouth.</span>"
. += msgs.Join("<BR>")
. += "<span class='warning'>[p_they(TRUE)] has blood dribbling from [p_their()] mouth.</span>"
/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 = "<a href=?src=[UID()];activate=1>(Click to control)</a>", 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, "<span class='terrorspider'>TerrorSense: [msgtext]</span>")
@@ -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, "<span class='warning'>Closing doors does not help us.</span>")
else if(D.welded)
to_chat(src, "<span class='warning'>The door is welded shut.</span>")
if(D.welded)
to_chat(src, "<span class='warning'>The door is welded.</span>")
else if(D.locked)
to_chat(src, "<span class='warning'>The door is bolted shut.</span>")
to_chat(src, "<span class='warning'>The door is bolted.</span>")
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, "<span class='warning'>The door's motors resist your efforts to force it.</span>")
else if(!spider_opens_doors)
to_chat(src, "<span class='warning'>Your type of spider is not strong enough to force open doors.</span>")
else
visible_message("<span class='danger'>[src] pries open the door!</span>")
visible_message("<span class='danger'>[src] forces the door!</span>")
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 <font color='#X'> because the status panel does NOT accept <span class='X'>.
if(statpanel("Status") && ckey && stat == CONSCIOUS)
if(degenerate)
stat(null, "<font color='#eb4034'>Hivemind Connection Severed! Dying...</font>") // 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]: <font color='[lcolor]'>[num2text(pc_of_max_per_second)]% of health per second</font>")
+35
View File
@@ -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)

Some files were not shown because too many files have changed in this diff Show More