diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm index 6f85668b..149416bf 100644 --- a/code/modules/cargo/packs.dm +++ b/code/modules/cargo/packs.dm @@ -2924,9 +2924,9 @@ name = "Kinkmate construction kit" cost = 2000 contraband = TRUE - contains = list(/obj/item/vending_refill/kink, - /obj/item/vending_refill/kink, - /obj/item/vending_refill/kink, + contains = list(/obj/item/vending_refill/kink, + /obj/item/vending_refill/kink, + /obj/item/vending_refill/kink, /obj/item/circuitboard/machine/kinkmate) crate_name = "Kinkmate construction kit" @@ -2966,6 +2966,36 @@ crate_name = "captain pen" crate_type = /obj/structure/closet/crate/secure/weapon //It is a combat pen +/datum/supply_pack/misc/dueling_stam + name = "Dueling Pistols" + desc = "Resolve all your quarrels with some nonlethal fun." + cost = 2000 + contains = list(/obj/item/storage/lockbox/dueling/hugbox/stamina, + /obj/item/storage/lockbox/dueling/hugbox/stamina, + /obj/item/storage/lockbox/dueling/hugbox/stamina, + /obj/item/storage/lockbox/dueling/hugbox/stamina, + /obj/item/storage/lockbox/dueling/hugbox/stamina) + crate_name = "dueling pistols" + +/datum/supply_pack/misc/dueling_lethal + name = "Lethal Dueling Pistols" + desc = "Settle your differences the true spaceman way." + cost = 3000 + contraband = TRUE + contains = list(/obj/item/storage/lockbox/dueling/hugbox, + /obj/item/storage/lockbox/dueling/hugbox, + /obj/item/storage/lockbox/dueling/hugbox) + crate_name = "dueling pistols (lethal)" + +/datum/supply_pack/misc/dueling_death + name = "Elimination Dueling Pistols" + desc = "It's high noon." + cost = 5000 + hidden = TRUE + contains = list(/obj/item/storage/lockbox/dueling) + crate_name = "dueling pistols (elimination)" + + /datum/supply_pack/misc/exoticfootwear name = "Exotic Footwear Crate" desc = "Popularised by lizards and exotic dancers, the footwear included in this shipment is sure to give your feet the breathing room they deserve. Sweet Kicks Inc. is not responsible for any damage, distress, or @r0u$a1 caused by this shipment." @@ -3006,7 +3036,7 @@ /obj/item/bedsheet/purple, /obj/item/bedsheet/red, /obj/item/bedsheet/yellow, - /obj/item/bedsheet/brown, + /obj/item/bedsheet/brown, /obj/item/bedsheet/black, /obj/item/bedsheet/rainbow) crate_name = "colored bedsheet crate" diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 8c44ca45..b0f9a7b4 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -235,7 +235,7 @@ sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) else //Smart spread sprd = round((((rand_spr/burst_size) * iteration) - (0.5 + (rand_spr * 0.25))) * (randomized_gun_spread + randomized_bonus_spread)) - + before_firing(target,user) if(!chambered.fire_casing(target, user, params, ,suppressed, zone_override, sprd)) shoot_with_empty_chamber(user) firing_burst = FALSE @@ -281,6 +281,7 @@ to_chat(user, " [src] is lethally chambered! You don't want to risk harming anyone...") return sprd = round((rand() - 0.5) * DUALWIELD_PENALTY_EXTRA_MULTIPLIER * (randomized_gun_spread + randomized_bonus_spread)) + before_firing(target,user) if(!chambered.fire_casing(target, user, params, , suppressed, zone_override, sprd)) shoot_with_empty_chamber(user) return @@ -470,6 +471,9 @@ qdel(pin) pin = new /obj/item/firing_pin +//Happens before the actual projectile creation +/obj/item/gun/proc/before_firing(atom/target,mob/user) + return ///////////// // ZOOMING // ///////////// diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm new file mode 100644 index 00000000..23968fe1 --- /dev/null +++ b/code/modules/projectiles/guns/energy/dueling.dm @@ -0,0 +1,389 @@ +#define DUEL_IDLE 1 +#define DUEL_PREPARATION 2 +#define DUEL_READY 3 +#define DUEL_COUNTDOWN 4 +#define DUEL_FIRING 5 + +//paper rock scissors +#define DUEL_SETTING_A "wide" +#define DUEL_SETTING_B "cone" +#define DUEL_SETTING_C "pinpoint" + +#define DUEL_HUGBOX_NONE 0 //dismember head +#define DUEL_HUGBOX_LETHAL 1 //200 damage to chest +#define DUEL_HUGBOX_NONLETHAL 2 //stamcrit + +/datum/duel + var/obj/item/gun/energy/dueling/gun_A + var/obj/item/gun/energy/dueling/gun_B + var/state = DUEL_IDLE + var/required_distance = 5 + var/list/confirmations = list() + var/list/fired = list() + var/countdown_length = 10 + var/countdown_step = 0 + var/static/next_id = 1 + var/id + +/datum/duel/New() + id = next_id++ + +/datum/duel/proc/try_begin() + //Check if both guns are held and if so begin. + var/mob/living/A = get_duelist(gun_A) + var/mob/living/B = get_duelist(gun_B) + if(!A || !B) + message_duelists("To begin the duel, both participants need to be holding paired dueling pistols.") + return + begin() + +/datum/duel/proc/begin() + state = DUEL_PREPARATION + confirmations.Cut() + fired.Cut() + countdown_step = countdown_length + + message_duelists("Set your gun setting and move [required_distance] steps away from your opponent.") + + START_PROCESSING(SSobj,src) + +/datum/duel/proc/get_duelist(obj/gun) + var/mob/living/G = gun.loc + if(!istype(G) || !G.is_holding(gun)) + return null + return G + +/datum/duel/proc/message_duelists(message) + var/mob/living/LA = get_duelist(gun_A) + if(LA) + to_chat(LA,message) + var/mob/living/LB = get_duelist(gun_B) + if(LB) + to_chat(LB,message) + +/datum/duel/proc/other_gun(obj/item/gun/energy/dueling/G) + return G == gun_A ? gun_B : gun_A + +/datum/duel/proc/end() + message_duelists("Duel finished. Re-engaging safety.") + STOP_PROCESSING(SSobj,src) + state = DUEL_IDLE + +/datum/duel/process() + switch(state) + if(DUEL_PREPARATION) + if(check_positioning()) + confirm_positioning() + else if (!get_duelist(gun_A) && !get_duelist(gun_B)) + end() + if(DUEL_READY) + if(!check_positioning()) + back_to_prep() + else if(confirmations.len == 2) + confirm_ready() + if(DUEL_COUNTDOWN) + if(!check_positioning()) + back_to_prep() + else + countdown_step() + if(DUEL_FIRING) + if(check_fired()) + end() + +/datum/duel/proc/back_to_prep() + message_duelists("Positions invalid. Please move to valid positions [required_distance] steps aways from each other to continue.") + state = DUEL_PREPARATION + confirmations.Cut() + countdown_step = countdown_length + +/datum/duel/proc/confirm_positioning() + message_duelists("Position confirmed. Confirm readiness by pulling the trigger once.") + state = DUEL_READY + +/datum/duel/proc/confirm_ready() + message_duelists("Readiness confirmed. Starting countdown. Commence firing at zero mark.") + state = DUEL_COUNTDOWN + +/datum/duel/proc/countdown_step() + countdown_step-- + if(countdown_step == 0) + state = DUEL_FIRING + message_duelists("Fire!") + else + message_duelists("[countdown_step]!") + +/datum/duel/proc/check_fired() + if(fired.len == 2) + return TRUE + //Let's say if gun was dropped/stowed the user is finished + if(!get_duelist(gun_A)) + return TRUE + if(!get_duelist(gun_B)) + return TRUE + return FALSE + +/datum/duel/proc/check_positioning() + var/mob/living/A = get_duelist(gun_A) + var/mob/living/B = get_duelist(gun_B) + if(!A || !B) + return FALSE + if(!isturf(A.loc) || !isturf(B.loc)) + return FALSE + if(get_dist(A,B) != required_distance) + return FALSE + for(var/turf/T in getline(get_turf(A),get_turf(B))) + if(is_blocked_turf(T,TRUE)) + return FALSE + return TRUE + +/obj/item/gun/energy/dueling + name = "dueling pistol" + desc = "High-tech dueling pistol. Launches chaff and projectile according to preset settings." + icon_state = "dueling_pistol" + item_state = "gun" + ammo_x_offset = 2 + w_class = WEIGHT_CLASS_SMALL + ammo_type = list(/obj/item/ammo_casing/energy/duel) + automatic_charge_overlays = FALSE + var/unlocked = FALSE + var/setting = DUEL_SETTING_A + var/datum/duel/duel + var/mutable_appearance/setting_overlay + var/hugbox = DUEL_HUGBOX_NONE + +/obj/item/gun/energy/dueling/hugbox + hugbox = DUEL_HUGBOX_LETHAL + +/obj/item/gun/energy/dueling/hugbox/stamina + hugbox = DUEL_HUGBOX_NONLETHAL + +/obj/item/gun/energy/dueling/Initialize() + . = ..() + setting_overlay = mutable_appearance(icon,setting_iconstate()) + add_overlay(setting_overlay) + +/obj/item/gun/energy/dueling/examine(mob/user) + . = ..() + if(duel) + . += "Its linking number is [duel.id]." + else + . += "ERROR: No linking number on gun." + +/obj/item/gun/energy/dueling/proc/setting_iconstate() + switch(setting) + if(DUEL_SETTING_A) + return "duel_red" + if(DUEL_SETTING_B) + return "duel_green" + if(DUEL_SETTING_C) + return "duel_blue" + return "duel_red" + +/obj/item/gun/energy/dueling/attack_self(mob/living/user) + . = ..() + if(duel.state == DUEL_IDLE) + duel.try_begin() + else + toggle_setting(user) + +/obj/item/gun/energy/dueling/proc/toggle_setting(mob/living/user) + switch(setting) + if(DUEL_SETTING_A) + setting = DUEL_SETTING_B + if(DUEL_SETTING_B) + setting = DUEL_SETTING_C + if(DUEL_SETTING_C) + setting = DUEL_SETTING_A + to_chat(user,"You switch [src] setting to [setting] mode.") + update_icon() + +/obj/item/gun/energy/dueling/update_icon(force_update) + . = ..() + if(setting_overlay) + cut_overlay(setting_overlay) + setting_overlay.icon_state = setting_iconstate() + add_overlay(setting_overlay) + +/obj/item/gun/energy/dueling/Destroy() + . = ..() + if(duel.gun_A == src) + duel.gun_A = null + if(duel.gun_B == src) + duel.gun_B = null + duel = null + +/obj/item/gun/energy/dueling/can_trigger_gun(mob/living/user) + . = ..() + switch(duel.state) + if(DUEL_FIRING) + return . && !duel.fired[src] + if(DUEL_READY) + return . + else + to_chat(user,"[src] is locked. Wait for FIRE signal before shooting.") + return FALSE + +/obj/item/gun/energy/dueling/proc/is_duelist(mob/living/L) + if(!istype(L)) + return FALSE + if(!L.is_holding(duel.other_gun(src))) + return FALSE + return TRUE + +/obj/item/gun/energy/dueling/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) + if(duel.state == DUEL_READY) + duel.confirmations[src] = TRUE + to_chat(user,"You confirm your readiness.") + return + else if(!is_duelist(target)) //I kinda want to leave this out just to see someone shoot a bystander or missing. + to_chat(user,"[src] safety system prevents shooting anyone but your designated opponent.") + return + else + duel.fired[src] = TRUE + . = ..() + +/obj/item/gun/energy/dueling/before_firing(target,user) + var/obj/item/ammo_casing/energy/duel/D = chambered + D.setting = setting + D.hugbox = hugbox + +/obj/effect/temp_visual/dueling_chaff + icon = 'icons/effects/effects.dmi' + icon_state = "shield-old" + duration = 30 + var/setting + +/obj/effect/temp_visual/dueling_chaff/update_icon() + . = ..() + switch(setting) + if(DUEL_SETTING_A) + color = "red" + if(DUEL_SETTING_B) + color = "green" + if(DUEL_SETTING_C) + color = "blue" + +//Casing + +/obj/item/ammo_casing/energy/duel + e_cost = 0 + projectile_type = /obj/item/projectile/energy/duel + var/setting + var/hugbox = DUEL_HUGBOX_NONE + +/obj/item/ammo_casing/energy/duel/ready_proj(atom/target, mob/living/user, quiet, zone_override) + . = ..() + var/obj/item/projectile/energy/duel/D = BB + D.setting = setting + D.hugbox = hugbox + D.update_icon() + +/obj/item/ammo_casing/energy/duel/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) + . = ..() + var/obj/effect/temp_visual/dueling_chaff/C = new(get_turf(user)) + C.setting = setting + C.update_icon() + +//Projectile + +/obj/item/projectile/energy/duel + name = "dueling beam" + icon_state = "declone" + is_reflectable = FALSE + homing = TRUE + var/setting + var/hugbox = DUEL_HUGBOX_NONE + +/obj/item/projectile/energy/duel/update_icon() + . = ..() + switch(setting) + if(DUEL_SETTING_A) + color = "red" + if(DUEL_SETTING_B) + color = "green" + if(DUEL_SETTING_C) + color = "blue" + +/obj/item/projectile/energy/duel/on_hit(atom/target, blocked) + . = ..() + var/turf/T = get_turf(target) + var/obj/effect/temp_visual/dueling_chaff/C = locate() in T + if(C) + var/counter_setting + switch(setting) + if(DUEL_SETTING_A) + counter_setting = DUEL_SETTING_B + if(DUEL_SETTING_B) + counter_setting = DUEL_SETTING_C + if(DUEL_SETTING_C) + counter_setting = DUEL_SETTING_A + if(C.setting == counter_setting) + return BULLET_ACT_BLOCK + + var/mob/living/L = target + if(!istype(target)) + return BULLET_ACT_BLOCK + + switch(hugbox) + if(DUEL_HUGBOX_NONE) + var/obj/item/bodypart/B = L.get_bodypart(BODY_ZONE_HEAD) + B.dismember() + qdel(B) + if(DUEL_HUGBOX_LETHAL) + L.adjustBruteLoss(180) + L.death() //Die, powergamers. + if(DUEL_HUGBOX_NONLETHAL) + L.adjustStaminaLoss(200, forced = TRUE) //Die, powergamers x 2 + L.Knockdown(100, override_hardstun = 100) //For good measure. + +//Storage case. +/obj/item/storage/lockbox/dueling + name = "dueling pistol case" + desc = "Let's solve this like gentlespacemen." + icon_state = "medalbox+l" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + req_access = list(ACCESS_CAPTAIN) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + var/gun_type = /obj/item/gun/energy/dueling + +/obj/item/storage/lockbox/dueling/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_items = 2 + STR.can_hold = typecacheof(/obj/item/gun/energy/dueling) + +/obj/item/storage/lockbox/dueling/update_icon() + cut_overlays() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = "medalbox+l" + else + icon_state = "medalbox" + if(open) + icon_state += "open" + if(broken) + icon_state += "+b" + +/obj/item/storage/lockbox/dueling/PopulateContents() + . = ..() + var/obj/item/gun/energy/dueling/gun_A = new gun_type(src) + var/obj/item/gun/energy/dueling/gun_B = new gun_type(src) + var/datum/duel/D = new + gun_A.duel = D + gun_B.duel = D + D.gun_A = gun_A + D.gun_B = gun_B + +/obj/item/storage/lockbox/dueling/hugbox + gun_type = /obj/item/gun/energy/dueling/hugbox + req_access = list(ACCESS_ARMORY) + +/obj/item/storage/lockbox/dueling/hugbox/stamina + gun_type = /obj/item/gun/energy/dueling/hugbox/stamina + req_access = null \ No newline at end of file diff --git a/icons/obj/guns/energy.dmi b/icons/obj/guns/energy.dmi index a6d5c8a5..8fafce6a 100644 Binary files a/icons/obj/guns/energy.dmi and b/icons/obj/guns/energy.dmi differ diff --git a/tgstation.dme b/tgstation.dme index f257f5cf..b4ddb8c6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2443,6 +2443,7 @@ #include "code\modules\projectiles\guns\ballistic\revolver.dm" #include "code\modules\projectiles\guns\ballistic\shotgun.dm" #include "code\modules\projectiles\guns\ballistic\toy.dm" +#include "code\modules\projectiles\guns\energy\dueling.dm" #include "code\modules\projectiles\guns\energy\energy_gun.dm" #include "code\modules\projectiles\guns\energy\kinetic_accelerator.dm" #include "code\modules\projectiles\guns\energy\laser.dm" diff --git a/tgstation.lk b/tgstation.lk new file mode 100644 index 00000000..e69de29b