diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
index d7e7377a26..cb460bd2f1 100644
--- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
+++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm
@@ -20,7 +20,7 @@
var/hibernate = 0 //Do we even process?
var/scrubbing = 1 //0 = siphoning, 1 = scrubbing
- var/list/scrubbing_gas = list("carbon_dioxide", "phoron")
+ var/list/scrubbing_gas = list("carbon_dioxide")
var/panic = 0 //is this scrubber panicked?
diff --git a/code/ZAS/Phoron.dm b/code/ZAS/Phoron.dm
index fc0d5a1191..b8ca13521c 100644
--- a/code/ZAS/Phoron.dm
+++ b/code/ZAS/Phoron.dm
@@ -40,15 +40,11 @@ var/image/contamination_overlay = image('icons/effects/contamination.dmi')
obj/var/contaminated = 0
-obj/var/phoronproof = 0
-
/obj/item/proc/can_contaminate()
//Clothing and backpacks can be contaminated.
if(flags & PHORONGUARD)
return 0
- else if(phoronproof == 1)
- return 0
else if(istype(src,/obj/item/weapon/storage/backpack))
return 0 //Cannot be washed :(
else if(istype(src,/obj/item/clothing))
@@ -144,7 +140,7 @@ obj/var/phoronproof = 0
//Checks if the head is adequately sealed. //This is just odd. TODO: Make this respect the body_parts_covered stuff like thermal gear does.
if(head)
if(vsc.plc.PHORONGUARD_ONLY)
- if(head.flags & PHORONGUARD || head.phoronproof)
+ if(head.flags & PHORONGUARD)
return 1
else if(head.body_parts_covered & EYES)
return 1
@@ -156,7 +152,7 @@ obj/var/phoronproof = 0
for(var/obj/item/protection in list(wear_suit, gloves, shoes)) //This is why it's odd. If I'm in a full suit, but my shoes and gloves aren't phoron proof, damage.
if(!protection)
continue
- if(vsc.plc.PHORONGUARD_ONLY && !(protection.flags & PHORONGUARD) && !protection.phoronproof)
+ if(vsc.plc.PHORONGUARD_ONLY && !(protection.flags & PHORONGUARD))
return 0
coverage |= protection.body_parts_covered
diff --git a/code/__defines/color.dm b/code/__defines/color.dm
new file mode 100644
index 0000000000..ba9efe20de
--- /dev/null
+++ b/code/__defines/color.dm
@@ -0,0 +1,17 @@
+//Color defines used by the assembly detailer.
+#define COLOR_ASSEMBLY_BLACK "#545454"
+#define COLOR_ASSEMBLY_BGRAY "#9497AB"
+#define COLOR_ASSEMBLY_WHITE "#E2E2E2"
+#define COLOR_ASSEMBLY_RED "#CC4242"
+#define COLOR_ASSEMBLY_ORANGE "#E39751"
+#define COLOR_ASSEMBLY_BEIGE "#AF9366"
+#define COLOR_ASSEMBLY_BROWN "#97670E"
+#define COLOR_ASSEMBLY_GOLD "#AA9100"
+#define COLOR_ASSEMBLY_YELLOW "#CECA2B"
+#define COLOR_ASSEMBLY_GURKHA "#999875"
+#define COLOR_ASSEMBLY_LGREEN "#789876"
+#define COLOR_ASSEMBLY_GREEN "#44843C"
+#define COLOR_ASSEMBLY_LBLUE "#5D99BE"
+#define COLOR_ASSEMBLY_BLUE "#38559E"
+#define COLOR_ASSEMBLY_PURPLE "#6F6192"
+#define COLOR_ASSEMBLY_HOT_PINK "#FF69B4"
\ No newline at end of file
diff --git a/code/__defines/integrated_circuits.dm b/code/__defines/integrated_circuits.dm
new file mode 100644
index 0000000000..b2859a232d
--- /dev/null
+++ b/code/__defines/integrated_circuits.dm
@@ -0,0 +1,3 @@
+// Methods of obtaining a circuit.
+#define IC_SPAWN_DEFAULT 1 // If the circuit comes in the default circuit box and able to be printed in the IC printer.
+#define IC_SPAWN_RESEARCH 2 // If the circuit design will be available in the IC printer after upgrading it.
\ No newline at end of file
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index 17c3ab8923..d1344e55c5 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -1,18 +1,3 @@
-//Timing subsystem
-//Don't run if there is an identical unique timer active
-#define TIMER_UNIQUE 0x1
-//For unique timers: Replace the old timer rather then not start this one
-#define TIMER_OVERRIDE 0x2
-//Timing should be based on how timing progresses on clients, not the sever.
-// tracking this is more expensive,
-// should only be used in conjuction with things that have to progress client side, such as animate() or sound()
-#define TIMER_CLIENT_TIME 0x4
-//Timer can be stopped using deltimer()
-#define TIMER_STOPPABLE 0x8
-//To be used with TIMER_UNIQUE
-//prevents distinguishing identical timers with the wait variable
-#define TIMER_NO_HASH_WAIT 0x10
-#define TIMER_NO_INVOKE_WARNING 600 //number of byond ticks that are allowed to pass before the timer subsystem thinks it hung on something
#define INITIALIZATION_INSSATOMS 0 //New should not call Initialize
#define INITIALIZATION_INNEW_MAPLOAD 1 //New should call Initialize(TRUE)
@@ -22,15 +7,6 @@
#define INITIALIZE_HINT_LATELOAD 1 //Call LateInitialize
#define INITIALIZE_HINT_QDEL 2 //Call qdel on the atom
-//type and all subtypes should always call Initialize in New()
-#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\
- ..();\
- if(!initialized) {\
- args[1] = TRUE;\
- SSatoms.InitAtom(src, args);\
- }\
-}
-
// SS runlevels
#define RUNLEVEL_INIT 0 // "Initialize Only" - Used for subsystems that should never be fired (Should also have SS_NO_FIRE set)
@@ -58,6 +34,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_HOLOMAPS -5
#define INIT_ORDER_OVERLAY -6
#define INIT_ORDER_XENOARCH -20
+#define INIT_ORDER_CIRCUIT -21
// Subsystem fire priority, from lowest to highest priority
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index da4385ef6f..c6cae7b464 100644
--- a/code/_helpers/icons.dm
+++ b/code/_helpers/icons.dm
@@ -634,7 +634,6 @@ as a single icon. Useful for when you want to manipulate an icon via the above a
The _flatIcons list is a cache for generated icon files.
*/
-// Creates a single icon from a given /atom or /image. Only the first argument is required.
/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE)
// We start with a blank canvas, otherwise some icon procs crash silently
var/icon/flat = icon('icons/effects/effects.dmi', "nothing") // Final flattened icon
@@ -677,7 +676,7 @@ The _flatIcons list is a cache for generated icon files.
var/curdir
var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have
-
+
//These should use the parent's direction (most likely)
if(!A.dir || A.dir == SOUTH)
curdir = defdir
@@ -686,7 +685,7 @@ The _flatIcons list is a cache for generated icon files.
//Let's check if the icon actually contains any diagonals, just skip if it's south to save (lot of) time
if(curdir != SOUTH)
- var/icon/test_icon
+ var/icon/test_icon
var/directionals_exist = FALSE
var/list/dirs_to_check = cardinal - SOUTH
outer:
@@ -824,6 +823,9 @@ The _flatIcons list is a cache for generated icon files.
else
return icon(flat, "", SOUTH)
+
+
+
/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N
var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A.
for(var/I in A.overlays)//For every image in overlays. var/image/I will not work, don't try it.
@@ -874,9 +876,10 @@ The _flatIcons list is a cache for generated icon files.
if(4) I.pixel_y++
overlays += I//And finally add the overlay.
-/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created.
+/proc/getHologramIcon(icon/A, safety=1, no_color = FALSE)//If safety is on, a new icon is not created.
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
- flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish.
+ if(!no_color)
+ flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish.
flat_icon.ChangeOpacity(0.5)//Make it half transparent.
var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect.
flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
diff --git a/code/_macros.dm b/code/_macros.dm
index cd28ee612d..7bc2345d5b 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -91,4 +91,6 @@
// Null-safe L.Cut()
#define LAZYCLEARLIST(L) if(L) L.Cut()
// Reads L or an empty list if L is not a list. Note: Does NOT assign, L may be an expression.
-#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
\ No newline at end of file
+#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
+// Turns LAZYINITLIST(L) L[K] = V into ... for associated lists
+#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
\ No newline at end of file
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index e64d4f946f..1dcc2bc955 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -76,7 +76,7 @@
if(A == loc || (A in loc) || (A in contents))
// No adjacency checks
- var/resolved = A.attackby(W,src)
+ var/resolved = A.attackby(W, src, 1)
if(!resolved && A && W)
W.afterattack(A,src,1,params)
return
@@ -88,7 +88,7 @@
if(isturf(A) || isturf(A.loc))
if(A.Adjacent(src)) // see adjacent.dm
- var/resolved = A.attackby(W, src)
+ var/resolved = A.attackby(W, src, 1)
if(!resolved && A && W)
W.afterattack(A, src, 1, params)
return
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 428485573d..afe99e2c1d 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -28,20 +28,20 @@ avoid code duplication. This includes items that may sometimes act as a standard
return
//I would prefer to rename this to attack(), but that would involve touching hundreds of files.
-/obj/item/proc/resolve_attackby(atom/A, mob/user)
+/obj/item/proc/resolve_attackby(atom/A, mob/user, var/attack_modifier = 1)
pre_attack(A, user)
add_fingerprint(user)
- return A.attackby(src, user)
+ return A.attackby(src, user, attack_modifier)
// No comment
-/atom/proc/attackby(obj/item/W, mob/user)
+/atom/proc/attackby(obj/item/W, mob/user, var/attack_modifier)
return
-/atom/movable/attackby(obj/item/W, mob/user)
+/atom/movable/attackby(obj/item/W, mob/user, var/attack_modifier)
if(!(W.flags & NOBLUDGEON))
visible_message("[src] has been hit by [user] with [W].")
-/mob/living/attackby(obj/item/I, mob/user)
+/mob/living/attackby(obj/item/I, mob/user, var/attack_modifier)
if(!ismob(user))
return 0
if(can_operate(src) && I.do_surgery(src,user))
@@ -49,7 +49,7 @@ avoid code duplication. This includes items that may sometimes act as a standard
return 1
else
return 0
- return I.attack(src, user, user.zone_sel.selecting)
+ return I.attack(src, user, user.zone_sel.selecting, attack_modifier)
// Used to get how fast a mob should attack, and influences click delay.
// This is just for inheritence.
@@ -73,7 +73,7 @@ avoid code duplication. This includes items that may sometimes act as a standard
return
//I would prefer to rename this attack_as_weapon(), but that would involve touching hundreds of files.
-/obj/item/proc/attack(mob/living/M, mob/living/user, var/target_zone)
+/obj/item/proc/attack(mob/living/M, mob/living/user, var/target_zone, var/attack_modifier)
if(!force || (flags & NOBLUDGEON))
return 0
if(M == user && user.a_intent != I_HURT)
@@ -92,12 +92,12 @@ avoid code duplication. This includes items that may sometimes act as a standard
var/hit_zone = M.resolve_item_attack(src, user, target_zone)
if(hit_zone)
- apply_hit_effect(M, user, hit_zone)
+ apply_hit_effect(M, user, hit_zone, attack_modifier)
return 1
//Called when a weapon is used to make a successful melee attack on a mob. Returns the blocked result
-/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
+/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone, var/attack_modifier)
user.break_cloak()
if(hitsound)
playsound(loc, hitsound, 50, 1, -1)
@@ -106,7 +106,10 @@ avoid code duplication. This includes items that may sometimes act as a standard
for(var/datum/modifier/M in user.modifiers)
if(!isnull(M.outgoing_melee_damage_percent))
power *= M.outgoing_melee_damage_percent
+
if(HULK in user.mutations)
power *= 2
- return target.hit_with_weapon(src, user, power, hit_zone)
+ power *= attack_modifier
+
+ return target.hit_with_weapon(src, user, power, hit_zone)
\ No newline at end of file
diff --git a/code/controllers/subsystems/circuits.dm b/code/controllers/subsystems/circuits.dm
new file mode 100644
index 0000000000..8fda048c60
--- /dev/null
+++ b/code/controllers/subsystems/circuits.dm
@@ -0,0 +1,96 @@
+//
+// This is for custom circuits, mostly the initialization of global properties about them.
+// Might make this also process them in the future if its better to do that than using the obj ticker.
+//
+SUBSYSTEM_DEF(circuit)
+ name = "Circuit"
+ init_order = INIT_ORDER_CIRCUIT
+ flags = SS_NO_FIRE
+ var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs
+ var/list/cached_components = list() // Associative list of [component_path]:[component] pairs
+ var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs
+ var/list/cached_assemblies = list() // Associative list of [assembly_path]:[assembly] pairs
+ var/list/all_circuits = list() // Associative list of [circuit_name]:[circuit_path] pairs
+ var/list/circuit_fabricator_recipe_list = list() // Associative list of [category_name]:[list_of_circuit_paths] pairs
+// var/cost_multiplier = MINERAL_MATERIAL_AMOUNT / 10 // Each circuit cost unit is 200cm3
+
+/datum/controller/subsystem/circuit/Recover()
+ flags |= SS_NO_INIT // Make extra sure we don't initialize twice.
+
+/datum/controller/subsystem/circuit/Initialize(timeofday)
+ circuits_init()
+ return ..()
+
+/datum/controller/subsystem/circuit/proc/circuits_init()
+ //Cached lists for free performance
+ for(var/path in typesof(/obj/item/integrated_circuit))
+ var/obj/item/integrated_circuit/IC = path
+ var/name = initial(IC.name)
+ all_components[name] = path // Populating the component lists
+ cached_components[IC] = new path
+
+ if(!(initial(IC.spawn_flags) & (IC_SPAWN_DEFAULT | IC_SPAWN_RESEARCH)))
+ continue
+
+ var/category = initial(IC.category_text)
+ if(!circuit_fabricator_recipe_list[category])
+ circuit_fabricator_recipe_list[category] = list()
+ var/list/category_list = circuit_fabricator_recipe_list[category]
+ category_list += IC // Populating the fabricator categories
+
+ for(var/path in typesof(/obj/item/device/electronic_assembly))
+ var/obj/item/device/electronic_assembly/A = path
+ var/name = initial(A.name)
+ all_assemblies[name] = path
+ cached_assemblies[A] = new path
+
+
+ circuit_fabricator_recipe_list["Assemblies"] = list(
+ /obj/item/device/electronic_assembly/default,
+ /obj/item/device/electronic_assembly/calc,
+ /obj/item/device/electronic_assembly/clam,
+ /obj/item/device/electronic_assembly/simple,
+ /obj/item/device/electronic_assembly/hook,
+ /obj/item/device/electronic_assembly/pda,
+ /obj/item/device/electronic_assembly/tiny/default,
+ /obj/item/device/electronic_assembly/tiny/cylinder,
+ /obj/item/device/electronic_assembly/tiny/scanner,
+ /obj/item/device/electronic_assembly/tiny/hook,
+ /obj/item/device/electronic_assembly/tiny/box,
+ /obj/item/device/electronic_assembly/medium/default,
+ /obj/item/device/electronic_assembly/medium/box,
+ /obj/item/device/electronic_assembly/medium/clam,
+ /obj/item/device/electronic_assembly/medium/medical,
+ /obj/item/device/electronic_assembly/medium/gun,
+ /obj/item/device/electronic_assembly/medium/radio,
+ /obj/item/device/electronic_assembly/large/default,
+ /obj/item/device/electronic_assembly/large/scope,
+ /obj/item/device/electronic_assembly/large/terminal,
+ /obj/item/device/electronic_assembly/large/arm,
+ /obj/item/device/electronic_assembly/large/tall,
+ /obj/item/device/electronic_assembly/large/industrial,
+ /obj/item/device/electronic_assembly/drone/default,
+ /obj/item/device/electronic_assembly/drone/arms,
+ /obj/item/device/electronic_assembly/drone/secbot,
+ /obj/item/device/electronic_assembly/drone/medbot,
+ /obj/item/device/electronic_assembly/drone/genbot,
+ /obj/item/device/electronic_assembly/drone/android,
+ /obj/item/device/electronic_assembly/wallmount/tiny,
+ /obj/item/device/electronic_assembly/wallmount/light,
+ /obj/item/device/electronic_assembly/wallmount,
+ /obj/item/device/electronic_assembly/wallmount/heavy,
+ /obj/item/weapon/implant/integrated_circuit,
+ /obj/item/clothing/under/circuitry,
+ /obj/item/clothing/gloves/circuitry,
+ /obj/item/clothing/glasses/circuitry,
+ /obj/item/clothing/shoes/circuitry,
+ /obj/item/clothing/head/circuitry,
+ /obj/item/clothing/ears/circuitry,
+ /obj/item/clothing/suit/circuitry
+ )
+
+ circuit_fabricator_recipe_list["Tools"] = list(
+ /obj/item/device/integrated_electronics/wirer,
+ /obj/item/device/integrated_electronics/debugger,
+ /obj/item/device/integrated_electronics/detailer
+ )
diff --git a/code/datums/observation/helpers.dm b/code/datums/observation/helpers.dm
index 7857434170..9116026700 100644
--- a/code/datums/observation/helpers.dm
+++ b/code/datums/observation/helpers.dm
@@ -1,5 +1,5 @@
/atom/movable/proc/recursive_move(var/atom/movable/am, var/old_loc, var/new_loc)
- moved_event.raise_event(src, old_loc, new_loc)
+ GLOB.moved_event.raise_event(src, old_loc, new_loc)
/atom/movable/proc/move_to_destination(var/atom/movable/am, var/old_loc, var/new_loc)
var/turf/T = get_turf(new_loc)
@@ -10,9 +10,9 @@
set_dir(new_dir)
/proc/register_all_movement(var/event_source, var/listener)
- moved_event.register(event_source, listener, /atom/movable/proc/recursive_move)
+ GLOB.moved_event.register(event_source, listener, /atom/movable/proc/recursive_move)
GLOB.dir_set_event.register(event_source, listener, /atom/proc/recursive_dir_set)
/proc/unregister_all_movement(var/event_source, var/listener)
- moved_event.unregister(event_source, listener, /atom/movable/proc/recursive_move)
+ GLOB.moved_event.unregister(event_source, listener, /atom/movable/proc/recursive_move)
GLOB.dir_set_event.unregister(event_source, listener, /atom/proc/recursive_dir_set)
diff --git a/code/datums/observation/logged_in.dm b/code/datums/observation/logged_in.dm
index 311ff8acb6..c59e146a48 100644
--- a/code/datums/observation/logged_in.dm
+++ b/code/datums/observation/logged_in.dm
@@ -6,7 +6,7 @@
// Arguments that the called proc should expect:
// /mob/joiner: The mob that has logged in
-var/decl/observ/logged_in/logged_in_event = new()
+GLOBAL_DATUM_INIT(logged_in_event, /decl/observ/logged_in, new)
/decl/observ/logged_in
name = "Logged In"
@@ -18,4 +18,4 @@ var/decl/observ/logged_in/logged_in_event = new()
/mob/Login()
..()
- logged_in_event.raise_event(src)
+ GLOB.logged_in_event.raise_event(src)
diff --git a/code/datums/observation/moved.dm b/code/datums/observation/moved.dm
index 86a6b793ac..311f9673f6 100644
--- a/code/datums/observation/moved.dm
+++ b/code/datums/observation/moved.dm
@@ -8,7 +8,8 @@
// /atom/old_loc: The loc before the move.
// /atom/new_loc: The loc after the move.
-var/decl/observ/moved/moved_event = new()
+
+GLOBAL_DATUM_INIT(moved_event, /decl/observ/moved, new)
/decl/observ/moved
name = "Moved"
@@ -27,26 +28,26 @@ var/decl/observ/moved/moved_event = new()
/atom/Entered(var/atom/movable/am, var/atom/old_loc)
. = ..()
- moved_event.raise_event(am, old_loc, am.loc)
+ GLOB.moved_event.raise_event(am, old_loc, am.loc)
/atom/movable/Entered(var/atom/movable/am, atom/old_loc)
. = ..()
- if(moved_event.has_listeners(am))
- moved_event.register(src, am, /atom/movable/proc/recursive_move)
+ if(GLOB.moved_event.has_listeners(am))
+ GLOB.moved_event.register(src, am, /atom/movable/proc/recursive_move)
/atom/movable/Exited(var/atom/movable/am, atom/old_loc)
. = ..()
- moved_event.unregister(src, am, /atom/movable/proc/recursive_move)
+ GLOB.moved_event.unregister(src, am, /atom/movable/proc/recursive_move)
// Entered() typically lifts the moved event, but in the case of null-space we'll have to handle it.
/atom/movable/Move()
var/old_loc = loc
. = ..()
if(. && !loc)
- moved_event.raise_event(src, old_loc, null)
+ GLOB.moved_event.raise_event(src, old_loc, null)
/atom/movable/forceMove(atom/destination)
var/old_loc = loc
. = ..()
if(. && !loc)
- moved_event.raise_event(src, old_loc, null)
+ GLOB.moved_event.raise_event(src, old_loc, null)
diff --git a/code/datums/observation/unequipped.dm b/code/datums/observation/unequipped.dm
index 3287c0a3b5..6ad8d8eca0 100644
--- a/code/datums/observation/unequipped.dm
+++ b/code/datums/observation/unequipped.dm
@@ -7,7 +7,7 @@
// /mob/equipped: The mob that unequipped/dropped the item.
// /obj/item/item: The unequipped item.
-var/decl/observ/mob_unequipped/mob_unequipped_event = new()
+GLOBAL_DATUM_INIT(mob_unequipped_event, /decl/observ/mob_unequipped, new)
/decl/observ/mob_unequipped
name = "Mob Unequipped"
@@ -22,7 +22,7 @@ var/decl/observ/mob_unequipped/mob_unequipped_event = new()
// /obj/item/item: The unequipped item.
// /mob/equipped: The mob that unequipped/dropped the item.
-var/decl/observ/item_unequipped/item_unequipped_event = new()
+GLOBAL_DATUM_INIT(item_unequipped_event, /decl/observ/item_unequipped, new)
/decl/observ/item_unequipped
name = "Item Unequipped"
@@ -34,5 +34,5 @@ var/decl/observ/item_unequipped/item_unequipped_event = new()
/obj/item/dropped(var/mob/user)
..()
- mob_unequipped_event.raise_event(user, src)
- item_unequipped_event.raise_event(src, user)
+ GLOB.mob_unequipped_event.raise_event(user, src)
+ GLOB.item_unequipped_event.raise_event(src, user)
diff --git a/code/datums/repositories/radiation.dm b/code/datums/repositories/radiation.dm
index 46f4a9a5b7..88510e0525 100644
--- a/code/datums/repositories/radiation.dm
+++ b/code/datums/repositories/radiation.dm
@@ -122,7 +122,7 @@ var/global/repository/radiation/radiation_repository = new()
/turf/simulated/wall/calc_rad_resistance()
radiation_repository.resistance_cache[src] = (length(contents) + 1)
- cached_rad_resistance = (density ? material.weight : 0)
+ cached_rad_resistance = (density ? material.weight + material.radiation_resistance : 0)
/obj
var/rad_resistance = 0 // Allow overriding rad resistance
diff --git a/code/datums/supplypacks/robotics.dm b/code/datums/supplypacks/robotics.dm
index 67292de654..1c1eb7f2bb 100644
--- a/code/datums/supplypacks/robotics.dm
+++ b/code/datums/supplypacks/robotics.dm
@@ -176,4 +176,25 @@
cost = 30
containertype = /obj/structure/closet/crate/secure/science
containername = "Jumper kit crate"
- access = access_robotics
\ No newline at end of file
+ access = access_robotics
+
+/datum/supply_pack/robotics/bike
+ name = "Spacebike Crate"
+ contains = list()
+ cost = 350
+ containertype = /obj/structure/largecrate/vehicle/bike
+ containername = "Spacebike Crate"
+
+/datum/supply_pack/robotics/quadbike
+ name = "ATV Crate"
+ contains = list()
+ cost = 300
+ containertype = /obj/structure/largecrate/vehicle/quadbike
+ containername = "ATV Crate"
+
+/datum/supply_pack/robotics/quadtrailer
+ name = "ATV Trailer Crate"
+ contains = list()
+ cost = 250
+ containertype = /obj/structure/largecrate/vehicle/quadtrailer
+ containername = "ATV Trailer Crate"
diff --git a/code/datums/supplypacks/supply.dm b/code/datums/supplypacks/supply.dm
index 0a40cae6a3..8397dc8c87 100644
--- a/code/datums/supplypacks/supply.dm
+++ b/code/datums/supplypacks/supply.dm
@@ -124,14 +124,14 @@
/datum/supply_pack/supply/cargotrain
name = "Cargo Train Tug"
- contains = list(/obj/vehicle/train/cargo/engine)
+ contains = list(/obj/vehicle/train/engine)
cost = 35
containertype = /obj/structure/largecrate
containername = "Cargo Train Tug Crate"
/datum/supply_pack/supply/cargotrailer
name = "Cargo Train Trolley"
- contains = list(/obj/vehicle/train/cargo/trolley)
+ contains = list(/obj/vehicle/train/trolley)
cost = 15
containertype = /obj/structure/largecrate
containername = "Cargo Train Trolley Crate"
\ No newline at end of file
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 5311d642a2..7f7bbaa3b3 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -16,8 +16,10 @@
var/mob/pulledby = null
var/item_state = null // Used to specify the item state for the on-mob overlays.
var/icon_scale = 1 // Used to scale icons up or down in update_transform().
+ var/icon_rotation = 0 // Used to rotate icons in update_transform()
var/old_x = 0
var/old_y = 0
+ var/does_spin = TRUE // Does the atom spin when thrown (of course it does :P)
/atom/movable/Destroy()
. = ..()
@@ -125,17 +127,23 @@
src.throw_impact(A,speed)
/atom/movable/proc/throw_at(atom/target, range, speed, thrower)
- if(!target || !src) return 0
+ if(!target || !src)
+ return 0
+ if(target.z != src.z)
+ return 0
//use a modified version of Bresenham's algorithm to get from the atom's current position to that of the target
-
src.throwing = 1
src.thrower = thrower
src.throw_source = get_turf(src) //store the origin turf
-
+ src.pixel_z = 0
if(usr)
if(HULK in usr.mutations)
src.throwing = 2 // really strong throw!
+ var/dist_travelled = 0
+ var/dist_since_sleep = 0
+ var/area/a = get_area(src.loc)
+
var/dist_x = abs(target.x - src.x)
var/dist_y = abs(target.y - src.y)
@@ -150,77 +158,57 @@
dy = NORTH
else
dy = SOUTH
- var/dist_travelled = 0
- var/dist_since_sleep = 0
- var/area/a = get_area(src.loc)
+
+ var/error
+ var/major_dir
+ var/major_dist
+ var/minor_dir
+ var/minor_dist
if(dist_x > dist_y)
- var/error = dist_x/2 - dist_y
-
-
-
- while(src && target &&((((src.x < target.x && dx == EAST) || (src.x > target.x && dx == WEST)) && dist_travelled < range) || (a && a.has_gravity == 0) || istype(src.loc, /turf/space)) && src.throwing && istype(src.loc, /turf))
- // only stop when we've gone the whole distance (or max throw range) and are on a non-space tile, or hit something, or hit the end of the map, or someone picks it up
- if(error < 0)
- var/atom/step = get_step(src, dy)
- if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
- break
- src.Move(step)
- hit_check(speed)
- error += dist_x
- dist_travelled++
- dist_since_sleep++
- if(dist_since_sleep >= speed)
- dist_since_sleep = 0
- sleep(1)
- else
- var/atom/step = get_step(src, dx)
- if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
- break
- src.Move(step)
- hit_check(speed)
- error -= dist_y
- dist_travelled++
- dist_since_sleep++
- if(dist_since_sleep >= speed)
- dist_since_sleep = 0
- sleep(1)
- a = get_area(src.loc)
+ error = dist_x/2 - dist_y
+ major_dir = dx
+ major_dist = dist_x
+ minor_dir = dy
+ minor_dist = dist_y
else
- var/error = dist_y/2 - dist_x
- while(src && target &&((((src.y < target.y && dy == NORTH) || (src.y > target.y && dy == SOUTH)) && dist_travelled < range) || (a && a.has_gravity == 0) || istype(src.loc, /turf/space)) && src.throwing && istype(src.loc, /turf))
- // only stop when we've gone the whole distance (or max throw range) and are on a non-space tile, or hit something, or hit the end of the map, or someone picks it up
- if(error < 0)
- var/atom/step = get_step(src, dx)
- if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
- break
- src.Move(step)
- hit_check(speed)
- error += dist_y
- dist_travelled++
- dist_since_sleep++
- if(dist_since_sleep >= speed)
- dist_since_sleep = 0
- sleep(1)
- else
- var/atom/step = get_step(src, dy)
- if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
- break
- src.Move(step)
- hit_check(speed)
- error -= dist_x
- dist_travelled++
- dist_since_sleep++
- if(dist_since_sleep >= speed)
- dist_since_sleep = 0
- sleep(1)
+ error = dist_y/2 - dist_x
+ major_dir = dy
+ major_dist = dist_y
+ minor_dir = dx
+ minor_dist = dist_x
- a = get_area(src.loc)
+ while(src && target && src.throwing && istype(src.loc, /turf) \
+ && ((abs(target.x - src.x)+abs(target.y - src.y) > 0 && dist_travelled < range) \
+ || (a && a.has_gravity == 0) \
+ || istype(src.loc, /turf/space)))
+ // only stop when we've gone the whole distance (or max throw range) and are on a non-space tile, or hit something, or hit the end of the map, or someone picks it up
+ var/atom/step
+ if(error >= 0)
+ step = get_step(src, major_dir)
+ error -= minor_dist
+ else
+ step = get_step(src, minor_dir)
+ error += major_dist
+ if(!step) // going off the edge of the map makes get_step return null, don't let things go off the edge
+ break
+ src.Move(step)
+ hit_check(speed)
+ dist_travelled++
+ dist_since_sleep++
+ if(dist_since_sleep >= speed)
+ dist_since_sleep = 0
+ sleep(1)
+ a = get_area(src.loc)
+ // and yet it moves
+ if(src.does_spin)
+ src.SpinAnimation(speed = 4, loops = 1)
//done throwing, either because it hit something or it finished moving
if(isobj(src)) src.throw_impact(get_turf(src),speed)
src.throwing = 0
src.thrower = null
src.throw_source = null
+ fall()
//Overlays
@@ -290,9 +278,14 @@
/atom/movable/proc/update_transform()
var/matrix/M = matrix()
M.Scale(icon_scale)
+ M.Turn(icon_rotation)
src.transform = M
// Use this to set the object's scale.
/atom/movable/proc/adjust_scale(new_scale)
icon_scale = new_scale
update_transform()
+
+/atom/movable/proc/adjust_rotation(new_rotation)
+ icon_rotation = new_rotation
+ update_transform()
\ No newline at end of file
diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm
index 63b85c4773..af3a21a053 100644
--- a/code/game/machinery/computer/supply.dm
+++ b/code/game/machinery/computer/supply.dm
@@ -22,7 +22,7 @@
icon_screen = "supply"
light_color = "#b88b2e"
req_access = list(access_cargo)
- circuit = /obj/item/weapon/circuitboard/supplycomp
+ circuit = /obj/item/weapon/circuitboard/supplycomp/control
authorization = SUP_SEND_SHUTTLE | SUP_ACCEPT_ORDERS
/obj/machinery/computer/supplycomp/attack_ai(var/mob/user as mob)
@@ -43,8 +43,6 @@
return 1
-
-
/obj/machinery/computer/supplycomp/ui_interact(mob/user, ui_key = "supply_records", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null)
var/data[0]
var/shuttle_status[0] // Supply shuttle status
@@ -166,7 +164,7 @@
data["supply_packs"] = pack_list
data["orders"] = orders
data["receipts"] = receipts
- data["contraband"] = can_order_contraband
+ data["contraband"] = can_order_contraband || (authorization & SUP_CONTRABAND)
// update the ui if it exists, returns null if no ui is passed/found
ui = GLOB.nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
@@ -182,8 +180,6 @@
ui.set_auto_update(20) // Longer term to reduce the rate of data collection and processing
-
-
/obj/machinery/computer/supplycomp/Topic(href, href_list)
if(!supply_controller)
world.log << "## ERROR: The supply_controller datum is missing."
@@ -398,7 +394,6 @@
if("send_to_station")
shuttle.launch(src)
to_chat(usr, "The supply shuttle has been called and will arrive in approximately [round(supply_controller.movetime/600,1)] minutes.")
- post_signal("supply")
if("cancel_shuttle")
shuttle.cancel_launch(src)
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index 64987c863c..6993d988dd 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -205,7 +205,7 @@
var/mob/M = grab.affecting
qdel(grab)
put_mob(M)
-
+
return
/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(var/mob/target, var/mob/user) //Allows borgs to put people into cryo without external assistance
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index a56ce2a880..55bcedfaca 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -205,80 +205,83 @@
/obj/machinery/door/attackby(obj/item/I as obj, mob/user as mob)
src.add_fingerprint(user)
- if(istype(I, /obj/item/stack/material) && I.get_material_name() == src.get_material_name())
- if(stat & BROKEN)
- user << "It looks like \the [src] is pretty busted. It's going to need more than just patching up now."
- return
- if(health >= maxhealth)
- user << "Nothing to fix!"
- return
- if(!density)
- user << "\The [src] must be closed before you can repair it."
- return
+ if(istype(I))
+ if(istype(I, /obj/item/stack/material) && I.get_material_name() == src.get_material_name())
+ if(stat & BROKEN)
+ user << "It looks like \the [src] is pretty busted. It's going to need more than just patching up now."
+ return
+ if(health >= maxhealth)
+ user << "Nothing to fix!"
+ return
+ if(!density)
+ user << "\The [src] must be closed before you can repair it."
+ return
- //figure out how much metal we need
- var/amount_needed = (maxhealth - health) / DOOR_REPAIR_AMOUNT
- amount_needed = (round(amount_needed) == amount_needed)? amount_needed : round(amount_needed) + 1 //Why does BYOND not have a ceiling proc?
+ //figure out how much metal we need
+ var/amount_needed = (maxhealth - health) / DOOR_REPAIR_AMOUNT
+ amount_needed = (round(amount_needed) == amount_needed)? amount_needed : round(amount_needed) + 1 //Why does BYOND not have a ceiling proc?
- var/obj/item/stack/stack = I
- var/transfer
- if (repairing)
- transfer = stack.transfer_to(repairing, amount_needed - repairing.amount)
- if (!transfer)
- user << "You must weld or remove \the [repairing] from \the [src] before you can add anything else."
- else
- repairing = stack.split(amount_needed)
+ var/obj/item/stack/stack = I
+ var/transfer
if (repairing)
- repairing.loc = src
- transfer = repairing.amount
+ transfer = stack.transfer_to(repairing, amount_needed - repairing.amount)
+ if (!transfer)
+ user << "You must weld or remove \the [repairing] from \the [src] before you can add anything else."
+ else
+ repairing = stack.split(amount_needed)
+ if (repairing)
+ repairing.loc = src
+ transfer = repairing.amount
- if (transfer)
- user << "You fit [transfer] [stack.singular_name]\s to damaged and broken parts on \the [src]."
+ if (transfer)
+ user << "You fit [transfer] [stack.singular_name]\s to damaged and broken parts on \the [src]."
- return
-
- if(repairing && istype(I, /obj/item/weapon/weldingtool))
- if(!density)
- user << "\The [src] must be closed before you can repair it."
return
- var/obj/item/weapon/weldingtool/welder = I
- if(welder.remove_fuel(0,user))
- user << "You start to fix dents and weld \the [repairing] into place."
- playsound(src, welder.usesound, 50, 1)
- if(do_after(user, (5 * repairing.amount) * welder.toolspeed) && welder && welder.isOn())
- user << "You finish repairing the damage to \the [src]."
- health = between(health, health + repairing.amount*DOOR_REPAIR_AMOUNT, maxhealth)
- update_icon()
- qdel(repairing)
- repairing = null
+ if(repairing && istype(I, /obj/item/weapon/weldingtool))
+ if(!density)
+ user << "\The [src] must be closed before you can repair it."
+ return
+
+ var/obj/item/weapon/weldingtool/welder = I
+ if(welder.remove_fuel(0,user))
+ user << "You start to fix dents and weld \the [repairing] into place."
+ playsound(src, welder.usesound, 50, 1)
+ if(do_after(user, (5 * repairing.amount) * welder.toolspeed) && welder && welder.isOn())
+ user << "You finish repairing the damage to \the [src]."
+ health = between(health, health + repairing.amount*DOOR_REPAIR_AMOUNT, maxhealth)
+ update_icon()
+ qdel(repairing)
+ repairing = null
+ return
+
+ if(repairing && I.is_crowbar())
+ user << "You remove \the [repairing]."
+ playsound(src, I.usesound, 100, 1)
+ repairing.loc = user.loc
+ repairing = null
+ return
+
+ //psa to whoever coded this, there are plenty of objects that need to call attack() on doors without bludgeoning them.
+ if(src.density && istype(I, /obj/item/weapon) && user.a_intent == I_HURT && !istype(I, /obj/item/weapon/card))
+ var/obj/item/weapon/W = I
+ user.setClickCooldown(user.get_attack_speed(W))
+ if(W.damtype == BRUTE || W.damtype == BURN)
+ user.do_attack_animation(src)
+ if(W.force < min_force)
+ user.visible_message("\The [user] hits \the [src] with \the [W] with no visible effect.")
+ else
+ user.visible_message("\The [user] forcefully strikes \the [src] with \the [W]!")
+ playsound(src.loc, hitsound, 100, 1)
+ take_damage(W.force)
+ return
+
+ if(src.operating > 0 || isrobot(user))
+ return //borgs can't attack doors open because it conflicts with their AI-like interaction with them.
+
+ if(src.operating)
return
- if(repairing && I.is_crowbar())
- user << "You remove \the [repairing]."
- playsound(src, I.usesound, 100, 1)
- repairing.loc = user.loc
- repairing = null
- return
-
- //psa to whoever coded this, there are plenty of objects that need to call attack() on doors without bludgeoning them.
- if(src.density && istype(I, /obj/item/weapon) && user.a_intent == I_HURT && !istype(I, /obj/item/weapon/card))
- var/obj/item/weapon/W = I
- user.setClickCooldown(user.get_attack_speed(W))
- if(W.damtype == BRUTE || W.damtype == BURN)
- user.do_attack_animation(src)
- if(W.force < min_force)
- user.visible_message("\The [user] hits \the [src] with \the [W] with no visible effect.")
- else
- user.visible_message("\The [user] forcefully strikes \the [src] with \the [W]!")
- playsound(src.loc, hitsound, 100, 1)
- take_damage(W.force)
- return
-
- if(src.operating > 0 || isrobot(user)) return //borgs can't attack doors open because it conflicts with their AI-like interaction with them.
-
- if(src.operating) return
-
if(src.allowed(user) && operable())
if(src.density)
open()
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 5cb6b0ba06..41a30feaa5 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -643,6 +643,20 @@
departments = list("Mercenary", "Charring")
can_repair = 1
+/obj/machinery/suit_cycler/exploration
+ name = "Explorer suit cycler"
+ model_text = "Exploration"
+ departments = list("Exploration","Old Exploration")
+
+/obj/machinery/suit_cycler/exploreration/initialize()
+ species -= SPECIES_TESHARI
+ return ..()
+
+/obj/machinery/suit_cycler/pilot
+ name = "Pilot suit cycler"
+ model_text = "Pilot"
+ departments = list("Pilot Blue","Pilot")
+
/obj/machinery/suit_cycler/attack_ai(mob/user as mob)
return attack_hand(user)
@@ -1106,6 +1120,51 @@
suit.icon_state = "rig-firebug"
suit.item_state_slots[slot_r_hand_str] = "rig-firebug"
suit.item_state_slots[slot_l_hand_str] = "rig-firebug"
+ if("Exploration")
+ if(helmet)
+ helmet.name = "exploration voidsuit helmet"
+ helmet.icon_state = "helm_explorer"
+ helmet.item_state = "helm_explorer"
+ if(suit)
+ suit.name = "exploration voidsuit"
+ suit.icon_state = "void_explorer"
+ suit.item_state = "void_explorer"
+ suit.item_state_slots[slot_r_hand_str] = "wiz_voidsuit"
+ suit.item_state_slots[slot_l_hand_str] = "wiz_voidsuit"
+ if("Old Exploration")
+ if(helmet)
+ helmet.name = "exploration voidsuit helmet"
+ helmet.icon_state = "helm_explorer2"
+ helmet.item_state = "helm_explorer2"
+ if(suit)
+ suit.name = "exploration voidsuit"
+ suit.icon_state = "void_explorer2"
+ suit.item_state = "void_explorer2"
+ suit.item_state_slots[slot_r_hand_str] = "wiz_voidsuit"
+ suit.item_state_slots[slot_l_hand_str] = "wiz_voidsuit"
+ if("Pilot")
+ if(helmet)
+ helmet.name = "pilot voidsuit helmet"
+ helmet.icon_state = "rig0_pilot"
+ helmet.item_state = "pilot_helm"
+ if(suit)
+ suit.name = "pilot voidsuit"
+ suit.icon_state = "rig-pilot"
+ suit.item_state = "rig-pilot"
+ suit.item_state_slots[slot_r_hand_str] = "sec_voidsuitTG"
+ suit.item_state_slots[slot_l_hand_str] = "sec_voidsuitTG"
+ if("Pilot Blue")
+ if(helmet)
+ helmet.name = "pilot voidsuit helmet"
+ helmet.icon_state = "rig0_pilot2"
+ helmet.item_state = "pilot_helm2"
+ if(suit)
+ suit.name = "pilot voidsuit"
+ suit.icon_state = "rig-pilot2"
+ suit.item_state = "rig-pilot2"
+ suit.item_state_slots[slot_r_hand_str] = "sec_voidsuitTG"
+ suit.item_state_slots[slot_l_hand_str] = "sec_voidsuitTG"
+
if(helmet) helmet.name = "refitted [helmet.name]"
if(suit) suit.name = "refitted [suit.name]"
diff --git a/code/game/mecha/equipment/tools/tools.dm b/code/game/mecha/equipment/tools/tools.dm
index 5dda5866d1..8eadacdc7e 100644
--- a/code/game/mecha/equipment/tools/tools.dm
+++ b/code/game/mecha/equipment/tools/tools.dm
@@ -1266,11 +1266,6 @@
to_chat(L, span("warning", "You have other entities attached to yourself. Remove them first."))
return
- for(var/mob/living/simple_mob/slime/xenobio/M in range(1,usr))
- if(M.victim == usr)
- usr << "You're too busy getting your life sucked out of you."
- return
-
//search for a valid passenger compartment
var/feedback = 0 //for nicer user feedback
for(var/obj/item/mecha_parts/mecha_equipment/tool/passenger/P in src)
@@ -1392,4 +1387,4 @@
/obj/item/mecha_parts/mecha_equipment/tool/jetpack/do_after_cooldown()
sleep(equip_cooldown)
wait = 0
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 5f2b7254e4..a53c471eb3 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -71,13 +71,7 @@
if(!istype(P))
return
- // Certain statuses make it harder to aim, blindness especially. Same chances as melee, however guns accuracy uses multiples of 15.
- if(user.eye_blind)
- P.accuracy -= 75
- if(user.eye_blurry)
- P.accuracy -= 30
- if(user.confused)
- P.accuracy -= 45
+ P.accuracy -= user.get_accuracy_penalty()
// Some modifiers make it harder or easier to hit things.
for(var/datum/modifier/M in user.modifiers)
@@ -472,6 +466,7 @@
if(istype(G))
G.det_time = det_time
G.activate(chassis.occupant) //Grenades actually look primed and dangerous, handle their own stuff.
+ AM.throw_at(target,missile_range, missile_speed, chassis)
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/grenade/clusterbang//Because I am a heartless bastard -Sieve
name = "\improper SOP-6 grenade launcher"
diff --git a/code/game/mecha/mech_sensor.dm b/code/game/mecha/mech_sensor.dm
index 5173f182ce..eee1ecefdb 100644
--- a/code/game/mecha/mech_sensor.dm
+++ b/code/game/mecha/mech_sensor.dm
@@ -45,8 +45,8 @@
var/obj/mecha/R = O
if(R && R.occupant)
R.occupant << block_message
- else if(istype(O, /obj/vehicle/train/cargo/engine))
- var/obj/vehicle/train/cargo/engine/E = O
+ else if(istype(O, /obj/vehicle/train/engine))
+ var/obj/vehicle/train/engine/E = O
if(E && E.load && E.is_train_head())
E.load << block_message
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index 9f4f0fa118..7504b6e65f 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -94,3 +94,8 @@
icon_state = "snowwall"
plane = MOB_PLANE
layer = ABOVE_MOB_LAYER
+
+/obj/effect/overlay/holographic
+ mouse_opacity = FALSE
+ anchored = TRUE
+ plane = ABOVE_PLANE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 4a80379c16..6c4895e42b 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -200,6 +200,9 @@
/obj/item/attack_hand(mob/living/user as mob)
if (!user) return
+ if(anchored)
+ to_chat(user, span("notice", "\The [src] won't budge, you can't pick it up!"))
+ return
if (hasorgans(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/external/temp = H.organs_by_name["r_hand"]
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 9dfb2c3173..bf034508de 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -655,6 +655,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
ui = new(user, src, ui_key, "pda.tmpl", title, 520, 400, state = inventory_state)
// add templates for screens in common with communicator.
ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
+ ui.add_template("crewManifest", "crew_manifest.tmpl")
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
diff --git a/code/game/objects/items/devices/communicator/UI.dm b/code/game/objects/items/devices/communicator/UI.dm
index 445b0b59c5..d414232a80 100644
--- a/code/game/objects/items/devices/communicator/UI.dm
+++ b/code/game/objects/items/devices/communicator/UI.dm
@@ -14,7 +14,6 @@
var/im_list_ui[0] //List of messages.
var/weather[0]
- var/injection = null
var/modules_ui[0] //Home screen info.
//First we add other 'local' communicators.
@@ -82,12 +81,14 @@
)
weather[++weather.len] = W
- injection = "
Test
"
+ // Update manifest
+ data_core.get_manifest_list()
//Modules for homescreen.
for(var/list/R in modules)
modules_ui[++modules_ui.len] = R
+ data["user"] = "\ref[user]" // For receiving input() via topic, because input(usr,...) wasn't working on cartridges
data["owner"] = owner ? owner : "Unset"
data["occupation"] = occupation ? occupation : "Swipe ID to set."
data["connectionStatus"] = get_connection_to_tcomms()
@@ -111,16 +112,29 @@
data["weather"] = weather
data["aircontents"] = src.analyze_air()
data["flashlight"] = fon
- data["injection"] = injection
+ data["manifest"] = PDA_Manifest
+ data["feeds"] = compile_news()
+ data["latest_news"] = get_recent_news()
+ if(cartridge) // If there's a cartridge, we need to grab the information from it
+ data["cart_devices"] = cartridge.get_device_status()
+ data["cart_templates"] = cartridge.ui_templates
+ for(var/list/L in cartridge.get_data())
+ data[L["field"]] = L["value"]
+ // cartridge.get_data() returns a list of tuples:
+ // The field element is the tag used to access the information by the template
+ // The value element is the actual data, and can take any form necessary for the template
// update the ui if it exists, returns null if no ui is passed/found
ui = GLOB.nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
// the ui does not exist, so we'll create a new() one
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state)
+ data["currentTab"] = 1 // Reset the current tab, because we're going to home page
+ ui = new(user, src, ui_key, "communicator_header.tmpl", "Communicator", 475, 700, state = key_state)
// add templates for screens in common with communicator.
ui.add_template("atmosphericScan", "atmospheric_scan.tmpl")
+ ui.add_template("crewManifest", "crew_manifest.tmpl")
+ ui.add_template("Body", "communicator.tmpl") // Main body
// when the ui is first opened this is the data it will use
ui.set_initial_data(data)
// open the new ui window
@@ -168,7 +182,7 @@
if(href_list["dial"])
if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
+ to_chat(usr, "Error: Cannot connect to Exonet node.")
return
var/their_address = href_list["dial"]
exonet.send_message(their_address, "voice")
@@ -181,7 +195,7 @@
if(href_list["message"])
if(!get_connection_to_tcomms())
- usr << "Error: Cannot connect to Exonet node."
+ to_chat(usr, "Error: Cannot connect to Exonet node.")
return
var/their_address = href_list["message"]
var/text = sanitizeSafe(input(usr,"Enter your message.","Text Message"))
@@ -246,9 +260,21 @@
note = ""
notehtml = note
+ if(href_list["switch_template"])
+ var/datum/nanoui/ui = GLOB.nanomanager.get_open_ui(usr, src, "main")
+ if(ui)
+ ui.add_template("Body", href_list["switch_template"])
+
if(href_list["Light"])
fon = !fon
set_light(fon * flum)
+ if(href_list["toggle_device"])
+ var/obj/O = cartridge.internal_devices[text2num(href_list["toggle_device"])]
+ cartridge.active_devices ^= list(O) // Exclusive or, will toggle its presence
+
+ if(href_list["cartridge_topic"] && cartridge) // Has to have a cartridge to perform these functions
+ cartridge.Topic(href, href_list)
+
GLOB.nanomanager.update_uis(src)
add_fingerprint(usr)
diff --git a/code/game/objects/items/devices/communicator/cartridge.dm b/code/game/objects/items/devices/communicator/cartridge.dm
index e69de29bb2..6b29f1121e 100644
--- a/code/game/objects/items/devices/communicator/cartridge.dm
+++ b/code/game/objects/items/devices/communicator/cartridge.dm
@@ -0,0 +1,952 @@
+// Communicator peripheral devices
+// Internal devices that attack() can be relayed to
+// Additional UI menus for added functionality
+/obj/item/weapon/commcard
+ name = "generic commcard"
+ desc = "A peripheral plug-in for personal communicators."
+ icon = 'icons/obj/pda.dmi'
+ icon_state = "cart"
+ item_state = "electronic"
+ w_class = ITEMSIZE_TINY
+
+ var/list/internal_devices = list() // Devices that can be toggled on to trigger on attack()
+ var/list/active_devices = list() // Devices that will be triggered on attack()
+ var/list/ui_templates = list() // List of ui templates the commcard can access
+ var/list/internal_data = list() // Data that shouldn't be updated every time nanoUI updates, or needs to persist between updates
+
+
+/obj/item/weapon/commcard/proc/get_device_status()
+ var/list/L = list()
+ var/i = 1
+ for(var/obj/I in internal_devices)
+ if(I in active_devices)
+ L[++L.len] = list("name" = "\proper[I.name]", "active" = 1, "index" = i++)
+ else
+ L[++L.len] = list("name" = I.name, "active" = 0, "index" = i++)
+ return L
+
+
+// cartridge.get_data() returns a list of tuples:
+// The field element is the tag used to access the information by the template
+// The value element is the actual data, and can take any form necessary for the template
+/obj/item/weapon/commcard/proc/get_data()
+ return list()
+
+// Handles cartridge-specific functions
+// The helper.link() MUST HAVE 'cartridge_topic' passed into the href in order for cartridge functions to be processed.
+// Doesn't matter what the value of it is for now, it's just a flag to say, "Hey, there's cartridge data to change!"
+/obj/item/weapon/commcard/Topic(href, href_list)
+
+ // Signalers
+ if(href_list["signaler_target"])
+
+ var/obj/item/device/assembly/signaler/S = locate(href_list["signaler_target"]) // Should locate the correct signaler
+
+ if(!istype(S)) // Ref is no longer valid
+ return
+
+ if(S.loc != src) // No longer within the cartridge
+ return
+
+ switch(href_list["signaler_action"])
+ if("Pulse")
+ S.activate()
+
+ if("Edit")
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Ref no longer valid
+ return
+
+ var/newVal = input(user, "Input a new [href_list["signaler_value"]].", href_list["signaler_value"], (href_list["signaler_value"] == "Code" ? S.code : S.frequency)) as num|null
+ if(newVal)
+ switch(href_list["signaler_value"])
+ if("Code")
+ S.code = newVal
+
+ if("Frequency")
+ S.frequency = newVal
+
+ // Refresh list of powernet sensors
+ if(href_list["powernet_refresh"])
+ internal_data["grid_sensors"] = find_powernet_sensors()
+
+ // Load apc's on targeted powernet
+ if(href_list["powernet_target"])
+ internal_data["powernet_target"] = href_list["powernet_target"]
+
+ // GPS units
+ if(href_list["gps_target"])
+ var/obj/item/device/gps/G = locate(href_list["gps_target"])
+
+ if(!istype(G)) // Ref is no longer valid
+ return
+
+ if(G.loc != src) // No longer within the cartridge
+ return
+
+ switch(href_list["gps_action"])
+ if("Power")
+ G.tracking = text2num(href_list["value"])
+
+ if("Long_Range")
+ G.local_mode = text2num(href_list["value"])
+
+ if("Hide_Signal")
+ G.hide_signal = text2num(href_list["value"])
+
+ if("Tag")
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Ref no longer valid
+ return
+
+ var/newTag = input(user, "Please enter desired tag.", G.tag) as text|null
+
+ if(newTag)
+ G.tag = newTag
+
+ if(href_list["active_category"])
+ internal_data["supply_category"] = href_list["active_category"]
+
+ // Supply topic
+ // Copied from /obj/machinery/computer/supplycomp/Topic()
+ // code\game\machinery\computer\supply.dm, line 188
+ // Unfortunately, in order to support complete functionality, the whole thing is necessary
+ if(href_list["pack_ref"])
+ var/datum/supply_pack/S = locate(href_list["pack_ref"])
+
+ // Invalid ref
+ if(!istype(S))
+ return
+
+ // Expand the supply pack's contents
+ if(href_list["expand"])
+ internal_data["supply_pack_expanded"] ^= S
+
+ // Make a request for the pack
+ if(href_list["request"])
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Invalid ref
+ return
+
+ if(world.time < internal_data["supply_reqtime"])
+ visible_message("[src] flashes, \"[internal_data["supply_reqtime"] - world.time] seconds remaining until another requisition form may be printed.\"")
+ return
+
+ var/timeout = world.time + 600
+ var/reason = sanitize(input(user, "Reason:","Why do you require this item?","") as null|text)
+ if(world.time > timeout)
+ to_chat(user, "Error. Request timed out.")
+ return
+ if(!reason)
+ return
+
+ supply_controller.create_order(S, user, reason)
+ internal_data["supply_reqtime"] = (world.time + 5) % 1e5
+
+ if(href_list["order_ref"])
+ var/datum/supply_order/O = locate(href_list["order_ref"])
+
+ // Invalid ref
+ if(!istype(O))
+ return
+
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Invalid ref
+ return
+
+ if(href_list["edit"])
+ var/new_val = sanitize(input(user, href_list["edit"], "Enter the new value for this field:", href_list["default"]) as null|text)
+ if(!new_val)
+ return
+
+ switch(href_list["edit"])
+ if("Supply Pack")
+ O.name = new_val
+
+ if("Cost")
+ var/num = text2num(new_val)
+ if(num)
+ O.cost = num
+
+ if("Index")
+ var/num = text2num(new_val)
+ if(num)
+ O.index = num
+
+ if("Reason")
+ O.comment = new_val
+
+ if("Ordered by")
+ O.ordered_by = new_val
+
+ if("Ordered at")
+ O.ordered_at = new_val
+
+ if("Approved by")
+ O.approved_by = new_val
+
+ if("Approved at")
+ O.approved_at = new_val
+
+ if(href_list["approve"])
+ supply_controller.approve_order(O, user)
+
+ if(href_list["deny"])
+ supply_controller.deny_order(O, user)
+
+ if(href_list["delete"])
+ supply_controller.delete_order(O, user)
+
+ if(href_list["clear_all_requests"])
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Invalid ref
+ return
+
+ supply_controller.deny_all_pending(user)
+
+ if(href_list["export_ref"])
+ var/datum/exported_crate/E = locate(href_list["export_ref"])
+
+ // Invalid ref
+ if(!istype(E))
+ return
+
+ var/mob/user = locate(href_list["user"])
+ if(!istype(user)) // Invalid ref
+ return
+
+ if(href_list["index"])
+ var/list/L = E.contents[href_list["index"]]
+
+ if(href_list["edit"])
+ var/field = alert(user, "Select which field to edit", , "Name", "Quantity", "Value")
+
+ var/new_val = sanitize(input(user, href_list["edit"], "Enter the new value for this field:", href_list["default"]) as null|text)
+ if(!new_val)
+ return
+
+ switch(field)
+ if("Name")
+ L["object"] = new_val
+
+ if("Quantity")
+ var/num = text2num(new_val)
+ if(num)
+ L["quantity"] = num
+
+ if("Value")
+ var/num = text2num(new_val)
+ if(num)
+ L["value"] = num
+
+ if(href_list["delete"])
+ E.contents.Cut(href_list["index"], href_list["index"] + 1)
+
+ // Else clause means they're editing/deleting the whole export report, rather than a specific item in it
+ else if(href_list["edit"])
+ var/new_val = sanitize(input(user, href_list["edit"], "Enter the new value for this field:", href_list["default"]) as null|text)
+ if(!new_val)
+ return
+
+ switch(href_list["edit"])
+ if("Name")
+ E.name = new_val
+
+ if("Value")
+ var/num = text2num(new_val)
+ if(num)
+ E.value = num
+
+ else if(href_list["delete"])
+ supply_controller.delete_export(E, user)
+
+ else if(href_list["add_item"])
+ supply_controller.add_export_item(E, user)
+
+ if(supply_controller && supply_controller.shuttle)
+ switch(href_list["send_shuttle"])
+ if("send_away")
+ if(supply_controller.shuttle.forbidden_atoms_check())
+ to_chat(usr, "For safety reasons the automated supply shuttle cannot transport live organisms, classified nuclear weaponry or homing beacons.")
+ else
+ supply_controller.shuttle.launch(src)
+ to_chat(usr, "Initiating launch sequence.")
+
+ if("send_to_station")
+ supply_controller.shuttle.launch(src)
+ to_chat(usr, "The supply shuttle has been called and will arrive in approximately [round(supply_controller.movetime/600,1)] minutes.")
+
+ if("cancel_shuttle")
+ supply_controller.shuttle.cancel_launch(src)
+
+ if("force_shuttle")
+ supply_controller.shuttle.force_launch(src)
+
+ // Status display
+ switch(href_list["stat_display"])
+ if("message")
+ post_status("message", internal_data["stat_display_line1"], internal_data["stat_display_line2"])
+ internal_data["stat_display_special"] = "message"
+ if("alert")
+ post_status("alert", href_list["alert"])
+ internal_data["stat_display_special"] = href_list["alert"]
+ if("setmsg")
+ internal_data["stat_display_line[href_list["line"]]"] = reject_bad_text(sanitize(input("Line 1", "Enter Message Text", internal_data["stat_display_line[href_list["line"]]"]) as text|null, 40), 40)
+ else
+ post_status(href_list["stat_display"])
+ internal_data["stat_display_special"] = href_list["stat_display"]
+
+ // Merc shuttle blast door controls
+ switch(href_list["all_blast_doors"])
+ if("open")
+ for(var/obj/machinery/door/blast/B in internal_data["shuttle_doors"])
+ B.open()
+ if("close")
+ for(var/obj/machinery/door/blast/B in internal_data["shuttle_doors"])
+ B.close()
+
+ if(href_list["scan_blast_doors"])
+ internal_data["shuttle_doors"] = find_blast_doors()
+
+ if(href_list["toggle_blast_door"])
+ var/obj/machinery/door/blast/B = locate(href_list["toggle_blast_door"])
+ if(!B)
+ return
+ spawn(0)
+ if(B.density)
+ B.open()
+ else
+ B.close()
+
+
+// Updates status displays with a new message
+// Copied from /obj/item/weapon/cartridge/proc/post_status(),
+// code/game/objects/items/devices/PDA/cart.dm, line 251
+/obj/item/weapon/commcard/proc/post_status(var/command, var/data1, var/data2)
+ var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
+ if(!frequency)
+ return
+
+ var/datum/signal/status_signal = new
+ status_signal.source = src
+ status_signal.transmission_method = 1
+ status_signal.data["command"] = command
+
+ switch(command)
+ if("message")
+ status_signal.data["msg1"] = data1
+ status_signal.data["msg2"] = data2
+ internal_data["stat_display_active1"] = data1 // Update the internally stored message, we won't get receive_signal if we're the sender
+ internal_data["stat_display_active2"] = data2
+ if(loc)
+ var/obj/item/PDA = loc
+ var/mob/user = PDA.fingerprintslast
+ log_admin("STATUS: [user] set status screen with [src]. Message: [data1] [data2]")
+ message_admins("STATUS: [user] set status screen with [src]. Message: [data1] [data2]")
+
+ if("alert")
+ status_signal.data["picture_state"] = data1
+
+ frequency.post_signal(src, status_signal)
+
+// Receives updates by external devices to the status displays
+/obj/item/weapon/commcard/receive_signal(var/datum/signal/signal, var/receive_method, var/receive_param)
+ internal_data["stat_display_special"] = signal.data["command"]
+ switch(signal.data["command"])
+ if("message")
+ internal_data["stat_display_active1"] = signal.data["msg1"]
+ internal_data["stat_display_active2"] = signal.data["msg2"]
+ if("alert")
+ internal_data["stat_display_special"] = signal.data["picture_state"]
+
+
+///////////////////////////
+// SUBTYPES
+///////////////////////////
+
+
+// Engineering Cartridge:
+// Devices
+// *- Halogen Counter
+// Templates
+// *- Power Monitor
+/obj/item/weapon/commcard/engineering
+ name = "\improper Power-ON cartridge"
+ icon_state = "cart-e"
+ ui_templates = list(list("name" = "Power Monitor", "template" = "comm_power_monitor.tmpl"))
+
+/obj/item/weapon/commcard/engineering/New()
+ ..()
+ internal_devices |= new /obj/item/device/halogen_counter(src)
+
+/obj/item/weapon/commcard/engineering/initialize()
+ internal_data["grid_sensors"] = find_powernet_sensors()
+ internal_data["powernet_target"] = ""
+
+/obj/item/weapon/commcard/engineering/get_data()
+ return list(
+ list("field" = "powernet_monitoring", "value" = get_powernet_monitoring_list()),
+ list("field" = "powernet_target", "value" = get_powernet_target(internal_data["powernet_target"]))
+ )
+
+// Atmospherics Cartridge:
+// Devices
+// *- Gas scanner
+/obj/item/weapon/commcard/atmos
+ name = "\improper BreatheDeep cartridge"
+ icon_state = "cart-a"
+
+/obj/item/weapon/commcard/atmos/New()
+ ..()
+ internal_devices |= new /obj/item/device/analyzer(src)
+
+
+// Medical Cartridge:
+// Devices
+// *- Halogen Counter
+// *- Health Analyzer
+// Templates
+// *- Medical Records
+/obj/item/weapon/commcard/medical
+ name = "\improper Med-U cartridge"
+ icon_state = "cart-m"
+ ui_templates = list(list("name" = "Medical Records", "template" = "med_records.tmpl"))
+
+/obj/item/weapon/commcard/medical/New()
+ ..()
+ internal_devices |= new /obj/item/device/healthanalyzer(src)
+ internal_devices |= new /obj/item/device/halogen_counter(src)
+
+/obj/item/weapon/commcard/medical/get_data()
+ return list(list("field" = "med_records", "value" = get_med_records()))
+
+
+// Chemistry Cartridge:
+// Devices
+// *- Halogen Counter
+// *- Health Analyzer
+// *- Reagent Scanner
+// Templates
+// *- Medical Records
+/obj/item/weapon/commcard/medical/chemistry
+ name = "\improper ChemWhiz cartridge"
+ icon_state = "cart-chem"
+
+/obj/item/weapon/commcard/medical/chemistry/New()
+ ..()
+ internal_devices |= new /obj/item/device/reagent_scanner(src)
+
+
+// Detective Cartridge:
+// Devices
+// *- Halogen Counter
+// *- Health Analyzer
+// Templates
+// *- Medical Records
+// *- Security Records
+/obj/item/weapon/commcard/medical/detective
+ name = "\improper D.E.T.E.C.T. cartridge"
+ icon_state = "cart-s"
+ ui_templates = list(
+ list("name" = "Medical Records", "template" = "med_records.tmpl"),
+ list("name" = "Security Records", "template" = "sec_records.tmpl")
+ )
+
+/obj/item/weapon/commcard/medical/detective/get_data()
+ var/list/data = ..()
+ data[++data.len] = list("field" = "sec_records", "value" = get_sec_records())
+ return data
+
+
+// Internal Affairs Cartridge:
+// Templates
+// *- Security Records
+// *- Employment Records
+/obj/item/weapon/commcard/int_aff
+ name = "\improper P.R.O.V.E. cartridge"
+ icon_state = "cart-s"
+ ui_templates = list(
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Security Records", "template" = "sec_records.tmpl")
+ )
+
+/obj/item/weapon/commcard/int_aff/get_data()
+ return list(
+ list("field" = "emp_records", "value" = get_emp_records()),
+ list("field" = "sec_records", "value" = get_sec_records())
+ )
+
+
+// Security Cartridge:
+// Templates
+// *- Security Records
+// *- Security Bot Access
+/obj/item/weapon/commcard/security
+ name = "\improper R.O.B.U.S.T. cartridge"
+ icon_state = "cart-s"
+ ui_templates = list(
+ list("name" = "Security Records", "template" = "sec_records.tmpl"),
+ list("name" = "Security Bot Control", "template" = "sec_bot_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/security/get_data()
+ return list(
+ list("field" = "sec_records", "value" = get_sec_records()),
+ list("field" = "sec_bot_access", "value" = get_sec_bot_access())
+ )
+
+
+// Janitor Cartridge:
+// Templates
+// *- Janitorial Locator Magicbox
+/obj/item/weapon/commcard/janitor
+ name = "\improper CustodiPRO cartridge"
+ desc = "The ultimate in clean-room design."
+ ui_templates = list(
+ list("name" = "Janitorial Supply Locator", "template" = "janitorialLocator.tmpl")
+ )
+
+/obj/item/weapon/commcard/janitor/get_data()
+ return list(
+ list("field" = "janidata", "value" = get_janitorial_locations())
+ )
+
+
+// Signal Cartridge:
+// Devices
+// *- Signaler
+// Templates
+// *- Signaler Access
+/obj/item/weapon/commcard/signal
+ name = "generic signaler cartridge"
+ desc = "A data cartridge with an integrated radio signaler module."
+ ui_templates = list(
+ list("name" = "Integrated Signaler Control", "template" = "signaler_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/signal/New()
+ ..()
+ internal_devices |= new /obj/item/device/assembly/signaler(src)
+
+/obj/item/weapon/commcard/signal/get_data()
+ return list(
+ list("field" = "signaler_access", "value" = get_int_signalers())
+ )
+
+
+// Science Cartridge:
+// Devices
+// *- Signaler
+// *- Reagent Scanner
+// *- Gas Scanner
+// Templates
+// *- Signaler Access
+/obj/item/weapon/commcard/signal/science
+ name = "\improper Signal Ace 2 cartridge"
+ desc = "Complete with integrated radio signaler!"
+ icon_state = "cart-tox"
+ // UI templates inherited
+
+/obj/item/weapon/commcard/signal/science/New()
+ ..()
+ internal_devices |= new /obj/item/device/reagent_scanner(src)
+ internal_devices |= new /obj/item/device/analyzer(src)
+
+
+// Supply Cartridge:
+// Templates
+// *- Supply Records
+/obj/item/weapon/commcard/supply
+ name = "\improper Space Parts & Space Vendors cartridge"
+ desc = "Perfect for the Quartermaster on the go!"
+ icon_state = "cart-q"
+ ui_templates = list(
+ list("name" = "Supply Records", "template" = "supply_records.tmpl")
+ )
+
+/obj/item/weapon/commcard/supply/New()
+ ..()
+ internal_data["supply_category"] = null
+ internal_data["supply_controls"] = FALSE // Cannot control the supply shuttle, cannot accept orders
+ internal_data["supply_pack_expanded"] = list()
+ internal_data["supply_reqtime"] = -1
+
+/obj/item/weapon/commcard/supply/get_data()
+ // Supply records data
+ var/list/shuttle_status = get_supply_shuttle_status()
+ var/list/orders = get_supply_orders()
+ var/list/receipts = get_supply_receipts()
+ var/list/misc_supply_data = get_misc_supply_data() // Packaging this stuff externally so it's less hardcoded into the specific cartridge
+ var/list/pack_list = list() // List of supply packs within the currently selected category
+
+ if(internal_data["supply_category"])
+ pack_list = get_supply_pack_list()
+
+ return list(
+ list("field" = "shuttle_auth", "value" = misc_supply_data["shuttle_auth"]),
+ list("field" = "order_auth", "value" = misc_supply_data["order_auth"]),
+ list("field" = "supply_points", "value" = misc_supply_data["supply_points"]),
+ list("field" = "categories", "value" = misc_supply_data["supply_categories"]),
+ list("field" = "contraband", "value" = misc_supply_data["contraband"]),
+ list("field" = "active_category", "value" = internal_data["supply_category"]),
+ list("field" = "shuttle", "value" = shuttle_status),
+ list("field" = "orders", "value" = orders),
+ list("field" = "receipts", "value" = receipts),
+ list("field" = "supply_packs", "value" = pack_list)
+ )
+
+
+// Command Cartridge:
+// Templates
+// *- Status Display Access
+// *- Employment Records
+/obj/item/weapon/commcard/head
+ name = "\improper Easy-Record DELUXE"
+ icon_state = "cart-h"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/New()
+ ..()
+ internal_data["stat_display_line1"] = null
+ internal_data["stat_display_line2"] = null
+ internal_data["stat_display_active1"] = null
+ internal_data["stat_display_active2"] = null
+ internal_data["stat_display_special"] = null
+
+/obj/item/weapon/commcard/head/initialize()
+ // Have to register the commcard with the Radio controller to receive updates to the status displays
+ radio_controller.add_object(src, 1435)
+ ..()
+
+/obj/item/weapon/commcard/head/Destroy()
+ // Have to unregister the commcard for proper bookkeeping
+ radio_controller.remove_object(src, 1435)
+ ..()
+
+/obj/item/weapon/commcard/head/get_data()
+ return list(
+ list("field" = "emp_records", "value" = get_emp_records()),
+ list("field" = "stat_display", "value" = get_status_display())
+ )
+
+// Head of Personnel Cartridge:
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Security Records
+// *- Supply Records
+// ?- Supply Bot Access
+// *- Janitorial Locator Magicbox
+/obj/item/weapon/commcard/head/hop
+ name = "\improper HumanResources9001 cartridge"
+ icon_state = "cart-h"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Security Records", "template" = "sec_records.tmpl"),
+ list("name" = "Supply Records", "template" = "supply_records.tmpl"),
+ list("name" = "Janitorial Supply Locator", "template" = "janitorialLocator.tmpl")
+ )
+
+
+/obj/item/weapon/commcard/head/hop/get_data()
+ var/list/data = ..()
+
+ // Sec records
+ data[++data.len] = list("field" = "sec_records", "value" = get_sec_records())
+
+ // Supply records data
+ var/list/shuttle_status = get_supply_shuttle_status()
+ var/list/orders = get_supply_orders()
+ var/list/receipts = get_supply_receipts()
+ var/list/misc_supply_data = get_misc_supply_data() // Packaging this stuff externally so it's less hardcoded into the specific cartridge
+ var/list/pack_list = list() // List of supply packs within the currently selected category
+
+ if(internal_data["supply_category"])
+ pack_list = get_supply_pack_list()
+
+ data[++data.len] = list("field" = "shuttle_auth", "value" = misc_supply_data["shuttle_auth"])
+ data[++data.len] = list("field" = "order_auth", "value" = misc_supply_data["order_auth"])
+ data[++data.len] = list("field" = "supply_points", "value" = misc_supply_data["supply_points"])
+ data[++data.len] = list("field" = "categories", "value" = misc_supply_data["supply_categories"])
+ data[++data.len] = list("field" = "contraband", "value" = misc_supply_data["contraband"])
+ data[++data.len] = list("field" = "active_category", "value" = internal_data["supply_category"])
+ data[++data.len] = list("field" = "shuttle", "value" = shuttle_status)
+ data[++data.len] = list("field" = "orders", "value" = orders)
+ data[++data.len] = list("field" = "receipts", "value" = receipts)
+ data[++data.len] = list("field" = "supply_packs", "value" = pack_list)
+
+ // Janitorial locator magicbox
+ data[++data.len] = list("field" = "janidata", "value" = get_janitorial_locations())
+
+ return data
+
+
+// Head of Security Cartridge:
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Security Records
+// *- Security Bot Access
+/obj/item/weapon/commcard/head/hos
+ name = "\improper R.O.B.U.S.T. DELUXE"
+ icon_state = "cart-hos"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Security Records", "template" = "sec_records.tmpl"),
+ list("name" = "Security Bot Control", "template" = "sec_bot_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/hos/get_data()
+ var/list/data = ..()
+ // Sec records
+ data[++data.len] = list("field" = "sec_records", "value" = get_sec_records())
+ // Sec bot access
+ data[++data.len] = list("field" = "sec_bot_access", "value" = get_sec_bot_access())
+ return data
+
+
+// Research Director Cartridge:
+// Devices
+// *- Signaler
+// *- Gas Scanner
+// *- Reagent Scanner
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Signaler Access
+/obj/item/weapon/commcard/head/rd
+ name = "\improper Signal Ace DELUXE"
+ icon_state = "cart-rd"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Integrated Signaler Control", "template" = "signaler_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/rd/New()
+ ..()
+ internal_devices |= new /obj/item/device/analyzer(src)
+ internal_devices |= new /obj/item/device/reagent_scanner(src)
+ internal_devices |= new /obj/item/device/assembly/signaler(src)
+
+/obj/item/weapon/commcard/head/rd/get_data()
+ var/list/data = ..()
+ // Signaler access
+ data[++data.len] = list("field" = "signaler_access", "value" = get_int_signalers())
+ return data
+
+
+// Chief Medical Officer Cartridge:
+// Devices
+// *- Health Analyzer
+// *- Reagent Scanner
+// *- Halogen Counter
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Medical Records
+/obj/item/weapon/commcard/head/cmo
+ name = "\improper Med-U DELUXE"
+ icon_state = "cart-cmo"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Medical Records", "template" = "med_records.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/cmo/New()
+ ..()
+ internal_devices |= new /obj/item/device/healthanalyzer(src)
+ internal_devices |= new /obj/item/device/reagent_scanner(src)
+ internal_devices |= new /obj/item/device/halogen_counter(src)
+
+/obj/item/weapon/commcard/head/cmo/get_data()
+ var/list/data = ..()
+ // Med records
+ data[++data.len] = list("field" = "med_records", "value" = get_med_records())
+ return data
+
+// Chief Engineer Cartridge:
+// Devices
+// *- Gas Scanner
+// *- Halogen Counter
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Power Monitoring
+/obj/item/weapon/commcard/head/ce
+ name = "\improper Power-On DELUXE"
+ icon_state = "cart-ce"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Power Monitor", "template" = "comm_power_monitor.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/ce/New()
+ ..()
+ internal_devices |= new /obj.item/device/analyzer(src)
+ internal_devices |= new /obj/item/device/halogen_counter(src)
+
+/obj/item/weapon/commcard/head/ce/initialize()
+ internal_data["grid_sensors"] = find_powernet_sensors()
+ internal_data["powernet_target"] = ""
+
+/obj/item/weapon/commcard/head/ce/get_data()
+ var/list/data = ..()
+ // Add power monitoring data
+ data[++data.len] = list("field" = "powernet_monitoring", "value" = get_powernet_monitoring_list())
+ data[++data.len] = list("field" = "powernet_target", "value" = get_powernet_target(internal_data["powernet_target"]))
+ return data
+
+
+// Captain Cartridge:
+// Devices
+// *- Health analyzer
+// *- Gas Scanner
+// *- Reagent Scanner
+// *- Halogen Counter
+// X- GPS - Balance
+// *- Signaler
+// Templates
+// *- Status Display Access
+// *- Employment Records
+// *- Medical Records
+// *- Security Records
+// *- Power Monitoring
+// *- Supply Records
+// X- Supply Bot Access - Mulebots usually break when used
+// *- Security Bot Access
+// *- Janitorial Locator Magicbox
+// X- GPS Access - Balance
+// *- Signaler Access
+/obj/item/weapon/commcard/head/captain
+ name = "\improper Value-PAK cartridge"
+ desc = "Now with 200% more value!"
+ icon_state = "cart-c"
+ ui_templates = list(
+ list("name" = "Status Display Access", "template" = "stat_display_access.tmpl"),
+ list("name" = "Employment Records", "template" = "emp_records.tmpl"),
+ list("name" = "Medical Records", "template" = "med_records.tmpl"),
+ list("name" = "Security Records", "template" = "sec_records.tmpl"),
+ list("name" = "Security Bot Control", "template" = "sec_bot_access.tmpl"),
+ list("name" = "Power Monitor", "template" = "comm_power_monitor.tmpl"),
+ list("name" = "Supply Records", "template" = "supply_records.tmpl"),
+ list("name" = "Janitorial Supply Locator", "template" = "janitorialLocator.tmpl"),
+ list("name" = "Integrated Signaler Control", "template" = "signaler_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/head/captain/New()
+ ..()
+ internal_devices |= new /obj.item/device/analyzer(src)
+ internal_devices |= new /obj/item/device/healthanalyzer(src)
+ internal_devices |= new /obj/item/device/reagent_scanner(src)
+ internal_devices |= new /obj/item/device/halogen_counter(src)
+ internal_devices |= new /obj/item/device/assembly/signaler(src)
+
+/obj/item/weapon/commcard/head/captain/get_data()
+ var/list/data = ..()
+ //Med records
+ data[++data.len] = list("field" = "med_records", "value" = get_med_records())
+
+ // Sec records
+ data[++data.len] = list("field" = "sec_records", "value" = get_sec_records())
+
+ // Sec bot access
+ data[++data.len] = list("field" = "sec_bot_access", "value" = get_sec_bot_access())
+
+ // Power Monitoring
+ data[++data.len] = list("field" = "powernet_monitoring", "value" = get_powernet_monitoring_list())
+ data[++data.len] = list("field" = "powernet_target", "value" = get_powernet_target(internal_data["powernet_target"]))
+
+ // Supply records data
+ var/list/shuttle_status = get_supply_shuttle_status()
+ var/list/orders = get_supply_orders()
+ var/list/receipts = get_supply_receipts()
+ var/list/misc_supply_data = get_misc_supply_data() // Packaging this stuff externally so it's less hardcoded into the specific cartridge
+ var/list/pack_list = list() // List of supply packs within the currently selected category
+
+ if(internal_data["supply_category"])
+ pack_list = get_supply_pack_list()
+
+ data[++data.len] = list("field" = "shuttle_auth", "value" = misc_supply_data["shuttle_auth"])
+ data[++data.len] = list("field" = "order_auth", "value" = misc_supply_data["order_auth"])
+ data[++data.len] = list("field" = "supply_points", "value" = misc_supply_data["supply_points"])
+ data[++data.len] = list("field" = "categories", "value" = misc_supply_data["supply_categories"])
+ data[++data.len] = list("field" = "contraband", "value" = misc_supply_data["contraband"])
+ data[++data.len] = list("field" = "active_category", "value" = internal_data["supply_category"])
+ data[++data.len] = list("field" = "shuttle", "value" = shuttle_status)
+ data[++data.len] = list("field" = "orders", "value" = orders)
+ data[++data.len] = list("field" = "receipts", "value" = receipts)
+ data[++data.len] = list("field" = "supply_packs", "value" = pack_list)
+
+ // Janitorial locator magicbox
+ data[++data.len] = list("field" = "janidata", "value" = get_janitorial_locations())
+
+ // Signaler access
+ data[++data.len] = list("field" = "signaler_access", "value" = get_int_signalers())
+
+ return data
+
+
+// Mercenary Cartridge
+// Templates
+// *- Merc Shuttle Door Controller
+/obj/item/weapon/commcard/mercenary
+ name = "\improper Detomatix cartridge"
+ icon_state = "cart"
+ ui_templates = list(
+ list("name" = "Shuttle Blast Door Control", "template" = "merc_blast_door_control.tmpl")
+ )
+
+/obj/item/weapon/commcard/mercenary/initialize()
+ internal_data["shuttle_door_code"] = "smindicate" // Copied from PDA code
+ internal_data["shuttle_doors"] = find_blast_doors()
+
+/obj/item/weapon/commcard/mercenary/get_data()
+ var/door_status[0]
+ for(var/obj/machinery/door/blast/B in internal_data["shuttle_doors"])
+ door_status[++door_status.len] += list(
+ "open" = B.density,
+ "name" = B.name,
+ "ref" = "\ref[B]"
+ )
+
+ return list(
+ list("field" = "blast_door", "value" = door_status)
+ )
+
+
+// Explorer Cartridge
+// Devices
+// *- GPS
+// Templates
+// *- GPS Access
+
+// IMPORTANT: NOT MAPPED IN DUE TO BALANCE CONCERNS RE: FINDING THE VICTIMS OF ANTAGS.
+// See suit sensors, specifically ease of turning them off, and variable level of settings which may or may not give location
+// A GPS in your phone that is either broadcasting position or totally off, and can be hidden in pockets, coats, bags, boxes, etc, is much harder to disable
+/obj/item/weapon/commcard/explorer
+ name = "\improper Explorator cartridge"
+ icon_state = "cart-tox"
+ ui_templates = list(
+ list("name" = "Integrated GPS", "template" = "gps_access.tmpl")
+ )
+
+/obj/item/weapon/commcard/explorer/New()
+ ..()
+ internal_devices |= new /obj/item/device/gps/explorer(src)
+
+/obj/item/weapon/commcard/explorer/get_data()
+ var/list/GPS = get_GPS_lists()
+
+ return list(
+ list("field" = "gps_access", "value" = GPS[1]),
+ list("field" = "gps_signal", "value" = GPS[2]),
+ list("field" = "gps_status", "value" = GPS[3])
+ )
\ No newline at end of file
diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm
index d7e62a53c7..ec9c09c161 100644
--- a/code/game/objects/items/devices/communicator/communicator.dm
+++ b/code/game/objects/items/devices/communicator/communicator.dm
@@ -4,6 +4,18 @@
var/global/list/obj/item/device/communicator/all_communicators = list()
+// List of core tabs the communicator can switch to
+#define HOMETAB 1
+#define PHONTAB 2
+#define CONTTAB 3
+#define MESSTAB 4
+#define NEWSTAB 5
+#define NOTETAB 6
+#define WTHRTAB 7
+#define MANITAB 8
+#define SETTTAB 9
+#define EXTRTAB 10
+
/obj/item/device/communicator
name = "communicator"
desc = "A personal device used to enable long range dialog between two people, utilizing existing telecommunications infrastructure to allow \
@@ -31,20 +43,22 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
var/note = "Thank you for choosing the T-14.2 Communicator, this is your notepad!" //Current note in the notepad function
var/notehtml = ""
- var/obj/item/weapon/cartridge/cartridge = null //current cartridge
+ var/obj/item/weapon/commcard/cartridge = null //current cartridge
var/fon = 0 // Internal light
var/flum = 2 // Brightness
var/list/modules = list(
- list("module" = "Phone", "icon" = "phone64", "number" = 2),
- list("module" = "Contacts", "icon" = "person64", "number" = 3),
- list("module" = "Messaging", "icon" = "comment64", "number" = 4),
- list("module" = "Note", "icon" = "note64", "number" = 5),
- list("module" = "Weather", "icon" = "sun64", "number" = 6),
- list("module" = "Settings", "icon" = "gear64", "number" = 7)
+ list("module" = "Phone", "icon" = "phone64", "number" = PHONTAB),
+ list("module" = "Contacts", "icon" = "person64", "number" = CONTTAB),
+ list("module" = "Messaging", "icon" = "comment64", "number" = MESSTAB),
+ list("module" = "News", "icon" = "note64", "number" = NEWSTAB), // Need a different icon,
+ list("module" = "Note", "icon" = "note64", "number" = NOTETAB),
+ list("module" = "Weather", "icon" = "sun64", "number" = WTHRTAB),
+ list("module" = "Crew Manifest", "icon" = "note64", "number" = MANITAB), // Need a different icon,
+ list("module" = "Settings", "icon" = "gear64", "number" = SETTTAB),
) //list("module" = "Name of Module", "icon" = "icon name64", "number" = "what tab is the module")
- var/selected_tab = 1
+ var/selected_tab = HOMETAB
var/owner = ""
var/occupation = ""
var/alert_called = 0
@@ -89,7 +103,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
/obj/item/device/communicator/examine(mob/user)
. = ..(user, 1)
if(. && video_source)
- user << "It looks like it's on a video call: \[view\]"
+ to_chat(user, "It looks like it's on a video call: \[view\]")
// Proc: initialize_exonet()
// Parameters: 1 (user - the person the communicator belongs to)
@@ -130,7 +144,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
else
msg += "The device doesn't appear to be transmitting any data.\n"
msg += ""
- user << msg
+ to_chat(user, msg)
return
// Proc: emp_act()
@@ -188,10 +202,22 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
if(!get_connection_to_tcomms())
close_connection(reason = "Connection timed out")
+// Proc: attack()
+// Parameters: 2 (M - what is being attacked. user - the mob that has the communicator)
+// Description: When the communicator has an attached commcard with internal devices, relay the attack() through to those devices.
+// Contents of the for loop are copied from gripper code, because that does approximately what we want to do.
+/obj/item/device/communicator/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
+ if(cartridge && cartridge.active_devices)
+ for(var/obj/item/wrapped in cartridge.active_devices)
+ if(wrapped) //The force of the wrapped obj gets set to zero during the attack() and afterattack().
+ wrapped.attack(M,user)
+ return 0
+
// Proc: attackby()
// Parameters: 2 (C - what is used on the communicator. user - the mob that has the communicator)
// Description: When an ID is swiped on the communicator, the communicator reads the job and checks it against the Owner name, if success, the occupation is added.
/obj/item/device/communicator/attackby(obj/item/C as obj, mob/user as mob)
+ ..()
if(istype(C, /obj/item/weapon/card/id))
var/obj/item/weapon/card/id/idcard = C
if(!idcard.registered_name || !idcard.assignment)
@@ -201,12 +227,14 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
else if(owner == idcard.registered_name)
occupation = idcard.assignment
to_chat(user, "Occupation updated.")
-// else if(istype(C, /obj/item/weapon/cartridge))
-// if(cartridge)
-// to_chat(user, "\The [src] already has an external device attached!")
-// else
-// modules.Add(list("module" = "External Device", "icon = external64", "number" = 8))
-// cartridge = C
+
+ if(istype(C, /obj/item/weapon/commcard) && !cartridge)
+ cartridge = C
+ user.drop_item()
+ cartridge.forceMove(src)
+ to_chat(usr, "You slot \the [cartridge] into \the [src].")
+ modules[++modules.len] = list("module" = "External Device", "icon" = "external64", "number" = EXTRTAB)
+ GLOB.nanomanager.update_uis(src) // update all UIs attached to src
return
// Proc: attack_self()
@@ -330,6 +358,38 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
client_huds |= global_hud.whitense
client_huds |= global_hud.darkMask
+/obj/item/device/communicator/verb/verb_remove_cartridge()
+ set category = "Object"
+ set name = "Remove commcard"
+ set src in usr
+
+ // Can't remove what isn't there
+ if(!cartridge)
+ to_chat(usr, "There isn't a commcard to remove!")
+ return
+
+ // Can't remove if you're physically unable to
+ if(usr.stat || usr.restrained() || usr.paralysis || usr.stunned || usr.weakened)
+ to_chat(usr, "You cannot do this while restrained.")
+ return
+
+ var/turf/T = get_turf(src)
+ cartridge.loc = T
+ // If it's in someone, put the cartridge in their hands
+ if (ismob(loc))
+ var/mob/M = loc
+ M.put_in_hands(cartridge)
+ // Else just set it on the ground
+ else
+ cartridge.loc = get_turf(src)
+ cartridge = null
+ // We have to iterate through the modules to find EXTRTAB, because list procs don't play nice with a list of lists
+ for(var/i = 1, i <= modules.len, i++)
+ if(modules[i]["number"] == EXTRTAB)
+ modules.Cut(i, i+1)
+ break
+ to_chat(usr, "You remove \the [cartridge] from the [name].")
+
//It's the 26th century. We should have smart watches by now.
/obj/item/device/communicator/watch
name = "communicator watch"
diff --git a/code/game/objects/items/devices/communicator/helper.dm b/code/game/objects/items/devices/communicator/helper.dm
index 9c121112ac..b7a3b752d9 100644
--- a/code/game/objects/items/devices/communicator/helper.dm
+++ b/code/game/objects/items/devices/communicator/helper.dm
@@ -11,17 +11,532 @@
var/co2_level = environment.gas["carbon_dioxide"]/total_moles
var/phoron_level = environment.gas["phoron"]/total_moles
var/unknown_level = 1-(o2_level+n2_level+co2_level+phoron_level)
+
+ // Label is what the entry is describing
+ // Type identifies which unit or other special characters to use
+ // Val is the information reported
+ // Bad_high/_low are the values outside of which the entry reports as dangerous
+ // Poor_high/_low are the values outside of which the entry reports as unideal
+ // Values were extracted from the template itself
results = list(
- "pressure" = "[round(pressure,0.1)]",
- "nitrogen" = "[round(n2_level*100,0.1)]",
- "oxygen" = "[round(o2_level*100,0.1)]",
- "carbon_dioxide" = "[round(co2_level*100,0.1)]",
- "phoron" = "[round(phoron_level*100,0.01)]",
- "other" = "[round(unknown_level, 0.01)]",
- "temp" = "[round(environment.temperature-T0C,0.1)]",
- "reading" = 1
+ list("entry" = "Pressure", "type" = "pressure", "val" = "[round(pressure,0.1)]", "bad_high" = 120, "poor_high" = 110, "poor_low" = 95, "bad_low" = 80),
+ list("entry" = "Temperature", "type" = "temp", "val" = "[round(environment.temperature-T0C,0.1)]", "bad_high" = 35, "poor_high" = 25, "poor_low" = 15, "bad_low" = 5),
+ list("entry" = "Oxygen", "type" = "pressure", "val" = "[round(o2_level*100,0.1)]", "bad_high" = 140, "poor_high" = 135, "poor_low" = 19, "bad_low" = 17),
+ list("entry" = "Nitrogen", "type" = "pressure", "val" = "[round(n2_level*100,0.1)]", "bad_high" = 105, "poor_high" = 85, "poor_low" = 50, "bad_low" = 40),
+ list("entry" = "Carbon Dioxide", "type" = "pressure", "val" = "[round(co2_level*100,0.1)]", "bad_high" = 10, "poor_high" = 5, "poor_low" = 0, "bad_low" = 0),
+ list("entry" = "Phoron", "type" = "pressure", "val" = "[round(phoron_level*100,0.01)]", "bad_high" = 0.5, "poor_high" = 0, "poor_low" = 0, "bad_low" = 0),
+ list("entry" = "Other", "type" = "pressure", "val" = "[round(unknown_level, 0.01)]", "bad_high" = 1, "poor_high" = 0.5, "poor_low" = 0, "bad_low" = 0)
)
if(isnull(results))
- results = list("reading" = 0)
- return results
\ No newline at end of file
+ results = list(list("entry" = "pressure", "val" = "0"))
+ return results
+
+
+// Proc - compile_news()
+// Parameters - none
+// Description - Returns the list of newsfeeds, compiled for template processing
+/obj/item/device/communicator/proc/compile_news()
+ var/list/feeds = list()
+ for(var/datum/feed_channel/channel in news_network.network_channels)
+ var/list/messages = list()
+ if(!channel.censored)
+ var/index = 0
+ for(var/datum/feed_message/FM in channel.messages)
+ index++
+ if(FM.img)
+ usr << browse_rsc(FM.img, "pda_news_tmp_photo_[feeds["channel"]]_[index].png")
+ // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI
+ var/body = replacetext(FM.body, "\n", "
")
+ messages[++messages.len] = list(
+ "author" = FM.author,
+ "body" = body,
+ "message_type" = FM.message_type,
+ "time_stamp" = FM.time_stamp,
+ "has_image" = (FM.img != null),
+ "caption" = FM.caption,
+ "index" = index
+ )
+
+ feeds[++feeds.len] = list(
+ "name" = channel.channel_name,
+ "censored" = channel.censored,
+ "author" = channel.author,
+ "messages" = messages
+ )
+ return feeds
+
+// Proc - get_recent_news()
+// Parameters - none
+// Description - Returns the latest three newscasts, compiled for template processing
+/obj/item/device/communicator/proc/get_recent_news()
+ var/list/news = list()
+
+ // Compile all the newscasts
+ for(var/datum/feed_channel/channel in news_network.network_channels)
+ if(!channel.censored)
+ for(var/datum/feed_message/FM in channel.messages)
+ var/body = replacetext(FM.body, "\n", "
")
+ news[++news.len] = list(
+ "channel" = channel.channel_name,
+ "author" = FM.author,
+ "body" = body,
+ "message_type" = FM.message_type,
+ "time_stamp" = FM.time_stamp,
+ "has_image" = (FM.img != null),
+ "caption" = FM.caption,
+ )
+
+ // Cut out all but the youngest three
+ while(news.len > 3)
+ var/oldest = min(news[0]["time_stamp"], news[1]["time_stamp"], news[2]["time_stamp"], news[3]["time_stamp"])
+ for(var/i = 0, i < 4, i++)
+ if(news[i]["time_stamp"] == oldest)
+ news.Remove(news[i])
+
+ return news
+
+
+
+// Putting the commcard data harvesting helpers here
+// Not ideal to put all the procs on the base type
+// but it may open options for adminbus,
+// And it saves duplicated code
+
+
+// Medical records
+/obj/item/weapon/commcard/proc/get_med_records()
+ var/med_records[0]
+ for(var/datum/data/record/M in sortRecord(data_core.medical))
+ var/record[0]
+ record[++record.len] = list("tab" = "Name", "val" = M.fields["name"])
+ record[++record.len] = list("tab" = "ID", "val" = M.fields["id"])
+ record[++record.len] = list("tab" = "Blood Type", "val" = M.fields["b_type"])
+ record[++record.len] = list("tab" = "DNA #", "val" = M.fields["b_dna"])
+ record[++record.len] = list("tab" = "Gender", "val" = M.fields["id_gender"])
+ record[++record.len] = list("tab" = "Entity Classification", "val" = M.fields["brain_type"])
+ record[++record.len] = list("tab" = "Minor Disorders", "val" = M.fields["mi_dis"])
+ record[++record.len] = list("tab" = "Major Disorders", "val" = M.fields["ma_dis"])
+ record[++record.len] = list("tab" = "Allergies", "val" = M.fields["alg"])
+ record[++record.len] = list("tab" = "Condition", "val" = M.fields["cdi"])
+ record[++record.len] = list("tab" = "Notes", "val" = M.fields["notes"])
+
+ med_records[++med_records.len] = list("name" = M.fields["name"], "record" = record)
+ return med_records
+
+
+// Employment records
+/obj/item/weapon/commcard/proc/get_emp_records()
+ var/emp_records[0]
+ for(var/datum/data/record/G in sortRecord(data_core.general))
+ var/record[0]
+ record[++record.len] = list("tab" = "Name", "val" = G.fields["name"])
+ record[++record.len] = list("tab" = "ID", "val" = G.fields["id"])
+ record[++record.len] = list("tab" = "Rank", "val" = G.fields["rank"])
+ record[++record.len] = list("tab" = "Fingerprint", "val" = G.fields["fingerprint"])
+ record[++record.len] = list("tab" = "Entity Classification", "val" = G.fields["brain_type"])
+ record[++record.len] = list("tab" = "Sex", "val" = G.fields["sex"])
+ record[++record.len] = list("tab" = "Species", "val" = G.fields["species"])
+ record[++record.len] = list("tab" = "Age", "val" = G.fields["age"])
+ record[++record.len] = list("tab" = "Notes", "val" = G.fields["notes"])
+
+ emp_records[++emp_records.len] = list("name" = G.fields["name"], "record" = record)
+ return emp_records
+
+
+// Security records
+/obj/item/weapon/commcard/proc/get_sec_records()
+ var/sec_records[0]
+ for(var/datum/data/record/G in sortRecord(data_core.general))
+ var/record[0]
+ record[++record.len] = list("tab" = "Name", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Sex", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Species", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Age", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Rank", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Fingerprint", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Physical Status", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Mental Status", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Criminal Status", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Major Crimes", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Minor Crimes", "val" = G.fields[""])
+ record[++record.len] = list("tab" = "Notes", "val" = G.fields["notes"])
+
+ sec_records[++sec_records.len] = list("name" = G.fields["name"], "record" = record)
+ return sec_records
+
+
+// Status of all secbots
+// Weaker than what PDAs appear to do, but as of 7/1/2018 PDA secbot access is nonfunctional
+/obj/item/weapon/commcard/proc/get_sec_bot_access()
+ var/sec_bots[0]
+ for(var/mob/living/bot/secbot/S in mob_list)
+ // Get new bot
+ var/status[0]
+ status[++status.len] = list("tab" = "Name", "val" = sanitize(S.name))
+
+ // If it's turned off, then it shouldn't be broadcasting any further info
+ if(!S.on)
+ status[++status.len] = list("tab" = "Power", "val" = "Off") // Encoding the span classes here so I don't have to do complicated switches in the ui template
+ continue
+ status[++status.len] = list("tab" = "Power", "val" = "On")
+
+ // -- What it's doing
+ // If it's engaged, then say who it thinks it's engaging
+ if(S.target)
+ status[++status.len] = list("tab" = "Status", "val" = "Apprehending Target")
+ status[++status.len] = list("tab" = "Target", "val" = S.target_name(S.target))
+ // Else if it's patrolling
+ else if(S.will_patrol)
+ status[++status.len] = list("tab" = "Status", "val" = "Patrolling")
+ // Otherwise we don't know what it's doing
+ else
+ status[++status.len] = list("tab" = "Status", "val" = "Idle")
+
+ // Where it is
+ status[++status.len] = list("tab" = "Location", "val" = sanitize("[get_area(S.loc)]"))
+
+ // Append bot to the list
+ sec_bots[++sec_bots.len] = list("bot" = S.name, "status" = status)
+ return sec_bots
+
+
+// Code and frequency of stored signalers
+// Supports multiple signalers within the device
+/obj/item/weapon/commcard/proc/get_int_signalers()
+ var/signalers[0]
+ for(var/obj/item/device/assembly/signaler/S in internal_devices)
+ var/unit[0]
+ unit[++unit.len] = list("tab" = "Code", "val" = S.code)
+ unit[++unit.len] = list("tab" = "Frequency", "val" = S.frequency)
+
+ signalers[++signalers.len] = list("ref" = "\ref[S]", "status" = unit)
+
+ return signalers
+
+// Returns list of all powernet sensors currently visible to the commcard
+/obj/item/weapon/commcard/proc/find_powernet_sensors()
+ var/grid_sensors[0]
+
+ // Find all the powernet sensors we need to pull data from
+ // Copied from /datum/nano_module/power_monitor/proc/refresh_sensors(),
+ // located in '/code/modules/nano/modules/power_monitor.dm'
+ // Minor tweaks for efficiency and cleanliness
+ var/turf/T = get_turf(src)
+ if(T)
+ var/list/levels = using_map.get_map_levels(T.z, FALSE)
+ for(var/obj/machinery/power/sensor/S in machines)
+ if((S.long_range) || (S.loc.z in levels) || (S.loc.z == T.z)) // Consoles have range on their Z-Level. Sensors with long_range var will work between Z levels.
+ if(S.name_tag == "#UNKN#") // Default name. Shouldn't happen!
+ warning("Powernet sensor with unset ID Tag! [S.x]X [S.y]Y [S.z]Z")
+ else
+ grid_sensors += S
+ return grid_sensors
+
+// List of powernets
+/obj/item/weapon/commcard/proc/get_powernet_monitoring_list()
+ // Fetch power monitor data
+ var/sensors[0]
+
+ for(var/obj/machinery/power/sensor/S in internal_data["grid_sensors"])
+ var/list/focus = S.return_reading_data()
+
+ sensors[++sensors.len] = list(
+ "name" = S.name_tag,
+ "alarm" = focus["alarm"]
+ )
+
+ return sensors
+
+// Information about the targeted powernet
+/obj/item/weapon/commcard/proc/get_powernet_target(var/target_sensor)
+ if(!target_sensor)
+ return
+
+ var/powernet_target[0]
+
+ for(var/obj/machinery/power/sensor/S in internal_data["grid_sensors"])
+ var/list/focus = S.return_reading_data()
+
+ // Packages the span class here so it doesn't need to be interpreted w/in the for loop in the ui template
+ var/load_stat = "Optimal"
+ if(focus["load_percentage"] >= 95)
+ load_stat = "DANGER: Overload"
+ else if(focus["load_percentage"] >= 85)
+ load_stat = "WARNING: High Load"
+
+ var/alarm_stat = focus["alarm"] ? "WARNING: Abnormal activity detected!" : "Secure"
+
+ if(target_sensor == S.name_tag)
+ powernet_target = list(
+ "name" = S.name_tag,
+ "alarm" = focus["alarm"],
+ "error" = focus["error"],
+ "apc_data" = focus["apc_data"],
+ "status" = list(
+ list("field" = "Network Load Status", "statval" = load_stat),
+ list("field" = "Network Security Status", "statval" = alarm_stat),
+ list("field" = "Load Percentage", "statval" = focus["load_percentage"]),
+ list("field" = "Available Power", "statval" = focus["total_avail"]),
+ list("field" = "APC Power Usage", "statval" = focus["total_used_apc"]),
+ list("field" = "Other Power Usage", "statval" = focus["total_used_other"]),
+ list("field" = "Total Usage", "statval" = focus["total_used_all"])
+ )
+ )
+
+ return powernet_target
+
+// Compiles the locations of all janitorial paraphernalia, as used by janitorialLocator.tmpl
+/obj/item/weapon/commcard/proc/get_janitorial_locations()
+ // Fetch janitorial locator
+ var/janidata[0]
+ var/list/cleaningList = list()
+ cleaningList += all_mops + all_mopbuckets + all_janitorial_carts
+
+ // User's location
+ var/turf/userloc = get_turf(src)
+ if(isturf(userloc))
+ janidata[++janidata.len] = list("field" = "Current Location", "val" = "[userloc.x], [userloc.y], [using_map.get_zlevel_name(userloc.z)]")
+ else
+ janidata[++janidata.len] = list("field" = "Current Location", "val" = "Unknown")
+ return janidata // If the user isn't on a valid turf, then it shouldn't be able to find anything anyways
+
+ // Mops, mop buckets, janitorial carts.
+ for(var/obj/C in cleaningList)
+ var/turf/T = get_turf(C)
+ if(isturf(T) )//&& T.z in using_map.get_map_levels(userloc, FALSE))
+ if(T.z == userloc.z)
+ janidata[++janidata.len] = list("field" = apply_text_macros("\proper [C.name]"), "val" = "[T.x], [T.y], [using_map.get_zlevel_name(T.z)]")
+ else
+ janidata[++janidata.len] = list("field" = apply_text_macros("\proper [C.name]"), "val" = "[T.x], [T.y], [using_map.get_zlevel_name(T.z)]")
+
+ // Cleanbots
+ for(var/mob/living/bot/cleanbot/B in living_mob_list)
+ var/turf/T = get_turf(B)
+ if(isturf(T) )//&& T.z in using_map.get_map_levels(userloc, FALSE))
+ var/textout = ""
+ if(B.on)
+ textout += "Status: Online
"
+ if(T.z == userloc.z)
+ textout += "[T.x], [T.y], [using_map.get_zlevel_name(T.z)]"
+ else
+ textout += "[T.x], [T.y], [using_map.get_zlevel_name(T.z)]"
+ else
+ textout += "Status: Offline"
+
+ janidata[++janidata.len] = list("field" = "[B.name]", "val" = textout)
+
+ return janidata
+
+// Compiles the three lists used by GPS_access.tmpl
+// The contents of the three lists are inherently related, so separating them into different procs would be largely redundant
+/obj/item/weapon/commcard/proc/get_GPS_lists()
+ // GPS Access
+ var/intgps[0] // Gps devices within the commcard -- Allow tag edits, turning on/off, etc
+ var/extgps[0] // Gps devices not inside the commcard -- Print locations if a gps is on
+ var/stagps[0] // Gps net status, location, whether it's on, if it's got long range
+ var/obj/item/device/gps/cumulative = new(src)
+ cumulative.tracking = FALSE
+ cumulative.local_mode = TRUE // Won't detect long-range signals automatically
+ cumulative.long_range = FALSE
+ var/list/toggled_gps = list() // List of GPS units that are turned off before display_list() is called
+
+ for(var/obj/item/device/gps/G in internal_devices)
+ var/gpsdata[0]
+ if(G.tracking && !G.emped)
+ cumulative.tracking = TRUE // Turn it on
+ if(G.long_range)
+ cumulative.long_range = TRUE // It can detect long-range
+ if(!G.local_mode)
+ cumulative.local_mode = FALSE // It is detecting long-range
+
+ gpsdata["ref"] = "\ref[G]"
+ gpsdata["tag"] = G.gps_tag
+ gpsdata["power"] = G.tracking
+ gpsdata["local_mode"] = G.local_mode
+ gpsdata["long_range"] = G.long_range
+ gpsdata["hide_signal"] = G.hide_signal
+ gpsdata["can_hide"] = G.can_hide_signal
+
+ intgps[++intgps.len] = gpsdata // Add it to the list
+
+ if(G.tracking)
+ G.tracking = FALSE // Disable the internal gps units so they don't show up in the report
+ toggled_gps += G
+
+ var/list/remote_gps = cumulative.display_list() // Fetch information for all units except the ones inside of this device
+
+ for(var/obj/item/device/gps/G in toggled_gps) // Reenable any internal GPS units
+ G.tracking = TRUE
+
+ stagps["enabled"] = cumulative.tracking
+ stagps["long_range_en"] = (cumulative.long_range && !cumulative.local_mode)
+
+ stagps["my_area_name"] = remote_gps["my_area_name"]
+ stagps["curr_x"] = remote_gps["curr_x"]
+ stagps["curr_y"] = remote_gps["curr_y"]
+ stagps["curr_z"] = remote_gps["curr_z"]
+ stagps["curr_z_name"] = remote_gps["curr_z_name"]
+
+ extgps = remote_gps["gps_list"] // Compiled by the GPS
+
+ qdel(cumulative) // Don't want spare GPS units building up in the contents
+
+ return list(
+ intgps,
+ extgps,
+ stagps
+ )
+
+// Collects the current status of the supply shuttle
+// Copied from /obj/machinery/computer/supplycomp/ui_interact(),
+// code\game\machinery\computer\supply.dm, starting at line 55
+/obj/item/weapon/commcard/proc/get_supply_shuttle_status()
+ var/shuttle_status[0]
+ var/datum/shuttle/ferry/supply/shuttle = supply_controller.shuttle
+ if(shuttle)
+ if(shuttle.has_arrive_time())
+ shuttle_status["location"] = "In transit"
+ shuttle_status["mode"] = SUP_SHUTTLE_TRANSIT
+ shuttle_status["time"] = shuttle.eta_minutes()
+
+ else
+ shuttle_status["time"] = 0
+ if(shuttle.at_station())
+ if(shuttle.docking_controller)
+ switch(shuttle.docking_controller.get_docking_status())
+ if("docked")
+ shuttle_status["location"] = "Docked"
+ shuttle_status["mode"] = SUP_SHUTTLE_DOCKED
+ if("undocked")
+ shuttle_status["location"] = "Undocked"
+ shuttle_status["mode"] = SUP_SHUTTLE_UNDOCKED
+ if("docking")
+ shuttle_status["location"] = "Docking"
+ shuttle_status["mode"] = SUP_SHUTTLE_DOCKING
+ shuttle_status["force"] = shuttle.can_force()
+ if("undocking")
+ shuttle_status["location"] = "Undocking"
+ shuttle_status["mode"] = SUP_SHUTTLE_UNDOCKING
+ shuttle_status["force"] = shuttle.can_force()
+
+ else
+ shuttle_status["location"] = "Station"
+ shuttle_status["mode"] = SUP_SHUTTLE_DOCKED
+
+ else
+ shuttle_status["location"] = "Away"
+ shuttle_status["mode"] = SUP_SHUTTLE_AWAY
+
+ if(shuttle.can_launch())
+ shuttle_status["launch"] = 1
+ else if(shuttle.can_cancel())
+ shuttle_status["launch"] = 2
+ else
+ shuttle_status["launch"] = 0
+
+ switch(shuttle.moving_status)
+ if(SHUTTLE_IDLE)
+ shuttle_status["engine"] = "Idle"
+ if(SHUTTLE_WARMUP)
+ shuttle_status["engine"] = "Warming up"
+ if(SHUTTLE_INTRANSIT)
+ shuttle_status["engine"] = "Engaged"
+
+ else
+ shuttle["mode"] = SUP_SHUTTLE_ERROR
+
+ return shuttle_status
+
+// Compiles the list of supply orders
+// Copied from /obj/machinery/computer/supplycomp/ui_interact(),
+// code\game\machinery\computer\supply.dm, starting at line 130
+/obj/item/weapon/commcard/proc/get_supply_orders()
+ var/orders[0]
+ for(var/datum/supply_order/S in supply_controller.order_history)
+ orders[++orders.len] = list(
+ "ref" = "\ref[S]",
+ "status" = S.status,
+ "entries" = list(
+ list("field" = "Supply Pack", "entry" = S.name),
+ list("field" = "Cost", "entry" = S.cost),
+ list("field" = "Index", "entry" = S.index),
+ list("field" = "Reason", "entry" = S.comment),
+ list("field" = "Ordered by", "entry" = S.ordered_by),
+ list("field" = "Ordered at", "entry" = S.ordered_at),
+ list("field" = "Approved by", "entry" = S.approved_by),
+ list("field" = "Approved at", "entry" = S.approved_at)
+ )
+ )
+
+ return orders
+
+// Compiles the list of supply export receipts
+// Copied from /obj/machinery/computer/supplycomp/ui_interact(),
+// code\game\machinery\computer\supply.dm, starting at line 147
+/obj/item/weapon/commcard/proc/get_supply_receipts()
+ var/receipts[0]
+ for(var/datum/exported_crate/E in supply_controller.exported_crates)
+ receipts[++receipts.len] = list(
+ "ref" = "\ref[E]",
+ "contents" = E.contents,
+ "error" = E.contents["error"],
+ "title" = list(
+ list("field" = "Name", "entry" = E.name),
+ list("field" = "Value", "entry" = E.value)
+ )
+ )
+ return receipts
+
+
+// Compiles the list of supply packs for the category currently stored in internal_data["supply_category"]
+// Copied from /obj/machinery/computer/supplycomp/ui_interact(),
+// code\game\machinery\computer\supply.dm, starting at line 147
+/obj/item/weapon/commcard/proc/get_supply_pack_list()
+ var/supply_packs[0]
+ for(var/pack_name in supply_controller.supply_pack)
+ var/datum/supply_pack/P = supply_controller.supply_pack[pack_name]
+ if(P.group == internal_data["supply_category"])
+ var/list/pack = list(
+ "name" = P.name,
+ "cost" = P.cost,
+ "contraband" = P.contraband,
+ "manifest" = uniquelist(P.manifest),
+ "random" = P.num_contained,
+ "expand" = 0,
+ "ref" = "\ref[P]"
+ )
+
+ if(P in internal_data["supply_pack_expanded"])
+ pack["expand"] = 1
+
+ supply_packs[++supply_packs.len] = pack
+
+ return supply_packs
+
+
+// Compiles miscellaneous data and permissions used by the supply template
+/obj/item/weapon/commcard/proc/get_misc_supply_data()
+ return list(
+ "shuttle_auth" = (internal_data["supply_controls"] & SUP_SEND_SHUTTLE),
+ "order_auth" = (internal_data["supply_controls"] & SUP_ACCEPT_ORDERS),
+ "supply_points" = supply_controller.points,
+ "supply_categories" = all_supply_groups
+ )
+
+/obj/item/weapon/commcard/proc/get_status_display()
+ return list(
+ "line1" = internal_data["stat_display_line1"],
+ "line2" = internal_data["stat_display_line2"],
+ "active_line1" = internal_data["stat_display_active1"],
+ "active_line2" = internal_data["stat_display_active2"],
+ "active" = internal_data["stat_display_special"]
+ )
+
+/obj/item/weapon/commcard/proc/find_blast_doors()
+ var/target_doors[0]
+ for(var/obj/machinery/door/blast/B in machines)
+ if(B.id == internal_data["shuttle_door_code"])
+ target_doors += B
+
+ return target_doors
\ No newline at end of file
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index 78f385a8aa..c62cb541ce 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -68,6 +68,55 @@ var/list/GPS_list = list()
/obj/item/device/gps/attack_self(mob/user)
display(user)
+ // Compiles all the data not available directly from the GPS
+ // Like the positions and directions to all other GPS units
+/obj/item/device/gps/proc/display_list()
+ var/list/dat = list()
+
+ var/turf/curr = get_turf(src)
+ var/area/my_area = get_area(src)
+
+ dat["my_area_name"] = my_area.name
+ dat["curr_x"] = curr.x
+ dat["curr_y"] = curr.y
+ dat["curr_z"] = curr.z
+ dat["curr_z_name"] = using_map.get_zlevel_name(curr.z)
+ dat["gps_list"] = list()
+ dat["z_level_detection"] = using_map.get_map_levels(curr.z, long_range)
+
+ for(var/obj/item/device/gps/G in GPS_list - src)
+ if(!G.tracking || G.emped || G.hide_signal)
+ continue
+
+ var/turf/T = get_turf(G)
+ if(local_mode && curr.z != T.z)
+ continue
+ if(!(T.z in dat["z_level_detection"]))
+ continue
+
+ var/gps_data[0]
+ gps_data["ref"] = G
+ gps_data["gps_tag"] = G.gps_tag
+
+ var/area/A = get_area(G)
+ gps_data["area_name"] = A.name
+ if(istype(A, /area/submap))
+ gps_data["area_name"] = "Unknown Area" // Avoid spoilers.
+
+ gps_data["z_name"] = using_map.get_zlevel_name(T.z)
+ gps_data["direction"] = get_adir(curr, T)
+ gps_data["degrees"] = round(Get_Angle(curr,T))
+ gps_data["distX"] = T.x - curr.x
+ gps_data["distY"] = T.y - curr.y
+ gps_data["distance"] = get_dist(curr, T)
+ gps_data["local"] = (curr.z == T.z)
+ gps_data["x"] = T.x
+ gps_data["y"] = T.y
+
+ dat["gps_list"][++dat["gps_list"].len] = gps_data
+
+ return dat
+
/obj/item/device/gps/proc/display(mob/user)
if(!tracking)
to_chat(user, "The device is off. Alt-click it to turn it on.")
@@ -77,48 +126,20 @@ var/list/GPS_list = list()
return
var/list/dat = list()
+ var/list/gps_data = display_list()
- var/turf/curr = get_turf(src)
- var/area/my_area = get_area(src)
- dat += "Current location: [my_area.name] ([curr.x], [curr.y], [using_map.get_zlevel_name(curr.z)])"
+ dat += "Current location: [gps_data["my_area_name"]] ([gps_data["curr_x"]], [gps_data["curr_y"]], [gps_data["curr_z_name"]])"
dat += "[hide_signal ? "Tagged" : "Broadcasting"] as '[gps_tag]'. \[Change Tag\] \
\[Toggle Scan Range\] \
[can_hide_signal ? "\[Toggle Signal Visibility\]":""]"
- var/list/signals = list()
-
- for(var/gps in GPS_list)
- var/obj/item/device/gps/G = gps
- if(G.emped || !G.tracking || G.hide_signal || G == src) // Their GPS isn't on or functional.
- continue
- var/turf/T = get_turf(G)
- var/z_level_detection = using_map.get_map_levels(curr.z, long_range)
-
- if(local_mode && T.z != curr.z) // Only care about the current z-level.
- continue
- else if(!(T.z in z_level_detection)) // Too far away.
- continue
-
- var/area/their_area = get_area(G)
- var/area_name = their_area.name
- if(istype(their_area, /area/submap))
- area_name = "Unknown Area" // Avoid spoilers.
- var/Z_name = using_map.get_zlevel_name(T.z)
- var/direction = get_adir(curr, T)
- var/distX = T.x - curr.x
- var/distY = T.y - curr.y
- var/distance = get_dist(curr, T)
- var/local = curr.z == T.z ? TRUE : FALSE
-
- if(istype(gps, /obj/item/device/gps/internal/poi))
- signals += " [G.gps_tag]: [area_name] - [local ? "[direction] Dist: [round(distance, 10)]m" : "in \the [Z_name]"]"
- else
- signals += " [G.gps_tag]: [area_name], ([T.x], [T.y]) - [local ? "[direction] Dist: [distX ? "[abs(round(distX, 1))]m [(distX > 0) ? "E" : "W"], " : ""][distY ? "[abs(round(distY, 1))]m [(distY > 0) ? "N" : "S"]" : ""]" : "in \the [Z_name]"]"
-
- if(signals.len)
+ if(gps_data["gps_list"].len)
dat += "Detected signals;"
- for(var/line in signals)
- dat += line
+ for(var/gps in gps_data["gps_list"])
+ if(istype(gps_data["ref"], /obj/item/device/gps/internal/poi))
+ dat += " [gps["gps_tag"]]: [gps["area_name"]] - [gps["local"] ? "[gps["direction"]] Dist: [round(gps["distance"], 10)]m" : "in \the [gps["z_name"]]"]"
+ else
+ dat += " [gps["gps_tag"]]: [gps["area_name"]], ([gps["x"]], [gps["y"]]) - [gps["local"] ? "[gps["direction"]] Dist: [gps["distX"] ? "[abs(round(gps["distX"], 1))]m [(gps["distX"] > 0) ? "E" : "W"], " : ""][gps["distY"] ? "[abs(round(gps["distY"], 1))]m [(gps["distY"] > 0) ? "N" : "S"]" : ""]" : "in \the [gps["z_name"]]"]"
else
dat += "No other signals detected."
@@ -221,45 +242,17 @@ var/list/GPS_list = list()
return
var/list/dat = list()
+ var/list/gps_data = display_list()
- var/turf/curr = get_turf(src)
- var/area/my_area = get_area(src)
- dat += "Current location: [my_area.name] ([curr.x], [curr.y], [using_map.get_zlevel_name(curr.z)])"
+ dat += "Current location: [gps_data["my_area_name"]] ([gps_data["curr_x"]], [gps_data["curr_y"]], [gps_data["curr_z_name"]])"
dat += "[hide_signal ? "Tagged" : "Broadcasting"] as '[gps_tag]'. \[Change Tag\] \
\[Toggle Scan Range\] \
[can_hide_signal ? "\[Toggle Signal Visibility\]":""]"
- var/list/signals = list()
-
- for(var/gps in GPS_list)
- var/obj/item/device/gps/G = gps
- if(G.emped || !G.tracking || G.hide_signal || G == src) // Their GPS isn't on or functional.
- continue
- var/turf/T = get_turf(G)
- var/z_level_detection = using_map.get_map_levels(curr.z, long_range)
-
- if(local_mode && T.z != curr.z) // Only care about the current z-level.
- continue
- else if(!(T.z in z_level_detection)) // Too far away.
- continue
-
- var/area/their_area = get_area(G)
- var/area_name = their_area.name
- if(istype(their_area, /area/submap))
- area_name = "Unknown Area" // Avoid spoilers.
- var/Z_name = using_map.get_zlevel_name(T.z)
- var/coord = "[T.x], [T.y], [Z_name]"
- var/degrees = round(Get_Angle(curr, T))
- var/direction = get_adir(curr, T)
- var/distance = get_dist(curr, T)
- var/local = curr.z == T.z ? TRUE : FALSE
-
- signals += " [G.gps_tag]: [area_name] ([coord]) [local ? "Dist: [distance]m Dir: [degrees]° ([direction])":""]"
-
- if(signals.len)
+ if(gps_data["gps_list"].len)
dat += "Detected signals;"
- for(var/line in signals)
- dat += line
+ for(var/gps in gps_data["gps_list"])
+ dat += " [gps["gps_tag"]]: [gps["area_name"]] ([gps["x"]], [gps["y"]], [gps["z_name"]]) [gps["local"] ? "Dist: [gps["distance"]]m Dir: [gps["degrees"]]° ([gps["direction"]])" :""]"
else
dat += "No other signals detected."
diff --git a/code/game/objects/items/devices/hacktool.dm b/code/game/objects/items/devices/hacktool.dm
index b5d6439da3..fb3d8d9bcc 100644
--- a/code/game/objects/items/devices/hacktool.dm
+++ b/code/game/objects/items/devices/hacktool.dm
@@ -62,7 +62,7 @@
if(hack_result && in_hack_mode)
user << "Your hacking attempt was succesful!"
- user.playsound_local(get_turf(src), 'sound/piano/A#6.ogg', 50)
+ user.playsound_local(get_turf(src), 'sound/instruments/piano/An6.ogg', 50)
else
user << "Your hacking attempt failed!"
return 0
diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm
index bc0c9d5304..47dfc256fc 100644
--- a/code/game/objects/items/devices/text_to_speech.dm
+++ b/code/game/objects/items/devices/text_to_speech.dm
@@ -1,7 +1,7 @@
/obj/item/device/text_to_speech
name = "TTS device"
desc = "A device that speaks an inputted message. Given to crew which can not speak properly or at all."
- icon = 'icons/obj/electronic_assemblies.dmi'
+ icon = 'icons/obj/integrated_electronics/electronic_setups.dmi'
icon_state = "setup_small"
w_class = ITEMSIZE_SMALL
var/named
diff --git a/code/game/objects/items/devices/violin.dm b/code/game/objects/items/devices/violin.dm
index 683c74bd02..5dc215cf91 100644
--- a/code/game/objects/items/devices/violin.dm
+++ b/code/game/objects/items/devices/violin.dm
@@ -1,392 +1,41 @@
//copy pasta of the space piano, don't hurt me -Pete
-
-/obj/item/device/violin
- name = "space violin"
- desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
+/obj/item/device/instrument
+ name = "generic instrument"
+ var/datum/song/handheld/song
+ var/instrumentId = "generic"
+ var/instrumentExt = "mid"
icon = 'icons/obj/musician.dmi'
- icon_state = "violin"
force = 10
- var/datum/song/song
- var/playing = 0
- var/help = 0
- var/edit = 1
- var/repeat = 0
-/obj/item/device/violin/proc/playnote(var/note as text)
- //world << "Note: [note]"
- var/soundfile
- /*BYOND loads resource files at compile time if they are ''. This means you can't really manipulate them dynamically.
- Tried doing it dynamically at first but its more trouble than its worth. Would have saved many lines tho.*/
- switch(note)
- if("Cn1") soundfile = 'sound/violin/Cn1.mid'
- if("C#1") soundfile = 'sound/violin/C#1.mid'
- if("Db1") soundfile = 'sound/violin/Db1.mid'
- if("Dn1") soundfile = 'sound/violin/Dn1.mid'
- if("D#1") soundfile = 'sound/violin/D#1.mid'
- if("Eb1") soundfile = 'sound/violin/Eb1.mid'
- if("En1") soundfile = 'sound/violin/En1.mid'
- if("E#1") soundfile = 'sound/violin/E#1.mid'
- if("Fb1") soundfile = 'sound/violin/Fb1.mid'
- if("Fn1") soundfile = 'sound/violin/Fn1.mid'
- if("F#1") soundfile = 'sound/violin/F#1.mid'
- if("Gb1") soundfile = 'sound/violin/Gb1.mid'
- if("Gn1") soundfile = 'sound/violin/Gn1.mid'
- if("G#1") soundfile = 'sound/violin/G#1.mid'
- if("Ab1") soundfile = 'sound/violin/Ab1.mid'
- if("An1") soundfile = 'sound/violin/An1.mid'
- if("A#1") soundfile = 'sound/violin/A#1.mid'
- if("Bb1") soundfile = 'sound/violin/Bb1.mid'
- if("Bn1") soundfile = 'sound/violin/Bn1.mid'
- if("B#1") soundfile = 'sound/violin/B#1.mid'
- if("Cb2") soundfile = 'sound/violin/Cb2.mid'
- if("Cn2") soundfile = 'sound/violin/Cn2.mid'
- if("C#2") soundfile = 'sound/violin/C#2.mid'
- if("Db2") soundfile = 'sound/violin/Db2.mid'
- if("Dn2") soundfile = 'sound/violin/Dn2.mid'
- if("D#2") soundfile = 'sound/violin/D#2.mid'
- if("Eb2") soundfile = 'sound/violin/Eb2.mid'
- if("En2") soundfile = 'sound/violin/En2.mid'
- if("E#2") soundfile = 'sound/violin/E#2.mid'
- if("Fb2") soundfile = 'sound/violin/Fb2.mid'
- if("Fn2") soundfile = 'sound/violin/Fn2.mid'
- if("F#2") soundfile = 'sound/violin/F#2.mid'
- if("Gb2") soundfile = 'sound/violin/Gb2.mid'
- if("Gn2") soundfile = 'sound/violin/Gn2.mid'
- if("G#2") soundfile = 'sound/violin/G#2.mid'
- if("Ab2") soundfile = 'sound/violin/Ab2.mid'
- if("An2") soundfile = 'sound/violin/An2.mid'
- if("A#2") soundfile = 'sound/violin/A#2.mid'
- if("Bb2") soundfile = 'sound/violin/Bb2.mid'
- if("Bn2") soundfile = 'sound/violin/Bn2.mid'
- if("B#2") soundfile = 'sound/violin/B#2.mid'
- if("Cb3") soundfile = 'sound/violin/Cb3.mid'
- if("Cn3") soundfile = 'sound/violin/Cn3.mid'
- if("C#3") soundfile = 'sound/violin/C#3.mid'
- if("Db3") soundfile = 'sound/violin/Db3.mid'
- if("Dn3") soundfile = 'sound/violin/Dn3.mid'
- if("D#3") soundfile = 'sound/violin/D#3.mid'
- if("Eb3") soundfile = 'sound/violin/Eb3.mid'
- if("En3") soundfile = 'sound/violin/En3.mid'
- if("E#3") soundfile = 'sound/violin/E#3.mid'
- if("Fb3") soundfile = 'sound/violin/Fb3.mid'
- if("Fn3") soundfile = 'sound/violin/Fn3.mid'
- if("F#3") soundfile = 'sound/violin/F#3.mid'
- if("Gb3") soundfile = 'sound/violin/Gb3.mid'
- if("Gn3") soundfile = 'sound/violin/Gn3.mid'
- if("G#3") soundfile = 'sound/violin/G#3.mid'
- if("Ab3") soundfile = 'sound/violin/Ab3.mid'
- if("An3") soundfile = 'sound/violin/An3.mid'
- if("A#3") soundfile = 'sound/violin/A#3.mid'
- if("Bb3") soundfile = 'sound/violin/Bb3.mid'
- if("Bn3") soundfile = 'sound/violin/Bn3.mid'
- if("B#3") soundfile = 'sound/violin/B#3.mid'
- if("Cb4") soundfile = 'sound/violin/Cb4.mid'
- if("Cn4") soundfile = 'sound/violin/Cn4.mid'
- if("C#4") soundfile = 'sound/violin/C#4.mid'
- if("Db4") soundfile = 'sound/violin/Db4.mid'
- if("Dn4") soundfile = 'sound/violin/Dn4.mid'
- if("D#4") soundfile = 'sound/violin/D#4.mid'
- if("Eb4") soundfile = 'sound/violin/Eb4.mid'
- if("En4") soundfile = 'sound/violin/En4.mid'
- if("E#4") soundfile = 'sound/violin/E#4.mid'
- if("Fb4") soundfile = 'sound/violin/Fb4.mid'
- if("Fn4") soundfile = 'sound/violin/Fn4.mid'
- if("F#4") soundfile = 'sound/violin/F#4.mid'
- if("Gb4") soundfile = 'sound/violin/Gb4.mid'
- if("Gn4") soundfile = 'sound/violin/Gn4.mid'
- if("G#4") soundfile = 'sound/violin/G#4.mid'
- if("Ab4") soundfile = 'sound/violin/Ab4.mid'
- if("An4") soundfile = 'sound/violin/An4.mid'
- if("A#4") soundfile = 'sound/violin/A#4.mid'
- if("Bb4") soundfile = 'sound/violin/Bb4.mid'
- if("Bn4") soundfile = 'sound/violin/Bn4.mid'
- if("B#4") soundfile = 'sound/violin/B#4.mid'
- if("Cb5") soundfile = 'sound/violin/Cb5.mid'
- if("Cn5") soundfile = 'sound/violin/Cn5.mid'
- if("C#5") soundfile = 'sound/violin/C#5.mid'
- if("Db5") soundfile = 'sound/violin/Db5.mid'
- if("Dn5") soundfile = 'sound/violin/Dn5.mid'
- if("D#5") soundfile = 'sound/violin/D#5.mid'
- if("Eb5") soundfile = 'sound/violin/Eb5.mid'
- if("En5") soundfile = 'sound/violin/En5.mid'
- if("E#5") soundfile = 'sound/violin/E#5.mid'
- if("Fb5") soundfile = 'sound/violin/Fb5.mid'
- if("Fn5") soundfile = 'sound/violin/Fn5.mid'
- if("F#5") soundfile = 'sound/violin/F#5.mid'
- if("Gb5") soundfile = 'sound/violin/Gb5.mid'
- if("Gn5") soundfile = 'sound/violin/Gn5.mid'
- if("G#5") soundfile = 'sound/violin/G#5.mid'
- if("Ab5") soundfile = 'sound/violin/Ab5.mid'
- if("An5") soundfile = 'sound/violin/An5.mid'
- if("A#5") soundfile = 'sound/violin/A#5.mid'
- if("Bb5") soundfile = 'sound/violin/Bb5.mid'
- if("Bn5") soundfile = 'sound/violin/Bn5.mid'
- if("B#5") soundfile = 'sound/violin/B#5.mid'
- if("Cb6") soundfile = 'sound/violin/Cb6.mid'
- if("Cn6") soundfile = 'sound/violin/Cn6.mid'
- if("C#6") soundfile = 'sound/violin/C#6.mid'
- if("Db6") soundfile = 'sound/violin/Db6.mid'
- if("Dn6") soundfile = 'sound/violin/Dn6.mid'
- if("D#6") soundfile = 'sound/violin/D#6.mid'
- if("Eb6") soundfile = 'sound/violin/Eb6.mid'
- if("En6") soundfile = 'sound/violin/En6.mid'
- if("E#6") soundfile = 'sound/violin/E#6.mid'
- if("Fb6") soundfile = 'sound/violin/Fb6.mid'
- if("Fn6") soundfile = 'sound/violin/Fn6.mid'
- if("F#6") soundfile = 'sound/violin/F#6.mid'
- if("Gb6") soundfile = 'sound/violin/Gb6.mid'
- if("Gn6") soundfile = 'sound/violin/Gn6.mid'
- if("G#6") soundfile = 'sound/violin/G#6.mid'
- if("Ab6") soundfile = 'sound/violin/Ab6.mid'
- if("An6") soundfile = 'sound/violin/An6.mid'
- if("A#6") soundfile = 'sound/violin/A#6.mid'
- if("Bb6") soundfile = 'sound/violin/Bb6.mid'
- if("Bn6") soundfile = 'sound/violin/Bn6.mid'
- if("B#6") soundfile = 'sound/violin/B#6.mid'
- if("Cb7") soundfile = 'sound/violin/Cb7.mid'
- if("Cn7") soundfile = 'sound/violin/Cn7.mid'
- if("C#7") soundfile = 'sound/violin/C#7.mid'
- if("Db7") soundfile = 'sound/violin/Db7.mid'
- if("Dn7") soundfile = 'sound/violin/Dn7.mid'
- if("D#7") soundfile = 'sound/violin/D#7.mid'
- if("Eb7") soundfile = 'sound/violin/Eb7.mid'
- if("En7") soundfile = 'sound/violin/En7.mid'
- if("E#7") soundfile = 'sound/violin/E#7.mid'
- if("Fb7") soundfile = 'sound/violin/Fb7.mid'
- if("Fn7") soundfile = 'sound/violin/Fn7.mid'
- if("F#7") soundfile = 'sound/violin/F#7.mid'
- if("Gb7") soundfile = 'sound/violin/Gb7.mid'
- if("Gn7") soundfile = 'sound/violin/Gn7.mid'
- if("G#7") soundfile = 'sound/violin/G#7.mid'
- if("Ab7") soundfile = 'sound/violin/Ab7.mid'
- if("An7") soundfile = 'sound/violin/An7.mid'
- if("A#7") soundfile = 'sound/violin/A#7.mid'
- if("Bb7") soundfile = 'sound/violin/Bb7.mid'
- if("Bn7") soundfile = 'sound/violin/Bn7.mid'
- if("B#7") soundfile = 'sound/violin/B#7.mid'
- if("Cb8") soundfile = 'sound/violin/Cb8.mid'
- if("Cn8") soundfile = 'sound/violin/Cn8.mid'
- if("C#8") soundfile = 'sound/violin/C#8.mid'
- if("Db8") soundfile = 'sound/violin/Db8.mid'
- if("Dn8") soundfile = 'sound/violin/Dn8.mid'
- if("D#8") soundfile = 'sound/violin/D#8.mid'
- if("Eb8") soundfile = 'sound/violin/Eb8.mid'
- if("En8") soundfile = 'sound/violin/En8.mid'
- if("E#8") soundfile = 'sound/violin/E#8.mid'
- if("Fb8") soundfile = 'sound/violin/Fb8.mid'
- if("Fn8") soundfile = 'sound/violin/Fn8.mid'
- if("F#8") soundfile = 'sound/violin/F#8.mid'
- if("Gb8") soundfile = 'sound/violin/Gb8.mid'
- if("Gn8") soundfile = 'sound/violin/Gn8.mid'
- if("G#8") soundfile = 'sound/violin/G#8.mid'
- if("Ab8") soundfile = 'sound/violin/Ab8.mid'
- if("An8") soundfile = 'sound/violin/An8.mid'
- if("A#8") soundfile = 'sound/violin/A#8.mid'
- if("Bb8") soundfile = 'sound/violin/Bb8.mid'
- if("Bn8") soundfile = 'sound/violin/Bn8.mid'
- if("B#8") soundfile = 'sound/violin/B#8.mid'
- if("Cb9") soundfile = 'sound/violin/Cb9.mid'
- if("Cn9") soundfile = 'sound/violin/Cn9.mid'
- else return
+/obj/item/device/instrument/New()
+ ..()
+ song = new(instrumentId, src)
+ song.instrumentExt = instrumentExt
- hearers(15, get_turf(src)) << sound(soundfile)
+/obj/item/device/instrument/Destroy()
+ qdel(song)
+ song = null
+ ..()
-/obj/item/device/violin/proc/playsong()
- do
- var/cur_oct[7]
- var/cur_acc[7]
- for(var/i = 1 to 7)
- cur_oct[i] = "3"
- cur_acc[i] = "n"
+/obj/item/device/instrument/attack_self(mob/user as mob)
+ if(!user.IsAdvancedToolUser())
+ to_chat(user, "You don't have the dexterity to do this!")
+ return 1
+ interact(user)
- for(var/line in song.lines)
- //world << line
- for(var/beat in splittext(lowertext(line), ","))
- //world << "beat: [beat]"
- var/list/notes = splittext(beat, "/")
- for(var/note in splittext(notes[1], "-"))
- //world << "note: [note]"
- if(!playing || !isliving(loc))//If the violin is playing, or isn't held by a person
- playing = 0
- return
- if(lentext(note) == 0)
- continue
- //world << "Parse: [copytext(note,1,2)]"
- var/cur_note = text2ascii(note) - 96
- if(cur_note < 1 || cur_note > 7)
- continue
- for(var/i=2 to lentext(note))
- var/ni = copytext(note,i,i+1)
- if(!text2num(ni))
- if(ni == "#" || ni == "b" || ni == "n")
- cur_acc[cur_note] = ni
- else if(ni == "s")
- cur_acc[cur_note] = "#" // so shift is never required
- else
- cur_oct[cur_note] = ni
- playnote(uppertext(copytext(note,1,2)) + cur_acc[cur_note] + cur_oct[cur_note])
- if(notes.len >= 2 && text2num(notes[2]))
- sleep(song.tempo / text2num(notes[2]))
- else
- sleep(song.tempo)
- if(repeat > 0)
- repeat-- //Infinite loops are baaaad.
- while(repeat > 0)
- playing = 0
-
-/obj/item/device/violin/attack_self(mob/user as mob)
- if(!isliving(user) || user.stat || user.restrained() || user.lying) return
- user.set_machine(src)
-
- var/dat = "Violin"
-
- if(song)
- if(song.lines.len > 0 && !(playing))
- dat += "Play Song
"
- dat += "Repeat Song: [repeat] times.
"
- if(playing)
- dat += "Stop Playing
"
- dat += "Repeats left: [repeat].
"
- if(!edit)
- dat += "Show Editor
"
- else
- dat += "Hide Editor
"
- dat += "Start a New Song
"
- dat += "Import a Song
"
- if(song)
- var/calctempo = (10/song.tempo)*60
- dat += "Tempo : -- [calctempo] BPM ++
"
- var/linecount = 0
- for(var/line in song.lines)
- linecount += 1
- dat += "Line [linecount]: [line] Delete Line Modify Line
"
- dat += "Add Line
"
- if(help)
- dat += "Hide Help
"
- dat += {"
- Lines are a series of chords, separated by commas (,), each with notes seperated by hyphens (-).
- Every note in a chord will play together, with chord timed by the tempo.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
- By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.
- Example: C,D,E,F,G,A,B will play a C major scale.
- After a note has an accidental placed, it will be remembered: C,C4,C,C3 is C3,C4,C4,C3
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B
- A pause may be denoted by an empty chord: C,E,,C,G
- To make a chord be a different time, end it with /x, where the chord length will be length
- defined by tempo / x: C,G/2,E/4
- Combined, an example is: E-E4/4,/2,G#/8,B/8,E3-E4/4
-
- Lines may be up to 50 characters.
- A song may only contain up to 50 lines.
- "}
- else
- dat += "Show Help
"
- dat += "