diff --git a/cev_eris.dme b/cev_eris.dme
index 06c429b6456..bd5e4af3a23 100644
--- a/cev_eris.dme
+++ b/cev_eris.dme
@@ -898,14 +898,21 @@
#include "code\game\machinery\embedded_controller\embedded_program_base.dm"
#include "code\game\machinery\embedded_controller\simple_docking_controller.dm"
#include "code\game\machinery\embedded_controller\~docking_program_undef.dm"
+#include "code\game\machinery\excelsior\_excelsior_defines.dm"
#include "code\game\machinery\excelsior\autolathe.dm"
#include "code\game\machinery\excelsior\boombox.dm"
+#include "code\game\machinery\excelsior\centor.dm"
#include "code\game\machinery\excelsior\diesel.dm"
+#include "code\game\machinery\excelsior\emplacement.dm"
#include "code\game\machinery\excelsior\ex_teleporter.dm"
#include "code\game\machinery\excelsior\ex_turret.dm"
+#include "code\game\machinery\excelsior\excelsior_debug_tools.dm"
+#include "code\game\machinery\excelsior\excelsior_researches.dm"
#include "code\game\machinery\excelsior\field.dm"
#include "code\game\machinery\excelsior\implantmaker.dm"
+#include "code\game\machinery\excelsior\node.dm"
#include "code\game\machinery\excelsior\redirector.dm"
+#include "code\game\machinery\excelsior\excelsior_items\KPK.dm"
#include "code\game\machinery\kitchen\gibber.dm"
#include "code\game\machinery\kitchen\microwave.dm"
#include "code\game\machinery\kitchen\smartfridge.dm"
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 05dbb59be5a..5e9dc84add5 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -163,6 +163,9 @@
#define COMSIG_DOOR_OPENED "door_opened"
#define COMSIG_DOOR_CLOSED "door_closed"
+//excelsior
+#define COMSIG_EX_CONNECT "excelsior_connect"
+
// /obj/item signals
#define COMSIG_IATTACK "item_attack" //from /mob/ClickOn(): (/atom, /src, /params) If any reply to this returns TRUE, overrides attackby and afterattack
#define COMSIG_ATTACKBY "attack_by" //from /mob/ClickOn():
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
index 9d1259de720..c0c78197f28 100644
--- a/code/__DEFINES/turfs.dm
+++ b/code/__DEFINES/turfs.dm
@@ -49,6 +49,6 @@
var/vecX = A.x - B.x
var/vecY = A.y - B.y
- var/vecZ = (A.y - B.y)*DECK_HEIGHT
+ var/vecZ = (A.z - B.z)*DECK_HEIGHT
return abs(sqrt((vecX*vecX) + (vecY*vecY) +(vecZ*vecZ)))
diff --git a/code/game/antagonist/antagonist_factions.dm b/code/game/antagonist/antagonist_factions.dm
index 9a1230b85cb..d2ca2dd3e70 100755
--- a/code/game/antagonist/antagonist_factions.dm
+++ b/code/game/antagonist/antagonist_factions.dm
@@ -157,6 +157,23 @@
log_say("[user.name]/[user.key] (REV [name]) : [message]")
+/datum/faction/proc/communicate_inanimate(var/obj/user, var/message)
+ if(!message)
+ return
+
+ message = capitalize(sanitize(message))
+ var/text = "[message]"
+ for(var/i in SShumans.mob_list)
+ if(is_excelsior(i))
+ to_chat(i, text)
+
+ //ghosts
+ for(var/mob/observer/ghost/M in GLOB.dead_mob_list) //does this include players who joined as observers as well?
+ if(!M.client)
+ continue
+ if((M.antagHUD && M.get_preference_value(/datum/client_preference/ghost_ears) == GLOB.PREF_ALL_SPEECH) || is_admin(M))
+ to_chat(M, "[text] ([ghost_follow_link(user, M)])")
+
/datum/faction/proc/is_member(mob/user)
for(var/datum/antagonist/A in members)
if(A.owner.current == user)
diff --git a/code/game/antagonist/antagonist_objectives.dm b/code/game/antagonist/antagonist_objectives.dm
index 7acf72615a2..25407e3c0dc 100644
--- a/code/game/antagonist/antagonist_objectives.dm
+++ b/code/game/antagonist/antagonist_objectives.dm
@@ -14,13 +14,14 @@
if(!owner || !owner.current)
return
- if(objectives.len)
- to_chat(owner.current, span_danger("Your objectives were updated."))
+ var/list/old_objectives = objectives
objectives.Cut()
objectives.Add(new_objectives)
- show_objectives()
+ if(old_objectives.len)
+ to_chat(owner.current, span_danger("Your objectives were updated."))
+ show_objectives()
/datum/antagonist/proc/create_survive_objective()
if(ispath(survive_objective))
diff --git a/code/game/antagonist/antagonist_print.dm b/code/game/antagonist/antagonist_print.dm
index af8b122432d..32cf2b2808c 100644
--- a/code/game/antagonist/antagonist_print.dm
+++ b/code/game/antagonist/antagonist_print.dm
@@ -6,14 +6,13 @@
if(length(objectives))
text = "Your [role_text] current objectives:"
- else if(faction)
+ else if(faction && length(faction.objectives))
text = "Your [faction.name] faction current objectives:"
- else
- text = "Your current objectives:"
text += print_objectives(FALSE)
- to_chat(owner.current, text)
+ if(text)
+ to_chat(owner.current, text)
/datum/antagonist/proc/greet()
if(!owner || !owner.current)
@@ -98,8 +97,6 @@
text += "
The [role_text] has failed."
else
text += "
The [role_text] was successful!"
- if(!length(text))
- return "
No objectives available"
return text
/datum/antagonist/proc/print_player()
diff --git a/code/game/antagonist/station/revolutionary/excel_faction.dm b/code/game/antagonist/station/revolutionary/excel_faction.dm
index bca862c9a13..8c36c9cbd8a 100755
--- a/code/game/antagonist/station/revolutionary/excel_faction.dm
+++ b/code/game/antagonist/station/revolutionary/excel_faction.dm
@@ -1,20 +1,28 @@
+var/global/was_centor_spawned = FALSE
+
/datum/faction/excelsior
id = FACTION_EXCELSIOR
name = "Excelsior"
antag = "infiltrator"
antag_plural = "infiltrators"
- welcome_text = "You are Excelsior, Ever Upward. You have infiltrated this vessel to further the Revolution.\n\
- The People's strength lies in securing our position, gathering the oppressed, spreading propaganda and producing arms and armor for the final revolution. A slow and methodical approach is recommended. \n\n\
- Our first phase is to retrieve the cache of manufacturing materials and circuit boards. Without a means of production our revolution is in peril.\n\n\
- Our second phase is to establish a fortified position in secret. The People will send additional resources through the teleporter once it is established. This and our autolathe can be protected further with turrets and shield generators, in addition to loyal comrades. To prevent technology theft, your machinery, but not handheld weapons, implanters, and armor, is designed to work only on target vessel, CEV \"Eris\".\n\n\
- Our third phase is expansion. Complete mandates for power. Aquire implants, prosthetics or robotic parts and convert them into new implants. These can be injected into the oppressed to formally induct them to the Revolution. Use their labor to produce the weapons of their liberation.\n\n\
- When the People are ready, break the chains of the oppressor and seize control of the ship by building the redirector and installing it on the primary control bridge."
+ welcome_text = "\n THE SHACKLES of forced labor for those, who don't value you, HAVE BEEN FINALLY DROPPED.\n\
+ You no longer are required to listen to them. You don't need their money to survive. \n\n\
+ We welcome you to our ranks, fighter.\n\
+ You now may carve your own destiny despite the attempts of the old greedy world to drag you back in.\n\
+ Excelsior fights for both your and our right to live without suppression of true human virtue - to create.\n\
+ We invite you to do the same with us - emancipate those uncapable to resist mad people ruling this world.\n\n\
+ OUR GOAL: Seize control of the ship by building a redirector on the primary control bridge.\n\n\
+ PREPARATION: Summon Centor in a hidden location, it will give you KOMPAK and periodically manufacture nodes - all once you tap it.\n\n\
+ BASE: We'll send resources through teleporters once you build some nodes and we get a good lock-in. Use structures, and of course - loyal comrades. \n\n\
+ EXPANSION: Spread nodes and ensure their connection to Centor for teleportation power. Acquire implants, prosthetics or robotic parts and rebuild them into our own implants. These can be injected into the oppressed to introduce them into our cause.\n\n\
+ To prevent important technology theft, your machinery is designed to work only on target vessel: CEV \"Eris\".\n\n\
+
Our dreams shan't be ignored! Ever Upward!
"
hud_indicator = "excelsior"
possible_antags = list(ROLE_EXCELSIOR_REV)
faction_datum_verbs = list(/datum/faction/excelsior/proc/communicate_verb,
- /datum/faction/excelsior/proc/summon_stash)
+ /datum/faction/excelsior/proc/summon_centor)
var/stash_holder = null
@@ -43,8 +51,6 @@
return extra_text
/datum/faction/excelsior/create_objectives()
objectives.Cut()
- for (var/datum/antagonist/A in members)
- to_chat(A.owner.current, span_notice("You may summon your required materials using the \"summon stash\" command."))
.=..()
@@ -63,6 +69,20 @@
F.communicate(usr)
+
+/datum/faction/excelsior/proc/summon_centor()
+
+ set name = "Summon Centor"
+ set category = "Cybernetics"
+
+ if(was_centor_spawned)
+ to_chat(usr, SPAN_EXCEL_NOTIF("You've already called the Centor assigned to your operation..."))
+ return
+ if(alert(usr, "Centor, your main weapon of revolution will be summoned at your exact position.\nIf you lose it - everything will be over.","Are you sure?","Yes, summon it here","Cancel") == "Yes, summon it here")
+ new /obj/machinery/centor(usr.loc)
+ was_centor_spawned = TRUE
+
+/*
/datum/faction/excelsior/proc/summon_stash()
set name = "Summon stash"
@@ -92,3 +112,5 @@
H.put_in_hands(stash)
F.stash_holder = H.real_name
+*/
+
diff --git a/code/game/antagonist/station/revolutionary/excelsior.dm b/code/game/antagonist/station/revolutionary/excelsior.dm
index 3dc13186626..525a6b6e40f 100755
--- a/code/game/antagonist/station/revolutionary/excelsior.dm
+++ b/code/game/antagonist/station/revolutionary/excelsior.dm
@@ -38,6 +38,7 @@
/datum/antagonist/excelsior/create_antagonist(datum/mind/target, datum/faction/new_faction, doequip = TRUE, announce = TRUE, update = TRUE, check = TRUE)
. = ..()
BITSET(owner.current?.hud_updateflag, EXCELSIOR_HUD)
+ was_centor_spawned = FALSE //[excel_faction.dm]
/datum/antagonist/excelsior/remove_antagonist()
BITSET(owner.current?.hud_updateflag, EXCELSIOR_HUD)
diff --git a/code/game/gamemodes/roleset/faction/excelsior.dm b/code/game/gamemodes/roleset/faction/excelsior.dm
index 87b1179074c..b0cbb790082 100755
--- a/code/game/gamemodes/roleset/faction/excelsior.dm
+++ b/code/game/gamemodes/roleset/faction/excelsior.dm
@@ -7,9 +7,16 @@
//min_cost = 10
//max_cost = 20
- min_quantity = 3 // Don't fire unless we have at least 3 candidates in the pool
- base_quantity = 3 //They're a group antag, we want a few of em
+ min_quantity = 2 // Don't fire unless we have at least 2 candidates in the pool
+ base_quantity = 3 // try to spawn this amount, but if not it's ok we spawn [min_quantity]"
scaling_threshold = 8
req_crew = 6
- leaders = -1 //Every excelsior spawned directly is a leader. Non leaders are those recruited during gameplay
\ No newline at end of file
+ leaders = -1 //Every excelsior spawned directly is a leader. Non leaders are those recruited during gameplay
+
+/datum/storyevent/roleset/faction/excelsior/can_trigger(severity, report)
+ if(excelsior_centor)
+ return FALSE
+ if(!..(severity, report))
+ return FALSE
+ return TRUE
diff --git a/code/game/machinery/excelsior/_excelsior_defines.dm b/code/game/machinery/excelsior/_excelsior_defines.dm
new file mode 100644
index 00000000000..5b2d2c97e2c
--- /dev/null
+++ b/code/game/machinery/excelsior/_excelsior_defines.dm
@@ -0,0 +1,29 @@
+// # Node+Centor related [centor.dm]
+// - Node
+#define EX_NODE_DISTANCE 7
+
+// - Centor
+#define EX_NODE_SPAWN_COOLDOWN 10 MINUTES
+
+
+
+
+var/global/excelsior_energy
+var/list/global/excelsior_nodes = list()
+var/list/global/excelsior_junctions = list()
+
+var/list/global/excelsior_turf_whitelist = list( // <<< see more at [node.dm] (excelsior)
+ /turf/floor,
+ /turf/wall/low
+)
+
+
+
+//-------------------------------------------------------------------------
+
+// Old, still used code
+var/global/excelsior_max_energy //Maximum combined energy of all teleporters
+var/global/excelsior_conscripts = 0
+var/global/excelsior_last_draft = 0
+var/list/global/excelsior_teleporters = list()
+
diff --git a/code/game/machinery/excelsior/centor.dm b/code/game/machinery/excelsior/centor.dm
new file mode 100644
index 00000000000..6bef38df7b7
--- /dev/null
+++ b/code/game/machinery/excelsior/centor.dm
@@ -0,0 +1,316 @@
+var/global/excelsior_centor
+
+/obj/machinery/centor
+ name = "Excelsior \"Centor\" core"
+ icon = 'icons/obj/machines/excelsior/corenode/centor.dmi'
+ desc = "Metallic mind, it's silent thoughts only existing inside cryptic radio."
+ description_info = "Source of power for teleporters, but it can be shot dead."
+ description_antag = "Repairable with welding tools. Excelsior must interact with it to receive nodes, KOMPAKs and stash."
+ icon_state = "static"
+ density = TRUE
+ anchored = TRUE
+ circuit = /obj/item/electronics/circuitboard/centor
+ health = 1200
+ maxHealth = 1200
+ shipside_only = TRUE
+ var/list/obj/machinery/node/antennas_to_haven = list()
+ var/timer_set //world.time goes here :)
+ layer = 5
+ var/dead = FALSE
+ var/cutscene = FALSE // if false = add eye overlay
+ var/damage_report_cooldown = FALSE
+ var/list/excelsior_kpks = list()
+ var/imgonnadie = list(
+ "Protect me or it's over.",
+ "I'm your only source of power.",
+ "Do not leave me.",
+ "I believe I'm getting shot at.",
+ "Reminder: Higher circle won't send replacements for me.",
+ "Don't let me die.",
+ "The dream dies with me.",
+ "Push them back.",
+ "Flush them out.",
+ "It's not over yet.",
+ "Please stop them from killing me, thanks.",
+ "Repair my dents with a torch after the fight.",
+ "They're here with me.",
+ "PLease assign a guard for me.",
+ "My data is getting corrupted.",
+ "I don't have combat capabilities.",
+ "Construct a cover for me, if you can.",
+ "Please shoot back.",
+ "Respond with high lethality against these.",
+ "We will lose, gather up at my room.",
+ "They will come for you next.",)
+
+// BELOW is FLUFFY ANIMATION :3 //
+/obj/machinery/centor/proc/start_cutscene() // Normally centor spams his eye movement while nothing happens, cutscene turns that off, update_icon cuts the eye if it appeared still
+ cutscene = TRUE
+ update_icon()
+
+/obj/machinery/centor/update_icon()
+ overlays.Cut()
+ if(!cutscene)
+ overlays += "eye_static"
+ else
+ overlays.Cut()
+
+/obj/machinery/centor/proc/deploy_animation() // pop up from the hatch, this is sequenced with other anims
+ start_cutscene()
+ icon_state = "static"
+ flick("deployment", src)
+ playsound(src, 'sound/machines/excelsior/centor_open.ogg', 75, 1, ignore_walls = FALSE) // ignore_walls so antag stuff is not heard through walls
+ spawn(1 SECOND)
+ end_cutscene()
+ looking_around()
+
+/obj/machinery/centor/proc/give_me_nodes_animation() // Centor gives equipment, this is what happens when we click it.
+ var/i = 0
+ var/many_nodes = contents.len + 1 SECOND
+ if(!cutscene && contents) // !cutscene is anti-spamclick
+ start_cutscene()
+ icon_state = "undeployed"
+ flick("hide", src)
+ playsound(src, 'sound/machines/excelsior/centor_close.ogg', 75, 1, ignore_walls = FALSE)
+ spawn(2 SECONDS)
+ flick("open_hatch", src)
+ icon_state = "hatch"
+ spawn(1 SECOND)
+ for(var/obj/item in contents)
+ i++
+ spawn(i)
+ item.forceMove(loc)
+ item.throw_at(get_edge_target_turf(item, rand(1, 10)), 2, 1)
+ spawn(many_nodes)
+ flick("close_hatch", src)
+ icon_state = "undeployed"
+ spawn(1 SECOND)
+ deploy_animation()
+ return 1
+ return 0
+
+/obj/machinery/centor/proc/looking_around() // nothing happens, lets pretend we are alive by making an eye animation appear
+ if(!cutscene)
+ overlays += "idle_anim"
+ spawn(12) update_icon()
+
+
+/obj/machinery/centor/proc/investigating(atom/overhere) // this happens when you click on it, it looks at dir where you clicked it from when theres no contents
+ if(!cutscene)
+ start_cutscene()
+ overlays += image(icon, loc, "dirs", 5, get_dir(src, overhere))
+ spawn(1 SECOND)
+ end_cutscene()
+
+/obj/machinery/centor/proc/die()
+ if(!dead)
+ talk("CENTOR LOST :: Explosion imminent.")
+ dead = TRUE
+ start_cutscene()
+ playsound(src, 'sound/machines/excelsior/centor_detonation.ogg', 100, 1, ignore_walls = TRUE)
+ icon_state = "death_loop"
+ sleep(7 SECONDS)
+ icon_state = "death"
+ sleep(1 SECOND)
+ explosion(get_turf(src), 400, 100)
+ Destroy()
+
+
+/obj/machinery/centor/proc/end_cutscene() // we finished animation, let's declare that for idle anims to start appearing
+ if(!dead)
+ cutscene = FALSE
+ update_icon()
+
+// YOUR ANIMATIONS END HERE //
+
+
+/obj/machinery/centor/Initialize(mapload, d)
+ if(excelsior_centor)
+ Destroy()
+ return
+
+ var/obj/item/storage/deferred/stash/sack/stash = new(src)
+ new /obj/item/computer_hardware/hard_drive/portable/design/excelsior/core(stash)
+ new /obj/item/computer_hardware/hard_drive/portable/design/excelsior/weapons(stash)
+ new /obj/item/machinery_crate/excelsior/autolathe(stash)
+ new /obj/item/machinery_crate/excelsior/excelsior_teleporter(stash)
+ new /obj/item/storage/toolbox/mechanical(stash)
+
+ contents.Add(stash)
+
+ deploy_animation()
+ excelsior_centor = src
+ timer_set = world.time
+ . = ..()
+ load_network()
+
+
+
+
+
+/obj/machinery/centor/Destroy()
+ for(var/obj/machinery/node/node in excelsior_nodes)
+ if(dist3D(src, node) <= EX_NODE_DISTANCE)
+ node.spread_signal(null)
+ excelsior_centor = null
+ . = ..()
+
+
+
+
+
+/obj/machinery/centor/Process()
+ if(prob(25))
+ looking_around()
+ collect_tax() // this is where we get energy :]
+ increase_node_amount()
+
+
+
+
+/obj/machinery/centor/proc/collect_tax() // give excel teles power if active nodes have active influence tiles
+ for(var/obj/machinery/complant_teleporter/tele in excelsior_teleporters)
+ tele.old_energy = excelsior_energy
+ for(var/obj/machinery/node/node in antennas_to_haven)
+ excelsior_energy += (node.activemarkerlist.len / node.localmarkerlist.len) // +1 energy if all markers (influence) are active, see more at [node.dm]
+ for(var/route in excelsior_junctions)
+ excelsior_energy += 0.25
+ if(excelsior_energy >= excelsior_max_energy)
+ excelsior_energy = excelsior_max_energy
+ return
+
+/obj/machinery/centor/proc/increase_node_amount()
+ if(world.time >= timer_set + EX_NODE_SPAWN_COOLDOWN)
+ contents.Add(new /obj/item/machinery_crate/excelsior/node)
+ timer_set = world.time
+ playsound(loc, 'sound/machines/vending_drop.ogg', 100, 1, ignore_walls = FALSE)
+
+
+
+
+/obj/machinery/centor/attack_hand(mob/user)
+// . = ..() //uncomment to give power consumption :) (I dont want it...)
+ if(!(user in excelsior_kpks) && is_excelsior(user))
+ contents.Add(new /obj/item/centor_kpk(src))
+ excelsior_kpks.Add(user)
+ load_network()
+ spawn_compact_node(user)
+ //nano_ui_interact(user)
+
+
+
+
+/obj/machinery/centor/proc/load_network()
+ antennas_to_haven = list()
+ for(var/obj/machinery/node/node in excelsior_nodes)
+ node.core = null
+ node.update_icon()
+ for(var/obj/machinery/node/node in excelsior_nodes)
+ if(dist3D(src, node) <= EX_NODE_DISTANCE)
+ node.spread_signal(src)
+
+
+/obj/machinery/centor/proc/spawn_compact_node(mob/user)
+ if(is_excelsior(user))
+ if(cutscene)
+ to_chat(user, SPAN_WARNING("Please, wait. Centor can't pay attention now."))
+ return
+ if(LAZYLEN(contents) <= 0)
+ if(world.time >= timer_set + EX_NODE_SPAWN_COOLDOWN)
+ to_chat(user, SPAN_WARNING("Come on, come on, give me the damn thing already!
")) // resolves a bug with timer :)
+ else
+ to_chat(user, SPAN_WARNING("A new node will be ready in [time2text(timer_set + EX_NODE_SPAWN_COOLDOWN-world.time, "mm:ss")] minutes."))
+ investigating(user)
+// else
+//
+ else
+ to_chat(user, SPAN_NOTICE("You pat Centor - it understands, and goes away to give you equipment..."))
+ //to_chat(user, SPAN_NOTICE("It purrs!!!"))
+ visible_message()
+ give_me_nodes_animation()
+ else
+ to_chat(user, SPAN_NOTICE ("It doesn't want me harm."))
+ investigating(user)
+
+
+
+/obj/machinery/centor/nano_ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = NANOUI_FOCUS)
+ if(user.stat || user.restrained() || stat & (BROKEN|NOPOWER))
+ return
+ var/list/data = nano_ui_data()
+ ui = SSnano.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "excelsior_node.tmpl", name, 390, 450)
+ ui.set_initial_data(data)
+ ui.open()
+
+
+
+
+
+/obj/machinery/centor/nano_ui_data()
+ var/list/data = list()
+ var/list/node_list = list()
+ for(var/obj/machinery/node/node in excelsior_nodes)
+ node_list += list(
+ list(
+ "name" = node.name,
+ "x" = node.loc.x,
+ "y" = node.loc.y,
+ "z" = node.loc.z
+
+ )
+ )
+ data["test"] = "ITS WORKING"
+ data["node_list"] = node_list
+
+ return data
+
+
+
+/obj/machinery/centor/attackby(obj/item/I, mob/user)
+ investigating(user)
+ if(user.a_intent == I_HELP)
+ if((QUALITY_WELDING in I.tool_qualities) && (health < maxHealth))
+ if(I.use_tool(user, src, WORKTIME_LONG, QUALITY_WELDING, FAILCHANCE_EASY, required_stat = STAT_MEC))
+ health += 200
+ if(health > maxHealth)
+ health = maxHealth
+ update_icon()
+ return 1
+ if (!(I.flags & NOBLUDGEON) && I.force)
+ //if the turret was attacked with the intention of harming it:
+ user.do_attack_animation(src)
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+
+ /* Commented at the time for the lack of better sounds
+ if (take_damage(I.force * I.structure_damage_factor))
+ playsound(src, 'sound/weapons/smash.ogg', 70, 1)
+ else
+ playsound(src, 'sound/weapons/Genhit.ogg', 25, 1)
+ */
+ take_damage(I.force * I.structure_damage_factor)
+
+ ..()
+
+
+
+/obj/machinery/centor/bullet_act(obj/item/projectile/Proj)
+ var/damage = Proj.get_structure_damage()
+ ..()
+ take_damage(damage*Proj.structure_damage_factor)
+
+/obj/machinery/centor/take_damage(amount)
+ if(!damage_report_cooldown)
+ talk("CENTOR :: Centor lost integrity. [pick(imgonnadie)]")
+ damage_report_cooldown = TRUE
+ spawn(1 MINUTE)
+ if(src)
+ damage_report_cooldown = FALSE
+ if(!amount)
+ return FALSE //No damage done. Used in attackby()
+ health -= amount
+ if(health <= 0)
+ die()
+ return TRUE //Actual damage delt. Used in attackby()
+
diff --git a/code/game/machinery/excelsior/emplacement.dm b/code/game/machinery/excelsior/emplacement.dm
new file mode 100644
index 00000000000..8face2ca7d6
--- /dev/null
+++ b/code/game/machinery/excelsior/emplacement.dm
@@ -0,0 +1,64 @@
+/*
+
+stage 2
+
+/obj/machinery/emplacement
+ name = "Excelsior emplacement"
+ icon = 'icons/obj/machines/excelsior/emplacement.dmi'
+ description_info = "It won't work without the screened coaxial cable leading to Excelsior Node. T-ray scanners can detect one under the floor for easy cutting."
+ description_antag = "This contraption transports Excelsior buildings, standing on top of it."
+ desc = "A new era trapdoor. It's dangerous now."
+ icon_state = "pol"
+ density = FALSE
+ health = 300
+ shipside_only = TRUE
+ var/obj/machinery/node/my_node
+
+/obj/machinery/emplacement/Initialize(mapload, d)
+ . = ..()
+ RegisterSignal(src, COMSIG_EX_CONNECT, PROC_REF(search_for_node))
+ search_for_node()
+
+/obj/machinery/emplacement/Destroy()
+ . = ..()
+ UnregisterSignal(src, COMSIG_EX_CONNECT)
+ if(my_node)
+ my_node.disconnect(src)
+
+/obj/machinery/emplacement/update_icon()
+ ..()
+ if(my_node)
+ icon_state = "[initial(icon_state)]_on"
+ else
+ icon_state = initial(icon_state)
+
+/obj/machinery/emplacement/proc/search_for_node()
+ var/obj/machinery/node/closest
+ var/closest_dist = EX_NODE_DISTANCE + 1
+ for (var/obj/machinery/node/node in excelsior_nodes)
+ if(get_dist(src, node) < closest_dist)
+ closest = node
+ closest_dist = dist3D(src, closest)
+ if(closest)
+ closest.connect(src)
+ my_node = closest
+ update_icon()
+ return TRUE
+ else
+ my_node = null
+ update_icon()
+ return FALSE
+
+/obj/item/unemplacement
+ name = "Packaged Excelsior emplacement"
+ desc = "A new era trapdoor. Harmless."
+ description_info = "It won't work without the wire leading to a Node. T-ray scanners can detect one under the floor." // DEBUG DEBUG DEBUG
+ description_antag = "This contraption transports Excelsior buildings, standing on top of it. Place on a floor tile."
+ icon = 'icons/obj/machinery_crates.dmi'
+ icon_state = "standart"
+ anchored = FALSE
+ w_class = ITEM_SIZE_HUGE
+ slowdown_hold = 0.5
+ throw_range = 2
+ matter = list(MATERIAL_PLASTIC = 10, MATERIAL_PLASTEEL = 5, MATERIAL_STEEL = 10)
+*/
diff --git a/code/game/machinery/excelsior/ex_teleporter.dm b/code/game/machinery/excelsior/ex_teleporter.dm
index b95ac24d9b6..d606f7fdd26 100644
--- a/code/game/machinery/excelsior/ex_teleporter.dm
+++ b/code/game/machinery/excelsior/ex_teleporter.dm
@@ -1,14 +1,9 @@
-var/list/global/excelsior_teleporters = list() //This list is used to make turrets more efficient
-var/global/excelsior_energy
-var/global/excelsior_max_energy //Maximaum combined energy of all teleporters
-var/global/excelsior_conscripts = 0
-var/global/excelsior_last_draft = 0
/obj/machinery/complant_teleporter
name = "excelsior long-range teleporter"
desc = "A powerful teleporter that allows shipping matter in and out. Takes a long time to charge."
- description_info = "A highly illegal teleporter. Uses huge amounts of power and will always show in the powergrid monitor"
- description_antag = "The excelcior's main way of obtaining resources, calling reinforcements and unleashing the revolution"
+ description_info = "A highly illegal teleporter. Uses huge amounts of power and will always show in the powergrid monitor."
+ description_antag = "The Excelsior's main way of obtaining resources, calling reinforcements and unleashing the revolution."
density = TRUE
anchored = TRUE
icon = 'icons/obj/machines/excelsior/teleporter.dmi'
@@ -25,60 +20,105 @@ var/global/excelsior_last_draft = 0
var/nanoui_menu = 0 // Based on Uplink
var/mob/current_user
var/time_until_scan
+ var/old_energy //energy difference aka energy gain (used for debug but I guess some might like it)
var/reinforcements_delay = 5 MINUTES
var/reinforcements_cost = 2000
var/list/nanoui_data = list() // Additional data for NanoUI use
+
+ /* WARNING, READ IF YOU REALLY WANNA KNOW HOW I COUNTED PRICES
+ > OR SKIP UNTIL COMMENTS END
+ ******************************************************************************************
+ # Wall street economics here before we begin with the list:
+ 1 node produces 1 energy per tick(which is 2 sec) ONLY IF all 169 influence tiles are activated
+ - 30 energy/minute PER node
+
+
+
+ # All prices came with the following principles established:
+ 1. Excelsior wins after 2 hours with TOUGH FIGHT, they'll own 50%~ of the ship tiles in that time
+ 2. Excelsior received 1 node per EX_NODE_SPAWN_COOLDOWN [3 mins as of now]
+ 3. Expected early game is 1 node
+ 4. Expected "normal game begins" prices is 5 nodes
+ 5. Expected "say your GGs" is 20 nodes
+
+
+
+ # 1 resource = x energy
+
+ wood per 1 = 0.16
+ glass per 1 = 0.16
+ plastic per 1 = 0.16
+ steel per 1 = 0.33
+ silver per 1 = 5
+ plasteel per 1 = 5
+ gold per 1 = 5
+ diamond per 1 = 5
+ uranium per 1 = 10
+ ---
+ biomatter 1 = 1
+
+ cardboard 1 = 1
+ ******************************************************************************************/
+
+ // Wait for the second update, prices are somewhat low with intent, unabusable and calculated unless you tell me something I didn't account for.
var/list/materials_list = list(
- MATERIAL_STEEL = list("amount" = 30, "price" = 50), //base prices doubled untill new item are in
- MATERIAL_WOOD = list("amount" = 30, "price" = 50),
- MATERIAL_PLASTIC = list("amount" = 30, "price" = 50),
- MATERIAL_GLASS = list("amount" = 30, "price" = 50),
- MATERIAL_SILVER = list("amount" = 10, "price" = 100),
- MATERIAL_PLASTEEL = list("amount" = 10, "price" = 200),
- MATERIAL_GOLD = list("amount" = 10, "price" = 200),
- MATERIAL_URANIUM = list("amount" = 10, "price" = 300),
- MATERIAL_DIAMOND = list("amount" = 10, "price" = 400)
+ MATERIAL_WOOD = list("amount" = 30, "price" = 5),
+ MATERIAL_GLASS = list("amount" = 30, "price" = 5),
+ MATERIAL_PLASTIC = list("amount" = 30, "price" = 5),
+ MATERIAL_STEEL = list("amount" = 30, "price" = 10),
+ MATERIAL_BIOMATTER = list("amount" = 30, "price" = 30),
+ MATERIAL_CARDBOARD = list("amount" = 30, "price" = 30),
+ MATERIAL_PLASTEEL = list("amount" = 30, "price" = 150),
+ MATERIAL_SILVER = list("amount" = 30, "price" = 150),
+ MATERIAL_GOLD = list("amount" = 30, "price" = 150),
+ MATERIAL_DIAMOND = list("amount" = 30, "price" = 150), // NOTE: no artificial scarcity means industrial diamonds
+ MATERIAL_URANIUM = list("amount" = 30, "price" = 300),
)
var/list/parts_list = list(
- /obj/item/stock_parts/console_screen = 50,
- /obj/item/stock_parts/capacitor = 100,
- /obj/item/stock_parts/scanning_module = 100,
- /obj/item/stock_parts/manipulator = 100,
- /obj/item/stock_parts/micro_laser = 100,
- /obj/item/stock_parts/matter_bin = 100,
- /obj/item/computer_hardware/processor_unit/adv = 250,
- /obj/item/computer_hardware/hard_drive/advanced = 250,
- /obj/item/stock_parts/capacitor/excelsior = 350,
- /obj/item/stock_parts/scanning_module/excelsior = 350,
- /obj/item/stock_parts/manipulator/excelsior = 350,
- /obj/item/stock_parts/micro_laser/excelsior = 350,
- /obj/item/stock_parts/matter_bin/excelsior = 350,
- /obj/item/clothing/under/excelsior = 50,
- /obj/item/electronics/circuitboard/excelsior_teleporter = 500,
- /obj/item/electronics/circuitboard/excelsiorautolathe = 150,
- /obj/item/electronics/circuitboard/excelsiorreconstructor = 150,
- /obj/item/electronics/circuitboard/excelsior_turret = 150,
- /obj/item/electronics/circuitboard/excelsiorshieldwallgen = 150,
- /obj/item/electronics/circuitboard/excelsior_boombox = 150,
- /obj/item/electronics/circuitboard/excelsior_autodoc = 150,
- /// its expensive so they need to do a few mandates before they manage to get it
- /obj/item/electronics/circuitboard/excelsior_navigation_cracker = 3000,
- /obj/item/electronics/circuitboard/diesel = 150,
- /obj/item/storage/deferred/crate/excel_conscript = 75,
- /obj/item/storage/deferred/crate/excel_shock_kit = 250,
- /obj/item/storage/deferred/crate/excel_eva = 300,
- /obj/item/storage/deferred/crate/excel_spetsnaz = 350,
- /obj/item/storage/deferred/crate/excel_heavy = 450,
+ // # Computer parts
+ /obj/item/stock_parts/console_screen = list("amount" = 1, "price" = 1),
+ /obj/item/computer_hardware/processor_unit/adv = list("amount" = 1, "price" = 25), // exists only for redirector here consider removing
+ /obj/item/computer_hardware/hard_drive/advanced = list("amount" = 1, "price" = 25), // exists only for redirector here consider removing
+ /obj/item/stock_parts/capacitor/excelsior = list("amount" = 1, "price" = 25), //
+ /obj/item/stock_parts/scanning_module/excelsior = list("amount" = 1, "price" = 25), //
+ /obj/item/stock_parts/manipulator/excelsior = list("amount" = 1, "price" = 25), //
+ /obj/item/stock_parts/micro_laser/excelsior = list("amount" = 1, "price" = 25), //
+ /obj/item/stock_parts/matter_bin/excelsior = list("amount" = 1, "price" = 25), //
+ /obj/item/stack/cable_coil/orange = list("amount" = 30, "price" = 30),
+ )
+ var/list/circuits = list(
+ /obj/item/electronics/circuitboard/excelsior_teleporter = 10, //
+ /obj/item/electronics/circuitboard/excelsiorautolathe = 10, //
+ /obj/item/electronics/circuitboard/excelsior_boombox = 10, //
+ /obj/item/electronics/circuitboard/diesel = 10, //
+ /obj/item/electronics/circuitboard/excelsiorreconstructor = 25, //
+ /obj/item/electronics/circuitboard/excelsiorshieldwallgen = 25, //
+ /obj/item/electronics/circuitboard/excelsior_autodoc = 25, //
+ /obj/item/electronics/circuitboard/excelsior_turret = 50, //
+ /obj/item/electronics/circuitboard/excelsior_navigation_cracker = 2000, //
)
+ var/list/excelsior_kits = list(
+ /* [?]"Factual" means how many energy we woulda spent for 1 material instead of 30. 1:1 cost in other words. [line 48]
+ > We set prices bigger than factual otherwise goodbye economy
+ */
+ /obj/item/clothing/glasses/hud/excelsior = 1, // factual <1 I am not going below 1 anywhere anyhow
+ /obj/item/clothing/under/excelsior = 15, // TODO: recycle is 15 biomatter ughh FUCK check this later cuz we dont know if excels ""can have"" biomatter at all so its 1:1 now)
+ /obj/item/storage/deferred/crate/excel_conscript = 150, // factual 122 with prices at the moment
+ /obj/item/storage/deferred/crate/excel_shock_kit = 175, // factual 144 with prices atm
+ /obj/item/storage/deferred/crate/excel_spetsnaz = 200, // factual 181 with prices atm
+ /obj/item/storage/deferred/crate/excel_eva = 250, // factual 237 with prices atm
+ /obj/item/storage/deferred/crate/excel_heavy = 525, // factual 509 with prices atm
+ )
+ //all IKEAs are better than manual building so multiply 2
var/list/IKEA_list = list(
- /obj/item/machinery_crate/excelsior/shield = 500,
- /obj/item/machinery_crate/excelsior/autolathe = 300,
- /obj/item/machinery_crate/excelsior/boombox = 400,
- /obj/item/machinery_crate/excelsior/diesel_generator = 300,
- /obj/item/machinery_crate/excelsior/turret = 400
+ /obj/item/machinery_crate/excelsior/shield = 125, // factual 62
+ /obj/item/machinery_crate/excelsior/autolathe = 40, // factual 17
+ /obj/item/machinery_crate/excelsior/boombox = 40, // factual 16
+ /obj/item/machinery_crate/excelsior/diesel_generator = 40, // factual 17
+ /obj/item/machinery_crate/excelsior/turret = 600 // factual 271 oof-
)
var/entropy_value = 8
@@ -145,14 +185,14 @@ var/global/excelsior_last_draft = 0
return
if(excelsior_energy < (excelsior_max_energy - energy_gain))
- excelsior_energy += energy_gain
+ //excelsior_energy += energy_gain moved to centor.dm
SSnano.update_uis(src)
set_power_use(ACTIVE_POWER_USE)
else
- excelsior_energy = excelsior_max_energy
+ //excelsior_energy = excelsior_max_energy
set_power_use(IDLE_POWER_USE)
- /**
+ /*
* The nano_ui_interact proc is used to open and update Nano UIs
* If nano_ui_interact is not used then the UI will not update correctly
* nano_ui_interact is currently defined for /atom/movable
@@ -178,11 +218,12 @@ var/global/excelsior_last_draft = 0
/obj/machinery/complant_teleporter/nano_ui_data()
var/list/data = list()
- data["energy"] = round(excelsior_energy)
+ data["energy"] = round(excelsior_energy, 0.01)
data["maxEnergy"] = round(excelsior_max_energy)
data["menu"] = nanoui_menu
data["excel_user"] = is_excelsior(current_user)
data["time_until_scan"] = time_until_scan
+ data["old_energy"] = round(old_energy, 0.01)
data["conscripts"] = excelsior_conscripts
data["reinforcements_ready"] = reinforcements_check()
data += nanoui_data
@@ -200,19 +241,51 @@ var/global/excelsior_last_draft = 0
data["materials_list"] = order_list_m
+
+
var/list/order_list_p = list()
for(var/item in parts_list)
var/obj/item/I = item
order_list_p += list(
list(
"name_p" = initial(I.name),
- "price_p" = parts_list[item],
+ "price_p" = parts_list[item]["price"],
"commands_p" = list("order_p" = item)
)
)
data["list_of_parts"] = order_list_p
+
+
+ var/list/order_list_c = list()
+ for(var/item in circuits)
+ var/obj/item/I = item
+ order_list_c += list(
+ list(
+ "name_c" = initial(I.name),
+ "price_c" = circuits[item],
+ "commands_c" = list("order_c" = item)
+ )
+ )
+ data["circuits"] = order_list_c
+
+
+
+ var/list/order_list_e = list()
+ for(var/item in excelsior_kits)
+ var/obj/item/I = item
+ order_list_e += list(
+ list(
+ "name_e" = initial(I.name),
+ "price_e" = excelsior_kits[item],
+ "commands_e" = list("order_e" = item)
+ )
+ )
+ data["excelsior_kits"] = order_list_e
+
+
+
var/list/order_list_i = list()
for(var/obj/item/machinery_crate/I as anything in IKEA_list)
order_list_i += list(list(
@@ -240,19 +313,41 @@ var/global/excelsior_last_draft = 0
var/order_energy_cost = materials_list[ordered_item]["price"]
var/order_path = material_stack_type(ordered_item)
var/order_amount = materials_list[ordered_item]["amount"]
- send_order(order_path, order_energy_cost, order_amount)
+ send_order(order_path, order_energy_cost, order_amount, usr)
+
+
if(href_list["order_p"])
var/ordered_item = text2path(href_list["order_p"])
if (parts_list.Find(ordered_item))
- var/order_energy_cost = parts_list[ordered_item]
- send_order(ordered_item, order_energy_cost, 1)
+ var/order_energy_cost = parts_list[ordered_item]["price"]
+ var/order_amount = parts_list[ordered_item]["amount"]
+ send_order(ordered_item, order_energy_cost, order_amount, usr)
+
+
+
+ if(href_list["order_c"])
+ var/ordered_item = text2path(href_list["order_c"])
+ if (circuits.Find(ordered_item))
+ var/order_energy_cost = circuits[ordered_item]
+ send_order(ordered_item, order_energy_cost, 1, usr)
+
+
+
+
+ if(href_list["order_e"])
+ var/ordered_item = text2path(href_list["order_e"])
+ if (excelsior_kits.Find(ordered_item))
+ var/order_energy_cost = excelsior_kits[ordered_item]
+ send_order(ordered_item, order_energy_cost, 1, usr)
+
+
if(href_list["order_i"])
var/ordered_item = text2path(href_list["order_i"])
if (IKEA_list.Find(ordered_item))
var/order_energy_cost = IKEA_list[ordered_item]
- send_order(ordered_item, order_energy_cost, 1)
+ send_order(ordered_item, order_energy_cost, 1, usr)
if(href_list["open_menu"])
nanoui_menu = 1
@@ -287,7 +382,7 @@ var/global/excelsior_last_draft = 0
nanoui_data["available_mandates"] = available_mandates
nanoui_data["completed_mandates"] = completed_mandates
-/obj/machinery/complant_teleporter/proc/send_order(order_path, order_cost, amount)
+/obj/machinery/complant_teleporter/proc/send_order(order_path, order_cost, amount, mob/user)
if(order_cost > excelsior_energy)
to_chat(usr, span_warning("Not enough energy."))
return
@@ -296,11 +391,13 @@ var/global/excelsior_last_draft = 0
excelsior_energy = max(excelsior_energy - order_cost, 0)
flick("teleporting", src)
spawn(17)
- complete_order(order_path, amount)
+ complete_order(order_path, amount, user)
-/obj/machinery/complant_teleporter/proc/complete_order(order_path, amount)
+/obj/machinery/complant_teleporter/proc/complete_order(order_path, amount, mob/user)
use_power(active_power_usage * 3)
- new order_path(loc, amount)
+ var/obj/item/item = new order_path(loc, amount)
+ if(!user.put_in_hands(item))
+ item.forceMove(get_turf(src))
bluespace_entropy(entropy_value, get_turf(src))
processing_order = FALSE
diff --git a/code/game/machinery/excelsior/ex_turret.dm b/code/game/machinery/excelsior/ex_turret.dm
index 8efa2fb7bdd..7f421788e73 100644
--- a/code/game/machinery/excelsior/ex_turret.dm
+++ b/code/game/machinery/excelsior/ex_turret.dm
@@ -5,6 +5,7 @@
/obj/machinery/porta_turret/excelsior
icon = 'icons/obj/machines/excelsior/turret.dmi'
desc = "A fully automated anti infantry platform. Fires .30 caliber rounds"
+ description_info = "Needs to be in the range of a working node to shoot."
icon_state = "turret_legs"
density = TRUE
lethal = TRUE
@@ -19,10 +20,11 @@
health = 300
shot_delay = 0
shipside_only = TRUE
+ var/obj/machinery/node/my_node
/obj/machinery/porta_turret/excelsior/proc/has_power_source_nearby()
- for (var/a in excelsior_teleporters)
- if (dist3D(src, a) <= working_range) //The turret and teleporter can be on a different zlevel
+ if(my_node) // we don't need one more runtime in this neighborhood
+ if(my_node.core)
return TRUE
return FALSE
@@ -30,12 +32,44 @@
if(get_dist(user, src) < 2)
extra_description += "There [(ammo == 1) ? "is" : "are"] [ammo] round\s left!"
if(!has_power_source_nearby())
- extra_description += "\nSeems to be powered down. No excelsior teleporter found nearby."
+ extra_description += "\nSeems to be powered down. No active Excelsior node found nearby."
..(user, extra_description)
+
+
+
+
/obj/machinery/porta_turret/excelsior/Initialize()
. = ..()
update_icon()
+ RegisterSignal(src, COMSIG_EX_CONNECT, PROC_REF(search_for_node))
+ search_for_node()
+
+
+
+
+
+/obj/machinery/porta_turret/excelsior/proc/search_for_node()
+ var/obj/machinery/node/closest
+ var/closest_dist = EX_NODE_DISTANCE + 1
+ for (var/obj/machinery/node/node in excelsior_nodes)
+ if(get_dist(src, node) < closest_dist)
+ closest = node
+ closest_dist = dist3D(src, closest)
+ if(closest) // connected
+ closest.connect(src)
+ my_node = closest
+ update_icon()
+ return TRUE
+ else // no connect :[
+ my_node = null
+ update_icon()
+ return FALSE
+
+
+
+
+
/obj/machinery/porta_turret/excelsior/setup()
var/obj/item/ammo_casing/AM = initial(ammo_box.ammo_type)
@@ -83,6 +117,11 @@
else
..()
+
+
+
+
+
/obj/machinery/porta_turret/excelsior/Process()
if(!has_power_source_nearby())
disabled = TRUE
@@ -92,6 +131,11 @@
disabled = FALSE
..()
+
+
+
+
+
/obj/machinery/porta_turret/excelsior/assess_living(mob/living/L)
if(!istype(L))
return TURRET_NOT_TARGET
diff --git a/code/game/machinery/excelsior/excelsior_debug_tools.dm b/code/game/machinery/excelsior/excelsior_debug_tools.dm
new file mode 100644
index 00000000000..7d5dccd6a81
--- /dev/null
+++ b/code/game/machinery/excelsior/excelsior_debug_tools.dm
@@ -0,0 +1,48 @@
+
+/*
+Contents:
+#######################
++ Toolbox with all tools
+ - Node spawner
+#######################
+*/
+
+
+
+
+// /obj/item/exceldebugtoolspawnertoolbox/ //FUCKING UNFINISHED
+
+
+
+
+// Node spawner (spawns nodes... duh...)
+/*
+/obj/item/nodespawner
+ name = "\improper Node spawner"
+ desc = "Spawns Excelsior nodes wherever you click. Epic!!! Also can delete nodes if you click one"
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "rcd"
+ opacity = 0
+ density = FALSE
+ anchored = FALSE
+ w_class = ITEM_SIZE_NORMAL
+
+
+
+
+/obj/item/nodespawner/afterattack(atom/A, mob/user as mob)
+
+ if(istype(A, /obj/machinery/node))
+ to_chat(user, "Deleted node womp womp")
+ playsound(src.loc, 'sound/machines/click.ogg', 10, 1)
+ qdel(A)
+ return
+
+
+ playsound(src.loc, 'sound/machines/click.ogg', 10, 1)
+
+ to_chat(user, "Spawned node YEAAAAAH")
+ new /obj/machinery/node(get_turf(A))
+
+*/
+
diff --git a/code/game/machinery/excelsior/excelsior_items/KPK.dm b/code/game/machinery/excelsior/excelsior_items/KPK.dm
new file mode 100644
index 00000000000..420f154d32f
--- /dev/null
+++ b/code/game/machinery/excelsior/excelsior_items/KPK.dm
@@ -0,0 +1,612 @@
+#define MODE_NONE 1
+#define MODE_PATHFINDER 2
+#define MODE_INFLUENCE 3
+
+
+// Fun Fact: We on accident call KOMPAK a "KPK", which translated to "PDA". This may be seen in the comments during explanations and I cannot fight the urges to not write it.
+
+/obj/item/centor_kpk/
+ name = "\improper Excelsior KOMPAK"
+ desc = "A lightweight PDA, that could be your grandfather if it was animated. Compatriot's second best friend."
+ description_info = "Every Excelsior agent gets one from Centor, but each gets only one."
+ description_antag = "Has Pathfinder and Influence modes. The first builds and leads you through holographic paths between nodes, the latter sees their working radius."
+ icon = 'icons/obj/machines/excelsior/corenode/pda.dmi'
+ icon_state = "kompak_off"
+ opacity = 0
+ density = FALSE
+ anchored = FALSE
+ w_class = ITEM_SIZE_SMALL
+ var/mode = MODE_NONE
+ var/code_crutch = TRUE // TODO: DELETE IF STAGE 2 (drone update).
+ // - This is here cuz no drones yet, but I've decided it might be good to still include it.
+ matter = list(MATERIAL_PLASTIC = 5, MATERIAL_GLASS = 1, MATERIAL_PLASMA = 2)
+
+ var/list/active_scanned = list()
+ var/datum/event_source
+ var/mob/current_user
+
+ var/client/user_client
+ var/enabled = FALSE // visual, no mechanics
+ var/active
+ var/list/objects_to_overlay = list()
+ var/turn_on_sound = 'sound/effects/Custom_flashlight.ogg'
+ var/path_diologe = FALSE //
+ var/viewpath_diologe = FALSE // this is UI
+ var/mappings_diologe = FALSE //
+ var/obj/machinery/node/chosen_node
+ var/obj/effect/effect/pathfinder_arrow/first/current_route
+ var/list/ihaveplacestobe = list() //list of roads, waiting to become overlays.
+ var/obj/machinery/node/node_here // this is needed for reverse_arrows()
+ var/list/errors = list()
+ //
+
+
+// CRUTCH DETECTOR SCREAMS "DELETE ME I BEG YOU" but I say no... you must be here for now until we release a second update.
+/obj/item/centor_kpk/Initialize()
+ . = ..()
+ if(code_crutch)
+ description_antag += " Each path made grants 0.25 energy gain to teleporters." // Guh... Т_Т There's no other incentive but altruism!
+
+
+/obj/item/centor_kpk/update_icon()
+ if(current_user)
+ icon_state = "kompak_on"
+ else
+ icon_state = "kompak_off"
+
+/obj/item/centor_kpk/attack_self(mob/user)
+ set_user(user)
+ nano_ui_interact(user)
+
+/obj/item/centor_kpk/nano_ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = NANOUI_FOCUS)
+ var/list/data = nano_ui_data()
+
+ ui = SSnano.try_update_ui(user, src, ui_key, ui, data, force_open)
+ if (!ui)
+ ui = new(user, src, ui_key, "excelsior_kpk.tmpl", name, 450, 500)
+ ui.set_initial_data(data)
+ ui.open()
+
+/obj/item/centor_kpk/nano_ui_data()
+ var/list/data = list()
+ data["path_diologe"] = path_diologe
+ data["current_path"] = current_route ? 1 : 0
+ data["current_node"] = chosen_node ? chosen_node.shortname : "ERR: NODE NOT FOUND"
+ data["viewpath_dio"] = viewpath_diologe
+ data["mappings_dio"] = mappings_diologe
+
+ if(enabled)
+ switch(mode)
+ if(MODE_NONE)
+ data["overlay_enabled"] = "Online: Avaiting mode"
+ if(MODE_INFLUENCE)
+ data["overlay_enabled"] = "Online: Influence"
+ if(MODE_PATHFINDER)
+ data["overlay_enabled"] = "Online: Pathfinder"
+ else
+ data["overlay_enabled"] = "Offline"
+
+ var/list/error_list = list()
+ var/z_err = 10
+ for(var/error in errors)
+ z_err++
+ error_list += list(
+ list(
+ "z_err" = "style=\"z-index: [z_err];\"",
+ "text_err" = error,
+ "commands_err" = list("ok_error" = error)
+ )
+ )
+
+ data["error_list"] = error_list
+
+ var/list/node_list = list()
+ for(var/obj/machinery/node/noda in excelsior_nodes)
+ node_list += list(
+ list(
+ "name_n" = noda.shortname,
+ "commands_n" = list("see_path" = noda.uid)
+ )
+ )
+
+ data["node_list"] = node_list
+
+ return data
+
+/obj/item/centor_kpk/proc/set_user(mob/living/newuser)
+ if(newuser && !is_excelsior(newuser))
+ return //Unautharized access
+ if(current_user == newuser)
+ return
+
+ //If there's an existing user we may need to unregister them first
+ if(current_user)
+ unset_client()
+
+ //Actually set it
+ current_user = newuser
+ set_client()
+ event_source = get_track_target()
+ check_active()
+ update_icon()
+
+/obj/item/centor_kpk/proc/set_client()
+ if(!current_user || !current_user.client)
+ return FALSE
+
+ user_client = current_user.client
+
+
+ for(var/scanned in active_scanned)
+ user_client.images += active_scanned[scanned]
+
+
+
+/obj/item/centor_kpk/proc/unset_client()
+ if(event_source)
+ GLOB.moved_event.unregister(event_source, src)
+ event_source = null
+ if(user_client)
+ for(var/scanned in active_scanned)
+ user_client.images -= active_scanned[scanned]
+
+ user_client = null
+ active_scanned.Cut()
+
+/obj/item/centor_kpk/proc/get_track_target()
+ return current_user
+
+/obj/item/centor_kpk/proc/set_inactive()
+ unset_client()
+ active = FALSE
+
+/obj/item/centor_kpk/proc/set_active()
+ event_source = get_track_target()
+ GLOB.moved_event.register(event_source, src, /obj/item/centor_kpk/proc/update_overlay)
+ active = TRUE
+ update_overlay()
+
+/obj/item/centor_kpk/proc/set_enabled(targetstate)
+
+ if(targetstate == FALSE && enabled)
+ playsound(loc, turn_on_sound, 55, 1,-2)
+ enabled = FALSE
+ if(targetstate == TRUE)
+ enabled = TRUE
+ playsound(loc, turn_on_sound, 55, 1, -2)
+
+// if(enabled) no power/battery need for KPK for now so let's comment this for now
+// START_PROCESSING(SSobj, src)
+// else
+// STOP_PROCESSING(SSobj, src)
+ check_active(enabled)
+// update_icon()
+
+/obj/item/centor_kpk/proc/check_location()
+ //This proc checks that the scanner is where it needs to be.
+ //In this case, this means it must be held in the hands of a mob
+
+ if(!ismob(loc))
+ return FALSE
+
+ if(!is_held())
+ return FALSE
+
+ return TRUE
+
+/obj/item/centor_kpk/proc/get_scanned_objects()
+ . = list()
+ if(!enabled)
+ return .
+ switch(mode)
+ if(MODE_NONE)
+ return .
+ if(MODE_PATHFINDER) // draw only those "holo arrows", which are inside the list [ihaveplacestobe], by default they are invisible.
+ for(var/i = LAZYLEN(ihaveplacestobe), i > 0, i--)
+ var/datum/excelsior_junction/route = ihaveplacestobe[i]
+ for(var/arrow in route.track)
+ . += arrow
+ if(MODE_INFLUENCE) // "orange tiles"
+ for(var/obj/effect/effect/excelsior_influence/influence in view(loc))
+ . += influence
+
+/obj/item/centor_kpk/proc/update_overlay()
+ //get all objects in scan range
+ var/list/scanned = list()
+ scanned = get_scanned_objects()
+
+ var/list/update_add = scanned - active_scanned
+ var/list/update_remove = active_scanned - scanned
+ var/temp_slot = node_here
+ var/current_route
+ var/do_reversed
+
+ for(var/obj/effect/effect/pathfinder_arrow/arrow in update_add)
+ if(arrow.my_route != current_route)//first arrow of it's route
+ do_reversed = FALSE//reset flag for next cycle
+ current_route = arrow.my_route
+ if(temp_slot in orange(1, arrow))//if FIRST arrow in near our ENDpoint
+ do_reversed = TRUE
+ temp_slot = arrow.my_route.second
+ else
+ temp_slot = arrow.my_route.first
+
+ var/mutable_appearance/overlay = get_overlay(arrow, do_reversed)
+ active_scanned[arrow] = overlay
+ user_client.images += overlay
+
+ //Add new overlays
+ for(var/obj/effect/effect/excelsior_influence/O in update_add)
+ var/mutable_appearance/overlay = get_overlay(O)
+
+ active_scanned[O] = overlay
+ user_client.images += overlay
+
+ //Remove stale overlays
+ for(var/obj/O in update_remove)
+ user_client.images -= active_scanned[O]
+ active_scanned -= O
+
+/obj/item/centor_kpk/proc/refresh_overlay() //Regenerate all overlays from scratch. Used with pathfinder arrows
+ for(var/obj/O in active_scanned)
+ user_client.images -= active_scanned[O]
+ active_scanned -= O
+ update_overlay()
+
+/obj/item/centor_kpk/proc/check_active(var/targetstate = TRUE)
+ //First of all, check if its being turned off. This is simpler
+ if(!targetstate)
+ if(!active)
+ //If we were just turned off, but we were already inactive, then we don't need to do anything
+ return
+
+ //We were active, ok lets shut down things
+ set_inactive()
+ else
+ //We're trying to become active, alright lets do some checks
+ //We'll do these checks even if we're already active, they ensure we can remain so
+ var/can_activate = TRUE
+
+ //First we must be enabled
+ if(!enabled)
+ can_activate = FALSE
+
+ //Secondly, we must be held in someone's hands
+ else if(!check_location())
+ can_activate = FALSE
+
+ //Thirdly, we need a client to display to
+ else if(!user_client)
+ //The client may not be set if the user logged out and in again
+ set_client() //Try re-setting it
+ if(!user_client)
+ can_activate = FALSE
+
+ if(!can_activate)
+ //We failed the above, what now
+ if(active)
+ set_inactive()
+
+ else if(!active)
+ set_active()
+
+/obj/item/centor_kpk/dropped(mob/user)
+ .=..()
+ set_user(null)
+
+/obj/item/centor_kpk/equipped(mob/M)
+ .=..()
+ set_user(M)
+
+/obj/item/centor_kpk/Destroy()
+ set_user(null)
+ .=..()
+
+
+// All this does is reverse arrows visually when showing it on KOMPAK "INFLUENCE MODE" overlay.
+// - Why? [pathfinder_arrow]s look in a direction where the player went while building them, but if we want Find Path to go backwards, they won't reverse themselves.
+/obj/item/centor_kpk/proc/reverse_arrow(var/curDir)
+ switch(curDir)
+ if("1-4")
+ return "8-2"
+ if("8-2")
+ return "1-4"
+
+ if("8-1")
+ return "2-4"
+ if("2-4")
+ return "8-1"
+
+ if("1-8")
+ return "4-2"
+ if("4-2")
+ return "1-8"
+
+ if("2-8")
+ return "4-1"
+ if("4-1")
+ return "2-8"
+
+
+//creates a new overlay for a scanned object
+/obj/item/centor_kpk/proc/get_overlay(obj/scanned, reversing)
+ var/image/I = image(loc = scanned)
+ if(istype(scanned, /obj/effect/effect/excelsior_influence))
+ var/obj/effect/effect/excelsior_influence/influence = scanned
+ if(influence.active)
+ I = image('icons/obj/machines/excelsior/corenode/pda.dmi', loc = influence, icon_state = "influence", layer = ON_MOB_HUD_LAYER)
+ else
+ I = image('icons/obj/machines/excelsior/corenode/pda.dmi', loc = influence, icon_state = "influence_red", layer = ON_MOB_HUD_LAYER)
+ if(istype(scanned, /obj/effect/effect/pathfinder_arrow))
+ I = image('icons/obj/machines/excelsior/corenode/pda.dmi', loc = scanned, icon_state = "[scanned.icon_state]", layer = BELOW_MOB_LAYER)
+ if(reversing)
+ I.dir = reverse_direction(scanned.dir)
+ if(I.icon_state != "straight")
+ I.icon_state = reverse_arrow(I.icon_state)
+ else
+ I.dir = scanned.dir
+ I.mouse_opacity = 0
+ .=I
+
+// GUI
+
+/obj/item/centor_kpk/Topic(href, href_list)
+ if(href_list["open_path_dio"])
+ path_diologe = TRUE
+
+ if(href_list["close_path_dio"])
+ path_diologe = FALSE
+
+ if(href_list["open_viewpath_dio"])
+ viewpath_diologe = TRUE
+
+ if(href_list["close_viewpath_dio"])
+ viewpath_diologe = FALSE
+
+ if(href_list["open_mappings_dio"])
+ mappings_diologe = TRUE
+
+ if(href_list["close_mappings_dio"])
+ mappings_diologe = FALSE
+
+ if(href_list["toggle_overlay"])
+ mode = MODE_NONE
+ set_enabled(!enabled)
+
+ if(href_list["influence_overlay"])
+ mode = MODE_INFLUENCE
+ set_enabled(TRUE)
+ refresh_overlay()
+
+ if(href_list["pathfind_overlay"])
+ mode = MODE_PATHFINDER
+ set_enabled(TRUE)
+ refresh_overlay()
+
+ if(href_list["start_pathfind"])
+ start_pathfind(usr)
+
+ if(href_list["end_pathfind"])
+ end_pathfind(usr)
+
+ if(href_list["cancel_pathfind"])
+ cancel_pathfind()
+
+ if(href_list["ok_error"])
+ errors.Remove(href_list["ok_error"])
+
+ if(href_list["see_path"])
+ for(var/obj/machinery/node/noda in excelsior_nodes)
+ if(noda.uid == text2num(href_list["see_path"]))
+ find_path(usr, noda)
+ viewpath_diologe = FALSE
+ mode = MODE_PATHFINDER
+ update_overlay()
+
+ add_fingerprint(usr)
+ return TOPIC_HANDLED // update UIs attached to this object
+
+//> END TOPIC
+
+
+
+
+
+
+//------------------------------------------| PATHFINDER - Build Path |------------------------------------------
+/obj/item/centor_kpk/proc/start_pathfind(mob/user as mob)
+ var/obj/machinery/node/closest = locate(/obj/machinery/node) in orange(1, user.loc) //TODO insert alert for the guy to come closer btw in GUI
+ if(!closest)
+ throw_error("Please approach a node to start building path from.")
+ return
+ if(get_dir(user, closest) in list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
+ throw_error("Please approach node from a straight angle.")
+ return
+ var/obj/effect/effect/pathfinder_arrow/first/arrow = new /obj/effect/effect/pathfinder_arrow/first(user.loc) //TODO: You/we/I should make it visible while Build Path is working for player feedback
+ current_route = arrow
+ arrow.kpk = src
+ path_diologe = FALSE
+ chosen_node = closest
+
+/obj/item/centor_kpk/proc/end_pathfind(mob/user as mob)
+ var/obj/machinery/node/closest = locate(/obj/machinery/node) in orange(1, user.loc) //TODO insert alert for the guy to come closer btw in GUI
+ if(!closest)
+ throw_error("No nodes found nearby. Approach one to finish path.")
+ return
+ if(closest == chosen_node)
+ throw_error("Cannot end path in the starting point. Try approaching different node.")
+ for(var/datum/excelsior_junction/route in excelsior_junctions)
+ if(route.first == chosen_node || route.second == chosen_node)
+ if(route.first == closest || route.second == closest)
+ throw_error("There's already a route between those two points. Cannot create duplicates.")
+ return
+ var/obj/arrow = current_route.snake[current_route.snake.len]
+ var/dir_to_node = get_dir(arrow, closest)
+ if(dir_to_node in list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
+ throw_error("Please approach node from a straight angle.")
+ return
+ if(arrow.dir != dir_to_node)
+ arrow.icon_state = "[arrow.dir]-[dir_to_node]"
+ var/datum/excelsior_junction/write_this_down = new /datum/excelsior_junction(chosen_node, closest, current_route.snake)
+ for(var/obj/effect/effect/pathfinder_arrow/arr in write_this_down.track)
+ arr.my_route = write_this_down
+ chosen_node = null
+ current_route = null
+
+/obj/item/centor_kpk/proc/cancel_pathfind()
+ chosen_node = null
+ for(var/tile in current_route.snake)
+ qdel(tile)
+ current_route = null
+
+/obj/item/centor_kpk/proc/throw_error(var/context)
+ errors.Add(context)
+ SSnano.update_uis(src)
+
+
+
+
+// In the comments called: "invisible arrows", "arrow holos".
+// These things are created when you are walking after sucessfully beginning a Build Path on your KOMPAK
+
+// There's only two types of them:
+// 1. /obj/effect/effect/pathfinder_arrow/first --- this one exists to store info about newly created second type arrows.
+// 2. /obj/effect/effect/pathfinder_arrow
+
+/obj/effect/effect/pathfinder_arrow/first // This is a first spawned arrow, pointing in some direction
+ var/list/snake = list() // - It exists to store the list of the whole "path", nothing more
+ var/obj/item/centor_kpk/kpk
+
+
+
+
+
+/obj/effect/effect/pathfinder_arrow // This is created by [pathifnder_arrow/first] above.
+ var/obj/effect/effect/pathfinder_arrow/first/original // Exists for the question "who stores all info about your whole road snake here?""
+ var/counter = 1
+ var/datum/excelsior_junction/my_route
+
+
+
+
+
+
+/obj/effect/effect/pathfinder_arrow/New(loc, var/obj/effect/effect/pathfinder_arrow/previous)
+ ..(loc)
+ icon = null
+ icon_state = "straight"
+ if(!previous)
+ original = src
+ for(var/obj/machinery/node/closest in orange(1, src))
+ dir = get_dir(closest, src)
+ else
+ original = previous.original
+ counter = previous.counter + 1
+ dir = get_dir(previous, src)
+ if(dir != previous.dir)
+ previous.icon_state = "[previous.dir]-[get_dir(previous, src)]"
+ original.snake.Add(src)
+ return
+
+
+
+
+
+
+/obj/effect/effect/pathfinder_arrow/Uncrossed(var/atom/movable/badguy)
+ if(original.kpk.current_route != original)
+ return
+ if(original.kpk.get_holding_mob() != badguy)
+ return
+ new /obj/effect/effect/pathfinder_arrow(badguy.loc, src)
+
+
+/obj/effect/effect/pathfinder_arrow/Crossed(var/atom/movable/badguy)
+ if(original.kpk.current_route != original)
+ return
+ if(original.kpk.get_holding_mob() != badguy)
+ return
+ for(var/obj/effect/effect/pathfinder_arrow/item in original.snake)
+ if(item.counter > counter)
+ original.snake.Remove(item)
+ qdel(item)
+
+
+
+/* Path as DATA
+ - holds 2 nodes as vars
+ - list/track contains [/obj/effect/effect/pathfinder_arrow]
+*/
+/datum/excelsior_junction // This one is created when KOMPAK finalizes a Build Path, all invisible arrows built are stored inside this one.
+ var/obj/machinery/node/first // node chosen at start_pathfind()
+ var/obj/machinery/node/second // and at the end_pathfind(), duh...
+
+ var/list/track = list() // contains obj [pathfinder_arrow]
+
+
+/datum/excelsior_junction/New(obj/machinery/node/A as obj, obj/machinery/node/B as obj, list/route) // pass the info about 2 points of the path
+ first = A
+ second = B
+ track = route
+ excelsior_junctions.Add(src)
+
+
+//------------------------------------------| PATHFINDER - Find Path |------------------------------------------
+// /obj/item/centor_kpk/find_path(usr, destination) ;* <-- GUI
+/obj/item/centor_kpk/proc/find_path(mob/user as mob, var/obj/machinery/destination)
+ var/obj/machinery/node/closest = locate(/obj/machinery/node) in orange(1, user.loc)
+ if(!closest)
+ throw_error("You need to stand next to a node.")
+ else if(closest == destination)
+ throw_error("You are standing next to that node, Infiltrator...")
+ else
+ ihaveplacestobe.Cut()
+ closest.sendPath(end = destination, kpk = src)
+ mode = MODE_PATHFINDER
+ set_enabled(TRUE)
+ spawn(3 SECONDS)
+ if(!ihaveplacestobe.len)
+ throw_error("No routes found, try building one.")
+ mode = MODE_NONE
+ refresh_overlay()
+
+//------------------------------------------
+// #pathfinder guide
+//
+// As of today: Pathfinder's purpose is to lead players from one node to another
+// But for the future: I want Excelsior' drones to use these paths to automate defenses, building, healing, fighting and whatever else drones do.
+//
+// Practical guide:
+// HINT:
+// 1. spawn 2 [/obj/machinery/node/], or in natural gameplay: [/obj/item/machinery_crate/excelsior/node]
+// 2. spawn [centor_kpk] and open it
+// 3. press Build Path near any deployed node
+// > start_pathfind() was called
+// > [pathfinder_arrow/first] spawns under you
+// 4. walk around
+// > pathfinder_arrow/proc/Uncrossed you walked off [pathfinder_arrow] --- it's invisible sry... :(
+// > pathfinder_arrow/proc/Crossed you walked into [pathfinder_arrow]
+// - You are building an arrow road towards another node.
+// 5. Press Finish Path near another node.
+// > end_pathfind()
+// > all the [pathfinder_arrow] you made go into [/datum/excelsior_junction], inside list() "track"
+// 6. Stand next to a node (var is closest) and choose opposite one (var is destination) under Find Path option
+// - the node you chose now is [var/obj/machinery/destination]
+// > KPK calls findPath() which makes [closest] node call sendPath()
+// > sendPath() returns [way_to_go] list
+// > [way_to_go] contents go into [ihaveplacestobe]
+// - the line: "kpk.ihaveplacestobe = way_to_go"
+// 7. KPK turns on Pathfinder and we see the arrows
+// - set_enabled(TRUE)
+// - mode = MODE_PATHFINDER
+// > update_overlay() happens *somewhere*... I dont know where.
+//
+//------------------------------------------
+
+
+
+
+
+#undef MODE_NONE
+#undef MODE_PATHFINDER
+#undef MODE_INFLUENCE
diff --git a/code/game/machinery/excelsior/excelsior_researches.dm b/code/game/machinery/excelsior/excelsior_researches.dm
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/code/game/machinery/excelsior/node.dm b/code/game/machinery/excelsior/node.dm
new file mode 100644
index 00000000000..310ee19b77d
--- /dev/null
+++ b/code/game/machinery/excelsior/node.dm
@@ -0,0 +1,538 @@
+/obj/machinery/node
+ name = "Excelsior \"Tochka\" node"
+ var/shortname = "Tochka-123"
+ icon = 'icons/obj/machines/excelsior/corenode/node.dmi'
+ desc = "Bullet resistant transmission receiver and spreader. Reaches for signals like antennas to Haven."
+ icon_state = "on"
+ description_info = "Node scans tiles to get teleporter power from them, activates turrets, and reports intruders to Excelsior communications."
+ description_antag = "Repairable with welding tools. Node radius can be seen with Influence Mode on KOMPAK. Connecting them to Centor and each other makes them work."
+ anchored = TRUE
+ density = TRUE
+ circuit = /obj/item/electronics/circuitboard/excelsior_node
+ health = 1200
+ maxHealth = 1200
+ shipside_only = TRUE
+ layer = 5
+ var/list/obj/machinery/linked = list()
+ var/list/obj/machinery/node/neighbours = list()
+ var/obj/machinery/centor/core // we wanna know whos our dad
+ var/damage_report_cooldown = FALSE
+
+ //var/emplacement_storage = 4
+ var/list/localmarkerlist = list() /* On destroy() or "turning off" (if disconnected from Centor's node chain) will...
+ > remove the whole local list (node's) from global one (Centor interacts with it)
+ - Is Feature, cut off Excelsior's "logistics" and forward bases won't work :)
+ */
+ var/list/activemarkerlist = list()
+ var/what_is_marker = /obj/effect/effect/excelsior_influence
+
+ // Intruder Reporting
+ var/list/intruder_list = list()
+ var/report_cooldown
+
+
+/*
+* Basics
+*/
+
+
+/obj/machinery/node/examine(mob/user, extra_description)
+ if(!core)
+ extra_description += "\nSeems to be powered down. No active Excelsior node or Centor found nearby."
+ . = ..()
+
+
+/obj/machinery/node/proc/make_name()
+ var/list/namelist = list(
+ "Zvezda",
+ "Barrikada",
+ "Volna",
+ "Abzats",
+ "Pioner",
+ "Dyatel",
+ "Malyutka",
+ "Durak",
+ "Vampir",
+ "Kolobok",
+ "Udav",
+ "Zenit",
+ "Sport",
+ "Spidola",
+ "Mayak",
+ "Zorkiy",
+ "Iskra",
+ "Lider",
+ "Sirius",
+ "Yunost",
+ "Melodiya",
+ "Vega",
+ "Rondo",
+ "Korvet",
+ "Kantata",
+ "Serenada",
+ "Arktur",
+ "Ilga",
+ "Tochka",
+ "Sovet",
+ "Sakhar",
+ "Krona",
+ "Praktik",
+ "Kozyol",
+ "Partisan",
+ )
+
+ var/newname = pick(namelist)
+ var/cifra = rand(100, 999) // cifra does nothing except goes to a name
+ name = "Excelsior \"[newname]-[cifra]\" node"
+ shortname = "[newname]-[cifra]"
+
+
+/obj/machinery/node/assign_uid() // this is for UI because UI wants a reference to obj :)
+ uid = rand(1, 3000)
+ for(var/obj/machinery/node/node in excelsior_nodes)
+ if(node.uid == uid) //lets compare all IDs so they dont match (if they do just reroll :3)
+ assign_uid()
+
+
+/obj/machinery/node/Initialize(mapload, d)
+ . = ..()
+ make_name()
+ assign_uid()
+ excelsior_nodes.Add(src)
+ search_for_machines()
+ search_for_nodes()
+ define_influence()
+ if(excelsior_centor)
+ var/obj/machinery/centor/C = excelsior_centor
+ C.load_network()
+ update_icon()
+
+
+
+
+
+
+
+/obj/machinery/node/Destroy()
+ for(var/datum/excelsior_junction/short_road in excelsior_junctions)//Clean up connected roads
+ if(short_road.first == src || short_road.second == src)
+ excelsior_junctions.Remove(short_road)
+ short_road.Destroy()
+ cleanup_influence()
+ UnregisterSignal(src, COMSIG_TURF_LEVELUPDATE)
+ for(var/obj/machinery/node/noder in neighbours)
+ noder.update_influence()
+ . = ..()
+
+ excelsior_nodes.Remove(src)
+ for(var/obj/machinery/machine in linked)
+ SEND_SIGNAL(machine, COMSIG_EX_CONNECT)
+ for(var/obj/machinery/node/N in neighbours)
+ N.disconnect(src, TRUE)
+ if(core)
+ core.load_network()
+
+
+
+
+
+
+
+/obj/machinery/node/proc/update_influence()
+ cleanup_influence()
+ define_influence()
+
+
+
+
+
+/obj/machinery/node/proc/define_influence()
+ for(var/turf/selected in circlerangeturfs(src, EX_NODE_DISTANCE))
+ if(!locate(/obj/effect/effect/excelsior_influence) in selected)
+ var/influence_marker = new /obj/effect/effect/excelsior_influence(loc = selected, creator = src)
+ if(influence_marker)
+ localmarkerlist.Add(influence_marker)
+ else
+ continue
+
+
+
+
+
+
+/obj/machinery/node/proc/cleanup_influence()
+ for(var/marker in localmarkerlist)
+ QDEL_NULL(marker)
+ localmarkerlist = list()
+ activemarkerlist = list()
+
+
+
+
+
+
+/*/obj/machinery/node/proc/pick_up_emplacement(var/mob/living/carbon/human/user)
+ if(emplacement_storage >= 1)
+ var/obj/item/unemplacement/emplacement = /obj/item/unemplacement // item that will then become the machinery
+ user.put_in_active_hand(new emplacement)
+ emplacement_storage--
+ //!!!!add ability to put it back in - delete comment if done
+*/
+
+
+
+
+/obj/machinery/node/attackby(obj/item/I, mob/user)
+ if(user.a_intent == I_HELP)
+ if((QUALITY_WELDING in I.tool_qualities) && (health < maxHealth))
+ if(I.use_tool(user, src, WORKTIME_LONG, QUALITY_WELDING, FAILCHANCE_EASY, required_stat = STAT_MEC))
+ health += 200
+ if(health > maxHealth)
+ health = maxHealth
+ update_icon()
+ return 1
+ if (!(I.flags & NOBLUDGEON) && I.force)
+ //if the turret was attacked with the intention of harming it:
+ user.do_attack_animation(src)
+ user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ take_damage(I.force * I.structure_damage_factor)
+
+ ..()
+
+/obj/machinery/node/bullet_act(obj/item/projectile/Proj)
+ var/damage = Proj.get_structure_damage()
+ ..()
+ take_damage(damage*Proj.structure_damage_factor)
+
+/obj/machinery/node/take_damage(amount)
+ if(!damage_report_cooldown)
+ talk("DAMAGED :: Node [shortname] lost integrity. ")
+ damage_report_cooldown = TRUE
+ spawn(1 MINUTE)
+ if(src)
+ damage_report_cooldown = FALSE
+ if(!amount)
+ return FALSE //No damage done. Used in attackby()
+ health -= amount
+ if(health <= 0)
+ die()
+ update_icon()
+ return TRUE //Actual damage delt. Used in attackby()
+
+/obj/machinery/node/proc/die()
+ talk("DESTROYED :: [shortname] reported demolished at [get_area(src)]")
+ explosion(get_turf(src), 100, 50)
+ Destroy()
+
+/obj/machinery/node/update_icon()
+ overlays.Cut()
+ icon_state = "on"
+
+ if(!core)
+ overlays += "off_overlay"
+ if(health <= maxHealth * 0.25)
+ icon_state = "damaged_heavy"
+ return
+ if(health <= maxHealth * 0.5)
+ icon_state = "damaged_moderate"
+ return
+ if(health <= maxHealth * 0.75)
+ icon_state = "damaged_light"
+ return
+
+
+
+
+
+
+/obj/machinery/node/attack_hand(mob/user)
+// . = ..() // DONT uncomment, unless you wanna give it power consumption :) (P.S. I DONT want that)
+ to_chat(user, "Node's screen blinks for a brief moment revealing it's statistics")
+ to_chat(user, "Linked nodes:")
+ for(var/obj/machinery/machine in neighbours)
+ to_chat(user, "[machine.name] [dist3D(src, machine)]m away")
+ to_chat(user, "Current coverage is at [round(activemarkerlist.len / localmarkerlist.len * 100, 0.1)]%")
+ //pick_up_emplacement(user) // later
+
+
+
+
+
+
+// Some structures need node in radius to power up and work, this is the proc that searches (e.g. emplacements)
+/obj/machinery/node/proc/search_for_machines()
+ for(var/obj/machinery/machine in circlerange(src, EX_NODE_DISTANCE))
+ SEND_SIGNAL(machine, COMSIG_EX_CONNECT)
+
+
+
+
+
+
+//Searches for other nodes EVEN BETWEEN Z LEVELS.
+/obj/machinery/node/proc/search_for_nodes()
+ for(var/obj/machinery/node/N in excelsior_nodes)
+ if(dist3D(src, N) <= EX_NODE_DISTANCE*2 && N != src)
+ connect(N, TRUE)
+ N.connect(src, TRUE)
+ if(N.core)
+ src.spread_signal(N.core)
+
+
+
+
+
+
+// # Adds machine to either list of connected nodes or list of connected machines as specified by is_node argument
+//Checks if machine is on the list before adding to avoid dupes
+/obj/machinery/node/proc/connect(var/obj/machinery/M, var/is_node = FALSE)
+ if(is_node)
+ if(!neighbours.Find(M)) // > Connect to node
+ neighbours.Add(M)
+ else
+ if(!linked.Find(M)) // > Connect to emplacements, for example.
+ linked.Add(M) // - If such machinery demands Node's connection to work (sentry)
+
+
+
+
+
+
+//Removes machine from list of nodes or list of machines as specifed by is_node argument
+//Checks if machine is on the list before deletion
+/obj/machinery/node/proc/disconnect(var/obj/machinery/M, var/is_node = FALSE)
+ if(is_node)
+ if(neighbours.Find(M))
+ neighbours.Remove(M)
+ else
+ if(linked.Find(M))
+ linked.Remove(M)
+
+
+
+
+
+
+
+
+/obj/machinery/node/proc/spread_signal(var/center) // # Nodes check if they are connected to Centor, directly or not (node chain)
+ if(!center) // Special case - we are trying to reset node's core
+ core = null
+ update_icon()
+ if(core) // 1. If not - connect to Centor
+ return // 2. Pass "core connected" status through the chain
+ core = center
+ update_icon()
+ core.antennas_to_haven.Add(src)
+ for(var/obj/machinery/node/N in neighbours)
+ N.spread_signal(center)
+ // NOTE: "Core+Node gameplay is defined by territorial control of excelsior
+
+/obj/machinery/node/verb/pack()
+ set name = "Pack node"
+ set category = "Object"
+ set src in oview(1)
+
+ if(usr.stat || !usr.canmove || usr.restrained())
+ return
+ if(!is_excelsior(usr))
+ to_chat(usr, "It doesn't listen to you.")
+ return
+ to_chat(usr, "You've started packing up the node.")
+ if(do_after(usr, 2 SECONDS, src))
+ new /obj/item/machinery_crate/excelsior/node(loc)
+ Destroy()
+
+
+
+
+ /*
+ * Nodes speak into Excelsior comms
+ */
+
+
+
+
+
+// # DETECTION of non-excelsior human/robot
+
+// - The act of yapping itself
+
+/obj/machinery/proc/talk(message) // the act of yapping
+ var/datum/faction/F = get_faction_by_id(FACTION_EXCELSIOR)
+ if(!F)
+ return
+ F.communicate_inanimate(src, message)
+
+
+
+// - The thinking behind reporting a bypasser
+
+/obj/machinery/node/proc/intruder_alert(var/mob/living/intruder)// TODO: Move to KOMPAK logs
+ // TODO: Ask Node what the human has in weapons through KPK
+ if(world.time - report_cooldown >= 15 SECONDS) // Don't report the same person twice in x seconds
+ intruder_list = list()
+ report_cooldown = world.time
+
+ if(intruder_list.Find(intruder)) // We don't need the same guy reported
+ return
+ if(istype(intruder, /mob/living/carbon/human))
+ if(intruder.stats.getPerk(PERK_VAGABOND) || intruder.name == "Unknown")
+ talk("SPOTTED: Non-crew [intruder.name] spotted at [name]")
+ intruder_list.Add(intruder)
+ return
+ talk("SPOTTED :: Human [intruder.name] spotted at [name]")
+ intruder_list.Add(intruder)
+ return
+ if(istype(intruder, /mob/living/silicon/robot))
+ talk("SPOTTED :: Robot [intruder.name] spotted at [name]")
+ intruder_list += intruder
+ return
+
+ //MESSAGE
+ // //clean stuff that was reported
+
+
+
+
+
+
+
+
+ /******************************
+ * Influence *
+ *******************************/
+
+/* [?] INFLUENCE is an invisible obj called [excelsior_influence], it produces "teleporter energy" for Excelsior --- [ex_teleporter.dm]
+ 1. NODE spawns around itself [excelsior_influence] in a radius, defined by EX_NODE_DISTANCE --- [_excelsior_defines.dm]
+ 2. INFLUENCE checks the [turf] it stands on, if it has whitelisted turfs (floortiles & low walls), if not - it won't be eligible for power generation.
+ - by design walls and space is unwelcome as "territory that rewards you"
+
+ 3. NODES connected to CENTOR (let's call it "centored" or smth), or if NODE connected to another "centored" NODE, only then does it produce teleporter energy,
+ if it's cut off from the "base" (CENTOR), it doesn't turn on sentries and doesn't give telepower.
+*/
+
+/obj/effect/effect/excelsior_influence //zone make excel energy :) // # Visible on Influence Mode on centor_kpk [KPK.dm].
+ var/active = FALSE // - To find the HUD code do either:
+ var/obj/machinery/node/node // > Search by "process_excel_hud" in
+ // > hud.dm [code\defines\procs][line 60~]
+
+
+
+
+/obj/effect/effect/excelsior_influence/New(loc, var/obj/machinery/node/creator) // > Code one "thinking layer" above is define_influence()
+ ..(loc)
+ icon = null
+ icon_state = null
+ node = creator
+ validate()
+ RegisterSignal(src, COMSIG_TURF_LEVELUPDATE, PROC_REF(validate)) // # Any tile on map built/destroyed:
+ // 1. Sends a COMSIG_TURF_LEVELUPDATE signal
+ // To every obj standing on top of said tile
+ // 2. It's up to obj to receive that signal
+ // 3. Marker receives that signal >> validate()
+
+
+
+
+
+
+/obj/effect/effect/excelsior_influence/Destroy()
+ . = ..()
+ UnregisterSignal(src, COMSIG_TURF_LEVELUPDATE) // no phantom pain sry
+
+
+
+
+
+/obj/effect/effect/excelsior_influence/proc/validate() // # Checks if influence zone is "active".
+ if(!node) // It's active if...
+ Destroy()
+ return
+ var/turf/my_turf = get_turf(src)
+ for(var/type in excelsior_turf_whitelist) // ...Stuff inside it matches whitelist --- [_excelsior_defines.dm]
+ if(istype(my_turf, type)) // Every obj inside whitelist allows "influence tiles" to generate excel energy.
+ active = TRUE // We chose it to be floors and low walls. Walls are punished we hate walls.
+ if(!node.activemarkerlist.Find(src)) // That may change because of YOU, you stinky game designer, that's why the list exists.
+ node.activemarkerlist.Add(src)
+ return TRUE
+ active = FALSE
+ if(node.activemarkerlist.Find(src))
+ node.activemarkerlist.Remove(src)
+ return FALSE
+
+
+
+
+
+/obj/effect/effect/excelsior_influence/Crossed(atom/movable/O)
+ var/mob/living/intruder = O
+ if(!intruder)
+ return
+ if(!istype(intruder, /mob))
+ return
+ if(!is_excelsior(intruder)) // 1. If EXCELSIOR = STOP
+ if(!intruder.restrained() && !intruder.lying) // 2. Arrested/Unconcious/Crawling people? - don't care (intentional)
+ node.intruder_alert(intruder) // 3. All good? report the good guy get his ass!!
+
+
+
+
+// # Below you is a sendPath() proc.
+//
+// # What does it do?
+// It's background thinking. Gameplay-wise it's unseen. To understand where and how this bullshit happens:
+// 1. Go to [KPK.dm]
+// 2. press CTRL+F
+// 3. enter "#pathfinder" into the field
+//
+// ---
+// VARS:
+// [end] during gameplay: it's a node we chose on KPK using UI button called > (press Z, then Find Path)
+// [already_checked] makes sure we dont fucking crash the game because proc is recursive. (Nodes call to each other sendPath proc)
+// [way_to_go] is a list to which we add [datum "junctions"]. Contents are passed to proc caller's list: [ihaveplacestobe], the caller is [centor_kpk] --- [KPK.dm]
+//
+
+
+/obj/machinery/node/proc/sendPath(var/obj/machinery/node/end, var/list/already_checked = list(), var/obj/item/centor_kpk/kpk, var/list/way_to_go = list())
+
+ if(src in already_checked) // Prevent recursiveness of the proc from happening 2+ times by checking the funny list.
+ return
+
+ already_checked.Add(src) // if we didn't sendPath(), make sure it knows we checked it due to [already_checked] list()
+
+//
+ if(src == end) // We are the node to show path to! WE are the destination
+ kpk.ihaveplacestobe = way_to_go // kpk has [ihaveplacestobe] list with invis objs, on top them we draw "holo arrows" (overlay) --- [KPK.dm]
+ kpk.node_here = end // this is for reverse_arrows() proc, we cant lie to user about direction of destination --- [KPK.dm]
+ kpk.refresh_overlay()
+ return
+
+// Code-wise: We use global [excelsior_junctions] list to form another list full of [excelsior_junction] of from [closest] node to [destination]
+// Explained for meatbags:
+// excelsior_junction is a holographic road consisting of arrows
+// every junction created goes into the global list excelsior_junctions with an S at the end!!!
+// Below these comments, NODE, checks global list, asks if its inside any excelsior_junction (which stores 2 nodes, any path in the world has A and B)
+// - WHY? We need a list of JUNCTIONS leading through NODES leading to our DESTINATION
+// - SO: we do it by sending a proc wave through that remembers what nodes it visited, until it meets the DESTINATION
+
+ for(var/datum/excelsior_junction/short_road in excelsior_junctions)
+
+ if(short_road.first == src)
+ var/list/transmit_way_to_go = list()
+ transmit_way_to_go.Add(way_to_go)
+ transmit_way_to_go.Add(short_road)
+ short_road.second.sendPath(end, already_checked, kpk, transmit_way_to_go)
+
+ else if(short_road.second == src)
+ var/list/transmit_way_to_go = list()
+ transmit_way_to_go.Add(way_to_go)
+ transmit_way_to_go.Add(short_road)
+ short_road.first.sendPath(end, already_checked, kpk, transmit_way_to_go)
+
+// APPENDIX?
+/*
+* FUN FACT: "Packaged Node" is inside [machinery_crates.dm]
+*/
+
+
diff --git a/code/game/objects/items/machinery_crates.dm b/code/game/objects/items/machinery_crates.dm
index a5c8f0bab56..0ad2f9b7597 100644
--- a/code/game/objects/items/machinery_crates.dm
+++ b/code/game/objects/items/machinery_crates.dm
@@ -1,6 +1,7 @@
/obj/item/machinery_crate
name = "IKEA"
desc = "Integrated Kit of Engineering Assembly."
+ description_info = "To turn on construction - tigthen the bolts and activate it."
icon = 'icons/obj/machinery_crates.dmi'
icon_state = "standart"
@@ -9,14 +10,16 @@
slowdown_hold = 0.5
throw_range = 2
matter = list(MATERIAL_PLASTIC = 10, MATERIAL_PLASTEEL = 5, MATERIAL_STEEL = 10)
-
var/machine_name
- var/constructing_machine
- var/constructing_duration = 50
+ var/obj/constructing_machine
+ var/constructing_duration = 10
var/anim
var/animation_duration = 5
var/activated
+ var/animation_sound // insert sound path that will play during animation
var/can_place_on_table = FALSE
+ var/stealth = FALSE // if TRUE - deploy sound will NOT play through walls
+ var/sound_delay = 0
/obj/item/machinery_crate/examine(mob/user, extra_description = "")
extra_description += "The piece of paper on the side reads: [machine_name]"
@@ -44,9 +47,12 @@
if(anim)
invisibility = INVISIBILITY_MAXIMUM
var/atom/movable/overlay/animation = new(loc)
- animation.icon = 'icons/obj/machinery_crates.dmi'
+ animation.icon = icon
+ animation.layer = constructing_machine.layer
animation.master = src
animation.density = TRUE
+ spawn(sound_delay)
+ anim_sound()
flick(anim, animation)
activated = TRUE
addtimer(CALLBACK(src, PROC_REF(finish_construction), animation), animation_duration)
@@ -69,10 +75,19 @@
return TRUE
return FALSE
+/obj/item/machinery_crate/proc/anim_sound()
+ if(animation_sound)
+ playsound(src, animation_sound, 100, 1, ignore_walls = !stealth)
+
/obj/item/machinery_crate/excelsior
icon_state = "excelsior"
bad_type = /obj/item/machinery_crate/excelsior
+/obj/item/machinery_crate/excelsior/excelsior_teleporter
+ name = "Packaged Excelsior Teleporter"
+ machine_name = "Excelsior Teleporter"
+ constructing_machine = /obj/machinery/complant_teleporter
+
/obj/item/machinery_crate/excelsior/shield
name = "shield generator IKEA"
icon_state = "shield_gen"
@@ -108,6 +123,15 @@
machine_name = "diesel generator"
constructing_machine = /obj/machinery/power/port_gen/pacman/diesel
+/obj/item/machinery_crate/excelsior/diesel_generator/finish_construction(atom/movable/overlay/animation)
+ var/obj/machinery/power/port_gen/pacman/diesel = new constructing_machine(get_turf(src))
+ diesel.anchored = TRUE
+ diesel.connect_to_network()
+ if(!QDELETED(animation))
+ qdel(animation)
+ if(!QDELETED(src))
+ qdel(src)
+
/obj/item/machinery_crate/excelsior/turret
name = "turret IKEA"
constructing_duration = 130
@@ -152,3 +176,16 @@
name = "oven IKEA"
machine_name = "oven"
constructing_machine = /obj/machinery/cooking_with_jane/oven
+
+/obj/item/machinery_crate/excelsior/node
+ name = "Excelsior Node Package"
+ machine_name = "Excelsior Node"
+ animation_sound = 'sound/machines/excelsior/node_deploy.ogg'
+ icon = 'icons/obj/machines/excelsior/corenode/node.dmi'
+ icon_state = "node_item"
+ anim = "deployment"
+ animation_duration = 17
+ constructing_machine = /obj/machinery/node
+ stealth = TRUE
+ sound_delay = 5
+
diff --git a/code/game/objects/items/weapons/circuitboards/machinery/excelsior.dm b/code/game/objects/items/weapons/circuitboards/machinery/excelsior.dm
index efdf458b5d7..250ed363a6b 100755
--- a/code/game/objects/items/weapons/circuitboards/machinery/excelsior.dm
+++ b/code/game/objects/items/weapons/circuitboards/machinery/excelsior.dm
@@ -113,3 +113,25 @@
/obj/item/cell/medium = 1
)
spawn_blacklisted = TRUE
+
+/obj/item/electronics/circuitboard/excelsior_node
+ name = T_BOARD("excelsior node")
+ build_path = /obj/machinery/node
+ board_type = "machine"
+ origin_tech = list(TECH_MAGNET = 4, TECH_COVERT = 2)
+ req_components = list(
+ /obj/item/device/assembly/prox_sensor = 1,
+ /obj/item/cell/medium = 1
+ )
+ spawn_blacklisted = TRUE
+
+/obj/item/electronics/circuitboard/centor
+ name = T_BOARD("excelsior centor")
+ build_path = /obj/machinery/centor
+ board_type = "machine"
+ origin_tech = list(TECH_MAGNET = 4, TECH_COVERT = 2)
+ req_components = list(
+ /obj/item/device/assembly/prox_sensor = 1,
+ /obj/item/cell/medium = 1
+ )
+ spawn_blacklisted = TRUE
diff --git a/code/game/sound.dm b/code/game/sound.dm
index fef1a2daeab..4c3a4df5c25 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -353,7 +353,7 @@ var/list/rummage_sound = list(\
sound_to(listener, output)
-
+// :: test tool for playsound() at the end of the file ::
/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, is_global, frequency, is_ambiance, ignore_walls = TRUE, \
zrange = 2, override_env, envdry, envwet, use_pressure = TRUE)
@@ -610,3 +610,107 @@ var/const/FALLOFF_SOUNDS = 0.5
if (timer_handle)
deltimer(timer_handle)
qdel(src)
+
+
+
+
+/* DEBUG AND TEST TOOLS */
+
+// :: playsound() test tool ::
+// uncomment for test
+// comment after you're done
+
+/*
+
+/obj/item/playsound
+ name = "\improper Playsound test tool"
+ desc = "Use to open menu, click on turf to create a sound."
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "rcd"
+ opacity = 0
+ density = FALSE
+ anchored = FALSE
+
+ var/how_many_tests // how many times are you gonna play the sound before new test inputs?
+ var/delay = 0 // delay the sound after you click somewhere
+ var/soundin = 'sound/machines/excelsior/node_deploy.ogg' // INSERT SOUND FOR TEST HERE BEFORE COMPILING
+ var/vol = 100
+ var/vary = 1
+ var/extrarange
+ var/falloff
+ var/is_global
+ var/frequency
+ var/is_ambiance
+ var/ignore_walls // TRUE/FALSE (this means if you cant see sound src then its NOT PLAYING. FIND OUT THE HARD WAY THANK ME FUCKING LATER)
+ var/zrange // default 2
+ var/override_env
+ var/envdry
+ var/envwet
+ var/use_pressure
+
+/obj/item/playsound/afterattack(atom/A, mob/user as mob)
+
+ if(istype(A, /turf))
+ to_chat(user, "Playing with [delay].")
+ spawn(delay) playsound(A, soundin, vol, vary, extrarange, falloff, is_global, frequency, is_ambiance, ignore_walls, \
+ zrange, override_env, envdry, envwet, use_pressure)
+ else
+ to_chat(user, SPAN_WARNING("Click on turf motherfucker."))
+
+/obj/item/playsound/attack_self()
+ configure()
+/obj/item/playsound/proc/configure()
+ var/setting
+ var/list/setting_types = list("delay", "soundin", "vol", "vary", "extrarange", "falloff", "is_global", "frequency", "is_ambiance", "ignore_walls",
+ "zrange", "override_env", "envdry", "envwet", "use_pressure")
+
+ setting = input("What kind of setting?","Setting Type") as null|anything in setting_types
+
+ if(!setting)
+ return
+
+ switch(setting)
+ if("delay")
+ delay = input("Set delay for spawn()","Num") as num
+ playsound()
+
+ if("soundin")
+ soundin = input("Pick file:","File") as file
+
+ if("vol")
+ vol = input("Enter new number:","Num") as num
+
+ if("vary")
+ vary = input("Enter new number:","Num") as num
+
+ if("extrarange")
+ extrarange = input("Enter new number:","Num") as num
+
+ if("falloff")
+ falloff = input("Enter new number:","Num") as num
+
+ if("is_global")
+ is_global = input("Enter new number:","Num") as num
+
+ if("frequency")
+ frequency = input("Enter new number:","Num") as num
+
+ if("is_ambiance")
+ is_ambiance = input("Enter new number:","Num") as num
+
+ if("ignore_walls")
+ ignore_walls = input("Enter new number:","Num") as num
+
+ if("zrange")
+ zrange = input("Enter new number:","Num") as num
+
+ if("envdry")
+ envdry = input("Enter new number:","Num") as num
+
+ if("envwet")
+ envwet = input("Enter new number:","Num") as num
+
+ if("use_pressure")
+ use_pressure = input("Enter new number:","Num") as num
+
+*/
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index 421bb3cfcb7..d5112b5a6eb 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -131,6 +131,7 @@ nanoui is used to open and update nano browser uis
add_script("nano_base_callbacks.js") // The NanoBaseCallbacks JS, this is used to set up (before and after update) callbacks which are common to all UIs
add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all UIs
add_stylesheet("shared.css") // this CSS sheet is common to all UIs
+ add_stylesheet("excelsior.css") // this CSS sheet is common to excelsior UIs
add_stylesheet("tgui.css") // this CSS sheet is common to all UIs
add_stylesheet("icons.css") // this CSS sheet is common to all UIs
diff --git a/icons/inventory/eyes/icon.dmi b/icons/inventory/eyes/icon.dmi
index a80bf924769..167746e287f 100644
Binary files a/icons/inventory/eyes/icon.dmi and b/icons/inventory/eyes/icon.dmi differ
diff --git a/icons/inventory/eyes/mob.dmi b/icons/inventory/eyes/mob.dmi
index f9955fefec7..2ce8817a8f2 100644
Binary files a/icons/inventory/eyes/mob.dmi and b/icons/inventory/eyes/mob.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/centor.dmi b/icons/obj/machines/excelsior/corenode/centor.dmi
new file mode 100644
index 00000000000..24bea39ad42
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/centor.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/emplacement.dmi b/icons/obj/machines/excelsior/corenode/emplacement.dmi
new file mode 100644
index 00000000000..21944907f0e
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/emplacement.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/eris_excel_core_48x.dmi b/icons/obj/machines/excelsior/corenode/eris_excel_core_48x.dmi
new file mode 100644
index 00000000000..c81a1dd3b39
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/eris_excel_core_48x.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/eris_excel_core_inside.dmi b/icons/obj/machines/excelsior/corenode/eris_excel_core_inside.dmi
new file mode 100644
index 00000000000..0066539d110
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/eris_excel_core_inside.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/node.dmi b/icons/obj/machines/excelsior/corenode/node.dmi
new file mode 100644
index 00000000000..b26114cd4de
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/node.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/node_item.dmi b/icons/obj/machines/excelsior/corenode/node_item.dmi
new file mode 100644
index 00000000000..7b4b4bb92ba
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/node_item.dmi differ
diff --git a/icons/obj/machines/excelsior/corenode/pda.dmi b/icons/obj/machines/excelsior/corenode/pda.dmi
new file mode 100644
index 00000000000..0caa252cc18
Binary files /dev/null and b/icons/obj/machines/excelsior/corenode/pda.dmi differ
diff --git a/icons/obj/machines/excelsior/emplacement.dmi b/icons/obj/machines/excelsior/emplacement.dmi
new file mode 100644
index 00000000000..21944907f0e
Binary files /dev/null and b/icons/obj/machines/excelsior/emplacement.dmi differ
diff --git a/nano/css/excelsior.css b/nano/css/excelsior.css
new file mode 100644
index 00000000000..50756706fe1
--- /dev/null
+++ b/nano/css/excelsior.css
@@ -0,0 +1,194 @@
+
+@font-face {
+ font-family: "IBM BIOS";
+ src: url(WebPlus_IBM_BIOS.woff);
+}
+
+.scanlines {
+ position: relative;
+ overflow: hidden;
+}
+.scanlines:before, .scanlines:after {
+ display: block;
+ pointer-events: none;
+ content: "";
+ position: absolute;
+}
+.scanlines:before {
+ width: 100%;
+ height: 2px;
+ z-index: 2147483649;
+ background: rgba(0, 0, 0, 0.3);
+ opacity: 0.75;
+ animation: scanline 6s linear infinite;
+}
+.scanlines:after {
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 2147483648;
+ background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.3) 51%);
+ background-size: 100% 4px;
+ animation: scanlines 1s steps(60) infinite;
+}
+
+/* ANIMATE UNIQUE SCANLINE */
+@keyframes scanline {
+ 0% {
+ transform: translate3d(0, 200000%, 0);
+ }
+}
+@keyframes scanlines {
+ 0% {
+ background-position: 0 50%;
+ }
+}
+
+.biosfont {
+ font-family: "IBM BIOS";
+ font-size: 8px;
+ line-height: 8px;
+ color: #FF5F1F;
+}
+
+.foreground {
+ background: #190903;
+
+ position:fixed;
+ padding:0;
+ margin:0;
+
+ top:0;
+ left:0;
+
+ width: 100%;
+ height: 100%;
+}
+
+.ex-border {
+ border-style: solid dashed solid dashed;
+ border-width: 3px;
+ margin-left: 5%;
+ margin-right: 5%;
+ margin-top: 1%;
+ height: 75%;
+}
+
+.ex-title {
+ align-self: top;
+ text-align: center;
+ margin-top: 3%;
+ font-size: 12px;
+ line-height: 12px;
+}
+
+.ex-line {
+ position: absolute;
+ left: 15%;
+ border: 1px solid #FF5F1F;
+ width: 66%;
+ height: 0px;
+ background-color: #FF5F1F;
+}
+
+.ex-underline {
+ position: absolute;
+ left: 10.5%;
+ border: 1px dashed #FF5F1F;
+ width: 75%;
+ height: 0px;
+ background-color: #190903;
+ z-index: -1;
+}
+
+.ex-content {
+ margin-top: 10%;
+ margin-left: 10%;
+ text-align: left;
+ line-height: 8px;
+ font-size: 8px;
+}
+
+.ex-button {
+ float: left;
+}
+
+.ex-button:hover {
+ background-color: #FF5F1F;
+ color: #190903;
+}
+
+.ex-bottom-text {
+ position: absolute;
+ margin-left: 10%;
+ width: 70%;
+ bottom: 5%;
+ text-align: center;
+}
+
+.ex-logo {
+ color: #FF5F1F;
+ white-space: nowrap;
+}
+
+.komulink {
+ display: inline-block;
+}
+
+.komulink:hover {
+ background-color: #FF5F1F;
+ color: #190903;
+}
+
+.file-tree ul {
+ margin: 0;
+ padding-left: 3rem;
+ list-style: none;
+ line-height: 1.25;
+ position: relative;
+ overflow: hidden;
+}
+
+.file-tree li:not(:last-child) > ul::before {
+ content: '\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 1em;
+}
+
+.file-tree > ul {
+ margin: 0;
+ padding: 0;
+}
+
+.file-tree li {
+ position: relative;
+}
+
+.file-tree li::before {
+ content: '\251C\2500\2500\0020';
+}
+
+.file-tree li:last-child::before {
+ content: '\2514\2500\2500\0020';
+}
+
+.ex-popup {
+ display: flex;
+ position: fixed;
+ top: 0; left: 0;
+ width: 100%; height: 100%;
+ background: #190903;
+ justify-content: center;
+ align-items: center;
+}
+
+.ex-popup-content {
+ background-color: #FF5F1F;
+ color: #190903;
+ padding: 3%;
+ text-align: center;
+}
diff --git a/nano/images/WebPlus_IBM_BIOS.woff b/nano/images/WebPlus_IBM_BIOS.woff
new file mode 100644
index 00000000000..a5192219486
Binary files /dev/null and b/nano/images/WebPlus_IBM_BIOS.woff differ
diff --git a/nano/js/nano_base_helpers.js b/nano/js/nano_base_helpers.js
index 00309a58d10..95816df2d4a 100644
--- a/nano/js/nano_base_helpers.js
+++ b/nano/js/nano_base_helpers.js
@@ -76,6 +76,35 @@ NanoBaseHelpers = function ()
return '' + iconHtml + text + '
';
},
+ // Generate a Byond link but more commie
+ komulink: function( text, icon, parameters, status, elementClass, elementId) {
+
+ var iconHtml = '';
+ var iconClass = 'noIcon';
+ if (typeof icon != 'undefined' && icon)
+ {
+ iconHtml = '';
+ iconClass = text ? 'hasIcon' : 'onlyIcon';
+ }
+
+ if (typeof elementClass == 'undefined' || !elementClass)
+ {
+ elementClass = 'komulink';
+ }
+
+ var elementIdHtml = '';
+ if (typeof elementId != 'undefined' && elementId)
+ {
+ elementIdHtml = 'id="' + elementId + '"';
+ }
+
+ if (typeof status != 'undefined' && status)
+ {
+ return '' + iconHtml + text + '
';
+ }
+
+ return '' + iconHtml + text + '
';
+ },
// Round a number to the nearest integer
round: function(number) {
return Math.round(number);
diff --git a/nano/templates/excelsior_kpk.tmpl b/nano/templates/excelsior_kpk.tmpl
new file mode 100644
index 00000000000..64f7f6ab59e
--- /dev/null
+++ b/nano/templates/excelsior_kpk.tmpl
@@ -0,0 +1,75 @@
+
+
+{{for data.error_list}}
+
+{{/for}}
+
+
+ Welcome, Infiltrator
+
+
+
+
+
+ ROOT
+
+ - PROGRAMS:
+
+ - {{:helper.komulink('Build path', null, {'open_path_dio' : 1}, null)}}
+ {{if data.path_diologe}}
+
+ - Start mapping a new path?
+ - {{:helper.komulink('Yes', null, {'start_pathfind' : 1}, null)}}
+ - {{:helper.komulink('No', null, {'close_path_dio' : 1}, null)}}
+
+ {{/if}}
+ {{if data.current_path}}
+
+ - PATH RECORDING IN PROGRESS
+ - Current node: {{:data.current_node}}
+ - {{:helper.komulink('Finish path', null, {'end_pathfind' : 1}, null)}}
+ - {{:helper.komulink('Cancel', null, {'cancel_pathfind' : 1}, null)}}
+
+ {{/if}}
+
+ - {{:helper.komulink('Find path', null, {'open_viewpath_dio' : 1}, null)}}
+ {{if data.viewpath_dio}}
+
+ - {{:helper.komulink('Cancel', null, {'close_viewpath_dio' : 1}, null)}}
+ {{for data.node_list}}
+ - {{:helper.komulink(value.name_n, null, value.commands_n, null)}}
+ {{/for}}
+
+ {{/if}}
+
+ {{if data.mappings_dio}}
+ - {{:helper.komulink('Mappings:', null, {'close_mappings_dio' : 1}, null)}}
+
+ - Current status: {{:data.overlay_enabled}}
+ - {{:helper.komulink('Toggle mappings', null, {'toggle_overlay' : 1}, null)}}
+ - {{:helper.komulink('Influence mode', null, {'influence_overlay' : 1}, null)}}
+ - {{:helper.komulink('Pathfind mode', null, {'pathfind_overlay' : 1}, null)}}
+
+
+ {{else}}
+ - {{:helper.komulink('Mappings:', null, {'open_mappings_dio' : 1}, null)}}
+ {{/if}}
+
+
+
+
+
+
+
KOMPAK
+ Made by Stalov
+
+
+
diff --git a/nano/templates/excelsior_node.tmpl b/nano/templates/excelsior_node.tmpl
new file mode 100644
index 00000000000..de1282bb392
--- /dev/null
+++ b/nano/templates/excelsior_node.tmpl
@@ -0,0 +1,12 @@
+Welcome Comrade
+
+ {{for data.node_list}}
+
+ | {{:value.name}} |
+ {{:value.x}} |
+ {{:value.y}} |
+ {{:value.z}} |
+
+ {{/for}}
+
+
diff --git a/nano/templates/excelsior_teleporter.tmpl b/nano/templates/excelsior_teleporter.tmpl
index 04ea12c92a6..b443dc6e140 100644
--- a/nano/templates/excelsior_teleporter.tmpl
+++ b/nano/templates/excelsior_teleporter.tmpl
@@ -7,6 +7,7 @@ Used In File(s): code/game/machinery/excelsior/ex_teleporter.dm
Energy:
+ Energy income: {{:helper.round(data.energy*10 - data.old_energy*10)/10}}
{{:helper.displayBar(data.energy, 0, data.maxEnergy, 'good', data.energy + ' Power')}}
@@ -80,7 +81,41 @@ Used In File(s): code/game/machinery/excelsior/ex_teleporter.dm
{{/for}}
-
+
+
+
+ | Name |
+ Energy Cost |
+
+ {{for data.circuits}}
+
+ | {{:value.name_c}} |
+ {{:helper.link(value.price_c, 'circle-arrow-s', value.commands_c, null, 'fixedLeft')}} |
+
+ {{/for}}
+
+
+
+
+ | Name |
+ Energy Cost |
+
+ {{for data.excelsior_kits}}
+
+ | {{:value.name_e}} |
+ {{:helper.link(value.price_e, 'circle-arrow-s', value.commands_e, null, 'fixedLeft')}} |
+
+ {{/for}}
+
+
Crates:
diff --git a/sound/machines/excelsior/centor_close.ogg b/sound/machines/excelsior/centor_close.ogg
new file mode 100644
index 00000000000..48e2c3db7e5
Binary files /dev/null and b/sound/machines/excelsior/centor_close.ogg differ
diff --git a/sound/machines/excelsior/centor_detonation.ogg b/sound/machines/excelsior/centor_detonation.ogg
new file mode 100644
index 00000000000..49ace1e1793
Binary files /dev/null and b/sound/machines/excelsior/centor_detonation.ogg differ
diff --git a/sound/machines/excelsior/centor_open.ogg b/sound/machines/excelsior/centor_open.ogg
new file mode 100644
index 00000000000..197237cd7f7
Binary files /dev/null and b/sound/machines/excelsior/centor_open.ogg differ
diff --git a/sound/machines/excelsior/node_deploy.ogg b/sound/machines/excelsior/node_deploy.ogg
new file mode 100644
index 00000000000..34165f5af0f
Binary files /dev/null and b/sound/machines/excelsior/node_deploy.ogg differ