diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm index f8f33c46644..96f0a7e023f 100644 --- a/code/__DEFINES/achievements.dm +++ b/code/__DEFINES/achievements.dm @@ -122,6 +122,9 @@ // DB ID for intento score #define INTENTO_SCORE "Intento Score" +// DB ID for style point count +#define STYLE_SCORE "Style Score" + // Tourist related achievements and scores //centcom grades (achievement) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 06f1850353c..68e7cdd11e2 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -139,3 +139,25 @@ /// From /mob/living/unfriend() : (mob/living/old_friend) #define COMSIG_LIVING_UNFRIENDED "living_unfriended" + +/// From /obj/effect/temp_visual/resonance/burst() : (mob/creator, mob/living/hit_living) +#define COMSIG_LIVING_RESONATOR_BURST "living_resonator_burst" + +/// From /obj/projectile/attempt_parry() : (obj/projectile/parried_projectile) +#define COMSIG_LIVING_PROJECTILE_PARRYING "living_projectile_parrying" + /// Return to allow the parry to happen + #define ALLOW_PARRY (1<<0) + +/// From /obj/projectile/on_parry() : (obj/projectile/parried_projectile) +#define COMSIG_LIVING_PROJECTILE_PARRIED "living_projectile_parried" + /// Return to prevent the projectile from executing any code in on_parry() + #define INTERCEPT_PARRY_EFFECTS (1<<0) + +/// From /turf/closed/mineral/gibtonite/defuse() : (det_time) +#define COMSIG_LIVING_DEFUSED_GIBTONITE "living_defused_gibtonite" + +/// From /obj/item/kinetic_crusher/afterattack() : (mob/living/target, obj/item/kinetic_crusher/crusher, backstabbed) +#define COMSIG_LIVING_CRUSHER_DETONATE "living_crusher_detonate" + +/// From /obj/structure/geyser/attackby() : (obj/structure/geyser/geyser) +#define COMSIG_LIVING_DISCOVERED_GEYSER "living_discovered_geyser" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 1c289acbf69..f652f75a341 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -173,6 +173,8 @@ #define COMSIG_MOB_AUTOMUTE_CHECK "client_automute_check" // The check is performed by the client. /// Prevents the automute system checking this client for repeated messages. #define WAIVE_AUTOMUTE_CHECK (1<<0) +///From base of /turf/closed/mineral/proc/gets_drilled(): (turf/closed/mineral/rock, give_exp) +#define COMSIG_MOB_MINED "mob_mined" ///from living/flash_act(), when a mob is successfully flashed. #define COMSIG_MOB_FLASHED "mob_flashed" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index a5ed899e17f..7438c78f4d2 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -249,6 +249,7 @@ #define CRIT_LAYER 5 #define CURSE_LAYER 6 #define ECHO_LAYER 7 +#define PARRY_LAYER 8 #define FOV_EFFECT_LAYER 100 diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 79e2e4e1411..afedfa5befe 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -118,6 +118,9 @@ icon_state = "oxydamageoverlay" layer = BLIND_LAYER +/atom/movable/screen/fullscreen/crit/projectile_parry + layer = PARRY_LAYER + /atom/movable/screen/fullscreen/blind icon_state = "blackimageoverlay" layer = BLIND_LAYER diff --git a/code/datums/achievements/misc_scores.dm b/code/datums/achievements/misc_scores.dm index 067cd68d7df..b91f46008de 100644 --- a/code/datums/achievements/misc_scores.dm +++ b/code/datums/achievements/misc_scores.dm @@ -15,3 +15,9 @@ name = "Intento Score" desc = "A blast from the future?" database_id = INTENTO_SCORE + +/// What's the highest amount of style points we've gotten? +/datum/award/score/style_score + name = "Style Score" + desc = "You might not be a robot, but you were damn close." + database_id = STYLE_SCORE diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm new file mode 100644 index 00000000000..fedfcb77d15 --- /dev/null +++ b/code/datums/components/parry.dm @@ -0,0 +1,38 @@ +/// Add to a living mob to allow them to "parry" projectiles by clicking on their tile, sending them back at the firer. +/datum/component/projectile_parry + /// typecache of valid projectiles to be able to parry + var/list/parryable_projectiles + + +/datum/component/projectile_parry/Initialize(list/projectiles_to_parry) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + parryable_projectiles = typecacheof(projectiles_to_parry) + + +/datum/component/projectile_parry/RegisterWithParent() + RegisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRYING, PROC_REF(parrying_projectile)) + RegisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRIED, PROC_REF(parried_projectile)) + + +/datum/component/projectile_parry/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_PROJECTILE_PARRYING, COMSIG_LIVING_PROJECTILE_PARRIED)) + + +/datum/component/projectile_parry/proc/parrying_projectile(datum/source, obj/projectile/parried_projectile) + SIGNAL_HANDLER + + if(is_type_in_typecache(parried_projectile, parryable_projectiles)) + return ALLOW_PARRY + + +/datum/component/projectile_parry/proc/parried_projectile(datum/source, obj/projectile/parried_projectile) + SIGNAL_HANDLER + + var/mob/living/living_parent = parent + + living_parent.playsound_local(get_turf(parried_projectile), 'sound/effects/parry.ogg', 50, TRUE) + living_parent.overlay_fullscreen("projectile_parry", /atom/movable/screen/fullscreen/crit/projectile_parry, 2) + addtimer(CALLBACK(living_parent, TYPE_PROC_REF(/mob, clear_fullscreen), "projectile_parry"), 0.25 SECONDS) + living_parent.visible_message(span_warning("[living_parent] expertly parries [parried_projectile] with [living_parent.p_their()] bare hand!"), span_warning("You parry [parried_projectile] with your hand!")) diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm new file mode 100644 index 00000000000..fa40df7ef10 --- /dev/null +++ b/code/datums/components/style/style.dm @@ -0,0 +1,496 @@ +#define STYLE_DULL 1 +#define STYLE_COOL 2 +#define STYLE_BRUTAL 3 +#define STYLE_ABSOLUTE 4 +#define STYLE_SPACED 5 + +#define ACTION_KILL "KILL" +#define ACTION_MINOR_KILL "MINOR KILL" +#define ACTION_MAJOR_KILL "MAJOR KILL" +#define ACTION_DISRESPECT "DISRESPECT" +#define ACTION_MELEED "MELEE'D" +#define ACTION_ROCK_MINED "ROCK MINED" +#define ACTION_ORE_MINED "ORE MINED" +#define ACTION_TRAPPER "TRAPPER" +#define ACTION_PARRIED "PARRIED" +#define ACTION_PROJECTILE_BOOST "PROJECTILE BOOST" +#define ACTION_GIBTONITE_HIT "GIBTONITE HIT" +#define ACTION_GIBTONITE_BOOM "GIBTONITE BOOM" +#define ACTION_GIBTONITE_DEFUSED "GIBTONITE DEFUSED" +#define ACTION_MARK_DETONATED "MARK DETONATED" +#define ACTION_GEYSER_MARKED "GEYSER MARKED" + +/datum/component/style + /// Amount of style we have. + var/style_points = -1 + /// Our style point multiplier. + var/point_multiplier = 1 + /// The current rank we have. + var/rank = STYLE_DULL + /// The last point affecting actions we've done + var/list/actions = list() + /// The style meter shown on screen. + var/atom/movable/screen/style_meter_background/meter + /// The image of the style meter. + var/atom/movable/screen/style_meter/meter_image + /// The timer for meter updating + var/timerid + /// Highest score attained by this component, to avoid as much overhead when considering to award a high score to the client + var/high_score = 0 + /// Weakref to the added projectile parry component + var/datum/weakref/projectile_parry + /// What rank, minimum, the user needs to be to hotswap items + var/hotswap_rank = STYLE_BRUTAL + /// If this is multitooled, making it make funny noises on the user's rank going up + var/multitooled = FALSE + /// A static list of lists of all the possible sounds to play when multitooled, in numerical order + var/static/list/rankup_sounds = list( + list( + 'sound/items/style/combo_dull1.ogg', + 'sound/items/style/combo_dull2.ogg', + 'sound/items/style/combo_dull3.ogg', + ), + list( + 'sound/items/style/combo_cool1.ogg', + 'sound/items/style/combo_cool2.ogg', + 'sound/items/style/combo_cool3.ogg', + ), + list( + 'sound/items/style/combo_brutal1.ogg', + 'sound/items/style/combo_brutal2.ogg', + 'sound/items/style/combo_brutal3.ogg', + ), + list( + 'sound/items/style/combo_absolute1.ogg', + 'sound/items/style/combo_absolute2.ogg', + 'sound/items/style/combo_absolute3.ogg', + ), + list( + 'sound/items/style/combo_spaced1.ogg', + 'sound/items/style/combo_spaced2.ogg', + ), + ) + + +/datum/component/style/Initialize(multitooled = FALSE) + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + + var/mob/mob_parent = parent + + meter = new() + meter_image = new() + meter.vis_contents += meter_image + meter_image.add_filter("meter_mask", 1, list(type = "alpha", icon = icon('icons/hud/style_meter.dmi', "style_meter"), flags = MASK_INVERSE)) + meter.update_appearance() + meter_image.update_appearance() + + update_screen() + + if(mob_parent.hud_used) + mob_parent.hud_used.static_inventory += meter + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + + START_PROCESSING(SSdcs, src) + + if(multitooled) + src.multitooled = multitooled + + RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_parent_multitool)) + +/datum/component/style/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_ITEM_AFTERATTACK, PROC_REF(hotswap)) + RegisterSignal(parent, COMSIG_MOB_MINED, PROC_REF(on_mine)) + RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) + RegisterSignal(parent, COMSIG_MOB_EMOTED("flip"), PROC_REF(on_flip)) + RegisterSignal(parent, COMSIG_MOB_EMOTED("spin"), PROC_REF(on_spin)) + RegisterSignal(parent, COMSIG_MOB_ITEM_ATTACK, PROC_REF(on_attack)) + RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_punch)) + RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(on_death)) + RegisterSignal(parent, COMSIG_LIVING_RESONATOR_BURST, PROC_REF(on_resonator_burst)) + RegisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRIED, PROC_REF(on_projectile_parry)) + RegisterSignal(parent, COMSIG_LIVING_DEFUSED_GIBTONITE, PROC_REF(on_gibtonite_defuse)) + RegisterSignal(parent, COMSIG_LIVING_CRUSHER_DETONATE, PROC_REF(on_crusher_detonate)) + RegisterSignal(parent, COMSIG_LIVING_DISCOVERED_GEYSER, PROC_REF(on_geyser_discover)) + + projectile_parry = WEAKREF(AddComponent(\ + /datum/component/projectile_parry,\ + list(\ + /obj/projectile/colossus,\ + /obj/projectile/temp/basilisk,\ + /obj/projectile/kinetic,\ + /obj/projectile/bileworm_acid,\ + /obj/projectile/herald,\ + )\ + ) + ) + + +/datum/component/style/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_MOB_ITEM_AFTERATTACK) + UnregisterSignal(parent, COMSIG_MOB_MINED) + UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE) + UnregisterSignal(parent, list(COMSIG_MOB_EMOTED("flip"), COMSIG_MOB_EMOTED("spin"))) + UnregisterSignal(parent, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)) + UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH) + UnregisterSignal(parent, COMSIG_LIVING_RESONATOR_BURST) + UnregisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRIED) + UnregisterSignal(parent, COMSIG_LIVING_DEFUSED_GIBTONITE) + UnregisterSignal(parent, COMSIG_LIVING_CRUSHER_DETONATE) + UnregisterSignal(parent, COMSIG_LIVING_DISCOVERED_GEYSER) + + if(projectile_parry) + qdel(projectile_parry.resolve()) + + +/datum/component/style/Destroy(force, silent) + STOP_PROCESSING(SSdcs, src) + var/mob/mob_parent = parent + if(mob_parent.hud_used) + mob_parent.hud_used.static_inventory -= meter + mob_parent.hud_used.show_hud(mob_parent.hud_used.hud_version) + return ..() + + +/datum/component/style/process(seconds_per_tick) + point_multiplier = round(max(point_multiplier - 0.2 * seconds_per_tick, 1), 0.1) + change_points(-5 * seconds_per_tick * ROUND_UP((style_points + 1) / 200), use_multiplier = FALSE) + update_screen() + + + +/datum/component/style/proc/add_action(action, amount) + if(length(actions) > 9) + actions.Cut(1, 2) + if(length(actions)) + var/last_action = actions[length(actions)] + if(action == actions[last_action]) + amount *= 0.5 + var/id + while(!id || (id in actions)) + id = "action[rand(1, 1000)]" + actions[id] = action + change_points(amount) + addtimer(CALLBACK(src, PROC_REF(remove_action), id), 10 SECONDS) + +/datum/component/style/proc/remove_action(action_id) + actions -= action_id + update_screen() + +/datum/component/style/proc/change_points(amount, use_multiplier = TRUE) + if(!amount) + return + + var/modified_amount = amount * (amount > 0 ? 1 - 0.1 * rank : 1) * (use_multiplier ? point_multiplier : 1) + style_points = max(style_points + modified_amount, -1) + update_screen() + + if(style_points > high_score) + var/mob/mob_parent = parent + if((mob_parent.flags_1 & ADMIN_SPAWNED_1) || (mob_parent.datum_flags & DF_VAR_EDITED) || (datum_flags & DF_VAR_EDITED) || !SSachievements.achievements_enabled || !mob_parent.client) // No varediting/spawning this or the owner + return + + var/award_status = mob_parent.client.get_award_status(/datum/award/score/style_score) + + if(award_status >= style_points) + high_score = style_points + return + + mob_parent.client.give_award(/datum/award/score/style_score, mob_parent, style_points - award_status) + high_score = style_points + +/datum/component/style/proc/update_screen(rank_changed) + var/go_back = null + if(!isnull(rank_changed)) + timerid = null + if(rank_changed == point_to_rank()) + go_back = rank > rank_changed ? 100 : 0 + + if(multitooled && (rank_changed > rank)) // make funny noises + var/mob/mob_parent = parent + mob_parent.playsound_local(get_turf(mob_parent), pick(rankup_sounds[rank_changed]), 70, vary = FALSE) + + if((rank < hotswap_rank) && (rank_changed >= hotswap_rank)) + var/mob/mob_parent = parent + mob_parent.balloon_alert(mob_parent, "hotswapping enabled") + + else if((rank >= hotswap_rank) && (rank_changed < hotswap_rank)) + var/mob/mob_parent = parent + mob_parent.balloon_alert(mob_parent, "hotswapping disabled") + + rank = rank_changed + meter.maptext = "[format_rank_string(rank)][generate_multiplier()][generate_actions()]" + meter.maptext_y = 100 - 9 * length(actions) + update_meter(point_to_rank(), go_back) + +/datum/component/style/proc/update_meter(new_rank, go_back) + if(!isnull(go_back)) + animate(meter_image.get_filter("meter_mask"), time = 0 SECONDS, flags = ANIMATION_END_NOW, x = go_back) + animate(meter_image.get_filter("meter_mask"), time = 1 SECONDS, x = (rank > new_rank ? 0 : (rank < new_rank ? 100 : (style_points % 100) + 1))) + if(!isnull(new_rank) && new_rank != rank && !timerid) + timerid = addtimer(CALLBACK(src, PROC_REF(update_screen), new_rank), 1 SECONDS) + +/datum/component/style/proc/rank_to_color(new_rank) + switch(new_rank) + if(STYLE_DULL) + return "#aaaaaa" + if(STYLE_COOL) + return "#aaaaff" + if(STYLE_BRUTAL) + return "#aaffff" + if(STYLE_ABSOLUTE) + return "#66ffff" + if(STYLE_SPACED) + return "#ffaa00" + +/datum/component/style/proc/point_to_rank() + switch(style_points) + if(-1 to 99) + return STYLE_DULL + if(100 to 199) + return STYLE_COOL + if(200 to 299) + return STYLE_BRUTAL + if(300 to 399) + return STYLE_ABSOLUTE + if(400 to INFINITY) + return STYLE_SPACED + + +/datum/component/style/proc/rank_to_string(new_rank) + switch(new_rank) + if(STYLE_DULL) + return "DULL" + if(STYLE_COOL) + return "COOL" + if(STYLE_BRUTAL) + return "BRUTAL" + if(STYLE_ABSOLUTE) + return "ABSOLUTE" + if(STYLE_SPACED) + return "SPACED!" + +/datum/component/style/proc/format_rank_string(new_rank) + var/rank_string = rank_to_string(new_rank) + var/final_string = "" + final_string += "[rank_string[1]]" + final_string += "[copytext(rank_string, 2)]" + return final_string + +/datum/component/style/proc/generate_multiplier() + return "
MULTIPLIER: [point_multiplier]X" + +/datum/component/style/proc/generate_actions() + var/action_string = "" + for(var/action in actions) + action_string += "
+ [actions[action]]" + return action_string + +/datum/component/style/proc/action_to_color(action) + switch(action) + if(ACTION_KILL) + return "#ff0000" + if(ACTION_MINOR_KILL) + return "#ff6666" + if(ACTION_MAJOR_KILL) + return "#ffaa00" + if(ACTION_DISRESPECT) + return "#990000" + if(ACTION_MELEED) + return "#660033" + if(ACTION_ROCK_MINED) + return "#664433" + if(ACTION_ORE_MINED) + return "#663366" + if(ACTION_TRAPPER) + return "#363366" + if(ACTION_PARRIED) + return "#591324" + if(ACTION_PROJECTILE_BOOST) + return "#80112c" + if(ACTION_GIBTONITE_HIT) + return "#201d40" + if(ACTION_GIBTONITE_BOOM) + return "#1e176e" + if(ACTION_GIBTONITE_DEFUSED) + return "#2b2573" + if(ACTION_MARK_DETONATED) + return "#ac870e" + if(ACTION_GEYSER_MARKED) + return "#364866" + +/// A proc that lets a user, when their rank >= `hotswap_rank`, swap items in storage with what's in their hands, simply by clicking on the stored item with a held item +/datum/component/style/proc/hotswap(mob/living/source, atom/target, obj/item/weapon, proximity_flag, click_parameters) + SIGNAL_HANDLER + + if((rank < hotswap_rank) || !isitem(target) || !(target in source.get_all_contents())) + return + + var/obj/item/item_target = target + + if(!(item_target.item_flags & IN_STORAGE)) + return + + var/datum/storage/atom_storage = item_target.loc.atom_storage + + if(!atom_storage.can_insert(weapon, source, messages = FALSE)) + source.balloon_alert(source, "unable to hotswap!") + return + + atom_storage.attempt_insert(weapon, source, override = TRUE) + INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, put_in_hands), target) + source.visible_message(span_notice("[source] quickly swaps [weapon] out with [target]!"), span_notice("You quickly swap [weapon] with [target].")) + + +/datum/component/style/proc/on_parent_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes) + multitooled = !multitooled + user.balloon_alert(user, "meter [multitooled ? "" : "un"]hacked") + + +// Point givers +/datum/component/style/proc/on_punch(mob/living/carbon/human/punching_person, atom/attacked_atom, proximity) + SIGNAL_HANDLER + + if(!proximity || !punching_person.combat_mode || !isliving(attacked_atom)) + return + + var/mob/living/disrespected = attacked_atom + if(disrespected.stat || faction_check(punching_person.faction, disrespected.faction) || !(FACTION_MINING in disrespected.faction)) + return + + add_action(ACTION_DISRESPECT, 60 * (ismegafauna(disrespected) ? 2 : 1)) + +/datum/component/style/proc/on_attack(mob/living/attacking_person, mob/living/attacked_mob) + SIGNAL_HANDLER + + if(!istype(attacked_mob) || attacked_mob.stat) + return + + var/mob/living/attacked = attacked_mob + var/mob/mob_parent = parent + if(faction_check(attacking_person.faction, attacked.faction) || !(FACTION_MINING in attacked.faction) || (istype(mob_parent.get_active_held_item(), /obj/item/kinetic_crusher) && attacked.has_status_effect(/datum/status_effect/crusher_mark))) + return + + add_action(ACTION_MELEED, 50 * (ismegafauna(attacked) ? 1.5 : 1)) + +/datum/component/style/proc/on_mine(datum/source, turf/closed/mineral/rock, give_exp) + SIGNAL_HANDLER + + if(istype(rock, /turf/closed/mineral/gibtonite)) + var/turf/closed/mineral/gibtonite/gib_rock = rock + switch(gib_rock.stage) + if(GIBTONITE_UNSTRUCK) + add_action(ACTION_GIBTONITE_HIT, 40) + return + + if(GIBTONITE_ACTIVE) + add_action(ACTION_GIBTONITE_BOOM, 50) + return + + if(GIBTONITE_DETONATE) + add_action(ACTION_GIBTONITE_BOOM, 50) + return + + if(rock.mineralType) + if(give_exp) + add_action(ACTION_ORE_MINED, 40) + rock.mineralAmt = ROUND_UP(rock.mineralAmt * (1 + ((rank * 0.1) - 0.3))) // You start out getting 20% less ore, but it goes up to 20% more at S-tier + + else if(give_exp) + add_action(ACTION_ROCK_MINED, 25) + +/datum/component/style/proc/on_resonator_burst(datum/source, mob/creator, mob/living/hit_living) + SIGNAL_HANDLER + + if(faction_check(creator.faction, hit_living.faction) || (hit_living.stat != CONSCIOUS) || !(FACTION_MINING in hit_living.faction)) + return + + add_action(ACTION_TRAPPER, 70) + +/datum/component/style/proc/on_projectile_parry(datum/source, obj/projectile/parried) + SIGNAL_HANDLER + + if(parried.firer == parent) + add_action(ACTION_PROJECTILE_BOOST, 160) // This is genuinely really impressive + else + add_action(ACTION_PARRIED, 110) + +/datum/component/style/proc/on_gibtonite_defuse(datum/source, det_time) + SIGNAL_HANDLER + + add_action(ACTION_GIBTONITE_DEFUSED, min(40, 20 * (10 - det_time))) // 40 to 180 points depending on speed + +/datum/component/style/proc/on_crusher_detonate(datum/source, mob/living/target, obj/item/kinetic_crusher/crusher, backstabbed) + SIGNAL_HANDLER + + var/has_brimdemon_trophy = locate(/obj/item/crusher_trophy/brimdemon_fang) in crusher.trophies + + add_action(ACTION_MARK_DETONATED, round((backstabbed ? 60 : 30) * (ismegafauna(target) ? 1.5 : 1) * (has_brimdemon_trophy ? 1.25 : 1))) + + +/datum/component/style/proc/on_geyser_discover(datum/source, obj/structure/geyser/geyser) + SIGNAL_HANDLER + + add_action(ACTION_GEYSER_MARKED, 100) + + +// Emote-based multipliers +/datum/component/style/proc/on_flip() + SIGNAL_HANDLER + + point_multiplier = round(min(point_multiplier + 0.5, 3), 0.1) + update_screen() + +/datum/component/style/proc/on_spin() + SIGNAL_HANDLER + + point_multiplier = round(min(point_multiplier + 0.3, 3), 0.1) + update_screen() + + +// Negative effects +/datum/component/style/proc/on_take_damage() + SIGNAL_HANDLER + + point_multiplier = round(max(point_multiplier - 0.3, 1), 0.1) + change_points(-30, use_multiplier = FALSE) + +/datum/component/style/proc/on_death(datum/source, mob/living/died, gibbed) + SIGNAL_HANDLER + + var/mob/mob_parent = parent + if(died == parent) + change_points(-500, use_multiplier = FALSE) + return + else if(faction_check(mob_parent.faction, died.faction) || !(FACTION_MINING in died.faction) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent)))) + return + if(ismegafauna(died)) + add_action(ACTION_MAJOR_KILL, 350) + + else if(died.maxHealth >= 75) //at least legions + add_action(ACTION_KILL, 125) + + else if(died.maxHealth >= 30) //at least goliath children, dont count legion skulls + add_action(ACTION_MINOR_KILL, 75) + +#undef STYLE_DULL +#undef STYLE_COOL +#undef STYLE_BRUTAL +#undef STYLE_ABSOLUTE +#undef STYLE_SPACED + +#undef ACTION_KILL +#undef ACTION_MINOR_KILL +#undef ACTION_MAJOR_KILL +#undef ACTION_DISRESPECT +#undef ACTION_MELEED +#undef ACTION_ROCK_MINED +#undef ACTION_ORE_MINED +#undef ACTION_TRAPPER +#undef ACTION_PARRIED +#undef ACTION_PROJECTILE_BOOST +#undef ACTION_GIBTONITE_HIT +#undef ACTION_GIBTONITE_BOOM +#undef ACTION_GIBTONITE_DEFUSED +#undef ACTION_MARK_DETONATED +#undef ACTION_GEYSER_MARKED diff --git a/code/datums/components/style/style_meter.dm b/code/datums/components/style/style_meter.dm new file mode 100644 index 00000000000..2b0d4871cca --- /dev/null +++ b/code/datums/components/style/style_meter.dm @@ -0,0 +1,139 @@ +/obj/item/style_meter + name = "style meter attachment" + desc = "Attach this to a pair of glasses to install a style meter system in them. \ + You get style points from performing stylish acts and lose them for breaking your style. \ + The style affects the quality of your mining, with you being able to mine ore better during a good chain. \ + A responsive data HUD gives you the ability to reflect lavaland-based projectiles by punching them with an empty hand. \ + In addition, at high style, you are able to swap an item in your hand with one in your backpack by hitting one with another." + icon_state = "style_meter" + icon = 'icons/obj/clothing/glasses.dmi' + /// The style meter component we give. + var/datum/component/style/style_meter + /// Mutable appearance added to the attached glasses + var/mutable_appearance/meter_appearance + /// If this is multitooled, which is passed onto the component on-creation, if one doesn't currently exist + var/multitooled = FALSE + +/obj/item/style_meter/Initialize(mapload) + . = ..() + meter_appearance = mutable_appearance(icon, icon_state) + RegisterSignal(src, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool)) + +/obj/item/style_meter/Destroy(force) + if(istype(loc, /obj/item/clothing/glasses)) + clean_up(loc) + return ..() + +/obj/item/style_meter/examine(mob/user) + . = ..() + . += span_notice("You feel like a multitool could be used on this.") + +/obj/item/style_meter/afterattack(atom/movable/attacked_atom, mob/user, proximity_flag, click_parameters) + . = ..() + if(!istype(attacked_atom, /obj/item/clothing/glasses)) + return + + forceMove(attacked_atom) + attacked_atom.add_overlay(meter_appearance) + RegisterSignal(attacked_atom, COMSIG_ITEM_EQUIPPED, PROC_REF(check_wearing)) + RegisterSignal(attacked_atom, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) + RegisterSignal(attacked_atom, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(attacked_atom, COMSIG_CLICK_ALT, PROC_REF(on_altclick)) + RegisterSignal(attacked_atom, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(on_multitool)) + balloon_alert(user, "style meter attached") + playsound(src, 'sound/machines/click.ogg', 30, TRUE) + if(!iscarbon(attacked_atom.loc)) + return + + var/mob/living/carbon/carbon_wearer = attacked_atom.loc + if(carbon_wearer.glasses != attacked_atom) + return + + style_meter = carbon_wearer.AddComponent(/datum/component/style, multitooled) + +/obj/item/style_meter/Moved(atom/old_loc, Dir, momentum_change) + . = ..() + if(!istype(old_loc, /obj/item/clothing/glasses)) + return + clean_up(old_loc) + + +/// Check if the glasses that this meter is linked with are being worn +/obj/item/style_meter/proc/check_wearing(datum/source, mob/equipper, slot) + SIGNAL_HANDLER + + if(!(slot & ITEM_SLOT_EYES)) + if(style_meter) + QDEL_NULL(style_meter) + return + + style_meter = equipper.AddComponent(/datum/component/style, multitooled) + + +/// Signal proc for when the meter-holding glasses are dropped/unequipped +/obj/item/style_meter/proc/on_drop(datum/source, mob/user) + SIGNAL_HANDLER + + if(!style_meter) + return + + QDEL_NULL(style_meter) + + +/// Signal proc for on-examine +/obj/item/style_meter/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + examine_list += span_notice("You feel like a multitool could be used on this.") + examine_list += span_notice("Alt-click to remove the style meter.") + + +/// Signal proc to remove from glasses +/obj/item/style_meter/proc/on_altclick(datum/source, mob/user) + SIGNAL_HANDLER + + if(istype(loc, /obj/item/clothing/glasses)) + clean_up() + forceMove(get_turf(src)) + + return COMPONENT_CANCEL_CLICK_ALT + + +/// Signal proc for when the glasses or the meter is multitooled +/obj/item/style_meter/proc/on_multitool(datum/source, mob/living/user, obj/item/tool, list/recipes) + multitooled = !multitooled + if(style_meter) + SEND_SIGNAL(style_meter, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), user, tool, recipes) + else + balloon_alert(user, "meter [multitooled ? "" : "un"]hacked") + + +/// Unregister signals and just generally clean up ourselves after being removed from glasses +/obj/item/style_meter/proc/clean_up(atom/movable/old_location) + old_location.cut_overlay(meter_appearance) + UnregisterSignal(old_location, COMSIG_ITEM_EQUIPPED) + UnregisterSignal(old_location, COMSIG_ITEM_DROPPED) + UnregisterSignal(old_location, COMSIG_PARENT_EXAMINE) + UnregisterSignal(old_location, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL)) + if(!style_meter) + return + QDEL_NULL(style_meter) + + +/atom/movable/screen/style_meter_background + icon_state = "style_meter_background" + icon = 'icons/hud/style_meter.dmi' + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + screen_loc = "WEST,CENTER:16" + maptext_height = 120 + maptext_width = 105 + maptext_x = 5 + maptext_y = 100 + maptext = "" + layer = SCREENTIP_LAYER + +/atom/movable/screen/style_meter + icon_state = "style_meter" + icon = 'icons/hud/style_meter.dmi' + layer = SCREENTIP_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT diff --git a/code/game/machinery/computer/orders/order_items/mining/order_mining.dm b/code/game/machinery/computer/orders/order_items/mining/order_mining.dm index 78242c12608..b41ccfa6254 100644 --- a/code/game/machinery/computer/orders/order_items/mining/order_mining.dm +++ b/code/game/machinery/computer/orders/order_items/mining/order_mining.dm @@ -97,3 +97,7 @@ /datum/orderable_item/mining/hiking_boots item_path = /obj/item/clothing/shoes/winterboots/ice_boots cost_per_order = 2500 + +/datum/orderable_item/mining/style_meter + item_path = /obj/item/style_meter + cost_per_order = 1500 diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm index 8e2649fea03..dcfc86c0310 100644 --- a/code/game/objects/structures/lavaland/geyser.dm +++ b/code/game/objects/structures/lavaland/geyser.dm @@ -67,6 +67,7 @@ return to_chat(user, span_notice("You discovered the geyser and mark it on the GPS system!")) + SEND_SIGNAL(user, COMSIG_LIVING_DISCOVERED_GEYSER, src) if(discovery_message) to_chat(user, discovery_message) diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm index 6d456f8ff67..f04d5e5c277 100644 --- a/code/game/turfs/closed/minerals.dm +++ b/code/game/turfs/closed/minerals.dm @@ -134,7 +134,9 @@ if(user.Adjacent(src)) attack_hand(user) -/turf/closed/mineral/proc/gets_drilled(user, give_exp = FALSE) +/turf/closed/mineral/proc/gets_drilled(mob/user, give_exp = FALSE) + if(istype(user)) + SEND_SIGNAL(user, COMSIG_MOB_MINED, src, give_exp) if (mineralType && (mineralAmt > 0)) new mineralType(src, mineralAmt) SSblackbox.record_feedback("tally", "ore_mined", mineralAmt, mineralType) @@ -674,7 +676,7 @@ var/previous_stage = stage if(istype(attacking_item, /obj/item/mining_scanner) || istype(attacking_item, /obj/item/t_scanner/adv_mining_scanner) && stage == GIBTONITE_ACTIVE) user.visible_message(span_notice("[user] holds [attacking_item] to [src]..."), span_notice("You use [attacking_item] to locate where to cut off the chain reaction and attempt to stop it...")) - defuse() + defuse(user) ..() if(istype(attacking_item, /obj/item/clothing/gloves/gauntlets) && previous_stage == GIBTONITE_UNSTRUCK && stage == GIBTONITE_ACTIVE && istype(user)) user.Immobilize(0.5 SECONDS) @@ -713,7 +715,7 @@ stage = GIBTONITE_DETONATE explosion(bombturf, devastation_range = 1, heavy_impact_range = 3, light_impact_range = 5, adminlog = notify_admins, explosion_cause = src) -/turf/closed/mineral/gibtonite/proc/defuse() +/turf/closed/mineral/gibtonite/proc/defuse(mob/living/defuser) if(stage == GIBTONITE_ACTIVE) cut_overlay(activated_overlay) activated_overlay.icon_state = "rock_Gibtonite_inactive" @@ -723,8 +725,13 @@ if(det_time < 0) det_time = 0 visible_message(span_notice("The chain reaction stopped! The gibtonite had [det_time] reactions left till the explosion!")) + if(defuser) + SEND_SIGNAL(defuser, COMSIG_LIVING_DEFUSED_GIBTONITE, det_time) /turf/closed/mineral/gibtonite/gets_drilled(mob/user, give_exp = FALSE, triggered_by_explosion = FALSE) + if(istype(user)) + SEND_SIGNAL(user, COMSIG_MOB_MINED, src, give_exp) + if(stage == GIBTONITE_UNSTRUCK && mineralAmt >= 1) //Gibtonite deposit is activated playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,TRUE) explosive_reaction(user) @@ -794,6 +801,9 @@ /turf/closed/mineral/strong/gets_drilled(mob/user, give_exp = FALSE) + if(istype(user)) + SEND_SIGNAL(user, COMSIG_MOB_MINED, src, give_exp) + if(!ishuman(user)) return // see attackby var/mob/living/carbon/human/H = user diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 9a79e33ef4f..beeecfc4169 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -919,7 +919,7 @@ Striking a noncultist, however, will tear their flesh."} for(var/turf/T in get_line(targets_from,temp_target)) if (locate(/obj/effect/blessing, T)) temp_target = T - playsound(T, 'sound/machines/clockcult/ark_damage.ogg', 50, TRUE) + playsound(T, 'sound/effects/parry.ogg', 50, TRUE) new /obj/effect/temp_visual/at_shield(T, T) break T.narsie_act(TRUE, TRUE) diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 6c096bd3ec0..a0658b8ad59 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -101,17 +101,21 @@ if(!QDELETED(C)) C.total_damage += target_health - L.health //we did some damage, but let's not assume how much we did new /obj/effect/temp_visual/kinetic_blast(get_turf(L)) + var/backstabbed = FALSE + var/combined_damage = detonation_damage var/backstab_dir = get_dir(user, L) var/def_check = L.getarmor(type = BOMB) if((user.dir & backstab_dir) && (L.dir & backstab_dir)) - if(!QDELETED(C)) - C.total_damage += detonation_damage + backstab_bonus //cheat a little and add the total before killing it, so certain mobs don't have much lower chances of giving an item - L.apply_damage(detonation_damage + backstab_bonus, BRUTE, blocked = def_check) + backstabbed = TRUE + combined_damage += backstab_bonus playsound(user, 'sound/weapons/kenetic_accel.ogg', 100, TRUE) //Seriously who spelled it wrong - else - if(!QDELETED(C)) - C.total_damage += detonation_damage - L.apply_damage(detonation_damage, BRUTE, blocked = def_check) + + if(!QDELETED(C)) + C.total_damage += combined_damage + + + SEND_SIGNAL(user, COMSIG_LIVING_CRUSHER_DETONATE, L, src, backstabbed) + L.apply_damage(combined_damage, BRUTE, blocked = def_check) /obj/item/kinetic_crusher/attack_secondary(atom/target, mob/living/user, clickparams) return SECONDARY_ATTACK_CONTINUE_CHAIN diff --git a/code/modules/mining/equipment/resonator.dm b/code/modules/mining/equipment/resonator.dm index ead9207f28f..99b27e15ca8 100644 --- a/code/modules/mining/equipment/resonator.dm +++ b/code/modules/mining/equipment/resonator.dm @@ -60,7 +60,7 @@ /// the modifier to resonance_damage; affected by the quick_burst_mod from the resonator var/damage_multiplier = 1 /// the parent creator (user) of this field - var/creator + var/mob/creator /// the parent resonator of this field var/obj/item/resonator/parent_resonator /// whether the field is rupturing currently or not (to prevent recursion) @@ -127,6 +127,7 @@ for(var/mob/living/attacked_living in src_turf) if(creator) log_combat(creator, attacked_living, "used a resonator field on", "resonator") + SEND_SIGNAL(creator, COMSIG_LIVING_RESONATOR_BURST, creator, attacked_living) to_chat(attacked_living, span_userdanger("[src] ruptured with you in it!")) attacked_living.apply_damage(resonance_damage, BRUTE) attacked_living.add_movespeed_modifier(/datum/movespeed_modifier/resonance) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 0cdd844da66..dcf7b915f46 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -203,6 +203,8 @@ /// If true directly targeted turfs can be hit var/can_hit_turfs = FALSE + /// If this projectile has been parried before + var/parried = FALSE /obj/projectile/Initialize(mapload) . = ..() @@ -210,6 +212,7 @@ if(embedding) updateEmbedding() AddElement(/datum/element/connect_loc, projectile_connections) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_enter)) /obj/projectile/proc/Range() range-- @@ -381,6 +384,41 @@ return Impact(A) +/// Signal proc for when a projectile enters a turf. +/obj/projectile/proc/on_enter(datum/source, atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + + UnregisterSignal(old_loc, COMSIG_ATOM_ATTACK_HAND) + + if(isturf(loc)) + RegisterSignal(loc, COMSIG_ATOM_ATTACK_HAND, PROC_REF(attempt_parry)) + +/// Signal proc for when a mob attempts to attack this projectile or the turf it's on with an empty hand. +/obj/projectile/proc/attempt_parry(datum/source, mob/user, list/modifiers) + SIGNAL_HANDLER + + if(parried) + return FALSE + + if(SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRYING, src) & ALLOW_PARRY) + on_parry(user, modifiers) + return TRUE + + return FALSE + + +/// Called when a mob with PARRY_TRAIT clicks on this projectile or the tile its on, reflecting the projectile within 17 degrees and increasing the bullet's stats. +/obj/projectile/proc/on_parry(mob/user, list/modifiers) + if(SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRIED, src) & INTERCEPT_PARRY_EFFECTS) + return + + parried = TRUE + set_angle(dir2angle(user.dir) + rand(-3, 3)) + speed *= 0.8 // Go 20% faster when parried + damage *= 1.15 // And do 15% more damage + add_atom_colour(COLOR_RED_LIGHT, TEMPORARY_COLOUR_PRIORITY) + + /** * Called when the projectile hits something * This can either be from it bumping something, @@ -745,6 +783,7 @@ fired = TRUE play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = Angle) SEND_SIGNAL(src, COMSIG_PROJECTILE_FIRE) + RegisterSignal(src, COMSIG_ATOM_ATTACK_HAND, PROC_REF(attempt_parry)) if(hitscan) process_hitscan() if(!(datum_flags & DF_ISPROCESSING)) diff --git a/icons/hud/style_meter.dmi b/icons/hud/style_meter.dmi new file mode 100644 index 00000000000..5da5f72c3ee Binary files /dev/null and b/icons/hud/style_meter.dmi differ diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi index a9d213d9fd8..25cfd514d9a 100644 Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ diff --git a/sound/machines/clockcult/ark_damage.ogg b/sound/effects/parry.ogg similarity index 100% rename from sound/machines/clockcult/ark_damage.ogg rename to sound/effects/parry.ogg diff --git a/sound/items/style/combo_absolute1.ogg b/sound/items/style/combo_absolute1.ogg new file mode 100644 index 00000000000..d82f318ac6a Binary files /dev/null and b/sound/items/style/combo_absolute1.ogg differ diff --git a/sound/items/style/combo_absolute2.ogg b/sound/items/style/combo_absolute2.ogg new file mode 100644 index 00000000000..804902024b7 Binary files /dev/null and b/sound/items/style/combo_absolute2.ogg differ diff --git a/sound/items/style/combo_absolute3.ogg b/sound/items/style/combo_absolute3.ogg new file mode 100644 index 00000000000..676d1d5864b Binary files /dev/null and b/sound/items/style/combo_absolute3.ogg differ diff --git a/sound/items/style/combo_brutal1.ogg b/sound/items/style/combo_brutal1.ogg new file mode 100644 index 00000000000..ba0b732dd69 Binary files /dev/null and b/sound/items/style/combo_brutal1.ogg differ diff --git a/sound/items/style/combo_brutal2.ogg b/sound/items/style/combo_brutal2.ogg new file mode 100644 index 00000000000..ce64cae15c9 Binary files /dev/null and b/sound/items/style/combo_brutal2.ogg differ diff --git a/sound/items/style/combo_brutal3.ogg b/sound/items/style/combo_brutal3.ogg new file mode 100644 index 00000000000..651e79acb76 Binary files /dev/null and b/sound/items/style/combo_brutal3.ogg differ diff --git a/sound/items/style/combo_cool1.ogg b/sound/items/style/combo_cool1.ogg new file mode 100644 index 00000000000..7afbe5a89f9 Binary files /dev/null and b/sound/items/style/combo_cool1.ogg differ diff --git a/sound/items/style/combo_cool2.ogg b/sound/items/style/combo_cool2.ogg new file mode 100644 index 00000000000..a9f3106fe2d Binary files /dev/null and b/sound/items/style/combo_cool2.ogg differ diff --git a/sound/items/style/combo_cool3.ogg b/sound/items/style/combo_cool3.ogg new file mode 100644 index 00000000000..c304be6dfff Binary files /dev/null and b/sound/items/style/combo_cool3.ogg differ diff --git a/sound/items/style/combo_dull1.ogg b/sound/items/style/combo_dull1.ogg new file mode 100644 index 00000000000..42b05cabc3f Binary files /dev/null and b/sound/items/style/combo_dull1.ogg differ diff --git a/sound/items/style/combo_dull2.ogg b/sound/items/style/combo_dull2.ogg new file mode 100644 index 00000000000..0e72e82d9f6 Binary files /dev/null and b/sound/items/style/combo_dull2.ogg differ diff --git a/sound/items/style/combo_dull3.ogg b/sound/items/style/combo_dull3.ogg new file mode 100644 index 00000000000..f4674d068d6 Binary files /dev/null and b/sound/items/style/combo_dull3.ogg differ diff --git a/sound/items/style/combo_spaced1.ogg b/sound/items/style/combo_spaced1.ogg new file mode 100644 index 00000000000..62624fd6fdd Binary files /dev/null and b/sound/items/style/combo_spaced1.ogg differ diff --git a/sound/items/style/combo_spaced2.ogg b/sound/items/style/combo_spaced2.ogg new file mode 100644 index 00000000000..eccdb97be2c Binary files /dev/null and b/sound/items/style/combo_spaced2.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 4d7aa3df402..5c10d37aa69 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1034,6 +1034,7 @@ #include "code\datums\components\orbiter.dm" #include "code\datums\components\overlay_lighting.dm" #include "code\datums\components\palette.dm" +#include "code\datums\components\parry.dm" #include "code\datums\components\payment.dm" #include "code\datums\components\pellet_cloud.dm" #include "code\datums\components\phylactery.dm" @@ -1151,6 +1152,8 @@ #include "code\datums\components\riding\riding.dm" #include "code\datums\components\riding\riding_mob.dm" #include "code\datums\components\riding\riding_vehicle.dm" +#include "code\datums\components\style\style.dm" +#include "code\datums\components\style\style_meter.dm" #include "code\datums\diseases\_disease.dm" #include "code\datums\diseases\_MobProcs.dm" #include "code\datums\diseases\adrenal_crisis.dm"