Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-15-10-2025

This commit is contained in:
Roxy
2025-10-15 19:34:41 -04:00
656 changed files with 24772 additions and 15866 deletions
-261
View File
@@ -1,261 +0,0 @@
//Originally coded by ISaidNo, later modified by Kelenius. Ported from Baystation12.
/obj/structure/closet/crate/secure/loot
name = "abandoned crate"
desc = "What could be inside?"
icon_state = "securecrate"
base_icon_state = "securecrate"
integrity_failure = 0 //no breaking open the crate
var/code = null
var/lastattempt = null
var/attempts = 10
var/codelen = 4
var/qdel_on_open = FALSE
var/spawned_loot = FALSE
tamperproof = 90
// Stop people from "diving into" the crate accidentally, and then detonating it.
divable = FALSE
/obj/structure/closet/crate/secure/loot/Initialize(mapload)
. = ..()
var/list/digits = list("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
code = ""
for(var/i in 1 to codelen)
var/dig = pick(digits)
code += dig
digits -= dig //there are never matching digits in the answer
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/structure/closet/crate/secure/loot/attack_hand(mob/user, list/modifiers)
if(locked)
to_chat(user, span_notice("The crate is locked with a Deca-code lock."))
var/input = input(usr, "Enter [codelen] digits. All digits must be unique.", "Deca-Code Lock", "") as text|null
if(user.can_perform_action(src) && locked)
var/list/sanitised = list()
var/sanitycheck = TRUE
var/char = ""
var/length_input = length(input)
for(var/i = 1, i <= length_input, i += length(char)) //put the guess into a list
char = input[i]
if(!(char >= "0" && char <= "9"))
sanitycheck = FALSE //if a non-digit is found, reject the input
sanitised += text2num(char)
for(var/i in 1 to length(sanitised) - 1) //compare each digit in the guess to all those following it
for(var/j in i + 1 to length(sanitised))
if(sanitised[i] == sanitised[j])
sanitycheck = FALSE //if a digit is repeated, reject the input
if(input == code)
if(!spawned_loot)
spawn_loot()
tamperproof = 0 // set explosion chance to zero, so we dont accidently hit it with a multitool and instantly die
togglelock(user)
else if(!input || !sanitycheck || length(sanitised) != codelen)
to_chat(user, span_notice("You leave the crate alone."))
else
to_chat(user, span_warning("A red light flashes."))
lastattempt = input
attempts--
if(attempts == 0)
boom(user)
return
return ..()
/obj/structure/closet/crate/secure/loot/click_alt(mob/living/user)
attack_hand(user) //this helps you not blow up so easily by overriding unlocking which results in an immediate boom.
return CLICK_ACTION_SUCCESS
/obj/structure/closet/crate/secure/loot/attackby(obj/item/W, mob/user)
if(locked)
if(W.tool_behaviour == TOOL_MULTITOOL)
to_chat(user, span_notice("DECA-CODE LOCK REPORT:"))
if(attempts == 1)
to_chat(user, span_warning("* Anti-Tamper Bomb will activate on next failed access attempt."))
else
to_chat(user, span_notice("* Anti-Tamper Bomb will activate after [attempts] failed access attempts."))
if(lastattempt != null)
var/bulls = 0 //right position, right number
var/cows = 0 //wrong position but in the puzzle
var/lastattempt_char = ""
var/length_lastattempt = length(lastattempt)
var/lastattempt_it = 1
var/code_char = ""
var/length_code = length(code)
var/code_it = 1
while(lastattempt_it <= length_lastattempt && code_it <= length_code) // Go through list and count matches
lastattempt_char = lastattempt[lastattempt_it]
code_char = code[code_it]
if(lastattempt_char == code_char)
++bulls
else if(findtext(code, lastattempt_char))
++cows
lastattempt_it += length(lastattempt_char)
code_it += length(code_char)
to_chat(user, span_notice("Last code attempt, [lastattempt], had [bulls] correct digits at correct positions and [cows] correct digits at incorrect positions."))
return
return ..()
/obj/structure/closet/crate/secure/loot/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
if(locked)
boom(user) // no feedback since it just explodes, thats its own feedback
return TRUE
return
/obj/structure/closet/crate/secure/loot/togglelock(mob/user, silent = FALSE)
if(!locked)
. = ..() //Run the normal code.
if(locked) //Double check if the crate actually locked itself when the normal code ran.
//reset the anti-tampering, number of attempts and last attempt when the lock is re-enabled.
tamperproof = initial(tamperproof)
attempts = initial(attempts)
lastattempt = null
return
if(tamperproof)
return
return ..()
/obj/structure/closet/crate/secure/loot/atom_deconstruct(disassembled = TRUE)
if(locked)
boom()
return
return ..()
/obj/structure/closet/crate/secure/loot/after_open(mob/living/user, force)
. = ..()
if(qdel_on_open)
qdel(src)
/obj/structure/closet/crate/secure/loot/proc/spawn_loot()
var/loot = rand(1,100) //100 different crates with varying chances of spawning
switch(loot)
if(1 to 5) //5% chance
new /obj/item/reagent_containers/cup/glass/bottle/rum(src)
new /obj/item/reagent_containers/cup/glass/bottle/whiskey(src)
new /obj/item/reagent_containers/cup/glass/bottle/whiskey(src)
new /obj/item/lighter(src)
new /obj/item/reagent_containers/cup/glass/bottle/absinthe/premium(src)
for(var/i in 1 to 3)
new /obj/item/cigarette/rollie(src)
if(6 to 10)
new /obj/item/melee/skateboard/pro(src)
if(11 to 15)
new /mob/living/basic/bot/honkbot(src)
if(16 to 20)
new /obj/item/stack/ore/diamond(src, 10)
if(21 to 25)
for(var/i in 1 to 5)
new /obj/item/poster/random_contraband(src)
if(26 to 30)
new /obj/item/vending_refill/sovietsoda(src)
var/obj/item/circuitboard/machine/vendor/board = new (src)
board.set_type(/obj/machinery/vending/sovietsoda)
if(31 to 35)
new /obj/item/seeds/firelemon(src)
if(36 to 40)
for(var/i in 1 to 5)
new /obj/item/toy/snappop/phoenix(src)
if(41 to 45)
new /obj/item/modular_computer/pda/clear(src)
if(46 to 50)
new /obj/item/storage/box/syndie_kit/chameleon/broken
if(51 to 52) // 2% chance
new /obj/item/melee/baton(src)
if(53 to 54)
new /obj/item/toy/balloon/corgi(src)
if(55 to 56)
var/newitem = pick(subtypesof(/obj/item/toy/mecha))
new newitem(src)
if(57 to 58)
new /obj/item/toy/balloon/syndicate(src)
if(59 to 60)
new /obj/item/borg/upgrade/modkit/aoe/mobs(src)
new /obj/item/clothing/suit/space(src)
new /obj/item/clothing/head/helmet/space(src)
if(61 to 62)
for(var/i in 1 to 5)
new /obj/item/clothing/head/costume/kitty(src)
new /obj/item/clothing/neck/petcollar(src)
if(63 to 64)
new /obj/item/clothing/shoes/kindle_kicks(src)
if(65 to 66)
new /obj/item/clothing/suit/costume/wellworn_shirt/graphic/ian(src)
new /obj/item/clothing/suit/hooded/ian_costume(src)
if(67 to 68)
var/obj/item/gibtonite/free_bomb = new /obj/item/gibtonite(src)
free_bomb.quality = rand(GIBTONITE_QUALITY_LOW, GIBTONITE_QUALITY_HIGH)
free_bomb.GibtoniteReaction(null, "A secure loot closet has spawned a live")
if(69 to 70)
new /obj/item/stack/ore/bluespace_crystal(src, 5)
if(71 to 72)
new /obj/item/toy/plush/snakeplushie(src)
if(73 to 74)
new /mob/living/basic/pet/gondola(src)
if(75 to 76)
new /obj/item/bikehorn/airhorn(src)
if(77 to 78)
new /obj/item/toy/plush/lizard_plushie(src)
if(79 to 80)
new /obj/item/stack/sheet/mineral/bananium(src, 10)
if(81 to 82)
new /obj/item/bikehorn/airhorn(src)
if(83 to 84)
new /obj/item/toy/plush/beeplushie(src)
if(85 to 86)
new /obj/item/defibrillator/compact(src)
if(87) //1% chance
var/list/cannabis_seeds = typesof(/obj/item/seeds/cannabis)
var/list/cannabis_plants = typesof(/obj/item/food/grown/cannabis)
for(var/i in 1 to rand(2, 4))
var/seed_type = pick(cannabis_seeds)
new seed_type(src)
for(var/i in 1 to rand(2, 4))
var/cannabis_type = pick(cannabis_plants)
new cannabis_type(src)
if(88)
new /obj/item/reagent_containers/cup/glass/bottle/lizardwine(src)
if(89)
new /obj/item/melee/energy/sword/bananium(src)
if(90)
new /obj/item/dnainjector/wackymut(src)
if(91)
for(var/i in 1 to 30)
new /mob/living/basic/cockroach/bloodroach(src)
if(92)
new /obj/item/katana(src)
if(93)
new /obj/item/dnainjector/xraymut(src)
if(94)
new /mob/living/basic/mimic/crate(src)
qdel_on_open = TRUE
if(95)
new /obj/item/toy/plush/nukeplushie(src)
if(96)
new /obj/item/banhammer(src)
for(var/i in 1 to 3)
var/obj/effect/mine/sound/bwoink/mine = new (src)
mine.set_anchored(FALSE)
mine.move_resist = MOVE_RESIST_DEFAULT
if(97)
for(var/i in 1 to 4)
new /obj/item/clothing/mask/balaclava(src)
new /obj/item/gun/ballistic/shotgun/toy(src)
new /obj/item/gun/ballistic/automatic/pistol/toy(src)
new /obj/item/gun/ballistic/automatic/toy(src)
new /obj/item/gun/ballistic/automatic/l6_saw/toy/unrestricted(src)
new /obj/item/ammo_box/foambox(src)
if(98)
for(var/i in 1 to 3)
new /mob/living/basic/bee/toxin(src)
if(99)
new /obj/item/implanter/sad_trombone(src)
if(100)
new /obj/item/melee/skateboard/hoverboard(src)
spawned_loot = TRUE
+1 -1
View File
@@ -61,7 +61,7 @@
attacked_atom = singular_turf
break
if(user.CanReach(attacked_atom))
if(attacked_atom.IsReachableBy(user))
return ITEM_INTERACT_BLOCKING
var/atom/bullet = fire_projectile(/obj/projectile/grapple_hook, attacked_atom, 'sound/items/weapons/zipline_fire.ogg')
@@ -208,6 +208,14 @@
SEND_SIGNAL(user, COMSIG_LIVING_CRUSHER_DETONATE, target, src, backstabbed)
target.apply_damage(combined_damage, BRUTE, blocked = def_check)
/obj/item/kinetic_crusher/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!istype(interacting_with, /obj/item/crusher_trophy))
return NONE
var/obj/item/crusher_trophy/new_trophy = interacting_with
if(new_trophy.add_to(src, user))
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
/obj/item/kinetic_crusher/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!HAS_TRAIT(src, TRAIT_WIELDED))
balloon_alert(user, "wield it first!")
@@ -25,11 +25,6 @@
SHOULD_CALL_PARENT(FALSE)
return "errors"
/obj/item/crusher_trophy/attackby(obj/item/attacking_item, mob/living/user)
if(!istype(attacking_item, /obj/item/kinetic_crusher))
return ..()
add_to(attacking_item, user)
/// Tries to add the trophy to our crusher
/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
for(var/obj/item/crusher_trophy/trophy as anything in crusher.trophies)
@@ -60,9 +60,7 @@ GLOBAL_LIST_INIT(marker_beacon_colors, sort_list(list(
/obj/item/stack/marker_beacon/click_alt(mob/living/user)
var/input_color = tgui_input_list(user, "Choose a color", "Beacon Color", GLOB.marker_beacon_colors)
if(isnull(input_color))
return CLICK_ACTION_BLOCKING
if(!user.can_perform_action(src))
if(isnull(input_color) || !user.can_perform_action(src))
return CLICK_ACTION_BLOCKING
picked_color = input_color
update_appearance()
@@ -154,15 +152,12 @@ GLOBAL_LIST_INIT(marker_beacon_colors, sort_list(list(
/obj/structure/marker_beacon/click_alt(mob/living/user)
var/input_color = tgui_input_list(user, "Choose a color", "Beacon Color", GLOB.marker_beacon_colors)
if(isnull(input_color))
if(isnull(input_color) || !user.can_perform_action(src))
return CLICK_ACTION_BLOCKING
if(!user.can_perform_action(src))
return NONE
picked_color = input_color
update_appearance()
return CLICK_ACTION_SUCCESS
/* Preset marker beacon types, for mapping */
// Set the icon_state here to make it clear for mappers.
@@ -73,6 +73,10 @@
hitsound = 'sound/items/weapons/drill.ogg'
desc = "An electric mining drill for the especially scrawny."
/obj/item/pickaxe/drill/Initialize(mapload)
. = ..()
AddElement(/datum/element/cuffable_item) //closed handle
/obj/item/pickaxe/drill/diamonddrill
name = "diamond-tipped mining drill"
icon_state = "diamonddrill"
@@ -4,6 +4,8 @@
#define BRIMDUST_STACKS_ON_LIFE 1
/// Number of stacks to add if you activate the item in hand
#define BRIMDUST_STACKS_ON_USE 3
/// Overlay alpha per effect stack
#define BRIMDUST_ALPHA_PER_STACK 75
/**
* Gives you three stacks of Brimdust Coating, when you get hit by anything it will make a short ranged explosion.
@@ -89,7 +91,8 @@
/atom/movable/screen/alert/status_effect/brimdust_coating
name = "Brimdust Coating"
desc = "You %STACKS% explosive dust, kinetic impacts will cause it to detonate! \
The explosion will not harm you as long as you're not under atmospheric pressure."
The explosion will not harm you as long as you're not under atmospheric pressure. \
Click this alert to shake off the dust."
/atom/movable/screen/alert/status_effect/brimdust_coating/MouseEntered(location,control,params)
desc = initial(desc)
@@ -106,6 +109,21 @@
desc = replacetext(desc, "%STACKS%", dust_amount_string)
return ..()
/atom/movable/screen/alert/status_effect/brimdust_coating/Click(location, control, params)
. = ..()
if(!.)
return
if(!owner.can_resist())
return
owner.balloon_alert(owner, "shaking off the dust...")
var/datum/status_effect/stacking/brimdust_coating/dust = attached_effect
if (!do_after(owner, dust.stacks * 1.5 SECONDS, owner))
return
dust.explode()
// The effect can get deleted from exploding and reducing stacks
if (!QDELETED(dust))
qdel(dust)
/datum/status_effect/stacking/brimdust_coating/refresh(effect, stacks_to_add)
. = ..()
add_stacks(stacks_to_add)
@@ -115,6 +133,10 @@
if (stacks == 0)
return
linked_alert.icon_state = "brimdemon_[stacks]"
if (dust_overlay)
owner.cut_overlay(dust_overlay)
dust_overlay.alpha = stacks * BRIMDUST_ALPHA_PER_STACK
owner.add_overlay(dust_overlay)
/datum/status_effect/stacking/brimdust_coating/on_creation(mob/living/new_owner, stacks_to_apply)
. = ..()
@@ -122,16 +144,19 @@
/datum/status_effect/stacking/brimdust_coating/on_apply()
. = ..()
dust_overlay = mutable_appearance('icons/effects/weather_effects.dmi', "ash_storm")
dust_overlay.alpha = stacks * BRIMDUST_ALPHA_PER_STACK
dust_overlay.color = COLOR_RED_LIGHT
dust_overlay.blend_mode = BLEND_INSET_OVERLAY
owner.add_overlay(dust_overlay)
owner.add_shared_particles(/particles/brimdust)
RegisterSignal(owner, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_cleaned))
RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage))
/datum/status_effect/stacking/brimdust_coating/on_remove()
. = ..()
owner.cut_overlay(dust_overlay)
owner.remove_shared_particles(/particles/brimdust)
UnregisterSignal(owner, list(COMSIG_MOB_APPLY_DAMAGE, COMSIG_COMPONENT_CLEAN_ACT))
/// When you are cleaned, wash off the buff
@@ -162,14 +187,14 @@
new /obj/effect/temp_visual/explosion/fast(origin_turf)
var/damage_dealt = blast_damage
var/list/possible_targets = range(1, origin_turf)
if(lavaland_equipment_pressure_check(origin_turf))
possible_targets -= owner
else
var/safe_explosion = lavaland_equipment_pressure_check(origin_turf)
if(!safe_explosion)
damage_dealt *= pressure_modifier
owner.apply_status_effect(/datum/status_effect/brimdust_concussion)
for(var/mob/living/target in possible_targets)
for(var/mob/living/target in range(1, origin_turf) - (safe_explosion ? list(owner, owner.buckled) : null))
if (safe_explosion && owner.faction_check_atom(target))
continue
var/armor = target.run_armor_check(attack_flag = BOMB)
target.apply_damage(damage_dealt, damagetype = BURN, blocked = armor, spread_damage = TRUE)
@@ -202,3 +227,4 @@
#undef BRIMDUST_LIFE_APPLY_COOLDOWN
#undef BRIMDUST_STACKS_ON_LIFE
#undef BRIMDUST_STACKS_ON_USE
#undef BRIMDUST_ALPHA_PER_STACK
@@ -2,7 +2,7 @@
#define HEALTH_DANGER_ZONE 30
/**
* On use in hand, makes you run really fast for 5 seconds and ignore injury movement decrease.
* On use in hand, makes you run faster for a bit and ignore injury movement decrease.
* On use when implanted, run for longer and ignore all negative movement. Automatically triggers if health is low (to escape).
*/
/obj/item/organ/monster_core/rush_gland
@@ -37,13 +37,13 @@
INVOKE_ASYNC(src, PROC_REF(trigger_organ_action))
/**
* Status effect: Makes you run really fast and ignore speed penalties for a short duration.
* Status effect: Makes you run faster and ignore damage speed penalties for a short duration.
* If you run into a wall indoors you will fall over and lose the buff.
* If you run into someone you both fall over.
*/
/datum/status_effect/lobster_rush
id = "lobster_rush"
duration = 3 SECONDS
duration = 30 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/lobster_rush
show_duration = TRUE
var/spawned_last_move = FALSE
@@ -55,50 +55,58 @@
/datum/status_effect/lobster_rush/on_apply()
. = ..()
RegisterSignal(owner, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move))
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
RegisterSignal(owner, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump))
owner.add_traits(list(TRAIT_IGNORESLOWDOWN, TRAIT_TENTACLE_IMMUNE), TRAIT_STATUS_EFFECT(id))
ADD_TRAIT(owner, TRAIT_TENTACLE_IMMUNE, TRAIT_STATUS_EFFECT(id))
owner.add_movespeed_mod_immunities(id, /datum/movespeed_modifier/damage_slowdown)
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/lobster_rush)
to_chat(owner, span_notice("You feel your blood pumping!"))
/datum/status_effect/lobster_rush/on_remove()
. = ..()
UnregisterSignal(owner, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_BUMP))
owner.remove_traits(list(TRAIT_IGNORESLOWDOWN, TRAIT_TENTACLE_IMMUNE), TRAIT_STATUS_EFFECT(id))
UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_BUMP))
REMOVE_TRAIT(owner, TRAIT_TENTACLE_IMMUNE, TRAIT_STATUS_EFFECT(id))
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/lobster_rush)
owner.remove_movespeed_mod_immunities(id, /datum/movespeed_modifier/damage_slowdown)
to_chat(owner, span_notice("Your pulse returns to normal."))
/// Spawn an afterimage every other step, because every step was too many
/datum/status_effect/lobster_rush/proc/on_move()
/datum/status_effect/lobster_rush/proc/on_move(datum/source, atom/old_loc, dir)
SIGNAL_HANDLER
if (!isturf(old_loc) || !isturf(owner.loc))
return
if (!spawned_last_move)
new /obj/effect/temp_visual/decoy/fading(owner.loc, owner)
new /obj/effect/temp_visual/decoy/fading(old_loc, owner, 150)
spawned_last_move = !spawned_last_move
/datum/status_effect/lobster_rush/proc/on_bump(mob/living/source, atom/target)
SIGNAL_HANDLER
if (!target.density)
return
if (isliving(target))
source.visible_message(span_warning("[source] crashes into [target]!"))
smack_into(source)
smack_into(target)
qdel(src)
return
if (lavaland_equipment_pressure_check(get_turf(source)))
return
smack_into(source)
source.visible_message(span_warning("[source] crashes into [target]!"))
if (isliving(target))
smack_into(target)
qdel(src)
/datum/status_effect/lobster_rush/proc/smack_into(mob/living/target)
target.Knockdown(5 SECONDS)
target.apply_damage(40, STAMINA)
target.apply_damage(20, BRUTE, spread_damage = TRUE)
target.Knockdown(2 SECONDS)
target.apply_damage(30, STAMINA)
target.apply_damage(10, BRUTE, spread_damage = TRUE)
/// You get a longer buff if you take the time to implant it in yourself
/datum/status_effect/lobster_rush/extended
duration = 5 SECONDS
duration = 60 SECONDS
/datum/status_effect/lobster_rush/extended/on_apply()
. = ..()
ADD_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
/datum/status_effect/lobster_rush/extended/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
/// Action used by the rush gland
/datum/action/cooldown/monster_core_action/adrenal_boost
@@ -106,6 +114,6 @@
desc = "Pump your rush gland to give yourself a boost of speed. \
Impacts with objects can be dangerous under atmospheric pressure."
button_icon_state = "lobster_gland_stable"
cooldown_time = 90 SECONDS
cooldown_time = 180 SECONDS
#undef HEALTH_DANGER_ZONE
@@ -24,39 +24,40 @@
reach = 2
attack_icon = 'icons/effects/effects.dmi'
attack_icon_state = "cain_abel_attack"
///our current combo count
/// Our current combo count
var/combo_count = 0
///the maximum combo we can reach
/// The maximum combo we can reach
var/max_combo = 6
///percentage boost we get on every combo
/// Percentage boost we get on every combo
var/damage_boost = 1.15
///pixel offsets of our wisps
var/static/list/wisp_offsets = list(
list(9, 12),
list(14, 0),
list(9, -12),
list(-9, 12),
list(-14, 0),
list(-9, -12),
)
///what throw mode we're using
/// Animation positions used by wisps
var/static/list/animation_steps
/// What throw mode we're using
var/throw_mode = THROW_MODE_CRYSTALS
///flames we have up!
/// Flames we have up!
var/list/current_wisps = list()
///cooldown till we can throw blades again
/// Cooldown till we can throw blades again
COOLDOWN_DECLARE(throw_cooldown)
/obj/item/cain_and_abel/Initialize(mapload)
. = ..()
AddComponent(/datum/component/two_handed, require_twohands = TRUE, force_unwielded = force, force_wielded = force)
if (length(animation_steps))
return
animation_steps = list(
new /datum/abel_wisp_frame(-18, -2, MOB_LAYER, EASE_OUT, EASE_IN),
new /datum/abel_wisp_frame(-6, -6, ABOVE_MOB_LAYER, EASE_IN, EASE_OUT),
new /datum/abel_wisp_frame(6, -6, ABOVE_MOB_LAYER, EASE_IN, EASE_OUT),
new /datum/abel_wisp_frame(18, -2, MOB_LAYER, EASE_OUT, EASE_IN),
new /datum/abel_wisp_frame(6, 2, BELOW_MOB_LAYER, EASE_IN, EASE_OUT),
new /datum/abel_wisp_frame(-6, 2, BELOW_MOB_LAYER, EASE_IN, EASE_OUT),
)
/obj/item/cain_and_abel/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(!isliving(old_loc))
return
unset_user(old_loc)
if(isliving(old_loc))
unset_user(old_loc)
/obj/item/cain_and_abel/proc/unset_user(mob/living/source)
if(HAS_TRAIT(source, TRAIT_RELAYING_ATTACKER))
@@ -65,7 +66,6 @@
set_combo(new_value = 0, user = source)
UnregisterSignal(source, list(COMSIG_ATOM_WAS_ATTACKED))
/obj/item/cain_and_abel/equipped(mob/user, slot)
. = ..()
@@ -92,8 +92,6 @@
for(var/index in 0 to (length(current_wisps) - 1))
addtimer(CALLBACK(src, PROC_REF(fire_wisp), user, interacting_with), index * 0.15 SECONDS)
set_combo(new_value = 0, user = user)
return ITEM_INTERACT_SUCCESS
/obj/item/cain_and_abel/attack(mob/living/target, mob/living/carbon/human/user)
@@ -117,29 +115,55 @@
user.balloon_alert(user, "crystals [throw_mode == THROW_MODE_CRYSTALS ? "activated" : "deactivated"]")
return TRUE
/obj/item/cain_and_abel/proc/set_combo(new_value, mob/living/user)
combo_count = (new_value <= max_combo) ? new_value : 0
handle_wisps(user)
/obj/item/cain_and_abel/proc/set_combo(new_value, mob/living/user, instant = FALSE)
new_value = clamp(new_value, 0, max_combo)
if (new_value == combo_count)
return
/obj/item/cain_and_abel/proc/handle_wisps(mob/living/user)
var/should_remove = length(current_wisps) > combo_count
var/wisps_to_alter = abs(combo_count - length(current_wisps))
for(var/i = 1, i <= wisps_to_alter, i++)
if(!should_remove)
if (new_value > combo_count)
for (var/i in 1 to new_value - combo_count)
add_wisp(user)
continue
var/obj/my_wisp = current_wisps[i]
remove_wisp(my_wisp)
else
for (var/i in 1 to combo_count - new_value)
remove_wisp(current_wisps[i], instant)
combo_count = new_value
/obj/item/cain_and_abel/proc/add_wisp(mob/living/user)
var/obj/effect/overlay/blood_wisp/new_wisp = new(src)
current_wisps += new_wisp
var/list/position = wisp_offsets[length(current_wisps)]
user.vis_contents += new_wisp
new_wisp.pixel_x = position[1]
new_wisp.pixel_y = position[2]
RegisterSignal(new_wisp, COMSIG_QDELETING, PROC_REF(on_wisp_delete))
for (var/wisp_index in 1 to length(current_wisps))
var/obj/effect/overlay/blood_wisp/wisp = current_wisps[wisp_index]
var/spawn_index = floor(length(animation_steps) / length(current_wisps) * wisp_index)
var/datum/abel_wisp_frame/spawn_position = animation_steps[spawn_index]
// Latest added wisp is teleported to its spawn position, others animate from their current position
if (wisp_index == length(current_wisps))
wisp.pixel_w = spawn_position.x
wisp.pixel_z = spawn_position.y
wisp.layer = spawn_position.layer
else
animate(wisp, tag = "wisp_anim_x")
animate(wisp, tag = "wisp_anim_y")
var/start_index = spawn_index % length(animation_steps) + 1
var/datum/abel_wisp_frame/position = animation_steps[start_index]
animate(wisp, pixel_w = position.x, layer = position.layer, time = 0.6 SECONDS, loop = -1, tag = "wisp_anim_x")
// We need to animate x and y coordinates separately as they have different easing steps
for (var/frame_index in 1 to length(animation_steps) - 1)
// Get actual index starting from our *next* animation step, not initial position
var/anim_index = (start_index + frame_index - 1) % length(animation_steps) + 1
position = animation_steps[anim_index]
animate(time = 0.6 SECONDS, pixel_w = position.x, layer = position.layer, easing = SINE_EASING | position.x_easing)
animate(wisp, time = 0.6 SECONDS, pixel_z = position.y, loop = -1, tag = "wisp_anim_y")
for (var/frame_index in 1 to length(animation_steps) - 1)
// Get actual index starting from our *next* animation step, not initial position
var/anim_index = (start_index + frame_index - 1) % length(animation_steps) + 1
position = animation_steps[anim_index]
animate(time = 0.6 SECONDS, pixel_z = position.y, easing = SINE_EASING | position.y_easing)
/obj/item/cain_and_abel/proc/on_wisp_delete(datum/source)
SIGNAL_HANDLER
@@ -148,7 +172,27 @@
/obj/item/cain_and_abel/proc/fire_wisp(atom/user, atom/target)
user.fire_projectile(/obj/projectile/dagger_wisp, target)
set_combo(combo_count - 1, user, TRUE)
/obj/item/cain_and_abel/proc/remove_wisp(obj/wisp_to_remove)
/obj/item/cain_and_abel/proc/remove_wisp(obj/wisp_to_remove, instant = FALSE)
if (instant)
qdel(wisp_to_remove)
return
animate(wisp_to_remove, alpha = 0, time = 0.2 SECONDS)
QDEL_IN(wisp_to_remove, 0.2 SECONDS)
/// Frame data for cain & abel wisps so we don't have to make list monstrocities
/datum/abel_wisp_frame
var/x = 0
var/y = 0
var/layer = MOB_LAYER
var/x_easing = NONE
var/y_easing = NONE
/datum/abel_wisp_frame/New(x, y, layer, x_easing, y_easing)
. = ..()
src.x = x
src.y = y
src.layer = layer
src.x_easing = x_easing
src.y_easing = y_easing
@@ -1,6 +1,5 @@
//effect when we're swinging wildly around
/obj/effect/temp_visual/dagger_slash
name = "Blood Wisp"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE
@@ -19,10 +18,10 @@
//flames we collect around our body
/obj/effect/overlay/blood_wisp
name = "Blood Wisp"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE
layer = ABOVE_HUD_PLANE
vis_flags = VIS_INHERIT_PLANE
icon = 'icons/effects/effects.dmi'
icon_state = "blood_wisp"
light_power = 2
@@ -175,7 +175,7 @@
// consumer.dna.features = list(
// FEATURE_MUTANT_COLOR = "#A02720",
// FEATURE_TAIL_LIZARD = "Dark Tiger",
// FEATURE_TAIL = "None",
// FEATURE_TAIL_CAT = "None",
// FEATURE_SNOUT = "Sharp",
// FEATURE_HORNS = "Curled",
// FEATURE_EARS = "None",
@@ -75,7 +75,7 @@
/obj/item/hierophant_club/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
// If our target is the beacon and the hierostaff is next to the beacon, we're trying to pick it up.
if (interacting_with == beacon)
if (interacting_with == beacon || !isturf(interacting_with.loc))
return NONE
if (!blink_activated)
@@ -96,8 +96,8 @@
/obj/structure/closet/crate/necropolis/colossus
name = "colossus chest"
/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/proj)
if(istype(proj, /obj/projectile/colossus))
/obj/structure/closet/crate/necropolis/colossus/projectile_hit(obj/projectile/hitting_projectile, def_zone, piercing_hit, blocked)
if(istype(hitting_projectile, /obj/projectile/colossus))
return BULLET_ACT_FORCE_PIERCE
return ..()
+1 -14
View File
@@ -648,7 +648,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
name = "eldritch coin"
desc = "A surprisingly heavy, ornate coin. Its sides seem to depict a different image each time you look."
icon_state = "coin_heretic"
custom_materials = list(/datum/material/diamond =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT)
custom_materials = list(/datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT)
sideslist = list("heretic", "blade")
heads_name = "heretic"
has_action = TRUE
@@ -679,17 +679,4 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
continue
target_airlock.lock()
/obj/item/coin/eldritch/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!istype(interacting_with, /obj/machinery/door/airlock))
return NONE
if(!IS_HERETIC(user))
user.adjustBruteLoss(5)
user.adjustFireLoss(5)
return ITEM_INTERACT_BLOCKING
var/obj/machinery/door/airlock/target_airlock = interacting_with
to_chat(user, span_warning("You insert [src] into the airlock."))
target_airlock.emag_act(user, src)
qdel(src)
return ITEM_INTERACT_SUCCESS
#undef ORESTACK_OVERLAYS_MAX