Merge branch 'master' into medical-records

This commit is contained in:
Contrabang
2022-12-14 22:02:21 -05:00
97 changed files with 5636 additions and 3926 deletions
+2 -2
View File
@@ -66353,7 +66353,7 @@
dir = 1
},
/turf/simulated/floor/plating/airless,
/area/space)
/area/turret_protected/aisat_interior)
"doe" = (
/obj/structure/sign/securearea{
pixel_y = -32
@@ -74107,7 +74107,7 @@
volume_rate = 200
},
/turf/simulated/floor/plating/airless,
/area/space/nearstation)
/area/engine/engineering)
"idz" = (
/obj/structure/disposalpipe/segment{
dir = 4
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -11971,6 +11971,9 @@
icon_state = "snow"
},
/area/holodeck/source_snowfield)
"UO" = (
/turf/simulated/wall/indestructible/opsglass/limited_smooth,
/area/syndicate_mothership)
"UT" = (
/obj/machinery/economy/vending/cola/free,
/turf/simulated/floor/plasteel{
@@ -60454,9 +60457,9 @@ KR
mI
KY
nB
BZ
BZ
BZ
UO
UO
UO
KY
rU
sn
@@ -60970,7 +60973,7 @@ ab
ab
BZ
hG
BZ
UO
iT
rU
sx
@@ -61227,7 +61230,7 @@ ab
ab
BZ
BY
BZ
UO
lB
KY
KY
+5
View File
@@ -4,6 +4,11 @@
// This exists so that world.Profile() is THE FIRST PROC TO RUN in the init sequence.
// This allows us to get the real details of everything lagging at server start.
world.Profile(PROFILE_START)
#if defined(ENABLE_BYOND_TRACY) && (DM_BUILD == 1589)
var/tracy_init = CALL_EXT("prof.dll", "init")() // Setup Tracy integration
if(tracy_init != "0")
CRASH("Tracy init error: [tracy_init]")
#endif
// After that, the debugger is initialized.
// Doing it this early makes it possible to set breakpoints in the New()
// of things assigned to global variables or objects included in a compiled map file.
+4
View File
@@ -824,3 +824,7 @@
// /obj/machinery/door/airlock signals
#define COMSIG_AIRLOCK_OPEN "airlock_open"
#define COMSIG_AIRLOCK_CLOSE "airlock_close"
// /datum/objective signals
///from datum/objective/proc/find_target()
#define COMSIG_OBJECTIVE_TARGET_FOUND "objective_target_found"
+2 -1
View File
@@ -23,7 +23,7 @@ DEFINE_BITFIELD(smoothing_flags, list(
/*smoothing macros*/
#define QUEUE_SMOOTH(thing_to_queue) if(thing_to_queue.smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)) {SSicon_smooth.add_to_queue(thing_to_queue)}
#define QUEUE_SMOOTH(thing_to_queue) if((thing_to_queue.smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)) && thing_to_queue.z) {SSicon_smooth.add_to_queue(thing_to_queue)}
#define QUEUE_SMOOTH_NEIGHBORS(thing_to_queue) for(var/neighbor in orange(1, thing_to_queue)) {var/atom/atom_neighbor = neighbor; QUEUE_SMOOTH(atom_neighbor)}
@@ -97,6 +97,7 @@ DEFINE_BITFIELD(smoothing_flags, list(
#define SMOOTH_GROUP_BRASS_WALL S_OBJ(16) ///turf/simulated/wall/mineral/brass, /obj/structure/falsewall/brass
#define SMOOTH_GROUP_REGULAR_WALLS S_OBJ(17) ///turf/simulated/wall, /obj/structure/falsewall
#define SMOOTH_GROUP_REINFORCED_WALLS S_OBJ(18) ///turf/simulated/wall/r_wall, /obj/structure/falsewall/reinforced
#define SMOOTH_GROUP_CULT_WALLS S_OBJ(19) ///turf/simulated/wall/cult
#define SMOOTH_GROUP_WINDOW_FULLTILE S_OBJ(21) ///turf/simulated/indestructible/fakeglass, /obj/structure/window/full/basic, /obj/structure/window/full/plasmabasic, /obj/structure/window/full/plasmareinforced, /obj/structure/window/full/reinforced
#define SMOOTH_GROUP_WINDOW_FULLTILE_BRASS S_OBJ(22) ///obj/structure/window/brass/fulltile
+1 -2
View File
@@ -152,8 +152,7 @@ DEFINE_BITFIELD(smoothing_junction, list(
//do not use, use QUEUE_SMOOTH(atom)
/atom/proc/smooth_icon()
smoothing_flags &= ~SMOOTH_QUEUED
if(!z) //nullspace are not sending their best
stack_trace("[type] called smooth_icon() without being on a z-level")
if(!z)
return
if(smoothing_flags & SMOOTH_CORNERS)
if(smoothing_flags & SMOOTH_DIAGONAL_CORNERS)
-59
View File
@@ -593,65 +593,6 @@
else
min = mid+1
/*
proc/dd_sortedObjectList(list/incoming)
/*
Use binary search to order by dd_SortValue().
This works by going to the half-point of the list, seeing if the node in
question is higher or lower cost, then going halfway up or down the list
and checking again. This is a very fast way to sort an item into a list.
*/
var/list/sorted_list = new()
var/low_index
var/high_index
var/insert_index
var/midway_calc
var/current_index
var/current_item
var/current_item_value
var/current_sort_object_value
var/list/list_bottom
var/current_sort_object
for(current_sort_object in incoming)
low_index = 1
high_index = sorted_list.len
while(low_index <= high_index)
// Figure out the midpoint, rounding up for fractions. (BYOND rounds down, so add 1 if necessary.)
midway_calc = (low_index + high_index) / 2
current_index = round(midway_calc)
if(midway_calc > current_index)
current_index++
current_item = sorted_list[current_index]
current_item_value = current_item:dd_SortValue()
current_sort_object_value = current_sort_object:dd_SortValue()
if(current_sort_object_value < current_item_value)
high_index = current_index - 1
else if(current_sort_object_value > current_item_value)
low_index = current_index + 1
else
// current_sort_object == current_item
low_index = current_index
break
// Insert before low_index.
insert_index = low_index
// Special case adding to end of list.
if(insert_index > sorted_list.len)
sorted_list += current_sort_object
continue
// Because BYOND lists don't support insert, have to do it by:
// 1) taking out bottom of list, 2) adding item, 3) putting back bottom of list.
list_bottom = sorted_list.Copy(insert_index)
sorted_list.Cut(insert_index)
sorted_list += current_sort_object
sorted_list += list_bottom
return sorted_list
*/
/proc/dd_sortedtextlist(list/incoming, case_sensitive = 0)
// Returns a new list with the text values sorted.
// Use binary search to order by sortValue.
+5
View File
@@ -4,6 +4,11 @@
// Uncomment the following line to compile unit tests.
// #define UNIT_TESTS
// Uncomment the following line to enable Tracy profiling.
// DO NOT DO THIS UNLESS YOU UNDERSTAND THE IMPLICATIONS
// Your data directory will grow by about a gigabyte every time you launch the server, as well as introducing potential instabilities over multiple BYOND versions.
// #define ENABLE_BYOND_TRACY
#ifdef CIBUILDING
#define UNIT_TESTS
+1 -4
View File
@@ -1195,10 +1195,7 @@
if("traitor")
if(!(has_antag_datum(/datum/antagonist/traitor)))
var/datum/antagonist/traitor/T = new()
T.give_objectives = FALSE
T.give_uplink = FALSE
add_antag_datum(T)
add_antag_datum(/datum/antagonist/traitor)
log_admin("[key_name(usr)] has traitored [key_name(current)]")
message_admins("[key_name_admin(usr)] has traitored [key_name_admin(current)]")
+4
View File
@@ -17,6 +17,10 @@
return new /datum/spell_targeting/self
/obj/effect/proc_holder/spell/rod_form/cast(list/targets,mob/user = usr)
if(get_turf(user) != user.loc)
to_chat(user, "<span class='warning'>You cannot summon a rod in the ether, the spell fizzles out!</span>")
revert_cast()
return FALSE
for(var/mob/living/M in targets)
var/turf/start = get_turf(M)
var/obj/effect/immovablerod/wizard/W = new(start, get_ranged_target_turf(M, M.dir, (15 + spell_level * 3)), rod_delay)
+5
View File
@@ -88,6 +88,11 @@
/area/mine/podbay
name = "Mining Podbay"
/area/mine/airlock
name = "Mining Airlock"
/area/mine/mechbay
name = "Mining Mechbay Storage"
/**********************Lavaland Areas**************************/
+1
View File
@@ -180,6 +180,7 @@
qdel(AA)
alternate_appearances = null
REMOVE_FROM_SMOOTH_QUEUE(src)
QDEL_NULL(reagents)
invisibility = INVISIBILITY_MAXIMUM
LAZYCLEARLIST(overlays)
+1 -1
View File
@@ -74,10 +74,10 @@
knockdown_duration = 2 SECONDS
/obj/item/restraints/legcuffs/bola/cult/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback)
. = ..()
if(!iscultist(thrower))
thrower.visible_message("<span class='danger'>The bola glows, and boomarangs back at [thrower]!</span>")
throw_impact(thrower)
. = ..()
/obj/item/restraints/legcuffs/bola/cult/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(iscultist(hit_atom))
@@ -43,6 +43,8 @@
var/bio_fluff_string = "Your scarabs fail to mutate. This shouldn't happen! Submit a bug report!"
var/admin_fluff_string = "URK URF!"//the wheels on the bus...
var/name_color = "white"//only used with protector shields for the time being
/// If true, it will not make a message on host when hit, or make an effect when deploying or recalling
var/stealthy_deploying = FALSE
/mob/living/simple_animal/hostile/guardian/Initialize(mapload, mob/living/host)
. = ..()
@@ -92,9 +94,10 @@
if(iseffect(summoner.loc))
Recall(TRUE)
else
new /obj/effect/temp_visual/guardian/phase/out(loc)
if(!stealthy_deploying)
new /obj/effect/temp_visual/guardian/phase/out(get_turf(src))
new /obj/effect/temp_visual/guardian/phase(get_turf(summoner))
forceMove(summoner.loc) //move to summoner's tile, don't recall
new /obj/effect/temp_visual/guardian/phase(loc)
/mob/living/simple_animal/hostile/guardian/proc/is_deployed()
return loc != summoner
@@ -139,7 +142,8 @@
summoner.adjustBruteLoss(damage)
if(damage)
to_chat(summoner, "<span class='danger'>Your [name] is under attack! You take damage!</span>")
summoner.visible_message("<span class='danger'>Blood sprays from [summoner] as [src] takes damage!</span>")
if(!stealthy_deploying)
summoner.visible_message("<span class='danger'>Blood sprays from [summoner] as [src] takes damage!</span>")
if(summoner.stat == UNCONSCIOUS)
to_chat(summoner, "<span class='danger'>Your body can't take the strain of sustaining [src] in this condition, it begins to fall apart!</span>")
summoner.adjustCloneLoss(damage/2)
@@ -169,8 +173,10 @@
return
if(!summoner) return
if(loc == summoner)
forceMove(get_turf(summoner))
new /obj/effect/temp_visual/guardian/phase(loc)
var/turf/T = get_turf(summoner)
forceMove(T)
if(!stealthy_deploying)
new /obj/effect/temp_visual/guardian/phase(T)
reset_perspective()
cooldown = world.time + 30
@@ -178,7 +184,8 @@
if(!summoner || loc == summoner || (cooldown > world.time && !forced))
return
if(!summoner) return
new /obj/effect/temp_visual/guardian/phase/out(get_turf(src))
if(!stealthy_deploying)
new /obj/effect/temp_visual/guardian/phase/out(get_turf(src))
forceMove(summoner)
buckled = null
cooldown = world.time + 30
@@ -1,22 +1,29 @@
/mob/living/simple_animal/hostile/guardian/assassin
melee_damage_lower = 15
melee_damage_upper = 15
armour_penetration_percentage = 0
playstyle_string = "As an <b>Assassin</b> type you do medium damage and have no damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage."
melee_damage_lower = 20
melee_damage_upper = 20
damage_transfer = 0.6
playstyle_string = "As an <b>Assassin</b> type you do medium damage and have moderate damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage."
magic_fluff_string = "..And draw the Space Ninja, a lethal, invisible assassin."
tech_fluff_string = "Boot sequence complete. Assassin modules loaded. Holoparasite swarm online."
bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of sneaking and stealthy attacks."
stealthy_deploying = TRUE
attacktext = "slashes"
attack_sound = 'sound/weapons/bladeslice.ogg'
var/toggle = FALSE
var/stealthcooldown = 0
var/default_stealth_cooldown = 16 SECONDS
var/default_stealth_cooldown = 10 SECONDS
var/obj/screen/alert/canstealthalert
var/obj/screen/alert/instealthalert
/mob/living/simple_animal/hostile/guardian/assassin/Initialize(mapload, mob/living/host)
. = ..()
remove_from_all_data_huds()
if(loc == summoner && toggle)
ToggleMode(0)
/mob/living/simple_animal/hostile/guardian/assassin/Life(seconds, times_fired)
. = ..()
updatestealthalert()
if(loc == summoner && toggle)
ToggleMode(0)
/mob/living/simple_animal/hostile/guardian/assassin/Stat()
..()
@@ -30,15 +37,19 @@
if(toggle && (isliving(target) || istype(target, /obj/structure/window) || istype(target, /obj/structure/grille)))
ToggleMode(1)
/mob/living/simple_animal/hostile/guardian/assassin/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect)
if(!no_effect && !visual_effect_icon)
visual_effect_icon = ATTACK_EFFECT_CLAW
return ..()
/mob/living/simple_animal/hostile/guardian/assassin/adjustHealth(amount, updating_health = TRUE)
. = ..()
if(. > 0 && toggle)
ToggleMode(1)
/mob/living/simple_animal/hostile/guardian/assassin/Recall()
..()
if(toggle)
ToggleMode(0)
/mob/living/simple_animal/hostile/guardian/assassin/Manifest()
. = ..()
ToggleMode(FALSE)
/mob/living/simple_animal/hostile/guardian/assassin/ToggleMode(forced = 0)
if(toggle)
@@ -58,17 +69,16 @@
toggle = FALSE
else if(stealthcooldown <= world.time)
if(loc == summoner)
to_chat(src, "<span class='danger'>You have to be manifested to enter stealth!</span>")
to_chat(src, "<span class='notice'>You automatically deploy stealthed!</span>")
return
melee_damage_lower = 50
melee_damage_upper = 50
armour_penetration_percentage = 100
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
new /obj/effect/temp_visual/guardian/phase/out(get_turf(src))
alpha = 15
alpha = 10
if(!forced)
to_chat(src, "<span class='danger'>You enter stealth, empowering your next attack.</span>")
to_chat(src, "<span class='danger'>You enter stealth, becoming mostly invisible, empowering your next attack.</span>")
updatestealthalert()
toggle = TRUE
else if(!forced)
@@ -328,7 +328,7 @@
continue
var/mob/living/simple_animal/revenant/R = M.current
total_essence += R.essence_accumulated
if(total_essence >= targetAmount)
if(total_essence < targetAmount)
return FALSE
return TRUE
+42 -9
View File
@@ -90,6 +90,8 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
if(possible_targets.len > 0)
target = pick(possible_targets)
SEND_SIGNAL(src, COMSIG_OBJECTIVE_TARGET_FOUND, target)
/**
* Called when the objective's target goes to cryo.
*/
@@ -348,23 +350,54 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
/datum/objective/escape/escape_with_identity
var/target_real_name // Has to be stored because the target's real_name can change over the course of the round
/// Stored because the target's `[mob/var/real_name]` can change over the course of the round.
var/target_real_name
/// If the objective has an assassinate objective tied to it.
var/has_assassinate_objective = FALSE
/datum/objective/escape/escape_with_identity/New(text, datum/objective/assassinate/assassinate)
..()
if(!assassinate)
return
target = assassinate.target
target_real_name = assassinate.target.current.real_name
explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card."
has_assassinate_objective = TRUE
RegisterSignal(assassinate, COMSIG_OBJECTIVE_TARGET_FOUND, PROC_REF(assassinate_found_target))
/datum/objective/escape/escape_with_identity/is_invalid_target(datum/mind/possible_target)
if(..() || !possible_target.current.client)
return TRUE
// If the target is geneless, then it's an invalid target.
return HAS_TRAIT(possible_target.current, TRAIT_GENELESS)
/datum/objective/escape/escape_with_identity/find_target()
var/list/possible_targets = list() //Copypasta because NO_DNA races, yay for snowflakes.
for(var/datum/mind/possible_target in SSticker.minds)
if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != DEAD) && possible_target.current.client)
var/mob/living/carbon/human/H = possible_target.current
if(!HAS_TRAIT(H, TRAIT_GENELESS))
possible_targets += possible_target
if(possible_targets.len > 0)
target = pick(possible_targets)
..()
if(target && target.current)
target_real_name = target.current.real_name
explanation_text = "Escape on the shuttle or an escape pod with the identity of [target_real_name], the [target.assigned_role] while wearing [target.p_their()] identification card."
else
explanation_text = "Free Objective"
/datum/objective/escape/escape_with_identity/proc/assassinate_found_target(datum/source, datum/mind/new_target)
SIGNAL_HANDLER
if(new_target)
target_real_name = new_target.current.real_name
return
// The assassinate objective was unable to find a new target after the old one cryo'd as was qdel'd. We're on our own.
find_target()
has_assassinate_objective = FALSE
/datum/objective/escape/escape_with_identity/on_target_cryo()
if(has_assassinate_objective)
return // Our assassinate objective will handle this.
..()
/datum/objective/escape/escape_with_identity/post_target_cryo()
if(has_assassinate_objective)
return // Our assassinate objective will handle this.
..()
// This objective should only be given to a single owner since only 1 person can have the ID card of the target.
// We're fine to use `owner` instead of `get_owners()`.
/datum/objective/escape/escape_with_identity/check_completion()
+3
View File
@@ -156,6 +156,9 @@
if(!is_authenticated(usr))
to_chat(usr, "<span class='warning'>Access denied.</span>")
return
if(SSticker.current_state == GAME_STATE_FINISHED)
to_chat(usr, "<span class='warning'>Access denied, borgs are no longer your station's property.</span>")
return
switch(action)
if("arm") // Arms the emergency self-destruct system
if(issilicon(usr))
+1
View File
@@ -736,6 +736,7 @@ GLOBAL_LIST_EMPTY(turret_icons)
A.current = T
A.yo = U.y - T.y
A.xo = U.x - T.x
A.starting = loc
A.fire()
else
A.throw_at(target, scan_range, 1)
+1 -1
View File
@@ -100,7 +100,7 @@
var/shoot_chance = 2
/// If true, enforce access checks on customers. Disabled by messing with wires.
var/scan_id
var/scan_id = TRUE
/// Holder for a coin inserted into the vendor
var/obj/item/coin/coin
var/datum/wires/vending/wires
+21 -3
View File
@@ -98,8 +98,19 @@
icon_state = "shield2"
density = FALSE
var/boing = FALSE
var/knockdown = FALSE
aSignal = /obj/item/assembly/signaler/anomaly/grav
/obj/effect/anomaly/grav/Initialize(mapload, new_lifespan, _drops_core = TRUE, event_spawned = TRUE)
. = ..()
if(!event_spawned) //So an anomaly in the hallway is assured to have some risk to it, but not make sm / vetus too much pain
return
for(var/I in 1 to 3)
if(prob(75))
new /obj/item/stack/rods(loc)
if(prob(75))
new /obj/item/shard(loc)
/obj/effect/anomaly/grav/anomalyEffect()
..()
boing = TRUE
@@ -112,7 +123,7 @@
if(!M.mob_negates_gravity())
step_towards(M,src)
for(var/obj/O in range(0, src))
if(!O.anchored && O.loc != src) // so it cannot throw the anomaly core
if(!O.anchored && O.loc != src && O.move_resist < MOVE_FORCE_OVERPOWERING) // so it cannot throw the anomaly core or super big things
var/mob/living/target = locate() in view(4, src)
if(target && !target.stat)
O.throw_at(target, 5, 10, dodgeable = FALSE)
@@ -129,7 +140,10 @@
/obj/effect/anomaly/grav/proc/gravShock(mob/living/A)
if(boing && isliving(A) && !A.stat)
A.Weaken(4 SECONDS)
if(!knockdown)
A.Weaken(4 SECONDS)
else
A.KnockDown(4 SECONDS) //You know, maybe hard stuns in a megafauna fight are a bad idea.
var/atom/target = get_edge_target_turf(A, get_dir(src, get_step_away(A, src)))
A.throw_at(target, 5, 1)
boing = FALSE
@@ -147,6 +161,7 @@
var/zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE
var/zap_range = 5
var/power = 5000
var/knockdown = FALSE
/obj/effect/anomaly/flux/Initialize(mapload, new_lifespan, drops_core = TRUE, _explosive = TRUE)
. = ..()
@@ -175,7 +190,10 @@
if(canshock && istype(M))
canshock = FALSE //Just so you don't instakill yourself if you slam into the anomaly five times in a second.
M.electrocute_act(shockdamage, name, flags = SHOCK_NOGLOVES)
M.Weaken(explosive ? 6 SECONDS : 3 SECONDS) //Back to being deadly if you touch it, rather than just being able to crawl out of it. Non explosive ones less deadly, since you can't loot them / vetus
if(!knockdown)
M.Weaken(explosive ? 6 SECONDS : 3 SECONDS) //Back to being deadly if you touch it, rather than just being able to crawl out of it. Non explosive ones less deadly, since you can't loot them
else
M.KnockDown(3 SECONDS)
/obj/effect/anomaly/flux/detonate()
if(explosive)
+7 -1
View File
@@ -214,7 +214,6 @@
//Decorative structures
///////
/obj/structure/decorative_structures
icon = 'icons/obj/decorations.dmi'
icon_state = ""
@@ -222,6 +221,13 @@
anchored = FALSE
max_integrity = 100
/obj/structure/decorative_structures/wrench_act(mob/user, obj/item/I)
. = TRUE
add_fingerprint(user)
if(!I.tool_use_check(user, 0))
return
default_unfasten_wrench(user, I, 0)
/obj/structure/decorative_structures/metal
flags = CONDUCT
+10
View File
@@ -195,6 +195,16 @@
desc = "A flag proudly boasting the logo of the cultists, sworn enemies of NT."
icon_state = "cultflag"
/obj/item/flag/ussp
name = "\improper USSP flag"
desc = "A flag proudly boasting the logo of the USSP, a noticable faction in the galaxy."
icon_state = "usspflag"
/obj/item/flag/solgov
name = "\improper Trans-Solar Federation flag"
desc = "A flag proudly boasting the logo of the SolGov, allied to NT government originated from Earth."
icon_state = "solgovflag"
//Chameleon
/obj/item/flag/chameleon
@@ -11,7 +11,7 @@
/obj/item/implant/traitor/implant(mob/living/carbon/human/mindslave_target, mob/living/carbon/human/user)
// Check `activated` here so you can't just keep taking it out and putting it back into other people.
if(!..() || activated || !istype(mindslave_target) || !istype(user)) // Both the target and the user need to be human.
if(activated || !istype(mindslave_target) || !istype(user)) // Both the target and the user need to be human.
return FALSE
// If the target is catatonic or doesn't have a mind, return.
@@ -24,7 +24,6 @@
mindslave_target.visible_message(
"<span class='warning'>[mindslave_target] seems to resist the bio-chip!</span>", \
"<span class='warning'>You feel a strange sensation in your head that quickly dissipates.</span>")
removed(mindslave_target)
qdel(src)
return FALSE
@@ -32,7 +31,6 @@
if(mindslave_target == user)
to_chat(user, "<span class='notice'>Making yourself loyal to yourself was a great idea! Perhaps even the best idea ever! Actually, you just feel like an idiot.</span>")
user.adjustBrainLoss(20)
removed(mindslave_target)
qdel(src)
return FALSE
@@ -40,7 +38,7 @@
mindslave_target.mind.add_antag_datum(new /datum/antagonist/mindslave(user.mind))
mindslave_UID = mindslave_target.mind.UID()
log_admin("[key_name_admin(user)] has mind-slaved [key_name_admin(mindslave_target)].")
return TRUE
return ..()
/obj/item/implant/traitor/removed(mob/target)
. = ..()
@@ -59,7 +59,7 @@
max_combined_w_class = 35
resistance_flags = FIRE_PROOF
flags_2 = NO_MAT_REDEMPTION_2
cant_hold = list(/obj/item/storage/backpack/holding)
cant_hold = list(/obj/item/storage/backpack, /obj/item/storage/belt/bluespace)
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 60, ACID = 50)
/obj/item/storage/backpack/holding/attackby(obj/item/W, mob/user, params)
@@ -356,16 +356,65 @@
new /obj/item/crowbar(src)
/*
* Duffelbags - My thanks to MrSnapWalk for the original icon and Neinhaus for the job variants - Dave.
* Duffelbags
*/
/obj/item/storage/backpack/duffel
name = "duffelbag"
desc = "A large grey duffelbag designed to hold more items than a regular bag."
desc = "A large grey duffelbag designed to hold more items than a regular bag. It slows you down when unzipped."
icon_state = "duffel"
item_state = "duffel"
max_combined_w_class = 30
slowdown = 1
/// Is the bag zipped up?
var/zipped = TRUE
/// How long it takes to toggle the zip state of this bag
var/zip_time = 0.7 SECONDS
/obj/item/storage/backpack/duffel/examine(mob/user)
. = ..()
. += "<span class='notice'>It is currently [zipped ? "zipped" : "unzipped"]. Alt+Shift+Click to [zipped ? "un-" : ""]zip it!</span>"
/obj/item/storage/backpack/duffel/AltShiftClick(mob/user)
. = ..()
handle_zipping(user)
/obj/item/storage/backpack/duffel/proc/handle_zipping(mob/user)
if(!zip_time || do_after(user, zip_time, target = src))
playsound(src, 'sound/items/zip.ogg', 75, TRUE)
zipped = !zipped
if(!zipped && zip_time) // Handle slowdown and stuff now that we just zipped it
slowdown = 1
show_to(user)
return
slowdown = 0
hide_from_all()
for(var/obj/item/storage/container in src)
container.hide_from_all() // Hide everything inside the bag too
// The following three procs handle refusing access to contents if the duffel is zipped
/obj/item/storage/backpack/duffel/handle_item_insertion(obj/item/I, prevent_warning)
if(zipped)
to_chat(usr, "<span class='notice'>[src] is zipped shut!</span>")
return FALSE
return ..()
/obj/item/storage/backpack/duffel/remove_from_storage(obj/item/I, atom/new_location)
if(zipped)
to_chat(usr, "<span class='notice'>[src] is zipped shut!</span>")
return FALSE
return ..()
/obj/item/storage/backpack/duffel/show_to(mob/user)
if(zipped)
to_chat(usr, "<span class='notice'>[src] is zipped shut!</span>")
return FALSE
return ..()
/obj/item/storage/backpack/duffel/syndie
name = "suspicious looking duffelbag"
@@ -374,7 +423,7 @@
item_state = "duffel-syndiammo"
origin_tech = "syndicate=1"
silent = TRUE
slowdown = 0
zip_time = 0
resistance_flags = FIRE_PROOF
/obj/item/storage/backpack/duffel/syndie/med
@@ -609,6 +609,7 @@
icon = 'icons/obj/cigarettes.dmi'
icon_state = "matchbox"
item_state = "matchbox"
base_icon_state = "matchbox"
storage_slots = 10
w_class = WEIGHT_CLASS_TINY
max_w_class = WEIGHT_CLASS_TINY
@@ -627,6 +628,18 @@
playsound(user.loc, 'sound/goonstation/misc/matchstick_light.ogg', 50, 1)
return
/obj/item/storage/box/matches/update_icon_state()
. = ..()
switch(length(contents))
if(10)
icon_state = base_icon_state
if(5 to 9)
icon_state = "[base_icon_state]_almostfull"
if(1 to 4)
icon_state = "[base_icon_state]_almostempty"
if(0)
icon_state = "[base_icon_state]_e"
/obj/item/storage/box/autoinjectors
name = "box of injectors"
desc = "Contains autoinjectors."
@@ -212,6 +212,13 @@
if(user.s_active == src)
user.s_active = null
/**
* Hides the current container interface from all viewers.
*/
/obj/item/storage/proc/hide_from_all()
for(var/mob/M in mobs_viewing)
hide_from(M)
/**
* Checks all mobs currently viewing the storage inventory, and hides it if they shouldn't be able to see it.
*/
+8
View File
@@ -91,6 +91,14 @@
C.deconstruct()
..()
/obj/structure/lattice/catwalk/mining
name = "reinforced catwalk"
desc = "A heavily reinforced catwalk used to build bridges in hostile environments. It doesn't look like anything could make this budge."
resistance_flags = INDESTRUCTIBLE
/obj/structure/lattice/catwalk/mining/deconstruction_hints(mob/user)
return
/obj/structure/lattice/catwalk/clockwork
name = "clockwork catwalk"
icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi'
+1 -1
View File
@@ -120,7 +120,7 @@
curse(user)
if("Body")
var/list/race_list = list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin")
var/list/race_list = list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin", "Nian")
for(var/species in GLOB.whitelisted_species)
if(can_use_species(H, species))
race_list += species
+5
View File
@@ -449,6 +449,11 @@
setDir(ini_dir)
move_update_air(T)
/obj/structure/window/force_pushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
. = ..()
anchored = FALSE
QUEUE_SMOOTH_NEIGHBORS(src)
/obj/structure/window/CanAtmosPass(turf/T)
if(!anchored || !density)
return TRUE
@@ -196,6 +196,10 @@
underlays += mutable_appearance('icons/obj/structures.dmi', "grille")
underlays += mutable_appearance('icons/turf/floors.dmi', "plating")
/turf/simulated/wall/indestructible/opsglass/limited_smooth
smoothing_groups = list(SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM)
canSmoothWith = list(SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM)
/turf/simulated/wall/indestructible/rock
name = "dense rock"
desc = "An extremely densely-packed rock, most mining tools or explosives would never get through this."
+2 -1
View File
@@ -5,7 +5,8 @@
icon_state = "cult_wall-0"
base_icon_state = "cult_wall"
smoothing_flags = SMOOTH_BITMASK
canSmoothWith = null
smoothing_groups = list(SMOOTH_GROUP_SIMULATED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_CULT_WALLS)
canSmoothWith = list(SMOOTH_GROUP_WALLS, SMOOTH_GROUP_REGULAR_WALLS, SMOOTH_GROUP_REINFORCED_WALLS)
sheet_type = /obj/item/stack/sheet/runed_metal
sheet_amount = 1
girder_type = /obj/structure/girder/cult
+1 -1
View File
@@ -202,7 +202,7 @@
// Removes all signs of lattice on the pos of the turf -Donkieyo
/turf/proc/RemoveLattice()
var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
if(L)
if(L && !(L.resistance_flags & INDESTRUCTIBLE))
qdel(L)
/turf/proc/dismantle_wall(devastated = FALSE, explode = FALSE)
+3
View File
@@ -299,4 +299,7 @@ GLOBAL_LIST_EMPTY(world_topic_handlers)
rustg_close_async_http_client() // Close the HTTP client. If you dont do this, youll get phantom threads which can crash DD from memory access violations
disable_auxtools_debugger() // Disables the debugger if running. See above comment
rustg_redis_disconnect() // Disconnects the redis connection. See above.
#ifdef ENABLE_BYOND_TRACY
CALL_EXT("prof.dll", "destroy")() // Setup Tracy integration
#endif
..()
+3 -1
View File
@@ -510,13 +510,15 @@
var/count_eggs = 0
var/count_spiderlings = 0
var/count_infected = 0
for(var/obj/structure/spider/eggcluster/terror_eggcluster/E in GLOB.ts_egg_list)
if(is_station_level(E.z))
count_eggs += E.spiderling_number
for(var/obj/structure/spider/spiderling/terror_spiderling/L in GLOB.ts_spiderling_list)
if(!L.stillborn && is_station_level(L.z))
count_spiderlings += 1
dat += "<table cellspacing=5><TR><TD>Growing TS on-station: [count_eggs] egg[count_eggs != 1 ? "s" : ""], [count_spiderlings] spiderling[count_spiderlings != 1 ? "s" : ""]. </TD></TR></TABLE>"
count_infected = length(GLOB.ts_infected_list)
dat += "<table cellspacing=5><tr><td>Growing TS on-station: [count_eggs] egg\s, [count_spiderlings] spiderling\s, [count_infected] infected</td></tr></table>"
if(SSticker.mode.ert.len)
dat += check_role_table("ERT", SSticker.mode.ert)
@@ -151,11 +151,8 @@
var/mob/living/carbon/human/H = kill_objective.target?.current
if(!(locate(/datum/objective/escape) in owner.get_all_objectives()) && H && !HAS_TRAIT(H, TRAIT_GENELESS))
var/datum/objective/escape/escape_with_identity/identity_theft = new
var/datum/objective/escape/escape_with_identity/identity_theft = new(assassinate = kill_objective)
identity_theft.owner = owner
identity_theft.target = kill_objective.target
identity_theft.target_real_name = kill_objective.target.current.real_name
identity_theft.explanation_text = "Escape on the shuttle or an escape pod with the identity of [identity_theft.target_real_name], the [identity_theft.target.assigned_role] while wearing [identity_theft.target.p_their()] identification card."
objectives += identity_theft
if(!(locate(/datum/objective/escape) in owner.get_all_objectives()))
@@ -81,6 +81,8 @@
return TRUE
/datum/action/changeling/evolution_menu/proc/try_purchase_power(power_type)
if(!(power_type in cling.purchaseable_powers))
return FALSE
if(power_type in purchased_abilities)
to_chat(owner, "<span class='warning'>We have already evolved this ability!</span>")
return FALSE
@@ -16,13 +16,14 @@
max_integrity = 350
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 30, ACID = 30)
var/temperature_archived
var/current_heat_capacity = 50
var/mob/living/carbon/occupant = null
var/obj/item/reagent_containers/glass/beaker = null
/// Holds two bitflags, AUTO_EJECT_DEAD and AUTO_EJECT_HEALTHY. Used to determine if the cryo cell will auto-eject dead and/or completely healthy patients.
var/auto_eject_prefs = AUTO_EJECT_HEALTHY | AUTO_EJECT_DEAD
var/next_trans = 0
var/current_heat_capacity = 50
var/obj/item/reagent_containers/glass/beaker = null
var/last_injection
var/injection_cooldown = 34 SECONDS
var/efficiency
var/running_bob_animation = FALSE // This is used to prevent threads from building up if update_icons is called multiple times
@@ -46,7 +47,7 @@
/obj/machinery/atmospherics/unary/cryo_cell/power_change()
..()
if(!(stat & (BROKEN|NOPOWER)))
if(!(stat & (BROKEN | NOPOWER)))
set_light(2)
else
set_light(0)
@@ -161,13 +162,13 @@
/obj/machinery/atmospherics/unary/cryo_cell/process()
..()
if(!occupant)
if(!on || !occupant)
return
if((auto_eject_prefs & AUTO_EJECT_DEAD) && occupant.stat == DEAD)
auto_eject(AUTO_EJECT_DEAD)
return
if((auto_eject_prefs & AUTO_EJECT_HEALTHY) && !occupant.has_organic_damage() && !occupant.has_mutated_organs())
if((auto_eject_prefs & AUTO_EJECT_HEALTHY) && !(occupant.has_organic_damage() || occupant.has_mutated_organs()))
auto_eject(AUTO_EJECT_HEALTHY)
return
@@ -178,9 +179,7 @@
/obj/machinery/atmospherics/unary/cryo_cell/process_atmos()
..()
if(!node)
return
if(!on)
if(!node || !on)
return
if(air_contents)
@@ -262,7 +261,7 @@
/obj/machinery/atmospherics/unary/cryo_cell/ui_act(action, params)
if(..() || usr == occupant)
return
if(stat & (NOPOWER|BROKEN))
if(stat & (NOPOWER | BROKEN))
return
. = TRUE
@@ -393,30 +392,28 @@
/obj/machinery/atmospherics/unary/cryo_cell/proc/process_occupant()
if(air_contents.total_moles() < 10)
return
if(occupant)
if(occupant.stat == 2 || (occupant.health >= 100 && !occupant.has_mutated_organs())) //Why waste energy on dead or healthy people
occupant.bodytemperature = T0C
return
occupant.bodytemperature += 2*(air_contents.temperature - occupant.bodytemperature)*current_heat_capacity/(current_heat_capacity + air_contents.heat_capacity())
occupant.bodytemperature = max(occupant.bodytemperature, air_contents.temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise
if(occupant.bodytemperature < T0C)
var/stun_time = (max(5 / efficiency, (1 / occupant.bodytemperature) * 2000/efficiency)) STATUS_EFFECT_CONSTANT
occupant.Sleeping(stun_time)
occupant.Paralyse(stun_time)
if(air_contents.oxygen > 2)
if(occupant.getOxyLoss())
occupant.adjustOxyLoss(-6)
else
occupant.adjustOxyLoss(-1.2)
if(beaker && next_trans == 0)
var/proportion = 10 * min(1/beaker.volume, 1)
// Yes, this means you can get more bang for your buck with a beaker of SF vs a patch
// But it also means a giant beaker of SF won't heal people ridiculously fast 4 cheap
beaker.reagents.reaction(occupant, REAGENT_TOUCH, proportion)
beaker.reagents.trans_to(occupant, 1, 10)
next_trans++
if(next_trans == 17)
next_trans = 0
if(occupant.stat == DEAD || !(occupant.has_organic_damage() || occupant.has_mutated_organs())) // Why waste energy on dead or healthy people
occupant.bodytemperature = T0C
return
occupant.bodytemperature += 2 * (air_contents.temperature - occupant.bodytemperature) * current_heat_capacity / (current_heat_capacity + air_contents.heat_capacity())
occupant.bodytemperature = max(occupant.bodytemperature, air_contents.temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise
if(occupant.bodytemperature < T0C)
var/stun_time = (max(5 / efficiency, (1 / occupant.bodytemperature) * 2000 / efficiency)) STATUS_EFFECT_CONSTANT
occupant.Sleeping(stun_time)
var/heal_mod = air_contents.oxygen < 2 ? 0.2 : 1
occupant.adjustOxyLoss(-6 * heal_mod)
if(beaker && world.time >= last_injection + injection_cooldown)
// Take 1u from the beaker mix, react and inject 10x the amount
var/proportion = 10 * min(1 / beaker.volume, 1)
beaker.reagents.reaction(occupant, REAGENT_TOUCH, proportion)
beaker.reagents.trans_to(occupant, 1, 10)
last_injection = world.time
/obj/machinery/atmospherics/unary/cryo_cell/proc/heat_gas_contents()
if(air_contents.total_moles() < 1)
@@ -430,14 +427,14 @@
/obj/machinery/atmospherics/unary/cryo_cell/proc/go_out()
if(!occupant)
return
occupant.forceMove(get_step(loc, SOUTH)) //this doesn't account for walls or anything, but i don't forsee that being a problem.
if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected
occupant.bodytemperature = 261
occupant.forceMove(get_step(loc, SOUTH)) // Doesn't account for walls
if(occupant.bodytemperature < occupant.dna.species.cold_level_1) // Hacky fix for people taking burn damage after being ejected
occupant.bodytemperature = occupant.dna.species.cold_level_1
occupant = null
update_icon(UPDATE_OVERLAYS)
// eject trash the occupant dropped
for(var/atom/movable/A in contents - component_parts - list(beaker))
A.forceMove(get_step(loc, SOUTH))
/obj/machinery/atmospherics/unary/cryo_cell/force_eject_occupant(mob/target)
go_out()
@@ -515,7 +512,7 @@
to_chat(usr, "<span class='warning'>[usr] will not fit into [src] because [usr.p_they()] [usr.p_have()] a slime latched onto [usr.p_their()] head.</span>")
return
if(stat & (NOPOWER|BROKEN))
if(stat & (NOPOWER | BROKEN))
return
if(usr.incapacitated() || usr.buckled) //are you cuffed, dying, lying, stunned or other
+1 -1
View File
@@ -808,7 +808,7 @@
icon_state = "bombersyndie"
item_state = "bombersyndie"
ignore_suitadjust = FALSE
allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/gun, /obj/item/melee/classic_baton/telescopic/contractor, /obj/item/kitchen/knife/combat)
body_parts_covered = UPPER_TORSO | LOWER_TORSO | ARMS
cold_protection = UPPER_TORSO | LOWER_TORSO | ARMS
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
@@ -44,14 +44,16 @@
var/list/account_data = list(
"account_number" = account.account_number,
"owner_name" = account.account_name,
"suspended" = account.suspended ? "SUSPENDED" : "Active")
"suspended" = account.suspended ? "SUSPENDED" : "Active",
"money" = account.credit_balance)
data["accounts"] += list(account_data)
data["department_accounts"] = list()
for(var/datum/money_account/account as anything in GLOB.station_money_database.get_all_department_accounts())
var/list/account_data = list(
"account_number" = account.account_number,
"name" = account.account_name,
"suspended" = account.suspended ? "SUSPENDED" : "Active")
"suspended" = account.suspended ? "SUSPENDED" : "Active",
"money" = account.credit_balance)
data["department_accounts"] += list(account_data)
if(AUT_ACCINF)
data["account_number"] = detailed_account_view.account_number
+7
View File
@@ -222,6 +222,13 @@
cost = 750
category = MERCH_CAT_DECORATION
/datum/merch_item/flag_solgov
name = "SolGov Flag"
desc = "The banner of Trans-Solar Federation, allied government."
typepath = /obj/item/flag/solgov
cost = 750
category = MERCH_CAT_DECORATION
/datum/merch_item/banhammer
name = "Banhammer"
desc = "A Banhammer."
-1
View File
@@ -168,7 +168,6 @@ GLOBAL_LIST_EMPTY(event_last_fired)
// NON-BAY EVENTS
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Mass Hallucination", /datum/event/mass_hallucination, 300),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Brand Intelligence", /datum/event/brand_intelligence, 50, list(ASSIGNMENT_ENGINEER = 25), TRUE),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Space Dust", /datum/event/dust, 50, list(ASSIGNMENT_ENGINEER = 50)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Dimensional Tear", /datum/event/tear, 0, list(ASSIGNMENT_SECURITY = 35)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Honknomoly", /datum/event/tear/honk, 0),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Vent Clog", /datum/event/vent_clog, 250),
@@ -13,7 +13,7 @@
if(can_opened)
. += "<span class='notice'>It has been opened.</span>"
else
. += "<span class='info'>Alt-click to shake it up!</span>"
. += "<span class='info'>Ctrl-click to shake it up!</span>"
/obj/item/reagent_containers/food/drinks/cans/attack_self(mob/user)
if(can_opened)
@@ -41,14 +41,14 @@
qdel(src)
return crushed_can
/obj/item/reagent_containers/food/drinks/cans/AltClick(mob/user)
/obj/item/reagent_containers/food/drinks/cans/CtrlClick(mob/user)
var/mob/living/carbon/human/H
if(!can_shake || !ishuman(user))
return ..()
H = user
if(can_opened)
to_chat(H, "<span class='warning'>You can't shake up an already opened drink!")
return ..()
return
if(src == H.l_hand || src == H.r_hand)
can_shake = FALSE
addtimer(CALLBACK(src, PROC_REF(reset_shakable)), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
@@ -65,8 +65,7 @@
addtimer(CALLBACK(src, PROC_REF(reset_shaken)), 20 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_NO_HASH_WAIT)
handle_bursting(user)
else
to_chat(H, "<span class='warning'>You need to hold [src] in order to shake it.</span>")
return ..()
return ..()
/obj/item/reagent_containers/food/drinks/cans/attack(mob/M, mob/user, proximity)
if(!can_opened)
@@ -19,7 +19,9 @@
/obj/item/reagent_containers/food/snacks/icecream/update_overlays()
. = ..()
var/mutable_appearance/filling = mutable_appearance('icons/obj/kitchen.dmi', "icecream_color")
filling.color = mix_color_from_reagents(reagents.reagent_list)
var/list/reagent_colors = rgb2num(mix_color_from_reagents(reagents.reagent_list), COLORSPACE_HSV) //switching to HSV colorspace lets us easily manipulate the saturation and brightness independently
//Clamping the brightness keeps us from having greyish ice cream while still alowing for a range of colours
filling.color = rgb(reagent_colors[1], ((1.5 * reagent_colors[2]) - 10), (clamp(reagent_colors[3], 85, 100) - 10), space = COLORSPACE_HSV)
. += filling
/obj/item/reagent_containers/food/snacks/icecream/icecreamcone
@@ -30,12 +32,20 @@
bitesize = 3
list_reagents = list("nutriment" = 3, "sugar" = 7, "ice" = 2)
/obj/item/reagent_containers/food/snacks/icecream/wafflecone
name = "ice cream in a waffle cone"
desc = "Delicious ice cream."
icon_state = "icecream_cone_waffle"
volume = 50
bitesize = 3
list_reagents = list("nutriment" = 3, "sugar" = 7, "ice" = 2)
/obj/item/reagent_containers/food/snacks/icecream/icecreamcup
name = "chocolate ice cream cone"
desc = "Delicious ice cream."
icon_state = "icecream_cup"
icon_state = "icecream_cone_chocolate"
volume = 50
bitesize = 6
bitesize = 3
list_reagents = list("nutriment" = 5, "chocolate" = 8, "ice" = 2)
/obj/item/reagent_containers/food/snacks/icecreamsandwich
@@ -2,7 +2,7 @@
//Code made by Sawu at Sawu-Station.
/obj/machinery/icemachine
name = "cream-master deluxe"
name = "\improper Cream-Master Deluxe"
density = TRUE
anchored = TRUE
icon = 'icons/obj/kitchen.dmi'
@@ -15,8 +15,8 @@
/obj/machinery/icemachine/proc/generate_name(reagent_name)
var/name_prefix = pick("Mr.","Mrs.","Super","Happy","Whippy")
var/name_suffix = pick(" Whippy "," Slappy "," Creamy "," Dippy "," Swirly "," Swirl ")
var/name_prefix = pick("Mr.","Mrs.","Mx.","Dr.","Super","Happy","Whippy", "Sugary", "Sweet", "Lawful", "Chaotic", "Neutral", "Drippy","Sicknasty","Tubular","Radical")
var/name_suffix = pick(" Whippy "," Slappy "," Creamy "," Dippy "," Swirly "," Swirl ", " Rootin'Tootin "," Frosty ", " Chilly "," Neutral ", " Good ", " Evil ", " Smooth ", " Chunky ", " Flaming ")
var/cone_name = null //Heart failure prevention.
cone_name += name_prefix
cone_name += name_suffix
@@ -55,13 +55,13 @@
/obj/machinery/icemachine/proc/validexchange(reag)
if(reag == "sprinkles" | reag == "cola" | reag == "kahlua" | reag == "dr_gibb" | reag == "vodka" | reag == "space_up" | reag == "rum" | reag == "spacemountainwind" | reag == "gin" | reag == "cream" | reag == "water")
return 1
else
if(reagents.total_volume < 500)
to_chat(usr, "<span class='notice'>[src] vibrates for a moment, apparently accepting the unknown liquid.</span>")
playsound(loc, 'sound/machines/twobeep.ogg', 10, 1)
return 1
var/list/static/invalid_reagents = list("sprinkles", "cola", "kahlua", "dr_gibb", "vodka", "space-up", "rum", "spacemountainwind", "gin", "cream", "vanilla")
if(reag in invalid_reagents)
return
if(reagents.total_volume < 500)
to_chat(usr, "<span class='notice'>[src] vibrates for a moment, apparently accepting the unknown liquid.</span>")
playsound(loc, 'sound/machines/twobeep.ogg', 10, 1)
return TRUE
/obj/machinery/icemachine/Topic(href, href_list)
@@ -141,25 +141,18 @@
else
reagents.add_reagent("gin",5)
else if(ID == 4)
if(reagents.total_volume <= 500 & reagents.total_volume >= 15)
reagents.add_reagent("cream",(30 - reagents.total_volume))
else if(reagents.total_volume <= 15)
reagents.add_reagent("cream",(15 - reagents.total_volume))
reagents.add_reagent("cream", 5)
else if(ID == 5)
if(reagents.total_volume <= 500 & reagents.total_volume >= 15)
reagents.add_reagent("water",(30 - reagents.total_volume))
else if(reagents.total_volume <= 15)
reagents.add_reagent("water",(15 - reagents.total_volume))
reagents.add_reagent("vanilla", 5)
else if(href_list["createcup"])
else if(href_list["createchoco"])
var/name = generate_name(reagents.get_master_reagent_name())
name += " Chocolate Cone"
var/obj/item/reagent_containers/food/snacks/icecream/icecreamcup/C
C = new/obj/item/reagent_containers/food/snacks/icecream/icecreamcup(loc)
var/obj/item/reagent_containers/food/snacks/icecream/icecreamcup/C = new(loc)
C.name = "[name]"
C.pixel_x = rand(-8, 8)
C.pixel_y = -16
reagents.trans_to(C,30)
reagents.trans_to(C, 50)
if(reagents)
reagents.clear_reagents()
C.update_icon()
@@ -167,12 +160,23 @@
else if(href_list["createcone"])
var/name = generate_name(reagents.get_master_reagent_name())
name += " Cone"
var/obj/item/reagent_containers/food/snacks/icecream/icecreamcone/C
C = new/obj/item/reagent_containers/food/snacks/icecream/icecreamcone(loc)
var/obj/item/reagent_containers/food/snacks/icecream/icecreamcone/C = new(loc)
C.name = "[name]"
C.pixel_x = rand(-8, 8)
C.pixel_y = -16
reagents.trans_to(C,15)
reagents.trans_to(C, 30)
if(reagents)
reagents.clear_reagents()
C.update_icon()
else if(href_list["createwaffle"])
var/name = generate_name(reagents.get_master_reagent_name())
name += " Waffle Cone"
var/obj/item/reagent_containers/food/snacks/icecream/wafflecone/C = new(loc)
C.name = "[name]"
C.pixel_x = rand(-8, 8)
C.pixel_y = -16
reagents.trans_to(C, 20)
if(reagents)
reagents.clear_reagents()
C.update_icon()
@@ -192,10 +196,11 @@
dat += "<A href='?src=[UID()];synthcond=1;type=3'>Alcohol</A><BR>"
dat += "<strong>Finish With:</strong><BR>"
dat += "<A href='?src=[UID()];synthcond=1;type=4'>Cream</A><BR>"
dat += "<A href='?src=[UID()];synthcond=1;type=5'>Water</A><BR>"
dat += "<A href='?src=[UID()];synthcond=1;type=5'>Vanilla</A><BR>"
dat += "<strong>Dispense in:</strong><BR>"
dat += "<A href='?src=[UID()];createcup=1'>Chocolate Cone</A><BR>"
dat += "<A href='?src=[UID()];createchoco=1'>Chocolate Cone</A><BR>"
dat += "<A href='?src=[UID()];createcone=1'>Cone</A><BR>"
dat += "<A href='?src=[UID()];createwaffle=1'>Waffle Cone</A><BR>"
dat += "</center>"
return dat
@@ -23,7 +23,7 @@
name = "cocoa pod"
desc = "Fattening... Mmmmm... chucklate."
icon_state = "cocoapod"
filling_color = "#FFD700"
filling_color = "#5F3A13"
bitesize_mod = 2
tastes = list("cocoa" = 1)
@@ -44,6 +44,6 @@
name = "vanilla pod"
desc = "Fattening... Mmmmm... vanilla."
icon_state = "vanillapod"
filling_color = "#FFD700"
filling_color = "#FEFEFE"
tastes = list("vanilla" = 1)
distill_reagent = "vanilla" //Takes longer, but you can get even more vanilla from it.
@@ -11,7 +11,8 @@ emp_act
/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone)
if(!dna.species.bullet_act(P, src))
add_attack_logs(P.firer, src, "hit by [P.type] but got deflected by species '[dna.species]'")
return FALSE
P.reflect_back(src) //It has to be here, not on species. Why? Who knows. Testing showed me no reason why it doesn't work on species, and neither did tracing. It has to be here, or it gets qdel'd by bump.
return -1
if(P.is_reflectable(REFLECTABILITY_ENERGY))
var/can_reflect = check_reflect(def_zone)
var/reflected = FALSE
@@ -170,12 +170,12 @@ I use this to standardize shadowling dethrall code
return O.parent_organ
/mob/living/carbon/human/has_organic_damage()
var/odmg = 0
for(var/obj/item/organ/external/O in bodyparts)
if(O.is_robotic())
odmg += O.brute_dam
odmg += O.burn_dam
return (health < (100 - odmg))
var/robo_damage = 0
for(var/obj/item/organ/external/E in bodyparts)
if(E.is_robotic())
robo_damage += E.brute_dam
robo_damage += E.burn_dam
return health < maxHealth - robo_damage
/mob/living/carbon/human/proc/handle_splints() //proc that rebuilds the list of splints on this person, for ease of processing
splinted_limbs.Cut()
@@ -415,9 +415,7 @@
H.visible_message("<span class='danger'>[P] gets reflected by [H]'s glass skin!</span>", \
"<span class='userdanger'>[P] gets reflected by [H]'s glass skin!</span>")
P.reflect_back(H)
return FALSE
return FALSE //Reflect back must be handled on the human bullet act for some arcane reason
return TRUE
/datum/unarmed_attack/golem/glass
@@ -12,8 +12,7 @@
final_pixel_y = pixel_y
else //if(lying != 0)
if(lying_prev == 0) //Standing to lying
pixel_y = pixel_y
final_pixel_y = pixel_y + PIXEL_Y_OFFSET_LYING
final_pixel_y = PIXEL_Y_OFFSET_LYING
if(dir & (EAST|WEST)) //Facing east or west
final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
if(resize != RESIZE_DEFAULT_SIZE)
@@ -0,0 +1,60 @@
# AI Freelook
## Credits
Initial code credit for this goes to Uristqwerty.
Debugging, functionality, all comments and porting by Giacom.
## What is this?
This is a replacement for the current camera movement system, of the AI. Before
this, the AI had to move between cameras and could only see what the cameras
could see. Not only this but the cameras could see through walls, which created
problems. With this, the AI controls an "AI Eye" mob, which moves just like a
ghost; such as moving through walls and being invisible to players. The AI's eye
is set to this mob and then we use a system (explained below) to determine what
the cameras around the AI Eye can and cannot see. If the camera cannot see a
turf, it will black it out, otherwise it won't and the AI will be able to see
it. This creates several features, such as.. no more see-through-wall cameras,
easier to control camera movement, easier tracking, the AI only being able to
track mobs which are visible to a camera, only trackable mobs appearing on the
mob list and many more.
## How it Works
It works by first creating a camera network datum. Inside of this camera network
are "chunks" (which will be explained later) and "cameras". The cameras list is
kept up to date by obj/machinery/camera/New() and Destroy().
Next the camera network has chunks. These chunks are a 16x16 tile block of turfs
and cameras contained inside the chunk. These turfs are then sorted out based on
what the cameras can and cannot see. If none of the cameras can see the turf,
inside the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't
be able to see it.
## How it Updates
The camera network uses a streaming method in order to effeciently update
chunks. Since the server will have doors opening, doors closing, turf being
destroyed and other lag inducing stuff, we want to update it under certain
conditions and not every tick.
The chunks are not created straight away, only when an AI eye moves into it's
area is when it gets created. One a chunk is created, when a non glass door
opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is
looking in the area. We do this with the "seenby" list, which updates everytime
an AI is near a chunk. If there is an AI eye inside the area, we update the
chunk that the changed atom is inside and all surrounding chunks, since a
camera's vision could leak onto another chunk. If there is no AI Eye, we instead
flag the chunk to update whenever it is loaded by an AI Eye. This is basically
how the chunks update and keep it in sync. We then add some lag reducing
measures, such as an UPDATE_BUFFER which stops a chunk from updating too many
times in a certain time-frame, only updating if the changed atom was blocking
sight; for example, we don't update glass airlocks or floors.
## Where is Everything?
- `cameranet.dm`: Everything about the cameranet datum.
- `chunk.dm`: Everything about the chunk datum.
- `eye.dm`: Everything about the AI and the AIEye.
- `updating.dm`: Everything about triggers that will update chunks.
@@ -1,51 +0,0 @@
// CREDITS
/*
Initial code credit for this goes to Uristqwerty.
Debugging, functionality, all comments and porting by Giacom.
Everything about freelook (or what we can put in here) will be stored here.
WHAT IS THIS?
This is a replacement for the current camera movement system, of the AI. Before this, the AI had to move between cameras and could
only see what the cameras could see. Not only this but the cameras could see through walls, which created problems.
With this, the AI controls an "AI Eye" mob, which moves just like a ghost; such as moving through walls and being invisible to players.
The AI's eye is set to this mob and then we use a system (explained below) to determine what the cameras around the AI Eye can and
cannot see. If the camera cannot see a turf, it will black it out, otherwise it won't and the AI will be able to see it.
This creates several features, such as.. no more see-through-wall cameras, easier to control camera movement, easier tracking,
the AI only being able to track mobs which are visible to a camera, only trackable mobs appearing on the mob list and many more.
HOW IT WORKS
It works by first creating a camera network datum. Inside of this camera network are "chunks" (which will be
explained later) and "cameras". The cameras list is kept up to date by obj/machinery/camera/New() and Destroy().
Next the camera network has chunks. These chunks are a 16x16 tile block of turfs and cameras contained inside the chunk.
These turfs are then sorted out based on what the cameras can and cannot see. If none of the cameras can see the turf, inside
the 16x16 block, it is listed as an "obscured" turf. Meaning the AI won't be able to see it.
HOW IT UPDATES
The camera network uses a streaming method in order to effeciently update chunks. Since the server will have doors opening, doors closing,
turf being destroyed and other lag inducing stuff, we want to update it under certain conditions and not every tick.
The chunks are not created straight away, only when an AI eye moves into it's area is when it gets created.
One a chunk is created, when a non glass door opens/closes or an opacity turf is destroyed, we check to see if an AI Eye is looking in the area.
We do this with the "seenby" list, which updates everytime an AI is near a chunk. If there is an AI eye inside the area, we update the chunk
that the changed atom is inside and all surrounding chunks, since a camera's vision could leak onto another chunk. If there is no AI Eye, we instead
flag the chunk to update whenever it is loaded by an AI Eye. This is basically how the chunks update and keep it in sync. We then add some lag reducing
measures, such as an UPDATE_BUFFER which stops a chunk from updating too many times in a certain time-frame, only updating if the changed atom was blocking
sight; for example, we don't update glass airlocks or floors.
WHERE IS EVERYTHING?
cameranet.dm = Everything about the cameranet datum.
chunk.dm = Everything about the chunk datum.
eye.dm = Everything about the AI and the AIEye.
updating.dm = Everything about triggers that will update chunks.
*/
@@ -363,13 +363,15 @@ Difficulty: Hard
var/obj/effect/anomaly/bluespace/A = new(spot, 150, FALSE)
A.mass_teleporting = FALSE
if(GRAV)
new /obj/effect/anomaly/grav(spot, 150, FALSE)
var/obj/effect/anomaly/grav/A = new(spot, 150, FALSE, FALSE)
A.knockdown = TRUE
if(PYRO)
var/obj/effect/anomaly/pyro/A = new(spot, 150, FALSE)
A.produces_slime = FALSE
if(FLUX)
var/obj/effect/anomaly/flux/A = new(spot, 150, FALSE)
A.explosive = FALSE
A.knockdown = TRUE
if(VORTEX)
new /obj/effect/anomaly/bhole(spot, 150, FALSE)
anomalies++
@@ -516,6 +518,9 @@ Difficulty: Hard
beam.forceMove(get_turf(src))
return ..()
/mob/living/simple_animal/hostile/megafauna/ancient_robot/mob_negates_gravity() //No more being thrown around like a spastic child by grav anomalies
return TRUE
/mob/living/simple_animal/hostile/ancient_robot_leg
name = "leg"
desc = "Legs with a mounted turret, for shooting and crushing small miners like you."
@@ -526,6 +531,7 @@ Difficulty: Hard
faction = list("mining", "boss") // No attacking your leg
weather_immunities = list("lava","ash")
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
flying = TRUE
check_friendly_fire = 1
ranged = TRUE
@@ -548,9 +554,9 @@ Difficulty: Hard
stat_attack = DEAD
var/range = 3
var/mob/living/simple_animal/hostile/megafauna/ancient_robot/core = null
var/fake_max_hp = 400
var/fake_hp = 400
var/fake_hp_regen = 10
var/fake_max_hp = 300
var/fake_hp = 300
var/fake_hp_regen = 2
var/transfer_rate = 0.75
var/who_am_i = null
var/datum/beam/leg_part
@@ -608,7 +614,7 @@ Difficulty: Hard
if(regen)
fake_hp = min(fake_hp + fake_hp_regen, fake_max_hp)
transfer_rate = 0.75 * (fake_hp/fake_max_hp)
if(fake_hp >= 300 && !ranged)
if(fake_hp >= 250 && !ranged)
ranged = TRUE
visible_message("<span class='danger'>[src]'s turret pops out of it!</span>")
if(get_dist(get_turf(core),get_turf(src)) <= range)
@@ -665,6 +671,9 @@ Difficulty: Hard
/mob/living/simple_animal/hostile/ancient_robot_leg/Moved(atom/OldLoc, Dir, Forced = FALSE)
playsound(src, 'sound/effects/meteorimpact.ogg', 60, TRUE, 2, TRUE) //turned way down from bubblegum levels due to 4 legs
/mob/living/simple_animal/hostile/ancient_robot_leg/mob_negates_gravity()
return TRUE
/obj/item/projectile/ancient_robot_bullet
damage = 8
damage_type = BRUTE
@@ -7,6 +7,7 @@ GLOBAL_VAR_INIT(ts_death_window, 9000) // 15 minutes
GLOBAL_LIST_EMPTY(ts_spiderlist)
GLOBAL_LIST_EMPTY(ts_egg_list)
GLOBAL_LIST_EMPTY(ts_spiderling_list)
GLOBAL_LIST_EMPTY(ts_infected_list)
// --------------------------------------------------------------------------------
// --------------------- TERROR SPIDERS: DEFAULTS ---------------------------------
@@ -53,6 +53,26 @@
return 1
return 0
/obj/item/organ/internal/body_egg/terror_eggs/Initialize(mapload)
. = ..()
GLOB.ts_infected_list += src
/obj/item/organ/internal/body_egg/terror_eggs/insert(mob/living/carbon/M, special)
. = ..()
RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(readd_infected))
/obj/item/organ/internal/body_egg/terror_eggs/proc/readd_infected(mob/infected, new_stat, old_stat)
SIGNAL_HANDLER
if(new_stat != DEAD)
GLOB.ts_infected_list |= src
/obj/item/organ/internal/body_egg/terror_eggs/Destroy()
GLOB.ts_infected_list -= src
return ..()
/obj/item/organ/internal/body_egg/terror_eggs/on_owner_death()
GLOB.ts_infected_list -= src
return ..()
/obj/structure/spider/terrorweb/white
name = "infested web"
+1
View File
@@ -12,6 +12,7 @@
add_attack_logs(src, null, "Fallen unconscious", ATKLOG_ALL)
log_game("[key_name(src)] fell unconscious at [atom_loc_line(get_turf(src))]")
set_stat(UNCONSCIOUS)
ADD_TRAIT(src, TRAIT_DEAF, STAT_TRAIT)
ADD_TRAIT(src, TRAIT_FLOORED, STAT_TRAIT)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, STAT_TRAIT)
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, STAT_TRAIT)
+9 -6
View File
@@ -50,7 +50,7 @@
if(S.glass_type == /obj/item/stack/sheet/rglass) //if the panel is in reinforced glass
max_integrity *= 2 //this need to be placed here, because panels already on the map don't have an assembly linked to
obj_integrity = max_integrity
update_icon()
update_icon(UPDATE_OVERLAYS)
/obj/machinery/power/solar/crowbar_act(mob/user, obj/item/I)
@@ -78,7 +78,7 @@
playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE)
stat |= BROKEN
unset_control()
update_icon()
update_icon(UPDATE_OVERLAYS)
/obj/machinery/power/solar/deconstruct(disassembled = TRUE)
if(!(flags & NODECONSTRUCT))
@@ -98,8 +98,11 @@
if(stat & BROKEN)
. += image('icons/goonstation/objects/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER)
else
. += image('icons/goonstation/objects/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER)
set_angle(adir)
var/image/panel = image('icons/goonstation/objects/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER)
var/matrix/M = matrix()
M.Turn(adir)
panel.transform = M
. += panel
//calculates the fraction of the sunlight that the panel recieves
/obj/machinery/power/solar/proc/update_solar_exposure()
@@ -137,7 +140,7 @@
. = (!(stat & BROKEN))
stat |= BROKEN
unset_control()
update_icon()
update_icon(UPDATE_OVERLAYS)
/obj/machinery/power/solar/fake/New(turf/loc, obj/item/solar_assembly/S)
..(loc, S, 0)
@@ -488,7 +491,7 @@
for(var/obj/machinery/power/solar/S in connected_panels)
S.adir = cdir //instantly rotates the panel
S.occlusion()//and
S.update_icon() //update it
S.update_icon(UPDATE_OVERLAYS) //update it
update_icon()
@@ -886,7 +886,7 @@
var/obj/effect/anomaly/flux/A = new(L, 300, FALSE)
A.explosive = FALSE
if(GRAVITATIONAL_ANOMALY)
new /obj/effect/anomaly/grav(L, 250, FALSE)
new /obj/effect/anomaly/grav(L, 250, FALSE, FALSE)
if(PYRO_ANOMALY)
new /obj/effect/anomaly/pyro(L, 200, FALSE)
+1 -1
View File
@@ -14,7 +14,7 @@
ammo_type = /obj/item/ammo_casing/a357
max_ammo = 7
multi_sprite_step = 1
icon_state = "357OLD"
icon_state = "357_box"
/obj/item/ammo_box/c9mm
name = "ammo box (9mm)"
@@ -116,6 +116,7 @@
desc = "An outdated personal defense weapon utilized by law enforcement. The WT-550 Automatic Rifle fires 4.6x30mm rounds."
icon_state = "wt550"
item_state = "wt550"
w_class = WEIGHT_CLASS_BULKY
mag_type = /obj/item/ammo_box/magazine/wt550m9
fire_sound = 'sound/weapons/gunshots/gunshot_rifle.ogg'
magin_sound = 'sound/weapons/gun_interactions/batrifle_magin.ogg'
@@ -210,6 +210,13 @@
sharp = TRUE
impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser
/obj/item/projectile/plasma/prehit(atom/target)
. = ..()
if(!lavaland_equipment_pressure_check(get_turf(target)))
name = "weakened [name]"
dismemberment = 0
sharp = FALSE
/obj/item/projectile/plasma/on_hit(atom/target)
. = ..()
if(ismineralturf(target))
+107 -3
View File
@@ -1,11 +1,31 @@
#define ADDICTION_TIME 4800 //8 minutes
///////////////////////////////////////////////////////////////////////////////////
/**
* # Reagents Holder
*
* The holder is the datum that holds a list of all reagents
* currently in the object.
*
* By default, all atom have an empty reagents var. If you want to use
* an object for the chemistry system you'll need to add something like this in
* its new proc:
*
* // Create a new datum, 100 is the maximum_volume of the new holder datum.
* var/datum/reagents/R = new/datum/reagents(100)
* reagents = R // Assign the new datum to the objects reagents var
* R.my_atom = src // set the holders my_atom to src so that we know where we are.
*
* This can also be done by calling a convenience proc e.g.
* /atom/proc/create_reagents(max_volume)
*/
/datum/reagents
/// All contained reagents. More specifically, references to the reagent datums.
var/list/datum/reagent/reagent_list = new/list()
/// The total volume of all reagents in this holder.
var/total_volume = 0
/// This is the maximum volume of the holder.
var/maximum_volume = 100
/// This is the atom the holder is 'in'. Useful if you need to find the location. (i.e. for explosions)
var/atom/my_atom = null
var/chem_temp = T20C
var/temperature_min = 0
@@ -53,6 +73,11 @@
GLOB.chemical_reactions_list[id] += D
break // Don't bother adding ourselves to other reagent ids, it is redundant.
/**
* Removes reagents from the holder until the passed amount is matched.
*
* It'll try to remove some of ALL reagents contained.
*/
/datum/reagents/proc/remove_any(amount = 1)
var/list/cached_reagents = reagent_list
var/total_transfered = 0
@@ -124,7 +149,17 @@
return the_id
/datum/reagents/proc/trans_to(target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE) //if preserve_data=0, the reagents data will be lost. Usefull if you use data for some strange stuff and don't want it to be transferred.
/**
* Equally transfer the contents of the holder to another objects holder.
*
* You need to pass it the object (not the holder) you want to transfer to and
* the amount you want to transfer. Its return value is the actual amount
* transfered (if one of the objects is full/empty).
*
* If `preserve_data = FALSE`, the reagents data will be lost. Useful if you use
* data for some strange stuff and don't want it to be transferred.
*/
/datum/reagents/proc/trans_to(target, amount = 1, multiplier = 1, preserve_data = TRUE, no_react = FALSE)
if(!target)
return
if(total_volume <= 0)
@@ -212,6 +247,12 @@
handle_reactions()
/**
* Same as [/datum/reagents/proc/trans_to] but only for a specific reagent in
* the reagent list. If the specified amount is greater than what is available,
* it will use the amount of the reagent that is available. If no reagent
* exists, returns null.
*/
/datum/reagents/proc/trans_id_to(obj/target, reagent, amount = 1, preserve_data = TRUE) //Not sure why this proc didn't exist before. It does now! /N
if(!target)
return
@@ -248,6 +289,9 @@
if((R.process_flags & ORGANIC) && (R.process_flags & SYNTHETIC) && (H.dna.species.reagent_tag & PROCESS_DUO))
return TRUE
/**
* Called by `/mob/living/proc/Life`. You shouldn't have to use this one directly.
*/
/datum/reagents/proc/metabolize(mob/living/M)
if(M)
temperature_reagents(M.bodytemperature - 30)
@@ -346,6 +390,9 @@
var/datum/reagent/R = A
R.on_mob_death(M)
/**
* Returns a list of all the chemical IDs in the reagent holder that are overdosing.
*/
/datum/reagents/proc/overdose_list()
var/od_chems[0]
for(var/A in reagent_list)
@@ -372,6 +419,14 @@
R.on_update(A)
update_total()
/**
* Check all recipes and, on a match, uses them.
*
* It will also call the recipe's on_reaction proc (for explosions or w/e).
* Currently, this proc is automatically called by [/datum/reagents/proc/trans_to].
* Modified from the original to preserve reagent data across reactions
* (originally for xenoarchaeology).
*/
/datum/reagents/proc/handle_reactions()
if(flags & REAGENT_NOREACT)
return //Yup, no reactions here. No siree.
@@ -468,6 +523,9 @@
update_total()
return FALSE
/**
* Remove all reagents but the specified one.
*/
/datum/reagents/proc/isolate_reagent(reagent)
for(var/A in reagent_list)
var/datum/reagent/R = A
@@ -475,6 +533,9 @@
del_reagent(R.id)
update_total()
/**
* Completely remove the reagent with the matching ID.
*/
/datum/reagents/proc/del_reagent(reagent)
var/list/cached_reagents = reagent_list
for(var/A in cached_reagents)
@@ -491,6 +552,9 @@
return FALSE
return TRUE
/**
* Update the total volume of the holder (the volume of all reagents added together).
*/
/datum/reagents/proc/update_total()
total_volume = 0
for(var/A in reagent_list)
@@ -501,6 +565,9 @@
total_volume += R.volume
return FALSE
/**
* Remove all reagents from the holder.
*/
/datum/reagents/proc/clear_reagents()
for(var/A in reagent_list)
var/datum/reagent/R = A
@@ -526,6 +593,22 @@
can_process = TRUE
return can_process
/**
* Calls the appropriate reaction procs of the reagents.
*
* I.e. if A is an object, it will call the reagent's reaction_obj
* proc. The method var is used for reaction on mobs. It simply tells
* us if the mob TOUCHed the reagent or if it INGESTed the reagent.
*
* Since the volume can be checked in a reagents proc, you might want to
* use the volume_modifier var to modifiy the passed value without actually
* changing the volume of the reagents.
*
* If you're not sure if you need to use this the answer is very most likely 'No'.
*
* You'll want to use this proc whenever an atom first comes in contact
* with the reagents of a holder. (in the 'splash' part of a beaker i.e.)
*/
/datum/reagents/proc/reaction(atom/A, method = REAGENT_TOUCH, volume_modifier = 1, show_message = TRUE)
var/react_type
if(isliving(A))
@@ -583,6 +666,11 @@
var/amt = list_reagents[r_id]
add_reagent(r_id, amt, data)
/**
* Attempts to add X of the matching reagent to the holder.
*
* You won't use this much. Mostly in new procs for pre-filled objects.
*/
/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = T20C, no_react = FALSE)
if(!isnum(amount))
return TRUE
@@ -637,6 +725,12 @@
add_reagent(reagent, add)
return TRUE
/**
* The exact opposite of the add_reagent proc.
*
* Modified from original to return the reagent's data, in order to preserve
* reagent data across reactions (originally for xenoarchaeology).
*/
/datum/reagents/proc/remove_reagent(reagent, amount, safety) //Added a safety check for the trans_id_to
if(!isnum(amount))
return TRUE
@@ -653,6 +747,11 @@
return FALSE
return TRUE
/**
* Return whether the holder contains the reagent.
*
* If you pass it an amount it will additionally check if the amount is matched.
*/
/datum/reagents/proc/has_reagent(reagent, amount = -1)
for(var/A in reagent_list)
var/datum/reagent/R = A
@@ -666,6 +765,11 @@
return FALSE
return FALSE
/**
* Returns the amount of the matching reagent inside the holder.
*
* Returns FALSE if the reagent is missing.
*/
/datum/reagents/proc/get_reagent_amount(reagent)
for(var/A in reagent_list)
var/datum/reagent/R = A
-241
View File
@@ -1,241 +0,0 @@
/*
NOTE: IF YOU UPDATE THE REAGENT-SYSTEM, ALSO UPDATE THIS README.
Structure: /////////////////// //////////////////////////
// Mob or object // -------> // Reagents var (datum) // Is a reference to the datum that holds the reagents.
/////////////////// //////////////////////////
| |
The object that holds everything. V
reagent_list var (list) A List of datums, each datum is a reagent.
| | |
V V V
reagents (datums) Reagents. I.e. Water , antitoxins or mercury.
Random important notes:
An objects on_reagent_change will be called every time the objects reagents change.
Useful if you want to update the objects icon etc.
About the Holder:
The holder (reagents datum) is the datum that holds a list of all reagents
currently in the object.It also has all the procs needed to manipulate reagents
remove_any(amount)
This proc removes reagents from the holder until the passed amount
is matched. It'll try to remove some of ALL reagents contained.
trans_to(obj/target, amount)
This proc equally transfers the contents of the holder to another
objects holder. You need to pass it the object (not the holder) you want
to transfer to and the amount you want to transfer. Its return value is the
actual amount transfered (if one of the objects is full/empty)
trans_id_to(obj/target, reagent, amount)
Same as above but only for a specific reagent in the reagent list.
If the specified amount is greater than what is available, it will use
the amount of the reagent that is available. If no reagent exists, returns null.
metabolize(mob/living/M)
This proc is called by the mobs life proc. It simply calls on_mob_life for
all contained reagents. You shouldnt have to use this one directly.
handle_reactions()
This proc check all recipes and, on a match, uses them.
It will also call the recipe's on_reaction proc (for explosions or w/e).
Currently, this proc is automatically called by trans_to.
- Modified from the original to preserve reagent data across reactions (originally for xenoarchaeology)
isolate_reagent(reagent)
Pass it a reagent id and it will remove all reagents but that one.
It's that simple.
del_reagent(reagent)
Completely remove the reagent with the matching id.
update_total()
This one simply updates the total volume of the holder.
(the volume of all reagents added together)
clear_reagents()
This proc removes ALL reagents from the holder.
reaction(atom/A, method=TOUCH, volume_modifier=0)
This proc calls the appropriate reaction procs of the reagents.
I.e. if A is an object, it will call the reagents reaction_obj
proc. The method var is used for reaction on mobs. It simply tells
us if the mob TOUCHed the reagent or if it INGESTed the reagent.
Since the volume can be checked in a reagents proc, you might want to
use the volume_modifier var to modifiy the passed value without actually
changing the volume of the reagents.
If you're not sure if you need to use this the answer is very most likely 'No'.
You'll want to use this proc whenever an atom first comes in
contact with the reagents of a holder. (in the 'splash' part of a beaker i.e.)
More on the reaction in the reagent part of this readme.
add_reagent(reagent, amount, data)
Attempts to add X of the matching reagent to the holder.
You wont use this much. Mostly in new procs for pre-filled
objects.
remove_reagent(reagent, amount)
The exact opposite of the add_reagent proc.
- Modified from original to return the reagent's data, in order to preserve reagent data across reactions (originally for xenoarchaeology)
has_reagent(reagent, amount)
Returns 1 if the holder contains this reagent.
Or 0 if not.
If you pass it an amount it will additionally check
if the amount is matched. This is optional.
get_reagent_amount(reagent)
Returns the amount of the matching reagent inside the
holder. Returns 0 if the reagent is missing.
overdose_list()
Returns a list of all the chemical IDs in the reagent holder that are overdosing
Important variables:
total_volume
This variable contains the total volume of all reagents in this holder.
reagent_list
This is a list of all contained reagents. More specifically, references
to the reagent datums.
maximum_volume
This is the maximum volume of the holder.
my_atom
This is the atom the holder is 'in'. Useful if you need to find the location.
(i.e. for explosions)
About Reagents:
Reagents are all the things you can mix and fille in bottles etc. This can be anything from
rejuvs over water to ... iron. Each reagent also has a few procs - i'll explain those below.
reaction_mob(mob/living/M, method=TOUCH)
This is called by the holder's reation proc.
This version is only called when the reagent
reacts with a mob. The method var can be either
TOUCH or INGEST. You'll want to put stuff like
acid-facemelting in here. Should only ever be
called, directly, on living mobs.
reaction_obj(obj/O)
This is called by the holder's reation proc.
This version is called when the reagents reacts
with an object. You'll want to put stuff like
object melting in here ... or something. i dunno.
reaction_turf(turf/T)
This is called by the holder's reation proc.
This version is called when the reagents reacts
with a turf. You'll want to put stuff like extra
slippery floors for lube or something in here.
on_mob_life(mob/living/M)
This proc is called everytime the mobs life proc executes.
This is the place where you put damage for toxins ,
drowsyness for sleep toxins etc etc.
You'll want to call the parents proc by using ..() .
If you dont, the chemical will stay in the mob forever -
unless you write your own piece of code to slowly remove it.
(Should be pretty easy, 1 line of code)
Important variables:
holder
This variable contains a reference to the holder the chemical is 'in'
volume
This is the volume of the reagent.
id
The id of the reagent
name
The name of the reagent.
data
This var can be used for whatever the fuck you want.You could use this
for DNA in a blood reagent or ... well whatever you want.
color
This is a hexadecimal color that represents the reagent outside of containers,
you define it as "#RRGGBB", or, red green blue. You can also define it using the
rgb() proc, which returns a hexadecimal value too. The color is black by default.
A good website for color calculations: http://www.psyclops.com/tools/rgb/
About Recipes:
Recipes are simple datums that contain a list of required reagents and a result.
They also have a proc that is called when the recipe is matched.
on_reaction(datum/reagents/holder, created_volume)
This proc is called when the recipe is matched.
You'll want to add explosions etc here.
To find the location you'll have to do something
like get_turf(holder.my_atom)
name & id
Should be pretty obvious.
result
This var contains the id of the resulting reagent.
required_reagents
This is a list of ids of the required reagents.
Each id also needs an associated value that gives us the minimum required amount
of that reagent. The handle_reaction proc can detect mutiples of the same recipes
so for most cases you want to set the required amount to 1.
required_catalysts (Added May 2011)
This is a list of the ids of the required catalysts.
Functionally similar to required_reagents, it is a list of reagents that are required
for the reaction. However, unlike required_reagents, catalysts are NOT consumed.
They mearly have to be present in the container.
result_amount
This is the amount of the resulting reagent this recipe will produce.
I recommend you set this to the total volume of all required reagent.
required_container
The container the recipe has to take place in in order to happen. Leave this blank/null
if you want the reaction to happen anywhere.
required_other
Basically like a reagent's data variable. You can set extra requirements for a
reaction with this.
About the Tools:
By default, all atom have a reagents var - but its empty. if you want to use an object for the chem.
system you'll need to add something like this in its new proc:
var/datum/reagents/R = new/datum/reagents(100), <<< create a new datum, 100 is the maximum_volume of the new holder datum.
reagents = R, <<< assign the new datum to the objects reagents var
R.my_atom = src, <<< set the holders my_atom to src so that we know where we are.
This can also be done by calling a convenience proc:
atom/proc/create_reagents(max_volume)
Other important stuff:
amount_per_transfer_from_this var
This var is mostly used by beakers and bottles.
It simply tells us how much to transfer when
'pouring' our reagents into something else.
*/
+19 -1
View File
@@ -2,11 +2,13 @@
var/name = "Reagent"
var/id = "reagent"
var/description = ""
/// A reference to the holder the chemical is 'in'.
var/datum/reagents/holder = null
var/reagent_state = SOLID
var/list/data = null
var/volume = 0
var/metabolization_rate = REAGENTS_METABOLISM
/// The color of the agent outside of containers.
var/color = "#000000" // rgb: 0, 0, 0 (does not support alpha channels - yet!)
var/shock_reduction = 0
var/heart_rate_increase = 0
@@ -45,7 +47,15 @@
/datum/reagent/proc/reaction_temperature(exposed_temperature, exposed_volume) //By default we do nothing.
return
/datum/reagent/proc/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume, show_message = TRUE) //Some reagents transfer on touch, others don't; dependent on if they penetrate the skin or not.
/**
* React with a mob.
*
* The method var can be either `REAGENT_TOUCH` or `REAGENT_INGEST`. Some
* reagents transfer on touch, others don't; dependent on if they penetrate the
* skin or not. You'll want to put stuff like acid-facemelting in here. Should
* only ever be called, directly, on living mobs.
*/
/datum/reagent/proc/reaction_mob(mob/living/M, method = REAGENT_TOUCH, volume, show_message = TRUE)
if(!holder) //for catching rare runtimes
return
if(method == REAGENT_TOUCH && penetrates_skin)
@@ -73,9 +83,17 @@
// This one only matters if the mob is dead.
M.absorb_blood()
/**
* React with an object.
*/
/datum/reagent/proc/reaction_obj(obj/O, volume)
return
/**
* React with a turf.
*
* You'll want to put stuff like extra slippery floors for lube or something in here.
*/
/datum/reagent/proc/reaction_turf(turf/T, volume, color)
return
@@ -18,7 +18,7 @@
name = "Tomato Juice"
id = "tomatojuice"
description = "Tomatoes made into juice. What a waste of big, juicy tomatoes, huh?"
color = "#731008" // rgb: 115, 16, 8
color = "#C00609"
drink_icon = "glass_red"
drink_name = "Glass of Tomato juice"
drink_desc = "Are you sure this is tomato juice?"
@@ -44,7 +44,7 @@
name = "Lime Juice"
id = "limejuice"
description = "The sweet-sour juice of limes."
color = "#365E30" // rgb: 54, 94, 48
color = "#68E735"
drink_icon = "glass_green"
drink_name = "Glass of Lime juice"
drink_desc = "A glass of sweet-sour lime juice."
@@ -60,7 +60,7 @@
name = "Carrot juice"
id = "carrotjuice"
description = "Just like a carrot, but without the crunching."
color = "#973800" // rgb: 151, 56, 0
color = "#FFA500"
drink_icon = "carrotjuice"
drink_name = "Glass of carrot juice"
drink_desc = "Just like a carrot, but without the crunching."
@@ -100,7 +100,7 @@
id = "triple_citrus"
description = "A refreshing mixed drink of orange, lemon and lime juice."
reagent_state = LIQUID
color = "#23A046"
color = "#B5FF00"
drink_icon = "triplecitrus"
drink_name = "Glass of Triplecitrus Juice"
drink_desc = "As colorful and healthy as it is delicious."
@@ -114,7 +114,7 @@
name = "Berry Juice"
id = "berryjuice"
description = "A delicious blend of several different kinds of berries."
color = "#863333" // rgb: 134, 51, 51
color = "#B23A4E"
drink_icon = "berryjuice"
drink_name = "Glass of berry juice"
drink_desc = "Berry juice. Or maybe it's jam. Who cares?"
@@ -124,7 +124,7 @@
name = "Poison Berry Juice"
id = "poisonberryjuice"
description = "A tasty juice blended from various kinds of very deadly and toxic berries."
color = "#863353" // rgb: 134, 51, 83
color = "#B23A4E"
drink_icon = "poisonberryjuice"
drink_name = "Glass of poison berry juice"
drink_desc = "A glass of deadly juice."
@@ -139,7 +139,7 @@
name = "Apple Juice"
id = "applejuice"
description = "The sweet juice of an apple, fit for all ages."
color = "#ECFF56" // rgb: 236, 255, 86
color = "#FBF969"
drink_name = "Apple Juice"
drink_desc = "Apple juice. Maybe it would have been better in a pie..."
taste_description = "apple juice"
@@ -157,7 +157,7 @@
name = "Lemon Juice"
id = "lemonjuice"
description = "This juice is VERY sour."
color = "#863333" // rgb: 175, 175, 0
color = "#E5F249"
drink_icon = "lemonglass"
drink_name = "Glass of lemonjuice"
drink_desc = "Sour..."
@@ -176,7 +176,7 @@
name = "Banana Juice"
id = "banana"
description = "The raw essence of a banana."
color = "#863333" // rgb: 175, 175, 0
color = "#F6F834"
drink_icon = "banana"
drink_name = "Glass of banana juice"
drink_desc = "The raw essence of a banana. HONK"
@@ -220,7 +220,7 @@
name = "Milk"
id = "milk"
description = "An opaque white liquid produced by the mammary glands of mammals."
color = "#DFDFDF" // rgb: 223, 223, 223
color = "#F1F1F1"
drink_icon = "glass_white"
drink_name = "Glass of milk"
drink_desc = "White and nutritious goodness!"
@@ -238,7 +238,7 @@
name = "Soy Milk"
id = "soymilk"
description = "An opaque white liquid made from soybeans."
color = "#DFDFC7" // rgb: 223, 223, 199
color = "#DCD3AF"
drink_name = "Glass of soy milk"
drink_desc = "White and nutritious soy goodness!"
taste_description = "fake milk"
@@ -246,8 +246,8 @@
/datum/reagent/consumable/drink/milk/cream
name = "Cream"
id = "cream"
description = "The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh?"
color = "#DFD7AF" // rgb: 223, 215, 175
description = "The fatty, still liquid part of milk. Why don't you mix this with some scotch, eh?"
color = "#F1F1F1"
drink_name = "Glass of cream"
drink_desc = "Ewwww..."
taste_description = "cream"
@@ -256,7 +256,7 @@
name = "Chocolate milk"
id ="chocolate_milk"
description = "Chocolate-flavored milk, tastes like being a kid again."
color = "#85432C"
color = "#652109"
drink_name = "Chocolate milk"
drink_desc = "Smells like childhood. What would they need to add to make it taste like childhood too?"
taste_description = "chocolate milk"
@@ -266,7 +266,7 @@
id = "hot_coco"
description = "Made with love! And coco beans."
nutriment_factor = 3 * REAGENTS_METABOLISM
color = "#403010" // rgb: 64, 48, 16
color = "#401101"
drink_icon = "hot_coco"
drink_name = "Glass of hot coco"
drink_desc = "Delicious and cozy."
@@ -307,7 +307,7 @@
name = "Iced Coffee"
id = "icecoffee"
description = "Coffee and ice, refreshing and cool."
color = "#102838" // rgb: 16, 40, 56
color = "#33250A"
drink_icon = "icedcoffeeglass"
drink_name = "Iced Coffee"
drink_desc = "A drink to perk you up and refresh you!"
@@ -317,12 +317,12 @@
name = "Soy Latte"
id = "soy_latte"
description = "A nice and tasty beverage while you are reading your hippie books."
color = "#664300" // rgb: 102, 67, 0
color = "#8A6723"
adj_sleepy = 0
drink_icon = "soy_latte"
drink_name = "Soy Latte"
drink_desc = "A nice and refrshing beverage while you are reading."
taste_description = "fake milky coffee"
taste_description = "milkish coffee"
/datum/reagent/consumable/drink/coffee/soy_latte/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
@@ -335,7 +335,7 @@
name = "Cafe Latte"
id = "cafe_latte"
description = "A nice, strong and tasty beverage while you are reading."
color = "#664300" // rgb: 102, 67, 0
color = "#7A5C21"
adj_sleepy = 0
drink_icon = "cafe_latte"
drink_name = "Cafe Latte"
@@ -353,7 +353,7 @@
name = "Cafe Mocha"
id = "cafe_mocha"
description = "The perfect blend of coffee, milk, and chocolate."
color = "#673629"
color = "#3E2603"
drink_name = "Cafe Mocha"
drink_desc = "The perfect blend of coffee, milk, and chocolate."
taste_description = "chocolatey coffee"
@@ -633,7 +633,7 @@
name = "Space-Up"
id = "space_up"
description = "Tastes like a hull breach in your mouth."
color = "#202800" // rgb: 32, 40, 0
color = "#C7DF67"
drink_icon = "space-up_glass"
drink_name = "Glass of Space-up"
drink_desc = "Space-up. It helps keep your cool."
@@ -643,7 +643,7 @@
name = "Lemon Lime"
description = "A tangy substance made of 0.5% natural citrus!"
id = "lemon_lime"
color = "#878F00" // rgb: 135, 40, 0
color = "#BEC80F"
taste_description = "citrus soda"
/datum/reagent/consumable/drink/lemonade
@@ -731,7 +731,7 @@
return ..()
// imitate alcohol effects using current cycle
M.AdjustDrunk(alcohol_perc STATUS_EFFECT_CONSTANT)
M.AdjustDizzy(dizzy_adj, bound_upper = 1.5 MINUTES)
M.AdjustDizzy(dizzy_adj, bound_upper = 1.5 MINUTES)
return ..()
/datum/reagent/consumable/drink/fyrsskar_tears/on_mob_delete(mob/living/M)
@@ -312,16 +312,16 @@
description = "A fatty, bitter paste made from cocoa beans."
reagent_state = SOLID
nutriment_factor = 5 * REAGENTS_METABOLISM
color = "#302000" // rgb: 48, 32, 0
color = "#5F3A13"
taste_description = "bitter cocoa"
/datum/reagent/consumable/vanilla
name = "Vanilla Powder"
name = "Vanilla"
id = "vanilla"
description = "A fatty, bitter paste made from vanilla pods."
reagent_state = SOLID
nutriment_factor = 5 * REAGENTS_METABOLISM
color = "#FFFACD"
color = "#FEFEFE"
taste_description = "bitter vanilla"
/datum/reagent/consumable/hot_coco
@@ -120,24 +120,28 @@
/datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
var/external_temp
if(istype(M.loc, /obj/machinery/atmospherics/unary/cryo_cell))
var/obj/machinery/atmospherics/unary/cryo_cell/C = M.loc
external_temp = C.temperature_archived
external_temp = C.air_contents.temperature
else
var/turf/T = get_turf(M)
external_temp = T.temperature
if(external_temp < TCRYO)
update_flags |= M.adjustCloneLoss(-4, FALSE)
update_flags |= M.adjustOxyLoss(-10, FALSE)
update_flags |= M.adjustToxLoss(-3, FALSE)
update_flags |= M.adjustBruteLoss(-12, FALSE)
update_flags |= M.adjustFireLoss(-12, FALSE)
if(ishuman(M))
var/mob/living/carbon/human/H = M
var/obj/item/organ/external/head/head = H.get_organ("head")
if(head)
head.status &= ~ORGAN_DISFIGURED
return ..() | update_flags
/datum/reagent/medicine/rezadone
+16 -3
View File
@@ -3,13 +3,26 @@
var/name = null
var/id = null
var/result = null
/// A list of IDs of the required reagents.
///
/// Each ID also needs an associated value that gives us the minimum
/// required amount / of that reagent. The handle_reaction proc can detect
/// mutiples of the same recipes / so for most cases you want to set the
/// required amount to 1.
var/list/required_reagents = list()
var/list/required_catalysts = list()
// Both of these variables are mostly going to be used with slime cores - but if you want to, you can use them for other things
var/atom/required_container = null // the container required for the reaction to happen
var/required_other = FALSE // extra requirements for the reaction to happen
// Both of these variables are mostly going to be used with slime cores
// but if you want to, you can use them for other things
/// The container required for the reaction to happen.
/// Leave this null if you want the reaction to happen anywhere.
var/atom/required_container = null
/// Extra requirements for the reaction to happen.
var/required_other = FALSE
/// This is the amount of the resulting reagent this recipe will produce.
/// It's recommended you set this to the total volume of all required reagents.
var/result_amount = 0
var/list/secondary_results = list() //additional reagents produced by the reaction
var/min_temp = 0 //Minimum temperature required for the reaction to occur (heat to/above this). min_temp = 0 means no requirement
@@ -15,7 +15,7 @@
name = "Bag of Holding"
desc = "A backpack that opens into a localized pocket of Blue Space."
id = "bag_holding"
req_tech = list("bluespace" = 7, "materials" = 5, "engineering" = 5, "plasmatech" = 6)
req_tech = list("bluespace" = 7, "materials" = 5, "engineering" = 7, "plasmatech" = 6)
build_type = PROTOLATHE
materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000)
build_path = /obj/item/storage/backpack/holding
@@ -25,7 +25,7 @@
name = "Belt of Holding"
desc = "An astonishingly complex belt popularized by a rich blue-space technology magnate."
id = "bluespace_belt"
req_tech = list("bluespace" = 7, "materials" = 5, "engineering" = 5, "plasmatech" = 6)
req_tech = list("bluespace" = 7, "materials" = 5, "engineering" = 6, "plasmatech" = 6)
build_type = PROTOLATHE
materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 3000, MAT_URANIUM = 1000)
build_path = /obj/item/storage/belt/bluespace
+3 -10
View File
@@ -602,7 +602,6 @@
A.unlock()
/obj/docking_port/mobile/proc/roadkill(list/L0, list/L1, dir)
var/list/hurt_mobs = list()
for(var/i in 1 to L0.len)
var/turf/T0 = L0[i]
var/turf/T1 = L1[i]
@@ -621,17 +620,11 @@
if(isliving(AM))
var/mob/living/L = AM
L.stop_pulling()
if(L.anchored)
L.gib()
else
if(!(L in hurt_mobs))
hurt_mobs |= L
L.visible_message("<span class='warning'>[L] is hit by \
a hyperspace ripple[L.anchored ? "":" and is thrown clear"]!</span>",
L.visible_message("<span class='warning'>[L] is hit by \
a hyperspace ripple!</span>",
"<span class='userdanger'>You feel an immense \
crushing pressure as the space around you ripples.</span>")
L.Paralyse(20 SECONDS)
L.ex_act(2)
L.gib()
// Move unanchored atoms
if(!AM.anchored)
@@ -360,6 +360,14 @@
containername = "crate" //let's keep it subtle, eh?
contraband = TRUE
/datum/supply_packs/misc/flags
name = "Unapproved flags Crate"
contains = list(/obj/item/flag/ussp,
/obj/item/flag/syndi)
cost = 200
containername = "flags crate"
contraband = TRUE
/datum/supply_packs/misc/formalwear //This is a very classy crate.
name = "Formal Wear Crate"
contains = list(/obj/item/clothing/under/dress/blacktango,
+49
View File
@@ -666,3 +666,52 @@
user.visible_message("<span class='warning'> [user]'s [tool.name] slips, failing to reprogram [target]'s [affected.name].</span>",
"<span class='warning'> Your [tool.name] slips, failing to reprogram [target]'s [affected.name].</span>")
return SURGERY_STEP_RETRY
/datum/surgery/robotics/reconfigure_id
name = "Identity Reconfiguration"
steps = list(
/datum/surgery_step/robotics/external/unscrew_hatch,
/datum/surgery_step/robotics/external/open_hatch,
/datum/surgery_step/robotics/edit_serial,
/datum/surgery_step/robotics/external/close_hatch
)
possible_locs = list(BODY_ZONE_HEAD)
/datum/surgery_step/robotics/edit_serial
name = "edit serial number"
allowed_tools = list(TOOL_MULTITOOL = 100)
time = 4.8 SECONDS
/datum/surgery_step/robotics/edit_serial/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
user.visible_message(
"[user] begins to edit [target]'s identity parameters with [tool].",
"You begin to alter [target]'s identity parameters with [tool]."
)
return ..()
/datum/surgery_step/robotics/edit_serial/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery)
var/new_name = copytext(reject_bad_text(input(user, "Choose a name for this machine.", "Set Name", "[target.real_name]") as null|text), 1, MAX_NAME_LEN)
if(!new_name)
to_chat(user, "<span class='warning'>Invalid name! Please try again.</span>")
return SURGERY_STEP_INCOMPLETE
else if(!target.Adjacent(user))
to_chat(user, "<span class='warning'>The multitool is out of range! Please try again.</span>")
return SURGERY_STEP_INCOMPLETE
var/static/list/gender_list = list("Male" = MALE, "Female" = FEMALE, "Genderless" = PLURAL, "Object" = NEUTER)
var/gender_key = input(user, "Choose a gender for this machine.", "Select Gender", target.gender) as null|anything in gender_list
if(!gender_key)
to_chat(user, "<span class='warning'>You must choose a gender! Please try again.</span>")
return SURGERY_STEP_INCOMPLETE
else if(!target.Adjacent(user))
to_chat(user, "<span class='warning'>The multitool is out of range! Please try again.</span>")
return SURGERY_STEP_INCOMPLETE
var/new_gender = gender_list[gender_key]
var/old_name = target.real_name
target.real_name = new_name
target.gender = new_gender
user.visible_message(
"<span class='notice'>[user] edits [old_name]'s identity parameters with [tool]; [target.p_they()] [target.p_are()] now known as [new_name].</span>",
"<span class='notice'>You alter [old_name]'s identity parameters with [tool]; [target.p_they()] [target.p_are()] now known as [new_name].</span>"
)
return SURGERY_STEP_CONTINUE
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

-2
View File
@@ -2027,7 +2027,6 @@
#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm"
#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
#include "code\modules\mob\living\silicon\ai\freelook\read_me.dm"
#include "code\modules\mob\living\silicon\decoy\death.dm"
#include "code\modules\mob\living\silicon\decoy\decoy.dm"
#include "code\modules\mob\living\silicon\decoy\life.dm"
@@ -2372,7 +2371,6 @@
#include "code\modules\reagents\reagent_dispenser.dm"
#include "code\modules\reagents\chemistry\colors.dm"
#include "code\modules\reagents\chemistry\holder.dm"
#include "code\modules\reagents\chemistry\readme.dm"
#include "code\modules\reagents\chemistry\reagents.dm"
#include "code\modules\reagents\chemistry\recipes.dm"
#include "code\modules\reagents\chemistry\machinery\chem_dispenser.dm"
BIN
View File
Binary file not shown.
+13 -13
View File
@@ -32,26 +32,25 @@ Don't be afraid to ask for help, whether from your peers or from mentors.
As the Captain, you are one of the highest priority targets on the station. Everything from nuclear operatives to traitors that need to rob you of your unique lasergun or your life are things to worry about.
As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head or Blueshield you can trust with keeping it safe.
As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny and a ban.
As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units and the unlike standard hypospray, yours is able to be filled with harmful reagents and injects without telling anyone what have you exactly injected the person with.
As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units and the unlike standard hypospray, yours is able to be filled with harmful reagents and can inject someone without telling anyone what you exactly injected the person with.
As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva.
As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone.
As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't revivable or clonable right now, but they might be clonable later.
As a Medical Doctor, treating plasmamen is not impossible! Salbutamol stops them from suffocating and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
As a Medical Doctor, remember to have either a shower or fire extinguisher at the ready when cloning plasmamen along with their envirosuit.
As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment!
As a Chemist, some chemicals can only be synthesized by heating up the contents with a chemical heater or manually with lighters and similar tools.
As a Chemist, you will be expected to supply crew with certain chemicals. For example, cryoxadone and mannitol for the cryo tubes, unstable mutagen and saltpetre for botany as well as healing pills and patches for the front desk.
As a Chemist, Water and Potassium mixed together will create an explosion, with power scaling by amount used. Don't do it.
As a Geneticist, you can eject someone from cloning early by disabling power in genetics. Note that they will suffer more genetic damage and may lose vital organs from this.
As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, stunlocking people, and punching through walls. However, you can't fire guns, will lose your hulk status if you go into critical condition. Being hulk also does not allow you to break any server rules and go berserk as non-antagonist.
As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, stunlocking people, and punching through walls. However, you can't fire guns and will lose your hulk status if you go into critical condition. Being hulk also does not allow you to break any server rules and go berserk as non-antagonist.
As the Virologist, your viruses can range from healing powers so great that you can heal out of critical status, to diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment!
As the Research Director, you can take AIs out of their cores by loading them into an intelliCard, which lets you see their laws, even ion/syndicate ones. It can then be placed into an AI system integrity restorer computer to revive and/or repair them.
As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled.
As the Research Director, you can spy on and even forge PDA communications with the message monitor console! The key is in your office.
As a Scientist, you can maximize the number of uses you get out of a slime by feeding it slime steroid, created from purple slimes, while alive. You can then apply extract enhancer, created from cerulean slimes, on each extract.
As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signaling device. This will leave behind an anomaly core, which can be used to construct a Phazon mech!
As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signaling device. This will leave behind an anomaly core, which can be used to construct nifty gadgets!
As a Scientist, researchable stock parts can seriously improve the efficiency and speed of machines around the station. In some cases, it can even unlock new functions.
As a Roboticist, keep an ear out for anomaly announcements. If you get your hands on an anomaly core, you can build a Phazon mech!
As a Roboticist, keep an ear out for anomaly announcements. If you get your hands on a bluespace anomaly core, you can build a Phazon mech!
As a Roboticist, you can repair your cyborgs with a welding tool. If they have taken burn damage, you can remove their battery, expose the wiring with a screwdriver and replace their wires with a cable coil.
As a Roboticist, you can reset a cyborg's module by cutting and mending the reset wire with a wire cutter or using a cyborg reset module.
As a Roboticist, you can augment people with cyborg limbs. Augmented limbs can easily be repaired with cables and welders.
@@ -64,12 +63,12 @@ As a Cyborg, you are impervious to fires and heat. If you are rogue, you can rel
As a Cyborg, you are extremely vulnerable to EMPs as EMPs both stun you and damage you. The ion rifle in the armory or a traitor with an EMP kit can kill you in seconds.
As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by using your magnetic gripper.
As a Medical Cyborg, you can partially perform surgery, as you cannot replace organs, but you cannot fail any surgery steps.
As a Janitor Cyborg, you are the bane of all slaughter demons. Cleaning up blood stains will severely gimp them.
As a Janitor Cyborg, you are the bane of all slaughter demons. Cleaning up blood stains will severely hinder them.
As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints.
As the Chief Engineer, your hardsuit is significantly better than everybody else's. It has the best features of both engineering and atmospherics hardsuits, boasting nigh-invulnerability to radiation and all atmospheric conditions.
As the Chief Engineer, the power flow control console in your office will show you APC infos and lets you control them remotely.
As an Engineer, you can electrify grilles by placing wire "nodes" beneath them: the big seemingly unconnected bulges from a half completed wiring job. The wire will be need to be connected to an active power source in order to function.
As an Engineer, return to Engineering once in a while to check on the engine and SMES cells. It's always a good idea to make sure containment isn't compromised.
As the Chief Engineer, your hardsuit is significantly better than everybody else's. It has the best features of both engineering and atmospherics hardsuits, boasting nigh-invulnerability to radiation and all atmospheric conditions. It even has a built in jet-pack!
As the Chief Engineer, using the power monitor consoles gives you a good overview of all the station APC units and their status. You can also use your PDA to remotely view this by connecting to a monitor console.
As an Engineer, you can electrify grilles and fences by placing wire "nodes" beneath them: the big seemingly unconnected bulges from a half completed wiring job. The wire will be need to be connected to an active power source in order to function.
As an Engineer, return to Engineering once in a while to check on the engines and SMES cells. It's always a good idea to make sure containment isn't compromised or if the SM is still properly being cooled.
As an Engineer, you can power the station solely with the solar arrays. They will provide just enough electricity to power the station, however their output is still much worse compared to the true engine.
As an Engineer, you can cool a supermatter shard by spraying it with a fire extinguisher. Only for the brave!
As an Engineer, you can repair windows by using a welding tool on them while on help intent.
@@ -92,7 +91,7 @@ As a Security Officer, mindshield implants can only prevent someone from being t
As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them.
As the Detective, keep in mind that people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department.
As the Detective, you can use your forensics scanner from a distance.
As the Detective, your revolver can be modified to load .357 ammunition obtained from a hacked autolathe. Firing it has a decent chance to blow up your revolver.
As the Detective, your energy revolver has a tracking mode. Shoot a fleeing suspect with it and use a crew pinpointer to track their position!
As the IAA, try to negotiate with the Warden if sentences seem too high for the crime.
As the IAA, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom.
As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain.
@@ -130,6 +129,7 @@ As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn
As a Traitor, the syndicate encryption key is very useful for coordinating plans with your fellow traitors -- or, of course, betraying them.
As a Traitor, plasma can be injected into many things to sabotage them. Power cells, light bulbs, cigars and e-cigs will all explode when used.
As a Traitor, if you can find another Traitor and pool your TC you can buy a mega surplus crate, which costs 40TC but contains a lot of random syndicate gear.
As a Traitor, you can eject someone from cloning early by disabling power in genetics. Note that they will suffer more genetic damage and may lose vital organs from this.
As a Nuclear Operative, communication is key! Use ; to speak to your fellow operatives and coordinate an attack plan.
As a Nuclear Operative, you should look into purchasing a syndicate cyborg, as they can provide heavy fire support, full access, are immune to conventional stuns, and can easily take down the AI.
As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire.
@@ -192,4 +192,4 @@ You can make lasertag turrets, for the ultimate lasertag tournament.
Blob structures take half damage from brute damage. Use lasers.
You can hide paper in vents, but you have to use a screwdriver to open it first.
While the Standard Operating Procedures aren't fully rules, they are there for safety and professional reasons.
Killing the Wizard usually ends the round, unless they are a lich or Space Wizard Federation is RAGING.
Killing the Wizard usually ends the round, unless they are a lich or Space Wizard Federation is RAGING.
@@ -103,6 +103,7 @@ const AccountsRecordList = (properties, context) => {
<SortButton id="owner_name">Account Holder</SortButton>
<SortButton id="account_number">Account Number</SortButton>
<SortButton id="suspended">Account Status</SortButton>
<SortButton id="money">Account Balance</SortButton>
</Table.Row>
{accounts
.filter(
@@ -112,7 +113,9 @@ const AccountsRecordList = (properties, context) => {
'|' +
account.account_number +
'|' +
account.suspended
account.suspended +
'|' +
account.money
);
})
)
@@ -123,6 +126,9 @@ const AccountsRecordList = (properties, context) => {
.map((account) => (
<Table.Row
key={account.account_number}
className={
'AccountsUplinkTerminal__listRow--' + account.suspended
}
onClick={() =>
act('view_account_detail', { account_num: account.account_number })
}
@@ -132,6 +138,7 @@ const AccountsRecordList = (properties, context) => {
</Table.Cell>
<Table.Cell>#{account.account_number}</Table.Cell>
<Table.Cell>{account.suspended}</Table.Cell>
<Table.Cell>{account.money}</Table.Cell>
</Table.Row>
))}
</Table>
@@ -153,17 +160,25 @@ const DepartmentAccountsList = (properties, context) => {
<Table.Row bold>
<TableCell>Department Name</TableCell>
<TableCell>Account Number</TableCell>
<TableCell>Account Status</TableCell>
<TableCell>Account Balance</TableCell>
</Table.Row>
{department_accounts
.map((account) => (
<Table.Row key={account.account_number}
<Table.Row
key={account.account_number}
className={
'AccountsUplinkTerminal__listRow--' + account.suspended
}
onClick={() => act('view_account_detail', {
account_num: account.account_number
})}>
<Table.Cell>
<Icon name="user"/> {account.name}
<Icon name="wallet"/> {account.name}
</Table.Cell>
<Table.Cell>#{account.account_number}</Table.Cell>
<Table.Cell>{account.suspended}</Table.Cell>
<Table.Cell>{account.money}</Table.Cell>
</Table.Row>
))}
</Table>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,3 +19,7 @@
}
}
}
.AccountsUplinkTerminal__listRow--SUSPENDED {
background-color: colors.bg(#890e26);
}
+3 -3
View File
@@ -2525,9 +2525,9 @@ decamelize@^1.2.0:
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
deep-is@^0.1.3:
version "0.1.4"
+12
View File
@@ -0,0 +1,12 @@
@echo off
cd ../../_maps/
for /R %%f in (*.dmm) do copy "%%f" "%%f.backup"
cls
echo All dmm files in _maps directories have been backed up
echo Now you can make your changes...
echo ---
echo Remember to run mapmerge.bat just before you commit your changes!
echo ---
pause
+5
View File
@@ -0,0 +1,5 @@
@echo off
set MAPROOT=../../_maps/
set TGM=1
python mapmerge.py
pause
+12 -3
View File
@@ -1,8 +1,17 @@
#!/usr/bin/env python3
import shutil
from collections import defaultdict
from . import frontend
from .dmm import *
# Import hacks to make this work as a normal script again
try:
from . import frontend
except:
import frontend
try:
from .dmm import *
except:
from dmm import *
def merge_map(new_map, old_map, delete_unused=False):
if new_map.key_length != old_map.key_length:
@@ -88,7 +97,7 @@ def main(settings):
shutil.copyfile(fname, fname + ".before")
old_map = DMM.from_file(fname + ".backup")
new_map = DMM.from_file(fname)
merge_map(new_map, old_map).to_file(fname, settings.tgm)
merge_map(new_map, old_map).to_file(fname, tgm=settings.tgm)
if __name__ == '__main__':
main(frontend.read_settings())
+7
View File
@@ -0,0 +1,7 @@
# Info
This directory contains the `replay.py` script. This is used to "replay" custom `.utracy` files generated by the production server.
`BYOND-Tracy` profiles can use excessive amounts of RAM, upwards of 48GB in a single process if captured normally at runtime. This is not viable for the production server, so they are written as a custom flatfile inside of `data/profiler/`. These need to be read into tracy over the "network" (localhost) via `Tracy.exe` or `capture.exe`. You will need >48GB of RAM for this process. I am not joking.
The version of `replay.py` in this folder is compatible with the protocol of `Tracy 0.8.2`. Newer versions will not work. It requires the `lz4` python module with the streams extension. This requires manually downloading, building and installing the `python-lz4/python-lz4` repo and building with `PYLZ4_EXPERIMENTAL=TRUE` as an environment variable.
+432
View File
@@ -0,0 +1,432 @@
import argparse
import ctypes
import socket
import selectors
import lz4.stream
# file protocol
FileSignature = 0x6D64796361727475
FileVersion = 2
FileEventZoneBegin =15
FileEventZoneEnd = 17
FileEventZoneColor = 62
FileEventFrameMark = 64
class FileHeader(ctypes.Structure):
_fields_ = (
("signature", ctypes.c_ulonglong),
("version", ctypes.c_uint),
("multiplier", ctypes.c_double),
("init_begin", ctypes.c_longlong),
("init_end", ctypes.c_longlong),
("delay", ctypes.c_longlong),
("resolution", ctypes.c_longlong),
("epoch", ctypes.c_longlong),
("exec_time", ctypes.c_longlong),
("pid", ctypes.c_longlong),
("sampling_period", ctypes.c_longlong),
("flags", ctypes.c_byte),
("cpu_arch", ctypes.c_byte),
("cpu_manufacturer", ctypes.c_char * 12),
("cpu_id", ctypes.c_uint),
("program_name", ctypes.c_char * 64),
("host_info", ctypes.c_char * 1024)
)
class FileZoneBegin(ctypes.Structure):
_fields_ = (
("tid", ctypes.c_uint32),
("srcloc", ctypes.c_uint32),
("timestamp", ctypes.c_int64)
)
class FileZoneEnd(ctypes.Structure):
_fields_ = (
("tid", ctypes.c_uint32),
("timestamp", ctypes.c_int64)
)
class FileZoneColor(ctypes.Structure):
_fields_ = (
("tid", ctypes.c_uint32),
("color", ctypes.c_uint32)
)
class FileFrameMark(ctypes.Structure):
_fields_ = (
("name", ctypes.c_uint32),
("timestamp", ctypes.c_int64)
)
class FileEvent(ctypes.Structure):
class Events(ctypes.Union):
_fields_ = (
("zone_begin", FileZoneBegin),
("zone_end", FileZoneEnd),
("zone_color", FileZoneColor),
("frame_mark", FileFrameMark),
)
_anonymous_ = ("event",)
_fields_ = (
("type", ctypes.c_byte),
("event", Events)
)
# network protocol
NetworkMaxFrameSize = 256 * 1024
NetworkHandshakeWelcome = b"\x01"
NetworkHandshakeProtocolMismatch = b"\x02"
NetworkEventZoneBegin = 15
NetworkEventZoneEnd = 17
NetworkEventTerminate = 55
NetworkEventThreadContext = 57
NetworkEventZoneColor = 62
NetworkEventFrameMark = 64
NetworkEventSrcloc = 67
NetworkResponseServerQueryNoop = 87
NetworkResponseSourceCodeNotAvailable = 88
NetworkResponseSymbolCodeNotAvailable = 89
NetworkResponseStringData = 94
NetworkResponseThreadName = 95
NetworkQueryTerminate = 0
NetworkQueryString = 1
NetworkQueryThreadString = 2
NetworkQuerySrcloc = 3
NetworkQueryPlotName = 4
NetworkQueryFrameName = 5
NetworkQueryParameter = 6
NetworkQueryFiberName = 7
NetworkQueryDisconnect = 8
NetworkQueryCallstackFrame = 9
NetworkQueryExternalName = 10
NetworkQuerySymbol = 11
NetworkQuerySymbolCode = 12
NetworkQueryCodeLocation = 13
NetworkQuerySourceCode = 14
NetworkQueryDataTransfer = 15
NetworkQueryDataTransferPart = 16
class NetworkHeader(ctypes.Structure):
_pack_ = 1
_fields_ = (
("multiplier", ctypes.c_double),
("init_begin", ctypes.c_longlong),
("init_end", ctypes.c_longlong),
("delay", ctypes.c_longlong),
("resolution", ctypes.c_longlong),
("epoch", ctypes.c_longlong),
("exec_time", ctypes.c_longlong),
("pid", ctypes.c_longlong),
("sampling_period", ctypes.c_longlong),
("flags", ctypes.c_uint8),
("cpu_arch", ctypes.c_uint8),
("cpu_manufacturer", ctypes.c_char * 12),
("cpu_id", ctypes.c_uint),
("program_name", ctypes.c_char * 64),
("host_info", ctypes.c_char * 1024)
)
class NetworkThreadContext(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("tid", ctypes.c_uint32)
)
class NetworkZoneBegin(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("timestamp", ctypes.c_int64),
("srcloc", ctypes.c_uint64)
)
class NetworkZoneEnd(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("timestamp", ctypes.c_int64)
)
class NetworkZoneColor(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("r", ctypes.c_uint8),
("g", ctypes.c_uint8),
("b", ctypes.c_uint8)
)
class NetworkFrameMark(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("timestamp", ctypes.c_int64),
("name", ctypes.c_uint64)
)
class NetworkSrcloc(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("name", ctypes.c_int64),
("function", ctypes.c_int64),
("file", ctypes.c_int64),
("line", ctypes.c_uint32),
("r", ctypes.c_uint8),
("g", ctypes.c_uint8),
("b", ctypes.c_uint8)
)
class NetworkRequest(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("ptr", ctypes.c_int64),
("extra", ctypes.c_uint32)
)
def main(stream):
def file_read_uint():
ctype = ctypes.c_uint()
stream.readinto(ctype)
return ctype.value
def file_read_chars(size):
if 0 == size:
return None
ctype = (ctypes.c_char * size)()
stream.readinto(ctype)
return ctype.value
header = FileHeader()
stream.readinto(header)
if header.signature != FileSignature:
print("incorrect signature")
return
if header.version != FileVersion:
print("incorrect version")
return
srclocs_len = file_read_uint()
strings = {}
srclocs = [None] * srclocs_len
for i in range(srclocs_len):
name_len = file_read_uint()
name = file_read_chars(name_len)
function_len = file_read_uint()
function = file_read_chars(function_len)
file_len = file_read_uint()
file = file_read_chars(file_len)
line = file_read_uint()
color = file_read_uint()
if name != None:
digest = hash(name)
strings[digest] = name
name = digest
else:
name = 0
if function != None:
digest = hash(function)
strings[digest] = function
function = digest
else:
function = 0
if file != None:
digest = hash(file)
strings[digest] = file
file = digest
else:
file = 0
srclocs[i] = (name, function, file, line, color)
server = socket.create_server(("127.0.0.1", 8086))
server.listen(1)
print("listening on 127.0.0.1:8086...")
client, addr = server.accept()
if client.recv(8) != b"TracyPrf":
print("bad client")
client.close()
return
protocol = ctypes.c_uint()
client.recv_into(protocol)
if protocol.value not in (56, 57):
print("bad protocol")
client.sendall(NetworkHandshakeProtocolMismatch)
client.close()
return
print(f"client accepted from {addr}")
client.sendall(NetworkHandshakeWelcome)
client.sendall(NetworkHeader(
multiplier = header.multiplier,
init_begin = header.init_begin,
init_end = header.init_end,
delay = header.delay,
resolution = header.resolution,
epoch = header.epoch,
exec_time = header.exec_time,
pid = header.pid,
sampling_period = header.sampling_period,
flags = header.flags,
cpu_arch = header.cpu_arch,
cpu_manufacturer = header.cpu_manufacturer,
cpu_id = header.cpu_id,
program_name = header.program_name,
host_info = header.host_info
))
event = FileEvent()
event_sz = ctypes.sizeof(event)
timestamp = 0
tid = None
buffer = bytearray(NetworkMaxFrameSize // event_sz)
offset = 0
compressor = lz4.stream.LZ4StreamCompressor(
"double_buffer",
NetworkMaxFrameSize,
store_comp_size = 4
)
def commit():
nonlocal offset
if offset > 0:
block = compressor.compress(buffer[:offset])
client.sendall(block)
offset = 0
def write_msg(msg):
nonlocal offset, buffer
sz = ctypes.sizeof(msg)
if offset + sz > len(buffer):
commit()
buffer[offset : offset + sz] = msg
offset += sz
def thread_context(event):
nonlocal tid, timestamp
if event.zone_begin.tid != tid:
tid = event.zone_begin.tid
timestamp = 0
write_msg(NetworkThreadContext(
type = NetworkEventThreadContext,
tid = tid
))
while event_sz == stream.readinto(event):
if event.type == FileEventZoneBegin:
thread_context(event)
write_msg(NetworkZoneBegin(
type = NetworkEventZoneBegin,
timestamp = event.zone_begin.timestamp - timestamp,
srcloc = event.zone_begin.srcloc
))
timestamp = event.zone_begin.timestamp
elif event.type == FileEventZoneEnd:
thread_context(event)
write_msg(NetworkZoneEnd(
type = NetworkEventZoneEnd,
timestamp = event.zone_end.timestamp - timestamp
))
timestamp = event.zone_end.timestamp
elif event.type == FileEventZoneColor:
thread_context(event)
write_msg(NetworkZoneColor(
type = NetworkEventZoneColor,
r = (event.zone_color.color >> 0x00) & 0xFF,
g = (event.zone_color.color >> 0x08) & 0xFF,
b = (event.zone_color.color >> 0x10) & 0xFF
))
elif event.type == FileEventFrameMark:
write_msg(NetworkFrameMark(
type = NetworkEventFrameMark,
name = 0,
timestamp = event.frame_mark.timestamp
))
commit()
def respond_string(string, ptr, type):
string_sz = len(string)
class NetworkStringData(ctypes.Structure):
_pack_ = 1
_fields_ = (
("type", ctypes.c_uint8),
("ptr", ctypes.c_uint64),
("len", ctypes.c_uint16),
("str", ctypes.c_char * string_sz)
)
write_msg(NetworkStringData(
type = type,
ptr = req.ptr,
len = string_sz,
str = string
))
req = NetworkRequest()
client.settimeout(1)
try:
while ctypes.sizeof(req) == client.recv_into(req):
if req.type == NetworkQuerySrcloc:
srcloc = srclocs[req.ptr]
write_msg(NetworkSrcloc(
type = NetworkEventSrcloc,
name = srcloc[0],
function = srcloc[1],
file = srcloc[2],
line = srcloc[3],
r = (srcloc[4] >> 0x00) & 0xFF,
g = (srcloc[4] >> 0x08) & 0xFF,
b = (srcloc[4] >> 0x10) & 0xFF
))
elif req.type == NetworkQueryString:
respond_string(strings[req.ptr], req.ptr, NetworkResponseStringData)
elif req.type == NetworkQuerySymbolCode:
write_msg(ctypes.c_uint8(NetworkResponseSymbolCodeNotAvailable))
elif req.type == NetworkQuerySourceCode:
write_msg(ctypes.c_uint8(NetworkResponseSourceCodeNotAvailable))
elif req.type == NetworkQueryDataTransfer:
write_msg(ctypes.c_uint8(NetworkResponseServerQueryNoop))
elif req.type == NetworkQueryDataTransferPart:
write_msg(ctypes.c_uint8(NetworkResponseServerQueryNoop))
elif req.type == NetworkQueryThreadString:
respond_string(b"main", req.ptr, NetworkResponseThreadName)
else:
print("unknown req:", req.type)
commit()
except socket.timeout:
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("file", type=argparse.FileType("rb"))
args = parser.parse_args()
main(args.file)