diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 6ae1dd3c00c..14f2e01d81c 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -501,6 +501,8 @@ #define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" ///Called when a movable is hit by a plunger in layer mode, from /obj/item/plunger/attack_atom() #define COMSIG_MOVABLE_CHANGE_DUCT_LAYER "movable_change_duct_layer" +///Called when a movable is teleported from `do_teleport()`: (destination, channel) +#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" // /mob signals @@ -1015,6 +1017,8 @@ ///called in /obj/item/gun/process_fire (user, target, params, zone_override) #define COMSIG_GRENADE_DETONATE "grenade_prime" +//called from many places in grenade code (armed_by, nade, det_time, delayoverride) +#define COMSIG_MOB_GRENADE_ARMED "grenade_mob_armed" ///called in /obj/item/gun/process_fire (user, target, params, zone_override) #define COMSIG_GRENADE_ARMED "grenade_armed" diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 269af6146aa..cafed708aab 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -485,18 +485,6 @@ GLOBAL_LIST_INIT(pda_styles, sortList(list(MONO, VT, ORBITRON, SHARE))) #define FALL_NO_MESSAGE (1<<1) //Used to suppress the "[A] falls through [old_turf]" messages where it'd make little sense at all, like going downstairs. #define FALL_STOP_INTERCEPTING (1<<2) //Used in situations where halting the whole "intercept" loop would be better, like supermatter dusting (and thus deleting) the atom. -//Religion -///role below priests, for losing most powers of priests but still being holy. -#define HOLY_ROLE_DEACON 1 -///default priestly role -#define HOLY_ROLE_PRIEST 2 -///the one who designates the religion -#define HOLY_ROLE_HIGHPRIEST 3 - -#define ALIGNMENT_GOOD "good" -#define ALIGNMENT_NEUT "neutral" -#define ALIGNMENT_EVIL "evil" - // Play time / EXP #define PLAYTIME_HARDCORE_RANDOM 120 diff --git a/code/__DEFINES/religion.dm b/code/__DEFINES/religion.dm new file mode 100644 index 00000000000..0db0961c12d --- /dev/null +++ b/code/__DEFINES/religion.dm @@ -0,0 +1,50 @@ +///role below priests, for losing most powers of priests but still being holy. +#define HOLY_ROLE_DEACON 1 +///default priestly role +#define HOLY_ROLE_PRIEST 2 +///the one who designates the religion +#define HOLY_ROLE_HIGHPRIEST 3 + +#define ALIGNMENT_GOOD "good" +#define ALIGNMENT_NEUT "neutral" +#define ALIGNMENT_EVIL "evil" + +//## which weapons should we use? + +// unused but for clarity +#define CONDITION_FIST_FIGHT 1 +///can only use the ritual weapons the sparring chaplain makes. +#define CONDITION_CEREMONIAL_ONLY 2 +///melee weapon condition, default sparring condition. +#define CONDITION_MELEE_ONLY 3 +///any weapon is cool... probably a terrible idea against security +#define CONDITION_ANY_WEAPON 4 + +// +///must use weapons the chaplain makes from their sect. no fist fighting, even! it ensures a fair fight. +// #define RITUAL_WEAPONS 2 + +//## where should we fight? + +// default value - /area/service/chapel + +//## what are the stakes? people you've beaten before can only fight in no stakes battles, to prevent farming + +///just for fun +#define STAKES_NONE 1 +///standard stakes, winning gets you a point. losing counts towards standard excommunication. +#define STAKES_HOLY_MATCH 2 +///no stakes god wise, but whomever wins gets all the money of the other +#define STAKES_MONEY_MATCH 3 +///the winner gets the other's soul. you said this was a neutral sect, right? +#define STAKES_YOUR_SOUL 4 + +///the left signing part of the contract +#define CONTRACT_LEFT_FIELD "left" + +///curses the sinner +#define PUNISHMENT_OMEN "omen" +///smites the sinner +#define PUNISHMENT_LIGHTNING "lightningbolt" +///brands the sinner +#define PUNISHMENT_BRAND "brand" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index a6e9fdc1b29..70f7bf2a700 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -325,6 +325,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures" /// Someone can safely be attacked with honorbound with ONLY a combat mode check, the trait is assuring holding a weapon and hitting won't hurt them.. #define TRAIT_ALLOWED_HONORBOUND_ATTACK "allowed_honorbound_attack" +/// The user is sparring +#define TRAIT_SPARRING "sparring" #define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed /// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met @@ -526,6 +528,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// If present on a [/mob/living/carbon], will make them appear to have a medium level disease on health HUDs. #define TRAIT_DISEASELIKE_SEVERITY_MEDIUM "diseaselike_severity_medium" +/// trait denoting someone will crawl faster in soft crit +#define TRAIT_TENACIOUS "tenacious" + +/// trait denoting someone will sometimes recover out of crit +#define TRAIT_UNBREAKABLE "unbreakable" + //Medical Categories for quirks #define CAT_QUIRK_ALL 0 #define CAT_QUIRK_NOTES 1 diff --git a/code/datums/components/unbreakable.dm b/code/datums/components/unbreakable.dm new file mode 100644 index 00000000000..8d75c563d57 --- /dev/null +++ b/code/datums/components/unbreakable.dm @@ -0,0 +1,31 @@ +/datum/component/unbreakable + COOLDOWN_DECLARE(surge_cooldown) + +/datum/component/unbreakable/Initialize() + if(!ishuman(parent)) + return COMPONENT_INCOMPATIBLE + ADD_TRAIT(parent, TRAIT_UNBREAKABLE, INNATE_TRAIT) + +/datum/component/unbreakable/Destroy(force, silent) + . = ..() + REMOVE_TRAIT(parent, TRAIT_UNBREAKABLE, INNATE_TRAIT) + +/datum/component/unbreakable/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_STATCHANGE, .proc/surge) + +/datum/component/unbreakable/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_MOB_STATCHANGE) + +/datum/component/unbreakable/proc/surge(mob/living/carbon/human/surged, new_stat) + SIGNAL_HANDLER + if(new_stat < SOFT_CRIT || new_stat >= DEAD) + return + if(!COOLDOWN_FINISHED(src, surge_cooldown)) + return + COOLDOWN_START(src, surge_cooldown, 1 MINUTES) + surged.balloon_alert(surged, "you refuse to give up!")//breaks balloon alert conventions by using a "!" for a fail message but that's okay because it's a pretty awesome moment + surged.heal_overall_damage(15, 15, 0, BODYPART_ORGANIC) + if(surged.reagents.get_reagent_amount(/datum/reagent/medicine/ephedrine) < 20) + surged.reagents.add_reagent(/datum/reagent/medicine/ephedrine, 10) + if(surged.reagents.get_reagent_amount(/datum/reagent/medicine/epinephrine) < 20) + surged.reagents.add_reagent(/datum/reagent/medicine/epinephrine, 10) diff --git a/code/datums/elements/tenacious.dm b/code/datums/elements/tenacious.dm new file mode 100644 index 00000000000..c917dc7518a --- /dev/null +++ b/code/datums/elements/tenacious.dm @@ -0,0 +1,33 @@ +/** + * tenacious element; which makes the parent move faster while crawling + * + * Used by sparring sect! + */ +/datum/element/tenacious + element_flags = ELEMENT_DETACH + +/datum/element/tenacious/Attach(datum/target) + . = ..() + + if(!ishuman(target)) + return COMPONENT_INCOMPATIBLE + var/mob/living/carbon/human/valid_target = target + on_stat_change(valid_target, new_stat = valid_target.stat) //immediately try adding movement bonus if they're in soft crit + RegisterSignal(target, COMSIG_MOB_STATCHANGE, .proc/on_stat_change) + ADD_TRAIT(target, TRAIT_TENACIOUS, INNATE_TRAIT) + +/datum/element/tenacious/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_MOB_STATCHANGE) + REMOVE_TRAIT(target, TRAIT_TENACIOUS, INNATE_TRAIT) + +///signal called by the stat of the target changing +/datum/element/tenacious/proc/on_stat_change(mob/living/carbon/human/target, new_stat) + SIGNAL_HANDLER + + if(new_stat == SOFT_CRIT) + target.balloon_alert(target, "your tenacity kicks in") + target.add_movespeed_modifier(/datum/movespeed_modifier/tenacious) + else + target.balloon_alert(target, "your tenacity wears off") + target.remove_movespeed_modifier(/datum/movespeed_modifier/tenacious) diff --git a/code/datums/greyscale/config_types/greyscale_configs.dm b/code/datums/greyscale/config_types/greyscale_configs.dm index 7d1759b99ea..4d94dc6a7c9 100644 --- a/code/datums/greyscale/config_types/greyscale_configs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs.dm @@ -283,6 +283,21 @@ icon_file = 'icons/obj/items/cleric_mace.dmi' json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_worn_gold.json' +/datum/greyscale_config/ceremonial_blade + name = "Base Ceremonial Blade" + icon_file = 'icons/obj/items/ritual_weapon.dmi' + json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade.json' + +/datum/greyscale_config/ceremonial_blade_lefthand + name = "Base Held Ceremonial Blade, Left" + icon_file = 'icons/obj/items/ritual_weapon.dmi' + json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade_lefthand.json' + +/datum/greyscale_config/ceremonial_blade_righthand + name = "Base Held Ceremonial Blade, Right" + icon_file = 'icons/obj/items/ritual_weapon.dmi' + json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade_righthand.json' + /datum/greyscale_config/beret name = "Beret" icon_file = 'icons/obj/clothing/head/beret.dmi' diff --git a/code/datums/greyscale/json_configs/items/ceremonial_blade.json b/code/datums/greyscale/json_configs/items/ceremonial_blade.json new file mode 100644 index 00000000000..16360a5e889 --- /dev/null +++ b/code/datums/greyscale/json_configs/items/ceremonial_blade.json @@ -0,0 +1,15 @@ +{ + "default": [ + { + "type": "icon_state", + "icon_state": "base", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "handle", + "blend_mode": "overlay" + } + ] +} diff --git a/code/datums/greyscale/json_configs/items/ceremonial_blade_lefthand.json b/code/datums/greyscale/json_configs/items/ceremonial_blade_lefthand.json new file mode 100644 index 00000000000..aafdb2d897e --- /dev/null +++ b/code/datums/greyscale/json_configs/items/ceremonial_blade_lefthand.json @@ -0,0 +1,10 @@ +{ + "default": [ + { + "type": "icon_state", + "icon_state": "inhand_left", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/greyscale/json_configs/items/ceremonial_blade_righthand.json b/code/datums/greyscale/json_configs/items/ceremonial_blade_righthand.json new file mode 100644 index 00000000000..d80b0c49a25 --- /dev/null +++ b/code/datums/greyscale/json_configs/items/ceremonial_blade_righthand.json @@ -0,0 +1,10 @@ +{ + "default": [ + { + "type": "icon_state", + "icon_state": "inhand_right", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index b35000f9b94..b07a28a07e8 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -26,6 +26,8 @@ if (isnull(precision)) precision = 0 + SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, destination, channel) + switch(channel) if(TELEPORT_CHANNEL_BLUESPACE) if(istype(teleatom, /obj/item/storage/backpack/holding)) diff --git a/code/datums/mutations/holy_mutation/honorbound.dm b/code/datums/mutations/holy_mutation/honorbound.dm index 18746918961..0b97be7e468 100644 --- a/code/datums/mutations/holy_mutation/honorbound.dm +++ b/code/datums/mutations/holy_mutation/honorbound.dm @@ -199,15 +199,6 @@ lightningbolt(user) SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "honorbound", /datum/mood_event/holy_smite)//permanently lose your moodlet after this -/datum/mutation/human/honorbound/proc/lightningbolt(mob/living/user) - var/turf/lightning_source = get_step(get_step(user, NORTH), NORTH) - lightning_source.Beam(user, icon_state="lightning[rand(1,12)]", time = 5) - user.adjustFireLoss(LIGHTNING_BOLT_DAMAGE) - playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, TRUE) - if(ishuman(user)) - var/mob/living/carbon/human/human_target = user - human_target.electrocution_animation(LIGHTNING_BOLT_ELECTROCUTION_ANIMATION_LENGTH) - /obj/effect/proc_holder/spell/pointed/declare_evil name = "Declare Evil" desc = "If someone is so obviously an evil of this world you can spend a huge amount of favor to declare them guilty." diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index bc25484ac6b..18bca49b06b 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -299,3 +299,10 @@ infestation_rate = 0.075 // appx 4.33 minutes to reach sepsis without any treatment flesh_damage = 20 scar_keyword = "burncritical" + +///special severe wound caused by sparring interference or other god related punishments. +/datum/wound/burn/severe/brand + name = "Holy Brand" + desc = "Patient is suffering extreme burns from a strange brand marking, creating serious risk of infection and greatly reduced limb integrity." + examine_desc = "appears to have holy symbols painfully branded into their flesh, leaving severe burns." + occur_text = "chars rapidly into a strange pattern of holy symbols, burned into the flesh." diff --git a/code/game/objects/items/grenades/atmos_grenades.dm b/code/game/objects/items/grenades/atmos_grenades.dm index cb0e90515f8..2a4ff608b8d 100644 --- a/code/game/objects/items/grenades/atmos_grenades.dm +++ b/code/game/objects/items/grenades/atmos_grenades.dm @@ -19,6 +19,8 @@ icon_state = initial(icon_state) + "_active" playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', volume, TRUE) SEND_SIGNAL(src, COMSIG_GRENADE_ARMED, det_time, delayoverride) + if(user) + SEND_SIGNAL(src, COMSIG_MOB_GRENADE_ARMED, user, src, det_time, delayoverride) addtimer(CALLBACK(src, .proc/detonate), isnull(delayoverride)? det_time : delayoverride) /obj/item/grenade/gas_crystal/healium_crystal diff --git a/code/modules/admin/smites/lightning.dm b/code/modules/admin/smites/lightning.dm index 27cab44e2d0..660af779f9b 100644 --- a/code/modules/admin/smites/lightning.dm +++ b/code/modules/admin/smites/lightning.dm @@ -4,11 +4,15 @@ /datum/smite/lightning/effect(client/user, mob/living/target) . = ..() - var/turf/lightning_source = get_step(get_step(target, NORTH), NORTH) - lightning_source.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) - target.adjustFireLoss(LIGHTNING_BOLT_DAMAGE) - playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, TRUE) - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - human_target.electrocution_animation(LIGHTNING_BOLT_ELECTROCUTION_ANIMATION_LENGTH) + lightningbolt(target) to_chat(target, span_userdanger("The gods have punished you for your sins!"), confidential = TRUE) + +///this is the actual bolt effect and damage, made into its own proc because it is used elsewhere +/proc/lightningbolt(mob/living/user) + var/turf/lightning_source = get_step(get_step(user, NORTH), NORTH) + lightning_source.Beam(user, icon_state="lightning[rand(1,12)]", time = 5) + user.adjustFireLoss(LIGHTNING_BOLT_DAMAGE) + playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, TRUE) + if(ishuman(user)) + var/mob/living/carbon/human/human_target = user + human_target.electrocution_animation(LIGHTNING_BOLT_ELECTROCUTION_ANIMATION_LENGTH) diff --git a/code/modules/antagonists/monkey/monkey.dm b/code/modules/antagonists/monkey/monkey.dm index 8e60c761550..6132ab4d202 100644 --- a/code/modules/antagonists/monkey/monkey.dm +++ b/code/modules/antagonists/monkey/monkey.dm @@ -123,8 +123,8 @@ /datum/antagonist/monkey/leader/on_gain() . = ..() - var/obj/item/organ/heart/freedom/F = new - F.Insert(owner.current, drop_if_replaced = FALSE) + var/obj/item/organ/heart/freedom/super_heart = new + super_heart.Insert(owner.current, drop_if_replaced = FALSE) owner.special_role = "Monkey Leader" /datum/antagonist/monkey/leader/on_removal() diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 9e993608f09..52e4872c39e 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -88,6 +88,15 @@ one_use = TRUE grab_sleeping = FALSE +/obj/item/soulstone/anybody/chaplain/sparring + icon_state = "purified_soulstone" + theme = THEME_HOLY + +/obj/item/soulstone/anybody/sparring/Initialize(mapload) + . = ..() + name = "[GLOB.deity]'s punishment" + desc = "A prison for those who lost [GLOB.deity]'s game." + /obj/item/soulstone/anybody/mining grab_sleeping = FALSE @@ -260,7 +269,8 @@ return TRUE else to_chat(user, "[span_userdanger("Capture failed!")]: The soul has already fled its mortal frame. You attempt to bring it back...") - return getCultGhost(victim,user) + INVOKE_ASYNC(src, .proc/getCultGhost, victim, user) + return TRUE //it'll probably get someone ;) ///captures a shade that was previously released from a soulstone. /obj/item/soulstone/proc/capture_shade(mob/living/simple_animal/shade/shade, mob/user) diff --git a/code/modules/movespeed/modifiers/components.dm b/code/modules/movespeed/modifiers/components.dm index 1a7aff3e0f8..30b41e89152 100644 --- a/code/modules/movespeed/modifiers/components.dm +++ b/code/modules/movespeed/modifiers/components.dm @@ -7,6 +7,10 @@ multiplicative_slowdown = -7 movetypes = GROUND +/datum/movespeed_modifier/tenacious + multiplicative_slowdown = -0.7 + movetypes = GROUND + /datum/movespeed_modifier/sanity id = MOVESPEED_ID_SANITY movetypes = (~FLYING) diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 8a891c3483d..f241c5c4e45 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -377,3 +377,32 @@ return TRUE #undef MINIMUM_YUCK_REQUIRED + +/datum/religion_sect/spar + name = "Sparring God" + quote = "Your next swing must be faster, neophyte. Steel your heart." + desc = "Spar other crewmembers to gain favor or other rewards. Exchange favor to steel yourself against real battles." + tgui_icon = "fist-raised" + altar_icon_state = "convertaltar-orange" + alignment = ALIGNMENT_NEUT + rites_list = list( + /datum/religion_rites/sparring_contract, + /datum/religion_rites/ceremonial_weapon, + /datum/religion_rites/declare_arena, + /datum/religion_rites/tenacious, + /datum/religion_rites/unbreakable, + ) + ///the one allowed contract. making a new contract dusts the old one + var/obj/item/sparring_contract/existing_contract + ///places you can spar in. rites can be used to expand this list with new arenas! + var/list/arenas = list( + "Recreation Area" = /area/commons/fitness/recreation, + "Chapel" = /area/service/chapel + ) + ///how many matches you've lost with holy stakes. 3 = excommunication + var/matches_lost = 0 + ///past opponents who you've beaten in holy battles. You can't fight them again to prevent favor farming + var/list/past_opponents = list() + +/datum/religion_sect/spar/tool_examine(mob/living/holy_creature) + return "You have [round(favor)] sparring matches won in [GLOB.deity]'s name to redeem. You have lost [matches_lost] holy matches. You will be excommunicated after losing three matches." diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index 9e0d22ce6e8..f639bd43a6d 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -117,7 +117,7 @@ ritual_invocations =list( "Let your will power our forges.", "...Help us in our great conquest!") invoke_msg = "The end of flesh is near!" - favor_cost = 2000 + favor_cost = 2000 /datum/religion_rites/machine_blessing/invoke_effect(mob/living/user, atom/movable/religious_tool) ..() @@ -603,4 +603,141 @@ new /obj/item/ritual_totem(altar_turf) return TRUE +///sparring god rites +/datum/religion_rites/sparring_contract + name = "Summon Sparring Contract" + desc = "Turns some paper into a sparring contract." + invoke_msg = "I will train in the name of my god." + ///paper to turn into a sparring contract + var/obj/item/paper/contract_target + +/datum/religion_rites/sparring_contract/perform_rite(mob/living/user, atom/religious_tool) + for(var/obj/item/paper/could_contract in get_turf(religious_tool)) + if(could_contract.info) //blank paper pls + continue + contract_target = could_contract + return ..() + to_chat(user, span_warning("You need to place blank paper on [religious_tool] to do this!")) + return FALSE + +/datum/religion_rites/sparring_contract/invoke_effect(mob/living/user, atom/movable/religious_tool) + ..() + var/obj/item/paper/blank_paper = contract_target + var/turf/tool_turf = get_turf(religious_tool) + contract_target = null + if(QDELETED(blank_paper) || !(tool_turf == blank_paper.loc)) //check if the same paper is still there + to_chat(user, span_warning("Your target left the altar!")) + return FALSE + blank_paper.visible_message(span_notice("words magically form on [blank_paper]!")) + playsound(tool_turf, 'sound/effects/pray.ogg', 50, TRUE) + var/datum/religion_sect/spar/sect = GLOB.religious_sect + if(sect.existing_contract) + sect.existing_contract.visible_message(span_warning("[src] fizzles into nothing!")) + qdel(sect.existing_contract) + sect.existing_contract = new /obj/item/sparring_contract(tool_turf) + qdel(blank_paper) + return TRUE + +/datum/religion_rites/declare_arena + name = "Declare Arena" + desc = "Declare a new area as fit for sparring. You'll be able to select it in contracts." + ritual_length = 6 SECONDS + ritual_invocations = list("I seek new horizons ...") + invoke_msg = "... may my climb be steep." + favor_cost = 1 //only costs one holy battle for a new area + var/area/area_instance + +/datum/religion_rites/declare_arena/perform_rite(mob/living/user, atom/religious_tool) + var/list/filtered = list() + for(var/area/unfiltered_area as anything in GLOB.sortedAreas) + if(istype(unfiltered_area, /area/centcom)) //youuu dont need thaaat + continue + if(!(unfiltered_area.area_flags & HIDDEN_AREA)) + filtered += unfiltered_area + area_instance = tgui_input_list(user, "Choose an area to mark as an arena!", "Arena Declaration", filtered) + if(!area_instance) + return FALSE + . = ..() + +/datum/religion_rites/declare_arena/invoke_effect(mob/living/user, atom/movable/religious_tool) + . = ..() + var/datum/religion_sect/spar/sect = GLOB.religious_sect + sect.arenas[area_instance.name] = area_instance.type + to_chat(user, span_warning("[area_instance] is a now an option to select on sparring contracts.")) + +/datum/religion_rites/ceremonial_weapon + name = "Forge Ceremonial Gear" + desc = "Turn some material into ceremonial gear. Ceremonial blades are weak outside of sparring, and are quite heavy to lug around." + ritual_length = 10 SECONDS + invoke_msg = "Weapons in your name! Battles with your blood!" + favor_cost = 0 + ///the material that will be attempted to be forged into a weapon + var/obj/item/stack/sheet/converted + +/datum/religion_rites/ceremonial_weapon/perform_rite(mob/living/user, atom/religious_tool) + for(var/obj/item/stack/sheet/could_blade in get_turf(religious_tool)) + if(!(GET_MATERIAL_REF(could_blade.material_type) in SSmaterials.materials_by_category[MAT_CATEGORY_ITEM_MATERIAL])) + continue + if(could_blade.amount < 5) + continue + converted = could_blade + return ..() + to_chat(user, span_warning("You need at least 5 sheets of a material that can be made into items!")) + return FALSE + +/datum/religion_rites/ceremonial_weapon/invoke_effect(mob/living/user, atom/movable/religious_tool) + ..() + var/altar_turf = get_turf(religious_tool) + var/obj/item/stack/sheet/used_for_blade = converted + converted = null + if(QDELETED(used_for_blade) || !(get_turf(religious_tool) == used_for_blade.loc) || used_for_blade.amount < 5) //check if the same food is still there + to_chat(user, span_warning("Your target left the altar!")) + return FALSE + var/material_used = used_for_blade.material_type + to_chat(user, span_warning("[used_for_blade] reshapes into a ceremonial blade!")) + if(!used_for_blade.use(5))//use 5 of the material + return + var/obj/item/ceremonial_blade/blade = new(altar_turf) + blade.set_custom_materials(list(GET_MATERIAL_REF(material_used) = MINERAL_MATERIAL_AMOUNT * 5)) + return TRUE + +/datum/religion_rites/unbreakable + name = "Become Unbreakable" + desc = "Your training has made you unbreakable. In times of crisis, you will attempt to keep fighting on." + ritual_length = 10 SECONDS + invoke_msg = "My will must be unbreakable. Grant me this boon!" + favor_cost = 4 //4 duels won + +/datum/religion_rites/unbreakable/perform_rite(mob/living/carbon/human/user, atom/religious_tool) + if(!ishuman(user)) + return FALSE + if(HAS_TRAIT_FROM(user, TRAIT_UNBREAKABLE, INNATE_TRAIT)) + to_chat(user, span_warning("Your spirit is already unbreakable!")) + return FALSE + return ..() + +/datum/religion_rites/unbreakable/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) + ..() + to_chat(user, span_nicegreen("You feel [GLOB.deity]'s will to keep fighting pouring into you!")) + user.AddComponent(/datum/component/unbreakable) + +/datum/religion_rites/tenacious + name = "Become Tenacious" + desc = "Your training has made you tenacious. In times of crisis, you will be able to crawl faster." + ritual_length = 10 SECONDS + invoke_msg = "Grant me your tenacity! I have proven myself!" + favor_cost = 3 //3 duels won + +/datum/religion_rites/tenacious/perform_rite(mob/living/carbon/human/user, atom/religious_tool) + if(!ishuman(user)) + return FALSE + if(HAS_TRAIT_FROM(user, TRAIT_TENACIOUS, INNATE_TRAIT)) + to_chat(user, span_warning("Your spirit is already tenacious!")) + return FALSE + return ..() + +/datum/religion_rites/tenacious/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) + ..() + to_chat(user, span_nicegreen("You feel [GLOB.deity]'s tenacity pouring into you!")) + user.AddElement(/datum/element/tenacious) diff --git a/code/modules/religion/sparring/ceremonial_gear.dm b/code/modules/religion/sparring/ceremonial_gear.dm new file mode 100644 index 00000000000..7814bc24017 --- /dev/null +++ b/code/modules/religion/sparring/ceremonial_gear.dm @@ -0,0 +1,56 @@ +///ritual weapons. they're really bad, but they become normal weapons when sparring. +/obj/item/ceremonial_blade + name = "ceremonial blade" + desc = "A blade created to spar with. It seems weak, but if you spar with it...?" + icon_state = "default" + inhand_icon_state = "default" + icon = 'icons/obj/items/ritual_weapon.dmi' + + //does the exact thing we want so heck why not + greyscale_config = /datum/greyscale_config/ceremonial_blade + greyscale_config_inhand_left = /datum/greyscale_config/ceremonial_blade_lefthand + greyscale_config_inhand_right = /datum/greyscale_config/ceremonial_blade_righthand + greyscale_colors = "#FFFFFF" + + hitsound = 'sound/weapons/bladeslice.ogg' + custom_materials = list(/datum/material/iron = 12000) //Defaults to an Iron blade. + force = 2 //20 + throwforce = 1 //10 + w_class = WEIGHT_CLASS_NORMAL + attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") + attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut") + block_chance = 3 //30 + sharpness = SHARP_EDGED + max_integrity = 200 + material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE //doesn't affect stats of the weapon as to avoid gamering your opponent with a dope weapon + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50) + resistance_flags = FIRE_PROOF + +/obj/item/ceremonial_blade/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 40, 105) + RegisterSignal(src, COMSIG_ITEM_SHARPEN_ACT, .proc/block_sharpening) + +/obj/item/ceremonial_blade/melee_attack_chain(mob/user, atom/target, params) + if(!HAS_TRAIT(target, TRAIT_SPARRING)) + return ..() + var/old_force = force + var/old_throwforce = throwforce + force *= 10 + throwforce *= 10 + . = ..() + force = old_force + throwforce = old_throwforce + +/obj/item/ceremonial_blade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + if(attack_type != MELEE_ATTACK || !ishuman(hitby.loc)) + return ..() + if(HAS_TRAIT(hitby.loc, TRAIT_SPARRING)) + //becomes 30 block + final_block_chance *= 10 + . = ..() + +/obj/item/ceremonial_blade/proc/block_sharpening(datum/source, increment, max) + SIGNAL_HANDLER + //this breaks it + return COMPONENT_BLOCK_SHARPEN_BLOCKED diff --git a/code/modules/religion/sparring/sparring_contract.dm b/code/modules/religion/sparring/sparring_contract.dm new file mode 100644 index 00000000000..0b7a4d05f2f --- /dev/null +++ b/code/modules/religion/sparring/sparring_contract.dm @@ -0,0 +1,145 @@ +/obj/item/sparring_contract + desc = "A contract for setting up sparring matches. Both sparring partners must agree with the terms to begin." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll" + drop_sound = 'sound/items/handling/paper_drop.ogg' + pickup_sound = 'sound/items/handling/paper_pickup.ogg' + throw_range = 1 + throw_speed = 1 + w_class = WEIGHT_CLASS_TINY + ///what weapons will be allowed during the sparring match + var/weapons_condition = CONDITION_MELEE_ONLY + ///what arena the fight will take place in + var/arena_condition = /area/service/chapel + ///what stakes the fight will have + var/stakes_condition = STAKES_NONE + ///who has signed this contract. fills itself with WEAKREFS, to prevent hanging references + var/list/datum/weakref/signed_by = list(null, null) + +/obj/item/sparring_contract/Initialize() + . = ..() + name = "[GLOB.deity]'s sparring contract" + +/obj/item/sparring_contract/Destroy() + QDEL_NULL(signed_by) + var/datum/religion_sect/spar/sect = GLOB.religious_sect + sect?.existing_contract = null + . = ..() + +/obj/item/sparring_contract/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SparringContract", name) + ui.open() + +/obj/item/sparring_contract/ui_static_data(mob/user) + var/list/data = list() + var/area/arena = GLOB.areas_by_type[arena_condition] + data["set_weapon"] = weapons_condition + data["set_area"] = arena?.name + data["set_stakes"] = stakes_condition + data["possible_areas"] = get_possible_areas() + + return data + +/obj/item/sparring_contract/ui_data(mob/user) + var/list/data = list() + var/area/arena = GLOB.areas_by_type[arena_condition] + var/mob/living/carbon/human/left_partner + if(signed_by[1]) + left_partner = signed_by[1].resolve() + var/mob/living/carbon/human/right_partner + if(signed_by[2]) + right_partner = signed_by[2].resolve() + data["in_area"] = ((left_partner && right_partner && arena) && (left_partner in arena.contents) && (right_partner in arena.contents)) + data["no_chaplains"] = (!left_partner?.mind?.holy_role && !right_partner?.mind?.holy_role) + data["left_sign"] = left_partner ? left_partner.real_name : "none" + data["right_sign"] = right_partner ? right_partner.real_name : "none" + return data + +/obj/item/sparring_contract/proc/get_possible_areas() + var/list/area_names = list() + var/datum/religion_sect/spar/sect = GLOB.religious_sect + for(var/key in sect.arenas) + area_names += key + return area_names + +/obj/item/sparring_contract/ui_act(action, list/params) + . = ..() + if(.) + return + + var/mob/user = usr + + if(!ishuman(user)) + to_chat(user, span_warning("This contract refuses to be signed by a lesser creature such as yourself.")) + return + + var/datum/religion_sect/spar/sect = GLOB.religious_sect + + var/list/resolved_opponents = list() + for(var/datum/weakref/resolve_me as anything in sect.past_opponents) + var/resolved = resolve_me.resolve() + if(!isnull(resolved)) + resolved_opponents += resolved + + if(user in resolved_opponents && params["stakes"] == STAKES_HOLY_MATCH) + to_chat(user, span_warning("This contract refuses to be signed up for a holy match by a previous holy match loser. Pick a different stake!")) + + //any updating of the terms should update the UI to display new terms + . = TRUE + + var/mob/living/carbon/human/left_partner + if(signed_by[1]) + left_partner = signed_by[1].resolve() + var/mob/living/carbon/human/right_partner + if(signed_by[2]) + right_partner = signed_by[2].resolve() + + switch(action) + if("clear") + signed_by = list(null, null)//remove weakrefs + if("fight") + if(!left_partner || !right_partner || !left_partner.mind || !right_partner.mind) + return + if(HAS_TRAIT(left_partner, TRAIT_SPARRING) || HAS_TRAIT(right_partner, TRAIT_SPARRING)) + to_chat(user, span_warning("One participant is already sparring!")) + return + var/chaplain = left_partner.mind.holy_role ? left_partner : right_partner + var/opponent = left_partner.mind.holy_role ? right_partner : left_partner + new /datum/sparring_match(weapons_condition, GLOB.areas_by_type[arena_condition], stakes_condition, chaplain, opponent) + qdel(src) + if("sign") + if(user == left_partner || user == right_partner) + to_chat(user, span_warning("You've already signed one side of the contract.")) + return + var/area/arena_condition_name = GLOB.areas_by_type[arena_condition] + arena_condition_name = format_text(arena_condition_name.name) + //setting/checking for terms changed + var/terms_changed = FALSE + if(params["weapon"] != weapons_condition) + if(!params["weapon"]) + return //they hit f5 to clear data then submitted + terms_changed = TRUE + weapons_condition = params["weapon"] + if(params["area"] != arena_condition_name) + if(!params["area"]) + return //they hit f5 to clear data then submitted + terms_changed = TRUE + var/new_area_condition = sect.arenas[params["area"]] + arena_condition = new_area_condition + if(params["stakes"] != stakes_condition) + if(!params["stakes"]) + return //they hit f5 to clear data then submitted + terms_changed = TRUE + stakes_condition = params["stakes"] + //if you change the terms you have to get the other person to sign again. + if(terms_changed && (left_partner || right_partner)) + signed_by = list(null, null)//remove weakrefs + to_chat(user, span_warning("You will need to get your sparring partner to sign again under these new terms you've set.")) + //fluff and signing + var/datum/weakref/user_ref = WEAKREF(user) + if(params["sign_position"] == CONTRACT_LEFT_FIELD) + signed_by[1] = user_ref + else + signed_by[2] = user_ref diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm new file mode 100644 index 00000000000..391b8378e16 --- /dev/null +++ b/code/modules/religion/sparring/sparring_datum.dm @@ -0,0 +1,304 @@ +/datum/sparring_match + ///the chaplain. it isn't actually a chaplain all the time, but in the cases where the chaplain is needed this will always be them. + var/mob/living/carbon/human/chaplain + ///the other fighter + var/mob/living/carbon/human/opponent + ///what weapons will be allowed during the sparring match + var/weapons_condition + ///area instance the participants must stay in + var/area/arena_condition + ///what stakes the fight will have + var/stakes_condition + ///cheats from the chaplain + var/chaplain_violations_allowed = 2 + ///cheats from the non-chaplain + var/opponent_violations_allowed = 2 + ///outside interventions that ruin the match + var/flubs = 2 + +/datum/sparring_match/New(weapons_condition, arena_condition, stakes_condition, mob/living/carbon/human/chaplain, mob/living/carbon/human/opponent) + . = ..() + src.weapons_condition = weapons_condition + src.arena_condition = arena_condition + src.stakes_condition = stakes_condition + src.chaplain = chaplain + src.opponent = opponent + ADD_TRAIT(chaplain, TRAIT_SPARRING, TRAIT_GENERIC) + ADD_TRAIT(opponent, TRAIT_SPARRING, TRAIT_GENERIC) + hook_signals(chaplain) + hook_signals(opponent) + chaplain.add_filter("sparring_outline", 9, list("type" = "outline", "color" = "#e02200")) + opponent.add_filter("sparring_outline", 9, list("type" = "outline", "color" = "#004ee0")) + +/datum/sparring_match/proc/hook_signals(mob/living/carbon/human/sparring) + //weapon conditions + if(weapons_condition < CONDITION_ANY_WEAPON) + RegisterSignal(sparring, COMSIG_MOB_FIRED_GUN, .proc/gun_violation) + RegisterSignal(sparring, COMSIG_MOB_GRENADE_ARMED, .proc/grenade_violation) + if(weapons_condition <= CONDITION_CEREMONIAL_ONLY) + RegisterSignal(sparring, COMSIG_PARENT_ATTACKBY, .proc/melee_violation) + //arena conditions + RegisterSignal(sparring, COMSIG_MOVABLE_MOVED, .proc/arena_violation) + //severe violations (insta violation win for other party) conditions + RegisterSignal(sparring, COMSIG_MOVABLE_TELEPORTED, .proc/teleport_violation) + //win conditions + RegisterSignal(sparring, COMSIG_MOB_STATCHANGE, .proc/check_for_victory) + //flub conditions + RegisterSignal(sparring, COMSIG_PARENT_ATTACKBY, .proc/outsider_interference) + RegisterSignal(sparring, COMSIG_ATOM_HULK_ATTACK, .proc/hulk_interference) + RegisterSignal(sparring, COMSIG_ATOM_ATTACK_HAND, .proc/hand_interference) + RegisterSignal(sparring, COMSIG_ATOM_ATTACK_PAW, .proc/paw_interference) + RegisterSignal(sparring, COMSIG_ATOM_HITBY, .proc/thrown_interference) + RegisterSignal(sparring, COMSIG_ATOM_BULLET_ACT, .proc/projectile_interference) + //severe flubs (insta match ender, no winners) conditions + RegisterSignal(sparring, COMSIG_LIVING_DEATH, .proc/death_flub) + RegisterSignal(sparring, COMSIG_PARENT_QDELETING, .proc/deletion_flub) + +/datum/sparring_match/proc/unhook_signals(mob/living/carbon/human/sparring) + if(!sparring) + return + UnregisterSignal(sparring, list( + COMSIG_MOB_FIRED_GUN, + COMSIG_MOB_GRENADE_ARMED, + COMSIG_MOB_ITEM_ATTACK, + COMSIG_MOVABLE_MOVED, + COMSIG_MOVABLE_TELEPORTED, + COMSIG_MOB_STATCHANGE, + COMSIG_PARENT_ATTACKBY, + COMSIG_ATOM_HULK_ATTACK, + COMSIG_ATOM_ATTACK_HAND, + COMSIG_ATOM_ATTACK_PAW, + COMSIG_ATOM_HITBY, + COMSIG_ATOM_BULLET_ACT, + COMSIG_LIVING_DEATH, + COMSIG_PARENT_QDELETING, + )) + +///someone is changing health state, end the fight in crit +/datum/sparring_match/proc/check_for_victory(datum/participant, new_stat) + SIGNAL_HANDLER + + //death needs to be a flub, conscious means they haven't won + if(new_stat == CONSCIOUS || new_stat == DEAD) + return + if(participant == chaplain) + end_match(opponent, chaplain) + else + end_match(chaplain, opponent) + +// SIGNALS THAT ARE FOR BEING ATTACKED FIRST (GUILTY) +/datum/sparring_match/proc/outsider_interference(datum/source, obj/item/I, mob/attacker) + SIGNAL_HANDLER + if(attacker == chaplain || attacker == opponent) + return + INVOKE_ASYNC(src, .proc/flub, attacker) + +/datum/sparring_match/proc/hulk_interference(datum/source, mob/attacker) + SIGNAL_HANDLER + if((attacker == chaplain || attacker == opponent)) + // fist fighting a hulk is so dumb. i can't fathom why you would do this. + return + INVOKE_ASYNC(src, .proc/flub, attacker) + +/datum/sparring_match/proc/hand_interference(datum/source, mob/living/attacker) + SIGNAL_HANDLER + if(attacker == chaplain || attacker == opponent) + //you can pretty much always use fists as a participant + return + + INVOKE_ASYNC(src, .proc/flub, attacker) + +/datum/sparring_match/proc/paw_interference(datum/source, mob/living/attacker) + SIGNAL_HANDLER + + if(attacker == chaplain || attacker == opponent) + //you can pretty much always use paws as a participant + return + + INVOKE_ASYNC(src, .proc/flub, attacker) + +/datum/sparring_match/proc/thrown_interference(datum/source, atom/movable/thrown_movable, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + SIGNAL_HANDLER + if(istype(thrown_movable, /obj/item)) + var/mob/living/honorbound = source + var/obj/item/thrown_item = thrown_movable + var/mob/thrown_by = thrown_item.thrownby?.resolve() + if(thrown_item.throwforce < honorbound.health && ishuman(thrown_by)) + INVOKE_ASYNC(src, .proc/flub, thrown_by) + +/datum/sparring_match/proc/projectile_interference(datum/participant, obj/projectile/proj) + SIGNAL_HANDLER + if(proj.firer == chaplain || proj.firer == opponent) + //oh, well that's allowed. or maybe it isn't. doesn't matter because firing the gun will trigger a violation, so no additional violation needed + return + var/mob/living/interfering + if(isliving(proj.firer)) + interfering = proj.firer + INVOKE_ASYNC(src, .proc/flub, interfering) + +///someone randomly fucking died +/datum/sparring_match/proc/death_flub(datum/deceased) + SIGNAL_HANDLER + + flubbed_match() + +///someone randomly fucking deleted +/datum/sparring_match/proc/deletion_flub(datum/qdeleting) + SIGNAL_HANDLER + + flubbed_match() + +///someone used a gun +/datum/sparring_match/proc/gun_violation(datum/offender) + SIGNAL_HANDLER + violation(offender, "using guns") + +///someone used a grenade +/datum/sparring_match/proc/grenade_violation(datum/offender) + SIGNAL_HANDLER + violation(offender, "using grenades") + +///someone used melee weapons +/datum/sparring_match/proc/melee_violation(datum/offender, obj/item/thing, mob/user, params) + SIGNAL_HANDLER + + if(weapons_condition != CONDITION_CEREMONIAL_ONLY) + violation(offender, "using melee weapons") + if(istype(thing, /obj/item/ceremonial_blade)) + return + violation(offender, "using non ceremonial weapons") + +/datum/sparring_match/proc/teleport_violation(datum/offender) + SIGNAL_HANDLER + if(offender == chaplain) + end_match(opponent, chaplain, violation_victory = TRUE) + else + end_match(chaplain, opponent, violation_victory = TRUE) + +///someone tried to leave +/datum/sparring_match/proc/arena_violation(atom/movable/mover, atom/oldloc, direction) + SIGNAL_HANDLER + + var/area/inhabited_area = get_area(mover) + if(inhabited_area == arena_condition) + return //still in the ring!! :) + + violation(mover, "leaving the arena") + var/atom/throw_target = get_edge_target_turf(mover, REVERSE_DIR(direction)) + mover.throw_at(throw_target, 6, 4) + +/datum/sparring_match/proc/violation(mob/living/carbon/human/offender, reason) + SIGNAL_HANDLER + + to_chat(offender, span_userdanger("Violation! No [reason]!")) + if(offender == chaplain) + chaplain_violations_allowed-- + if(!chaplain_violations_allowed) + end_match(opponent, chaplain, violation_victory = TRUE) + else + opponent_violations_allowed-- + if(!opponent_violations_allowed) + end_match(chaplain, opponent, violation_victory = TRUE) + +/datum/sparring_match/proc/flub(mob/living/interfering) + if(interfering) + var/list/possible_punishments = list(PUNISHMENT_OMEN, PUNISHMENT_LIGHTNING) + if(ishuman(interfering)) + possible_punishments += PUNISHMENT_BRAND + switch(pick(possible_punishments)) + if(PUNISHMENT_OMEN) + to_chat(interfering, span_warning("You get a bad feeling... for interfering with [chaplain]'s sparring match...")) + interfering.AddComponent(/datum/component/omen, TRUE, null, FALSE) + if(PUNISHMENT_LIGHTNING) + to_chat(interfering, span_warning("[GLOB.deity] has punished you for interfering with [chaplain]'s sparring match!")) + lightningbolt(interfering) + if(PUNISHMENT_BRAND) + var/mob/living/carbon/human/branded = interfering + to_chat(interfering, span_warning("[GLOB.deity] brands your flesh for interfering with [chaplain]'s sparring match!!")) + var/obj/item/bodypart/branded_limb = pick(branded.bodyparts) + branded_limb.force_wound_upwards(/datum/wound/burn/severe/brand) + branded.emote("scream") + + flubs-- + if(!flubs) //too many interferences + flubbed_match() + +///this match was interfered on, nobody wins or loses anything, just end +/datum/sparring_match/proc/flubbed_match() + cleanup_sparring_match() + + if(chaplain) //flubing means we don't know who is still standing + to_chat(chaplain, span_boldannounce("The match was flub'd! No winners, no losers. You may restart the match with another contract.")) + if(opponent) + to_chat(opponent, span_boldannounce("The match was flub'd! No winners, no losers.")) + qdel(src) + +///helper to remove all the effects after a match ends +/datum/sparring_match/proc/cleanup_sparring_match() + REMOVE_TRAIT(chaplain, TRAIT_SPARRING, TRAIT_GENERIC) + REMOVE_TRAIT(opponent, TRAIT_SPARRING, TRAIT_GENERIC) + unhook_signals(chaplain) + unhook_signals(opponent) + chaplain.remove_filter("sparring_outline") + opponent.remove_filter("sparring_outline") + +/datum/sparring_match/proc/end_match(mob/living/carbon/human/winner, mob/living/carbon/human/loser, violation_victory = FALSE) + cleanup_sparring_match() + to_chat(chaplain, span_boldannounce("[violation_victory ? "[loser] DISQUALIFIED!" : ""] [winner] HAS WON!")) + to_chat(opponent, span_boldannounce("[violation_victory ? "[loser] DISQUALIFIED!" : ""] [winner] HAS WON!")) + win(winner, loser, violation_victory) + lose(loser, winner) + if(stakes_condition != STAKES_YOUR_SOUL) + var/healing_message = "You may want to heal up the loser now." + if(winner == chaplain) + healing_message += " Your bible will heal the loser for awhile." + to_chat(winner, span_notice(healing_message)) + qdel(src) + +///most of the effects are handled on `lose()` instead. +/datum/sparring_match/proc/win(mob/living/carbon/human/winner, mob/living/carbon/human/loser, violation_victory) + switch(stakes_condition) + if(STAKES_HOLY_MATCH) + if(winner == chaplain) + if(violation_victory) + to_chat(winner, span_warning("[GLOB.deity] is not entertained from a matched decided by violations. No favor awarded...")) + else + to_chat(winner, span_nicegreen("You've won favor with [GLOB.deity]!")) + var/datum/religion_sect/spar/sect = GLOB.religious_sect + sect.adjust_favor(1, winner) + sect.past_opponents += WEAKREF(loser) + if(STAKES_MONEY_MATCH) + to_chat(winner, span_nicegreen("You've won all of [loser]'s money!")) + if(STAKES_YOUR_SOUL) + to_chat(winner, span_nicegreen("You've won [loser]'s SOUL!")) + +/datum/sparring_match/proc/lose(mob/living/carbon/human/loser, mob/living/carbon/human/winner) + if(!loser) //shit happened? + return + switch(stakes_condition) + if(STAKES_HOLY_MATCH) + if(loser == chaplain) + var/datum/religion_sect/spar/sect = GLOB.religious_sect + sect.matches_lost++ + if(sect.matches_lost < 3) + to_chat(loser, span_userdanger("[GLOB.deity] is angry you lost in their name!")) + return + to_chat(loser, span_userdanger("[GLOB.deity] is enraged by your lackluster sparring record!")) + lightningbolt(loser) + SEND_SIGNAL(loser, COMSIG_ADD_MOOD_EVENT, "sparring", /datum/mood_event/banished) + loser.mind.holy_role = NONE + to_chat(loser, span_userdanger("You have been excommunicated! You are no longer holy!")) + if(STAKES_MONEY_MATCH) + to_chat(loser, span_userdanger("You've lost all your money to [winner]!")) + var/datum/bank_account/loser_account = loser.get_bank_account() + var/datum/bank_account/winner_account = winner.get_bank_account() + if(!loser_account || !winner_account)//the winner is pretty owned in this case but whatever shoulda read the fine print of the contract + return + winner_account.transfer_money(loser_account, loser_account.account_balance) + if(STAKES_YOUR_SOUL) + var/turf/shard_turf = get_turf(loser) + if(!shard_turf) + return + to_chat(loser, span_userdanger("You've lost ownership over your soul to [winner]!")) + var/obj/item/soulstone/anybody/chaplain/sparring/shard = new(shard_turf) + shard.capture_soul(loser, winner, forced = TRUE) diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index f78bc77de47..83eefbc5fb6 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -253,9 +253,6 @@ if(owner.reagents.get_reagent_amount(/datum/reagent/medicine/ephedrine) < 20) owner.reagents.add_reagent(/datum/reagent/medicine/ephedrine, 10) - - - /obj/item/organ/heart/ethereal name = "crystal core" icon_state = "ethereal_heart" //Welp. At least it's more unique in functionaliy. diff --git a/icons/obj/hand_of_god_structures.dmi b/icons/obj/hand_of_god_structures.dmi index 25ef4c527bc..4c19d560b22 100644 Binary files a/icons/obj/hand_of_god_structures.dmi and b/icons/obj/hand_of_god_structures.dmi differ diff --git a/icons/obj/items/ritual_weapon.dmi b/icons/obj/items/ritual_weapon.dmi new file mode 100644 index 00000000000..806f1014e7b Binary files /dev/null and b/icons/obj/items/ritual_weapon.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 66810c350a5..583b5140f76 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -122,6 +122,7 @@ #include "code\__DEFINES\reactions.dm" #include "code\__DEFINES\reagents.dm" #include "code\__DEFINES\reagents_specific_heat.dm" +#include "code\__DEFINES\religion.dm" #include "code\__DEFINES\research.dm" #include "code\__DEFINES\robots.dm" #include "code\__DEFINES\role_preferences.dm" @@ -629,6 +630,7 @@ #include "code\datums\components\trapdoor.dm" #include "code\datums\components\twohanded.dm" #include "code\datums\components\udder.dm" +#include "code\datums\components\unbreakable.dm" #include "code\datums\components\uplink.dm" #include "code\datums\components\usb_port.dm" #include "code\datums\components\vacuum.dm" @@ -790,6 +792,7 @@ #include "code\datums\elements\strippable.dm" #include "code\datums\elements\surgery_initiator.dm" #include "code\datums\elements\swabbable.dm" +#include "code\datums\elements\tenacious.dm" #include "code\datums\elements\tool_flash.dm" #include "code\datums\elements\turf_transparency.dm" #include "code\datums\elements\undertile.dm" @@ -3441,6 +3444,9 @@ #include "code\modules\religion\religion_sects.dm" #include "code\modules\religion\religion_structures.dm" #include "code\modules\religion\rites.dm" +#include "code\modules\religion\sparring\ceremonial_gear.dm" +#include "code\modules\religion\sparring\sparring_contract.dm" +#include "code\modules\religion\sparring\sparring_datum.dm" #include "code\modules\requests\request.dm" #include "code\modules\requests\request_manager.dm" #include "code\modules\research\bepis.dm" diff --git a/tgui/packages/tgui/interfaces/SparringContract.tsx b/tgui/packages/tgui/interfaces/SparringContract.tsx new file mode 100644 index 00000000000..21daf5dd824 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SparringContract.tsx @@ -0,0 +1,235 @@ +import { BooleanLike } from 'common/react'; +import { multiline } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { BlockQuote, Button, Dropdown, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +// defined this so the code is more readable +const STAKES_HOLY_MATCH = 1; + +const weaponlist = [ + "Fist Fight", + "Ceremonial Weapons", + "Melee Only", + "Any Weapons", +]; + +const stakelist = [ + "No Stakes", + "Holy Match", + "Money Match", + "Your Soul", +]; + +const weaponblurb = [ + "You will fight with your fists only. Any weapons will be considered a violation.", + "You can only fight with ceremonial weapons. You will be at a severe disadvantage without one!", + "You can fight with weapons, or fists if you have none. Ranged weapons are a violation.", + "You can fight with any and all weapons as you please. Try not to kill them, okay?", +]; + +const stakesblurb = [ + "No stakes, just for fun. Who doesn't love some recreational sparring?", + "A match for the chaplain's deity. The Chaplain suffers large consequences for failure, but advances their sect by winning.", + "A match with money on the line. Whomever wins takes all the money of whomever loses.", + "A lethal match with the loser's soul becoming under ownership of the winner.", +]; + +type Info = { + set_weapon: number; + set_area: string; + set_stakes: number; + left_sign: string; + right_sign: string; + in_area: BooleanLike; + no_chaplains: BooleanLike; + possible_areas: Array; +}; + +export const SparringContract = (props, context) => { + const { data, act } = useBackend(context); + const { + set_weapon, + set_area, + set_stakes, + possible_areas, + left_sign, + right_sign, + in_area, + no_chaplains, + } = data; + const [weapon, setWeapon] = useLocalState(context, "weapon", set_weapon); + const [area, setArea] = useLocalState(context, "area", set_area); + const [stakes, setStakes] = useLocalState(context, "stakes", set_stakes); + return ( + + +
+ + + + + Weapons: + + + + setWeapon(weaponlist.findIndex(title => ( + title === value + ))+1)} /> + + +
+ {weaponblurb[weapon-1]} +
+
+
+
+ + + + Arena: + + + setArea(value)} /> + + +
+ This fight will take place in the {area}. + Leaving the arena mid-fight is a violation. +
+
+
+
+ + + + Stakes: + + + + setStakes(stakelist.findIndex(title => ( + title === value + ))+1)} /> + + +
+ {stakesblurb[stakes-1]} +
+
+
+
+ + + + {left_sign === 'none' && ( + + ) || ( + left_sign + )} + + + VS + + + {right_sign === "none" && ( + + ) || ( + right_sign + )} + + + + + + + + + + + + + + + + +
+
+
+
+ ); +};