[READY] The Portal Update - Linked portals, momentum conservation, and more! (#28055)

* THE PORTAL UPDATE

* portal gun checks

* damn functions

* o_range(s)

* Atmos Portals (#12)

* Rod Form's damage now scales with how much it's upgraded

* buffs chem grenades

* Fix getrev runtime

* Automatic changelog generation for PR #27948 [ci skip]

* Automatic changelog generation for PR #27951 [ci skip]

* Fixes admiral recall (#27861)

* the man with the 2 shotguns that blasted me to the end of the world with dualwielding. nerf (#27978)

* Beam rifles now slowdown while scoped (#27898)

* Update beam_rifle.dm

* Update beam_rifle.dm

* Update beam_rifle.dm

* Fixes a very silly clockwork false wall trick (#27996)

* Fixes #27979 (#28002)

* Automatic changelog generation for PR #28002 [ci skip]

* Fixes #27989 : Riot suits no longer hide jumpsuits (#28003)

* Fixes #27989

* remove tag

* Automatic changelog generation for PR #28003 [ci skip]

* Refactors a cooldown var to not require a spawn or sleep (#28011)

* wat (#28012)

* Remove spawns in favour of stacktrace calls, which don't stop the called (#28013)

proc

* Refactor another spawned cooldown var (#28014)

* Come on, pathetic (#28015)

* Refactor another spawn cooldown (#28016)

* Goodbye spawn (#28017)

* Another spawn timer (#28018)

* Remove a commented out function (#28019)

* Gotta go with the fro2.0 (#28010)

* This doesn't do anything (#28020)

* refactor another spawn cooldown (#28022)

* Refactors another spawn cooldown var (#28023)

* Refactor another spawn (#28024)

* Refactor another spawn var (#28027)

* woops

* woops2

* atmos links!

* unused

* wew

* ffs!

* Forced updates

* update

* Update portals.dm

* adjacent/atmospass checks

* Create portals.dm

* Update portals.dm

* Update other_tools.dm

* stuff

* crossed

* documentation

* reee

* no portal stacking!

* woops
This commit is contained in:
kevinz000
2017-06-20 08:02:01 -07:00
committed by Jordan Brown
parent 8125e875ea
commit d1108df80c
12 changed files with 249 additions and 115 deletions

View File

@@ -1242,9 +1242,15 @@ proc/pick_closest_path(value, list/matches = get_fancy_list_of_atom_types())
#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE)
#define QDEL_NULL(item) qdel(item); item = null
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qel(L[I]); L.Cut(); }
/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly.
QDEL_LIST(L)
/proc/random_nukecode()
var/val = rand(0, 99999)
var/str = "[val]"

View File

@@ -67,8 +67,8 @@
user.forceMove(get_turf(com.locked))
/obj/effect/portal/attack_ghost(mob/user)
if(target)
user.forceMove(get_turf(target))
if(linked)
user.forceMove(get_turf(linked))
/obj/machinery/gateway/centerstation/attack_ghost(mob/user)
if(awaygate)

View File

@@ -15,7 +15,6 @@
var/soundout //soundfile to play after teleportation
var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
/datum/teleport/proc/start(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
if(!initTeleport(arglist(args)))
return 0
@@ -100,21 +99,7 @@
var/turf/destturf
var/turf/curturf = get_turf(teleatom)
if(precision)
var/list/posturfs = list()
var/center = get_turf(destination)
if(!center)
center = destination
for(var/turf/T in range(precision,center))
if(T.is_transition_turf())
continue // Avoid picking these.
var/area/A = T.loc
if(!A.noteleport)
posturfs.Add(T)
destturf = safepick(posturfs)
else
destturf = get_turf(destination)
destturf = get_teleport_turf(get_turf(destination), precision)
if(!destturf || !curturf || destturf.is_transition_turf())
return 0
@@ -124,13 +109,16 @@
return 0
playSpecials(curturf,effectin,soundin)
if(force_teleport)
teleatom.forceMove(destturf)
if(ismegafauna(teleatom))
message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
playSpecials(destturf,effectout,soundout)
else
if(teleatom.Move(destturf))
playSpecials(destturf,effectout,soundout)
if(ismegafauna(teleatom))
message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_COORDJMP(curturf)] to [ADMIN_COORDJMP(destturf)].")
return 1
/datum/teleport/proc/teleport()
@@ -225,3 +213,18 @@
// DING! You have passed the gauntlet, and are "probably" safe.
return F
/proc/get_teleport_turfs(turf/center, precision = 0)
if(!precision)
return list(center)
var/list/posturfs = list()
for(var/turf/T in range(precision,center))
if(T.is_transition_turf())
continue // Avoid picking these.
var/area/A = T.loc
if(!A.noteleport)
posturfs.Add(T)
return posturfs
/proc/get_teleport_turf(turf/center, precision = 0)
return safepick(get_teleport_turfs(center, precision))

View File

@@ -213,7 +213,8 @@
// Previously known as HasEntered()
// This is automatically called when something enters your square
/atom/movable/Crossed(atom/movable/AM)
//oldloc = old location on atom, inserted when forceMove is called and ONLY when forceMove is called!
/atom/movable/Crossed(atom/movable/AM, oldloc)
return
/atom/movable/Bump(atom/A, yes) //the "yes" arg is to differentiate our Bump proc from byond's, without it every Bump() call would become a double Bump().
@@ -249,7 +250,7 @@
for(var/atom/movable/AM in destination)
if(AM == src)
continue
AM.Crossed(src)
AM.Crossed(src, oldloc)
Moved(oldloc, 0)
return 1

View File

@@ -57,18 +57,12 @@
var/turf/target_turf = pick(L)
if(!target_turf)
return
var/obj/effect/portal/P = new /obj/effect/portal(get_turf(target))
P.target = target_turf
P.creator = null
P.icon = 'icons/obj/objects.dmi'
P.icon_state = "anom"
P.name = "wormhole"
P.mech_sized = TRUE
var/list/obj/effect/portal/created = create_portal_pair(get_turf(src), target_turf, src, 300, 1, /obj/effect/portal/anom)
var/turf/T = get_turf(target)
message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] used a Wormhole Generator in [ADMIN_COORDJMP(T)]",0,1)
log_game("[key_name(chassis.occupant)] used a Wormhole Generator in [COORD(T)]")
src = null
QDEL_IN(P, rand(150,300))
QDEL_LIST_IN(created, rand(150,300))
return 1

View File

@@ -1,25 +1,42 @@
/proc/create_portal_pair(turf/source, turf/destination, _creator = null, _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, _creator, _lifespan, null, FALSE)
var/obj/effect/portal/P2 = new newtype(actual_destination, _creator, _lifespan, P1, TRUE)
if(!istype(P1)||!istype(P2))
return
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/stationobjs.dmi'
icon_state = "portal"
density = 1
var/obj/item/target = null
var/creator = null
anchored = 1
var/precision = 1 // how close to the portal you will teleport. 0 = on the portal, 1 = adjacent
anchored = TRUE
var/mech_sized = FALSE
var/obj/effect/portal/linked
var/hardlinked = TRUE //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/creator
var/turf/hard_target //For when a portal needs a hard target and isn't to be linked.
var/atmos_link = FALSE //Link source/destination atmos.
var/turf/open/atmos_source //Atmos link source
var/turf/open/atmos_destination //Atmos link destination
/obj/effect/portal/Bumped(mob/M as mob|obj)
teleport(M)
/obj/effect/portal/anom
name = "wormhole"
icon = 'icons/obj/objects.dmi'
icon_state = "anom"
mech_sized = TRUE
/obj/effect/portal/attack_tk(mob/user)
return
/obj/effect/portal/attack_hand(mob/user)
if(Adjacent(user))
teleport(user)
/obj/effect/portal/Move(newloc)
for(var/T in newloc)
if(istype(T, /obj/effect/portal))
return FALSE
return ..()
/obj/effect/portal/attackby(obj/item/weapon/W, mob/user, params)
if(user && Adjacent(user))
@@ -28,42 +45,113 @@
/obj/effect/portal/make_frozen_visual()
return
/obj/effect/portal/New(loc, turf/target, creator=null, lifespan=300)
..()
GLOB.portals += src
src.target = target
src.creator = creator
var/area/A = get_area(target)
if(A && A.noteleport) // No point in persisting if the target is unreachable.
qdel(src)
return
if(lifespan > 0)
QDEL_IN(src, lifespan)
/obj/effect/portal/Destroy()
GLOB.portals -= src
if(istype(creator, /obj/item/weapon/hand_tele))
var/obj/item/weapon/hand_tele/O = creator
O.active_portals--
else if(istype(creator, /obj/item/weapon/gun/energy/wormhole_projector))
var/obj/item/weapon/gun/energy/wormhole_projector/P = creator
P.portal_destroyed(src)
creator = null
/obj/effect/portal/Crossed(atom/movable/AM, oldloc)
if(get_turf(oldloc) == get_turf(linked))
return ..()
if(!teleport(AM))
return ..()
/obj/effect/portal/proc/teleport(atom/movable/M as mob|obj)
if(istype(M, /obj/effect)) //sparks don't teleport
/obj/effect/portal/attack_tk(mob/user)
return
if(M.anchored)
if(!(istype(M, /obj/mecha) && mech_sized))
/obj/effect/portal/attack_hand(mob/user)
if(Adjacent(user))
teleport(user)
/obj/effect/portal/Initialize(mapload, _creator, _lifespan = 300, obj/effect/portal/_linked, automatic_link = TRUE, hard_target_override, atmos_link_override)
. = ..()
GLOB.portals += src
if(!istype(_linked) && automatic_link)
. = INITIALIZE_HINT_QDEL
CRASH("Somebody fucked up.")
if(_lifespan > 0)
QDEL_IN(src, _lifespan)
if(!isnull(atmos_link_override))
atmos_link = atmos_link_override
link_portal(_linked)
hardlinked = automatic_link
creator = _creator
/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink)
linked = newlink
if(atmos_link)
link_atmos()
/obj/effect/portal/proc/link_atmos()
if(atmos_source || atmos_destination)
unlink_atmos()
if(!isopenturf(get_turf(src)))
return FALSE
if(linked)
if(isopenturf(get_turf(linked)))
atmos_source = get_turf(src)
atmos_destination = get_turf(linked)
else if(hard_target)
if(isopenturf(hard_target))
atmos_source = get_turf(src)
atmos_destination = hard_target
else
return FALSE
if(!istype(atmos_source) || !istype(atmos_destination))
return FALSE
LAZYINITLIST(atmos_source.atmos_adjacent_turfs)
LAZYINITLIST(atmos_destination.atmos_adjacent_turfs)
if(atmos_source.atmos_adjacent_turfs[atmos_destination] || atmos_destination.atmos_adjacent_turfs[atmos_source]) //Already linked!
return FALSE
atmos_source.atmos_adjacent_turfs[atmos_destination] = TRUE
atmos_destination.atmos_adjacent_turfs[atmos_source] = TRUE
atmos_source.air_update_turf(FALSE)
atmos_destination.air_update_turf(FALSE)
/obj/effect/portal/proc/unlink_atmos()
if(istype(atmos_source))
if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source))
LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination)
atmos_source = null
if(istype(atmos_destination))
if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination))
LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source)
atmos_destination = null
/obj/effect/portal/Destroy() //Calls on_portal_destroy(destroyed portal, location of destroyed portal) on creator if creator has such call.
if(creator && hascall(creator, "on_portal_destroy"))
call(creator, "on_portal_destroy")(src, src.loc)
creator = null
GLOB.portals -= src
unlink_atmos()
if(hardlinked && !QDELETED(linked))
QDEL_NULL(linked)
else
linked = null
return ..()
/obj/effect/portal/proc/teleport(atom/movable/M)
if(!istype(M) || istype(M, /obj/effect) || (istype(M, /obj/mecha) && !mech_sized) || (!isobj(M) && !ismob(M))) //Things that shouldn't teleport.
return
if (!( target ))
qdel(src)
return
if (istype(M, /atom/movable))
var/turf/real_target = get_link_target_turf()
if(!istype(real_target))
return FALSE
if(ismegafauna(M))
message_admins("[M] [ADMIN_FLW(M)] has teleported through [src].")
do_teleport(M, target, precision) ///You will appear adjacent to the beacon
message_admins("[M] has used a portal at [ADMIN_COORDJMP(src)] made by [usr].")
if(do_teleport(M, real_target, 0))
if(istype(M, /obj/item/projectile))
var/obj/item/projectile/P = M
P.ignore_source_check = TRUE
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

View File

@@ -1,3 +1,7 @@
#define SOURCE_PORTAL 1
#define DESTINATION_PORTAL 2
/* Teleportation devices.
* Contains:
* Locator
@@ -136,7 +140,19 @@ Frequency:
origin_tech = "magnets=3;bluespace=4"
armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 30, bio = 0, rad = 0, fire = 100, acid = 100)
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/active_portals = 0
var/list/active_portal_pairs
var/max_portal_pairs = 3
/obj/item/weapon/hand_tele/Initialize()
. = ..()
active_portal_pairs = list()
/obj/item/weapon/hand_tele/afterattack(atom/target, mob/user, proximity, params)
if(is_parent_of_portal(target))
qdel(target)
to_chat(user, "<span class='notice'>You dispel [target] remotely with \the [src]!</span>")
return ..()
/obj/item/weapon/hand_tele/attack_self(mob/user)
var/turf/current_location = get_turf(user)//What turf is the user on?
@@ -169,7 +185,7 @@ Frequency:
var/t1 = input(user, "Please select a teleporter to lock in on.", "Hand Teleporter") as null|anything in L
if (!t1 || user.get_active_held_item() != src || user.incapacitated())
return
if(active_portals >= 3)
if(active_portal_pairs.len >= max_portal_pairs)
user.show_message("<span class='notice'>\The [src] is recharging!</span>")
return
var/atom/T = L[t1]
@@ -178,7 +194,22 @@ Frequency:
to_chat(user, "<span class='notice'>\The [src] is malfunctioning.</span>")
return
user.show_message("<span class='notice'>Locked In.</span>", 2)
var/obj/effect/portal/P = new /obj/effect/portal(get_turf(src), T, src)
try_move_adjacent(P)
active_portals++
var/list/obj/effect/portal/created = create_portal_pair(current_location, get_teleport_turf(get_turf(T)), src, 300, 1)
if(!(LAZYLEN(created) == 2))
return
try_move_adjacent(created[1])
active_portal_pairs[created[1]] = created[2]
add_fingerprint(user)
/obj/item/weapon/hand_tele/proc/on_portal_destroy(obj/effect/portal/P)
active_portal_pairs -= P //If this portal pair is made by us it'll be erased along with the other portal by the portal.
/obj/item/weapon/hand_tele/proc/is_parent_of_portal(obj/effect/portal/P)
if(!istype(P))
return FALSE
if(active_portal_pairs[P])
return SOURCE_PORTAL
for(var/i in active_portal_pairs)
if(active_portal_pairs[i] == P)
return DESTINATION_PORTAL
return FALSE

View File

@@ -26,7 +26,7 @@
for(var/i = 1, i <= number_of_wormholes, i++)
var/turf/T = pick(pick_turfs)
wormholes += new /obj/effect/portal/wormhole(T, null, null, -1)
wormholes += new /obj/effect/portal/wormhole(T, null, 300, null, FALSE)
/datum/round_event/wormholes/announce()
priority_announce("Space-time anomalies detected on the station. There is no additional data.", "Anomaly Alert", 'sound/ai/spanomalies.ogg')
@@ -36,14 +36,10 @@
for(var/obj/effect/portal/wormhole/O in wormholes)
var/turf/T = pick(pick_turfs)
if(T)
O.loc = T
O.forceMove(T)
/datum/round_event/wormholes/end()
GLOB.portals.Remove(wormholes)
for(var/obj/effect/portal/wormhole/O in wormholes)
O.loc = null
wormholes.Cut()
QDEL_LIST(wormholes)
/obj/effect/portal/wormhole
name = "wormhole"
@@ -70,7 +66,7 @@
if(GLOB.portals.len)
var/obj/effect/portal/P = pick(GLOB.portals)
if(P && isturf(P.loc))
target = P.loc
hard_target = P.loc
if(!target)
return
do_teleport(M, target, 1, 1, 0, 0) ///You will appear adjacent to the beacon
do_teleport(M, hard_target, 1, 1, 0, 0) ///You will appear adjacent to the beacon

View File

@@ -53,8 +53,7 @@
to_chat(user, "<span class='notice'>The [src.name] found no beacons in the world to anchor a wormhole to.</span>")
return
var/chosen_beacon = pick(L)
var/obj/effect/portal/wormhole/jaunt_tunnel/J = new /obj/effect/portal/wormhole/jaunt_tunnel(get_turf(src), chosen_beacon, lifespan=100)
J.target = chosen_beacon
var/obj/effect/portal/wormhole/jaunt_tunnel/J = new /obj/effect/portal/wormhole/jaunt_tunnel(get_turf(src), src, lifespan=100, null, FALSE, get_turf(chosen_beacon))
try_move_adjacent(J)
playsound(src,'sound/effects/sparks4.ogg',50,1)
qdel(src)
@@ -98,7 +97,7 @@
return
if(istype(M, /atom/movable))
if(do_teleport(M, target, 6))
if(do_teleport(M, hard_target, 6))
// KERPLUNK
playsound(M,'sound/weapons/resonator_blast.ogg',50,1)
if(iscarbon(M))

View File

@@ -172,8 +172,8 @@
item_state = null
icon_state = "wormhole_projector"
origin_tech = "combat=4;bluespace=6;plasmatech=4;engineering=4"
var/obj/effect/portal/blue
var/obj/effect/portal/orange
var/obj/effect/portal/p_blue
var/obj/effect/portal/p_orange
/obj/item/weapon/gun/energy/wormhole_projector/update_icon()
icon_state = "[initial(icon_state)][select]"
@@ -184,30 +184,44 @@
..()
select_fire()
/obj/item/weapon/gun/energy/wormhole_projector/proc/portal_destroyed(obj/effect/portal/P)
if(P.icon_state == "portal")
blue = null
if(orange)
orange.target = null
else
orange = null
if(blue)
blue.target = null
/obj/item/weapon/gun/energy/wormhole_projector/proc/on_portal_destroy(obj/effect/portal/P)
if(P == p_blue)
p_blue = null
else if(P == p_orange)
p_orange = null
/obj/item/weapon/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W)
var/obj/effect/portal/P = new /obj/effect/portal(get_turf(W), null, src)
P.precision = 0
if(W.name == "bluespace beam")
qdel(blue)
blue = P
else
qdel(orange)
/obj/item/weapon/gun/energy/wormhole_projector/proc/has_blue_portal()
if(istype(p_blue) && !QDELETED(p_blue))
return TRUE
return FALSE
/obj/item/weapon/gun/energy/wormhole_projector/proc/has_orange_portal()
if(istype(p_orange) && !QDELETED(p_orange))
return TRUE
return FALSE
/obj/item/weapon/gun/energy/wormhole_projector/proc/crosslink()
if(!has_blue_portal() && !has_orange_portal())
return
if(!has_blue_portal() && has_orange_portal())
p_orange.link_portal(null)
return
if(!has_orange_portal() && has_blue_portal())
p_blue.link_portal(null)
return
p_orange.link_portal(p_blue)
p_blue.link_portal(p_orange)
/obj/item/weapon/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W, turf/target)
var/obj/effect/portal/P = new /obj/effect/portal(target, src, 300, null, FALSE, null)
if(istype(W, /obj/item/projectile/beam/wormhole/orange))
qdel(p_orange)
p_orange = P
P.icon_state = "portal1"
orange = P
if(orange && blue)
blue.target = get_turf(orange)
orange.target = get_turf(blue)
else
qdel(p_blue)
p_blue = P
crosslink()
/* 3d printer 'pseudo guns' for borgs */

View File

@@ -31,6 +31,7 @@
var/ricochets = 0
var/ricochets_max = 2
var/ricochet_chance = 30
var/ignore_source_check = FALSE
var/damage = 10
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
@@ -138,8 +139,9 @@
if(check_ricochet() && check_ricochet_flag(A) && ricochets < ricochets_max)
ricochets++
if(A.handle_ricochet(src))
ignore_source_check = TRUE
return FALSE
if(firer && !ricochets)
if(firer && !ignore_source_check)
if(A == firer || (A == firer.loc && istype(A, /obj/mecha))) //cannot shoot yourself or your mech
loc = A.loc
return FALSE

View File

@@ -183,7 +183,7 @@
return ..()
if(!gun)
qdel(src)
gun.create_portal(src)
gun.create_portal(src, get_turf(src))
/obj/item/projectile/bullet/frag12
name ="explosive slug"