This commit is contained in:
Ghommie
2020-06-15 02:21:21 +02:00
378 changed files with 290231 additions and 39698 deletions
+70 -45
View File
@@ -346,7 +346,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
//print the key
if(islist(key))
recursive_list_print(output, key, datum_handler, atom_handler)
else if(is_proper_datum(key) && (datum_handler || (isatom(key) && atom_handler)))
else if(is_object_datatype(key) && (datum_handler || (isatom(key) && atom_handler)))
if(isatom(key) && atom_handler)
output += atom_handler.Invoke(key)
else
@@ -360,7 +360,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/value = input[key]
if(islist(value))
recursive_list_print(output, value, datum_handler, atom_handler)
else if(is_proper_datum(value) && (datum_handler || (isatom(value) && atom_handler)))
else if(is_object_datatype(value) && (datum_handler || (isatom(value) && atom_handler)))
if(isatom(value) && atom_handler)
output += atom_handler.Invoke(value)
else
@@ -498,7 +498,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
if(length(select_text))
var/text = islist(select_text)? select_text.Join() : select_text
var/static/result_offset = 0
showmob << browse(text, "window=SDQL-result-[result_offset++]")
showmob << browse(text, "window=SDQL-result-[result_offset++];size=800x1200")
show_next_to_key = null
if(qdel_on_finish)
qdel(src)
@@ -646,7 +646,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
switch(query_tree[1])
if("call")
for(var/i in found)
if(!is_proper_datum(i))
if(!is_object_datatype(i))
continue
world.SDQL_var(i, query_tree["call"][1], null, i, superuser, src)
obj_count_finished++
@@ -664,7 +664,10 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/list/text_list = list()
var/print_nulls = !(options & SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS)
obj_count_finished = select_refs
var/n = 0
for(var/i in found)
if(++n == 20000)
text_list += "<br><font color='red'><b>TRUNCATED - 20000 OBJECT LIMIT HIT</b></font>"
SDQL_print(i, text_list, print_nulls)
select_refs[REF(i)] = TRUE
SDQL2_TICK_CHECK
@@ -675,7 +678,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
if("set" in query_tree)
var/list/set_list = query_tree["set"]
for(var/d in found)
if(!is_proper_datum(d))
if(!is_object_datatype(d))
continue
SDQL_internal_vv(d, set_list)
obj_count_finished++
@@ -685,47 +688,72 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
obj_count_finished = length(obj_count_finished)
state = SDQL2_STATE_SWITCHING
/datum/SDQL2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE)
if(is_proper_datum(object))
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>[REF(object)]</A> : [object]"
if(istype(object, /atom))
var/atom/A = object
var/turf/T = A.loc
var/area/a
if(istype(T))
text_list += " <font color='gray'>at</font> [T] [ADMIN_COORDJMP(T)]"
a = T.loc
else
var/turf/final = get_turf(T) //Recursive, hopefully?
if(istype(final))
text_list += " <font color='gray'>at</font> [final] [ADMIN_COORDJMP(final)]"
a = final.loc
/**
* Recursively prints out an object to text list for SDQL2 output to admins, with VV links and all.
* Recursion limit: 50
* Limit imposed by callers should be around 10000 objects
* Seriously, if you hit those limits, you're doing something wrong.
*/
/datum/SDQL2_query/proc/SDQL_print(datum/object, list/text_list, print_nulls = TRUE, recursion = 1, linebreak = TRUE)
if(recursion > 50)
text_list += "<br><font color='red'><b>RECURSION LIMIT REACHED.</font></b><br>"
return
if(is_object_datatype(object))
if(!islist(object))
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>[object.type] [REF(object)]</A>: [object]"
if(istype(object, /atom))
if(istype(object, /turf))
var/turf/T = object
text_list += " [ADMIN_COORDJMP(T)] <font color='gray'>at</font> [T.loc]"
else
text_list += " <font color='gray'>at</font> nonexistant location"
if(a)
text_list += " <font color='gray'>in</font> area [a]"
if(T.loc != a)
text_list += " <font color='gray'>inside</font> [T]"
text_list += "<br>"
else if(islist(object))
var/list/L = object
var/first = TRUE
text_list += "\["
for (var/x in L)
if (!first)
text_list += ", "
first = FALSE
SDQL_print(x, text_list)
if (!isnull(x) && !isnum(x) && L[x] != null)
text_list += " -> "
SDQL_print(L[L[x]], text_list)
text_list += "]<br>"
var/atom/A = object
var/atom/container = A.loc
if(isturf(container))
text_list += " <font color='gray'>in</font> [container] [ADMIN_COORDJMP(container)] <font color='gray'>at</font> [container.loc]"
else if(container)
var/turf/T = get_turf(container)
var/cref = REF(container)
text_list += " <font color='gray'>in</font> <A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[cref]'>[container]([cref])</A>"
if(T)
text_list += " <font color='gray'>on</font> [T] [ADMIN_COORDJMP(T)] <font color='gray'>at</font>[T.loc]"
else
text_list += " <font color='gray'>in</font> nullspace"
else // lists are snowflake and get special treatment.
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>/list [REF(object)]</A> \[<br>"
var/list/L = object
if(length(L))
for(var/key in object)
if(islist(key))
text_list += "<span style='margin-left: [min(10, recursion) * 2]em;'>"
SDQL_print(key, text_list, TRUE, recursion + 1, FALSE)
text_list += "</span>"
else
SDQL_print(key, text_list, TRUE, recursion, FALSE)
if(IS_VALID_ASSOC_KEY(key) && !isnull(L[key]))
var/value = L[key]
text_list += " --> "
if(islist(value))
text_list += "<span style='margin-left: [min(10, recursion) * 2]em;'>"
SDQL_print(value, text_list, TRUE, recursion + 1, FALSE)
text_list += "</span>"
else
SDQL_print(value, text_list, TRUE, recursion, FALSE)
text_list += "<br>"
text_list += "\]"
if(linebreak)
text_list += "<br>"
else
if(isnull(object))
if(print_nulls)
text_list += "NULL<br>"
text_list += "NULL"
else if(istext(object))
text_list += "\"[object]\""
else if(isnum(object) || ispath(object))
text_list += "[object]"
else
text_list += "[object]<br>"
text_list += "UNKNOWN: [object]"
if(linebreak)
text_list += "<br>"
/datum/SDQL2_query/CanProcCall()
if(!allow_admin_interact)
@@ -957,7 +985,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/static/list/exclude = list("usr", "src", "marked", "global")
var/long = start < expression.len
var/datum/D
if(is_proper_datum(object))
if(is_object_datatype(object))
D = object
if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude)) //3 == length("SS") + 1
@@ -1161,9 +1189,6 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
query_list += word
return query_list
/proc/is_proper_datum(thing)
return istype(thing, /datum) || istype(thing, /client)
/obj/effect/statclick/SDQL2_delete/Click()
var/datum/SDQL2_query/Q = target
Q.delete_click()
@@ -16,11 +16,19 @@
header = "<li>"
var/item
var/name_part = VV_HTML_ENCODE(name)
if(level > 0 || islist(D)) //handling keys in assoc lists
if(istype(name,/datum))
name_part = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(name)]'>[VV_HTML_ENCODE(name)] [REF(name)]</a>"
else if(islist(name))
var/list/L = name
name_part = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(name)]'> /list ([length(L)]) [REF(name)]</a>"
if (isnull(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>null</span>"
item = "[name_part] = <span class='value'>null</span>"
else if (istext(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>\"[VV_HTML_ENCODE(value)]\"</span>"
item = "[name_part] = <span class='value'>\"[VV_HTML_ENCODE(value)]\"</span>"
else if (isicon(value))
#ifdef VARSICON
@@ -28,33 +36,31 @@
var/rnd = rand(1,10000)
var/rname = "tmp[REF(I)][rnd].png"
usr << browse_rsc(I, rname)
item = "[VV_HTML_ENCODE(name)] = (<span class='value'>[value]</span>) <img class=icon src=\"[rname]\">"
item = "[name_part] = (<span class='value'>[value]</span>) <img class=icon src=\"[rname]\">"
#else
item = "[VV_HTML_ENCODE(name)] = /icon (<span class='value'>[value]</span>)"
item = "[name_part] = /icon (<span class='value'>[value]</span>)"
#endif
else if (isfile(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>'[value]'</span>"
item = "[name_part] = <span class='value'>'[value]'</span>"
else if(istype(value, /matrix)) // Needs to be before datum
else if(istype(value,/matrix)) // Needs to be before datum
var/matrix/M = value
item = {"[VV_HTML_ENCODE(name)] = <span class='value'>
<table class='matrixbrak'><tbody><tr>
<td class='lbrak'>&nbsp;</td>
<td><table class='matrix'><tbody>
<tr><td>[M.a]</td><td>[M.d]</td><td>0</td></tr>
<tr><td>[M.b]</td><td>[M.e]</td><td>0</td></tr>
<tr><td>[M.c]</td><td>[M.f]</td><td>1</td></tr>
</tbody></table></td>
<td class='rbrak'>&nbsp;</td>
</tr></tbody></table></span>"} //TODO link to modify_transform wrapper for all matrices
item = {"[name_part] = <span class='value'>
<table class='matrixbrak'><tbody><tr><td class='lbrak'>&nbsp;</td><td>
<table class='matrix'>
<tbody>
<tr><td>[M.a]</td><td>[M.d]</td><td>0</td></tr>
<tr><td>[M.b]</td><td>[M.e]</td><td>0</td></tr>
<tr><td>[M.c]</td><td>[M.f]</td><td>1</td></tr>
</tbody>
</table></td><td class='rbrak'>&nbsp;</td></tr></tbody></table></span>"} //TODO link to modify_transform wrapper for all matrices
else if (istype(value, /datum))
var/datum/DV = value
if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it.
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV] [DV.type]"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[DV] [DV.type] [REF(value)]</a>"
else
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV.type]"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[DV.type] [REF(value)]</a>"
else if (islist(value))
var/list/L = value
@@ -72,19 +78,19 @@
items += debug_variable(key, val, level + 1, sanitize = sanitize)
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a><ul>[items.Join()]</ul>"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>/list ([L.len])</a><ul>[items.Join()]</ul>"
else
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a>"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>/list ([L.len])</a>"
else if (name in GLOB.bitfields)
var/list/flags = list()
for (var/i in GLOB.bitfields[name])
if (value & GLOB.bitfields[name][i])
flags += i
item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
item = "[name_part] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
else
item = "[VV_HTML_ENCODE(name)] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
item = "[name_part] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
return "[header][item]</li>"
#undef VV_HTML_ENCODE
#undef VV_HTML_ENCODE
@@ -167,4 +167,4 @@
///obj/item/pipe = 2)
time = 80
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
subcategory = CAT_MELEE
@@ -91,6 +91,7 @@
B.organ_flags |= ORGAN_VITAL
B.decoy_override = FALSE
remove_changeling_powers()
owner.special_role = null
. = ..()
/datum/antagonist/changeling/proc/remove_clownmut()
@@ -432,15 +432,18 @@
/obj/item/shield/changeling
name = "shield-like mass"
desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield."
item_flags = ABSTRACT | DROPDEL
item_flags = ABSTRACT | DROPDEL | ITEM_CAN_BLOCK
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "ling_shield"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
block_chance = 50
block_parry_data = /datum/block_parry_data/shield/changeling
var/remaining_uses //Set by the changeling ability.
/datum/block_parry_data/shield/changeling
block_slowdown = 0
/obj/item/shield/changeling/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
@@ -451,7 +454,7 @@
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
return ..()
/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
/obj/item/shield/changeling/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
. = ..()
if(--remaining_uses < 1)
if(ishuman(loc))
@@ -1,21 +1,31 @@
//horrifying power drain proc made for clockcult's power drain in lieu of six istypes or six for(x in view) loops
/atom/movable/proc/power_drain(clockcult_user)
/atom/movable/proc/power_drain(clockcult_user, drain_weapons = FALSE) //This proc as of now is only in use for void volt
var/obj/item/stock_parts/cell/cell = get_cell()
if(cell)
return cell.power_drain(clockcult_user)
return 0
return 0 //Returns 0 instead of FALSE to symbolise it returning the power amount in other cases, not TRUE aka 1
/obj/item/melee/baton/power_drain(clockcult_user) //balance memes
return 0
/obj/item/melee/baton/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
if(!drain_weapons)
return 0
return ..()
/obj/item/gun/power_drain(clockcult_user) //balance memes
return 0
/obj/item/gun/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
if(!drain_weapons)
return 0
var/obj/item/stock_parts/cell/cell = get_cell()
if(!cell)
return 0
if(cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4) //Done snowflakey because guns have far smaller cells than batons / other equipment
cell.use(.)
update_icon()
/obj/machinery/power/apc/power_drain(clockcult_user)
/obj/machinery/power/apc/power_drain(clockcult_user, drain_weapons = FALSE)
if(cell && cell.charge)
playsound(src, "sparks", 50, 1)
flick("apc-spark", src)
. = min(cell.charge, MIN_CLOCKCULT_POWER*3)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.) //Better than a power sink!
if(!cell.charge && !shorted)
shorted = 1
@@ -23,9 +33,9 @@
update()
update_icon()
/obj/machinery/power/smes/power_drain(clockcult_user)
/obj/machinery/power/smes/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
. = min(charge, MIN_CLOCKCULT_POWER*3)
. = min(charge, MIN_CLOCKCULT_POWER*4)
charge -= . * 50
if(!charge && !panel_open)
panel_open = TRUE
@@ -34,19 +44,19 @@
visible_message("<span class='warning'>[src]'s panel flies open with a flurry of sparks!</span>")
update_icon()
/obj/item/stock_parts/cell/power_drain(clockcult_user)
/obj/item/stock_parts/cell/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
. = min(charge, MIN_CLOCKCULT_POWER*3)
charge = use(.)
. = min(charge, MIN_CLOCKCULT_POWER * 4) //Done like this because normal cells are usually quite a bit bigger than the ones used in guns / APCs
use(min(charge, . * 10)) //Usually cell-powered equipment that is not a gun has at least ten times the capacity of a gun / 5 times the amount of an APC. This adjusts the drain to account for that.
update_icon()
/mob/living/silicon/robot/power_drain(clockcult_user)
/mob/living/silicon/robot/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || !is_servant_of_ratvar(src)) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
spark_system.start()
/obj/mecha/power_drain(clockcult_user)
/obj/mecha/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || (occupant && !is_servant_of_ratvar(occupant))) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
@@ -135,6 +135,29 @@
return TRUE
//For the Volt Void scripture, fires a ray of energy at a target location
/obj/effect/proc_holder/slab/volt
ranged_mousepointer = 'icons/effects/volt_target.dmi'
/obj/effect/proc_holder/slab/volt/InterceptClickOn(mob/living/caller, params, atom/target)
if(target == slab || ..()) //we can't cancel
return TRUE
var/turf/T = ranged_ability_user.loc
if(!isturf(T))
return TRUE
if(target in view(7, get_turf(ranged_ability_user)))
successful = TRUE
ranged_ability_user.visible_message("<span class='warning'>[ranged_ability_user] fires a ray of energy at [target]!</span>", "<span class='nzcrentr'>You fire a volt ray at [target].</span>")
playsound(ranged_ability_user, 'sound/effects/light_flicker.ogg', 50, 1)
T = get_turf(target)
new/obj/effect/temp_visual/ratvar/volt_hit(T, ranged_ability_user)
log_combat(ranged_ability_user, T, "fired a volt ray")
remove_ranged_ability()
return TRUE
//For the Kindle scripture; stuns and mutes a target non-servant.
/obj/effect/proc_holder/slab/kindle
ranged_mousepointer = 'icons/effects/volt_target.dmi'
@@ -203,6 +203,10 @@ Applications: 8 servants, 3 caches, and 100 CV
if(!do_after(invoker, chant_interval, target = invoker, extra_checks = CALLBACK(src, .proc/can_recite)))
break
clockwork_say(invoker, text2ratvar(pick(chant_invocations)), whispered)
if(multiple_invokers_used)
for(var/mob/living/L in range(1, get_turf(invoker)))
if(can_recite_scripture(L) && L != invoker)
clockwork_say(L, text2ratvar(pick(chant_invocations)), whispered)
if(!chant_effects(i))
break
if(invoker && slab)
@@ -115,7 +115,7 @@
tier = SCRIPTURE_SCRIPT
space_allowed = TRUE
primary_component = VANGUARD_COGWHEEL
sort_priority = 5
sort_priority = 6
quickbind = TRUE
quickbind_desc = "Creates a Ratvarian shield, which can absorb energy from attacks for use in powerful bashes."
@@ -131,7 +131,7 @@
usage_tip = "Throwing the spear at a mob will do massive damage and knock them down, but break the spear. You will need to wait for 30 seconds before resummoning it."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
sort_priority = 6
sort_priority = 7
important = TRUE
quickbind = TRUE
quickbind_desc = "Permanently binds clockwork armor and a Ratvarian spear to you."
@@ -231,7 +231,7 @@
usage_tip = "This gateway is strictly one-way and will only allow things through the invoker's portal."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
sort_priority = 7
sort_priority = 9
quickbind = TRUE
quickbind_desc = "Allows you to create a one-way Spatial Gateway to a living Servant or Clockwork Obelisk."
@@ -263,3 +263,227 @@
duration = max(duration, 100)
return slab.procure_gateway(invoker, duration, portal_uses)
//Mending Mantra: Channeled for up to ten times over twenty seconds to repair structures and heal allies
/datum/clockwork_scripture/channeled/mending_mantra
descname = "Channeled, Area Healing and Repair"
name = "Mending Mantra"
desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed. Channeled every two seconds for a maximum of twenty seconds."
chant_invocations = list("Mend our dents!", "Heal our scratches!", "Repair our gears!")
chant_amount = 10
chant_interval = 20
power_cost = 400
usage_tip = "This is a very effective way to rapidly reinforce a base after an attack."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
sort_priority = 8
quickbind = TRUE
quickbind_desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed.<br><b>Maximum 10 chants.</b>"
var/heal_attempts = 4
var/heal_amount = 2.5
var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
var/static/list/heal_finish_messages = list("There, all mended!", "Try not to get too damaged.", "No more dents and scratches for you!", "Champions never die.", "All patched up.", \
"Ah, child, it's okay now.", "Pain is temporary.", "What you do for the Justiciar is eternal.", "Bear this for me.", "Be strong, child.", "Please, be careful!", \
"If you die, you will be remembered.")
var/static/list/heal_target_typecache = typecacheof(list(
/obj/structure/destructible/clockwork,
/obj/machinery/door/airlock/clockwork,
/obj/machinery/door/window/clockwork,
/obj/structure/window/reinforced/clockwork,
/obj/structure/table/reinforced/brass))
var/static/list/ratvarian_armor_typecache = typecacheof(list(
/obj/item/clothing/suit/armor/clockwork,
/obj/item/clothing/head/helmet/clockwork,
/obj/item/clothing/gloves/clockwork,
/obj/item/clothing/shoes/clockwork))
/datum/clockwork_scripture/channeled/mending_mantra/chant_effects(chant_number)
var/turf/T
for(var/atom/movable/M in range(7, invoker))
if(isliving(M))
if(isclockmob(M) || istype(M, /mob/living/simple_animal/drone/cogscarab))
var/mob/living/simple_animal/S = M
if(S.health == S.maxHealth || S.stat == DEAD)
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(S.health < S.maxHealth)
S.adjustHealth(-heal_amount)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_attempts && S.health >= S.maxHealth) //we finished healing on the last tick, give them the message
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(issilicon(M))
var/mob/living/silicon/S = M
if(S.health == S.maxHealth || S.stat == DEAD || !is_servant_of_ratvar(S))
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(S.health < S.maxHealth)
S.heal_ordered_damage(heal_amount, damage_heal_order)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_attempts && S.health >= S.maxHealth)
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.health == H.maxHealth || H.stat == DEAD || !is_servant_of_ratvar(H))
continue
T = get_turf(M)
var/heal_ticks = 0 //one heal tick for each piece of ratvarian armor worn
var/obj/item/I = H.get_item_by_slot(SLOT_WEAR_SUIT)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_HEAD)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_GLOVES)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_SHOES)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
if(heal_ticks)
for(var/i in 1 to heal_ticks)
if(H.health < H.maxHealth)
H.heal_ordered_damage(heal_amount, damage_heal_order)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_ticks && H.health >= H.maxHealth)
to_chat(H, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(H, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(is_type_in_typecache(M, heal_target_typecache))
var/obj/structure/destructible/clockwork/C = M
if(C.obj_integrity == C.max_integrity || (istype(C) && !C.can_be_repaired))
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(C.obj_integrity < C.max_integrity)
C.obj_integrity = min(C.obj_integrity + 5, C.max_integrity)
C.update_icon()
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
else
break
new /obj/effect/temp_visual/ratvar/mending_mantra(get_turf(invoker))
return TRUE
//Volt Blaster: Channeled for up to five times over ten seconds to fire up to five rays of energy at target locations.
/datum/clockwork_scripture/channeled/volt_blaster
descname = "Channeled, Targeted Energy Blasts"
name = "Volt Blaster"
desc = "Allows you to fire five energy rays at target locations. Channeled every fourth of a second for a maximum of ten seconds."
channel_time = 30
invocations = list("Amperage...", "...grant me your power!")
chant_invocations = list("Use charge to kill!", "Slay with power!", "Hunt with energy!")
chant_amount = 5
chant_interval = 4
power_cost = 500
usage_tip = "Though it requires you to stand still, this scripture can do massive damage."
tier = SCRIPTURE_SCRIPT
primary_component = BELLIGERENT_EYE
sort_priority = 5
quickbind = TRUE
quickbind_desc = "Allows you to fire energy rays at target locations.<br><b>Maximum 5 chants.</b>"
var/static/list/nzcrentr_insults = list("You're not very good at aiming.", "You hunt badly.", "What a waste of energy.", "Almost funny to watch.",
"Boss says </span><span class='heavy_brass'>\"Click something, you idiot!\"</span><span class='nzcrentr'>.", "Stop wasting power if you can't aim.")
/datum/clockwork_scripture/channeled/volt_blaster/chant_effects(chant_number)
slab.busy = null
var/datum/clockwork_scripture/ranged_ability/volt_ray/ray = new
ray.slab = slab
ray.invoker = invoker
var/turf/T = get_turf(invoker)
if(!ray.run_scripture() && slab && invoker)
if(can_recite() && T == get_turf(invoker))
to_chat(invoker, "<span class='nzcrentr'>\"[text2ratvar(pick(nzcrentr_insults))]\"</span>")
else
return FALSE
return TRUE
/obj/effect/ebeam/volt_ray
name = "volt_ray"
layer = LYING_MOB_LAYER
/datum/clockwork_scripture/ranged_ability/volt_ray
name = "Volt Ray"
slab_overlay = "volt"
allow_mobility = FALSE
ranged_type = /obj/effect/proc_holder/slab/volt
ranged_message = "<span class='nzcrentr_small'><i>You charge the clockwork slab with shocking might.</i>\n\
<b>Left-click a target to fire, quickly!</b></span>"
timeout_time = 20
/datum/clockwork_scripture/channeled/void_volt
descname = "Channeled, Power Drain"
name = "Void Volt"
desc = "A channeled spell that quickly drains any powercells in a radius of eight tiles, but burns the invoker. \
Can be channeled with more cultists to increase range and split the caused damage evenly over all invokers. \
Also charges clockwork power by a small percentage of the drained power amount, which can help offset this scriptures powercost."
invocations = list("Channel their energy through my body... ", "... so it may fuel Engine!")
chant_invocations = list("Make their lights fall dark!", "They shall be powerless!", "Rob them of their power!")
chant_amount = 20
chant_interval = 10 //100KW drain per pulse for guns / APCs / 1MW for other cells = 10 chants / 100ds / 10s to drain a charged weapon or a baton with a nonupgraded cell
channel_time = 50
power_cost = 300
multiple_invokers_used = TRUE
multiple_invokers_optional = TRUE
usage_tip = "It may be useful to end channelling early if the burning becomes too much to handle.."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
sort_priority = 10
quickbind = TRUE
quickbind_desc = "Quickly drains power in an area around the invoker, causing burns proportional to the amount of energy drained.<br><b>Maximum of 20 chants.</b>"
/datum/clockwork_scripture/channeled/void_volt/scripture_effects()
invoker.visible_message("<span class='warning'>[invoker] glows in a brilliant golden light!</span>")
invoker.add_atom_colour("#FFD700", ADMIN_COLOUR_PRIORITY)
invoker.light_power = 2
invoker.light_range = 4
invoker.light_color = LIGHT_COLOR_FIRE
invoker.update_light()
return ..()
/datum/clockwork_scripture/channeled/void_volt/chant_effects(chant_number)
var/power_drained = 0
var/power_mod = 0.005 //Amount of power drained (generally) is multiplied with this, and subsequently dealt in damage to the invoker, then 15 times that is added to the clockwork cult's power reserves.
var/drain_range = 8
var/additional_chanters = 0
var/list/chanters = list()
chanters += invoker
for(var/mob/living/L in range(1, invoker))
if(!L.stat && is_servant_of_ratvar(L))
additional_chanters++
chanters += L
drain_range = min(drain_range + 2 * additional_chanters, drain_range * 2) //s u c c
for(var/t in spiral_range_turfs(drain_range, invoker))
var/turf/T = t
for(var/M in T)
var/atom/movable/A = M
power_drained += A.power_drain(TRUE, TRUE) //Yes, this absolutely does drain weaponry. 10 pulses to drain guns / batons, though of course they can just be recharged.
new /obj/effect/temp_visual/ratvar/sigil/transgression(invoker.loc, 1 + (power_drained * power_mod))
var/datum/effect_system/spark_spread/S = new
S.set_up(round(1 + (power_drained * power_mod), 1), 0, get_turf(invoker))
S.start()
adjust_clockwork_power(power_drained * power_mod * 15)
for(var/mob/living/L in chanters)
L.adjustFireLoss(round(clamp(power_drained * power_mod / (1 + additional_chanters), 0, 20), 0.1)) //No you won't just immediately melt if you do this in a very power-rich area
return TRUE
/datum/clockwork_scripture/channeled/void_volt/chant_end_effects()
invoker.visible_message("<span class='warning'>[invoker] stops glowing...</span>")
invoker.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
invoker.light_power = 0
invoker.light_range = 0
invoker.update_light()
return ..()
@@ -101,7 +101,7 @@
return 1
return ..()
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user)
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
if(is_servant_of_ratvar(user) && immune_to_servant_attacks)
return FALSE
return ..()
+1 -1
View File
@@ -343,7 +343,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NO_ATTACK_CHAIN_SOFT_STAMCRIT
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
@@ -273,6 +273,7 @@
knockdown = 20
/obj/item/restraints/legcuffs/bola/cult/pickup(mob/living/user)
. = ..()
if(!iscultist(user))
to_chat(user, "<span class='warning'>The bola seems to take on a life of its own!</span>")
ensnare(user)
@@ -116,24 +116,11 @@
/mob/living/carbon/true_devil/get_ear_protection()
return 2
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone)
var/weakness = check_weakness(I, user)
apply_damage(I.force * weakness, I.damtype, def_zone)
var/message_verb = ""
if(I.attack_verb && I.attack_verb.len)
message_verb = "[pick(I.attack_verb)]"
else if(I.force)
message_verb = "attacked"
var/attack_message = "[src] has been [message_verb] with [I]."
if(user)
user.do_attack_animation(src)
if(user in viewers(src, null))
attack_message = "[user] has [message_verb] [src] with [I]!"
if(message_verb)
visible_message("<span class='danger'>[attack_message]</span>",
"<span class='userdanger'>[attack_message]</span>", null, COMBAT_MESSAGE_RANGE)
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = pre_attacked_by(I, user)
totitemdamage *= check_weakness(I, user)
apply_damage(totitemdamage, I.damtype, def_zone)
send_item_attack_message(I, user, null, totitemdamage)
return TRUE
/mob/living/carbon/true_devil/singularity_act()
@@ -4,6 +4,7 @@
weight = 3
chaos = 5
threat = 3
min_players = 25
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit)
/datum/traitor_class/human/hijack/forge_objectives(datum/antagonist/traitor/T)
@@ -4,6 +4,7 @@
weight = 2
chaos = 5
threat = 5
min_players = 20
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_TC/contract_kit)
/datum/traitor_class/human/martyr/forge_objectives(datum/antagonist/traitor/T)
@@ -7,6 +7,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
var/chaos = 0
var/threat = 0
var/TC = 20
/// Minimum players for this to randomly roll via get_random_traitor_class().
var/min_players = 0
var/list/uplink_filters
/datum/traitor_class/New()
@@ -41,4 +43,4 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T)
// Any effects that need to be cleaned up if traitor class is being swapped.
@@ -47,6 +47,8 @@
for(var/C in GLOB.traitor_classes)
if(!(C in blacklist))
var/datum/traitor_class/class = GLOB.traitor_classes[C]
if(class.min_players > length(GLOB.joined_player_list))
continue
var/weight = LOGISTIC_FUNCTION(1.5*class.weight,chaos_weight,class.chaos,0)
weights[C] = weight * 1000
var/choice = pickweight(weights, 0)
@@ -36,7 +36,7 @@
/obj/item/electronics/airalarm
name = "air alarm electronics"
icon_state = "airalarm_electronics"
custom_price = 50
custom_price = PRICE_CHEAP
/obj/item/wallframe/airalarm
name = "air alarm frame"
@@ -162,6 +162,9 @@
to_chat(user, "<span class='danger'>Access denied.</span>")
return UI_CLOSE
/obj/machinery/atmospherics/components/attack_ghost(mob/dead/observer/O)
. = ..()
atmosanalyzer_scan(airs, O, src, FALSE)
// Tool acts
@@ -111,3 +111,10 @@
pipe_color = paint_color
update_node_icon()
return TRUE
/obj/machinery/atmospherics/pipe/attack_ghost(mob/dead/observer/O)
. = ..()
if(parent)
atmosanalyzer_scan(parent.air, O, src, FALSE)
else
to_chat(O, "<span class='warning'>[src] doesn't have a pipenet, which is probably a bug.</span>")
@@ -147,10 +147,14 @@
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user, attackchain_flags = NONE, damage_multiplier = 1)
if(I.force < 10 && !(stat & BROKEN))
take_damage(0)
else
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
add_fingerprint(user)
..()
/obj/machinery/portable_atmospherics/attack_ghost(mob/dead/observer/O)
. = ..()
atmosanalyzer_scan(air_contents, O, src, FALSE)
+2 -2
View File
@@ -171,10 +171,10 @@
var/worth = 10
var/gases = C.air_contents.gases
worth += gases[/datum/gas/bz]*4
worth += gases[/datum/gas/bz]*3
worth += gases[/datum/gas/stimulum]*25
worth += gases[/datum/gas/hypernoblium]*1000
worth += gases[/datum/gas/miasma]*4
worth += gases[/datum/gas/miasma]*2
worth += gases[/datum/gas/tritium]*7
worth += gases[/datum/gas/pluoxium]*6
worth += gases[/datum/gas/nitryl]*30
+2 -1
View File
@@ -71,7 +71,8 @@
/obj/item/clothing/mask/gas/syndicate,
/obj/item/clothing/neck/necklace/dope,
/obj/item/vending_refill/donksoft,
/obj/item/circuitboard/computer/arcade/amputation)
/obj/item/circuitboard/computer/arcade/amputation,
/obj/item/storage/bag/ammo)
crate_name = "crate"
/datum/supply_pack/costumes_toys/foamforce
+10
View File
@@ -66,6 +66,16 @@
crate_name = "shaft miner starter kit"
crate_type = /obj/structure/closet/crate/secure
/datum/supply_pack/service/snowmobile
name = "Snowmobile kit"
desc = "trapped on a frigid wasteland? need to get around fast? purchase a refurbished snowmobile, with a FREE 10 microsecond warranty!"
cost = 1500 // 1000 points cheaper than ATV
contains = list(/obj/vehicle/ridden/atv/snowmobile = 1,
/obj/item/key = 1,
/obj/item/clothing/mask/gas/explorer = 1)
crate_name = "Snowmobile kit"
crate_type = /obj/structure/closet/crate/large
//////////////////////////////////////////////////////////////////////////////
/////////////////////// Chef, Botanist, Bartender ////////////////////////////
//////////////////////////////////////////////////////////////////////////////
+3
View File
@@ -133,3 +133,6 @@
var/parallax_movedir = 0
var/parallax_layers_max = 3
var/parallax_animate_timer
//world.time of when the crew manifest can be accessed
var/crew_manifest_delay
+66 -27
View File
@@ -50,6 +50,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// Custom Keybindings
var/list/key_bindings = list()
/// List with a key string associated to a list of keybindings. Unlike key_bindings, this one operates on raw key, allowing for binding a key that triggers regardless of if a modifier is depressed as long as the raw key is sent.
var/list/modless_key_bindings = list()
var/tgui_fancy = TRUE
@@ -154,6 +156,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"ipc_screen" = "Sunburst",
"ipc_antenna" = "None",
"flavor_text" = "",
"silicon_flavor_text" = "",
"ooc_notes" = "",
"meat_type" = "Mammalian",
"body_model" = MALE,
@@ -367,6 +370,15 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[features["flavor_text"]]"
else
dat += "[TextPreview(features["flavor_text"])]...<BR>"
dat += "<h2>Silicon Flavor Text</h2>"
dat += "<a href='?_src_=prefs;preference=silicon_flavor_text;task=input'><b>Set Silicon Examine Text</b></a><br>"
if(length(features["silicon_flavor_text"]) <= 40)
if(!length(features["silicon_flavor_text"]))
dat += "\[...\]"
else
dat += "[features["silicon_flavor_text"]]"
else
dat += "[TextPreview(features["silicon_flavor_text"])]...<BR>"
dat += "<h2>OOC notes</h2>"
dat += "<a href='?_src_=prefs;preference=ooc_notes;task=input'><b>Set OOC notes</b></a><br>"
var/ooc_notes_len = length(features["ooc_notes"])
@@ -1088,11 +1100,16 @@ GLOBAL_LIST_EMPTY(preferences_datums)
Input mode is the closest thing to the old input system.<br>\
<b>IMPORTANT:</b> While in input mode's non hotkey setting (tab toggled), Ctrl + KEY will send KEY to the keybind system as the key itself, not as Ctrl + KEY. This means Ctrl + T/W/A/S/D/all your familiar stuff still works, but you \
won't be able to access any regular Ctrl binds.<br>"
dat += "<br><b>Modifier-Independent binding</b> - This is a singular bind that works regardless of if Ctrl/Shift/Alt are held down. For example, if combat mode is bound to C in modifier-independent binds, it'll trigger regardless of if you are \
holding down shift for sprint. <b>Each keybind can only have one independent binding, and each key can only have one keybind independently bound to it.</b>"
// Create an inverted list of keybindings -> key
var/list/user_binds = list()
var/list/user_modless_binds = list()
for (var/key in key_bindings)
for(var/kb_name in key_bindings[key])
user_binds[kb_name] += list(key)
for (var/key in modless_key_bindings)
user_modless_binds[modless_key_bindings[key]] = key
var/list/kb_categories = list()
// Group keybinds by category
@@ -1100,21 +1117,29 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
kb_categories[kb.category] += list(kb)
dat += "<style>label { display: inline-block; width: 200px; }</style><body>"
dat += {"
<style>
span.bindname { display: inline-block; position: absolute; width: 20% ; left: 5px; padding: 5px; } \
span.bindings { display: inline-block; position: relative; width: auto; left: 20%; width: auto; right: 20%; padding: 5px; } \
span.independent { display: inline-block; position: absolute; width: 20%; right: 5px; padding: 5px; } \
</style><body>
"}
for (var/category in kb_categories)
dat += "<h3>[category]</h3>"
for (var/i in kb_categories[category])
var/datum/keybinding/kb = i
var/current_independent_binding = user_modless_binds[kb.name] || "Unbound"
if(!length(user_binds[kb.name]))
dat += "<label>[kb.full_name]</label> <a href ='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=["Unbound"]'>Unbound</a>"
dat += "<span class='bindname'>[kb.full_name]</span><span class='bindings'><a href ='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=["Unbound"]'>Unbound</a>"
var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += "</span><span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "<br>"
else
var/bound_key = user_binds[kb.name][1]
dat += "<label>[kb.full_name]</label> <a href ='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[bound_key]'>[bound_key]</a>"
dat += "<span class='bindname'l>[kb.full_name]</span><span class='bindings'><a href ='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[bound_key]'>[bound_key]</a>"
for(var/bound_key_index in 2 to length(user_binds[kb.name]))
bound_key = user_binds[kb.name][bound_key_index]
dat += " | <a href ='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[bound_key]'>[bound_key]</a>"
@@ -1123,6 +1148,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += "</span><span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "<br>"
dat += "<br><br>"
@@ -1148,7 +1174,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
#undef APPEARANCE_CATEGORY_COLUMN
#undef MAX_MUTANT_ROWS
/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, var/old_key)
/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, old_key, independent = FALSE)
var/HTML = {"
<div id='focus' style="outline: 0;" tabindex=0>Keybinding: [kb.full_name]<br>[kb.description]<br><br><b>Press any key to change<br>Press ESC to clear</b></div>
<script>
@@ -1160,7 +1186,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var shift = e.shiftKey ? 1 : 0;
var numpad = (95 < e.keyCode && e.keyCode < 112) ? 1 : 0;
var escPressed = e.keyCode == 27 ? 1 : 0;
var url = 'byond://?_src_=prefs;preference=keybindings_set;keybinding=[kb.name];old_key=[old_key];clear_key='+escPressed+';key='+e.key+';alt='+alt+';ctrl='+ctrl+';shift='+shift+';numpad='+numpad+';key_code='+e.keyCode;
var url = 'byond://?_src_=prefs;preference=keybindings_set;keybinding=[kb.name];old_key=[old_key];[independent?"independent=1":""];clear_key='+escPressed+';key='+e.key+';alt='+alt+';ctrl='+ctrl+';shift='+shift+';numpad='+numpad+';key_code='+e.keyCode;
window.location=url;
deedDone = true;
}
@@ -1612,14 +1638,19 @@ GLOBAL_LIST_EMPTY(preferences_datums)
age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN)
if("flavor_text")
var/msg = stripped_multiline_input(usr, "Set the flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Flavor Text", features["flavor_text"], MAX_FLAVOR_LEN, TRUE)
var/msg = stripped_multiline_input(usr, "Set the flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Flavor Text", html_decode(features["flavor_text"]), MAX_FLAVOR_LEN, TRUE)
if(!isnull(msg))
features["flavor_text"] = html_decode(msg)
features["flavor_text"] = msg
if("silicon_flavor_text")
var/msg = stripped_multiline_input(usr, "Set the silicon flavor text in your 'examine' verb. This can also be used for OOC notes and preferences!", "Silicon Flavor Text", html_decode(features["silicon_flavor_text"]), MAX_FLAVOR_LEN, TRUE)
if(!isnull(msg))
features["silicon_flavor_text"] = msg
if("ooc_notes")
var/msg = stripped_multiline_input(usr, "Set always-visible OOC notes related to content preferences. THIS IS NOT FOR CHARACTER DESCRIPTIONS!", "OOC notes", features["ooc_notes"], MAX_FLAVOR_LEN, TRUE)
var/msg = stripped_multiline_input(usr, "Set always-visible OOC notes related to content preferences. THIS IS NOT FOR CHARACTER DESCRIPTIONS!", "OOC notes", html_decode(features["ooc_notes"]), MAX_FLAVOR_LEN, TRUE)
if(!isnull(msg))
features["ooc_notes"] = html_decode(msg)
features["ooc_notes"] = msg
if("hair")
var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null
@@ -2357,8 +2388,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("keybindings_capture")
var/datum/keybinding/kb = GLOB.keybindings_by_name[href_list["keybinding"]]
var/old_key = href_list["old_key"]
CaptureKeybinding(user, kb, old_key)
CaptureKeybinding(user, kb, href_list["old_key"], text2num(href_list["independent"]))
return
if("keybindings_set")
@@ -2368,13 +2398,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
ShowChoices(user)
return
var/independent = href_list["independent"]
var/clear_key = text2num(href_list["clear_key"])
var/old_key = href_list["old_key"]
if(clear_key)
if(key_bindings[old_key])
key_bindings[old_key] -= kb_name
if(!length(key_bindings[old_key]))
key_bindings -= old_key
if(independent)
modless_key_bindings -= old_key
else
if(key_bindings[old_key])
key_bindings[old_key] -= kb_name
if(!length(key_bindings[old_key]))
key_bindings -= old_key
user << browse(null, "window=capturekeypress")
save_preferences()
ShowChoices(user)
@@ -2400,15 +2435,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
full_key = "[AltMod][CtrlMod][new_key]"
else
full_key = "[AltMod][CtrlMod][ShiftMod][numpad][new_key]"
if(key_bindings[old_key])
key_bindings[old_key] -= kb_name
if(!length(key_bindings[old_key]))
key_bindings -= old_key
key_bindings[full_key] += list(kb_name)
key_bindings[full_key] = sortList(key_bindings[full_key])
user << browse(null, "window=capturekeypress")
if(independent)
modless_key_bindings -= old_key
modless_key_bindings[full_key] = kb_name
else
if(key_bindings[old_key])
key_bindings[old_key] -= kb_name
if(!length(key_bindings[old_key]))
key_bindings -= old_key
key_bindings[full_key] += list(kb_name)
key_bindings[full_key] = sortList(key_bindings[full_key])
user.client.update_movement_keys()
user << browse(null, "window=capturekeypress")
save_preferences()
if("keybindings_reset")
@@ -2418,6 +2456,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
hotkeys = (choice == "Hotkey")
key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
modless_key_bindings = list()
user.client.update_movement_keys()
if("chat_on_map")
@@ -2572,8 +2611,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
current_tab = text2num(href_list["tab"])
if(href_list["preference"] == "gear")
if(href_list["clear_loadout"])
LAZYCLEARLIST(chosen_gear)
gear_points = initial(gear_points)
chosen_gear = list()
gear_points = CONFIG_GET(number/initial_gear_points)
save_preferences()
if(href_list["select_category"])
for(var/i in GLOB.loadout_items)
@@ -2585,7 +2624,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
var/toggle = text2num(href_list["toggle_gear"])
if(!toggle && (G.type in chosen_gear))//toggling off and the item effectively is in chosen gear)
LAZYREMOVE(chosen_gear, G.type)
chosen_gear -= G.type
gear_points += initial(G.cost)
else if(toggle && (!(is_type_in_ref_list(G, chosen_gear))))
if(!is_loadout_slot_available(G.category))
@@ -2595,7 +2634,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
to_chat(user, "<span class='danger'>This is an item intended for donator use only. You are not authorized to use this item.</span>")
return
if(gear_points >= initial(G.cost))
LAZYADD(chosen_gear, G.type)
chosen_gear += G.type
gear_points -= initial(G.cost)
ShowChoices(user)
+20 -2
View File
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
#define SAVEFILE_VERSION_MAX 32
#define SAVEFILE_VERSION_MAX 33
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -194,6 +194,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(current_version < 31)
S["wing_color"] >> features["wings_color"]
S["horn_color"] >> features["horns_color"]
if(current_version < 33)
features["flavor_text"] = html_encode(features["flavor_text"])
features["silicon_flavor_text"] = html_encode(features["silicon_flavor_text"])
features["ooc_notes"] = html_encode(features["ooc_notes"])
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
@@ -262,6 +267,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
// Custom hotkeys
S["key_bindings"] >> key_bindings
S["modless_key_bindings"] >> modless_key_bindings
//citadel code
S["arousable"] >> arousable
@@ -315,7 +321,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
cit_toggles = sanitize_integer(cit_toggles, 0, 16777215, initial(cit_toggles))
auto_ooc = sanitize_integer(auto_ooc, 0, 1, initial(auto_ooc))
no_tetris_storage = sanitize_integer(no_tetris_storage, 0, 1, initial(no_tetris_storage))
key_bindings = sanitize_islist(key_bindings, list())
key_bindings = sanitize_islist(key_bindings, list())
modless_key_bindings = sanitize_islist(modless_key_bindings, list())
verify_keybindings_valid() // one of these days this will runtime and you'll be glad that i put it in a different proc so no one gets their saves wiped
@@ -333,6 +340,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(!length(binds))
key_bindings -= key
// End
// I hate copypaste but let's do it again but for modless ones
for(var/key in modless_key_bindings)
var/bindname = modless_key_bindings[key]
if(!GLOB.keybindings_by_name[bindname])
modless_key_bindings -= key
/datum/preferences/proc/save_preferences()
if(!path)
@@ -387,6 +399,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["pda_color"], pda_color)
WRITE_FILE(S["pda_skin"], pda_skin)
WRITE_FILE(S["key_bindings"], key_bindings)
WRITE_FILE(S["modless_key_bindings"], modless_key_bindings)
//citadel code
WRITE_FILE(S["screenshake"], screenshake)
@@ -553,7 +566,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
else //We have no old flavortext, default to new
S["feature_flavor_text"] >> features["flavor_text"]
S["silicon_feature_flavor_text"] >> features["silicon_flavor_text"]
S["feature_ooc_notes"] >> features["ooc_notes"]
S["silicon_flavor_text"] >> features["silicon_flavor_text"]
S["vore_flags"] >> vore_flags
S["vore_taste"] >> vore_taste
@@ -678,6 +695,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
features["flavor_text"] = copytext(features["flavor_text"], 1, MAX_FLAVOR_LEN)
features["silicon_flavor_text"] = copytext(features["silicon_flavor_text"], 1, MAX_FLAVOR_LEN)
features["ooc_notes"] = copytext(features["ooc_notes"], 1, MAX_FLAVOR_LEN)
joblessrole = sanitize_integer(joblessrole, 1, 3, initial(joblessrole))
+2 -2
View File
@@ -6,7 +6,7 @@
throwforce = 0
slot_flags = ITEM_SLOT_EARS
resistance_flags = NONE
custom_price = 250
custom_price = PRICE_BELOW_NORMAL
/obj/item/clothing/ears/earmuffs
name = "earmuffs"
@@ -31,7 +31,7 @@
slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD | ITEM_SLOT_NECK //Fluff item, put it whereever you want!
actions_types = list(/datum/action/item_action/toggle_headphones)
var/headphones_on = FALSE
custom_price = 125
custom_price = PRICE_ALMOST_CHEAP
/obj/item/clothing/ears/headphones/Initialize()
. = ..()
+2 -2
View File
@@ -10,8 +10,8 @@
permeability_coefficient = 0.05
resistance_flags = NONE
var/can_be_cut = 1
custom_price = 1200
custom_premium_price = 1200
custom_price = PRICE_EXPENSIVE
custom_premium_price = PRICE_ALMOST_ONE_GRAND
/obj/item/toy/sprayoncan
name = "spray-on insulation applicator"
@@ -10,7 +10,7 @@
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
strip_mod = 0.9
custom_price = 75
custom_price = PRICE_ALMOST_CHEAP
/obj/item/clothing/gloves/fingerless/pugilist
name = "armwraps"
+1 -1
View File
@@ -7,7 +7,7 @@
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
resistance_flags = NONE
//custom_premium_price = 350
//custom_premium_price = PRICE_EXPENSIVE
/// For storing our tackler datum so we can remove it after
var/datum/component/tackler
/// See: [/datum/component/tackler/var/stamina_cost]
-1
View File
@@ -5,7 +5,6 @@
name = "white beanie"
desc = "A stylish beanie. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their heads."
icon_state = "beanie" //Default white
custom_price = 60
/obj/item/clothing/head/beanie/black
name = "black beanie"
-6
View File
@@ -1,6 +0,0 @@
/obj/item/clothing/head/hunter
name = "hunter"
desc = "A basic hat for hunting things."
icon = 'modular_citadel/icons/obj/clothing/cit_hats.dmi'
icon_state = "hunter"
item_state = "hunter_worn"
+1 -1
View File
@@ -58,7 +58,7 @@
desc = "A reliable, blue tinted helmet reminding you that you <i>still</i> owe that engineer a beer."
icon_state = "blueshift"
item_state = "blueshift"
custom_premium_price = 750
custom_premium_price = PRICE_ABOVE_EXPENSIVE
/obj/item/clothing/head/helmet/riot
name = "riot helmet"
+1 -6
View File
@@ -375,12 +375,6 @@
icon_state = "telegram"
dog_fashion = /datum/dog_fashion/head/telegram
/obj/item/clothing/head/colour
name = "Singer cap"
desc = "A light white hat that has bands of color. Just makes you want to sing and dance!"
icon_state = "colour"
dog_fashion = /datum/dog_fashion/head/colour
/obj/item/clothing/head/christmashat
name = "red santa hat"
desc = "A red Christmas Hat! How festive!"
@@ -434,3 +428,4 @@
desc = "A symbol of discipline, honor, and lots and lots of removal of some type of skewered food."
icon_state = "russobluecamohat"
item_state = "russobluecamohat"
dynamic_hair_suffix = ""
@@ -114,6 +114,7 @@
desc = "A jack o' lantern! Believed to ward off evil spirits."
icon_state = "hardhat0_pumpkin"
item_state = "hardhat0_pumpkin"
hat_type = "pumpkin"
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
brightness_on = 2 //luminosity when on
@@ -150,6 +151,7 @@
desc = "Some fake antlers and a very fake red nose."
icon_state = "hardhat0_reindeer"
item_state = "hardhat0_reindeer"
hat_type = "reindeer"
flags_inv = 0
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
brightness_on = 1 //luminosity when on
+1 -1
View File
@@ -134,7 +134,7 @@
name = "baseball cap"
desc = "It's a robust baseball hat, this one belongs to syndicate major league team."
icon_state = "baseballsoft"
soft_type = "baseballsoft"
soft_type = "baseball"
item_state = "baseballsoft"
flags_inv = HIDEEYES|HIDEFACE
armor = list("melee" = 35, "bullet" = 35, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 90)
-2
View File
@@ -22,7 +22,6 @@
icon_state = "bluetie"
item_state = "" //no inhands
w_class = WEIGHT_CLASS_SMALL
custom_price = 60
/obj/item/clothing/neck/tie/blue
name = "blue tie"
@@ -88,7 +87,6 @@
/obj/item/clothing/neck/scarf //Default white color, same functionality as beanies.
name = "white scarf"
icon_state = "scarf"
custom_price = 60
desc = "A stylish scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks."
dog_fashion = /datum/dog_fashion/head
-1
View File
@@ -1,6 +1,5 @@
/obj/item/clothing/shoes/sneakers
dying_key = DYE_REGISTRY_SNEAKERS
custom_price = 50
/obj/item/clothing/shoes/sneakers/black
name = "black shoes"
+1 -2
View File
@@ -72,7 +72,7 @@
equip_delay_other = 50
resistance_flags = NONE
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 40, "acid" = 75)
custom_price = 600
custom_price = PRICE_ABOVE_EXPENSIVE
/obj/item/clothing/shoes/galoshes/dry
name = "absorbent galoshes"
@@ -384,7 +384,6 @@
/obj/item/clothing/shoes/cowboyboots
name = "cowboy boots"
desc = "A standard pair of brown cowboy boots."
custom_price = 60 //remember to replace these lame cosmetics with tg's YEEEEHAW counterparts.
icon_state = "cowboyboots"
/obj/item/clothing/shoes/cowboyboots/black
@@ -469,7 +469,7 @@ Contains:
desc = "Voices echo from the hardsuit, driving the user insane. This one is pretty battle-worn, but still fearsome."
armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
slowdown = 0.8
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor/old
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
charges = 6
/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
+1 -1
View File
@@ -49,7 +49,7 @@
desc = "A large, yet comfortable piece of armor, protecting you from some threats."
icon_state = "blueshift"
item_state = "blueshift"
custom_premium_price = 750
custom_premium_price = PRICE_ABOVE_EXPENSIVE
/obj/item/clothing/suit/armor/hos
name = "armored greatcoat"
@@ -87,7 +87,6 @@
icon_state = "overalls"
item_state = "lb_suit"
can_adjust = FALSE
custom_price = 60
/obj/item/clothing/under/misc/assistantformal
name = "assistant's formal uniform"
+1 -2
View File
@@ -3,7 +3,6 @@
body_parts_covered = GROIN|LEGS
fitted = NO_FEMALE_UNIFORM
can_adjust = FALSE
custom_price = 60
mutantrace_variation = STYLE_DIGITIGRADE //how do they show up on taurs otherwise?
/obj/item/clothing/under/pants/classicjeans
@@ -15,7 +14,7 @@
name = "Must Hang jeans"
desc = "Made in the finest space jeans factory this side of Alpha Centauri."
icon_state = "jeansmustang"
custom_price = 180
custom_price = PRICE_ABOVE_NORMAL
/obj/item/clothing/under/pants/blackjeans
name = "black jeans"
@@ -17,7 +17,6 @@
body_parts_covered = CHEST|GROIN|ARMS
fitted = FEMALE_UNIFORM_TOP
can_adjust = FALSE
custom_price = 60
/obj/item/clothing/under/dress/skirt/red
name = "red skirt"
@@ -27,7 +26,6 @@
body_parts_covered = CHEST|GROIN|ARMS
fitted = FEMALE_UNIFORM_TOP
can_adjust = FALSE
custom_price = 60
/obj/item/clothing/under/dress/skirt/purple
name = "purple skirt"
@@ -37,7 +35,6 @@
body_parts_covered = CHEST|GROIN|ARMS
fitted = FEMALE_UNIFORM_TOP
can_adjust = FALSE
custom_price = 60
/obj/item/clothing/under/dress/sundress
name = "sundress"
@@ -151,7 +148,6 @@
fitted = FEMALE_UNIFORM_TOP
can_adjust = TRUE
alt_covers_chest = TRUE
custom_price = 60
/obj/item/clothing/under/dress/skirt/plaid/blue
name = "blue plaid skirt"
+42
View File
@@ -97,6 +97,48 @@
icon_state = "trek_ds9_medsci"
item_state = "b_suit"
//Orvilike (Orville-inspired clothing with TOS-like color code)
/obj/item/clothing/under/trek/command/orv
desc = "An uniform worn by command officers since 2420s."
icon_state = "orv_com"
/obj/item/clothing/under/trek/engsec/orv
desc = "An uniform worn by operations officers since 2420s."
icon_state = "orv_ops"
/obj/item/clothing/under/trek/medsci/orv
desc = "An uniform worn by medsci officers since 2420s."
icon_state = "orv_medsci"
//Orvilike Extra (Ditto, but expands it for Civilian department with SS13 colors and gives specified command uniform)
//honestly no idea why i added specified comm. uniforms but w/e
/obj/item/clothing/under/trek/command/orv/captain
name = "captain uniform"
desc = "An uniform worn by captains since 2550s."
icon_state = "orv_com_capt"
/obj/item/clothing/under/trek/command/orv/engsec
name = "operations command uniform"
desc = "An uniform worn by operations command officers since 2550s."
icon_state = "orv_com_ops"
/obj/item/clothing/under/trek/command/orv/medsci
name = "medsci command uniform"
desc = "An uniform worn by medsci command officers since 2550s."
icon_state = "orv_com_medsci"
/obj/item/clothing/under/trek/orv
name = "adjutant uniform"
desc = "An uniform worn by adjutants <i>(assistants)</i> since 2550s."
icon_state = "orv_ass"
item_state = "gy_suit"
/obj/item/clothing/under/trek/orv/service
name = "service uniform"
desc = "An uniform worn by service officers since 2550s."
icon_state = "orv_srv"
item_state = "g_suit"
//The Motion Picture
/obj/item/clothing/under/trek/fedutil
name = "federation utility uniform"
+3 -2
View File
@@ -330,8 +330,8 @@
if(!override)
qdel(src)
/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user)
var/damage_dealt = I.force
/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
var/damage_dealt = I.force * damage_multiplier
if(I.get_sharpness())
damage_dealt *= 4
if(I.damtype == BURN)
@@ -383,6 +383,7 @@
/datum/spacevine_controller/New(turf/location, list/muts, potency, production, datum/round_event/event = null)
vines = list()
growth_queue = list()
spawn_spacevine_piece(location, null, muts)
START_PROCESSING(SSobj, src)
vine_mutations_list = list()
init_subtypes(/datum/spacevine_mutation/, vine_mutations_list)
@@ -268,12 +268,15 @@
/obj/item/reagent_containers/food/drinks/ice
name = "ice cup"
desc = "Careful, cold ice, do not chew."
custom_price = 15
custom_price = PRICE_CHEAP_AS_FREE
icon_state = "coffee"
list_reagents = list(/datum/reagent/consumable/ice = 30)
spillable = TRUE
isGlass = FALSE
/obj/item/reagent_containers/food/drinks/ice/sustanance
custom_price = PRICE_FREE
/obj/item/reagent_containers/food/drinks/mug/ // parent type is literally just so empty mug sprites are a thing
name = "mug"
desc = "A drink served in a classy mug."
@@ -303,7 +306,7 @@
list_reagents = list(/datum/reagent/consumable/hot_coco = 30, /datum/reagent/consumable/sugar = 5)
foodtype = SUGAR
resistance_flags = FREEZE_PROOF
custom_price = 120
custom_price = PRICE_ALMOST_CHEAP
/obj/item/reagent_containers/food/drinks/dry_ramen
name = "cup ramen"
@@ -312,7 +315,7 @@
list_reagents = list(/datum/reagent/consumable/dry_ramen = 30)
foodtype = GRAIN
isGlass = FALSE
custom_price = 95
custom_price = PRICE_PRETTY_CHEAP
/obj/item/reagent_containers/food/drinks/beer
name = "space beer"
@@ -320,7 +323,7 @@
icon_state = "beer"
list_reagents = list(/datum/reagent/consumable/ethanol/beer = 30)
foodtype = GRAIN | ALCOHOL
custom_price = 60
custom_price = PRICE_PRETTY_CHEAP
/obj/item/reagent_containers/food/drinks/beer/light
name = "Carp Lite"
@@ -421,7 +424,7 @@
custom_materials = list(/datum/material/iron=250)
volume = 60
isGlass = FALSE
custom_price = 200
custom_price = PRICE_ABOVE_NORMAL
/obj/item/reagent_containers/food/drinks/flask/gold
name = "captain's flask"
@@ -452,7 +455,7 @@
reagent_flags = NONE
spillable = FALSE
isGlass = FALSE
custom_price = 45
custom_price = PRICE_CHEAP_AS_FREE
/obj/item/reagent_containers/food/drinks/soda_cans/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is trying to eat \the [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>")
@@ -346,7 +346,7 @@
/obj/item/reagent_containers/food/drinks/bottle/applejack
name = "Buckin' Bronco's Applejack"
desc = "Kicks like a horse, tastes like an apple!"
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "applejack_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/applejack = 100)
foodtype = FRUIT
@@ -357,7 +357,6 @@
/obj/item/reagent_containers/food/drinks/bottle/champagne
name = "Eau d' Dandy Brut Champagne"
desc = "Finely sourced from only the most pretentious French vineyards."
custom_premium_price = 250
icon_state = "champagne_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/champagne = 100)
@@ -376,7 +375,7 @@
/obj/item/reagent_containers/food/drinks/bottle/trappist
name = "Mont de Requin Trappistes Bleu"
desc = "Brewed in space-Belgium. Fancy!"
custom_premium_price = 170
custom_premium_price = PRICE_ABOVE_NORMAL
icon_state = "trappistbottle"
volume = 50
list_reagents = list(/datum/reagent/consumable/ethanol/trappist = 50)
@@ -389,7 +388,7 @@
/obj/item/reagent_containers/food/drinks/bottle/orangejuice
name = "orange juice"
desc = "Full of vitamins and deliciousness!"
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "orangejuice"
item_state = "carton"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
@@ -411,7 +410,7 @@
/obj/item/reagent_containers/food/drinks/bottle/cream
name = "milk cream"
desc = "It's cream. Made from milk. What else did you think you'd find in there?"
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "cream"
item_state = "carton"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
@@ -423,7 +422,7 @@
/obj/item/reagent_containers/food/drinks/bottle/tomatojuice
name = "tomato juice"
desc = "Well, at least it LOOKS like tomato juice. You can't tell with all that redness."
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "tomatojuice"
item_state = "carton"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
@@ -435,7 +434,7 @@
/obj/item/reagent_containers/food/drinks/bottle/limejuice
name = "lime juice"
desc = "Sweet-sour goodness."
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "limejuice"
item_state = "carton"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
@@ -469,7 +468,7 @@
/obj/item/reagent_containers/food/drinks/bottle/menthol
name = "menthol"
desc = "Tastes naturally minty, and imparts a very mild numbing sensation."
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "mentholbox"
item_state = "carton"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
@@ -480,7 +479,7 @@
/obj/item/reagent_containers/food/drinks/bottle/grenadine
name = "Jester Grenadine"
desc = "Contains 0% real cherries!"
custom_price = 100
custom_price = PRICE_CHEAP
icon_state = "grenadine"
isGlass = TRUE
list_reagents = list(/datum/reagent/consumable/grenadine = 100)
@@ -11,7 +11,7 @@
spillable = TRUE
resistance_flags = ACID_PROOF
obj_flags = UNIQUE_RENAME
custom_price = 25
custom_price = PRICE_REALLY_CHEAP
/obj/item/reagent_containers/food/drinks/drinkingglass/on_reagent_change(changetype)
cut_overlays()
@@ -47,7 +47,7 @@
possible_transfer_amounts = list()
volume = 15
custom_materials = list(/datum/material/glass=100)
custom_price = 20
custom_price = PRICE_CHEAP_AS_FREE
/obj/item/reagent_containers/food/drinks/drinkingglass/shotglass/on_reagent_change(changetype)
cut_overlays()
@@ -54,6 +54,15 @@
tastes = list("fish" = 1, "chips" = 1)
foodtype = MEAT | VEGETABLES | FRIED
/obj/item/reagent_containers/food/snacks/fishfry
name = "fish fry"
desc = "All that and no bag of chips..."
icon_state = "fish_fry"
list_reagents = list (/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 3)
filling_color = "#ee7676"
tastes = list("fish" = 1, "pan seared vegtables" = 1)
foodtype = MEAT | VEGETABLES | FRIED
/obj/item/reagent_containers/food/snacks/sushi_basic
name = "funa hosomaki"
desc = "A small cylindrical kudzu skin, filled with rice and fish."
@@ -12,6 +12,17 @@
tastes = list("cheese" = 1)
foodtype = DAIRY
/obj/item/reagent_containers/food/snacks/royalcheese
name = "royal cheese"
desc = "Ascend the throne. Consume the wheel. Feel the POWER."
icon_state = "royalcheese"
list_reagents = list(/datum/reagent/consumable/nutriment = 15, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/gold = 20, /datum/reagent/toxin/mutagen = 5)
w_class = WEIGHT_CLASS_BULKY
tastes = list("cheese" = 4, "royalty" = 1)
foodtype = DAIRY
/obj/item/reagent_containers/food/snacks/cheesewedge
name = "cheese wedge"
desc = "A wedge of delicious Cheddar. The cheese wheel it was cut from can't have gone far."
@@ -144,6 +144,15 @@
is_decorated = TRUE
filling_color = "#879630"
/obj/item/reagent_containers/food/snacks/donut/laugh
name = "sweet pea donut"
desc = "Goes great with a glass of Bastion Burbon!"
icon_state = "donut_laugh"
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
tastes = list("donut" = 3, "fizzy tutti frutti" = 1,)
is_decorated = TRUE
filling_color = "#803280"
//////////////////////JELLY DONUTS/////////////////////////
/obj/item/reagent_containers/food/snacks/donut/jelly
@@ -234,6 +243,15 @@
is_decorated = TRUE
filling_color = "#879630"
/obj/item/reagent_containers/food/snacks/donut/jelly/laugh
name = "sweet pea jelly donut"
desc = "Goes great with a glass of Bastion Burbon!"
icon_state = "jelly_laugh"
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
tastes = list("jelly" = 3, "donut" = 1, "fizzy tutti frutti" = 1)
is_decorated = TRUE
filling_color = "#803280"
//////////////////////////SLIME DONUTS/////////////////////////
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly
@@ -315,6 +333,15 @@
is_decorated = TRUE
filling_color = "#879630"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/laugh
name = "sweet pea jelly donut"
desc = "Goes great with a glass of Bastion Burbon!"
icon_state = "jelly_laugh"
bonus_reagents = list(/datum/reagent/consumable/laughter = 3)
tastes = list("jelly" = 3, "donut" = 1, "fizzy tutti frutti" = 1)
is_decorated = TRUE
filling_color = "#803280"
/obj/item/reagent_containers/food/snacks/donut/glaze
name = "glazed donut"
desc = "A sugar glazed donut."
@@ -124,4 +124,22 @@
trash = /obj/item/kitchen/knife
bonus_reagents = list(/datum/reagent/medicine/earthsblood = 1, /datum/reagent/iron = 4)
tastes = list("iron" = 1, "conspiracy" = 1)
foodtype = VEGETABLES
foodtype = VEGETABLES
/obj/item/reagent_containers/food/snacks/salad/edensalad
name = "\improper Salad of Eden"
desc = "A salad brimming with untapped potential."
icon_state = "eden_salad"
trash = /obj/item/reagent_containers/glass/bowl
list_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/medicine/earthsblood = 3, /datum/reagent/medicine/omnizine = 5, /datum/reagent/drug/happiness = 2)
tastes = list("hope" = 1)
foodtype = VEGETABLES
/obj/item/reagent_containers/food/snacks/salad/gumbo
name = "black eyed gumbo"
desc = "A spicy and savory meat and rice dish."
icon_state = "gumbo"
trash = /obj/item/reagent_containers/glass/bowl
list_reagents = list(/datum/reagent/consumable/capsaicin = 2, /datum/reagent/consumable/nutriment/vitamin = 3, /datum/reagent/consumable/nutriment = 5)
tastes = list("building heat" = 2, "savory meat and vegtables" = 1)
foodtype = GRAIN | MEAT | VEGETABLES
@@ -262,3 +262,13 @@
tastes = list("bungo" = 2, "hot curry" = 4, "tropical sweetness" = 1)
filling_color = "#E6A625"
foodtype = VEGETABLES | FRUIT | DAIRY
/obj/item/reagent_containers/food/snacks/soup/peasoup
name = "pea soup"
desc = "A humble split pea soup."
icon_state = "peasoup"
bonus_reagents = list (/datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/medicine/oculine = 2)
list_reagents = list (/datum/reagent/consumable/nutriment = 8)
tastes = list("creamy peas"= 2, "parsnip" = 1)
filling_color = "#9dc530"
foodtype = VEGETABLES
@@ -41,7 +41,6 @@
filling_color = "#FFD700"
tastes = list("salt" = 1, "crisps" = 1)
foodtype = JUNKFOOD | FRIED
custom_price = 90
/obj/item/reagent_containers/food/snacks/no_raisin
name = "4no raisins"
@@ -69,7 +68,7 @@
junkiness = 25
filling_color = "#FFD700"
foodtype = JUNKFOOD | GRAIN | SUGAR
custom_price = 30
custom_price = PRICE_CHEAP_AS_FREE
/obj/item/reagent_containers/food/snacks/cheesiehonkers
name = "cheesie honkers"
@@ -81,7 +80,6 @@
filling_color = "#FFD700"
tastes = list("cheese" = 5, "crisps" = 2)
foodtype = JUNKFOOD | DAIRY | SUGAR
custom_price = 45
/obj/item/reagent_containers/food/snacks/syndicake
name = "syndi-cakes"
@@ -92,3 +90,4 @@
filling_color = "#F5F5DC"
tastes = list("sweetness" = 3, "cake" = 1)
foodtype = GRAIN | FRUIT | VEGETABLES
custom_price = PRICE_CHEAP
@@ -121,6 +121,15 @@ datum/crafting_recipe/food/donut/meat
)
result = /obj/item/reagent_containers/food/snacks/donut/matcha
/datum/crafting_recipe/food/donut/laugh
name = "Sweet Pea Donut"
reqs = list(
/datum/reagent/consumable/laughsyrup = 3,
/obj/item/reagent_containers/food/snacks/donut/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/donut/laugh
////////////////////////////////////////////////////JELLY DONUTS///////////////////////////////////////////////////////
/datum/crafting_recipe/food/donut/jelly/apple
@@ -187,6 +196,14 @@ datum/crafting_recipe/food/donut/meat
)
result = /obj/item/reagent_containers/food/snacks/donut/jelly/trumpet
/datum/crafting_recipe/food/donut/jelly/laugh
name = "Sweet Pea Jelly Donut"
reqs = list(
/datum/reagent/consumable/laughsyrup = 3,
/obj/item/reagent_containers/food/snacks/donut/jelly/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/donut/jelly/laugh
////////////////////////////////////////////////////SLIME DONUTS///////////////////////////////////////////////////////
/datum/crafting_recipe/food/donut/slimejelly/apple
@@ -253,3 +270,11 @@ datum/crafting_recipe/food/donut/meat
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/matcha
/datum/crafting_recipe/food/donut/slimejelly/laugh
name = "Sweet Pea Jelly Donut"
reqs = list(
/datum/reagent/consumable/laughsyrup = 3,
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/laugh
@@ -171,3 +171,14 @@
)
result = /obj/item/reagent_containers/food/snacks/salad/ricepork
subcategory = CAT_MEAT
/datum/crafting_recipe/food/gumbo
name = "Black eyed gumbo"
reqs = list(
/obj/item/reagent_containers/food/snacks/salad/boiledrice = 1,
/obj/item/reagent_containers/food/snacks/grown/peas = 1,
/obj/item/reagent_containers/food/snacks/grown/chili = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet = 1
)
result = /obj/item/reagent_containers/food/snacks/salad/gumbo
subcategory = CAT_MEAT
@@ -194,3 +194,14 @@
)
result = /mob/living/simple_animal/hostile/bear/butter
subcategory = CAT_MISCFOOD
/datum/crafting_recipe/food/royalcheese
name = "Royal Cheese"
reqs = list(
/obj/item/reagent_containers/food/snacks/store/cheesewheel = 1,
/obj/item/clothing/head/crown = 1,
/datum/reagent/medicine/strange_reagent = 5,
/datum/reagent/toxin/mutagen = 5
)
result = /obj/item/reagent_containers/food/snacks/royalcheese
subcategory = CAT_MISCFOOD
@@ -93,4 +93,17 @@
/obj/item/reagent_containers/food/snacks/grown/cabbage = 1
)
result = /obj/item/reagent_containers/food/snacks/salad/caesar
subcategory = CAT_SALAD
subcategory = CAT_SALAD
/datum/crafting_recipe/food/edensalad
name = "Salad of Eden"
reqs = list(
/obj/item/reagent_containers/glass/bowl =1,
/obj/item/reagent_containers/food/snacks/grown/ambrosia/vulgaris = 1,
/obj/item/reagent_containers/food/snacks/grown/ambrosia/deus = 1,
/obj/item/reagent_containers/food/snacks/grown/ambrosia/gaia = 1,
/obj/item/reagent_containers/food/snacks/grown/peace = 1
)
result = /obj/item/reagent_containers/food/snacks/salad/edensalad
subcategory = CAT_SALAD
@@ -135,4 +135,14 @@
/obj/item/reagent_containers/food/snacks/carpmeat = 1
)
result = /obj/item/reagent_containers/food/snacks/fishandchips
subcategory = CAT_SEAFOOD
subcategory = CAT_SEAFOOD
/datum/crafting_recipe/food/fishfry
name = "Fish fry"
reqs = list(
/obj/item/reagent_containers/food/snacks/grown/corn = 1,
/obj/item/reagent_containers/food/snacks/grown/peas =1,
/obj/item/reagent_containers/food/snacks/carpmeat = 1
)
result = /obj/item/reagent_containers/food/snacks/fishfry
subcategory = CAT_SEAFOOD
@@ -255,4 +255,16 @@
/obj/item/reagent_containers/glass/bowl = 1
)
result= /obj/item/reagent_containers/food/snacks/soup/wish
subcategory = CAT_SOUP
subcategory = CAT_SOUP
/datum/crafting_recipe/food/peasoup
name = "Pea soup"
reqs = list(
/datum/reagent/water = 10,
/obj/item/reagent_containers/food/snacks/grown/peas = 2,
/obj/item/reagent_containers/food/snacks/grown/parsnip = 1,
/obj/item/reagent_containers/food/snacks/grown/carrot = 1
)
result = /obj/item/reagent_containers/food/snacks/soup/peasoup
subcategory = CAT_SOUP
+95 -1
View File
@@ -189,6 +189,14 @@
begin_day = 22
begin_month = APRIL
/datum/holiday/lesbianvisibility
name = "Lesbian Visibility Day"
begin_day = 26
begin_month = APRIL
/datum/holiday/lesbianvisibility/greet()
return "Today is Lesbian Visibility Day!"
/datum/holiday/labor
name = "Labor Day"
begin_day = 1
@@ -292,6 +300,14 @@
/datum/holiday/programmers/getStationPrefix()
return pick("span>","DEBUG: ","null","/list","EVENT PREFIX NOT FOUND") //Portability
/datum/holiday/bivisibility
name = "Bisexual Visibility Day"
begin_day = 23
begin_month = SEPTEMBER
/datum/holiday/bivisibility/greet()
return "Today is Bisexual Visibility Day!"
/datum/holiday/questions
name = "Stupid-Questions Day"
begin_day = 28
@@ -314,12 +330,25 @@
begin_month = OCTOBER
drone_hat = /obj/item/clothing/head/papersack/smiley
/datum/holiday/comingoutday
name = "Coming Out Day"
begin_day = 11
begin_month = OCTOBER
/datum/holiday/boss
name = "Boss' Day"
begin_day = 16
begin_month = OCTOBER
drone_hat = /obj/item/clothing/head/that
/datum/holiday/intersexawareness
name = "Intersex Awareness Day"
begin_day = 26
begin_month = OCTOBER
/datum/holiday/intersexawareness/greet()
return "Today is Intersex Awareness Day! It has been [text2num(time2text(world.timeofday, "YYYY")) - 1996] years since the first public protest speaking out against the human rights issues faced by intersex people."
/datum/holiday/halloween
name = HALLOWEEN
begin_day = 28
@@ -359,6 +388,23 @@
begin_month = NOVEMBER
drone_hat = /obj/item/reagent_containers/food/snacks/grown/moonflower
/datum/holiday/transawareness
name = "Transgender Awareness Week"
begin_day = 13
begin_month = NOVEMBER
end_day = 19
/datum/holiday/transawareness/greet()
return "This week is Transgender Awareness Week!"
/datum/holiday/transremembrance
name = "Transgender Day of Remembrance"
begin_day = 20
begin_month = NOVEMBER
/datum/holiday/transremembrance/greet()
return "Today is the Transgender Day of Remembrance."
/datum/holiday/hello
name = "Saying-'Hello' Day"
begin_day = 21
@@ -397,6 +443,26 @@
begin_month = OCTOBER
begin_weekday = MONDAY
/datum/holiday/aceawareness
name = "Asexual Awareness Week"
begin_month = OCTOBER
/datum/holiday/aceawareness/greet()
return "This week is Asexual Awareness Week!"
/datum/holiday/aceawareness/shouldCelebrate(dd, mm, yy, ww, ddd) //Ace awareness week falls on the last full week of October.
if(mm != begin_month)
return FALSE //it's not even the right month
var/daypointer = world.timeofday - ((WEEKDAY2NUM(ddd) - 1) * 24 HOURS)
if(text2num(time2text(daypointer, "MM")) != mm)
return FALSE //it's the beginning of the month and it isn't even a full week
daypointer += (24 HOURS * 6)
if(text2num(time2text(daypointer, "MM")) != mm)
return FALSE //this is the end of the month, and it is not a full week.
daypointer += (24 HOURS * 7)
if(text2num(time2text(daypointer, "MM")) != mm)
return TRUE //the end of next week falls on a different month, meaning that the current week is the last full week
/datum/holiday/mother
name = "Mother's Day"
begin_week = 2
@@ -412,11 +478,39 @@
begin_month = JUNE
begin_weekday = SUNDAY
/datum/holiday/pride
name = PRIDE_MONTH
begin_day = 1
begin_month = JUNE
end_day = 30
/datum/holiday/pride/getStationPrefix()
return pick("Pride", "Gay", "Bi", "Trans", "Lesbian", "Ace", "Aro", "Agender", pick("Enby", "Enbie"), "Pan", "Intersex", "Demi", "Poly", "Closeted", "Genderfluid")
/datum/holiday/stonewall
name = "Stonewall Riots Anniversary"
begin_day = 28
begin_month = JUNE
/datum/holiday/stonewall/greet() //Not gonna lie, I was fairly tempted to make this use the IC year instead of the IRL year, but I was worried that it would have caused too much confusion.
return "Today marks the [text2num(time2text(world.timeofday, "YYYY")) - 1969]\th anniversary of the riots at the Stonewall Inn!"
/datum/holiday/moth
name = "Moth Week"
begin_month = JULY
/datum/holiday/moth/shouldCelebrate(dd, mm, yy, ww, ddd) //National Moth Week falls on the last full week of July
return mm == JULY && (ww == 4 || (ww == 5 && ddd == SUNDAY))
if(mm != begin_month)
return FALSE //it's not even the right month
var/daypointer = world.timeofday - ((WEEKDAY2NUM(ddd) - 1) * 24 HOURS)
if(text2num(time2text(daypointer, "MM")) != mm)
return FALSE //it's the beginning of the month and it isn't even a full week
daypointer += (24 HOURS * 6)
if(text2num(time2text(daypointer, "MM")) != mm)
return FALSE //this is the end of the month, and it is not a full week.
daypointer += (24 HOURS * 7)
if(text2num(time2text(daypointer, "MM")) != mm)
return TRUE //the end of next week falls on a different month, meaning that the current week is the last full week
/datum/holiday/moth/getStationPrefix()
return pick("Mothball","Lepidopteran","Lightbulb","Moth","Giant Atlas","Twin-spotted Sphynx","Madagascan Sunset","Luna","Death's Head","Emperor Gum","Polyphenus","Oleander Hawk","Io","Rosy Maple","Cecropia","Noctuidae","Giant Leopard","Dysphania Militaris","Garden Tiger")
-3
View File
@@ -40,9 +40,6 @@
return ..()
return ..()
/obj/item/holo/esword/attack(target as mob, mob/user as mob)
..()
/obj/item/holo/esword/Initialize()
. = ..()
saber_color = pick("red","blue","green","purple")
+8 -3
View File
@@ -205,6 +205,11 @@
popup.set_content(dat)
popup.open()
/obj/machinery/biogenerator/AltClick(mob/living/user)
. = ..()
if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
detach(user)
/obj/machinery/biogenerator/proc/activate()
if (usr.stat != CONSCIOUS)
return
@@ -293,9 +298,9 @@
update_icon()
return .
/obj/machinery/biogenerator/proc/detach()
/obj/machinery/biogenerator/proc/detach(mob/living/user)
if(beaker)
beaker.forceMove(drop_location())
user.put_in_hands(beaker)
beaker = null
update_icon()
@@ -310,7 +315,7 @@
updateUsrDialog()
else if(href_list["detach"])
detach()
detach(usr)
updateUsrDialog()
else if(href_list["create"])
+1 -1
View File
@@ -69,7 +69,7 @@
/obj/item/instrument/dropped(mob/user)
. = ..()
if((loc != user) && (user.machine == src))
user.set_machine(null)
user.unset_machine()
/obj/item/instrument/interact(mob/user)
ui_interact(user)
@@ -24,6 +24,7 @@
/obj/structure/musician/ui_interact(mob/user)
. = ..()
user.set_machine(src)
song.ui_interact(user)
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
-2
View File
@@ -223,8 +223,6 @@
/// Updates the window for our user. Override in subtypes.
/datum/song/proc/updateDialog(mob/user = usr)
if(user.machine != src)
return
ui_interact(user)
/datum/song/process(wait)
+4 -7
View File
@@ -35,12 +35,9 @@ Assistant
..()
var/suited = !preference_source || preference_source.prefs.jumpsuit_style == PREF_SUIT
if (CONFIG_GET(flag/grey_assistants))
if(suited)
uniform = /obj/item/clothing/under/color/grey
else
uniform = /obj/item/clothing/under/color/jumpskirt/grey
uniform = suited ? /obj/item/clothing/under/color/grey : /obj/item/clothing/under/color/jumpskirt/grey
else
if(suited)
uniform = /obj/item/clothing/under/color/random
if(SSevents.holidays && SSevents.holidays[PRIDE_MONTH])
uniform = suited ? /obj/item/clothing/under/color/rainbow : /obj/item/clothing/under/color/jumpskirt/rainbow
else
uniform = /obj/item/clothing/under/color/jumpskirt/random
uniform = suited ? /obj/item/clothing/under/color/random : /obj/item/clothing/under/color/jumpskirt/random
@@ -59,6 +59,11 @@
else
full_key = "[AltMod][CtrlMod][ShiftMod][_key]"
var/keycount = 0
if(prefs.modless_key_bindings[_key])
var/datum/keybinding/kb = GLOB.keybindings_by_name[prefs.modless_key_bindings[_key]]
if(kb.can_use(src))
kb.down(src)
keycount++
for(var/kb_name in prefs.key_bindings[full_key])
keycount++
var/datum/keybinding/kb = GLOB.keybindings_by_name[kb_name]
@@ -8,6 +8,7 @@
#define CATEGORY_MISC "MISC"
#define CATEGORY_MOVEMENT "MOVEMENT"
#define CATEGORY_TARGETING "TARGETING"
#define CATEGORY_COMBAT "COMBAT"
#define WEIGHT_HIGHEST 0
#define WEIGHT_ADMIN 10
@@ -0,0 +1,38 @@
/datum/keybinding/living/toggle_combat_mode
hotkey_keys = list("C")
name = "toggle_combat_mode"
full_name = "Toggle combat mode"
category = CATEGORY_COMBAT
description = "Toggles whether or not you're in combat mode."
/datum/keybinding/living/toggle_combat_mode/down(client/user)
SEND_SIGNAL(user.mob, COMSIG_TOGGLE_COMBAT_MODE)
return TRUE
/datum/keybinding/living/active_block
hotkey_keys = list("Northwest", "F") // HOME
name = "active_block"
full_name = "Block (Hold)"
category = CATEGORY_COMBAT
description = "Hold down to actively block with your currently in-hand object."
/datum/keybinding/living/active_block/down(client/user)
var/mob/living/L = user.mob
L.keybind_start_active_blocking()
return TRUE
/datum/keybinding/living/active_block/up(client/user)
var/mob/living/L = user.mob
L.keybind_stop_active_blocking()
/datum/keybinding/living/active_parry
hotkey_keys = list("Insert", "G")
name = "active_parry"
full_name = "Parry"
category = CATEGORY_COMBAT
description = "Press to initiate a parry sequence with your currently in-hand object."
/datum/keybinding/living/active_parry/down(client/user)
var/mob/living/L = user.mob
L.keybind_parry()
return TRUE
@@ -16,16 +16,6 @@
L.resist()
return TRUE
/datum/keybinding/living/toggle_combat_mode
hotkey_keys = list("C")
name = "toggle_combat_mode"
full_name = "Toggle combat mode"
description = "Toggles whether or not you're in combat mode."
/datum/keybinding/living/toggle_combat_mode/down(client/user)
SEND_SIGNAL(user.mob, COMSIG_TOGGLE_COMBAT_MODE)
return TRUE
/datum/keybinding/living/toggle_resting
hotkey_keys = list("V")
name = "toggle_resting"
+2 -2
View File
@@ -17,7 +17,7 @@
return TRUE
/datum/keybinding/mob/cycle_intent_right
hotkey_keys = list("Northwest", "F") // HOME
hotkey_keys = list("Unbound")
name = "cycle_intent_right"
full_name = "Cycle Action Intent Right"
description = ""
@@ -28,7 +28,7 @@
return TRUE
/datum/keybinding/mob/cycle_intent_left
hotkey_keys = list("Insert", "G")
hotkey_keys = list("Unbound")
name = "cycle_intent_left"
full_name = "Cycle Action Intent Left"
description = ""
@@ -11,6 +11,7 @@
plane = EMISSIVE_BLOCKER_PLANE
layer = EMISSIVE_BLOCKER_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
rad_flags = RAD_NO_CONTAMINATE | RAD_PROTECT_CONTENTS
//Why?
//render_targets copy the transform of the target as well, but vis_contents also applies the transform
//to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
+5 -19
View File
@@ -9,7 +9,7 @@
var/turf/pixel_turf // The turf the top_atom appears to over.
var/light_power // Intensity of the emitter light.
var/light_range // The range of the emitted light.
var/light_color // The colour of the light, string, decomposed by parse_light_color()
var/light_color // The colour of the light, string, decomposed by PARSE_LIGHT_COLOR()
// Variables for keeping track of the colour.
var/lum_r
@@ -48,12 +48,10 @@
light_range = source_atom.light_range
light_color = source_atom.light_color
parse_light_color()
PARSE_LIGHT_COLOR(src)
update()
return ..()
/datum/light_source/Destroy(force)
remove_lum()
if (source_atom)
@@ -99,17 +97,6 @@
/datum/light_source/proc/vis_update()
EFFECT_UPDATE(LIGHTING_VIS_UPDATE)
// Decompile the hexadecimal colour into lumcounts of each perspective.
/datum/light_source/proc/parse_light_color()
if (light_color)
lum_r = GetRedPart (light_color) / 255
lum_g = GetGreenPart (light_color) / 255
lum_b = GetBluePart (light_color) / 255
else
lum_r = 1
lum_g = 1
lum_b = 1
// Macro that applies light to a new corner.
// It is a macro in the interest of speed, yet not having to copy paste it.
// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
@@ -224,7 +211,7 @@
if (source_atom.light_color != light_color)
light_color = source_atom.light_color
parse_light_color()
PARSE_LIGHT_COLOR(src)
update = TRUE
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
@@ -241,17 +228,16 @@
var/list/turf/turfs = list()
var/thing
var/turf/T
if (source_turf)
var/oldlum = source_turf.luminosity
source_turf.luminosity = CEILING(light_range, 1)
for(T in view(CEILING(light_range, 1), source_turf))
turfs += T
if(!IS_DYNAMIC_LIGHTING(T) && !T.light_sources)
if((!IS_DYNAMIC_LIGHTING(T) && !T.light_sources) || T.has_opaque_atom )
continue
if(!T.lighting_corners_initialised)
T.generate_missing_corners()
if(T.has_opaque_atom)
continue
corners[T.lc_topright] = 0
corners[T.lc_bottomright] = 0
corners[T.lc_bottomleft] = 0
@@ -176,9 +176,6 @@
SSticker.queue_delay = 4
qdel(src)
if(!ready && href_list["preference"])
if(client)
client.prefs.process_link(src, href_list)
else if(!href_list["late_join"])
new_player_panel()
@@ -582,6 +579,12 @@
qdel(src)
/mob/dead/new_player/proc/ViewManifest()
if(!client)
return
if(world.time < client.crew_manifest_delay)
return
client.crew_manifest_delay = world.time + (1 SECONDS)
var/dat = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'></head><body>"
dat += "<h4>Crew Manifest</h4>"
dat += GLOB.data_core.get_manifest(OOC = 1)
@@ -189,6 +189,26 @@
name = "Coffee House"
icon_state = "hair_coffeehouse"
/datum/sprite_accessory/hair/cornrows1
name = "Cornrows"
icon_state = "hair_cornrows"
/datum/sprite_accessory/hair/cornrows2
name = "Cornrows 2"
icon_state = "hair_cornrows2"
/datum/sprite_accessory/hair/cornrowbun
name = "Cornrow Bun"
icon_state = "hair_cornrowbun"
/datum/sprite_accessory/hair/cornrowbraid
name = "Cornrow Braid"
icon_state = "hair_cornrowbraid"
/datum/sprite_accessory/hair/cornrowdualtail
name = "Cornrow Tail"
icon_state = "hair_cornrowtail"
/datum/sprite_accessory/hair/country
name = "Country"
icon_state = "hair_country"
@@ -717,6 +717,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "View Crew Manifest"
set category = "Ghost"
if(!client)
return
if(world.time < client.crew_manifest_delay)
return
client.crew_manifest_delay = world.time + (1 SECONDS)
var/dat
dat += "<h4>Crew Manifest</h4>"
dat += GLOB.data_core.get_manifest()
+1
View File
@@ -333,6 +333,7 @@
I.moveToNullspace()
else
I.forceMove(newloc)
on_item_dropped(I)
if(I.dropped(src) == ITEM_RELOCATED_BY_DROPPED)
return FALSE
return TRUE
@@ -77,20 +77,18 @@
return TRUE
/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0)
. = ..()
if(leaping)
return -32
else if(custom_pixel_y_offset)
return custom_pixel_y_offset
else
return initial(pixel_y)
. -= 32
if(custom_pixel_y_offset)
. += custom_pixel_y_offset
/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0)
. = ..()
if(leaping)
return -32
else if(custom_pixel_x_offset)
return custom_pixel_x_offset
else
return initial(pixel_x)
. -= 32
if(custom_pixel_x_offset)
. += custom_pixel_x_offset
/mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones)
return 0.8
+2 -3
View File
@@ -432,10 +432,9 @@
return
/mob/living/carbon/get_standard_pixel_y_offset(lying = 0)
. = ..()
if(lying)
return -6
else
return initial(pixel_y)
. -= 6
/mob/living/carbon/proc/accident(obj/item/I)
if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP))
@@ -76,11 +76,13 @@
visible_message("<span class='danger'>[I] embeds itself in [src]'s [L.name]!</span>","<span class='userdanger'>[I] embeds itself in your [L.name]!</span>")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user)
var/totitemdamage = pre_attacked_by(I, user)
/mob/living/carbon/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = pre_attacked_by(I, user) * damage_multiplier
var/impacting_zone = (user == src)? check_zone(user.zone_selected) : ran_zone(user.zone_selected)
if((user != src) && (mob_run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone, null) & BLOCK_SUCCESS))
var/list/block_return = list()
if((user != src) && (mob_run_block(I, totitemdamage, "the [I]", ((attackchain_flags & ATTACKCHAIN_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE) | ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone, block_return) & BLOCK_SUCCESS))
return FALSE
totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
var/obj/item/bodypart/affecting = get_bodypart(impacting_zone)
if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest)
affecting = bodyparts[1]
@@ -20,7 +20,7 @@
if(istype(J) && (movement_dir || J.stabilizers) && J.allow_thrust(0.01, src))
return 1
/mob/living/carbon/Move(NewLoc, direct)
/mob/living/carbon/Moved()
. = ..()
if(. && (movement_type & FLOATING)) //floating is easy
if(HAS_TRAIT(src, TRAIT_NOHUNGER))
@@ -1,8 +1,11 @@
/mob/living/carbon/human/get_blocking_items()
. = ..()
if(wear_suit)
. |= wear_suit
if(!.[wear_suit])
.[wear_suit] = wear_suit.block_priority
if(w_uniform)
. |= w_uniform
if(!.[w_uniform])
.[w_uniform] = w_uniform.block_priority
if(wear_neck)
. |= wear_neck
if(!.[wear_neck])
.[wear_neck] = wear_neck.block_priority
@@ -78,7 +78,7 @@
..()
/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user)
/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
if(!I || !user)
return 0
@@ -95,7 +95,7 @@
SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area)
// the attacked_by code varies among species
return dna.species.spec_attacked_by(I, user, affecting, a_intent, src)
return dna.species.spec_attacked_by(I, user, affecting, a_intent, src, attackchain_flags, damage_multiplier)
/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
if(user.a_intent == INTENT_HARM)
@@ -214,7 +214,7 @@
/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M)
. = ..()
if(.)
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
var/damage = .
var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
if(!dam_zone) //Dismemberment successful
return TRUE
@@ -1700,12 +1700,14 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if("disarm")
disarm(M, H, attacker_style)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
var/totitemdamage = H.pre_attacked_by(I, user)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = H.pre_attacked_by(I, user) * damage_multiplier
// Allows you to put in item-specific reactions based on species
if(user != H)
if(H.mob_run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone, null) & BLOCK_SUCCESS)
var/list/block_return = list()
if(H.mob_run_block(I, totitemdamage, "the [I.name]", ((attackchain_flags & ATTACKCHAIN_PARRY_COUNTERATTACK)? ATTACK_TYPE_PARRY_COUNTERATTACK : NONE) | ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone, block_return) & BLOCK_SUCCESS)
return 0
totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
if(H.check_martial_melee_block())
H.visible_message("<span class='warning'>[H] blocks [I]!</span>")
return 0
@@ -1720,6 +1722,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/armor_block = H.run_armor_check(affecting, "melee", "<span class='notice'>Your armor has protected your [hit_area].</span>", "<span class='notice'>Your armor has softened a hit to your [hit_area].</span>",I.armour_penetration)
armor_block = min(90,armor_block) //cap damage reduction at 90%
var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
var/weakness = H.check_weakness(I, user)
apply_damage(totitemdamage * weakness, I.damtype, def_zone, armor_block, H) //CIT CHANGE - replaces I.force with totitemdamage
@@ -280,25 +280,15 @@
. = ..()
C.faction |= "plants"
C.faction |= "vines"
C.AddElement(/datum/element/photosynthesis)
/datum/species/golem/wood/on_species_loss(mob/living/carbon/C)
. = ..()
C.faction -= "plants"
C.faction -= "vines"
C.RemoveElement(/datum/element/photosynthesis)
/datum/species/golem/wood/spec_life(mob/living/carbon/human/H)
if(H.stat == DEAD)
return
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
if(isturf(H.loc)) //else, there's considered to be no light
var/turf/T = H.loc
light_amount = min(1,T.get_lumcount()) - 0.5
H.adjust_nutrition(light_amount * 4, NUTRITION_LEVEL_FULL)
if(light_amount > 0.2) //if there's enough light, heal
H.heal_overall_damage(1,1)
H.adjustToxLoss(-1)
H.adjustOxyLoss(-1)
if(H.nutrition < NUTRITION_LEVEL_STARVING + 50)
H.take_overall_damage(2,0)
@@ -14,34 +14,24 @@
liked_food = VEGETABLES | FRUIT | GRAIN
species_language_holder = /datum/language_holder/sylvan
var/light_nutrition_gain_factor = 4
var/light_toxheal = 1
var/light_oxyheal = 1
var/light_burnheal = 1
var/light_bruteheal = 1
var/light_toxheal = -1
var/light_oxyheal = -1
var/light_burnheal = -1
var/light_bruteheal = -1
/datum/species/pod/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
C.faction |= "plants"
C.faction |= "vines"
C.AddElement(/datum/element/photosynthesis, light_bruteheal, light_burnheal, light_toxheal, light_oxyheal, light_nutrition_gain_factor)
/datum/species/pod/on_species_loss(mob/living/carbon/C)
. = ..()
C.faction -= "plants"
C.faction -= "vines"
C.RemoveElement(/datum/element/photosynthesis, light_bruteheal, light_burnheal, light_toxheal, light_oxyheal, light_nutrition_gain_factor)
/datum/species/pod/spec_life(mob/living/carbon/human/H)
if(H.stat == DEAD)
return
var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing
if(isturf(H.loc)) //else, there's considered to be no light
var/turf/T = H.loc
light_amount = min(1,T.get_lumcount()) - 0.5
H.adjust_nutrition(light_amount * light_nutrition_gain_factor, NUTRITION_LEVEL_FULL)
if(light_amount > 0.2) //if there's enough light, heal
H.heal_overall_damage(light_bruteheal, light_burnheal)
H.adjustToxLoss(-light_toxheal)
H.adjustOxyLoss(-light_oxyheal)
if(H.nutrition < NUTRITION_LEVEL_STARVING + 50)
H.take_overall_damage(2,0)
@@ -77,9 +67,9 @@
mutant_bodyparts = list("mcolor" = "FFF","mcolor2" = "FFF","mcolor3" = "FFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "mam_body_markings" = "Husky", "taur" = "None", "legs" = "Normal Legs")
limbs_id = "pod"
light_nutrition_gain_factor = 3
light_bruteheal = 0.2
light_burnheal = 0.2
light_toxheal = 0.7
light_bruteheal = -0.2
light_burnheal = -0.2
light_toxheal = -0.7
/datum/species/pod/pseudo_weak/spec_death(gibbed, mob/living/carbon/human/H)
if(H)
@@ -15,16 +15,13 @@
dangerous_existence = 1
mutanteyes = /obj/item/organ/eyes/night_vision
/datum/species/shadow/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
C.AddElement(/datum/element/photosynthesis, 1, 1, 0, 0, 0, 0, SHADOW_SPECIES_LIGHT_THRESHOLD, SHADOW_SPECIES_LIGHT_THRESHOLD)
/datum/species/shadow/spec_life(mob/living/carbon/human/H)
var/turf/T = H.loc
if(istype(T))
var/light_amount = T.get_lumcount()
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying
H.take_overall_damage(1,1)
else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
H.heal_overall_damage(1,1)
/datum/species/shadow/on_species_loss(mob/living/carbon/C)
. = ..()
C.RemoveElement(/datum/element/photosynthesis, 1, 1, 0, 0, 0, 0, SHADOW_SPECIES_LIGHT_THRESHOLD, SHADOW_SPECIES_LIGHT_THRESHOLD)
/datum/species/shadow/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
@@ -141,7 +141,7 @@
/mob/living/carbon/monkey/attack_animal(mob/living/simple_animal/M)
. = ..()
if(.)
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
var/damage = .
var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
if(!dam_zone) //Dismemberment successful
return TRUE
+1 -3
View File
@@ -78,8 +78,6 @@
apply_damage(brain, BRAIN, def_zone, blocked)
return 1
/mob/living/proc/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = FALSE, knockdown_stamoverride, knockdown_stammax)
var/hit_percent = (100-blocked)/100
if(!effect || (hit_percent <= 0))
@@ -108,7 +106,7 @@
return 1
/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = FALSE, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax)
/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = 0, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax)
if(blocked >= 100)
return BULLET_ACT_BLOCK
if(stun)
+2
View File
@@ -64,6 +64,8 @@
handle_gravity()
handle_block_parry(seconds)
if(machine)
machine.check_eye(src)
+2
View File
@@ -19,6 +19,8 @@
med_hud_set_status()
/mob/living/Destroy()
end_parry_sequence()
stop_active_blocking()
if(LAZYLEN(status_effects))
for(var/s in status_effects)
var/datum/status_effect/S = s
@@ -0,0 +1,275 @@
// Active directional block system. Shared code is in [living_blocking_parrying.dm]
/mob/living/proc/stop_active_blocking(was_forced = FALSE)
if(!(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING)))
return FALSE
var/obj/item/I = active_block_item
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCKING | COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
active_block_effect_end()
active_block_item = null
REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT)
REMOVE_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
remove_movespeed_modifier(/datum/movespeed_modifier/active_block)
var/datum/block_parry_data/data = I.get_block_parry_data()
if(timeToNextMove() < data.block_end_click_cd_add)
changeNext_move(data.block_end_click_cd_add)
return TRUE
/mob/living/proc/start_active_blocking(obj/item/I)
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
return FALSE
if(!(I in held_items))
return FALSE
var/datum/block_parry_data/data = I.get_block_parry_data()
if(!istype(data)) //Typecheck because if an admin/coder screws up varediting or something we do not want someone being broken forever, the CRASH logs feedback so we know what happened.
CRASH("start_active_blocking called with an item with no valid data: [I] --> [I.block_parry_data]!")
combat_flags |= COMBAT_FLAG_ACTIVE_BLOCKING
active_block_item = I
if(data.block_lock_attacking)
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT) //probably should be something else at some point
if(data.block_lock_sprinting)
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_BLOCK_TRAIT)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/active_block, multiplicative_slowdown = data.block_slowdown)
active_block_effect_start()
return TRUE
/// Visual effect setup for starting a directional block
/mob/living/proc/active_block_effect_start()
visible_message("<span class='warning'>[src] raises their [active_block_item], dropping into a defensive stance!</span>")
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_OUT)
/// Visual effect cleanup for starting a directional block
/mob/living/proc/active_block_effect_end()
visible_message("<span class='warning'>[src] lowers their [active_block_item].</span>")
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN)
/mob/living/proc/continue_starting_active_block()
if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
return DO_AFTER_STOP
return (combat_flags & COMBAT_FLAG_ACTIVE_BLOCK_STARTING)? DO_AFTER_CONTINUE : DO_AFTER_STOP
/mob/living/get_standard_pixel_x_offset()
. = ..()
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
if(dir & EAST)
. += 8
if(dir & WEST)
. -= 8
/mob/living/get_standard_pixel_y_offset()
. = ..()
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
if(dir & NORTH)
. += 8
if(dir & SOUTH)
. -= 8
/**
* Proc called by keybindings to toggle active blocking.
*/
/mob/living/proc/keybind_toggle_active_blocking()
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
return keybind_stop_active_blocking()
else
return keybind_start_active_blocking()
/**
* Proc called by keybindings to start active blocking.
*/
/mob/living/proc/keybind_start_active_blocking()
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
return FALSE
if(!(combat_flags & COMBAT_FLAG_BLOCK_CAPABLE))
to_chat(src, "<span class='warning'>You're not something that can actively block.</span>")
return FALSE
// QOL: Instead of trying to just block with held item, grab first available item.
var/obj/item/I = find_active_block_item()
if(!I)
to_chat(src, "<span class='warning'>You can't block with your bare hands!</span>")
return
if(!I.can_active_block())
to_chat(src, "<span class='warning'>[I] is either not capable of being used to actively block, or is not currently in a state that can! (Try wielding it if it's twohanded, for example.)</span>")
return
// QOL: Attempt to toggle on combat mode if it isn't already
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
if(!SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
to_chat(src, "<span class='warning'>You must be in combat mode to actively block!</span>")
return FALSE
var/datum/block_parry_data/data = I.get_block_parry_data()
var/delay = data.block_start_delay
combat_flags |= COMBAT_FLAG_ACTIVE_BLOCK_STARTING
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = delay, FALSE, SINE_EASING | EASE_IN)
if(!do_after_advanced(src, delay, src, DO_AFTER_REQUIRES_USER_ON_TURF|DO_AFTER_NO_COEFFICIENT, CALLBACK(src, .proc/continue_starting_active_block), MOBILITY_USE, null, null, I))
to_chat(src, "<span class='warning'>You fail to raise [I].</span>")
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN, ANIMATION_END_NOW)
return
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
start_active_blocking(I)
/**
* Gets the first item we can that can block, but if that fails, default to active held item.COMSIG_ENABLE_COMBAT_MODE
*/
/mob/living/proc/find_active_block_item()
var/obj/item/held = get_active_held_item()
if(!held?.can_active_block())
for(var/obj/item/I in held_items - held)
if(I.can_active_block())
return I
return held
/**
* Proc called by keybindings to stop active blocking.
*/
/mob/living/proc/keybind_stop_active_blocking()
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
stop_active_blocking(FALSE)
return TRUE
/**
* Returns if we can actively block.
*/
/obj/item/proc/can_active_block()
return block_parry_data && (item_flags & ITEM_CAN_BLOCK)
/**
* Calculates FINAL ATTACK DAMAGE after mitigation
*/
/obj/item/proc/active_block_calculate_final_damage(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
var/datum/block_parry_data/data = get_block_parry_data()
var/absorption = data.attack_type_list_scan(data.block_damage_absorption_override, attack_type)
var/efficiency = data.attack_type_list_scan(data.block_damage_multiplier_override, attack_type)
var/limit = data.attack_type_list_scan(data.block_damage_limit_override, attack_type)
// must use isnulls to handle 0's.
if(isnull(absorption))
absorption = data.block_damage_absorption
if(isnull(efficiency))
efficiency = data.block_damage_multiplier
if(isnull(limit))
limit = data.block_damage_limit
// now we calculate damage to reduce.
var/final_damage = 0
// apply limit
if(damage > limit) //clamp and apply overrun
final_damage += (damage - limit)
damage = limit
// apply absorption
damage -= min(absorption, damage) //this way if damage is less than absorption it 0's properly.
// apply multiplier to remaining
final_damage += (damage * efficiency)
return final_damage
/// Amount of stamina from damage blocked. Note that the damage argument is damage_blocked.
/obj/item/proc/active_block_stamina_cost(mob/living/owner, atom/object, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
var/datum/block_parry_data/data = get_block_parry_data()
var/efficiency = data.attack_type_list_scan(data.block_stamina_efficiency_override, attack_type)
if(isnull(efficiency))
efficiency = data.block_stamina_efficiency
var/multiplier = 1
if(!CHECK_MOBILITY(owner, MOBILITY_STAND))
multiplier = data.attack_type_list_scan(data.block_resting_stamina_penalty_multiplier_override, attack_type)
if(isnull(multiplier))
multiplier = data.block_resting_stamina_penalty_multiplier
return (damage_blocked / efficiency) * multiplier
/// Apply the stamina damage to our user, notice how damage argument is stamina_amount.
/obj/item/proc/active_block_do_stamina_damage(mob/living/owner, atom/object, stamina_amount, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
var/datum/block_parry_data/data = get_block_parry_data()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
var/held_index = C.get_held_index_of_item(src)
var/obj/item/bodypart/BP = C.hand_bodyparts[held_index]
if(!BP?.body_zone)
return C.adjustStaminaLossBuffered(stamina_amount) //nah
var/zone = BP.body_zone
var/stamina_to_zone = data.block_stamina_limb_ratio * stamina_amount
var/stamina_to_chest = stamina_amount - stamina_to_zone
var/stamina_buffered = stamina_to_chest * data.block_stamina_buffer_ratio
stamina_to_chest -= stamina_buffered
C.apply_damage(stamina_to_zone, STAMINA, zone)
C.apply_damage(stamina_to_chest, STAMINA, BODY_ZONE_CHEST)
C.adjustStaminaLossBuffered(stamina_buffered)
else
owner.adjustStaminaLossBuffered(stamina_amount)
/obj/item/proc/on_active_block(mob/living/owner, atom/object, damage, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
return
/obj/item/proc/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
if(!can_active_block())
return BLOCK_NONE
var/datum/block_parry_data/data = get_block_parry_data()
if(attack_type && !(attack_type & data.can_block_attack_types))
return BLOCK_NONE
var/incoming_direction
if(isnull(override_direction))
if(istype(object, /obj/item/projectile))
var/obj/item/projectile/P = object
incoming_direction = angle2dir(P.Angle)
else
incoming_direction = get_dir(get_turf(attacker) || get_turf(object), src)
if(!CHECK_MOBILITY(owner, MOBILITY_STAND) && !(data.block_resting_attack_types_anydir & attack_type) && (!(data.block_resting_attack_types_directional & attack_type) || !can_block_direction(owner.dir, incoming_direction)))
return BLOCK_NONE
else if(!can_block_direction(owner.dir, incoming_direction))
return BLOCK_NONE
block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE
var/final_damage = active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
var/damage_blocked = damage - final_damage
var/stamina_cost = active_block_stamina_cost(owner, object, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
active_block_do_stamina_damage(owner, object, stamina_cost, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage - final_damage
block_return[BLOCK_RETURN_SET_DAMAGE_TO] = final_damage
. = BLOCK_SHOULD_CHANGE_DAMAGE
if((final_damage <= 0) || (damage <= 0))
. |= BLOCK_SUCCESS //full block
owner.visible_message("<span class='warning'>[owner] blocks \the [attack_text] with [src]!</span>")
else
owner.visible_message("<span class='warning'>[owner] dampens \the [attack_text] with [src]!</span>")
block_return[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = data.block_projectile_mitigation
if(length(data.block_sounds))
playsound(loc, pickweight(data.block_sounds), 75, TRUE)
on_active_block(owner, object, damage, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, override_direction)
/obj/item/proc/check_active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(!can_active_block())
return
var/incoming_direction = get_dir(get_turf(attacker) || get_turf(object), src)
if(!can_block_direction(owner.dir, incoming_direction))
return
block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE
block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage - active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
/**
* Gets the block direction bitflags of what we can block.
*/
/obj/item/proc/blockable_directions()
var/datum/block_parry_data/data = get_block_parry_data()
return data.can_block_directions
/**
* Checks if we can block from a specific direction from our direction.
*
* @params
* * our_dir - our direction.
* * their_dir - their direction. Must be a single direction, or NONE for an attack from the same tile. This is incoming direction.
*/
/obj/item/proc/can_block_direction(our_dir, their_dir)
their_dir = turn(their_dir, 180)
if(our_dir != NORTH)
var/turn_angle = dir2angle(our_dir)
// dir2angle(), ss13 proc is clockwise so dir2angle(EAST) == 90
// turn(), byond proc is counterclockwise so turn(NORTH, 90) == WEST
their_dir = turn(their_dir, turn_angle)
return (DIR2BLOCKDIR(their_dir) & blockable_directions())
/**
* can_block_direction but for "compound" directions to check all of them and return the number of directions that were blocked.
*
* @params
* * our_dir - our direction.
* * their_dirs - list of their directions as we cannot use bitfields here.
*/
/obj/item/proc/can_block_directions_multiple(our_dir, list/their_dirs)
. = FALSE
for(var/i in their_dirs)
. |= can_block_direction(our_dir, i)
@@ -0,0 +1,332 @@
// Active parry system goes in here.
/**
* Determines if we can actively parry.
*/
/obj/item/proc/can_active_parry()
return block_parry_data && (item_flags & ITEM_CAN_PARRY)
/**
* Called from keybindings.
*/
/mob/living/proc/keybind_parry()
initiate_parry_sequence()
/**
* Initiates a parrying sequence.
*/
/mob/living/proc/initiate_parry_sequence()
if(parrying)
return // already parrying
if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE))
to_chat(src, "<span class='warning'>You are not something that can parry attacks.</span>")
return
// Prioritize item, then martial art, then unarmed.
// yanderedev else if time
var/obj/item/using_item = get_active_held_item()
var/datum/block_parry_data/data
var/method
if(using_item?.can_active_parry())
data = using_item.block_parry_data
method = ITEM_PARRY
else if(mind?.martial_art?.can_martial_parry)
data = mind.martial_art.block_parry_data
method = MARTIAL_PARRY
else if(combat_flags & COMBAT_FLAG_UNARMED_PARRY)
data = block_parry_data
method = UNARMED_PARRY
else
// QOL: If none of the above work, try to find another item.
var/obj/item/backup = find_backup_parry_item()
if(!backup)
to_chat(src, "<span class='warning'>You have nothing to parry with!</span>")
return FALSE
data = backup.block_parry_data
using_item = backup
method = ITEM_PARRY
//QOL: Try to enable combat mode if it isn't already
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
if(!SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
to_chat(src, "<span class='warning'>You must be in combat mode to parry!</span>")
return FALSE
data = return_block_parry_datum(data)
var/full_parry_duration = data.parry_time_windup + data.parry_time_active + data.parry_time_spindown
// no system in place to "fallback" if out of the 3 the top priority one can't parry due to constraints but something else can.
// can always implement it later, whatever.
if((data.parry_respect_clickdelay && (next_move > world.time)) || ((parry_end_time_last + data.parry_cooldown) > world.time))
to_chat(src, "<span class='warning'>You are not ready to parry (again)!</span>")
return
// Point of no return, make sure everything is set.
parrying = method
if(method == ITEM_PARRY)
active_parry_item = using_item
adjustStaminaLossBuffered(data.parry_stamina_cost)
parry_start_time = world.time
successful_parries = list()
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
if(data.parry_flags & PARRY_LOCK_ATTACKING)
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
if(data.parry_flags & PARRY_LOCK_SPRINTING)
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
handle_parry_starting_effects(data)
return TRUE
/**
* Tries to find a backup parry item.
* Does not look at active held item.
*/
/mob/living/proc/find_backup_parry_item()
for(var/obj/item/I in held_items - get_active_held_item())
if(I.can_active_parry())
return I
/**
* Called via timer when the parry sequence ends.
*/
/mob/living/proc/end_parry_sequence()
if(!parrying)
return
REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
REMOVE_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
if(parry_visual_effect)
QDEL_NULL(parry_visual_effect)
var/datum/block_parry_data/data = get_parry_data()
var/list/effect_text = list()
var/successful = FALSE
for(var/efficiency in successful_parries)
if(efficiency >= data.parry_efficiency_considered_successful)
successful = TRUE
break
if(!successful) // didn't parry anything successfully
if(data.parry_failed_stagger_duration)
Stagger(data.parry_failed_stagger_duration)
effect_text += "staggering themselves"
if(data.parry_failed_clickcd_duration)
changeNext_move(data.parry_failed_clickcd_duration)
effect_text += "throwing themselves off balance"
handle_parry_ending_effects(data, effect_text)
parrying = NOT_PARRYING
parry_start_time = 0
parry_end_time_last = world.time
successful_parries = null
/**
* Handles starting effects for parrying.
*/
/mob/living/proc/handle_parry_starting_effects(datum/block_parry_data/data)
playsound(src, data.parry_start_sound, 75, 1)
parry_visual_effect = new /obj/effect/abstract/parry/main(null, TRUE, src, data.parry_effect_icon_state, data.parry_time_windup_visual_override || data.parry_time_windup, data.parry_time_active_visual_override || data.parry_time_active, data.parry_time_spindown_visual_override || data.parry_time_spindown)
switch(parrying)
if(ITEM_PARRY)
visible_message("<span class='warning'>[src] swings [active_parry_item]!</span>")
else
visible_message("<span class='warning'>[src] rushes forwards!</span>")
/**
* Handles ending effects for parrying.
*/
/mob/living/proc/handle_parry_ending_effects(datum/block_parry_data/data, list/failed_effect_text)
if(length(successful_parries))
return
visible_message("<span class='warning'>[src] fails to connect their parry[failed_effect_text? ", [english_list(failed_effect_text)]" : ""]!")
/**
* Gets this item's datum/block_parry_data
*/
/obj/item/proc/get_block_parry_data()
return return_block_parry_datum(block_parry_data)
//Stubs.
/**
* Called when an attack is parried using this, whether or not the parry was successful.
*/
/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
/**
* Called when an attack is parried innately, whether or not the parry was successful.
*/
/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
/**
* Called when an attack is parried using this, whether or not the parry was successful.
*/
/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
/**
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
*/
/obj/item/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
/**
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
*/
/mob/living/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
/**
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
*/
/datum/martial_art/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
/**
* Gets the stage of our parry sequence we're currently in.
*/
/mob/living/proc/get_parry_stage()
if(!parrying)
return NOT_PARRYING
var/datum/block_parry_data/data = get_parry_data()
var/windup_end = data.parry_time_windup
var/active_end = windup_end + data.parry_time_active
var/spindown_end = active_end + data.parry_time_spindown
var/current_time = get_parry_time()
// Not a switch statement because byond switch statements don't support floats at time of writing with "to" keyword.
if(current_time < 0)
return NOT_PARRYING
else if(current_time < windup_end)
return PARRY_WINDUP
else if(current_time <= active_end) // this uses <= on purpose, give a slight bit of advantage because time is rounded to world.tick_lag
return PARRY_ACTIVE
else if(current_time <= spindown_end)
return PARRY_SPINDOWN
else
return NOT_PARRYING
/**
* Gets the current decisecond "frame" of an active parry.
*/
/mob/living/proc/get_parry_time()
return world.time - parry_start_time
/// same return values as normal blocking, called with absolute highest priority in the block "chain".
/mob/living/proc/run_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
var/stage = get_parry_stage()
if(stage != PARRY_ACTIVE)
return BLOCK_NONE
var/datum/block_parry_data/data = get_parry_data()
if(attack_type && (!(attack_type & data.parry_attack_types) || (attack_type & ATTACK_TYPE_PARRY_COUNTERATTACK))) // if this attack is from a parry do not parry it lest we infinite loop.
return BLOCK_NONE
var/efficiency = data.get_parry_efficiency(attack_type, get_parry_time())
switch(parrying)
if(ITEM_PARRY)
if(!active_parry_item.can_active_parry())
return BLOCK_NONE
. = active_parry_item.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
if(UNARMED_PARRY)
. = on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
if(MARTIAL_PARRY)
. = mind.martial_art.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
if(!isnull(return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY])) // one of our procs overrode
efficiency = return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY]
if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now.
return
. |= BLOCK_SHOULD_PARTIAL_MITIGATE
if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence.
return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now.
if((return_list[BLOCK_RETURN_MITIGATION_PERCENT] >= 100) || (damage <= 0))
. |= BLOCK_SUCCESS
var/list/effect_text
if(efficiency >= data.parry_efficiency_to_counterattack)
run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
successful_parries += efficiency
if(length(successful_parries) >= data.parry_max_attacks)
end_parry_sequence()
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text)
var/datum/block_parry_data/data = get_parry_data()
if(data.parry_sounds)
playsound(src, pick(data.parry_sounds), 75)
visible_message("<span class='danger'>[src] parries \the [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""]!</span>")
/// Run counterattack if any
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
if(!isliving(attacker))
return
var/mob/living/L = attacker
var/datum/block_parry_data/data = get_parry_data()
var/list/effect_text = list()
// Always proc so items can override behavior easily
switch(parrying)
if(ITEM_PARRY)
active_parry_item.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
if(UNARMED_PARRY)
active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
if(MARTIAL_PARRY)
mind.martial_art.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency, effect_text)
if(Adjacent(attacker) || data.parry_data[PARRY_COUNTERATTACK_IGNORE_ADJACENCY])
if(data.parry_data[PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN])
switch(parrying)
if(ITEM_PARRY)
active_parry_item.melee_attack_chain(src, attacker, null, ATTACKCHAIN_PARRY_COUNTERATTACK, data.parry_data[PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN])
effect_text += "reflexively counterattacking with [active_parry_item]"
if(UNARMED_PARRY) // WARNING: If you are using these two, the attackchain parry counterattack flags and damage multipliers are unimplemented. Be careful with how you handle this.
UnarmedAttack(attacker)
effect_text += "reflexively counterattacking in the process"
if(MARTIAL_PARRY) // Not well implemeneted, recommend custom implementation using the martial art datums.
UnarmedAttack(attacker)
effect_text += "reflexively maneuvering to retaliate"
if(data.parry_data[PARRY_DISARM_ATTACKER])
L.drop_all_held_items()
effect_text += "disarming"
if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
effect_text += "knocking them to the ground"
if(data.parry_data[PARRY_STAGGER_ATTACKER])
L.Stagger(data.parry_data[PARRY_STAGGER_ATTACKER])
effect_text += "staggering"
if(data.parry_data[PARRY_DAZE_ATTACKER])
L.Daze(data.parry_data[PARRY_DAZE_ATTACKER])
effect_text += "dazing"
return effect_text
/// Gets the datum/block_parry_data we're going to use to parry.
/mob/living/proc/get_parry_data()
if(parrying == ITEM_PARRY)
return active_parry_item.get_block_parry_data()
else if(parrying == UNARMED_PARRY)
return return_block_parry_datum(block_parry_data)
else if(parrying == MARTIAL_PARRY)
return return_block_parry_datum(mind.martial_art.block_parry_data)
/// Effects
/obj/effect/abstract/parry
icon = 'icons/effects/block_parry.dmi'
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
layer = FLOAT_LAYER
plane = FLOAT_PLANE
vis_flags = VIS_INHERIT_LAYER|VIS_INHERIT_PLANE
/// The person we're on
var/mob/living/owner
/obj/effect/abstract/parry/main
name = null
/obj/effect/abstract/parry/main/Initialize(mapload, autorun, mob/living/owner, set_icon_state, windup, active, spindown)
. = ..()
icon_state = set_icon_state
if(owner)
attach_to(owner)
if(autorun)
INVOKE_ASYNC(src, .proc/run_animation, windup, active, spindown)
/obj/effect/abstract/parry/main/Destroy()
detach_from(owner)
return ..()
/obj/effect/abstract/parry/main/proc/attach_to(mob/living/attaching)
if(owner)
detach_from(owner)
owner = attaching
owner.vis_contents += src
/obj/effect/abstract/parry/main/proc/detach_from(mob/living/detaching)
if(detaching == owner)
owner = null
detaching.vis_contents -= src
/obj/effect/abstract/parry/main/proc/run_animation(windup_time = 2, active_time = 5, spindown_time = 3)
var/matrix/current = transform
transform = matrix(0.1, 0, 0, 0, 0.1, 0)
animate(src, transform = current, time = windup_time)
sleep(active_time)
animate(src, alpha = 0, spindown_time)
+41 -32
View File
@@ -1,31 +1,6 @@
// This file has a weird name, but it's for anything related to the checks for shields, blocking, dodging,
// and similar "stop this attack before it actually impacts the target" as opposed to "defend once it has hit".
/*
/// You can find the mob_check_block() and mob_run_block() macros in __DEFINES/combat.dm
/// Bitflags for check_block() and run_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS.
/// Attack was not blocked
#define BLOCK_NONE NONE
/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION!
#define BLOCK_SUCCESS (1<<1)
/// The below are for "metadata" on "how" the attack was blocked.
/// Attack was and should be reflected (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!)
#define BLOCK_SHOULD_REFLECT (1<<2)
/// Attack was manually redirected (including reflected) by any means by the defender. For when YOU are handling the reflection, rather than the thing hitting you. (see sleeping carp)
#define BLOCK_REDIRECTED (1<<3)
/// Attack was blocked by something like a shield.
#define BLOCK_PHYSICAL_EXTERNAL (1<<4)
/// Attack was blocked by something worn on you.
#define BLOCK_PHYSICAL_INTERNAL (1<<5)
/// Attack should pass through. Like SHOULD_REFLECT but for.. well, passing through harmlessly.
#define BLOCK_SHOULD_PASSTHROUGH (1<<6)
/// Attack outright missed because the target dodged. Should usually be combined with SHOULD_PASSTHROUGH or something (see martial arts)
#define BLOCK_TARGET_DODGED (1<<7)
*/
/** The actual proc for block checks. DO NOT USE THIS DIRECTLY UNLESS YOU HAVE VERY GOOD REASON TO. To reduce copypaste for differences between handling for real attacks and virtual checks.
* Automatically checks all held items for /obj/item/proc/run_block() with the same parameters.
* @params
@@ -39,21 +14,31 @@
* attacker - Set to the mob attacking IF KNOWN. Do not expect this to always be set!
* def_zone - The zone this'll impact.
* return_list - If something wants to grab things from what items/whatever put into list/block_return on obj/item/run_block and the comsig, pass in a list so you can grab anything put in it after block runs.
* attack_direction - Direction of the attack. It is highly recommended to put this in, as the automatic guesswork that's done otherwise is quite inaccurate at times.
*/
/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), attack_direction)
if(real_attack)
. = run_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list) //Parry - Highest priority!
if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN))
return
// Component signal block runs have highest priority.. for now.
. = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
. = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, attack_direction)
if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN))
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = 100
return
var/list/obj/item/tocheck = get_blocking_items()
sortTim(tocheck, /proc/cmp_item_block_priority_asc)
sortTim(tocheck, /proc/cmp_numeric_dsc, TRUE)
// i don't like this
var/block_chance_modifier = round(damage / -3)
if(real_attack)
for(var/obj/item/I in tocheck)
// i don't like this too
var/final_block_chance = I.block_chance - (clamp((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
var/results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
var/results
if(I == active_block_item)
results = I.active_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
else
results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
. |= results
if((results & BLOCK_SUCCESS) && !(results & BLOCK_CONTINUE_CHAIN))
break
@@ -61,17 +46,29 @@
for(var/obj/item/I in tocheck)
// i don't like this too
var/final_block_chance = I.block_chance - (clamp((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
if(I == active_block_item) //block is long termed enough we give a damn. parry, not so much.
I.check_active_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
else
I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
if(. & BLOCK_SUCCESS)
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = 100
else if(isnull(return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE]))
return_list[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE] = return_list[BLOCK_RETURN_MITIGATION_PERCENT]
/// Gets an unsortedlist of objects to run block checks on.
/// Gets an unsortedlist of objects to run block checks on. List must have associative values for priorities!
/mob/living/proc/get_blocking_items()
. = list()
if(active_block_item)
var/datum/block_parry_data/data = active_block_item.get_block_parry_data()
.[active_block_item] = data.block_active_priority
SEND_SIGNAL(src, COMSIG_LIVING_GET_BLOCKING_ITEMS, .)
for(var/obj/item/I in held_items)
// this is a bad check but i am not removing it until a better catchall is made
if(istype(I, /obj/item/clothing))
continue
. |= I
if(.[I]) //don't override block/parry.
continue
.[I] = I.block_priority
/obj/item
/// The 0% to 100% chance for the default implementation of random block rolls.
@@ -95,3 +92,15 @@
SEND_SIGNAL(src, COMSIG_ITEM_CHECK_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
var/existing = block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE]
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = max(existing || 0, final_block_chance)
// HELPER PROCS
/**
* Considers a block return_list and calculates damage to use from that.
*/
/proc/block_calculate_resultant_damage(damage, list/block_return)
if(!isnull(block_return[BLOCK_RETURN_SET_DAMAGE_TO])) // higher priority
return block_return[BLOCK_RETURN_SET_DAMAGE_TO]
else if(!isnull(block_return[BLOCK_RETURN_MITIGATION_PERCENT]))
return damage * ((100 - block_return[BLOCK_RETURN_MITIGATION_PERCENT]) * 0.01)
return damage
@@ -0,0 +1,310 @@
// yell at me later for file naming
// This file contains stuff relating to the new directional blocking and parry system.
GLOBAL_LIST_EMPTY(block_parry_data)
/proc/return_block_parry_datum(datum/block_parry_data/type_id_datum)
if(istype(type_id_datum))
return type_id_datum
if(ispath(type_id_datum))
. = GLOB.block_parry_data["[type_id_datum]"]
if(!.)
. = GLOB.block_parry_data["[type_id_datum]"] = new type_id_datum
else //text id
return GLOB.block_parry_data["[type_id_datum]"]
/proc/set_block_parry_datum(id, datum/block_parry_data/data)
if(ispath(id))
CRASH("Path-fetching of block parry data is only to grab static data, do not attempt to modify global caches of paths. Use string IDs.")
GLOB.block_parry_data["[id]"] = data
/// Carries data like list data that would be a waste of memory if we initialized the list on every /item as we can cache datums easier.
/datum/block_parry_data
/////////// BLOCKING ////////////
/// NOTE: FOR ATTACK_TYPE_DEFINE, you MUST wrap it in "[DEFINE_HERE]"! The defines are bitflags, and therefore, NUMBERS!
/// See defines. Point of reference is someone facing north.
var/can_block_directions = BLOCK_DIR_NORTH | BLOCK_DIR_NORTHEAST | BLOCK_DIR_NORTHWEST
/// Attacks we can block
var/can_block_attack_types = ALL
/// Our slowdown added while blocking
var/block_slowdown = 1
/// Clickdelay added to user after block ends
var/block_end_click_cd_add = 0
/// Disallow attacking during block
var/block_lock_attacking = TRUE
/// Disallow sprinting during block
var/block_lock_sprinting = FALSE
/// The priority we get in [mob/do_run_block()] while we're being used to parry.
var/block_active_priority = BLOCK_PRIORITY_ACTIVE_BLOCK
/// Windup before we have our blocking active.
var/block_start_delay = 5
/// Amount of "free" damage blocking absorbs
var/block_damage_absorption = 10
/// Override absorption, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_absorption]
var/list/block_damage_absorption_override
/// Ratio of damage to allow through above absorption and below limit. Multiplied by damage to determine how much to let through. Lower is better.
var/block_damage_multiplier = 0.5
/// Override damage overrun efficiency, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_efficiency]
var/list/block_damage_multiplier_override
/// Upper bound of damage block, anything above this will go right through.
var/block_damage_limit = 80
/// Override upper bound of damage block, list("[ATTACK_TYPE_DEFINE]" = absorption), see [block_damage_limit]
var/list/block_damage_limit_override
/// The blocked variable of on_hit() on projectiles is impacted by this. Higher is better, 0 to 100, percentage.
var/block_projectile_mitigation = 50
/*
* NOTE: Overrides for attack types for most the block_stamina variables were removed,
* because at the time of writing nothing needed to use it. Add them if you need it,
* it should be pretty easy, just copy [active_block_damage_mitigation]
* for how to override with list.
*/
/// Default damage-to-stamina coefficient, higher is better. This is based on amount of damage BLOCKED, not initial damage, to prevent damage from "double dipping".
var/block_stamina_efficiency = 2
/// Override damage-to-stamina coefficient, see [block_efficiency], this should be list("[ATTACK_TYPE_DEFINE]" = coefficient_number)
var/list/block_stamina_efficiency_override
/// Ratio of stamina incurred by blocking that goes to the arm holding the object instead of the chest. Has no effect if this is not held in hand.
var/block_stamina_limb_ratio = 0.5
/// Ratio of stamina incurred by chest (so after [block_stamina_limb_ratio] runs) that is buffered.
var/block_stamina_buffer_ratio = 1
/// Stamina dealt directly via adjustStaminaLossBuffered() per SECOND of block.
var/block_stamina_cost_per_second = 1.5
/// Bitfield for attack types that we can block while down. This will work in any direction.
var/block_resting_attack_types_anydir = ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED | ATTACK_TYPE_TACKLE
/// Bitfield for attack types that we can block while down but only in our normal directions.
var/block_resting_attack_types_directional = ATTACK_TYPE_PROJECTILE | ATTACK_TYPE_THROWN
/// Multiplier to stamina damage taken for attacks blocked while downed.
var/block_resting_stamina_penalty_multiplier = 1.5
/// Override list for multiplier to stamina damage taken for attacks blocked while down. list("[ATTACK_TYPE_DEFINE]" = multiplier_number)
var/list/block_resting_stamina_penalty_multiplier_override
/// Sounds for blocking
var/list/block_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
/////////// PARRYING ////////////
/// Prioriry for [mob/do_run_block()] while we're being used to parry.
// None - Parry is always highest priority!
/// Parry doesn't work if you aren't able to otherwise attack due to clickdelay
var/parry_respect_clickdelay = TRUE
/// Parry stamina cost
var/parry_stamina_cost = 5
/// Attack types we can block
var/parry_attack_types = ALL
/// Parry flags
var/parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING
/// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage.
var/parry_time_windup = 2
/// Parry spindown duration in deciseconds. main stage end to this is the spindown stage, afterwards the parry fully ends.
var/parry_time_spindown = 3
/// Main parry window in deciseconds. This is between [parry_time_windup] and [parry_time_spindown]
var/parry_time_active = 5
// Visual overrides
/// If set, overrides visual duration of windup
var/parry_time_windup_visual_override
/// If set, overrides visual duration of active period
var/parry_time_active_visual_override
/// If set, overrides visual duration of spindown
var/parry_time_spindown_visual_override
/// Perfect parry window in deciseconds from the start of the main window. 3 with main 5 = perfect on third decisecond of main window.
var/parry_time_perfect = 2.5
/// Time on both sides of perfect parry that still counts as part of the perfect window.
var/parry_time_perfect_leeway = 1
/// [parry_time_perfect_leeway] override for attack types, list("[ATTACK_TYPE_DEFINE]" = deciseconds)
var/list/parry_time_perfect_leeway_override
/// Parry "efficiency" falloff in percent per decisecond once perfect window is over.
var/parry_imperfect_falloff_percent = 20
/// [parry_imperfect_falloff_percent] override for attack types, list("[ATTACK_TYPE_DEFINE]" = deciseconds)
var/list/parry_imperfect_falloff_percent_override
/// Efficiency in percent on perfect parry.
var/parry_efficiency_perfect = 120
/// Parry effect data.
var/list/parry_data = list(
PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 1
)
/// Efficiency must be at least this to be considered successful
var/parry_efficiency_considered_successful = 0.1
/// Efficiency must be at least this to run automatic counterattack
var/parry_efficiency_to_counterattack = 0.1
/// Maximum attacks to parry successfully or unsuccessfully (but not efficiency < 0) during active period, hitting this immediately ends the sequence.
var/parry_max_attacks = INFINITY
/// Visual icon state override for parrying
var/parry_effect_icon_state = "parry_bm_hold"
/// Parrying cooldown, separate of clickdelay. It must be this much deciseconds since their last parry for them to parry with this object.
var/parry_cooldown = 0
/// Parry start sound
var/parry_start_sound = 'sound/block_parry/sfx-parry.ogg'
/// Sounds for parrying
var/list/parry_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
/// Stagger duration post-parry if you fail to parry an attack
var/parry_failed_stagger_duration = 3.5 SECONDS
/// Clickdelay duration post-parry if you fail to parry an attack
var/parry_failed_clickcd_duration = 2 SECONDS
/**
* Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag.
*/
/datum/block_parry_data/proc/attack_type_list_scan(list/L, attack_type)
var/total = 0
var/div = 0
for(var/flagtext in L)
if(attack_type & text2num(flagtext))
total += L[flagtext]
div++
// if none, return null.
if(!div)
return
return total/div //groan
/**
* Gets the percentage efficiency of our parry.
*
* Returns a percentage in normal 0 to 100 scale, but not clamped to just 0 to 100.
* This is a proc to allow for overriding.
* @params
* * attack_type - int, bitfield of the attack type(s)
* * parry_time - deciseconds since start of the parry.
*/
/datum/block_parry_data/proc/get_parry_efficiency(attack_type, parry_time)
var/difference = abs(parry_time - (parry_time_perfect + parry_time_windup))
var/leeway = attack_type_list_scan(parry_time_perfect_leeway_override, attack_type)
if(isnull(leeway))
leeway = parry_time_perfect_leeway
difference -= leeway
. = parry_efficiency_perfect
if(difference <= 0)
return
var/falloff = attack_type_list_scan(parry_imperfect_falloff_percent_override, attack_type)
if(isnull(falloff))
falloff = parry_imperfect_falloff_percent
. -= falloff * difference
#define RENDER_VARIABLE_SIMPLE(varname, desc) dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>[varname]</th></tr>"
#define RENDER_OVERRIDE_LIST(varname, desc) \
dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
var/list/assembled__##varname = list(); \
for(var/textbit in varname){ \
assembled__##varname += "[GLOB.attack_type_names[textbit]] = [varname[textbit]]"; \
} \
dat += "[english_list(assembled__##varname)]</th>";
#define RENDER_ATTACK_TYPES(varname, desc) dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
var/list/assembled__##varname = list(); \
for(var/bit in bitfield2list(varname)){ \
var/name = GLOB.attack_type_names[num2text(bit)]; \
if(name){ \
assembled__##varname += "[name]"; \
} \
} \
dat += "[english_list(assembled__##varname)]</th>";
#define RENDER_BLOCK_DIRECTIONS(varname, desc) \
dat += "<tr><th>[#varname]<br><i>[desc]</i></th><th>"; \
var/list/assembled__##varname = list(); \
for(var/bit in bitfield2list(varname)){ \
var/name = GLOB.block_direction_names[num2text(bit)]; \
if(name){ \
assembled__##varname += "[name]"; \
} \
} \
dat += "[english_list(assembled__##varname)]</th>";
/datum/block_parry_data/Topic(href, href_list)
. = ..()
if(.)
return
if(href_list["render"])
var/datum/browser/B = new(usr, REF(src), href_list["name"], 800, 1000)
B.set_content(render_html_readout(href_list["block"], href_list["parry"]))
B.open()
/**
* Generates a HTML render of this datum for self-documentation
* Maybe make this tgui-next someday haha god this is ugly as sin.
* Does NOT include the popout or title or anything. Just the variables and explanations..
*/
/datum/block_parry_data/proc/render_html_readout(block_data = FALSE, parry_data = FALSE)
var/list/dat = list()
if(block_data)
dat += "<div class='statusDisplay'><h3>Block Stats</h3><table style='width:100%'><tr><th>Name/Description</th><th>Value</th></tr>"
RENDER_BLOCK_DIRECTIONS(can_block_directions, "Which directions this can block in.")
RENDER_ATTACK_TYPES(can_block_attack_types, "The kinds of attacks this can block.")
RENDER_VARIABLE_SIMPLE(block_slowdown, "How much slowdown is applied to the user while blocking. Lower is better.")
RENDER_VARIABLE_SIMPLE(block_end_click_cd_add, "How much click delay in deciseconds is applied to the user when blocking ends. Lower is better.")
RENDER_VARIABLE_SIMPLE(block_lock_attacking, "Whether or not (1 or 0) the user is locked from atacking and/or item usage while blocking.")
RENDER_VARIABLE_SIMPLE(block_active_priority, "The priority of this item in the block sequence. This will probably mean nothing to you unless you are a coder.")
RENDER_VARIABLE_SIMPLE(block_start_delay, "The amount of time in deciseconds it takes to start a block with this item. Lower is better.")
RENDER_VARIABLE_SIMPLE(block_damage_absorption, "The amount of damage that is absorbed by default. Higher is better.")
RENDER_OVERRIDE_LIST(block_damage_absorption_override, "Overrides for the above for each attack type")
RENDER_VARIABLE_SIMPLE(block_damage_multiplier, "Damage between absorption and limit is multiplied by this. Lower is better.")
RENDER_OVERRIDE_LIST(block_damage_multiplier_override, "Overrides for the above for each attack type")
RENDER_VARIABLE_SIMPLE(block_damage_limit, "Damage above this passes right through and is not impacted. Higher is better.")
RENDER_OVERRIDE_LIST(block_damage_limit_override, "Overrides for the above for each attack type.")
RENDER_VARIABLE_SIMPLE(block_stamina_efficiency, "Coefficient for stamina damage dealt to user by damage blocked. Higher is better.")
RENDER_OVERRIDE_LIST(block_stamina_efficiency_override, "Overrides for the above for each attack type.")
RENDER_VARIABLE_SIMPLE(block_stamina_limb_ratio, "The ratio of stamina that is applied to the limb holding this object (if applicable) rather than whole body/chest.")
RENDER_VARIABLE_SIMPLE(block_stamina_buffer_ratio, "The ratio of stamina incurred by chest/whole body that is buffered rather than direct (buffer = your stamina buffer, direct = direct stamina damage like from a disabler.)")
RENDER_VARIABLE_SIMPLE(block_stamina_cost_per_second, "The buffered stamina damage the user incurs per second of block. Lower is better.")
RENDER_ATTACK_TYPES(block_resting_attack_types_anydir, "The kinds of attacks you can block while resting/otherwise knocked to the floor from any direction. can_block_attack_types takes precedence.")
RENDER_ATTACK_TYPES(block_resting_attack_types_directional, "The kinds of attacks you can block wihle resting/otherwise knocked to the floor that are directional only. can_block_attack_types takes precedence.")
RENDER_VARIABLE_SIMPLE(block_resting_stamina_penalty_multiplier, "Multiplier to stamina damage incurred from blocking while downed. Lower is better.")
RENDER_OVERRIDE_LIST(block_resting_stamina_penalty_multiplier, "Overrides for the above for each attack type.")
dat += "</div></table>"
if(parry_data)
dat += "<div class='statusDisplay'><h3>Parry Stats</h3><table style='width:100%'><tr><th>Name/Description</th><th>Value</th></tr>"
RENDER_VARIABLE_SIMPLE(parry_respect_clickdelay, "Whether or not (1 or 0) you can only parry if your attack cooldown isn't in effect.")
RENDER_VARIABLE_SIMPLE(parry_stamina_cost, "Buffered stamina damage incurred by you for parrying with this.")
RENDER_ATTACK_TYPES(parry_attack_types, "Attack types you can parry.")
// parry_flags
dat += ""
RENDER_VARIABLE_SIMPLE(parry_time_windup, "Deciseconds of parry windup.")
RENDER_VARIABLE_SIMPLE(parry_time_spindown, "Deciseconds of parry spindown.")
RENDER_VARIABLE_SIMPLE(parry_time_active, "Deciseconds of active parry window - This is the ONLY time your parry is active.")
RENDER_VARIABLE_SIMPLE(parry_time_windup_visual_override, "Visual effect length override")
RENDER_VARIABLE_SIMPLE(parry_time_spindown_visual_override, "Visual effect length override")
RENDER_VARIABLE_SIMPLE(parry_time_active_visual_override, "Visual effect length override")
RENDER_VARIABLE_SIMPLE(parry_time_perfect, "Deciseconds <b>into the active window</b> considered the 'center' of the perfect period.")
RENDER_VARIABLE_SIMPLE(parry_time_perfect_leeway, "Leeway on both sides of the perfect period's center still considered perfect.")
RENDER_OVERRIDE_LIST(parry_time_perfect_leeway_override, "Override for the above for each attack type")
RENDER_VARIABLE_SIMPLE(parry_imperfect_falloff_percent, "Linear falloff in percent per decisecond for attacks parried outside of perfect window.")
RENDER_OVERRIDE_LIST(parry_imperfect_falloff_percent_override, "Override for the above for each attack type")
RENDER_VARIABLE_SIMPLE(parry_efficiency_perfect, "Efficiency in percentage a parry in the perfect window is considered.")
// parry_data
dat += ""
RENDER_VARIABLE_SIMPLE(parry_efficiency_considered_successful, "Minimum parry efficiency to be considered a successful parry.")
RENDER_VARIABLE_SIMPLE(parry_efficiency_to_counterattack, "Minimum parry efficiency to trigger counterattack effects.")
RENDER_VARIABLE_SIMPLE(parry_max_attacks, "Max attacks parried per parry cycle.")
RENDER_VARIABLE_SIMPLE(parry_effect_icon_state, "Parry effect image name")
RENDER_VARIABLE_SIMPLE(parry_cooldown, "Deciseconds it has to be since the last time a parry sequence <b>ended</b> for you before you can parry again.")
RENDER_VARIABLE_SIMPLE(parry_failed_stagger_duration, "Deciseconds you are staggered for at the of the parry sequence if you do not successfully parry anything.")
RENDER_VARIABLE_SIMPLE(parry_failed_clickcd_duration, "Deciseconds you are put on attack cooldown at the end of the parry sequence if you do not successfully parry anything.")
dat += "</div></table>"
return dat.Join("")
#undef RENDER_VARIABLE_SIMPLE
#undef RENDER_OVERRIDE_LIST
#undef RENDER_ATTACK_TYPES
#undef RENDER_BLOCK_DIRECTIONS
// MOB PROCS
/**
* Called every life tick to handle blocking/parrying effects.
*/
/mob/living/proc/handle_block_parry(seconds = 1)
if(combat_flags & COMBAT_FLAG_ACTIVE_BLOCKING)
var/datum/block_parry_data/data = return_block_parry_datum(active_block_item.block_parry_data)
adjustStaminaLossBuffered(data.block_stamina_cost_per_second * seconds)
/mob/living/on_item_dropped(obj/item/I)
if(I == active_block_item)
stop_active_blocking()
if(I == active_parry_item)
end_parry_sequence()
return ..()
+26 -10
View File
@@ -66,22 +66,30 @@
CRASH("Invalid rediretion mode [redirection_mode]")
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
var/totaldamage = P.damage
var/final_percent = 0
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
var/list/returnlist = list()
var/returned = mob_run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist)
final_percent = returnlist[BLOCK_RETURN_PROJECTILE_BLOCK_PERCENTAGE]
if(returned & BLOCK_SHOULD_REDIRECT)
handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD])
if(returned & BLOCK_REDIRECTED)
return BULLET_ACT_FORCE_PIERCE
if(returned & BLOCK_SUCCESS)
P.on_hit(src, 100, def_zone)
P.on_hit(src, final_percent, def_zone)
return BULLET_ACT_BLOCK
totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
if(!P.nodamage)
apply_damage(P.damage, P.damage_type, def_zone, armor)
apply_damage(totaldamage, P.damage_type, def_zone, armor)
if(P.dismemberment)
check_projectile_dismemberment(P, def_zone)
return P.on_hit(src, armor) ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
var/missing = 100 - final_percent
var/armor_ratio = armor * 0.01
if(missing > 0)
final_percent += missing * armor_ratio
return P.on_hit(src, final_percent, def_zone) ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone)
return 0
@@ -111,10 +119,13 @@
I = AM
throwpower = I.throwforce
var/impacting_zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
if(mob_run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, null) & BLOCK_SUCCESS)
var/list/block_return = list()
var/total_damage = I.throwforce
if(mob_run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, block_return) & BLOCK_SUCCESS)
hitpush = FALSE
skipcatch = TRUE
blocked = TRUE
total_damage = block_calculate_resultant_damage(total_damage, block_return)
else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item()))
embed_item(I)
hitpush = FALSE
@@ -143,7 +154,7 @@
visible_message("<span class='danger'>[src] has been hit by [I].</span>", \
"<span class='userdanger'>You have been hit by [I].</span>")
var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration)
apply_damage(I.throwforce, dtype, impacting_zone, armor)
apply_damage(total_damage, dtype, impacting_zone, armor)
if(I.thrownby)
log_combat(I.thrownby, src, "threw and hit", I)
else
@@ -313,8 +324,10 @@
var/damage = rand(5, 35)
if(M.is_adult)
damage = rand(20, 40)
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected), null) & BLOCK_SUCCESS)
var/list/block_return = list()
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected), block_return) & BLOCK_SUCCESS)
return FALSE
damage = block_calculate_resultant_damage(damage, block_return)
if (stat != DEAD)
log_combat(M, src, "attacked")
@@ -330,13 +343,16 @@
M.visible_message("<span class='notice'>\The [M] [M.friendly_verb_continuous] [src]!</span>",
"<span class='notice'>You [M.friendly_verb_simple] [src]!</span>", target = src,
target_message = "<span class='notice'>\The [M] [M.friendly_verb_continuous] you!</span>")
return FALSE
return 0
else
if(HAS_TRAIT(M, TRAIT_PACIFISM))
to_chat(M, "<span class='notice'>You don't want to hurt anyone!</span>")
return FALSE
if(mob_run_block(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected), null) & BLOCK_SUCCESS)
return FALSE
var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
var/list/return_list = list()
if(mob_run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected), return_list) & BLOCK_SUCCESS)
return 0
damage = block_calculate_resultant_damage(damage, return_list)
if(M.attack_sound)
playsound(loc, M.attack_sound, 50, 1, 1)
M.do_attack_animation(src)
@@ -344,7 +360,7 @@
"<span class='userdanger'>\The [M] [M.attack_verb_continuous] you!</span>", null, COMBAT_MESSAGE_RANGE, null,
M, "<span class='danger'>You [M.attack_verb_simple] [src]!</span>")
log_combat(M, src, "attacked")
return TRUE
return damage
/mob/living/attack_paw(mob/living/carbon/monkey/M)
if (M.a_intent == INTENT_HARM)
+22
View File
@@ -8,6 +8,8 @@
typing_indicator_enabled = TRUE
var/last_click_move = 0 // Stores the previous next_move value.
var/resize = 1 //Badminnery resize
var/lastattacker = null
var/lastattackerckey = null
@@ -27,6 +29,26 @@
var/mobility_flags = MOBILITY_FLAGS_DEFAULT
// Combat - Blocking/Parrying system
/// Our block_parry_data for unarmed blocks/parries. Currently only used for parrying, as unarmed block isn't implemented yet. YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING!
var/datum/block_parry_data/block_parry_data = /datum/block_parry_data // defaults to *something* because [combat_flags] dictates whether or not we can unarmed block/parry.
// Blocking
/// The item the user is actively blocking with if any.
var/obj/item/active_block_item
// Parrying
/// Whether or not the user is in the middle of an active parry. Set to [UNARMED_PARRY], [ITEM_PARRY], [MARTIAL_PARRY] if parrying.
var/parrying = FALSE
/// The itme the user is currently parrying with, if any.
var/obj/item/active_parry_item
/// world.time of parry action start
var/parry_start_time = 0
/// Current parry effect.
var/obj/effect/abstract/parry/parry_visual_effect
/// world.time of last parry end
var/parry_end_time_last = 0
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
var/list/successful_parries
var/confused = 0 //Makes the mob move in random directions.
var/hallucination = 0 //Directly affects how long a mob will hallucinate for

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