Files
Bubberstation/code/game/objects/effects/portals.dm
MrMelbert 5261efb67f Re-refactors batons / Refactors attack chain force modifiers (#90809)
## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
2025-05-19 13:32:12 +10:00

246 lines
8.0 KiB
Plaintext

/proc/create_portal_pair(turf/source, turf/destination, _lifespan = 300, accuracy = 0, newtype = /obj/effect/portal)
if(!istype(source) || !istype(destination))
return
var/turf/actual_destination = get_teleport_turf(destination, accuracy)
var/obj/effect/portal/P1 = new newtype(source, _lifespan, null, FALSE, null)
var/obj/effect/portal/P2 = new newtype(actual_destination, _lifespan, P1, TRUE, null)
if(!istype(P1) || !istype(P2))
return
playsound(P1, SFX_PORTAL_CREATED, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(P2, SFX_PORTAL_CREATED, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
P1.link_portal(P2)
P1.hardlinked = TRUE
return list(P1, P2)
/obj/effect/portal
name = "portal"
desc = "Looks unstable. Best to test it with the clown."
icon = 'icons/obj/anomaly.dmi'
icon_state = "portal"
anchored = TRUE
density = TRUE // dense for receiving bumbs
layer = HIGH_OBJ_LAYER
light_system = COMPLEX_LIGHT
light_range = 3
light_power = 1
light_on = TRUE
light_color = COLOR_BLUE_LIGHT
/// Are mechs able to enter this portal?
var/mech_sized = FALSE
/// A reference to another "linked" destination portal
var/obj/effect/portal/linked
/// Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted.
var/hardlinked = TRUE
/// What teleport channel does this portal use?
var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE
/// For when a portal needs a hard target and isn't to be linked.
var/turf/hard_target
/// Do we teleport anchored objects?
var/allow_anchored = FALSE
/// What precision value do we pass to do_teleport (how far from the target destination we will pop out at).
var/innate_accuracy_penalty = 0
/// Used to track how often sparks should be output. Might want to turn this into a cooldown.
var/last_effect = 0
/// Does this portal bypass teleport restrictions? like TRAIT_NO_TELEPORT and NOTELEPORT flags.
var/force_teleport = FALSE
/// Does this portal create spark effect when teleporting?
var/sparkless = TRUE
/// If FALSE, the wibble filter will not be applied to this portal (only a visual effect).
var/wibbles = TRUE
/obj/effect/portal/anom
name = "wormhole"
icon = 'icons/obj/anomaly.dmi'
icon_state = "anom"
layer = RIPPLE_LAYER
plane = ABOVE_GAME_PLANE
mech_sized = TRUE
teleport_channel = TELEPORT_CHANNEL_WORMHOLE
light_on = FALSE
wibbles = FALSE
/obj/effect/portal/Move(newloc)
for(var/T in newloc)
if(istype(T, /obj/effect/portal))
return FALSE
return ..()
// Prevents portals spawned by jaunter/handtele from floating into space when relocated to an adjacent tile.
/obj/effect/portal/newtonian_move(inertia_angle, instant = FALSE, start_delay = 0, drift_force = 0, controlled_cap = null)
return TRUE
/obj/effect/portal/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers)
if(user && Adjacent(user))
teleport(user)
return TRUE
/obj/effect/portal/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(HAS_TRAIT(mover, TRAIT_NO_TELEPORT) && !force_teleport)
return TRUE
/obj/effect/portal/Bumped(atom/movable/bumper)
teleport(bumper)
/obj/effect/portal/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if(Adjacent(user))
teleport(user)
/obj/effect/portal/attack_robot(mob/living/user)
if(Adjacent(user))
teleport(user)
/obj/effect/portal/Initialize(mapload, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override)
. = ..()
GLOB.portals += src
if(!istype(_linked) && automatic_link)
. = INITIALIZE_HINT_QDEL
CRASH("Somebody fucked up.")
if(_lifespan > 0)
addtimer(CALLBACK(src, PROC_REF(expire)), _lifespan, TIMER_DELETE_ME)
link_portal(_linked)
hardlinked = automatic_link
if(isturf(hard_target_override))
hard_target = hard_target_override
if(wibbles)
apply_wibbly_filters(src)
/obj/effect/portal/proc/expire()
playsound(loc, SFX_PORTAL_CLOSE, 50, FALSE, SHORT_RANGE_SOUND_EXTRARANGE)
qdel(src)
/obj/effect/portal/singularity_pull(atom/singularity, current_size)
return
/obj/effect/portal/singularity_act()
return
/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink)
linked = newlink
/obj/effect/portal/Destroy()
GLOB.portals -= src
if(hardlinked && !QDELETED(linked))
QDEL_NULL(linked)
else
linked = null
return ..()
/obj/effect/portal/attack_ghost(mob/dead/observer/ghost)
if(!teleport(ghost, force = TRUE))
return ..()
return BULLET_ACT_FORCE_PIERCE
/obj/effect/portal/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
if (!teleport(hitting_projectile, force = TRUE))
return ..()
return BULLET_ACT_FORCE_PIERCE
/obj/effect/portal/proc/teleport(atom/movable/moving, force = FALSE)
if(!force && (!istype(moving) || iseffect(moving) || (ismecha(moving) && !mech_sized) || (!isobj(moving) && !ismob(moving)))) //Things that shouldn't teleport.
return
var/turf/real_target = get_link_target_turf()
if(!istype(real_target))
return FALSE
if(!force && (!ismecha(moving) && !isprojectile(moving) && moving.anchored && !allow_anchored))
return
var/no_effect = FALSE
if(last_effect == world.time || sparkless)
no_effect = TRUE
else
last_effect = world.time
var/turf/start_turf = get_turf(moving)
if(do_teleport(moving, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport))
if(isprojectile(moving))
var/obj/projectile/proj = moving
proj.ignore_source_check = TRUE
new /obj/effect/temp_visual/portal_animation(start_turf, src, moving)
playsound(start_turf, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
playsound(real_target, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
return TRUE
return FALSE
/obj/effect/portal/proc/get_link_target_turf()
var/turf/real_target
if(!istype(linked) || QDELETED(linked))
if(hardlinked)
qdel(src)
if(!istype(hard_target) || QDELETED(hard_target))
hard_target = null
return
else
real_target = hard_target
linked = null
else
real_target = get_turf(linked)
return real_target
/obj/effect/portal/permanent
name = "permanent portal"
desc = "An unwavering portal that will never fade."
hardlinked = FALSE // dont qdel my portal nerd
force_teleport = TRUE // force teleports because they're a mapmaker tool
var/id // var edit or set id in map editor
/obj/effect/portal/permanent/proc/set_linked()
if(!id)
return
for(var/obj/effect/portal/permanent/P in GLOB.portals - src)
if(P.id == id)
P.linked = src
linked = P
break
/obj/effect/portal/permanent/teleport(atom/movable/moving, force = FALSE)
set_linked() // update portal links
. = ..()
/obj/effect/portal/permanent/one_way // doesn't have a return portal, can have multiple exits, /obj/effect/landmark/portal_exit to mark them
name = "one-way portal"
desc = "You get the feeling that this might not be the safest thing you've ever done."
/obj/effect/portal/permanent/one_way/set_linked()
if(!id)
return
var/list/possible_turfs = list()
for(var/obj/effect/landmark/portal_exit/PE in GLOB.landmarks_list)
if(PE.id == id)
var/turf/T = get_turf(PE)
if(T)
possible_turfs |= T
if(possible_turfs.len)
hard_target = pick(possible_turfs)
/obj/effect/portal/permanent/one_way/one_use
name = "one-use portal"
desc = "This is probably the worst decision you'll ever make in your life."
/obj/effect/portal/permanent/one_way/one_use/teleport(atom/movable/moving, force = FALSE)
. = ..()
if (. && !isdead(moving))
expire()
/**
* Animation used for transitioning atoms which are teleporting somewhere via a portal
*
* To use, pass it the atom doing the teleporting and the atom that is being teleported in init.
*/
/obj/effect/temp_visual/portal_animation
duration = 0.25 SECONDS
/obj/effect/temp_visual/portal_animation/Initialize(mapload, atom/portal, atom/movable/teleporting)
. = ..()
if(isnull(portal) || isnull(teleporting))
return
appearance = teleporting.appearance
dir = teleporting.dir
layer = portal.layer + 0.01
alpha = teleporting.alpha
animate(src, pixel_x = (portal.x * 32) - (x * 32), pixel_y = (portal.y * 32) - (y * 32), alpha = 0, time = duration)