[MIRROR] Singularity component (#2082)

* Singularity component (#55096)

Adds singularity component

* Singularity component

Co-authored-by: Jared-Fogle <35135081+Jared-Fogle@users.noreply.github.com>
This commit is contained in:
SkyratBot
2020-12-08 11:30:39 +00:00
committed by GitHub
co-authored by Jared-Fogle
parent bd29550b77
commit f105b47a55
30 changed files with 749 additions and 514 deletions
+9 -1
View File
@@ -102,6 +102,8 @@
#define COMSIG_ATOM_USED_IN_CRAFT "atom_used_in_craft"
///from base of atom/blob_act(): (/obj/structure/blob)
#define COMSIG_ATOM_BLOB_ACT "atom_blob_act"
/// if returned, forces nothing to happen when the atom is attacked by a blob
#define COMPONENT_CANCEL_BLOB_ACT (1<<0)
///from base of atom/acid_act(): (acidpwr, acid_volume)
#define COMSIG_ATOM_ACID_ACT "atom_acid_act"
///from base of atom/emag_act(): (/mob/user)
@@ -112,7 +114,7 @@
#define COMSIG_ATOM_NARSIE_ACT "atom_narsie_act"
///from base of atom/rcd_act(): (/mob, /obj/item/construction/rcd, passed_mode)
#define COMSIG_ATOM_RCD_ACT "atom_rcd_act"
///from base of atom/singularity_pull(): (S, current_size)
///from base of atom/singularity_pull(): (/datum/component/singularity, current_size)
#define COMSIG_ATOM_SING_PULL "atom_sing_pull"
///from obj/machinery/bsa/full/proc/fire(): ()
#define COMSIG_ATOM_BSA_BEAM "atom_bsa_beam_pass"
@@ -193,6 +195,12 @@
///called on /living when someone starts pulling it (atom/movable/puller, state, force)
#define COMSIG_LIVING_START_PULL "living_start_pull"
/// from /datum/component/singularity/proc/can_move(), as well as /obj/energy_ball/proc/can_move()
/// if a callback returns `SINGULARITY_TRY_MOVE_BLOCK`, then the singularity will not move to that turf
#define COMSIG_ATOM_SINGULARITY_TRY_MOVE "atom_singularity_try_move"
/// When returned from `COMSIG_ATOM_SINGULARITY_TRY_MOVE`, the singularity will move to that turf
#define SINGULARITY_TRY_MOVE_BLOCK (1 << 0)
/////////////////
///from base of area/Entered(): (/area)
+2
View File
@@ -48,6 +48,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define UNPAINTABLE_1 (1 << 20)
/// Is the thing currently spinning?
#define IS_SPINNING_1 (1 << 21)
/// Does the supermatter skip over this atom?
#define SUPERMATTER_IGNORES_1 (1 << 22)
/// If the thing can reflect light (lasers/energy)
#define RICOCHET_SHINY (1<<0)
+1 -1
View File
@@ -9,7 +9,7 @@ GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report
//TODO clear this one up too
GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult)
GLOBAL_DATUM(cult_narsie, /obj/narsie)
///We want reality_smash_tracker to exist only once and be accesable from anywhere.
GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new)
+1 -1
View File
@@ -13,7 +13,7 @@ GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with d
GLOBAL_LIST_EMPTY(wayfindingbeacons) //list of all navigation beacons used by wayfinding pinpointers
GLOBAL_LIST_EMPTY(nuke_list)
GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts
GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines)
GLOBAL_LIST_EMPTY_TYPED(singularities, /datum/component/singularity) //list of all singularities on the station
GLOBAL_LIST_EMPTY(mechpad_list) //list of all /obj/machinery/mechpad
GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions
+1 -1
View File
@@ -130,7 +130,7 @@ SUBSYSTEM_DEF(traumas)
/obj/item/melee/cultblade, /obj/item/cult_bastard,
/obj/item/restraints/legcuffs/bola/cult, /obj/item/clothing/suit/space/hardsuit/cult,
/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/head/hooded/cult_hoodie, /obj/effect/rune,
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/singularity/narsie,
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/narsie,
/obj/item/soulstone,
/obj/item/clothing/suit/wizrobe, /obj/item/clothing/head/wizard, /obj/item/spellbook, /obj/item/staff,
/obj/item/clothing/suit/space/hardsuit/shielded/wizard, /obj/item/clothing/suit/space/hardsuit/wizard,
+2
View File
@@ -7,6 +7,8 @@
var/static/list/falling_atoms = list() // Atoms currently falling into chasms
var/static/list/forbidden_types = typecacheof(list(
/obj/singularity,
/obj/energy_ball,
/obj/narsie,
/obj/docking_port,
/obj/structure/lattice,
/obj/structure/stone_tile,
+294
View File
@@ -0,0 +1,294 @@
/// The range at which a singularity is considered "contained" to admins
#define FIELD_CONTAINMENT_DISTANCE 30
/// What's the chance that, when a singularity moves, it'll go to its target?
#define CHANCE_TO_MOVE_TO_TARGET 60
/// Things that maybe move around and does stuff to things around them
/// Used for the singularity (duh) and Nar'Sie
/datum/component/singularity
/// Callback for consuming objects (for example, Nar'Sie replaces this to call narsie_act)
var/datum/callback/consume_callback
/// The range to pull in stuff around it
var/consume_range
/// Does this singularity move?
var/roaming
/// The chosen direction to drift in
var/drifting_dir
/// How many tiles out to pull in
var/grav_pull
/// The last direction we failed to move in (for example: if we are contained)
var/last_failed_movement
/// How big is the singularity?
var/singularity_size
/// Should we disregard the possibility of failed movements? Used by stage five singularities
var/disregard_failed_movements
/// Can this singularity be BSA'd?
var/bsa_targetable
/// Should the admins be alerted when this is created?
var/notify_admins
/// If specified, the singularity will slowly move to this target
var/atom/target
/datum/component/singularity/Initialize(
bsa_targetable = TRUE,
consume_range = 0,
consume_callback = CALLBACK(src, .proc/default_singularity_act),
disregard_failed_movements = FALSE,
grav_pull = 4,
notify_admins = TRUE,
singularity_size = STAGE_ONE,
roaming = TRUE,
)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
src.bsa_targetable = bsa_targetable
src.consume_callback = consume_callback
src.consume_range = consume_range
src.disregard_failed_movements = disregard_failed_movements
src.grav_pull = grav_pull
src.notify_admins = notify_admins
src.roaming = roaming
src.singularity_size = singularity_size
/datum/component/singularity/RegisterWithParent()
START_PROCESSING(SSdcs, src)
// The singularity stops drifting for no man!
parent.AddElement(/datum/element/forced_gravity, FALSE)
parent.AddElement(/datum/element/bsa_blocker)
RegisterSignal(parent, COMSIG_ATOM_BSA_BEAM, .proc/bluespace_reaction)
RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, .proc/block_blob)
RegisterSignal(parent, list(
COMSIG_ATOM_ATTACK_ANIMAL,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_ATTACK_PAW,
), .proc/consume_attack)
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/consume_attackby)
RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, .proc/moved)
RegisterSignal(parent, list(COMSIG_ATOM_BUMPED, COMSIG_MOVABLE_CROSSED), .proc/consume)
RegisterSignal(parent, COMSIG_ATOM_BULLET_ACT, .proc/consume_bullets)
if (notify_admins)
admin_investigate_setup()
GLOB.singularities |= src
/datum/component/singularity/Destroy(force, silent)
GLOB.singularities -= src
QDEL_NULL(consume_callback)
target = null
return ..()
/datum/component/singularity/UnregisterFromParent()
STOP_PROCESSING(SSdcs, src)
parent.RemoveElement(/datum/element/bsa_blocker)
parent.RemoveElement(/datum/element/forced_gravity)
UnregisterSignal(parent, list(
COMSIG_ATOM_ATTACK_ANIMAL,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_ATTACK_PAW,
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_BSA_BEAM,
COMSIG_ATOM_BULLET_ACT,
COMSIG_ATOM_BUMPED,
COMSIG_MOVABLE_CROSSED,
COMSIG_MOVABLE_PRE_MOVE,
COMSIG_PARENT_ATTACKBY,
))
/datum/component/singularity/process(delta_time)
if (roaming)
move()
eat()
/datum/component/singularity/proc/block_blob()
SIGNAL_HANDLER
return COMPONENT_CANCEL_BLOB_ACT
/datum/component/singularity/proc/consume(datum/source, atom/thing)
consume_callback?.Invoke(thing, src)
/datum/component/singularity/proc/consume_attack(datum/source, mob/user)
SIGNAL_HANDLER
consume(source, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/singularity/proc/consume_attackby(datum/source, obj/item/item, mob/user)
SIGNAL_HANDLER
consume(source, user)
// Will there be an impact? Who knows. Will we see it? No.
/datum/component/singularity/proc/consume_bullets(obj/projectile/projectile)
SIGNAL_HANDLER
qdel(projectile)
/// Calls singularity_act on the thing passed, usually destroying the object
/datum/component/singularity/proc/default_singularity_act(atom/thing)
thing.singularity_act(singularity_size, parent)
/datum/component/singularity/proc/eat()
var/atom/atom_parent = parent
for (var/_tile in spiral_range_turfs(grav_pull, parent))
var/turf/tile = _tile
if (!tile || !isturf(atom_parent.loc))
continue
if (get_dist(tile, parent) > consume_range)
tile.singularity_pull(src, singularity_size)
else
consume(src, tile)
for (var/_thing in tile)
var/atom/thing = _thing
// Because we can possibly yield in the middle of iteration, let's make sure what were looking at is still there
// Without this, you get "Qdeleted thing being thrown around"
if (QDELETED(thing))
continue
if (isturf(atom_parent.loc) && thing != parent)
var/atom/movable/movable_thing = thing
if (get_dist(movable_thing, parent) > consume_range)
movable_thing.singularity_pull(parent, singularity_size)
else
consume(src, movable_thing)
CHECK_TICK
/datum/component/singularity/proc/move()
var/drifting_dir = pick(GLOB.alldirs - last_failed_movement)
if (!QDELETED(target) && prob(CHANCE_TO_MOVE_TO_TARGET))
drifting_dir = get_dir(parent, target)
step(parent, drifting_dir)
/datum/component/singularity/proc/moved(datum/source, atom/new_location)
SIGNAL_HANDLER
var/atom/atom_parent = parent
var/current_direction = atom_parent.dir
var/turf/current_turf = get_turf(parent)
for(var/dir in GLOB.cardinals)
if(current_direction & dir)
current_turf = get_step(current_turf, dir)
if(!current_turf)
break
// eat the stuff if we're going to move into it so it doesn't mess up our movement
for(var/atom/thing_on_turf in current_turf.contents)
consume(src, thing_on_turf)
consume(src, current_turf)
if(disregard_failed_movements || check_turfs_in(current_direction))
last_failed_movement = null
else
last_failed_movement = current_direction
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
/datum/component/singularity/proc/can_move(turf/to_move)
if (!to_move)
return FALSE
for (var/_thing in to_move)
var/atom/thing = _thing
if (SEND_SIGNAL(thing, COMSIG_ATOM_SINGULARITY_TRY_MOVE) & SINGULARITY_TRY_MOVE_BLOCK)
return FALSE
return TRUE
/// Makes sure we don't move out of the z-level by checking the turfs around us.
/// Takes in the direction we're going, and optionally how many steps forward to look.
/// If steps are not provided, it will be inferred by singularity_size.
/datum/component/singularity/proc/check_turfs_in(direction, steps)
if (!direction)
return FALSE
var/atom/atom_parent = parent
if (!steps)
switch (singularity_size)
if (STAGE_ONE)
steps = 1
if (STAGE_TWO)
steps = 3//Yes this is right
if (STAGE_THREE)
steps = 3
if (STAGE_FOUR)
steps = 4
if (STAGE_FIVE)
steps = 5
var/list/turfs = list()
var/turf/farthest_turf = atom_parent.loc
for (var/_ = 1 to steps)
farthest_turf = get_step(farthest_turf, direction)
if (!isturf(farthest_turf))
return FALSE
turfs.Add(farthest_turf)
var/dir2
var/dir3
switch (direction)
if (NORTH || SOUTH)
dir2 = EAST
dir3 = WEST
if (EAST || WEST)
dir2 = NORTH
dir3 = SOUTH
var/turf/farthest_perpendicular_turf = farthest_turf
for (var/_ = 1 to steps - 1)
farthest_perpendicular_turf = get_step(farthest_perpendicular_turf, dir2)
if (!isturf(farthest_perpendicular_turf))
return FALSE
turfs.Add(farthest_perpendicular_turf)
for (var/_ = 1 to steps - 1)
farthest_turf = get_step(farthest_turf, dir3)
if (!isturf(farthest_turf))
return FALSE
turfs.Add(farthest_turf)
for (var/turf_in_range in turfs)
if (isnull(turf_in_range))
continue
if (!can_move(turf_in_range))
return FALSE
return TRUE
/// Logs to admins that a singularity was created
/datum/component/singularity/proc/admin_investigate_setup()
var/turf/spawned_turf = get_turf(parent)
message_admins("A singulo has been created at [ADMIN_VERBOSEJMP(spawned_turf)].")
var/atom/atom_parent = parent
atom_parent.investigate_log("was made a singularity at [AREACOORD(spawned_turf)].", INVESTIGATE_SINGULO)
/// Fired when the singularity is fired at with the BSA and deletes it
/datum/component/singularity/proc/bluespace_reaction()
if (!bsa_targetable)
return
var/atom/atom_parent = parent
atom_parent.investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_SINGULO)
qdel(parent)
#undef CHANCE_TO_MOVE_TO_TARGET
#undef FIELD_CONTAINMENT_DISTANCE
@@ -23,5 +23,5 @@
log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
user.gib(TRUE, TRUE, TRUE)
new/obj/singularity/boh_tear(loccheck)
new/obj/boh_tear(loccheck)
qdel(A)
+3 -1
View File
@@ -120,7 +120,9 @@
/obj/machinery/atmospherics,
/obj/item/ammo_casing,
/obj/item/implant,
/obj/singularity
/obj/singularity,
/obj/energy_ball,
/obj/narsie,
))
if(!can_contaminate || !can_contam || blacklisted[thing.type])
continue
+4 -2
View File
@@ -682,8 +682,10 @@
* default behaviour is to send the [COMSIG_ATOM_BLOB_ACT] signal
*/
/atom/proc/blob_act(obj/structure/blob/B)
SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
return
var/blob_act_result = SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
if (blob_act_result & COMPONENT_CANCEL_BLOB_ACT)
return FALSE
return TRUE
/atom/proc/fire_act(exposed_temperature, exposed_volume)
SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
+13
View File
@@ -278,6 +278,7 @@
. = ..()
if(anchored)
connect_to_network()
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, .proc/block_singularity_if_active)
/obj/machinery/power/shieldwallgen/Destroy()
for(var/d in GLOB.cardinals)
@@ -364,6 +365,12 @@
if(F && (F.gen_primary == src || F.gen_secondary == src)) //it's ours, kill it.
qdel(F)
/obj/machinery/power/shieldwallgen/proc/block_singularity_if_active()
SIGNAL_HANDLER
if (active)
return SINGULARITY_TRY_MOVE_BLOCK
/obj/machinery/power/shieldwallgen/can_be_unfasten_wrench(mob/user, silent)
if(active)
if(!silent)
@@ -455,6 +462,7 @@
for(var/mob/living/L in get_turf(src))
visible_message("<span class='danger'>\The [src] is suddenly occupying the same space as \the [L]!</span>")
L.gib()
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, .proc/block_singularity)
/obj/machinery/shieldwall/Destroy()
gen_primary = null
@@ -489,6 +497,11 @@
if(gen_secondary) //using power may cause us to be destroyed
gen_secondary.add_load(drain_amount * 0.5)
/obj/machinery/shieldwall/proc/block_singularity()
SIGNAL_HANDLER
return SINGULARITY_TRY_MOVE_BLOCK
/obj/machinery/shieldwall/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
if(istype(mover) && (mover.pass_flags & PASSGLASS))
+9 -4
View File
@@ -22,8 +22,10 @@
if(user)
to_chat(user, "<span class='notice'>The connected wire doesn't have enough current.</span>")
return
for(var/obj/singularity/singulo in GLOB.singularities)
if(singulo.z == z)
for (var/_singulo in GLOB.singularities)
var/datum/component/singularity/singulo = _singulo
var/atom/singulo_atom = singulo.parent
if(singulo_atom.z == z)
singulo.target = src
icon_state = "[icontype]1"
active = TRUE
@@ -32,7 +34,8 @@
/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user = null)
for(var/obj/singularity/singulo in GLOB.singularities)
for(var/_singulo in GLOB.singularities)
var/datum/component/singularity/singulo = _singulo
if(singulo.target == src)
singulo.target = null
icon_state = "[icontype]0"
@@ -93,7 +96,9 @@
add_load(1500)
if(cooldown <= world.time)
cooldown = world.time + 80
for(var/obj/singularity/singulo in GLOB.singularities)
for(var/_singulo_component in GLOB.singularities)
var/datum/component/singularity/singulo_component = _singulo_component
var/atom/singulo = singulo_component.parent
if(singulo.z == z)
say("[singulo] is now [get_dist(src,singulo)] standard lengths away to the [dir2text(get_dir(src,singulo))]")
else
+1 -1
View File
@@ -570,7 +570,7 @@
/obj/item/melee/roastingstick/Initialize()
. = ..()
if (!ovens)
ovens = typecacheof(list(/obj/singularity, /obj/machinery/power/supermatter_crystal, /obj/structure/bonfire))
ovens = typecacheof(list(/obj/singularity, /obj/energy_ball, /obj/machinery/power/supermatter_crystal, /obj/structure/bonfire))
/obj/item/melee/roastingstick/attack_self(mob/user)
on = !on
+2
View File
@@ -94,6 +94,8 @@
return TRUE
/obj/blob_act(obj/structure/blob/B)
if (!..())
return
if(isturf(loc))
var/turf/T = loc
if(T.intact && HAS_TRAIT(src, TRAIT_T_RAY_VISIBLE))
+1 -1
View File
@@ -477,7 +477,7 @@
to_chat(user, "<span class='warning'>You try to shatter the orb, but it remains as solid as a rock!</span>")
to_chat(user, "<span class='danger'><span class='big'>It seems that the blood cult has exhausted its ability to curse the emergency escape shuttle. It would be unwise to create more cursed orbs or to continue to try to shatter this one.</span></span>")
return
if(locate(/obj/singularity/narsie) in GLOB.poi_list)
if(locate(/obj/narsie) in GLOB.poi_list)
to_chat(user, "<span class='warning'>Nar'Sie is already on this plane, there is no delaying the end of all things.</span>")
return
+2 -2
View File
@@ -503,7 +503,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(!(place in summon_objective.summon_spots))
to_chat(user, "<span class='cultlarge'>The Geometer can only be summoned where the veil is weak - in [english_list(summon_objective.summon_spots)]!</span>")
return
if(locate(/obj/singularity/narsie) in GLOB.poi_list)
if(locate(/obj/narsie) in GLOB.poi_list)
for(var/M in invokers)
to_chat(M, "<span class='warning'>Nar'Sie is already on this plane!</span>")
log_game("Nar'Sie rune failed - already summoned")
@@ -516,7 +516,7 @@ structure_check() searches for nearby cultist structures required for the invoca
sleep(40)
if(src)
color = RUNE_COLOR_RED
new /obj/singularity/narsie/large/cult(T) //Causes Nar'Sie to spawn even if the rune has been removed
new /obj/narsie(T) //Causes Nar'Sie to spawn even if the rune has been removed
//Rite of Resurrection: Requires a dead or inactive cultist. When reviving the dead, you can only perform one revival for every three sacrifices your cult has carried out.
/obj/effect/rune/raise_dead
+2
View File
@@ -38,6 +38,8 @@
var/static/list/blacklist_typecache = typecacheof(list(
/atom/movable/screen,
/obj/singularity,
/obj/energy_ball,
/obj/narsie,
/mob/living/simple_animal/hostile/morph,
/obj/effect))
@@ -16,7 +16,7 @@
w_class = WEIGHT_CLASS_NORMAL
hitsound = 'sound/weapons/bladeslice.ogg'
var/charges = 1
var/spawn_type = /obj/singularity/wizard
var/spawn_type = /obj/tear_in_reality
var/spawn_amt = 1
var/activate_descriptor = "reality"
var/rend_desc = "You should run now."
@@ -98,29 +98,38 @@
rend_desc = "Gently wafting with the sounds of mirthful grunting."
icon_state = "clownrender"
////TEAR IN REALITY
#define TEAR_IN_REALITY_CONSUME_RANGE 3
#define TEAR_IN_REALITY_SINGULARITY_SIZE STAGE_FOUR
/obj/singularity/wizard
/// Tear in reality, spawned by the veil render
/obj/tear_in_reality
name = "tear in the fabric of reality"
desc = "This isn't right."
icon = 'icons/effects/224x224.dmi'
icon_state = "reality"
pixel_x = -96
pixel_y = -96
dissipate = 0
move_self = 0
consume_range = 3
grav_pull = 4
current_size = STAGE_FOUR
allowed_size = STAGE_FOUR
anchored = TRUE
density = TRUE
move_resist = INFINITY
layer = MASSIVE_OBJ_LAYER
light_range = 6
appearance_flags = LONG_GLIDE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
/obj/singularity/wizard/process()
move()
eat()
return
/obj/tear_in_reality/Initialize(mapload)
. = ..()
AddComponent(
/datum/component/singularity, \
consume_range = TEAR_IN_REALITY_CONSUME_RANGE, \
notify_admins = !mapload, \
roaming = FALSE, \
singularity_size = TEAR_IN_REALITY_SINGULARITY_SIZE, \
)
/obj/singularity/wizard/attack_tk(mob/user)
/obj/tear_in_reality/attack_tk(mob/user)
if(!iscarbon(user))
return
. = COMPONENT_CANCEL_ATTACK_CHAIN
@@ -132,18 +141,17 @@
insaneinthemembrane.sanity = 0
for(var/lore in typesof(/datum/brain_trauma/severe))
jedi.gain_trauma(lore)
addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, jedi), 10 SECONDS)
addtimer(CALLBACK(src, .proc/deranged, jedi), 10 SECONDS)
/obj/singularity/wizard/proc/deranged(mob/living/carbon/C)
/obj/tear_in_reality/proc/deranged(mob/living/carbon/C)
if(!C || C.stat == DEAD)
return
C.vomit(0, TRUE, TRUE, 3, TRUE)
C.spew_organ(3, 2)
C.death()
/obj/singularity/wizard/mapped/admin_investigate_setup()
return
#undef TEAR_IN_REALITY_CONSUME_RANGE
#undef TEAR_IN_REALITY_SINGULARITY_SIZE
/////////////////////////////////////////Scrying///////////////////
@@ -60,21 +60,20 @@
name = "Pyromancy Evaluation"
info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage."
/// The immobile, close pulling singularity seen in the academy away mission
/obj/singularity/academy
dissipate = FALSE
move_self = FALSE
grav_pull = TRUE
/obj/singularity/academy/admin_investigate_setup()
return
/obj/singularity/academy/Initialize()
. = ..()
var/datum/component/singularity/singularity = singularity_component.resolve()
singularity?.grav_pull = 1
/obj/singularity/academy/process(delta_time)
eat()
if(DT_PROB(0.5, delta_time))
mezzer()
/obj/item/clothing/glasses/meson/truesight
name = "The Lens of Truesight"
desc = "I can see forever!"
@@ -135,22 +135,3 @@ GLOBAL_VAR_INIT(sc_safecode5, "[rand(0,9)]")
new /obj/item/clothing/suit/space/hardsuit/cult(src)
//new /obj/item/teleportation_scroll(src)
new /obj/item/stack/ore/diamond(src)
/*
* Modified Nar'Sie
*/
/obj/singularity/narsie/mini
desc = "Your body becomes weak and your feel your mind slipping away as you try to comprehend what you know can't be possible."
move_self = 0 //Contianed narsie does not move!
grav_pull = 0 //Contained narsie does not pull stuff in!
//Override this to prevent no adminlog runtimes and admin warnings about a singularity without containment
/obj/singularity/narsie/mini/admin_investigate_setup()
return
/obj/singularity/narsie/mini/process(delta_time)
eat()
if(DT_PROB(13, delta_time))
mezzer()
/obj/singularity/narsie/mini/ex_act()
return
+26 -30
View File
@@ -1,51 +1,47 @@
/// BoH tear
/// The BoH tear is a stationary singularity with a really high gravitational pull, which collapses briefly after being created
/// The BoH isn't deleted for 10 minutes (only moved to nullspace) so that admins may retrieve the things back in case of a grief
/obj/singularity/boh_tear
#define BOH_TEAR_CONSUME_RANGE 1
#define BOH_TEAR_GRAV_PULL 25
/obj/boh_tear
name = "tear in the fabric of reality"
desc = "Your own comprehension of reality starts bending as you stare this."
anchored = TRUE
appearance_flags = LONG_GLIDE
density = TRUE
icon = 'icons/effects/96x96.dmi'
icon_state = "boh_tear"
layer = MASSIVE_OBJ_LAYER
light_range = 6
move_resist = INFINITY
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
pixel_x = -32
pixel_y = -32
dissipate = 0
move_self = 0
consume_range = 1
grav_pull = 25
current_size = STAGE_SIX
allowed_size = STAGE_SIX
var/ghosts = list()
var/old_loc
var/start_time = 0
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
flags_1 = SUPERMATTER_IGNORES_1
/obj/singularity/boh_tear/Initialize()
/obj/boh_tear/Initialize()
. = ..()
old_loc = loc
start_time = world.time
QDEL_IN(src, 5 SECONDS) // vanishes after 5 seconds
/obj/singularity/boh_tear/process()
//Backup to catch timerss errors
if(start_time + (10 SECONDS) < world.time)
stack_trace("The timer subsytem isn't firing properly, yell at your local coders posthaste")
qdel(src)
eat()
AddComponent(
/datum/component/singularity, \
consume_range = BOH_TEAR_CONSUME_RANGE, \
grav_pull = BOH_TEAR_GRAV_PULL, \
roaming = FALSE, \
singularity_size = STAGE_SIX, \
)
/obj/singularity/boh_tear/consume(atom/A)
A.singularity_act(current_size, src)
/obj/singularity/boh_tear/admin_investigate_setup()
var/turf/T = get_turf(src)
message_admins("A BoH tear has been created at [ADMIN_VERBOSEJMP(T)].")
investigate_log("was created at [AREACOORD(T)].", INVESTIGATE_SINGULO)
/obj/singularity/boh_tear/attack_tk(mob/user)
/obj/boh_tear/attack_tk(mob/user)
if(!isliving(user))
return
var/mob/living/jedi = user
to_chat(jedi, "<span class='userdanger'>You don't feel like you are real anymore.</span>")
jedi.dust_animation()
jedi.spawn_dust()
addtimer(CALLBACK(src, .proc/consume, jedi), 0.5 SECONDS)
addtimer(CALLBACK(src, /atom/proc/attack_hand, jedi), 0.5 SECONDS)
return COMPONENT_CANCEL_ATTACK_CHAIN
#undef BOH_TEAR_CONSUME_RANGE
#undef BOH_TEAR_GRAV_PULL
@@ -20,6 +20,7 @@
/obj/machinery/field/containment/Initialize()
. = ..()
air_update_turf(TRUE)
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, .proc/block_singularity)
/obj/machinery/field/containment/Destroy()
FG1.fields -= src
@@ -79,6 +80,11 @@
FG2 = master2
return TRUE
/obj/machinery/field/containment/proc/block_singularity()
SIGNAL_HANDLER
return SINGULARITY_TRY_MOVE_BLOCK
/obj/machinery/field/containment/shock(mob/living/user)
if(!FG1 || !FG2)
qdel(src)
@@ -58,6 +58,7 @@ no power level overlay is currently in the overlays list.
. = ..()
fields = list()
connected_gens = list()
RegisterSignal(src, COMSIG_ATOM_SINGULARITY_TRY_MOVE, .proc/block_singularity_if_active)
/obj/machinery/field/generator/anchored/Initialize()
. = ..()
@@ -340,11 +341,6 @@ no power level overlay is currently in the overlays list.
clean_up = 0
update_icon()
//This is here to help fight the "hurr durr, release singulo cos nobody will notice before the
//singulo eats the evidence". It's not fool-proof but better than nothing.
//I want to avoid using global variables.
INVOKE_ASYNC(src, .proc/notify_admins)
move_resist = initial(move_resist)
/obj/machinery/field/generator/proc/shield_floor(create)
@@ -386,17 +382,11 @@ no power level overlay is currently in the overlays list.
if(S)
qdel(S)
/obj/machinery/field/generator/proc/notify_admins()
var/temp = TRUE //stops spam
for(var/obj/singularity/O in GLOB.singularities)
if(O.last_warning && temp)
if((world.time - O.last_warning) > 50) //to stop message-spam
temp = FALSE
var/turf/T = get_turf(src)
message_admins("A singulo exists and a containment field has failed at [ADMIN_VERBOSEJMP(T)].")
investigate_log("has <font color='red'>failed</font> whilst a singulo exists at [AREACOORD(T)].", INVESTIGATE_SINGULO)
notify_ghosts("IT'S LOOSE", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, ghost_sound = 'sound/machines/warning-buzzer.ogg', header = "IT'S LOOSE", notify_volume = 75)
O.last_warning = world.time
/obj/machinery/field/generator/proc/block_singularity_if_active()
SIGNAL_HANDLER
if (active)
return SINGULARITY_TRY_MOVE_BLOCK
/obj/machinery/field/generator/shock(mob/living/user)
if(fields.len)
+181 -163
View File
@@ -1,74 +1,204 @@
/obj/singularity/narsie //Moving narsie to a child object of the singularity so it can be made to function differently. --NEO
name = "Nar'Sie's Avatar"
#define NARSIE_CHANCE_TO_PICK_NEW_TARGET 5
#define NARSIE_CONSUME_RANGE 12
#define NARSIE_GRAV_PULL 10
#define NARSIE_MESMERIZE_CHANCE 25
#define NARSIE_MESMERIZE_EFFECT 60
#define NARSIE_SINGULARITY_SIZE 12
/// Nar'Sie, the God of the blood cultists
/obj/narsie
name = "Nar'Sie"
desc = "Your mind begins to bubble and ooze as it tries to comprehend what it sees."
icon = 'icons/obj/narsie_small.dmi'
icon = 'icons/obj/narsie.dmi'
icon_state = "narsie"
pixel_x = -89
pixel_y = -85
anchored = TRUE
appearance_flags = LONG_GLIDE
density = FALSE
current_size = 9 //It moves/eats like a max-size singulo, aside from range. --NEO
contained = 0 //Are we going to move around?
dissipate = 0 //Do we lose energy over time?
move_self = 1 //Do we move on our own?
grav_pull = 5 //How many tiles out do we pull?
consume_range = 6 //How many tiles out do we eat
gender = FEMALE
layer = MASSIVE_OBJ_LAYER
light_color = COLOR_RED
light_power = 0.7
light_range = 15
light_color = COLOR_RED
gender = FEMALE
/obj/singularity/narsie/large
name = "Nar'Sie"
icon = 'icons/obj/narsie.dmi'
// Pixel stuff centers Narsie.
light_range = 6
move_resist = INFINITY
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
pixel_x = -236
pixel_y = -256
current_size = 12
grav_pull = 10
consume_range = 12 //How many tiles out do we eat
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
flags_1 = SUPERMATTER_IGNORES_1
/obj/singularity/narsie/large/Initialize()
. = ..()
send_to_playing_players("<span class='narsie'>NAR'SIE HAS RISEN</span>")
sound_to_playing_players('sound/creatures/narsie_rises.ogg')
/// The singularity component to move around Nar'Sie.
/// A weak ref in case an admin removes the component to preserve the functionality.
var/datum/weakref/singularity
var/area/A = get_area(src)
if(A)
var/mutable_appearance/alert_overlay = mutable_appearance('icons/effects/cult_effects.dmi', "ghostalertsie")
notify_ghosts("Nar'Sie has risen in \the [A.name]. Reach out to the Geometer to be given a new shell for your soul.", source = src, alert_overlay = alert_overlay, action=NOTIFY_ATTACK)
narsie_spawn_animation()
UnregisterSignal(src, COMSIG_ATOM_BSA_BEAM) //set up in /singularity/Initialize()
/obj/singularity/narsie/large/cult // For the new cult ending, guaranteed to end the round within 3 minutes
var/list/souls_needed = list()
var/soul_goal = 0
var/souls = 0
var/resolved = FALSE
/obj/singularity/narsie/large/cult/Initialize()
/obj/narsie/Initialize()
. = ..()
singularity = WEAKREF(AddComponent(
/datum/component/singularity, \
bsa_targetable = FALSE, \
consume_callback = CALLBACK(src, .proc/consume), \
consume_range = NARSIE_CONSUME_RANGE, \
disregard_failed_movements = TRUE, \
grav_pull = NARSIE_GRAV_PULL, \
roaming = FALSE, /* This is set once the animation finishes */ \
singularity_size = NARSIE_SINGULARITY_SIZE, \
))
send_to_playing_players("<span class='narsie'>NAR'SIE HAS RISEN</span>")
sound_to_playing_players('sound/creatures/narsie_rises.ogg')
var/area/area = get_area(src)
if(area)
var/mutable_appearance/alert_overlay = mutable_appearance('icons/effects/cult_effects.dmi', "ghostalertsie")
notify_ghosts("Nar'Sie has risen in [area]. Reach out to the Geometer to be given a new shell for your soul.", source = src, alert_overlay = alert_overlay, action = NOTIFY_ATTACK)
narsie_spawn_animation()
GLOB.cult_narsie = src
var/list/all_cults = list()
for(var/datum/antagonist/cult/C in GLOB.antagonists)
if(!C.owner)
for (var/datum/antagonist/cult/cultist in GLOB.antagonists)
if (!cultist.owner)
continue
all_cults |= C.cult_team
for(var/datum/team/cult/T in all_cults)
deltimer(T.blood_target_reset_timer)
T.blood_target = src
var/datum/objective/eldergod/summon_objective = locate() in T.objectives
all_cults |= cultist.cult_team
for (var/_cult_team in all_cults)
var/datum/team/cult/cult_team = _cult_team
deltimer(cult_team.blood_target_reset_timer)
cult_team.blood_target = src
var/datum/objective/eldergod/summon_objective = locate() in cult_team.objectives
if(summon_objective)
summon_objective.summoned = TRUE
for(var/datum/mind/cult_mind in SSticker.mode.cult)
if(isliving(cult_mind.current))
for (var/_cult_mind in SSticker.mode.cult)
var/datum/mind/cult_mind = _cult_mind
if (isliving(cult_mind.current))
var/mob/living/L = cult_mind.current
L.narsie_act()
for(var/mob/living/player in GLOB.player_list)
if(player.stat != DEAD && player.loc && is_station_level(player.loc.z) && !iscultist(player) && !isanimal(player))
for (var/mob/living/carbon/player in GLOB.player_list)
if (player.stat != DEAD && is_station_level(player.loc?.z) && !iscultist(player))
souls_needed[player] = TRUE
soul_goal = round(1 + LAZYLEN(souls_needed) * 0.75)
INVOKE_ASYNC(GLOBAL_PROC, .proc/begin_the_end)
/obj/narsie/Destroy()
send_to_playing_players("<span class='narsie'>\"<b>[pick("Nooooo...", "Not die. How-", "Die. Mort-", "Sas tyen re-")]\"</b></span>")
sound_to_playing_players('sound/magic/demon_dies.ogg', 50)
var/list/all_cults = list()
for (var/datum/antagonist/cult/cultist in GLOB.antagonists)
if (!cultist.owner)
continue
all_cults |= cultist.cult_team
for(var/_cult_team in all_cults)
var/datum/team/cult/cult_team = _cult_team
var/datum/objective/eldergod/summon_objective = locate() in cult_team.objectives
if (summon_objective)
summon_objective.summoned = FALSE
summon_objective.killed = TRUE
return ..()
/obj/narsie/attack_ghost(mob/user)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = loc)
/obj/narsie/process()
var/datum/component/singularity/singularity_component = singularity.resolve()
if (!isnull(singularity_component) && (!singularity_component?.target || prob(NARSIE_CHANCE_TO_PICK_NEW_TARGET)))
pickcultist()
if (prob(NARSIE_MESMERIZE_CHANCE))
mesmerize()
/obj/narsie/Bump(atom/target)
var/turf/target_turf = get_turf(target)
if (target_turf == loc)
target_turf = get_step(target, target.dir) //please don't slam into a window like a bird, Nar'Sie
forceMove(target_turf)
/// Stun people around Nar'Sie that aren't cultists
/obj/narsie/proc/mesmerize()
for (var/mob/living/carbon/victim in viewers(NARSIE_CONSUME_RANGE, src))
if (victim.stat == CONSCIOUS)
if (!iscultist(victim))
to_chat(victim, "<span class='cultsmall'>You feel conscious thought crumble away in an instant as you gaze upon [src]...</span>")
victim.apply_effect(NARSIE_MESMERIZE_EFFECT, EFFECT_STUN)
/// Narsie rewards her cultists with being devoured first, then picks a ghost to follow.
/obj/narsie/proc/pickcultist()
var/list/cultists = list()
var/list/noncultists = list()
for (var/mob/living/carbon/food in GLOB.alive_mob_list) //we don't care about constructs or cult-Ians or whatever. cult-monkeys are fair game i guess
var/turf/pos = get_turf(food)
if (!pos || (pos.z != z))
continue
if (iscultist(food))
cultists += food
else
noncultists += food
if (cultists.len) //cultists get higher priority
acquire(pick(cultists))
return
if (noncultists.len)
acquire(pick(noncultists))
return
//no living humans, follow a ghost instead.
for (var/mob/dead/observer/ghost in GLOB.player_list)
var/turf/pos = get_turf(ghost)
if (!pos || (pos.z != z))
continue
cultists += ghost
if (cultists.len)
acquire(pick(cultists))
return
/// Nar'Sie gets a taste of something, and will start to gravitate towards it
/obj/narsie/proc/acquire(atom/food)
var/datum/component/singularity/singularity_component = singularity.resolve()
if (isnull(singularity_component))
return
var/old_target = singularity_component.target
if (food == old_target)
return
to_chat(old_target, "<span class='cultsmall'>NAR'SIE HAS LOST INTEREST IN YOU.</span>")
singularity_component.target = food
if(ishuman(food))
to_chat(food, "<span class='cult'>NAR'SIE HUNGERS FOR YOUR SOUL.</span>")
else
to_chat(food, "<span class='cult'>NAR'SIE HAS CHOSEN YOU TO LEAD HER TO HER NEXT MEAL.</span>")
/// Called to make Nar'Sie convert objects to cult stuff, or to eat
/obj/narsie/proc/consume(atom/target)
if (isturf(target))
target.narsie_act()
/obj/narsie/proc/narsie_spawn_animation()
setDir(SOUTH)
flick("narsie_spawn_anim", src)
addtimer(CALLBACK(src, .proc/narsie_spawn_animation_end), 3.5 SECONDS)
/obj/narsie/proc/narsie_spawn_animation_end()
var/datum/component/singularity/singularity_component = singularity.resolve()
singularity_component?.roaming = TRUE
/proc/begin_the_end()
sleep(50)
if(QDELETED(GLOB.cult_narsie)) // uno
@@ -105,21 +235,6 @@
sound_to_playing_players('sound/machines/alarm.ogg')
addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper), 120)
/obj/singularity/narsie/large/cult/Destroy()
send_to_playing_players("<span class='narsie'>\"<b>[pick("Nooooo...", "Not die. How-", "Die. Mort-", "Sas tyen re-")]\"</b></span>")
sound_to_playing_players('sound/magic/demon_dies.ogg', 50)
var/list/all_cults = list()
for(var/datum/antagonist/cult/C in GLOB.antagonists)
if(!C.owner)
continue
all_cults |= C.cult_team
for(var/datum/team/cult/T in all_cults)
var/datum/objective/eldergod/summon_objective = locate() in T.objectives
if(summon_objective)
summon_objective.summoned = FALSE
summon_objective.killed = TRUE
return ..()
/proc/ending_helper()
SSticker.force_ending = 1
@@ -131,106 +246,9 @@
else // explosion
Cinematic(CINEMATIC_CULT_NUKE,world,CALLBACK(GLOBAL_PROC,/proc/ending_helper))
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/singularity/narsie/large/attack_ghost(mob/dead/observer/user as mob)
makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, user, cultoverride = TRUE, loc_override = src.loc)
/obj/singularity/narsie/process()
eat()
if(!target || prob(5))
pickcultist()
move()
if(prob(25))
mezzer()
/obj/singularity/narsie/Bump(atom/A)
var/turf/T = get_turf(A)
if(T == loc)
T = get_step(A, A.dir) //please don't slam into a window like a bird, Nar'Sie
forceMove(T)
/obj/singularity/narsie/mezzer()
for(var/mob/living/carbon/M in viewers(consume_range, src))
if(M.stat == CONSCIOUS)
if(!iscultist(M))
to_chat(M, "<span class='cultsmall'>You feel conscious thought crumble away in an instant as you gaze upon [src.name]...</span>")
M.apply_effect(60, EFFECT_STUN)
/obj/singularity/narsie/consume(atom/A)
if(isturf(A))
A.narsie_act()
/obj/singularity/narsie/ex_act() //No throwing bombs at her either.
return
/obj/singularity/narsie/proc/pickcultist() //Narsie rewards her cultists with being devoured first, then picks a ghost to follow.
var/list/cultists = list()
var/list/noncultists = list()
for(var/mob/living/carbon/food in GLOB.alive_mob_list) //we don't care about constructs or cult-Ians or whatever. cult-monkeys are fair game i guess
var/turf/pos = get_turf(food)
if(!pos || (pos.z != z))
continue
if(iscultist(food))
cultists += food
else
noncultists += food
if(cultists.len) //cultists get higher priority
acquire(pick(cultists))
return
if(noncultists.len)
acquire(pick(noncultists))
return
//no living humans, follow a ghost instead.
for(var/mob/dead/observer/ghost in GLOB.player_list)
var/turf/pos = get_turf(ghost)
if(!pos || (pos.z != z))
continue
cultists += ghost
if(cultists.len)
acquire(pick(cultists))
return
/obj/singularity/narsie/proc/acquire(atom/food)
if(food == target)
return
to_chat(target, "<span class='cultsmall'>NAR'SIE HAS LOST INTEREST IN YOU.</span>")
target = food
if(ishuman(target))
to_chat(target, "<span class='cult'>NAR'SIE HUNGERS FOR YOUR SOUL.</span>")
else
to_chat(target, "<span class='cult'>NAR'SIE HAS CHOSEN YOU TO LEAD HER TO HER NEXT MEAL.</span>")
//Wizard narsie
/obj/singularity/narsie/wizard
grav_pull = 0
/obj/singularity/narsie/wizard/eat()
// if(defer_powernet_rebuild != 2)
// defer_powernet_rebuild = 1
for(var/atom/X in urange(consume_range,src,1))
if(isturf(X) || ismovable(X))
consume(X)
// if(defer_powernet_rebuild != 2)
// defer_powernet_rebuild = 0
return
/obj/singularity/narsie/proc/narsie_spawn_animation()
setDir(SOUTH)
move_self = FALSE
flick("narsie_spawn_anim",src)
addtimer(CALLBACK(src, .proc/narsie_spawn_animation_end), 3.5 SECONDS)
/obj/singularity/narsie/proc/narsie_spawn_animation_end()
move_self = TRUE
#undef NARSIE_CHANCE_TO_PICK_NEW_TARGET
#undef NARSIE_CONSUME_RANGE
#undef NARSIE_GRAV_PULL
#undef NARSIE_MESMERIZE_CHANCE
#undef NARSIE_MESMERIZE_EFFECT
#undef NARSIE_SINGULARITY_SIZE
+67 -168
View File
@@ -1,5 +1,4 @@
/// The gravitational singularity
/obj/singularity
name = "gravitational singularity"
desc = "A gravitational singularity."
@@ -11,41 +10,51 @@
layer = MASSIVE_OBJ_LAYER
light_range = 6
appearance_flags = LONG_GLIDE
/// The singularity component itself.
/// A weak ref in case an admin removes the component to preserve the functionality.
var/datum/weakref/singularity_component
var/current_size = 1
var/allowed_size = 1
var/contained = 1 //Are we going to move around?
var/energy = 100 //How strong are we?
var/dissipate = TRUE //Do we lose energy over time?
var/dissipate_delay = 10
var/dissipate_track = 0
var/dissipate_strength = 1 //How much energy do we lose?
var/move_self = 1 //Do we move on our own?
var/grav_pull = 4 //How many tiles out do we pull?
var/consume_range = 0 //How many tiles out do we eat
var/event_chance = 10 //Prob for event each tick
var/target = null //its target. moves towards the target if it has one
var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing
var/last_warning
var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six
var/drifting_dir = 0 // Chosen direction to drift in
var/move_self = TRUE
var/consumed_supermatter = FALSE //If the singularity has eaten a supermatter shard and can go to stage six
flags_1 = SUPERMATTER_IGNORES_1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
/obj/singularity/Initialize(mapload, starting_energy = 50)
//CARN: admin-alert for chuckle-fuckery.
admin_investigate_setup()
src.energy = starting_energy
. = ..()
energy = starting_energy
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
GLOB.singularities |= src
for(var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines)
if(singubeacon.active)
target = singubeacon
var/datum/component/singularity/new_component = AddComponent(
/datum/component/singularity, \
consume_callback = CALLBACK(src, .proc/consume), \
)
singularity_component = WEAKREF(new_component)
expand(current_size)
for (var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines)
if (singubeacon.active)
new_component.target = singubeacon
break
AddElement(/datum/element/bsa_blocker)
RegisterSignal(src, COMSIG_ATOM_BSA_BEAM, .proc/bluespace_reaction)
if (!mapload)
notify_ghosts("IT'S LOOSE", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, ghost_sound = 'sound/machines/warning-buzzer.ogg', header = "IT'S LOOSE", notify_volume = 75)
/obj/singularity/Destroy()
STOP_PROCESSING(SSobj, src)
@@ -53,49 +62,6 @@
GLOB.singularities.Remove(src)
return ..()
/obj/singularity/Move(atom/newloc, direct)
var/turf/T = get_turf(src)
for(var/dir in GLOB.cardinals)
if(direct & dir)
T = get_step(T, dir)
if(!T)
break
// eat the stuff if we're going to move into it so it doesn't mess up our movement
for(var/atom/A in T.contents)
consume(A)
consume(T)
if(current_size >= STAGE_FIVE || check_turfs_in(direct))
last_failed_movement = 0//Reset this because we moved
return ..()
else
last_failed_movement = direct
return FALSE
/obj/singularity/attack_hand(mob/user)
consume(user)
return TRUE
/obj/singularity/attack_paw(mob/user)
consume(user)
/obj/singularity/attack_alien(mob/user)
consume(user)
/obj/singularity/attack_animal(mob/user)
consume(user)
/obj/singularity/attackby(obj/item/W, mob/user, params)
consume(user)
return TRUE
/obj/singularity/Process_Spacemove() //The singularity stops drifting for no man!
return FALSE
/obj/singularity/blob_act(obj/structure/blob/B)
return
/obj/singularity/attack_tk(mob/user)
if(!iscarbon(user))
return
@@ -150,7 +116,6 @@
rip_u.dismember(BURN)
qdel(rip_u)
/obj/singularity/ex_act(severity, target)
switch(severity)
if(1)
@@ -164,52 +129,14 @@
energy -= round(((energy+1)/3),1)
if(3)
energy -= round(((energy+1)/4),1)
return
/obj/singularity/bullet_act(obj/projectile/P)
qdel(P)
return BULLET_ACT_HIT //Will there be an impact? Who knows. Will we see it? No.
/obj/singularity/Bump(atom/A)
consume(A)
if(QDELETED(A)) // don't keep moving into objects that weren't destroyed infinitely
step(src, drifting_dir)
return
/obj/singularity/Crossed(atom/A)
..()
consume(A)
/obj/singularity/Bumped(atom/movable/AM)
consume(AM)
/obj/singularity/process()
if(current_size >= STAGE_TWO)
move()
if(prob(event_chance))//Chance for it to run a special event TODO:Come up with one or two more that fit
event()
eat()
dissipate()
check_energy()
return
/obj/singularity/attack_ai() //to prevent ais from gibbing themselves when they click on one.
return
/obj/singularity/proc/admin_investigate_setup()
var/turf/T = get_turf(src)
last_warning = world.time
var/count = locate(/obj/machinery/field/containment) in urange(30, src, 1)
if(!count)
message_admins("A singulo has been created without containment fields active at [ADMIN_VERBOSEJMP(T)].")
investigate_log("was created at [AREACOORD(T)]. [count?"":"<font color='red'>No containment fields were active</font>"]", INVESTIGATE_SINGULO)
/obj/singularity/proc/dissipate()
if(!dissipate)
return
@@ -219,13 +146,18 @@
else
dissipate_track++
/obj/singularity/proc/expand(force_size = 0)
/obj/singularity/proc/expand(force_size)
var/temp_allowed_size = src.allowed_size
if(force_size)
temp_allowed_size = force_size
if(temp_allowed_size >= STAGE_SIX && !consumedSupermatter)
if(temp_allowed_size >= STAGE_SIX && !consumed_supermatter)
temp_allowed_size = STAGE_FIVE
var/new_grav_pull
var/new_consume_range
switch(temp_allowed_size)
if(STAGE_ONE)
current_size = STAGE_ONE
@@ -233,8 +165,8 @@
icon_state = "singularity_s1"
pixel_x = 0
pixel_y = 0
grav_pull = 4
consume_range = 0
new_grav_pull = 4
new_consume_range = 0
dissipate_delay = 10
dissipate_track = 0
dissipate_strength = 1
@@ -245,8 +177,8 @@
icon_state = "singularity_s3"
pixel_x = -32
pixel_y = -32
grav_pull = 6
consume_range = 1
new_grav_pull = 6
new_consume_range = 1
dissipate_delay = 5
dissipate_track = 0
dissipate_strength = 5
@@ -257,8 +189,8 @@
icon_state = "singularity_s5"
pixel_x = -64
pixel_y = -64
grav_pull = 8
consume_range = 2
new_grav_pull = 8
new_consume_range = 2
dissipate_delay = 4
dissipate_track = 0
dissipate_strength = 20
@@ -269,8 +201,8 @@
icon_state = "singularity_s7"
pixel_x = -96
pixel_y = -96
grav_pull = 10
consume_range = 3
new_grav_pull = 10
new_consume_range = 3
dissipate_delay = 10
dissipate_track = 0
dissipate_strength = 10
@@ -280,18 +212,27 @@
icon_state = "singularity_s9"
pixel_x = -128
pixel_y = -128
grav_pull = 10
consume_range = 4
dissipate = 0 //It cant go smaller due to e loss
new_grav_pull = 10
new_consume_range = 4
dissipate = FALSE //It cant go smaller due to e loss
if(STAGE_SIX) //This only happens if a stage 5 singulo consumes a supermatter shard.
current_size = STAGE_SIX
icon = 'icons/effects/352x352.dmi'
icon_state = "singularity_s11"
pixel_x = -160
pixel_y = -160
grav_pull = 15
consume_range = 5
dissipate = 0
new_grav_pull = 15
new_consume_range = 5
dissipate = FALSE
var/datum/component/singularity/resolved_singularity = singularity_component.resolve()
if (!isnull(resolved_singularity))
resolved_singularity.consume_range = new_consume_range
resolved_singularity.grav_pull = new_grav_pull
resolved_singularity.disregard_failed_movements = current_size >= STAGE_FIVE
resolved_singularity.roaming = move_self && current_size >= STAGE_TWO
resolved_singularity.singularity_size = current_size
if(current_size == allowed_size)
investigate_log("<font color='red'>grew to size [current_size]</font>", INVESTIGATE_SINGULO)
return TRUE
@@ -316,7 +257,7 @@
if(1000 to 1999)
allowed_size = STAGE_FOUR
if(2000 to INFINITY)
if(energy >= 3000 && consumedSupermatter)
if(energy >= 3000 && consumed_supermatter)
allowed_size = STAGE_SIX
else
allowed_size = STAGE_FIVE
@@ -324,51 +265,14 @@
expand()
return TRUE
/obj/singularity/proc/eat()
for(var/tile in spiral_range_turfs(grav_pull, src))
var/turf/T = tile
if(!T || !isturf(loc))
continue
if(get_dist(T, src) > consume_range)
T.singularity_pull(src, current_size)
else
consume(T)
for(var/thing in T)
if(isturf(loc) && thing != src)
var/atom/movable/X = thing
if(get_dist(X, src) > consume_range)
X.singularity_pull(src, current_size)
else
consume(X)
CHECK_TICK
return
/obj/singularity/proc/consume(atom/A)
var/gain = A.singularity_act(current_size, src)
src.energy += gain
if(istype(A, /obj/machinery/power/supermatter_crystal) && !consumedSupermatter)
/obj/singularity/proc/consume(atom/thing)
var/gain = thing.singularity_act(current_size, src)
energy += gain
if(istype(thing, /obj/machinery/power/supermatter_crystal) && !consumed_supermatter)
desc = "[initial(desc)] It glows fiercely with inner fire."
name = "supermatter-charged [initial(name)]"
consumedSupermatter = 1
consumed_supermatter = TRUE
set_light(10)
return
/obj/singularity/proc/move(force_move = 0)
if(!move_self)
return FALSE
var/drifting_dir = pick(GLOB.alldirs - last_failed_movement)
if(force_move)
drifting_dir = force_move
if(target && prob(60))
drifting_dir = get_dir(src,target) //moves to a singulo beacon, if there is one
step(src, drifting_dir)
/obj/singularity/proc/check_cardinals_range(steps, retry_with_move = FALSE)
. = length(GLOB.cardinals) //Should be 4.
@@ -497,18 +401,13 @@
/obj/singularity/proc/emp_area()
empulse(src, 8, 10)
return
/obj/singularity/singularity_act()
var/gain = (energy/2)
var/dist = max((current_size - 2),1)
explosion(src.loc,(dist),(dist*2),(dist*4))
qdel(src)
return(gain)
/obj/singularity/proc/bluespace_reaction()
investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_SINGULO)
qdel(src)
return gain
/obj/singularity/deadchat_controlled
move_self = FALSE
@@ -467,7 +467,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
else if(power > POWER_PENALTY_THRESHOLD)
investigate_log("has spawned additional energy balls.", INVESTIGATE_SUPERMATTER)
if(T)
var/obj/singularity/energy_ball/E = new(T)
var/obj/energy_ball/E = new(T)
E.energy = 200 //Gets us about 9 balls
else if(power > EVENT_POWER_PENALTY_THRESHOLD && prob(power/50) && !istype(src, /obj/machinery/power/supermatter_crystal/shard))
var/datum/round_event_control/crystal_invasion/crystals = new/datum/round_event_control/crystal_invasion
@@ -1008,7 +1008,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
user.dust(force = TRUE)
if(power_changes)
matter_power += 200
else if(istype(AM, /obj/singularity))
else if(AM.flags_1 & SUPERMATTER_IGNORES_1)
return
else if(isobj(AM))
if(!iseffect(AM))
+76 -71
View File
@@ -10,22 +10,26 @@
#define BLOB (STRUCTURE + 1)
#define STRUCTURE (1)
/obj/singularity/energy_ball
/// The Tesla engine
/obj/energy_ball
name = "energy ball"
desc = "An energy ball."
icon = 'icons/obj/tesla_engine/energy_ball.dmi'
icon_state = "energy_ball"
anchored = TRUE
appearance_flags = LONG_GLIDE
density = TRUE
layer = MASSIVE_OBJ_LAYER
light_range = 6
move_resist = INFINITY
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
pixel_x = -32
pixel_y = -32
current_size = STAGE_TWO
move_self = 1
grav_pull = 0
contained = 0
density = TRUE
energy = 0
dissipate = FALSE
dissipate_delay = 5
dissipate_strength = 1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
flags_1 = SUPERMATTER_IGNORES_1
var/energy
var/target
var/list/orbiting_balls = list()
var/miniball = FALSE
var/produced_power
@@ -33,39 +37,37 @@
var/energy_to_lower = -20
var/list/shocked_things = list()
/obj/singularity/energy_ball/Initialize(mapload, starting_energy = 50, is_miniball = FALSE)
miniball = is_miniball
/obj/energy_ball/Initialize(mapload, starting_energy = 50, is_miniball = FALSE)
. = ..()
if(!is_miniball)
energy = starting_energy
miniball = is_miniball
START_PROCESSING(SSobj, src)
if (!is_miniball)
set_light(10, 7, "#5e5edd")
/obj/singularity/energy_ball/ex_act(severity, target)
return
var/turf/spawned_turf = get_turf(src)
message_admins("A tesla has been created at [ADMIN_VERBOSEJMP(spawned_turf)].")
investigate_log("(tesla) was created at [AREACOORD(spawned_turf)].", INVESTIGATE_SINGULO)
/obj/singularity/energy_ball/consume(severity, target)
return
/obj/energy_ball/Destroy()
if(orbiting && istype(orbiting.parent, /obj/energy_ball))
var/obj/energy_ball/parent_energy_ball = orbiting.parent
parent_energy_ball.orbiting_balls -= src
/obj/singularity/energy_ball/Destroy()
if(orbiting && istype(orbiting.parent, /obj/singularity/energy_ball))
var/obj/singularity/energy_ball/EB = orbiting.parent
EB.orbiting_balls -= src
QDEL_LIST(orbiting_balls)
STOP_PROCESSING(SSobj, src)
for(var/ball in orbiting_balls)
var/obj/singularity/energy_ball/EB = ball
QDEL_NULL(EB)
. = ..()
return ..()
/obj/singularity/energy_ball/admin_investigate_setup()
if(miniball)
return //don't annnounce miniballs
..()
/obj/singularity/energy_ball/process()
if(!orbiting)
/obj/energy_ball/process()
if(orbiting)
energy = 0 // ensure we dont have miniballs of miniballs
else
handle_energy()
move_the_basket_ball(4 + orbiting_balls.len * 1.5)
move(4 + orbiting_balls.len * 1.5)
playsound(src.loc, 'sound/magic/lightningbolt.ogg', 100, TRUE, extrarange = 30)
@@ -81,40 +83,46 @@
var/list/temp_shock = list()
tesla_zap(ball, range, TESLA_MINI_POWER/7*range, shocked_targets = temp_shock)
shocked_things += temp_shock
else
energy = 0 // ensure we dont have miniballs of miniballs //But it'll be cool broooooooooooooooo
/obj/singularity/energy_ball/examine(mob/user)
/obj/energy_ball/examine(mob/user)
. = ..()
if(orbiting_balls.len)
. += "There are [orbiting_balls.len] mini-balls orbiting it."
/obj/singularity/energy_ball/proc/move_the_basket_ball(move_amount)
/obj/energy_ball/proc/move(move_amount)
var/list/dirs = GLOB.alldirs.Copy()
for(var/I in 1 to 30)
for (var/i in 1 to 30)
var/atom/real_thing = pick(shocked_things)
dirs += get_dir(src, real_thing) //Carry some momentum yeah? Just a bit tho
for(var/i in 0 to move_amount)
for (var/i in 0 to move_amount)
var/move_dir = pick(dirs) //ensures teslas don't just sit around
if(target && prob(10))
move_dir = get_dir(src,target)
var/turf/T = get_step(src, move_dir)
if(can_move(T))
forceMove(T)
if (target && prob(10))
move_dir = get_dir(src, target)
var/turf/turf_to_move = get_step(src, move_dir)
if (can_move(turf_to_move))
forceMove(turf_to_move)
setDir(move_dir)
for(var/mob/living/carbon/C in loc)
dust_mobs(C)
for (var/mob/living/carbon/mob_to_dust in loc)
dust_mobs(mob_to_dust)
/obj/energy_ball/proc/can_move(turf/to_move)
if (!to_move)
return FALSE
/obj/singularity/energy_ball/proc/handle_energy()
for (var/_thing in to_move)
var/atom/thing = _thing
if (SEND_SIGNAL(thing, COMSIG_ATOM_SINGULARITY_TRY_MOVE) & SINGULARITY_TRY_MOVE_BLOCK)
return FALSE
return TRUE
/obj/energy_ball/proc/handle_energy()
if(energy >= energy_to_raise)
energy_to_lower = energy_to_raise - 20
energy_to_raise = energy_to_raise * 1.25
playsound(src.loc, 'sound/magic/lightning_chargeup.ogg', 100, TRUE, extrarange = 30)
addtimer(CALLBACK(src, .proc/new_mini_ball), 100)
else if(energy < energy_to_lower && orbiting_balls.len)
energy_to_raise = energy_to_raise / 1.25
energy_to_lower = (energy_to_raise / 1.25) - 20
@@ -122,31 +130,31 @@
var/Orchiectomy_target = pick(orbiting_balls)
qdel(Orchiectomy_target)
else if(orbiting_balls.len)
dissipate() //sing code has a much better system.
/obj/singularity/energy_ball/proc/new_mini_ball()
/obj/energy_ball/proc/new_mini_ball()
if(!loc)
return
var/obj/singularity/energy_ball/EB = new(loc, 0, TRUE)
EB.transform *= pick(0.3, 0.4, 0.5, 0.6, 0.7)
var/icon/I = icon(icon,icon_state,dir)
var/obj/energy_ball/miniball = new /obj/energy_ball(
loc,
/* starting_energy = */ 0,
/* is_miniball = */ TRUE
)
miniball.transform *= pick(0.3, 0.4, 0.5, 0.6, 0.7)
var/icon/I = icon(icon, icon_state,dir)
var/orbitsize = (I.Width() + I.Height()) * pick(0.4, 0.5, 0.6, 0.7, 0.8)
orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)
EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))
miniball.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))
/obj/singularity/energy_ball/Bump(atom/A)
/obj/energy_ball/Bump(atom/A)
dust_mobs(A)
/obj/singularity/energy_ball/Bumped(atom/movable/AM)
/obj/energy_ball/Bumped(atom/movable/AM)
dust_mobs(AM)
/obj/singularity/energy_ball/attack_tk(mob/user)
/obj/energy_ball/attack_tk(mob/user)
if(!iscarbon(user))
return
var/mob/living/carbon/jedi = user
@@ -158,25 +166,22 @@
jedi.death()
return COMPONENT_CANCEL_ATTACK_CHAIN
/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target)
/obj/energy_ball/orbit(obj/energy_ball/target)
if (istype(target))
target.orbiting_balls += src
GLOB.poi_list -= src
target.dissipate_strength = target.orbiting_balls.len
. = ..()
/obj/singularity/energy_ball/stop_orbit()
if (orbiting && istype(orbiting.parent, /obj/singularity/energy_ball))
var/obj/singularity/energy_ball/orbitingball = orbiting.parent
/obj/energy_ball/stop_orbit()
if (orbiting && istype(orbiting.parent, /obj/energy_ball))
var/obj/energy_ball/orbitingball = orbiting.parent
orbitingball.orbiting_balls -= src
orbitingball.dissipate_strength = orbitingball.orbiting_balls.len
. = ..()
if (!QDELETED(src))
qdel(src)
/obj/singularity/energy_ball/proc/dust_mobs(atom/A)
/obj/energy_ball/proc/dust_mobs(atom/A)
if(isliving(A))
var/mob/living/L = A
if(L.incorporeal_move || L.status_flags & GODMODE)
+1 -1
View File
@@ -38,7 +38,7 @@ All ShuttleMove procs go here
else //non-living mobs shouldn't be affected by shuttles, which is why this is an else
if(istype(thing, /obj/singularity) && !istype(thing, /obj/singularity/narsie)) //it's a singularity but not a god, ignore it.
if(istype(thing, /obj/singularity) || istype(thing, /obj/energy_ball))
continue
if(!thing.anchored)
step(thing, shuttle_dir)
+2 -2
View File
@@ -6,8 +6,8 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/obj/item/disk/nuclear,
/obj/machinery/nuclearbomb,
/obj/item/beacon,
/obj/singularity/narsie,
/obj/singularity/wizard,
/obj/narsie,
/obj/tear_in_reality,
/obj/machinery/teleport/station,
/obj/machinery/teleport/hub,
/obj/machinery/quantumpad,
+1
View File
@@ -480,6 +480,7 @@
#include "code\datums\components\rot.dm"
#include "code\datums\components\rotation.dm"
#include "code\datums\components\shrink.dm"
#include "code\datums\components\singularity.dm"
#include "code\datums\components\sitcomlaughter.dm"
#include "code\datums\components\sizzle.dm"
#include "code\datums\components\slippery.dm"