diff --git a/_maps/map_files/RandomRuins/SpaceRuins/alien_cache_site.dmm b/_maps/map_files/RandomRuins/SpaceRuins/alien_cache_site.dmm new file mode 100644 index 00000000000..0154741ff81 --- /dev/null +++ b/_maps/map_files/RandomRuins/SpaceRuins/alien_cache_site.dmm @@ -0,0 +1,1195 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"cK" = ( +/obj/structure/closet, +/obj/item/soap, +/obj/item/soap, +/obj/item/clothing/under/rank/cargo/expedition/skirt, +/obj/item/clothing/under/rank/cargo/expedition/skirt, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"dV" = ( +/obj/structure/lightable/bonfire, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"ec" = ( +/obj/machinery/hydroponics/soil, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"en" = ( +/obj/structure/table/abductor, +/obj/item/kitchen/knife, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"et" = ( +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site/cache_room) +"eF" = ( +/obj/effect/decal/cleanable/dirt, +/obj/item/pickaxe, +/obj/item/weldingtool/largetank, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"fA" = ( +/obj/structure/fans/tiny/invisible, +/obj/machinery/door/airlock/abductor/secure, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"gf" = ( +/obj/effect/spawner/random/glowstick, +/obj/structure/table/abductor, +/obj/effect/spawner/random/engineering/tools, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"hj" = ( +/obj/effect/spawner/random/snacks, +/obj/effect/spawner/random/snacks, +/obj/effect/spawner/random/snacks, +/obj/structure/table/wood, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"jZ" = ( +/obj/item/reagent_containers/glass/bottle/nutrient/empty, +/obj/item/reagent_containers/glass/bucket, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"kY" = ( +/obj/effect/simon_says, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"ml" = ( +/obj/structure/table/abductor, +/obj/item/reagent_containers/glass/bucket, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"mQ" = ( +/turf/space, +/area/space) +"ok" = ( +/obj/effect/spawner/random/food_or_drink, +/obj/effect/spawner/random/food_or_drink, +/obj/structure/table/wood, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"pX" = ( +/obj/item/assembly/igniter, +/obj/effect/decal/cleanable/dirt, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"uD" = ( +/obj/structure/bed/abductor, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"wy" = ( +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"zB" = ( +/obj/structure/closet, +/obj/item/clothing/under/rank/cargo/expedition, +/obj/item/clothing/under/rank/cargo/expedition, +/obj/item/soap, +/obj/item/reagent_containers/glass/bucket, +/obj/item/reagent_containers/glass/bucket, +/obj/item/reagent_containers/glass/bucket, +/obj/item/gps{ + gpstag = "ALN0"; + tracking = 1 + }, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"Bl" = ( +/obj/structure/reagent_dispensers/watertank, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"CK" = ( +/turf/simulated/wall/indestructible/abductor, +/area/ruin/powered/alien_cache_site) +"Do" = ( +/obj/machinery/shower/directional/east, +/obj/structure/curtain/open/shower, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"Ey" = ( +/obj/machinery/alien_cache, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site/cache_room) +"Ha" = ( +/turf/simulated/wall/indestructible/abductor, +/area/ruin/powered/alien_cache_site/cache_room) +"JC" = ( +/obj/structure/curtain/open, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"LK" = ( +/obj/effect/mapping_helpers/turfs/damage, +/turf/simulated/wall/indestructible/abductor{ + desc = "Effectively impervious to conventional methods of destruction. It looks like someone tried to break it, but failed" + }, +/area/ruin/powered/alien_cache_site) +"TW" = ( +/turf/simulated/wall/mineral/wood, +/area/ruin/powered/alien_cache_site) +"Yg" = ( +/obj/machinery/door/airlock/abductor, +/obj/structure/fans/tiny/invisible, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) +"YA" = ( +/obj/effect/spawner/random/engineering/toolbox, +/obj/structure/table/abductor, +/turf/simulated/floor/plating/abductor, +/area/ruin/powered/alien_cache_site) + +(1,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(2,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(3,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(4,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(5,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(6,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(7,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(8,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(9,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(10,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +CK +CK +CK +CK +CK +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(11,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +CK +wy +wy +hj +ok +pX +dV +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(12,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +gf +wy +wy +wy +wy +wy +wy +ml +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(13,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +Ha +Ha +Ha +Ha +CK +YA +wy +wy +wy +wy +wy +wy +en +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(14,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +Ha +Ha +et +et +et +Ha +CK +wy +wy +wy +wy +wy +wy +wy +wy +wy +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(15,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +et +et +et +et +et +et +LK +eF +wy +wy +wy +wy +wy +wy +wy +wy +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(16,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +Ey +et +et +et +et +et +fA +wy +wy +wy +wy +wy +wy +wy +kY +wy +Yg +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(17,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +et +et +et +et +et +et +CK +wy +wy +wy +wy +wy +wy +wy +wy +jZ +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(18,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +Ha +Ha +et +et +et +Ha +CK +wy +wy +wy +wy +wy +wy +wy +ec +Bl +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(19,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +Ha +Ha +Ha +Ha +Ha +CK +TW +TW +JC +TW +JC +TW +TW +TW +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(20,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +zB +wy +wy +TW +wy +wy +wy +Do +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(21,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +CK +uD +uD +TW +uD +uD +cK +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(22,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +CK +CK +CK +CK +CK +CK +CK +CK +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(23,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(24,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(25,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(26,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(27,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(28,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(29,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(30,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} +(31,1,1) = {" +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +mQ +"} diff --git a/code/__DEFINES/_math.dm b/code/__DEFINES/_math.dm index 9e6a3e8be9a..4a6a54d626a 100644 --- a/code/__DEFINES/_math.dm +++ b/code/__DEFINES/_math.dm @@ -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) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 414926c99ca..d00977bfb74 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -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) diff --git a/code/datums/mapping/templates/space_map_templates.dm b/code/datums/mapping/templates/space_map_templates.dm index e81ac14e3d6..4c32628448d 100644 --- a/code/datums/mapping/templates/space_map_templates.dm +++ b/code/datums/mapping/templates/space_map_templates.dm @@ -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 diff --git a/code/game/area/areas/ruins/alien_cache_site_areas.dm b/code/game/area/areas/ruins/alien_cache_site_areas.dm new file mode 100644 index 00000000000..d1a492d3bbc --- /dev/null +++ b/code/game/area/areas/ruins/alien_cache_site_areas.dm @@ -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" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index d435c5bfc1e..893d04bb7af 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -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 diff --git a/code/game/objects/effects/spawners/random/alien_cache_random_spawners.dm b/code/game/objects/effects/spawners/random/alien_cache_random_spawners.dm new file mode 100644 index 00000000000..b5e216b2aa5 --- /dev/null +++ b/code/game/objects/effects/spawners/random/alien_cache_random_spawners.dm @@ -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 diff --git a/code/goonstation/modules/power/power_transmission_laser/transmission_laser.dm b/code/goonstation/modules/power/power_transmission_laser/transmission_laser.dm index 58a6a32f051..ec2eeda3ab3 100644 --- a/code/goonstation/modules/power/power_transmission_laser/transmission_laser.dm +++ b/code/goonstation/modules/power/power_transmission_laser/transmission_laser.dm @@ -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("\The [src] is reduced to dust by the beam!") - 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) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 19c95eec056..475134099ef 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -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) . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index eae50b6984e..dc9aa895a7a 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -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("\The [src] is reduced to dust by the beam!") + 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)) diff --git a/code/modules/power/alien_cache.dm b/code/modules/power/alien_cache.dm new file mode 100644 index 00000000000..6e302053b06 --- /dev/null +++ b/code/modules/power/alien_cache.dm @@ -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) + . += "The panel is open, revealing unterminated internal wiring." + else + . += "There's a loose panel on the front that could be pried open with a screwdriver" + +/obj/machinery/alien_cache/display_parts(user) + return list("ERROR: UNIDENTIFIED MACHINE DESIGN") + +/// 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, "[src] already has a terminal!") + return ITEM_INTERACT_COMPLETE + + if(!panel_open) // Checks to see if the panel is closed + to_chat(user, "You must open the panel first!") + return ITEM_INTERACT_COMPLETE + + var/turf/T = get_turf(user) + if(T.intact) // Checks to see if floor plating is present + to_chat(user, "You must first remove the floor plating!") + return ITEM_INTERACT_COMPLETE + + var/obj/item/stack/cable_coil/C = used + if(C.get_amount() < 10) + to_chat(user, "You need more wires.") + return ITEM_INTERACT_COMPLETE + + if(user.loc == loc) + to_chat(user, "You must not be on the same tile as [src].") + 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, "You can't build a terminal on space.") + return ITEM_INTERACT_COMPLETE + + else if(istype(temporary_location)) + if(temporary_location.intact) + to_chat(user, "You must remove the floor plating first.") + return ITEM_INTERACT_COMPLETE + + to_chat(user, "You try to connect the cable to [src]") + 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(\ + "[user.name] adds the cables and connects the power terminal.",\ + "You add the cables and connect the power terminal.") + + 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("Total energy: [DisplayJoules(total_energy)]
Levels opened: [level_reached]
")) + +/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, "You must first expose the power terminal!") + return + if(!panel_open) + to_chat(user, "You must first open the panel") + to_chat(user, "You begin to dismantle the power terminal...") + 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(\ + "[user.name] cuts the cables and dismantles the power terminal.",\ + "You cut the cables and dismantle the power terminal.") + 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 diff --git a/code/modules/power/laser_terminal.dm b/code/modules/power/laser_terminal.dm new file mode 100644 index 00000000000..c1ad00c2b13 --- /dev/null +++ b/code/modules/power/laser_terminal.dm @@ -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, "Close the maintenance panel first!") + 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("Unit ID: [id]
Input Power: [DisplayPower(total_input)]
Output Power: [DisplayPower(total_input * conversion_efficiency)]
")) diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 9957eba1f62..e548e7d3038 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -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" diff --git a/code/modules/ruins/objects_and_mobs/secure_alien_airlock.dm b/code/modules/ruins/objects_and_mobs/secure_alien_airlock.dm new file mode 100644 index 00000000000..4b820846491 --- /dev/null +++ b/code/modules/ruins/objects_and_mobs/secure_alien_airlock.dm @@ -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 diff --git a/code/modules/ruins/objects_and_mobs/simon_says.dm b/code/modules/ruins/objects_and_mobs/simon_says.dm new file mode 100644 index 00000000000..18e535adef2 --- /dev/null +++ b/code/modules/ruins/objects_and_mobs/simon_says.dm @@ -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) diff --git a/config/example/config.toml b/config/example/config.toml index 86230f38660..b2fe19e30eb 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -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", diff --git a/icons/effects/simon_says_32x32.dmi b/icons/effects/simon_says_32x32.dmi new file mode 100644 index 00000000000..a76856b5baf Binary files /dev/null and b/icons/effects/simon_says_32x32.dmi differ diff --git a/icons/obj/machines/alien_cache.dmi b/icons/obj/machines/alien_cache.dmi new file mode 100644 index 00000000000..d3649e496e3 Binary files /dev/null and b/icons/obj/machines/alien_cache.dmi differ diff --git a/icons/obj/machines/ptl_terminal.dmi b/icons/obj/machines/ptl_terminal.dmi new file mode 100644 index 00000000000..1774edd1104 Binary files /dev/null and b/icons/obj/machines/ptl_terminal.dmi differ diff --git a/paradise.dme b/paradise.dme index d8d715bca1c..5d605c95be8 100644 --- a/paradise.dme +++ b/paradise.dme @@ -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" diff --git a/sound/effects/simon_says_1.ogg b/sound/effects/simon_says_1.ogg new file mode 100644 index 00000000000..a5fce7c329a Binary files /dev/null and b/sound/effects/simon_says_1.ogg differ diff --git a/sound/effects/simon_says_2.ogg b/sound/effects/simon_says_2.ogg new file mode 100644 index 00000000000..b223084c912 Binary files /dev/null and b/sound/effects/simon_says_2.ogg differ diff --git a/sound/effects/simon_says_3.ogg b/sound/effects/simon_says_3.ogg new file mode 100644 index 00000000000..aa54566cb4d Binary files /dev/null and b/sound/effects/simon_says_3.ogg differ diff --git a/sound/effects/simon_says_4.ogg b/sound/effects/simon_says_4.ogg new file mode 100644 index 00000000000..bff11a3b6c2 Binary files /dev/null and b/sound/effects/simon_says_4.ogg differ diff --git a/sound/effects/simon_says_5.ogg b/sound/effects/simon_says_5.ogg new file mode 100644 index 00000000000..1ce4b870ce3 Binary files /dev/null and b/sound/effects/simon_says_5.ogg differ diff --git a/sound/effects/simon_says_6.ogg b/sound/effects/simon_says_6.ogg new file mode 100644 index 00000000000..9c482b8dd0b Binary files /dev/null and b/sound/effects/simon_says_6.ogg differ diff --git a/sound/effects/simon_says_success.ogg b/sound/effects/simon_says_success.ogg new file mode 100644 index 00000000000..a50a32c0c3e Binary files /dev/null and b/sound/effects/simon_says_success.ogg differ diff --git a/sound/effects/simon_says_wrong.ogg b/sound/effects/simon_says_wrong.ogg new file mode 100644 index 00000000000..e3943888dc8 Binary files /dev/null and b/sound/effects/simon_says_wrong.ogg differ