diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm index 4adc41832a2..41bd7c89115 100644 --- a/code/__HELPERS/matrices.dm +++ b/code/__HELPERS/matrices.dm @@ -16,11 +16,14 @@ /// If other operations were applied on the matrix, such as shearing, the result /// will not be precise. /// -/// Negative scales are not supported. +/// Negative scales are now supported. =) /matrix/proc/decompose() var/datum/decompose_matrix/decompose_matrix = new . = decompose_matrix - decompose_matrix.scale_x = sqrt(a * a + d * d) + var/flip_sign = (a*e - b*d < 0)? -1 : 1 // Det < 0 => only 1 axis is flipped - start doing some sign flipping + // If both axis are flipped, nothing bad happens and Det >= 0, it just treats it like a 180° rotation + // If only 1 axis is flipped, we need to flip one direction - in this case X, so we flip a, b and the x scaling + decompose_matrix.scale_x = sqrt(a * a + d * d) * flip_sign decompose_matrix.scale_y = sqrt(b * b + e * e) decompose_matrix.shift_x = c decompose_matrix.shift_y = f @@ -28,8 +31,8 @@ return // If only translated, scaled and rotated, a/xs == e/ys and -d/xs == b/xy var/cossine = (a/decompose_matrix.scale_x + e/decompose_matrix.scale_y) / 2 - var/sine = (b/decompose_matrix.scale_y - d/decompose_matrix.scale_x) / 2 - decompose_matrix.rotation = arctan(cossine, sine) + var/sine = (b/decompose_matrix.scale_y - d/decompose_matrix.scale_x) / 2 * flip_sign + decompose_matrix.rotation = arctan(cossine, sine) * flip_sign /matrix/proc/TurnTo(old_angle, new_angle) . = new_angle - old_angle diff --git a/code/_globalvars/traumas.dm b/code/_globalvars/traumas.dm index 9b341a64376..9da6ec89102 100644 --- a/code/_globalvars/traumas.dm +++ b/code/_globalvars/traumas.dm @@ -215,7 +215,7 @@ GLOBAL_LIST_INIT(phobia_objs, list( "anime" = typecacheof(list(/obj/item/clothing/under/costume/schoolgirl, /obj/item/katana, /obj/item/food/sashimi, /obj/item/food/chawanmushi, /obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja, - /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/vibro_weapon, + /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/highfrequencyblade, /obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe )), @@ -236,10 +236,10 @@ GLOBAL_LIST_INIT(phobia_objs, list( "anime" = typecacheof(list(/obj/item/clothing/under/costume/schoolgirl, /obj/item/katana, /obj/item/food/sashimi, /obj/item/food/chawanmushi, /obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja, - /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/vibro_weapon, + /obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/highfrequencyblade, /obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe )), - + "blood" = typecacheof(list(/obj/machinery/iv_drip, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/blood, /obj/effect/decal/cleanable/blood)), )) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index aa75617ebcd..d1397ef3201 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -785,67 +785,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' -//HF blade -/obj/item/vibro_weapon - icon_state = "hfrequency0" - base_icon_state = "hfrequency" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - name = "vibro sword" - desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire." - armour_penetration = 100 - block_chance = 40 - force = 20 - throwforce = 20 - throw_speed = 4 - sharpness = SHARP_EDGED - attack_verb_continuous = list("cuts", "slices", "dices") - attack_verb_simple = list("cut", "slice", "dice") - w_class = WEIGHT_CLASS_BULKY - slot_flags = ITEM_SLOT_BACK - hitsound = 'sound/weapons/bladeslice.ogg' - var/wielded = FALSE // track wielded status on item - -/obj/item/vibro_weapon/Initialize(mapload) - . = ..() - RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) - RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) - -/obj/item/vibro_weapon/ComponentInitialize() - . = ..() - AddComponent(/datum/component/butchering, 20, 105) - AddComponent(/datum/component/two_handed, force_multiplier=2, icon_wielded="[base_icon_state]1") - -/// triggered on wield of two handed item -/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user) - SIGNAL_HANDLER - - wielded = TRUE - -/// triggered on unwield of two handed item -/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user) - SIGNAL_HANDLER - - wielded = FALSE - -/obj/item/vibro_weapon/update_icon_state() - icon_state = "[base_icon_state]0" - return ..() - -/obj/item/vibro_weapon/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(wielded) - final_block_chance *= 2 - if(wielded || attack_type != PROJECTILE_ATTACK) - if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) - owner.visible_message(span_danger("[owner] deflects [attack_text] with [src]!")) - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) - return TRUE - else - owner.visible_message(span_danger("[owner] parries [attack_text] with [src]!")) - return TRUE - return FALSE - /obj/item/melee/moonlight_greatsword name = "moonlight greatsword" desc = "Don't tell anyone you put any points into dex, though." @@ -863,3 +802,158 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 hitsound = 'sound/weapons/bladeslice.ogg' 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") + +//High Frequency Blade + +/obj/item/highfrequencyblade + name = "high frequency blade" + desc = "A sword reinforced by a powerful alternating current and resonating at extremely high vibration frequencies. \ + This oscillation weakens the molecular bonds of anything it cuts, thereby increasing its cutting ability." + icon_state = "hfrequency0" + worn_icon_state = "hfrequency0" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + force = 10 + wound_bonus = 25 + bare_wound_bonus = 50 + throwforce = 25 + throw_speed = 4 + embedding = list("embed_chance" = 100) + block_chance = 25 + sharpness = SHARP_EDGED + w_class = WEIGHT_CLASS_BULKY + slot_flags = ITEM_SLOT_BACK + /// Wielding status. + var/wielded = FALSE + /// Previous x position of where we clicked on the target's icon + var/previous_x + /// Previous y position of where we clicked on the target's icon + var/previous_y + /// The previous target we attacked + var/datum/weakref/previous_target + +/obj/item/highfrequencyblade/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/highfrequencyblade/ComponentInitialize() + . = ..() + AddComponent(/datum/component/two_handed) + +/obj/item/highfrequencyblade/update_icon_state() + icon_state = "hfrequency[wielded]" + return ..() + +/obj/item/highfrequencyblade/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 == PROJECTILE_ATTACK) + if(wielded || prob(final_block_chance)) + owner.visible_message(span_danger("[owner] deflects [attack_text] with [src]!")) + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE) + return TRUE + return FALSE + if(prob(final_block_chance * (wielded ? 2 : 1))) + owner.visible_message(span_danger("[owner] parries [attack_text] with [src]!")) + return TRUE + +/obj/item/highfrequencyblade/attack(mob/living/target, mob/living/user, params) + if(!wielded) + return ..() + slash(target, user, params) + +/obj/item/highfrequencyblade/attack_atom(atom/target, mob/living/user, params) + if(wielded) + return + return ..() + +/obj/item/highfrequencyblade/afterattack(atom/target, mob/user, proximity_flag, params) + if(!wielded) + return ..() + if(!proximity_flag || !(isclosedturf(target) || isitem(target) || ismachinery(target) || isstructure(target))) + return + slash(target, user, params) + +/// triggered on wield of two handed item +/obj/item/highfrequencyblade/proc/on_wield(obj/item/source, mob/user) + SIGNAL_HANDLER + + wielded = TRUE + update_icon(UPDATE_ICON_STATE) + +/// triggered on unwield of two handed item +/obj/item/highfrequencyblade/proc/on_unwield(obj/item/source, mob/user) + SIGNAL_HANDLER + + wielded = FALSE + update_icon(UPDATE_ICON_STATE) + +/obj/item/highfrequencyblade/proc/slash(atom/target, mob/living/user, params) + user.changeNext_move(0.1 SECONDS) + user.do_attack_animation(target, "nothing") + var/list/modifiers = params2list(params) + var/damage_mod = 1 + var/x_slashed = text2num(modifiers[ICON_X]) || world.icon_size/2 //in case we arent called by a client + var/y_slashed = text2num(modifiers[ICON_Y]) || world.icon_size/2 //in case we arent called by a client + new /obj/effect/temp_visual/slash(get_turf(target), target, x_slashed, y_slashed) + if(target == previous_target?.resolve()) //if the same target, we calculate a damage multiplier if you swing your mouse around + var/x_mod = previous_x - x_slashed + var/y_mod = previous_y - y_slashed + damage_mod = max(1, round((sqrt(x_mod ** 2 + y_mod ** 2) / 10), 0.1)) + previous_target = WEAKREF(target) + previous_x = x_slashed + previous_y = y_slashed + playsound(src, 'sound/weapons/bladeslice.ogg', 100, vary = TRUE) + playsound(src, 'sound/weapons/zapbang.ogg', 50, vary = TRUE) + if(isliving(target)) + var/mob/living/living_target = target + living_target.apply_damage(force*damage_mod, BRUTE, sharpness = SHARP_EDGED, wound_bonus = wound_bonus, bare_wound_bonus = bare_wound_bonus, def_zone = user.zone_selected) + log_combat(user, living_target, "slashed", src) + if(living_target.stat == DEAD && prob(force*damage_mod*0.5)) + living_target.visible_message(span_danger("[living_target] explodes in a shower of gore!"), blind_message = span_hear("You hear organic matter ripping and tearing!")) + living_target.gib() + log_combat(user, living_target, "gibbed", src) + else if(target.uses_integrity) + target.take_damage(force*damage_mod*3, BRUTE, MELEE, FALSE, null, 50) + else if(iswallturf(target) && prob(force*damage_mod*0.5)) + var/turf/closed/wall/wall_target = target + wall_target.dismantle_wall() + else if(ismineralturf(target) && prob(force*damage_mod)) + var/turf/closed/mineral/mineral_target = target + mineral_target.gets_drilled() + +/obj/effect/temp_visual/slash + icon_state = "highfreq_slash" + alpha = 150 + duration = 0.5 SECONDS + plane = ABOVE_GAME_PLANE + +/obj/effect/temp_visual/slash/Initialize(mapload, atom/target, x_slashed, y_slashed) + . = ..() + if(!target) + return + var/matrix/new_transform = matrix() + new_transform.Turn(rand(1, 360)) // Random slash angle + var/datum/decompose_matrix/decomp = target.transform.decompose() + new_transform.Translate((x_slashed - world.icon_size/2) * decomp.scale_x, (y_slashed - world.icon_size/2) * decomp.scale_y) // Move to where we clicked + //Follow target's transform while ignoring scaling + new_transform.Turn(decomp.rotation) + new_transform.Translate(decomp.shift_x, decomp.shift_y) + new_transform.Translate(target.pixel_x, target.pixel_y) // Follow target's pixel offsets + transform = new_transform + //Double the scale of the matrix by doubling the 2x2 part without touching the translation part + var/matrix/scaled_transform = new_transform + matrix(new_transform.a, new_transform.b, 0, new_transform.d, new_transform.e, 0) + animate(src, duration*0.5, color = COLOR_BLUE, transform = scaled_transform, alpha = 255) + +/obj/item/highfrequencyblade/wizard + desc = "A blade that was mastercrafted by a legendary blacksmith. Its' enchantments let it slash through anything." + force = 8 + throwforce = 20 + wound_bonus = 20 + bare_wound_bonus = 25 + +/obj/item/highfrequencyblade/wizard/attack_self(mob/user, modifiers) + if(!IS_WIZARD(user)) + balloon_alert(user, "you're too weak!") + return + return ..() diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm index f204bb91378..96aada05a6d 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -509,6 +509,12 @@ category = "Mobility" cost = 1 +/datum/spellbook_entry/item/highfrequencyblade + name = "High Frequency Blade" + desc = "An incredibly swift enchanted blade resonating at a frequency high enough to be able to slice through anything." + item_path = /obj/item/highfrequencyblade/wizard + cost = 3 + /datum/spellbook_entry/duffelbag name = "Bestow Cursed Duffel Bag" desc = "A curse that firmly attaches a demonic duffel bag to the target's back. The duffel bag will make the person it's attached to take periodical damage if it is not fed regularly, and regardless of whether or not it's been fed, it will slow the person wearing it down significantly." diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index d1dd9720a40..db919138bb4 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -65,7 +65,7 @@ permeability_coefficient = 0.01 body_parts_covered = CHEST|GROIN|ARMS|LEGS armor = list(MELEE = 30, BULLET = 20, LASER = 20, ENERGY = 30, BOMB = 20, BIO = 20, FIRE = 100, ACID = 100, WOUND = 20) - allowed = list(/obj/item/teleportation_scroll) + allowed = list(/obj/item/teleportation_scroll, /obj/item/highfrequencyblade/wizard) flags_inv = HIDEJUMPSUIT strip_delay = 50 equip_delay_other = 50 diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm index d26b95e9c6d..6b48e29da52 100644 --- a/code/modules/photography/camera/camera_image_capturing.dm +++ b/code/modules/photography/camera/camera_image_capturing.dm @@ -91,8 +91,11 @@ if(decompose.scale_x != 1 || decompose.scale_y != 1) var/base_w = img.Width() var/base_h = img.Height() - img.Scale(base_w * decompose.scale_x, base_h * decompose.scale_y) - xo -= base_w * (decompose.scale_x - 1) / 2 + // scale_x can be negative + img.Scale(base_w * abs(decompose.scale_x), base_h * decompose.scale_y) + if(decompose.scale_x < 0) + img.Flip(EAST) + xo -= base_w * (decompose.scale_x - SIGN(decompose.scale_x)) / 2 * SIGN(decompose.scale_x) yo -= base_h * (decompose.scale_y - 1) / 2 // Rotation if(decompose.rotation != 0) diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 6a7badc23b5..251c02da7fd 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi index ffe0c492e27..9c7e201741d 100644 Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ diff --git a/icons/mob/clothing/belt_mirror.dmi b/icons/mob/clothing/belt_mirror.dmi index 36b84a26050..6856d0db493 100644 Binary files a/icons/mob/clothing/belt_mirror.dmi and b/icons/mob/clothing/belt_mirror.dmi differ diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi index 30186200697..37c10ae688c 100644 Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi index 625ad4dd4cd..677cca9792f 100644 Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi index 55ed855d608..bf8eaac8af5 100644 Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ