Adds the alien cache ruin and a PTL terminal (#30367)

* Make the cache room

* adds alien cache machine

* adds the cache properly

* adds multitile component

* adds terminal building and reward selection procs

* adds laser receptacle file

* Refactors ptl targetting code

* more work on the ptl terminal

* change file name to laser_terminal

* changes area to powered makes laser terminal work properly

* makes a new pickweight function because the old one is bad

* Update alien_cache.dm

* Update canister.dm

* Update alien_cache.dm

* adds a template of the ruin

* adds some spawners to the reward pool

* adjusts agent b amount

* adjustments to loot table and changes to spawner comments

* new placeholder sprite for the cache

* randomizes reward spawn location and makes a PTL terminal sprite

* updates alien cache sprite and examine proc

* Adds overlays to the cache to indicate the current progress

* fixes mob capsule spawner and laser terminal

* Adds animations to PTL terminal

* new box part of the sprite for the PTL terminal. Adds glow

* fixes untargetting the PTL terminal and adds a little light overlay to the receiving dish

* changes PTL terminal id generation slightly

* adds simon says. incomplete

* Update paradise.dme

* simon says works now

* adds more rhythms

* Adds success and failure sounds

* Makes the ptl terminal board printable

* Update secure_alien_airlock.dm

* undo changes to preferences.dm

* undefs things

* removes identical to parent vars

* Update alien_cache_random_spawners.dm

* replaces animation end loop flag so open dream CI doesn't give errors

* changes centre platform colour and adds a fan to the entrance

* more pads more sounds

* better sprites

* remove old sprite

* adds the cache to example config

* makes it not always spawn

* Update simon_says_32x32.dmi

* adds some flavor to the ruin

* Update alien_cache_site.dmm

* adds a signal to the ruin
This commit is contained in:
Migratingcocofruit
2025-10-01 12:54:10 +00:00
committed by GitHub
parent 57c797a791
commit ad98a804cb
28 changed files with 2050 additions and 25 deletions
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -209,8 +209,11 @@
#define LORENTZ_DISTRIBUTION(x, s) ( s*tan(TODEGREES(PI*(rand()-0.5))) + x )
#define LORENTZ_CUMULATIVE_DISTRIBUTION(x, y, s) ( (1/PI)*TORADIANS(arctan((x-y)/s)) + 1/2 )
#define RULE_OF_THREE(a, b, x) ((a*x)/b)
/// Returns probability based on deviation from the mean(m) and sigma(s)
#define normal_distribution(x, m, s) ((1 / ((2 * PI * s**2)**0.5)) * NUM_E ** -(((x - m) ** 2) / (2 * s ** 2)))
// )
/proc/RaiseToPower(num, power)
+22
View File
@@ -293,6 +293,28 @@
return null
/**
* Picks an element based on its weight. Weight can be any real number
* L - The input list
*
* example: list("a" = 0.33, "b" = 0.67) will have a 67% chance to pick "b"
*/
/proc/pickweight_fraction(list/L)
var/total = 0
var/item
for(item in L)
if(L[item] < 0)
continue
total += L[item]
total = rand() * total
for(item in L)
total -=L [item]
if(total <= 0)
return item
return null
//Pick a random element from the list and remove it from the list.
/proc/pick_n_take(list/listfrom)
if(length(listfrom) > 0)
@@ -400,3 +400,11 @@
description = "Nanotrasen uses relays like these to further extend telecommunications around an area of space, as well as long range beacons for easier deployment in the future. \
Unfortunately, the anomalous activity around Epsilon Eridani, along with orbital debris and space-faring hostiles, has rendered many of these relay stations inoperable, leaving their communications and teleportation systems offline."
never_spawn_on_the_same_level = list("bluespace_relay_beacon")
/datum/map_template/ruin/space/alien_cache_site
id = "alien_cache_site"
suffix = "alien_cache_site.dmm"
name = "Alien Cache Site"
description = "A site containing a cache of alien design, who knows what valuable technology is hiding inside?"
never_spawn_on_the_same_level = list("alien_cache_site")
allow_duplicates = FALSE
@@ -0,0 +1,7 @@
/area/ruin/powered/alien_cache_site
name = "Alien Cache Site"
report_alerts = FALSE
dynamic_lighting = FALSE
/area/ruin/powered/alien_cache_site/cache_room
name = "Alien Cache Chamber"
+35
View File
@@ -1510,3 +1510,38 @@ GLOBAL_LIST_EMPTY(blood_splatter_icons)
attack_info.last_attacker_ckey = attacker.ckey
if(istype(weapon))
attack_info.last_attacker_weapon = "[weapon] ([weapon.type])"
// MARK: PTL PROCS
// Called when the target is selected
/atom/proc/on_ptl_target(obj/machinery/power/transmission_laser/ptl)
if(ptl.firing)
on_ptl_fire()
return
// Called for each process of the PTL
/atom/proc/on_ptl_tick(obj/machinery/power/transmission_laser/ptl, output_level)
return
// Called when no longer targeted by the ptl
/atom/proc/on_ptl_untarget(obj/machinery/power/transmission_laser/ptl)
return
// Called when the PTL starts firing on the target
/atom/proc/on_ptl_fire(obj/machinery/power/transmission_laser/ptl)
return
// Called when the PTL stops firing on the target
/atom/proc/on_ptl_stop(obj/machinery/power/transmission_laser/ptl)
return
// Used in the PTL ui
/atom/proc/ptl_data()
return name
// Called if an atom untargets itself
/atom/proc/untarget_self(obj/machinery/power/transmission_laser/ptl)
on_ptl_untarget(ptl)
if(ptl)
ptl.target = null
return
@@ -0,0 +1,150 @@
// Canister
#define COMPRESSED_GAS_AMOUNT 300000
#define AGENT_B_AMOUNT 100000
#define NAME_O2 "O2"
#define NAME_CO2 "CO2"
#define NAME_N2 "N2"
#define NAME_N2O "N2O"
#define NAME_TOX "Plasma"
#define NAME_AGENT_B "Agent B"
// Lazarus Capsule
#define MOB_TYPE (1 << 0)
#define MOB_PROTOTYPE (1 << 1)
// MARK: Random Spanwers
/obj/effect/spawner/random/alien_cache
name = "alien cache loot spawner"
// Random alien tools
/obj/effect/spawner/random/alien_cache/alien_tool
name = "alien tool spawner"
loot = list(
/obj/item/screwdriver/abductor,
/obj/item/wrench/abductor,
/obj/item/multitool/abductor,
/obj/item/wirecutters/abductor,
/obj/item/crowbar/abductor,
)
// Random alien surgical tools
/obj/effect/spawner/random/alien_cache/alien_surgical_tool
name = "alien surgical tool spawner"
loot = list(
/obj/item/bonegel/alien,
/obj/item/scalpel/laser/alien,
/obj/item/bonesetter/alien,
/obj/item/circular_saw/alien,
/obj/item/hemostat/alien,
/obj/item/fix_o_vein/alien,
/obj/item/retractor/alien,
)
// Random alien tool implant
/obj/effect/spawner/random/alien_cache/alien_tool_implant
name = "alien tool implant spawner"
loot = list(
/obj/item/organ/internal/cyberimp/arm/toolset_abductor,
/obj/item/organ/internal/cyberimp/arm/toolset_abductor/l,
/obj/item/organ/internal/cyberimp/arm/janitorial_abductor,
/obj/item/organ/internal/cyberimp/arm/janitorial_abductor/l,
/obj/item/organ/internal/cyberimp/arm/surgical_abductor,
/obj/item/organ/internal/cyberimp/arm/surgical_abductor/l,
)
// Random alien tool borg upgrade
/obj/effect/spawner/random/alien_cache/alien_tool_borg
name = "alien borg tool upgrade"
loot = list(
/obj/item/borg/upgrade/abductor_engi,
/obj/item/borg/upgrade/abductor_medi,
/obj/item/borg/upgrade/abductor_jani,
)
//MARK: Canister Spawners
// Gas canister with random gas
/obj/effect/spawner/alien_cache/gas_canister
name = "random gas canister spawner"
var/list/gasses = list(
NAME_O2 = COMPRESSED_GAS_AMOUNT,
NAME_N2 = COMPRESSED_GAS_AMOUNT,
NAME_CO2 = COMPRESSED_GAS_AMOUNT,
NAME_TOX = COMPRESSED_GAS_AMOUNT,
NAME_N2O = COMPRESSED_GAS_AMOUNT)
// Agent B canister
/obj/effect/spawner/alien_cache/gas_canister/agent_b
name = "agent b canister spawner"
gasses = list(NAME_AGENT_B = AGENT_B_AMOUNT)
/obj/effect/spawner/alien_cache/gas_canister/proc/add_gas(obj/machinery/atmospherics/portable/canister/spawned)
var/picked = pick(gasses)
switch(picked)
if(NAME_O2)
spawned.air_contents.set_oxygen(gasses["[picked]"])
if(NAME_N2)
spawned.air_contents.set_nitrogen(gasses["[picked]"])
if(NAME_N2O)
spawned.air_contents.set_sleeping_agent(gasses["[picked]"])
if(NAME_CO2)
spawned.air_contents.set_carbon_dioxide(gasses["[picked]"])
if(NAME_TOX)
spawned.air_contents.set_toxins(gasses["[picked]"])
if(NAME_AGENT_B)
spawned.air_contents.set_agent_b(gasses["[picked]"])
/obj/effect/spawner/alien_cache/gas_canister/Initialize(mapload)
. = ..()
var/obj/machinery/atmospherics/portable/canister/spawned = new(get_turf(src))
add_gas(spawned)
update_icon()
qdel(src)
// MARK: Lazarus Spawners
// Lazarus Capsule with a mob inside
/obj/effect/spawner/alien_cache/mob_capsule
var/list/mob_types = list()
// Lazarus Capsule with a megafauna inside
/obj/effect/spawner/alien_cache/mob_capsule/megafauna
mob_types = list(/mob/living/simple_animal/hostile/megafauna = MOB_PROTOTYPE)
/obj/effect/spawner/alien_cache/mob_capsule/Initialize(mapload)
. = ..()
var/obj/item/mobcapsule/capsule = new(get_turf(src))
var/type
if(length(mob_types))
var/list/pool = list()
for(var/mob_type in mob_types)
if(mob_types[mob_type] & MOB_TYPE)
pool += mob_type
if(mob_types[mob_type] & MOB_PROTOTYPE)
pool += subtypesof(mob_type)
type = pick(pool)
else
type = pick(subtypesof(/mob/living/simple_animal))
var/mob/living/simple_animal/my_mob = new type(loc)
my_mob.faction = list("neutral")
capsule.captured = my_mob
my_mob.forceMove(capsule)
// TEG crate for the cache
/obj/structure/closet/crate/teg
/obj/structure/closet/crate/teg/populate_contents()
new /obj/machinery/power/teg(src)
new /obj/item/pipe/circulator(src)
new /obj/item/pipe/circulator(src)
#undef COMPRESSED_GAS_AMOUNT
#undef AGENT_B_AMOUNT
#undef NAME_O2
#undef NAME_CO2
#undef NAME_N2
#undef NAME_N2O
#undef NAME_TOX
#undef NAME_AGENT_B
#undef MOB_TYPE
#undef MOB_PROTOTYPE
@@ -71,10 +71,8 @@
/area/mine/outpost,
/area/shuttle/mining,
)
/// Megafauna being targeted
var/mob/living/simple_animal/hostile/megafauna/target
/// Overlay that goes over the mob that gets beamed
var/image/orbital_strike
/// PTL target
var/atom/target
/obj/machinery/power/transmission_laser/north
pixel_x = -64
@@ -276,7 +274,7 @@
data["accepting_power"] = turned_on
data["sucking_power"] = inputting
data["firing"] = firing
data["target"] = target ? target.internal_gps.gpstag : ""
data["target"] = target ? target.ptl_data() : ""
data["power_format"] = power_format_multi
data["input_number"] = input_number
@@ -338,23 +336,24 @@
for(var/area_type in targetable_areas)
if(istype(boss_loc, area_type))
target_list[monster.internal_gps.gpstag] = monster
for(var/obj/machinery/power/laser_terminal/receptacle in GLOB.laser_terminals)
target_list["[receptacle.name]: [receptacle.id]"] = receptacle
// Target CC to sell power
target_list["Collection Terminal"] = null
var/choose = tgui_input_list(user, "Select target", "Target", target_list)
if(!choose)
return
untarget()
target = target_list[choose]
RegisterSignal(target, COMSIG_MOB_DEATH, PROC_REF(untarget))
if(firing && target)
orbital_strike = image(target.icon, target, "orbital_strike", FLY_LAYER, SOUTH)
target.add_overlay(orbital_strike)
if(target)
target.on_ptl_target(src)
/// Stop targeting a mob once it dies
/obj/machinery/power/transmission_laser/proc/untarget()
SIGNAL_HANDLER
target.cut_overlay(orbital_strike)
UnregisterSignal(target, COMSIG_MOB_DEATH)
SIGNAL_HANDLER // This can be called via various signals depending on what we register on the a target
if(target)
target.on_ptl_untarget(src)
target = null
/obj/machinery/power/transmission_laser/process()
@@ -394,14 +393,8 @@
if(!target)
sell_power(output_level * WATT_TICK_TO_JOULE)
else
if(!QDELETED(target)) // Just for safety.
target.loot = list() // disable loot drops form the target to prevent cheese
if(10 * output_level * target.damage_coeff[BURN] / (1 MW) > target.health) // If we would kill the target dust it.
target.health = 0 // We need this so can_die() won't prevent dusting
visible_message("<span class='danger'>\The [src] is reduced to dust by the beam!</span>")
target.dust()
else
target.adjustFireLoss(10 * output_level / (1 MW))
if(!QDELETED(target)) // Make sure our target still exists
INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, on_ptl_tick), src, output_level)
else
target = null
if(output_level > EYE_DAMAGE_THRESHOLD)
@@ -481,8 +474,7 @@
/obj/machinery/power/transmission_laser/proc/setup_lasers()
if(target)
orbital_strike = image(target.icon, target, "orbital_strike", FLY_LAYER, SOUTH)
target.add_overlay(orbital_strike)
target.on_ptl_fire()
var/turf/last_step = get_step(get_front_turf(), dir)
for(var/num in 1 to range)
if(!(locate(/obj/effect/transmission_beam) in last_step))
@@ -495,7 +487,7 @@
/obj/machinery/power/transmission_laser/proc/destroy_lasers()
if(target)
target.cut_overlay(orbital_strike)
target.on_ptl_stop()
for(var/obj/effect/transmission_beam/listed_beam as anything in laser_effects)
laser_effects -= listed_beam
qdel(listed_beam)
@@ -439,12 +439,12 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new())
name = "Canister \[Air\]"
icon_state = "grey" //See Initialize()
can_label = FALSE
/obj/machinery/atmospherics/portable/canister/custom_mix
name = "Canister \[Custom\]"
icon_state = "whiters" //See Initialize()
can_label = FALSE
/obj/machinery/atmospherics/portable/canister/toxins/Initialize(mapload)
. = ..()
@@ -193,6 +193,36 @@
difficulty_ore_modifier -= 4
enraged = FALSE
// MARK: PTL Interaction
/mob/living/simple_animal/hostile/megafauna/on_ptl_target(obj/machinery/power/transmission_laser/ptl)
ptl.RegisterSignal(src, COMSIG_MOB_DEATH, TYPE_PROC_REF(/obj/machinery/power/transmission_laser, untarget), ptl)
if(ptl?.firing)
on_ptl_fire(ptl)
return
/mob/living/simple_animal/hostile/megafauna/on_ptl_tick(obj/machinery/power/transmission_laser/ptl, output_level)
loot = list() // disable loot drops form the target to prevent cheese
if(10 * output_level * damage_coeff[BURN] / (1 MW) > health) // If we would kill the target dust it.
health = 0 // We need this so can_die() won't prevent dusting
visible_message("<span class='danger'>\The [src] is reduced to dust by the beam!</span>")
dust()
else
adjustFireLoss(10 * output_level / (1 MW))
/mob/living/simple_animal/hostile/megafauna/on_ptl_untarget(obj/machinery/power/transmission_laser/ptl)
on_ptl_stop(ptl)
if(ptl)
ptl.UnregisterSignal(src, COMSIG_MOB_DEATH)
/mob/living/simple_animal/hostile/megafauna/on_ptl_fire(obj/machinery/power/transmission_laser/ptl)
var/orbital_strike = image(icon, src, "orbital_strike", FLY_LAYER, SOUTH)
add_overlay(orbital_strike)
/mob/living/simple_animal/hostile/megafauna/on_ptl_stop(obj/machinery/power/transmission_laser/ptl)
for(var/image/overlay in overlays)
if(overlay.name == "orbital_strike")
cut_overlay(overlay)
/mob/living/simple_animal/hostile/megafauna/DestroySurroundings()
. = ..()
for(var/turf/simulated/floor/chasm/C in circlerangeturfs(src, 1))
+259
View File
@@ -0,0 +1,259 @@
#define CACHE_MAX_LEVEL 10
#define LEVEL_DISTRIBUTION_SIGMA 0.5
#define LEVEL_REQUIREMENT(level) ((300 MJ) * ((level) ** 3))
/obj/machinery/alien_cache
name = "Alien Cache"
desc = "A strange storage device of alien design"
icon = 'icons/obj/machines/alien_cache.dmi'
icon_state = "cache_0"
base_icon_state = "cache_0"
pixel_x = -32
pixel_y = -32
density = TRUE
anchored = TRUE
// Alien stuff is pretty tough
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
/// The maximum reachable level
var/max_level = CACHE_MAX_LEVEL
/// The deepest level we opened
var/level_reached = 0
/// The total amount of energy consumed
var/total_energy = 0
/// Assoc list of level to rewards found at that level with a weight for each
var/list/random_rewards = list(
// level 1
list(
/obj/item/stack/sheet/mineral/abductor/fifty = 1,
/obj/machinery/the_singularitygen/tesla = 0.3,
/obj/machinery/the_singularitygen = 0.3,
/obj/item/stack/sheet/mineral/bananium/thirty = 1,
/obj/item/stack/sheet/mineral/tranquillite/thirty = 1,
),
// level 2
list(
/obj/effect/spawner/random/alien_cache/alien_surgical_tool = 1,
/obj/item/mod/module/dispenser = 1,
/obj/effect/spawner/alien_cache/mob_capsule = 1,
/obj/item/storage/pill_bottle/random_meds/labelled = 1,
),
// level 3
list(
/obj/item/mod/module/jetpack/advanced = 1,
/obj/effect/spawner/alien_cache/gas_canister = 1,
/obj/effect/spawner/random/alien_cache/alien_tool_borg = 1,
),
// level 4
list(
/obj/effect/spawner/random/alien_cache/alien_tool = 1,
/obj/item/assembly/signaler/anomaly/random = 1,
/obj/effect/spawner/alien_cache/gas_canister/agent_b = 1,
),
// level 5
list(
/obj/item/storage/belt/military/abductor/full = 1,
/obj/item/guardiancreator/biological = 1,
),
)
/// List of guaranteed rewards you get from the last stage
var/list/open_rewards = list(
/obj/item/circuitboard/machine/bluespace_tap = 1,
)
/// Terminal for receiving power through
var/obj/machinery/power/terminal/terminal
/obj/machinery/alien_cache/Initialize(mapload)
. = ..()
AddComponent(/datum/component/multitile, list(
list(1, MACH_CENTER, 1),
list(1, 0, 1),
))
/obj/machinery/alien_cache/update_overlays()
. = ..()
var/my_alpha = 0
for(var/cache_level in 1 to CACHE_MAX_LEVEL)
if(total_energy <= 0 || cache_level > level_reached + 1)
break
if(cache_level < level_reached + 1)
my_alpha = 255
if(cache_level == level_reached + 1)
my_alpha = clamp(1 - ((LEVEL_REQUIREMENT(cache_level) - total_energy) / (LEVEL_REQUIREMENT(cache_level + 1) - LEVEL_REQUIREMENT(cache_level))), 0, 1)
my_alpha *= 255
. += mutable_appearance(icon, "cache_level_[cache_level]", alpha = my_alpha)
/obj/machinery/alien_cache/examine(mob/user)
. = ..()
if(panel_open)
. += "<span class='notice'>The panel is open, revealing unterminated internal wiring.</span>"
else
. += "<span class='notice'>There's a loose panel on the front that could be pried open with a screwdriver</span>"
/obj/machinery/alien_cache/display_parts(user)
return list("<span class='warning'>ERROR: UNIDENTIFIED MACHINE DESIGN</span>")
/// generate a list with weights for tiers of loot depending on the reached level
/obj/machinery/alien_cache/proc/tier_weights()
var/list/tiers = list()
// Generate a weighted list of all possible tiers using normal distribution
var/avg_tier = (length(random_rewards) * level_reached / max_level)
for(var/i in 1 to length(random_rewards))
var/tier_prob = normal_distribution(i, avg_tier, LEVEL_DISTRIBUTION_SIGMA)
if(tier_prob > 0)
tiers["[i]"] = tier_prob
return tiers
/// Returns a list of rewards with length equal to amount
/obj/machinery/alien_cache/proc/pick_rewards(amount = 1)
. = list()
if(!amount || amount < 1)
return
var/list/tiers = tier_weights()
for(var/i in 1 to amount)
var/selected_level = text2num(pickweight_fraction(tiers))
if(length(random_rewards[selected_level]))
. += pickweight_fraction(random_rewards[selected_level])
else
. += "no rewards in level [selected_level]"
/obj/machinery/alien_cache/process()
if(terminal && level_reached < max_level)
var/available = terminal.get_surplus()
terminal.consume_direct_power(available)
total_energy += available * WATT_TICK_TO_JOULE
if(total_energy >= LEVEL_REQUIREMENT(level_reached + 1))
level_reached++
var/list/rewards = pick_rewards(3)
to_chat(world, "Level: [level_reached]\nRewards: [english_list(rewards)]")
spawn_loot(rewards)
if(level_reached >= max_level)
spawn_loot(open_rewards)
update_icon(UPDATE_OVERLAYS)
/obj/machinery/alien_cache/proc/spawn_loot(list/loot)
var/spawn_side = prob(50) ? WEST : EAST
if(dir & (EAST | WEST))
// We can turn EAST/WEST into NORTH/SOUTH simply by right shifting
spawn_side = spawn_side >> 2
var/turf/spawnloc = get_step(get_step(get_step(src, dir), dir), spawn_side)
new /obj/effect/portal(spawnloc, null, src, 10)
for(var/lootpath in loot)
new lootpath(spawnloc)
/// Items interaction mostly stolen from SMES
/obj/machinery/alien_cache/item_interaction(mob/living/user, obj/item/used, list/modifiers)
// Building and linking a terminal
if(istype(used, /obj/item/stack/cable_coil))
var/dir = get_dir(user, src)
if(dir & (dir - 1)) // Checks for diagonal interaction
return ITEM_INTERACT_COMPLETE
if(terminal) // Checks for an existing terminal
to_chat(user, "<span class='alert'>[src] already has a terminal!</span>")
return ITEM_INTERACT_COMPLETE
if(!panel_open) // Checks to see if the panel is closed
to_chat(user, "<span class='alert'>You must open the panel first!</span>")
return ITEM_INTERACT_COMPLETE
var/turf/T = get_turf(user)
if(T.intact) // Checks to see if floor plating is present
to_chat(user, "<span class='alert'>You must first remove the floor plating!</span>")
return ITEM_INTERACT_COMPLETE
var/obj/item/stack/cable_coil/C = used
if(C.get_amount() < 10)
to_chat(user, "<span class='alert'>You need more wires.</span>")
return ITEM_INTERACT_COMPLETE
if(user.loc == loc)
to_chat(user, "<span class='warning'>You must not be on the same tile as [src].</span>")
return ITEM_INTERACT_COMPLETE
// Direction the terminal will face to
var/turf/temporary_location = get_step(src, REVERSE_DIR(dir))
if(isspaceturf(temporary_location))
to_chat(user, "<span class='warning'>You can't build a terminal on space.</span>")
return ITEM_INTERACT_COMPLETE
else if(istype(temporary_location))
if(temporary_location.intact)
to_chat(user, "<span class='warning'>You must remove the floor plating first.</span>")
return ITEM_INTERACT_COMPLETE
to_chat(user, "<span class='notice'>You try to connect the cable to [src]</span>")
playsound(loc, C.usesound, 50, TRUE)
if(do_after(user, 5 SECONDS, target = get_turf(src)))
if(!terminal && panel_open)
T = get_turf(user)
var/obj/structure/cable/N = T.get_cable_node() //get the connecting node cable, if there's one
if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) //animate the electrocution if uncautious and unlucky
do_sparks(5, TRUE, src)
return
C.use(10) // make sure the cable gets used up
user.visible_message(\
"<span class='notice'>[user.name] adds the cables and connects the power terminal.</span>",\
"<span class='notice'>You add the cables and connect the power terminal.</span>")
make_terminal(user, temporary_location)
terminal.connect_to_network()
stat &= ~BROKEN
return ITEM_INTERACT_COMPLETE
return ..()
/obj/machinery/alien_cache/multitool_act(mob/living/user, obj/item/I)
. = TRUE
to_chat(user, chat_box_examine("<span class='notice'>Total energy: [DisplayJoules(total_energy)]<br>Levels opened: [level_reached]</span>"))
/obj/machinery/alien_cache/screwdriver_act(mob/living/user, obj/item/I)
// Opening using screwdriver
return default_deconstruction_screwdriver(user, "[icon_state]-o", initial(icon_state), I)
/obj/machinery/alien_cache/wirecutter_act(mob/living/user, obj/item/I)
// No terminal to cut, just bonk
if(!terminal)
return FALSE
// If we have a terminal assume we are trying to disassemble it
. = TRUE
var/turf/T = get_turf(terminal)
if(T.intact) //is the floor plating removed ?
to_chat(user, "<span class='alert'>You must first expose the power terminal!</span>")
return
if(!panel_open)
to_chat(user, "<span class='alert'>You must first open the panel</span>")
to_chat(user, "<span class='notice'>You begin to dismantle the power terminal...</span>")
playsound(src.loc, I.usesound, 50, TRUE)
if(do_after(user, 5 SECONDS * I.toolspeed, target = src))
if(terminal && panel_open)
if(prob(50) && electrocute_mob(usr, terminal.powernet, terminal, 1, TRUE)) // Animate the electrocution if uncautious and unlucky
do_sparks(5, TRUE, src)
return
// Returns wires on deletion of the terminal
new /obj/item/stack/cable_coil(T, 10)
user.visible_message(\
"<span class='alert'>[user.name] cuts the cables and dismantles the power terminal.</span>",\
"<span class='notice'>You cut the cables and dismantle the power terminal.</span>")
qdel(terminal)
return
/// Terminal creation proc stolen from SMES
/obj/machinery/alien_cache/proc/make_terminal(user, temporary_location)
// Create a terminal object at the same position as original turf loc
// Wires will attach to this
terminal = new /obj/machinery/power/terminal(temporary_location)
terminal.master = src
terminal.dir = REVERSE_DIR(dir)
#undef CACHE_MAX_LEVEL
#undef LEVEL_DISTRIBUTION_SIGMA
#undef LEVEL_REQUIREMENT
+108
View File
@@ -0,0 +1,108 @@
GLOBAL_LIST_EMPTY(laser_terminals)
/obj/machinery/power/laser_terminal
name = "Laser Terminal"
desc = "A terminal designed to capture power sent by the power transmission laser"
icon = 'icons/obj/machines/ptl_terminal.dmi'
icon_state = "ptl_terminal_0"
base_icon_state = "ptl_terminal_0"
pixel_y = 10
density = TRUE
/// How much of the received energy we end up outputting to the grid
var/conversion_efficiency = 0.5
/// List of lasers targetting us
var/list/lasers = list()
/// The amount of power coming into the PTL
var/total_input
/// id of the terminal so you know which one you're targetting
var/id = 0
/obj/item/circuitboard/machine/laser_terminal
board_name = "Laser Terminal"
icon_state = "engineering"
build_path = /obj/machinery/power/laser_terminal
origin_tech = "plasmatech=3; powerstorage=4"
req_components = list(
/obj/item/stack/cable_coil = 5,
/obj/item/stock_parts/capacitor = 2,
)
/obj/machinery/power/laser_terminal/Initialize(mapload)
. = ..()
id = copytext(UID(), findlasttext(UID(), "_") + 1)
GLOB.laser_terminals += src
component_parts = list()
component_parts += new /obj/item/circuitboard/machine/laser_terminal(null)
component_parts += new /obj/item/stock_parts/capacitor(null)
component_parts += new /obj/item/stock_parts/capacitor(null)
component_parts += new /obj/item/stack/cable_coil(null, 5)
RefreshParts()
connect_to_network()
/obj/machinery/power/laser_terminal/Destroy()
GLOB.laser_terminals -= src
. = ..()
/obj/machinery/power/laser_terminal/RefreshParts()
var/cap_rating = 0
for(var/obj/item/stock_parts/capacitor/cap in component_parts)
cap_rating += cap.rating
// Goes from half to full conversion depending on capacitor level
conversion_efficiency = cap_rating / 8
/obj/machinery/power/laser_terminal/on_ptl_target(obj/machinery/power/transmission_laser/ptl)
lasers |= ptl
/obj/machinery/power/laser_terminal/on_ptl_untarget(obj/machinery/power/transmission_laser/ptl)
lasers -= ptl
/obj/machinery/power/laser_terminal/update_overlays()
. = ..()
if(total_input > 0)
. += "ptl_terminal_graph"
. += "ptl_terminal_blinkers"
. += "ptl_terminal_light"
switch(total_input)
if(1 MW to 10 MW)
. += "ptl_terminal_bar_1"
if(10 MW to 20 MW)
. += "ptl_terminal_bar_2"
if(20 MW to 50 MW)
. += "ptl_terminal_bar_3"
if(50 MW to 100 MW)
. += "ptl_terminal_bar_4"
if(100 MW to 1 GW)
. += "ptl_terminal_bar_5"
if(1 GW to INFINITY)
. += "ptl_terminal_bar_6"
/obj/machinery/power/laser_terminal/process()
var/tick_input = 0
for(var/obj/machinery/power/transmission_laser/ptl in lasers)
if(ptl.firing)
tick_input += ptl.output_level
// We use another var so we don't zero total input since it's also used for other things that could potentially happen mid-process
total_input = tick_input
produce_direct_power(total_input * conversion_efficiency)
update_icon(UPDATE_OVERLAYS)
/obj/machinery/power/laser_terminal/screwdriver_act(mob/living/user, obj/item/I)
return default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)
/obj/machinery/power/laser_terminal/wrench_act(mob/living/user, obj/item/I)
. = TRUE
if(panel_open)
to_chat(user, "<span class='warning'>Close the maintenance panel first!</span>")
return
. = default_unfasten_wrench(user, I, 2)
if(anchored)
connect_to_network()
else
disconnect_from_network()
/obj/machinery/power/laser_terminal/crowbar_act(mob/living/user, obj/item/I)
return default_deconstruction_crowbar(user, I, FALSE)
/obj/machinery/power/laser_terminal/multitool_act(mob/living/user, obj/item/I)
. = TRUE
to_chat(user, chat_box_examine("<span class='notice'>Unit ID: [id]<br>Input Power: [DisplayPower(total_input)]<br>Output Power: [DisplayPower(total_input * conversion_efficiency)]</span>"))
@@ -12,6 +12,16 @@
build_path = /obj/item/circuitboard/thermomachine
category = list ("Engineering Machinery")
/datum/design/laser_terminal
name = "Machine Board (Laser Terminal)"
desc = "The circuit board for a PTL terminal."
id = "thermomachine"
req_tech = list("powerstorage" = 4, "plasmatech" = 3)
build_type = IMPRINTER
materials = list(MAT_GLASS = 1000)
build_path = /obj/item/circuitboard/machine/laser_terminal
category = list ("Engineering Machinery")
/datum/design/space_heater
name = "Machine Board (Space Heater)"
desc = "The circuit board for a space heater"
@@ -0,0 +1,15 @@
/obj/machinery/door/airlock/abductor/secure
security_level = 6
autoclose = FALSE
flags = INDESTRUCTIBLE
locked = TRUE
req_access = list()
/obj/machinery/door/airlock/abductor/secure/Initialize(mapload)
. = ..()
// Randomize the wires so they aren't the same as the station
wires.randomize()
/// Always returns false since you aren't supposed to open it normally
/obj/machinery/door/airlock/abductor/secure/check_access(obj/item/I)
return FALSE
@@ -0,0 +1,184 @@
/obj/effect/simon_says
name = "strange platform"
desc = "A strange platform of alien design."
icon = 'icons/effects/simon_says_32x32.dmi'
icon_state = "pad"
var/fail_sound = 'sound/effects/simon_says_wrong.ogg'
var/success_sound = 'sound/effects/simon_says_success.ogg'
/// The chosen rhythm to play
var/list/rhythm = list()
/// List of all rhythms to choose from, made from lists of time from the start in deciseconds
var/static/list/rhythms = list(
// 7 Elevens are like the greatest thing
list(0, 7, 11, 14, 21, 22, 28, 33, 35, 42, 44, 49, 55, 56, 63, 66, 70),
list(0, 2.5, 7.5, 10, 15, 17.5, 22.5, 27.5, 30, 35, 40),
list(0, 8, 10, 16, 20, 24, 30, 32, 40),
list(0, 6, 10, 12, 18, 20, 24, 30),
list(0, 3, 6, 12, 18, 30, 33, 36, 39, 45, 51),
)
var/static/list/pad_colors = list(
"#b45e5e",
"#3c6474",
"#8a7d44",
"#b45e5e",
"#3c6474",
"#8a7d44",
)
var/static/list/pad_sounds = list(
'sound/effects/simon_says_1.ogg',
'sound/effects/simon_says_2.ogg',
'sound/effects/simon_says_3.ogg',
'sound/effects/simon_says_4.ogg',
'sound/effects/simon_says_5.ogg',
'sound/effects/simon_says_6.ogg',)
/// The time at which we should finish playing the sequence
var/playing_until = 0
/// Buttons pressed by the player so far
var/list/pressed_buttons = list()
/// The sequence we generated
var/list/sequence = list()
/// List of connected pads(normally generated on init)
var/list/pads = list()
/// current index in the sequence we check our pressed button against
var/current_index = 0
/// ID for autolinking an airlock on init
var/autolink_id
/// UID of the linked airlock
var/airlock_uid
/obj/effect/simon_says/Initialize(mapload)
. = ..()
var/turf/place = get_turf(src)
place = locate(place.x, place.y + 4, place.z)
if(place.x >= world.maxx - 1 || place.x <= 2 || place.y >= world.maxy - 1 || place.y <= 2)
qdel(src)
return
var/id = 1
for(var/cardinal in list(NORTHWEST, NORTHEAST, EAST, SOUTHEAST, SOUTHWEST, WEST))
var/turf/curr_place = place
if(cardinal & (NORTH | SOUTH))
curr_place = get_step(place, cardinal & (NORTH | SOUTH))
else
curr_place = get_step(place, cardinal & (EAST | WEST))
pads += new /obj/effect/simon_says_pad(get_step(curr_place, cardinal), id++, src)
/obj/effect/simon_says/Destroy()
for(var/obj/effect/simon_says_pad/pad in pads)
qdel(pad)
return ..()
/obj/effect/simon_says/Cross(atom/movable/crossed_atom)
. = ..()
if(!ismob(crossed_atom) || (playing_until >= world.time))
return
play_music()
/obj/effect/simon_says/proc/reset_buttons()
pressed_buttons = list()
current_index = 0
/obj/effect/simon_says/proc/play_music()
if(!length(sequence))
generate_sequence()
playing_until = world.time + rhythm[length(rhythm)]
reset_buttons()
for(var/i in 1 to length(sequence))
var/obj/effect/simon_says_pad/curr_pad = pads[sequence[i]]
addtimer(CALLBACK(curr_pad, TYPE_PROC_REF(/obj/effect/simon_says_pad, play_note)), rhythm[i])
for(var/obj/machinery/door/airlock/to_link in range(10, get_turf(src)))
if(to_link.id_tag == autolink_id)
airlock_uid = to_link.UID()
/obj/effect/simon_says/proc/generate_sequence()
sequence = list()
if(!length(rhythm))
rhythm = pick(rhythms)
var/list/pad_options = list()
// The reason we do all this instead of just rand(1, length(pads)) is so we can exclude pads that were played too recently
for(var/i in 1 to length(pads))
pad_options += list(list(i, -5))
for(var/beat in rhythm)
// Exclude pads that have played too recently
var/list/excluded = list()
for(var/option in pad_options)
if(beat - option[2] < 3)
excluded += list(option)
pad_options -= excluded
var/picked = pick(pad_options)
sequence += picked[1]
picked[2] = beat
pad_options += excluded
/obj/effect/simon_says/proc/check_completion()
if(!sequence || !length(sequence))
return
if(sequence[current_index] != pressed_buttons[current_index])
do_failure()
return
if(current_index == length(sequence))
do_success()
/obj/effect/simon_says/proc/play_sound(sound, blink_color)
animate_flash(src, blink_color, 2)
playsound(src, sound, 100, FALSE, falloff_exponent = SOUND_FALLOFF_EXPONENT / 4)
/obj/effect/simon_says/proc/do_failure()
reset_buttons()
play_sound(fail_sound, "#ff1100")
/obj/effect/simon_says/proc/do_success()
reset_buttons()
play_sound(success_sound, "#00e600")
var/obj/machinery/door/airlock/activated = locateUID(airlock_uid)
activated.airlock_cycle_callback("secure_open")
/obj/effect/simon_says_pad
name = "strange platform"
desc = "A strange platform of alien design, it looks like something will happen if you stand on it"
icon = 'icons/effects/simon_says_32x32.dmi'
icon_state = "pad"
//pixel_x = -8
//pixel_y = -8
/// The simon says minigame we are a part of
var/obj/effect/simon_says/owner
/// Sound we make when playing
var/sound = 'sound/effects/simon_says_1.ogg'
/// ID used for comparing what we pressed against the sequence
var/id = 1
/// Time the pad was last played
var/last_played = 0
/obj/effect/simon_says_pad/Initialize(mapload, _id, _owner)
. = ..()
owner = _owner
id = _id
if(owner)
color = owner.pad_colors[id]
sound = owner.pad_sounds[id]
/obj/effect/simon_says_pad/proc/play_note()
last_played = world.time
var/list/hsl = rgb2num(color, COLORSPACE_HSL)
hsl[3] = hsl[3] + (100 - hsl[3]) * 0.7
var/target = rgb(hsl[1], hsl[2], hsl[3], space= COLORSPACE_HSL)
playsound(src, sound, 100, FALSE, falloff_exponent = SOUND_FALLOFF_EXPONENT / 4)
animate_flash(src, target, 1.5)
/obj/effect/simon_says_pad/Cross(atom/movable/crossed_atom)
. = ..()
if(!ismob(crossed_atom) || (owner.playing_until >= world.time) || world.time < last_played + 3)
return
play_note()
owner.pressed_buttons += id
owner.current_index++
owner.check_completion()
/proc/animate_flash(atom/to_flash , color, length)
var/my_color = to_flash.color
animate(to_flash, color = color, time = length, easing = SINE_EASING)
spawn(length)
animate(to_flash, color = my_color, time = length, easing = SINE_EASING)
+1
View File
@@ -674,6 +674,7 @@ active_space_ruins = [
"_maps/map_files/RandomRuins/SpaceRuins/way_home.dmm",
"_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm",
"_maps/map_files/RandomRuins/SpaceRuins/wreckedcargoship.dmm",
"_maps/map_files/RandomRuins/SpaceRuins/alien_cache_site.dmm",
### The following ruins are based from past pre-spawned Zlevel content ###
"_maps/map_files/RandomRuins/SpaceRuins/abandonedtele.dmm",
Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

+6
View File
@@ -850,6 +850,7 @@
#include "code\game\area\shuttle_areas.dm"
#include "code\game\area\areas\depot-areas.dm"
#include "code\game\area\areas\mining_areas.dm"
#include "code\game\area\areas\ruins\alien_cache_site_areas.dm"
#include "code\game\area\areas\ruins\blackmarketpackers.dm"
#include "code\game\area\areas\ruins\lavaland_areas.dm"
#include "code\game\area\areas\ruins\space_areas.dm"
@@ -1194,6 +1195,7 @@
#include "code\game\objects\effects\spawners\random_barrier.dm"
#include "code\game\objects\effects\spawners\vaultspawner.dm"
#include "code\game\objects\effects\spawners\windowspawner.dm"
#include "code\game\objects\effects\spawners\random\alien_cache_random_spawners.dm"
#include "code\game\objects\effects\spawners\random\bluespace_tap_spawners.dm"
#include "code\game\objects\effects\spawners\random\decal_spawners.dm"
#include "code\game\objects\effects\spawners\random\engineering_spawners.dm"
@@ -2874,8 +2876,10 @@
#include "code\modules\pda\games\minesweeper.dm"
#include "code\modules\persistence\persistence.dm"
#include "code\modules\point\point.dm"
#include "code\modules\power\alien_cache.dm"
#include "code\modules\power\cell.dm"
#include "code\modules\power\gravitygenerator.dm"
#include "code\modules\power\laser_terminal.dm"
#include "code\modules\power\lights.dm"
#include "code\modules\power\power_machine.dm"
#include "code\modules\power\smes.dm"
@@ -3087,6 +3091,8 @@
#include "code\modules\ruins\objects_and_mobs\gym.dm"
#include "code\modules\ruins\objects_and_mobs\id_upgrader.dm"
#include "code\modules\ruins\objects_and_mobs\necropolis_gate.dm"
#include "code\modules\ruins\objects_and_mobs\secure_alien_airlock.dm"
#include "code\modules\ruins\objects_and_mobs\simon_says.dm"
#include "code\modules\ruins\spaceruin_code\voyager.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
#include "code\modules\security_levels\security_level_datums.dm"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.