Merge branch 'master' into upstream-merge-10704

This commit is contained in:
Nadyr
2021-06-19 22:01:09 -04:00
committed by GitHub
395 changed files with 24387 additions and 9983 deletions
+7 -1
View File
@@ -14,7 +14,13 @@ vchat.db*
data
data/
cfg/
.vscode
#Visual studio stuff
*.vscode/*
!/.vscode/launch.json
!/.vscode/extensions.json
!/.vscode/settings.json
!/.vscode/tasks.json
ch-merge-upstream-pull-request.sh
code/game/gamemodes/technomancer/spells/projectile/overload.dm
+16
View File
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "byond",
"request": "launch",
"name": "Launch DreamDaemon",
"preLaunchTask": "dm: build - ${command:CurrentDME}",
"dmb": "${workspaceFolder}/${command:CurrentDMB}",
"dreamDaemon": true
}
]
}
+36
View File
@@ -0,0 +1,36 @@
{
"eslint.nodePath": "./tgui/.yarn/sdks",
"eslint.workingDirectories": [
"./tgui"
],
"typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"workbench.editorAssociations": {
"*.dmi": "imagePreview.previewEditor"
},
"files.eol": "\n",
"gitlens.advanced.blame.customArguments": [
"-w"
],
"tgstationTestExplorer.project.resultsType": "json",
"[javascript]": {
"editor.rulers": [
120
]
},
"[typescript]": {
"editor.rulers": [
120
]
},
"[scss]": {
"editor.rulers": [
120
]
},
"tgstationTestExplorer.project.DMEName": "tgmc.dme"
}
+38
View File
@@ -0,0 +1,38 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "dreammaker",
"dme": "vorestation.dme",
"problemMatcher": [
"$dreammaker"
],
"group": "build",
"label": "dm: build - vorestation.dme"
},
{
"type": "shell",
"command": "tgui/bin/tgui",
"windows": {
"command": ".\\tgui\\bin\\tgui.bat"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: build"
},
{
"type": "shell",
"command": "yarn build",
"options": {
"cwd": "tgui/packages/tgfont/",
},
"group": "build",
"problemMatcher": [],
"label": "tgui: build tgfont",
"detail": "node mkdist.cjs && fantasticon --config config.cjs"
}
]
}
+3
View File
@@ -42,6 +42,9 @@
#define COMSIG_PARENT_QDELETING "parent_qdeleting"
/// generic topic handler (usr, href_list)
#define COMSIG_TOPIC "handle_topic"
/// from datum ui_act (usr, action)
#define COMSIG_UI_ACT "COMSIG_UI_ACT"
/// fires on the target datum when an element is attached to it (/datum/element)
#define COMSIG_ELEMENT_ATTACH "element_attach"
+8
View File
@@ -18,6 +18,14 @@
/// Get a pool index of the provided window id
#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13))
/// Creates a message packet for sending via output()
// This is {"type":type,"payload":payload}, but pre-encoded. This is much faster
// than doing it the normal way.
// To ensure this is correct, this is unit tested in tgui_create_message.
#define TGUI_CREATE_MESSAGE(type, payload) ( \
"%7b%22type%22%3a%22[type]%22%2c%22payload%22%3a[url_encode(json_encode(payload))]%7d" \
)
/// Max length for Modal Input
#define TGUI_MODAL_INPUT_MAX_LENGTH 1024
/// Max length for Modal Input for names
-6
View File
@@ -57,12 +57,6 @@
if (!.)
. = B[STAT_ENTRY_COUNT] - A[STAT_ENTRY_COUNT]
// Compares complexity of recipes for use in cooking, etc. This is for telling which recipe to make, not for showing things to the player.
/proc/cmp_recipe_complexity_dsc(datum/recipe/A, datum/recipe/B)
var/a_score = LAZYLEN(A.items) + LAZYLEN(A.reagents) + LAZYLEN(A.fruit)
var/b_score = LAZYLEN(B.items) + LAZYLEN(B.reagents) + LAZYLEN(B.fruit)
return b_score - a_score
/proc/cmp_typepaths_asc(A, B)
return sorttext("[B]","[A]")
+3
View File
@@ -1614,6 +1614,9 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
// 'Utility' planes
. += new /obj/screen/plane_master/fullbright //Lighting system (lighting_overlay objects)
. += new /obj/screen/plane_master/lighting //Lighting system (but different!)
. += new /obj/screen/plane_master/o_light_visual //Object lighting (using masks)
. += new /obj/screen/plane_master/emissive //Emissive overlays
. += new /obj/screen/plane_master/ghosts //Ghosts!
. += new /obj/screen/plane_master{plane = PLANE_AI_EYE} //AI Eye!
+1 -1
View File
@@ -22,7 +22,7 @@ SUBSYSTEM_DEF(tgui)
var/basehtml
/datum/controller/subsystem/tgui/PreInit()
basehtml = file2text('tgui/packages/tgui/public/tgui.html')
basehtml = file2text('tgui/public/tgui.html')
/datum/controller/subsystem/tgui/Shutdown()
close_all_uis()
-3
View File
@@ -306,9 +306,6 @@
var/output = {"<form><input type="hidden" name="src" value="\ref[src]"><ul class="sparse">"}
if (inputtype == "checkbox" || inputtype == "radio")
for (var/i in values)
var/div_slider = slidecolor
if(!i["allowed_edit"])
div_slider = "locked"
output += {"<li>
<label class="switch">
<input type="[inputtype]" value="1" name="[i["name"]]"[i["checked"] ? " checked" : ""][i["allowed_edit"] ? "" : " disabled onclick='return false' onkeydown='return false'"]>
+2 -1
View File
@@ -66,7 +66,8 @@
/obj/item/weapon/surgical/bonegel,
/obj/item/weapon/surgical/retractor,
/obj/item/weapon/surgical/bonesetter,
/obj/item/weapon/surgical/circular_saw
/obj/item/weapon/surgical/circular_saw,
/obj/item/weapon/surgical/bioregen
)
cost = 25
containertype = /obj/structure/closet/crate/secure/veymed
+11 -1
View File
@@ -7,4 +7,14 @@
cost = 35
containertype = /obj/structure/closet/crate/secure
containername = "frontier phaser crate"
access = access_explorer
access = access_explorer
/datum/supply_pack/munitions/expeditionbows
name = "Frontier bows (station-locked) crate"
contains = list(
/obj/item/weapon/gun/energy/locked/frontier/handbow=2
)
cost = 20
containertype = /obj/structure/closet/crate/secure
containername = "frontier phaser crate"
access = access_explorer
+1 -1
View File
@@ -80,7 +80,7 @@
/obj/machinery/sleep_console/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state)
if(sleeper)
return sleeper.tgui_act(action, params, ui, state)
return FALSE
return ..()
/obj/machinery/sleeper
name = "sleeper"
+12 -11
View File
@@ -568,21 +568,22 @@
var/list/info = alarm_area.air_scrub_info[id_tag]
if(!info)
continue
var/list/list/filters = list()
scrubbers.Add(list(list(
scrubbers += list(list(
"id_tag" = id_tag,
"long_name" = sanitize(long_name),
"power" = info["power"],
"scrubbing" = info["scrubbing"],
"panic" = info["panic"],
"filters" = filters
)))
filters.Add(list(list("name" = "Oxygen", "command" = "o2_scrub", "val" = info["filter_o2"])))
filters.Add(list(list("name" = "Nitrogen", "command" = "n2_scrub", "val" = info["filter_n2"])))
filters.Add(list(list("name" = "Carbon Dioxide", "command" = "co2_scrub","val" = info["filter_co2"])))
filters.Add(list(list("name" = "Toxin" , "command" = "tox_scrub","val" = info["filter_phoron"])))
filters.Add(list(list("name" = "Nitrous Oxide", "command" = "n2o_scrub","val" = info["filter_n2o"])))
filters.Add(list(list("name" = "Fuel", "command" = "fuel_scrub","val" = info["filter_fuel"])))
"filters" = list(
list("name" = "Oxygen", "command" = "o2_scrub", "val" = info["filter_o2"]),
list("name" = "Nitrogen", "command" = "n2_scrub", "val" = info["filter_n2"]),
list("name" = "Carbon Dioxide", "command" = "co2_scrub","val" = info["filter_co2"]),
list("name" = "Toxin" , "command" = "tox_scrub","val" = info["filter_phoron"]),
list("name" = "Nitrous Oxide", "command" = "n2o_scrub","val" = info["filter_n2o"]),
list("name" = "Fuel", "command" = "fuel_scrub","val" = info["filter_fuel"])
)
))
data["scrubbers"] = scrubbers
data["mode"] = mode
@@ -798,4 +799,4 @@
TLV["temperature"] = list(T0C - 40, T0C - 20, T0C + 40, T0C + 66) // K, Lower Temperature for Freezer Air Alarms (This is because TLV is hardcoded to be generated on first_run, and therefore the only way to modify this without changing TLV generation)
// VOREStation Edit End
// VOREStation Edit End
@@ -30,7 +30,7 @@
)
/obj/machinery/embedded_controller/radio/docking_port_multi/tgui_act(action, params)
return // Apparently we swallow all input (this is corrected legacy code)
return ..() // Apparently we swallow all input (this is corrected legacy code)
//a docking port based on an airlock
// This is the actual controller that will be commanded by the master defined above
@@ -0,0 +1,28 @@
/obj/effect/graffitispawner
name = "old scrawling"
icon = 'icons/effects/map_effects.dmi'
icon_state = "graffiti"
var/graffiti_type // The text string for the desired icon state of your graffiti.
// If the effect's color is not set, it will be chosen at random.
var/color_secondary // The hexcode for the desired secondary color of your graffiti. If blank, it will inherit this effect's color.
/obj/effect/graffitispawner/Initialize()
..()
if(!color)
color = rgb(rand(1,255),rand(1,255),rand(1,255))
if(!color_secondary)
color_secondary = color
if(!graffiti_type)
graffiti_type = pick("rune", "graffiti", "left", "right", "up", "down")
var/turf/T = get_turf(src)
var/obj/effect/decal/cleanable/crayon/C = new(T, color, color_secondary, graffiti_type)
C.name = name
qdel(src)
@@ -0,0 +1,187 @@
/mob/living/simple_mob //makes it so that any simplemob can potentially be revived by players and joined by ghosts
var/ghostjoin = 0
var/ic_revivable = 0
var/revivedby = "no one"
//The stuff we want to be revivable normally
/mob/living/simple_mob/animal
ic_revivable = 1
/mob/living/simple_mob/otie
ic_revivable = 1
/mob/living/simple_mob/vore
ic_revivable = 1
//The stuff that would be revivable but that we don't want to be revivable
/mob/living/simple_mob/animal/giant_spider/nurse //no you can't revive the ones who can lay eggs and get webs everywhere
ic_revivable = 0
/mob/living/simple_mob/animal/giant_spider/carrier //or the ones who fart babies when they die
ic_revivable = 0
//WHEN GHOSTS ATTACK!!!!!
/mob/living/simple_mob/attack_ghost(mob/observer/dead/user as mob)
if(!ghostjoin)
return ..()
var/reply = alert("Would you like to become [src]? It is bound to [revivedby].",,"Yes","No")
if(reply == "No")
return
if(ckey) //FIRST ONE TO CLICK YES GETS IT!!!!!! Channel your inner youtube commenter.
to_chat(src, "<span class='notice'>Sorry, someone else has already inhabited [src].</span>")
return
log_and_message_admins("[key_name_admin(user)] joined [src] as a ghost [ADMIN_FLW(src)]")
active_ghost_pods -= src
if(user.mind)
user.mind.active = TRUE
user.mind.transfer_to(src)
else
src.ckey = user.ckey
qdel(user)
ghostjoin = 0
ghostjoin_icon()
if(revivedby != "no one")
to_chat(src, "<span class='notice'>Where once your life had been rough and scary, you have been assisted by [revivedby]. They seem to be the reason you are on your feet again... so perhaps you should help them out.</span> <span class= warning> Being as you were revived, you are allied with the station. Do not attack anyone unless they are threatening the one who revived you. And try to listen to the one who revived you within reason. Of course, you may do scenes as you like, but you must still respect preferences.</span>")
visible_message("[src]'s eyes flicker with a curious intelligence.")
/obj/item/device/denecrotizer //Away map reward. FOR TRAINED NECROMANCERS ONLY. >:C
name = "experimental denecrotizer"
desc = "It looks simple on the outside but this device radiates some unknown dread. It does not appear to be of any ordinary make, and just how it works is unclear, but this device seems to interact with dead flesh."
icon = 'icons/obj/device_vr.dmi'
icon_state = "denecrotizer"
w_class = ITEMSIZE_COST_NORMAL
var/charges = 5 //your army of minions can only be this big
var/last_used
var/cooldown = 10 MINUTES //LONG
var/revive_time = 30 SECONDS //Don't do this in combat
var/advanced = 1 //allows for ghosts to join mobs who get revived by this, and updates their faction to yours
/obj/item/device/denecrotizer/examine(var/mob/user)
. = ..()
var/cooldowntime = round((cooldown - (world.time - last_used)) * 0.1)
if(Adjacent(user))
if(cooldowntime <= 0)
. += "<span class='notice'>The screen indicates that this device is ready to be used, and that it has enough energy for [charges] uses.</span>"
else
. += "<span class='notice'>The screen indicates that this device can be used again in [cooldowntime] seconds, and that it has enough energy for [charges] uses.</span>"
/obj/item/device/denecrotizer/proc/check_target(mob/living/simple_mob/target, mob/living/user)
if(!target.Adjacent(user))
return FALSE
if(user.a_intent != I_HELP) //be gentle
user.visible_message("[user] bonks [target] with [src].", runemessage = "bonks [target]")
return FALSE
if(!istype(target))
to_chat(user, "<span class='notice'>[target] seems to be too complicated for [src] to interface with.</span>")
return FALSE
if(!(world.time - last_used > cooldown))
to_chat(user, "<span class='notice'>[src] doesn't seem to be ready yet.</span>")
return FALSE
if(!charges)
to_chat(user, "<span class='notice'>[src] doesn't seem to be active anymore.</span>")
return FALSE
if(!target.ic_revivable)
to_chat(user, "<span class='notice'>[src] doesn't seem to interface with [target].</span>")
return FALSE
if(target.stat != DEAD)
if(!advanced)
to_chat(user, "<span class='notice'>[src] doesn't seem to work on that.</span>")
return FALSE
if(target.ai_holder.retaliate || target.ai_holder.hostile) // You can be friends with still living mobs if they are passive I GUESS
to_chat(user, "<span class='notice'>[src] doesn't seem to work on that.</span>")
return FALSE
if(!target.mind)
user.visible_message("[user] gently presses [src] to [target]...", runemessage = "presses [src] to [target]")
if(do_after(user, revive_time, exclusive = 1, target = target))
target.faction = user.faction
target.revivedby = user.name
target.ghostjoin = 1
active_ghost_pods += target
target.ghostjoin_icon()
last_used = world.time
charges--
log_and_message_admins("[key_name_admin(user)] used a denecrotizer to tame/offer a simplemob to ghosts: [target]. [ADMIN_FLW(src)]")
target.visible_message("[target]'s eyes widen, as though in revelation as it looks at [user].", runemessage = "eyes widen")
if(charges == 0)
icon_state = "[initial(icon_state)]-o"
update_icon()
return FALSE
else
to_chat(user, "<span class='notice'>[src] doesn't seem to work on that.</span>")
return FALSE
return TRUE
/obj/item/device/denecrotizer/proc/ghostjoin_rez(mob/living/simple_mob/target, mob/living/user)
user.visible_message("[user] gently presses [src] to [target]...", runemessage = "presses [src] to [target]")
if(do_after(user, revive_time, exclusive = 1, target = target))
target.faction = user.faction
target.revivedby = user.name
target.ai_holder.returns_home = FALSE
target.revive()
target.sight = initial(target.sight)
target.see_in_dark = initial(target.see_in_dark)
target.see_invisible = initial(target.see_invisible)
target.update_icon()
visible_message("[target] lifts its head and looks at [user].", runemessage = "lifts its head and looks at [user]")
log_and_message_admins("[key_name_admin(user)] used a denecrotizer to revive a simple mob: [target]. [ADMIN_FLW(src)]")
if(!target.mind) //if it doesn't have a mind then no one has been playing as it, and it is safe to offer to ghosts.
target.ghostjoin = 1
active_ghost_pods += target
target.ghostjoin_icon()
last_used = world.time
charges--
if(charges == 0)
icon_state = "[initial(icon_state)]-o"
update_icon()
return
/obj/item/device/denecrotizer/proc/basic_rez(mob/living/simple_mob/target, mob/living/user) //so medical can have a way to bring back people's pets or whatever, does not change any settings about the mob or offer it to ghosts.
user.visible_message("[user] presses [src] to [target]...", runemessage = "presses [src] to [target]")
if(do_after(user, revive_time, exclusive = 1, target = target))
target.revive()
target.sight = initial(target.sight)
target.see_in_dark = initial(target.see_in_dark)
target.see_invisible = initial(target.see_invisible)
target.update_icon()
visible_message("[target] lifts its head and looks at [user].", runemessage = "lifts its head and looks at [user]")
last_used = world.time
charges--
if(charges == 0)
icon_state = "[initial(icon_state)]-o"
update_icon()
return
else
user.visible_message("[user] bonks [target] with [src]. Nothing happened.")
return
/obj/item/device/denecrotizer/attack(mob/living/simple_mob/target, mob/living/user)
if(check_target(target, user))
if(advanced)
ghostjoin_rez(target, user)
else
basic_rez(target, user)
else
return ..()
/mob/living/simple_mob/proc/ghostjoin_icon() //puts an icon on mobs for ghosts, so they can see if a mob has been revived and is joinable
var/static/image/I
if(!I)
I = image('icons/mob/hud_vr.dmi', "ghostjoin")
I.invisibility = INVISIBILITY_OBSERVER
I.plane = PLANE_GHOSTS
I.appearance_flags = KEEP_APART|RESET_TRANSFORM
if(ghostjoin)
add_overlay(I)
else
cut_overlay(I)
/obj/item/device/denecrotizer/medical //Can revive more things, but without the special ghost and faction stuff. For medical use.
name = "commercial denecrotizer"
desc = "A curious device who's purpose is reviving simpler life forms. It seems to radiate menace."
icon_state = "m-denecrotizer"
advanced = 0 //This one isn't as fancy
cooldown = 5 MINUTES //not as long
charges = 20 //in case spiders merc Ian
@@ -11,12 +11,11 @@
thrown_force_divisor = 1
origin_tech = list(TECH_MATERIAL = 1)
attack_verb = list("attacked", "stabbed", "poked")
sharp = 1
edge = 1
sharp = TRUE
edge = TRUE
force_divisor = 0.1 // 6 when wielded with hardness 60 (steel)
thrown_force_divisor = 0.25 // 5 when thrown with weight 20 (steel)
var/loaded //Descriptive string for currently loaded food object.
var/scoop_food = 1
var/weakref/loaded //Weakref for currently loaded food object.
/obj/item/weapon/material/kitchen/utensil/New()
..()
@@ -25,6 +24,15 @@
create_reagents(5)
return
/obj/item/weapon/material/kitchen/utensil/update_icon()
. = ..()
cut_overlays()
var/obj/item/weapon/reagent_containers/food/snacks/eaten = loaded?.resolve()
if(eaten)
var/image/I = new(icon, "loadedfood")
I.color = eaten.filling_color
add_overlay(I)
/obj/item/weapon/material/kitchen/utensil/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
if(!istype(M))
return ..()
@@ -37,30 +45,32 @@
else
return ..()
if (reagents.total_volume > 0)
if (loaded && reagents.total_volume > 0)
var/atom/movable/eaten = loaded?.resolve()
reagents.trans_to_mob(M, reagents.total_volume, CHEM_INGEST)
if(M == user)
if(!M.can_eat(loaded))
return
M.visible_message("<span class='notice'>\The [user] eats some [loaded] from \the [src].</span>")
else
user.visible_message("<span class='warning'>\The [user] begins to feed \the [M]!</span>")
if(!(M.can_force_feed(user, loaded) && do_mob(user, M, 5 SECONDS)))
return
M.visible_message("<span class='notice'>\The [user] feeds some [loaded] to \the [M] with \the [src].</span>")
playsound(src,'sound/items/eatfood.ogg', rand(10,40), 1)
cut_overlays()
if(eaten)
if(M == user)
if(!M.can_eat(eaten))
return
M.visible_message(SPAN_NOTICE("\The [user] eats some of \the [eaten] with \the [src]."))
else
user.visible_message(SPAN_WARNING("\The [user] begins to feed \the [M]!"))
if(!(M.can_force_feed(user, eaten) && do_mob(user, M, 5 SECONDS)))
return
M.visible_message(SPAN_NOTICE("\The [user] feeds some of \the [eaten] to \the [M] with \the [src]."))
playsound(src,'sound/items/eatfood.ogg', rand(10,40), 1)
update_icon()
return
else
to_chat(user, "<span class='warning'>You don't have anything on \the [src].</span>") //if we have help intent and no food scooped up DON'T STAB OURSELVES WITH THE FORK
to_chat(user, SPAN_WARNING("You don't have anything on \the [src].")) //if we have help intent and no food scooped up DON'T STAB OURSELVES WITH THE FORK
return
/obj/item/weapon/material/kitchen/utensil/fork
name = "fork"
desc = "It's a fork. Sure is pointy."
icon_state = "fork"
sharp = 1
edge = 0
sharp = TRUE
edge = FALSE
/obj/item/weapon/material/kitchen/utensil/fork/plastic
default_material = "plastic"
@@ -70,8 +80,8 @@
desc = "It's a spoon. You can see your own upside-down face in it."
icon_state = "spoon"
attack_verb = list("attacked", "poked")
edge = 0
sharp = 0
edge = FALSE
sharp = FALSE
force_divisor = 0.1 //2 when wielded with weight 20 (steel)
/obj/item/weapon/material/kitchen/utensil/spoon/plastic
@@ -115,6 +115,38 @@
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "explorer_duffle"
///Talon Bags///
/obj/item/weapon/storage/backpack/talon
name = "ITV backpack"
desc = "A backpack for carrying a large number of supplies easily."
icon = 'icons/obj/clothing/backpack_vr.dmi'
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "talon"
/obj/item/weapon/storage/backpack/satchel/talon
name = "ITV satchel"
desc = "A satchel for carrying a large number of supplies easily."
icon = 'icons/obj/clothing/backpack_vr.dmi'
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "talon_satchel"
item_state_slots = null
/obj/item/weapon/storage/backpack/messenger/talon
name = "ITV messenger bag"
desc = "A sturdy backpack worn over one shoulder."
icon = 'icons/obj/clothing/backpack_vr.dmi'
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "talon_courier"
item_state_slots = null
/obj/item/weapon/storage/backpack/dufflebag/talon
name = "ITV dufflebag"
desc = "A large dufflebag for holding extra supplies."
icon = 'icons/obj/clothing/backpack_vr.dmi'
icon_override = 'icons/mob/back_vr.dmi'
icon_state = "talon_duffle"
///Roboticist Bags///
/obj/item/weapon/storage/backpack/satchel/roboticist
@@ -17,14 +17,8 @@
use_sound = 'sound/items/drop/flesh.ogg'
/obj/item/weapon/storage/vore_egg/open(mob/user as mob)
<<<<<<< HEAD
||||||| parent of 7f8d5449b9... Merge pull request #10704 from Novacat/nova-basicfixes
if(isobserver(usr))
return
=======
if(isobserver(user))
return
>>>>>>> 7f8d5449b9... Merge pull request #10704 from Novacat/nova-basicfixes
icon = open_egg_icon
..()
@@ -137,7 +137,8 @@
/obj/item/stack/medical/advanced/bruise_pack,
/obj/item/stack/nanopaste,
/obj/item/device/healthanalyzer/advanced,
/obj/item/weapon/autopsy_scanner
/obj/item/weapon/autopsy_scanner,
/obj/item/weapon/surgical/bioregen
)
starts_with = list(
@@ -152,7 +153,8 @@
/obj/item/weapon/surgical/FixOVein,
/obj/item/stack/medical/advanced/bruise_pack,
/obj/item/device/healthanalyzer/advanced,
/obj/item/weapon/autopsy_scanner
/obj/item/weapon/autopsy_scanner,
/obj/item/weapon/surgical/bioregen
)
/obj/item/weapon/storage/firstaid/clotting
@@ -6,6 +6,7 @@
* Surgical Drill
* Scalpel
* Circular Saw
* Bio-Regenerator
*/
/obj/item/weapon/surgical
@@ -22,6 +23,16 @@
return 0
..()
/*
* Bio-Regenerator
*/
/obj/item/weapon/surgical/bioregen
name="bioregenerator"
desc="A special tool used in surgeries which can pull toxins from and restore oxygen to organic tissue as well as recreate missing biological structures to allow otherwise irreperable flesh to be mended."
icon='icons/obj/surgery_vr.dmi'
icon_state="bioregen"
drop_sound = 'sound/items/drop/scrap.ogg'
/*
* Retractor
*/
@@ -1,6 +0,0 @@
/obj/item/weapon/surgical/bioregen
name="bioregenerator"
desc="A special tool used in surgeries which can pull toxins from and restore oxygen to organic tissue as well as recreate missing biological structures to allow otherwise irreperable flesh to be mended."
icon='icons/obj/surgery_ch.dmi'
icon_state="bioregen"
drop_sound = 'sound/items/drop/scrap.ogg'
+17 -2
View File
@@ -1,11 +1,18 @@
//DEFINITIONS FOR ASSET DATUMS START HERE.
/datum/asset/simple/tgui_common
// keep_local_name = TRUE
assets = list(
"tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"),
)
/datum/asset/simple/tgui
// keep_local_name = TRUE
assets = list(
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
"tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
/datum/asset/simple/headers
assets = list(
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
@@ -175,6 +182,14 @@
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css'
)
/datum/asset/simple/tgfont
assets = list(
"tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"),
"tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"),
"tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"),
)
// /datum/asset/spritesheet/goonchat
// name = "chat"
@@ -102,7 +102,7 @@
display_name = "pilot qualification pin"
description = "An iron pin denoting the qualification to fly USG spacecraft."
path = /obj/item/clothing/accessory/solgov/specialty/pilot
allowed_roles = list("Pathfinder", "Pilot", "Field Medic","Security Pilot") //YW ADDITIONS
allowed_roles = list("Pathfinder","Pilot","Field Medic","Talon Pilot")
/datum/gear/accessory/flops
display_name = "drop straps"
@@ -112,3 +112,11 @@
/datum/gear/accessory/flops/New()
..()
gear_tweaks += gear_tweak_free_color_choice
/*
Talon pin
*/
/datum/gear/accessory/talonpin
display_name = "ITV pin"
description = "A small enamel pin of the ITV logo."
path = /obj/item/clothing/accessory/talon
@@ -23,4 +23,39 @@
/datum/gear/head/detective_alt
display_name = "cyberscope headgear, detective"
path = /obj/item/clothing/head/helmet/detective_alt
allowed_roles = list("Head of Security", "Detective")
allowed_roles = list("Head of Security", "Detective")
/datum/gear/head/bearpelt
display_name = "brown bear pelt"
path = /obj/item/clothing/head/pelt
/datum/gear/head/wolfpelt
display_name = "brown wolf pelt"
path = /obj/item/clothing/head/pelt/wolfpelt
/datum/gear/head/wolfpeltblack
display_name = "black wolf pelt"
path = /obj/item/clothing/head/pelt/wolfpeltblack
/datum/gear/head/tigerpelt
display_name = "shiny tiger pelt"
path = /obj/item/clothing/head/pelt/tigerpelt
/datum/gear/head/tigerpeltsnow
display_name = "snow tiger pelt"
path = /obj/item/clothing/head/pelt/tigerpeltsnow
/datum/gear/head/tigerpeltpink
display_name = "pink tiger pelt"
path = /obj/item/clothing/head/pelt/tigerpeltpink
/*
Talon hats
*/
/datum/gear/head/cap/talon
display_name = "cap, ITV"
path = /obj/item/clothing/head/soft/talon
/datum/gear/head/beret/talon
display_name = "beret, ITV"
path = /obj/item/clothing/head/beret
@@ -73,3 +73,10 @@
/datum/gear/suit/oversize/New()
..()
gear_tweaks += gear_tweak_free_color_choice
/*
Talon winter coat
*/
/datum/gear/suit/wintercoat/talon
display_name = "winter coat, ITV"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/talon
@@ -227,4 +227,12 @@ Bluespace jumpsuit
/datum/gear/uniform/hfjumpsuit
display_name = "HYPER jumpsuit"
path = /obj/item/clothing/under/hyperfiber
cost = 2
cost = 2
/*
Talon jumpsuit
*/
/datum/gear/uniform/talonbasic
display_name = "ITV Jumpsuit"
description = "A jumpsuit that is usually issued to ITV contractors, however others can purchase it to show their support towards ITV."
path = /obj/item/clothing/under/rank/talon/basic
@@ -61,6 +61,10 @@
path = /obj/item/weapon/storage/backpack/dufflebag/explorer
allowed_roles = list("Pathfinder","Explorer")
/datum/gear/utility/dufflebag/talon
display_name = "ITV dufflebag"
path = /obj/item/weapon/storage/backpack/dufflebag/explorer
/datum/gear/utility/ID
display_name = "contractor identification card"
path = /obj/item/weapon/card/id/event/polymorphic/altcard
+13 -6
View File
@@ -2,17 +2,17 @@
name = "techpriest hood"
armor = list(melee = 20, bullet = 10, laser = 10, energy = 10, bomb = 25, bio = 50, rad = 25)
// Armor versions here
// Armor versions here
/obj/item/clothing/head/hood/galahad
name = "galahad hood"
armor = list(melee = 80, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0)
siemens_coefficient = 2
/obj/item/clothing/head/hood/lancelot
name = "lancelot hood"
armor = list(melee = 80, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0)
siemens_coefficient = 2
/obj/item/clothing/head/hood/robin
name = "robin hood"
armor = list(melee = 80, bullet = 10, laser = 10, energy = 0, bomb = 0, bio = 0, rad = 0)
@@ -21,9 +21,16 @@
// Costume Versions Here
/obj/item/clothing/head/hood/galahad_costume
name = "galahad costume hood"
/obj/item/clothing/head/hood/lancelot_costume
name = "lancelot costume hood"
/obj/item/clothing/head/hood/robin_costume
name = "robin costume hood"
name = "robin costume hood"
// Talon Winter Hood
/obj/item/clothing/head/hood/winter/talon
name = "ITV winter hood"
desc = "A cozy winter hood attached to a heavy winter jacket."
icon_override = 'icons/mob/head_vr.dmi'
icon_state = "winterhood_talon"
+35
View File
@@ -94,3 +94,38 @@
icon_state = "pizzadelivery"
icon = 'icons/obj/clothing/hats_vr.dmi'
icon_override = 'icons/mob/head_vr.dmi'
//////////TALON HATS//////////
/obj/item/clothing/head/soft/talon
name = "ITV baseball cap"
desc = "It's a ballcap bearing the colors of ITV."
icon = 'icons/obj/clothing/hats_vr.dmi'
icon_override = 'icons/mob/head_vr.dmi'
icon_state = "talonsoft"
item_state = "talonsoft"
item_state_slots = list(slot_r_hand_str = "blacksoft", slot_l_hand_str = "blacksoft")
/obj/item/clothing/head/caphat/talon
name = "ITV nautical hat"
desc = "It's a classic nautical hat bearing the colors of ITV. Perfect for commanding the Talon."
icon = 'icons/obj/clothing/hats_vr.dmi'
icon_override = 'icons/mob/head_vr.dmi'
icon_state = "talon_captain_cap"
item_state = "taloncaptaincap"
obj/item/clothing/head/beret/talon
name = "ITV beret"
desc = "It's a basic baret colored to match ITV's uniforms."
icon = 'icons/obj/clothing/hats_vr.dmi'
icon_override = 'icons/mob/head_vr.dmi'
icon_state = "beret_talon"
item_state = "baret_talon"
obj/item/clothing/head/beret/talon/command
name = "ITV officer beret"
desc = "It's a basic baret colored to match ITV's uniforms with a badge pinned on the front. Perfect for commanders."
icon = 'icons/obj/clothing/hats_vr.dmi'
icon_override = 'icons/mob/head_vr.dmi'
icon_state = "beret_talon_officer"
item_state = "baret_talon_command"
@@ -44,3 +44,24 @@
src.icon_state = initial(icon_state)
to_chat(user, "You lower the visor on the pilot helmet.")
update_clothing_icon() //so our mob-overlays update
//////////Talon Pilot Headgear//////////
/obj/item/clothing/head/pilot_vr/talon
name = "ITV pilot helmet"
desc = "An ITV version of the standard pilot helmet. Protects the head from impacts. This one has a retractable visor"
icon_state = "pilot3"
item_icons = list(slot_head_str = 'icons/mob/head_vr.dmi')
sprite_sheets = list(
SPECIES_TESHARI = 'icons/mob/species/teshari/helmet_vr.dmi'
)
action_button_name = "Toggle Visor"
/obj/item/clothing/head/pilot_vr/alt/attack_self(mob/user as mob)
if(src.icon_state == initial(icon_state))
src.icon_state = "[icon_state]up"
to_chat(user, "You raise the visor on the pilot helmet.")
else
src.icon_state = initial(icon_state)
to_chat(user, "You lower the visor on the pilot helmet.")
update_clothing_icon() //so our mob-overlays update
+11 -1
View File
@@ -67,4 +67,14 @@
name = "crusader's armor"
desc = "ye olde knight, risen again. This one seems slightly faster than the rest, but weaker."
icon_state = "robin"
hoodtype = /obj/item/clothing/head/hood/robin_costume
hoodtype = /obj/item/clothing/head/hood/robin_costume
// Talon Winter Coat
/obj/item/clothing/suit/storage/hooded/wintercoat/talon
name = "ITV winter coat"
desc = "A cozy winter coat, covered in thick fur and baring the colors of ITV."
icon = 'icons/obj/clothing/suits_vr.dmi'
icon_override = 'icons/mob/suit_vr.dmi'
icon_state = "taloncoat"
armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
hoodtype = /obj/item/clothing/head/hood/winter/talon
@@ -100,3 +100,12 @@
icon_override = 'icons/mob/suit_vr.dmi'
icon_state = "cyberpunksleek_long"
item_state = "cyberpunksleek_long"
//Talon Hoodie
/obj/item/clothing/suit/storage/toggle/hoodie/talon
name = "ITV hoodie"
desc = "A warm, blue sweatshirt bearing ITV markings."
icon = 'icons/obj/clothing/suits_vr.dmi'
icon_override = 'icons/mob/suit_vr.dmi'
icon_state = "talonhoodie"
item_state_slots = list(slot_r_hand_str = "suit_blue", slot_l_hand_str = "suit_blue")
@@ -385,4 +385,13 @@
icon_state = "silverthree"
item_state = "silverthree"
overlay_state = "silverthree"
desc = "A silver medal awarded to a group which has demonstrated exceptional teamwork to achieve a notable feat. This one has two bronze service stars, denoting that it has been awarded three times."
desc = "A silver medal awarded to a group which has demonstrated exceptional teamwork to achieve a notable feat. This one has three bronze service stars, denoting that it has been awarded four times."
/obj/item/clothing/accessory/talon
name = "ITV pin"
desc = "A collectable enamel pin that resembles ITV's company logo."
icon = 'icons/obj/clothing/ties_vr.dmi'
icon_override = 'icons/mob/ties_vr.dmi'
icon_state = "talon_pin"
item_state = "talonpin"
overlay_state = "talonpin"
@@ -146,3 +146,60 @@
icon_state = "pizzadelivery"
item_state = "pizzadelivery"
rolled_down = 0
//////////////////////TALON JUMPSUITS//////////////////////
/obj/item/clothing/under/rank/talon/basic
name = "ITV jumpsuit"
desc = "A basic jumpsuit that bares the ITV logo on the breast."
icon = 'icons/obj/clothing/uniforms_vr.dmi'
index = "vr"
rolled_down_icon = 'icons/mob/uniform_rolled_down_vr.dmi'
icon_override = 'icons/mob/uniform_vr.dmi'
icon_state = "talon_basic"
item_state = "talon_basic"
rolled_sleeves = 0
/obj/item/clothing/under/rank/talon/proper
name = "ITV proper jumpsuit"
desc = "A neat and proper uniform for a proper company."
icon = 'icons/obj/clothing/uniforms_vr.dmi'
index = "vr"
rolled_down_icon = 'icons/mob/uniform_rolled_down_vr.dmi'
icon_override = 'icons/mob/uniform_vr.dmi'
icon_state = "talon_jumpsuit"
item_state = "talon_jumpsuit"
rolled_sleeves = 0
/obj/item/clothing/under/rank/talon/security
name = "ITV security jumpsuit"
desc = "A sleek, streamlined version of ITV's standard jumpsuit that bares security markings."
icon = 'icons/obj/clothing/uniforms_vr.dmi'
index = "vr"
rolled_down_icon = 'icons/mob/uniform_rolled_down_vr.dmi'
icon_override = 'icons/mob/uniform_vr.dmi'
icon_state = "talon_security"
item_state = "talon_security"
rolled_sleeves = 0
/obj/item/clothing/under/rank/talon/pilot
name = "ITV pilot jumpsuit"
desc = "A sleek, streamlined version of ITV's standard jumpsuit. Made from cushioned fabric to handle intense flight."
icon = 'icons/obj/clothing/uniforms_vr.dmi'
index = "vr"
rolled_down_icon = 'icons/mob/uniform_rolled_down_vr.dmi'
icon_override = 'icons/mob/uniform_vr.dmi'
icon_state = "talon_pilot"
item_state = "talon_pilot"
rolled_sleeves = 0
/obj/item/clothing/under/rank/talon/command
name = "ITV command jumpsuit"
desc = "A commanding jumpsuit fit for a commanding officer."
icon = 'icons/obj/clothing/uniforms_vr.dmi'
index = "vr"
rolled_down_icon = 'icons/mob/uniform_rolled_down_vr.dmi'
icon_override = 'icons/mob/uniform_vr.dmi'
icon_state = "talon_captain"
item_state = "talon_captain"
rolled_sleeves = 0
@@ -9,7 +9,7 @@
return "You [H.species.scream_verb_1p]!"
. = ..()
/decl/emote/audible/cough/get_emote_message_3p(var/atom/user, var/atom/target, var/extra_params)
/decl/emote/audible/scream/get_emote_message_3p(var/atom/user, var/atom/target, var/extra_params)
if(ishuman(user))
var/mob/living/carbon/human/H = user
return "[H.species.scream_verb_3p]!"
+17 -23
View File
@@ -181,32 +181,26 @@
// Eating with forks
if(istype(W,/obj/item/weapon/material/kitchen/utensil))
var/obj/item/weapon/material/kitchen/utensil/U = W
if(U.scoop_food)
if(!U.reagents)
U.create_reagents(5)
if(!U.reagents)
U.create_reagents(5)
if (U.reagents.total_volume > 0)
to_chat(user, "<font color='red'>You already have something on your [U].</font>")
return
user.visible_message( \
"[user] scoops up some [src] with \the [U]!", \
"<font color='blue'>You scoop up some [src] with \the [U]!</font>" \
)
src.bitecount++
U.cut_overlays()
U.loaded = "[src]"
var/image/I = new(U.icon, "loadedfood")
I.color = src.filling_color
U.add_overlay(I)
reagents.trans_to_obj(U, min(reagents.total_volume,5))
if (reagents.total_volume <= 0)
qdel(src)
if (U.reagents.total_volume > 0)
to_chat(user, "<font color='red'>You already have something on your [U].</font>")
return
user.visible_message( \
"[user] scoops up some [src] with \the [U]!", \
"<font color='blue'>You scoop up some [src] with \the [U]!</font>" \
)
bitecount++
reagents.trans_to_obj(U, min(reagents.total_volume,5))
if (reagents.total_volume <= 0)
qdel(src)
return
if (is_sliceable())
//these are used to allow hiding edge items in food that is not on a table/tray
var/can_slice_here = isturf(src.loc) && ((locate(/obj/structure/table) in src.loc) || (locate(/obj/machinery/optable) in src.loc) || (locate(/obj/item/weapon/tray) in src.loc))
+8 -9
View File
@@ -314,20 +314,19 @@
// When exact is false, extraneous ingredients are ignored
// When exact is true, extraneous ingredients will fail the recipe
// In both cases, the full complement of required inredients is still needed
// In both cases, the full set of required ingredients is still needed
/proc/select_recipe(var/list/datum/recipe/available_recipes, var/obj/obj as obj, var/exact)
var/list/datum/recipe/possible_recipes = list()
var/highest_count = 0
var/count = 0
for (var/datum/recipe/recipe in available_recipes)
if(!recipe.check_reagents(obj.reagents, exact) || !recipe.check_items(obj, exact) || !recipe.check_fruit(obj, exact))
continue
possible_recipes |= recipe
if (!possible_recipes.len)
return null
else if (possible_recipes.len == 1)
return possible_recipes[1]
else //okay, let's select the most complicated recipe
sortTim(possible_recipes, /proc/cmp_recipe_complexity_dsc)
return possible_recipes[1]
// Taken from cmp_recipe_complexity_dsc, but is way faster.
count = LAZYLEN(recipe.items) + LAZYLEN(recipe.reagents) + LAZYLEN(recipe.fruit)
if(count >= highest_count)
highest_count = count
. = recipe
// Both of these are just placeholders to allow special behavior for mob holders, but you can do other things in here later if you feel like it.
/datum/recipe/proc/before_cook(obj/container) // Called Before the Microwave starts delays and cooking stuff
@@ -358,3 +358,27 @@
req_access = newlist() //for toggling safety
locked = 0
lockable = 0
///phaser bow///
/obj/item/weapon/gun/energy/locked/frontier/handbow
name = "phaser handbow"
desc = "An minaturized weapon that fires a bolt of engery. Includes a built-in crank charger for recharging away from civilization. This one has a safety interlock that prevents firing while in proximity to the facility."
icon = 'icons/obj/gun_vr.dmi'
icon_state = "handbowkill"
item_state = null
phase_power = 100
w_class = ITEMSIZE_SMALL
charge_cost = 600
modifystate = "handbowkill"
firemodes = list(
list(mode_name="lethal", fire_delay=12, projectile_type=/obj/item/projectile/energy/bolt/heavy, modifystate="handbowkill", charge_cost = 600),
list(mode_name="low-power", fire_delay=8, projectile_type=/obj/item/projectile/energy/bolt, modifystate="handbowstun", charge_cost = 160),
)
/obj/item/weapon/gun/energy/locked/frontier/holdout/unlocked
desc = "An minaturized weapon that fires a bolt of engery. Includes a built-in crank charger for recharging away from civilization."
req_access = newlist() //for toggling safety
locked = 0
lockable = 0
@@ -8,4 +8,18 @@
flash_range = 1
/obj/item/projectile/energy/flash/flare
flash_range = 2
flash_range = 2
/obj/item/projectile/energy/bolt
name = "engergy bolt"
icon_state = "cbbolt"
damage = 20
/obj/item/projectile/energy/bolt/heavy
damage = 30
icon_state = "cbbolt"
/obj/item/projectile/energy/bolt/stun
name = "stun bolt"
agony = 30
-149
View File
@@ -1,149 +0,0 @@
/datum/surgery_status/
var/dehusk = 0
/datum/surgery_step/dehusk/
priority = 1
can_infect = 0
blood_level = 1
/datum/surgery_step/dehusk/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if (!hasorgans(target))
return 0
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if (!affected || (affected.robotic >= ORGAN_ROBOT))
return 0
if(coverage_check(user,target,affected,tool))
return 0
return target_zone == BP_TORSO && (HUSK in target.mutations)
/datum/surgery_step/dehusk/structinitial
allowed_tools = list(
/obj/item/weapon/surgical/bioregen = 100
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/structinitial/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 0
/datum/surgery_step/dehusk/structinitial/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You begin to create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>")
..()
/datum/surgery_step/dehusk/structinitial/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] creates a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>")
target.op_stage.dehusk = 1
..()
/datum/surgery_step/dehusk/structinitial/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user]'s hand slips, and the mesh falls, with \the [tool] scraping [target]'s body.</span>", \
"<span class='danger'>Your hand slips, and the mesh falls, with \the [tool] scraping [target]'s body.</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/dehusk/relocateflesh
allowed_tools = list(
/obj/item/weapon/surgical/hemostat = 100, \
/obj/item/stack/cable_coil = 75, \
/obj/item/device/assembly/mousetrap = 20
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/relocateflesh/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 1
/datum/surgery_step/dehusk/relocateflesh/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>", \
"<span class='notice'>You begin to relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>")
..()
/datum/surgery_step/dehusk/relocateflesh/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] relocates some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>", \
"<span class='notice'>You relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>")
target.op_stage.dehusk = 2
..()
/datum/surgery_step/dehusk/relocateflesh/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user] accidentally rips a massive chunk out of [target]'s flesh with \the [tool], causing massive damage.</span>", \
"<span class='danger'>You accidentally rip a massive chunk out of [target]'s flesh with \the [tool], causing massive damage.</span>")
affected.createwound(CUT, 25)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/dehusk/structfinish
allowed_tools = list(
/obj/item/weapon/surgical/bioregen = 100, \
/obj/item/weapon/surgical/FixOVein = 30
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/structfinish/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 2
/datum/surgery_step/dehusk/structfinish/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(istype(tool,/obj/item/weapon/surgical/bioregen))
user.visible_message("<span class='notice'>[user] begins to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You begin to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>")
else if(istype(tool,/obj/item/weapon/surgical/FixOVein))
user.visible_message("<span class='notice'>[user] attempts to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You attempt to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>")
..()
/datum/surgery_step/dehusk/structfinish/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] finishes recreating the missing biological structures and filling in gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You finish recreating the missing biological structures and filling in gaps in [target]'s flesh with \the [tool].</span>")
target.op_stage.dehusk = 0
target.mutations.Remove(HUSK)
target.status_flags &= ~DISFIGURED
target.update_icons_body()
..()
/datum/surgery_step/dehusk/structfinish/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if(istype(tool,/obj/item/weapon/surgical/bioregen))
user.visible_message("<span class='danger'>[user]'s hand slips, causing \the [tool] to scrape [target]'s body.</span>", \
"<span class='danger'>Your hand slips, causing \the [tool] to scrape [target]'s body.</span>")
else if(istype(tool,/obj/item/weapon/surgical/FixOVein))
user.visible_message("<span class='danger'>[user] fails to finish the structure over the gaps in [target]'s flesh, doing more damage than good.</span>", \
"<span class='danger'>You fail to finish the structure over the gaps in [target]'s flesh, doing more damage than good.</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/internal/detoxify
blood_level = 1
allowed_tools = list(/obj/item/weapon/surgical/bioregen=100)
min_duration = 90
max_duration = 120
/datum/surgery_step/internal/detoxify/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_TORSO && (target.toxloss > 25 || target.oxyloss > 25)
/datum/surgery_step/internal/detoxify/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to pull toxins from, and restore oxygen to [target]'s musculature and organs with \the [tool].</span>", \
"<span class='notice'>You begin to pull toxins from, and restore oxygen to [target]'s musculature and organs with \the [tool].</span>")
..()
/datum/surgery_step/internal/detoxify/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] finishes pulling toxins from, and restoring oxygen to [target]'s musculature and organs with \the [tool].</span>", \
"<span class='notice'>You finish pulling toxins from, and restoring oxygen to [target]'s musculature and organs with \the [tool].</span>")
if(target.toxloss>25)
target.adjustToxLoss(-20)
if(target.oxyloss>25)
target.adjustOxyLoss(-20)
..()
/datum/surgery_step/internal/detoxify/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user]'s hand slips, failing to finish the surgery, and damaging [target] with \the [tool].</span>", \
"<span class='danger'>Your hand slips, failing to finish the surgery, and damaging [target] with \the [tool].</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
+154
View File
@@ -232,3 +232,157 @@
/datum/surgery_step/hardsuit/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='danger'>[user]'s [tool] can't quite seem to get through the metal...</span>", \
"<span class='danger'>\The [tool] can't quite seem to get through the metal. It's weakening, though - try again.</span>")
///////////////////////////////////////////
// De-Husking Surgery //
/////////////////////////////////////////
/datum/surgery_status/
var/dehusk = 0
/datum/surgery_step/dehusk/
priority = 1
can_infect = 0
blood_level = 1
/datum/surgery_step/dehusk/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if (!hasorgans(target))
return 0
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if (!affected || (affected.robotic >= ORGAN_ROBOT))
return 0
if(coverage_check(user,target,affected,tool))
return 0
return target_zone == BP_TORSO && (HUSK in target.mutations)
/datum/surgery_step/dehusk/structinitial
allowed_tools = list(
/obj/item/weapon/surgical/bioregen = 100
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/structinitial/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 0
/datum/surgery_step/dehusk/structinitial/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You begin to create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>")
..()
/datum/surgery_step/dehusk/structinitial/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] creates a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You create a fleshy but rigid looking mesh over gaps in [target]'s flesh with \the [tool].</span>")
target.op_stage.dehusk = 1
..()
/datum/surgery_step/dehusk/structinitial/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user]'s hand slips, and the mesh falls, with \the [tool] scraping [target]'s body.</span>", \
"<span class='danger'>Your hand slips, and the mesh falls, with \the [tool] scraping [target]'s body.</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/dehusk/relocateflesh
allowed_tools = list(
/obj/item/weapon/surgical/hemostat = 100, \
/obj/item/stack/cable_coil = 75, \
/obj/item/device/assembly/mousetrap = 20
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/relocateflesh/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 1
/datum/surgery_step/dehusk/relocateflesh/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>", \
"<span class='notice'>You begin to relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>")
..()
/datum/surgery_step/dehusk/relocateflesh/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] relocates some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>", \
"<span class='notice'>You relocate some of [target]'s flesh with \the [tool], using it to fill in gaps.</span>")
target.op_stage.dehusk = 2
..()
/datum/surgery_step/dehusk/relocateflesh/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user] accidentally rips a massive chunk out of [target]'s flesh with \the [tool], causing massive damage.</span>", \
"<span class='danger'>You accidentally rip a massive chunk out of [target]'s flesh with \the [tool], causing massive damage.</span>")
affected.createwound(CUT, 25)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/dehusk/structfinish
allowed_tools = list(
/obj/item/weapon/surgical/bioregen = 100, \
/obj/item/weapon/surgical/FixOVein = 30
)
min_duration = 90
max_duration = 120
/datum/surgery_step/dehusk/structfinish/can_use(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.dehusk == 2
/datum/surgery_step/dehusk/structfinish/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(istype(tool,/obj/item/weapon/surgical/bioregen))
user.visible_message("<span class='notice'>[user] begins to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You begin to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>")
else if(istype(tool,/obj/item/weapon/surgical/FixOVein))
user.visible_message("<span class='notice'>[user] attempts to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You attempt to recreate blood vessels and fill in the gaps in [target]'s flesh with \the [tool].</span>")
..()
/datum/surgery_step/dehusk/structfinish/end_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] finishes recreating the missing biological structures and filling in gaps in [target]'s flesh with \the [tool].</span>", \
"<span class='notice'>You finish recreating the missing biological structures and filling in gaps in [target]'s flesh with \the [tool].</span>")
target.op_stage.dehusk = 0
target.mutations.Remove(HUSK)
target.status_flags &= ~DISFIGURED
target.update_icons_body()
..()
/datum/surgery_step/dehusk/structfinish/fail_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if(istype(tool,/obj/item/weapon/surgical/bioregen))
user.visible_message("<span class='danger'>[user]'s hand slips, causing \the [tool] to scrape [target]'s body.</span>", \
"<span class='danger'>Your hand slips, causing \the [tool] to scrape [target]'s body.</span>")
else if(istype(tool,/obj/item/weapon/surgical/FixOVein))
user.visible_message("<span class='danger'>[user] fails to finish the structure over the gaps in [target]'s flesh, doing more damage than good.</span>", \
"<span class='danger'>You fail to finish the structure over the gaps in [target]'s flesh, doing more damage than good.</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
/datum/surgery_step/internal/detoxify
blood_level = 1
allowed_tools = list(/obj/item/weapon/surgical/bioregen=100)
min_duration = 90
max_duration = 120
/datum/surgery_step/internal/detoxify/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_TORSO && (target.toxloss > 25 || target.oxyloss > 25)
/datum/surgery_step/internal/detoxify/begin_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] begins to pull toxins from, and restore oxygen to [target]'s musculature and organs with \the [tool].</span>", \
"<span class='notice'>You begin to pull toxins from, and restore oxygen to [target]'s musculature and organs with \the [tool].</span>")
..()
/datum/surgery_step/internal/detoxify/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
user.visible_message("<span class='notice'>[user] finishes pulling toxins from, and restoring oxygen to [target]'s musculature and organs with \the [tool].</span>", \
"<span class='notice'>You finish pulling toxins from, and restoring oxygen to [target]'s musculature and organs with \the [tool].</span>")
if(target.toxloss>25)
target.adjustToxLoss(-20)
if(target.oxyloss>25)
target.adjustOxyLoss(-20)
..()
/datum/surgery_step/internal/detoxify/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<span class='danger'>[user]'s hand slips, failing to finish the surgery, and damaging [target] with \the [tool].</span>", \
"<span class='danger'>Your hand slips, failing to finish the surgery, and damaging [target] with \the [tool].</span>")
affected.createwound(CUT, 15)
affected.createwound(BRUISE, 10)
..()
+25
View File
@@ -76,6 +76,8 @@
* return bool If the UI should be updated or not.
*/
/datum/proc/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action)
// If UI is not interactive or usr calling Topic is not the UI user, bail.
if(!ui || ui.status != STATUS_INTERACTIVE)
return TRUE
@@ -133,6 +135,13 @@
*/
/client/var/list/tgui_windows = list()
/**
* global
*
* TRUE if cache was reloaded by tgui dev server at least once.
*/
/client/var/tgui_cache_reloaded = FALSE
/**
* public
*
@@ -193,6 +202,22 @@
// Unconditionally collect tgui logs
if(type == "log")
log_tgui(usr, href_list["message"])
// Reload all tgui windows
if(type == "cacheReloaded")
// Note: Find a solution for the below causing asset CDN to stop working
// which doesn't prevent players from using the dev server on prod
// whenever the asset CDN is actually used (currently using rsc only)
if(/* !check_rights(R_ADMIN) || */ usr.client.tgui_cache_reloaded)
return TRUE
// Mark as reloaded
usr.client.tgui_cache_reloaded = TRUE
// Notify windows
var/list/windows = usr.client.tgui_windows
for(var/window_id in windows)
var/datum/tgui_window/window = windows[window_id]
if (window.status == TGUI_WINDOW_READY)
window.on_message(type, null, href_list)
return TRUE
// Locate window
var/window_id = href_list["window_id"]
var/datum/tgui_window/window
+15 -4
View File
@@ -90,12 +90,16 @@
opened_at = world.time
window.acquire_lock(src)
if(!window.is_ready())
window.initialize(inline_assets = list(
window.initialize(
fancy = user.client.prefs.tgui_fancy,
inline_assets = list(
get_asset_datum(/datum/asset/simple/tgui_common),
get_asset_datum(/datum/asset/simple/tgui)
))
else
window.send_message("ping")
window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
window.send_asset(get_asset_datum(/datum/asset/simple/tgfont))
for(var/datum/asset/asset in src_object.ui_assets(user))
window.send_asset(asset)
window.send_message("update", get_payload(
@@ -159,8 +163,8 @@
*/
/datum/tgui/proc/send_asset(datum/asset/asset)
if(!window)
CRASH("send_asset() can only be called after open().")
window.send_asset(asset)
CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.")
return window.send_asset(asset)
/**
* public
@@ -216,6 +220,11 @@
"fancy" = user.client.prefs.tgui_fancy,
"locked" = user.client.prefs.tgui_lock,
),
"client" = list(
"ckey" = user.client.ckey,
"address" = user.client.address,
"computer_id" = user.client.computer_id,
),
"user" = list(
"name" = "[user]",
"ckey" = "[user.ckey]",
@@ -295,8 +304,10 @@
/datum/tgui/proc/on_message(type, list/payload, list/href_list)
// Pass act type messages to tgui_act
if(type && copytext(type, 1, 5) == "act/")
var/act_type = copytext(type, 5)
log_tgui(user, "Action: [act_type] [href_list["payload"]], Window: [window.id], Source: [src_object]")
process_status()
if(src_object.tgui_act(copytext(type, 5), payload, src, state))
if(src_object.tgui_act(act_type, payload, src, state))
SStgui.update_uis(src_object)
return FALSE
switch(type)
+124 -44
View File
@@ -8,12 +8,18 @@
var/client/client
var/pooled
var/pool_index
var/is_browser = FALSE
var/status = TGUI_WINDOW_CLOSED
var/locked = FALSE
var/datum/tgui/locked_by
var/datum/subscriber_object
var/subscriber_delegate
var/fatally_errored = FALSE
var/message_queue
var/sent_assets = list()
// Vars passed to initialize proc (and saved for later)
var/inline_assets
var/fancy
/**
* public
@@ -26,11 +32,12 @@
/datum/tgui_window/New(client/client, id, pooled = FALSE)
src.id = id
src.client = client
src.client.tgui_windows[id] = src
src.pooled = pooled
if(pooled)
client.tgui_windows[id] = src
src.pool_index = TGUI_WINDOW_INDEX(id)
/**
* public
*
@@ -39,43 +46,54 @@
* will be put into the queue until the window finishes loading.
*
* optional inline_assets list List of assets to inline into the html.
* optional inline_html string Custom HTML to inject.
* optional fancy bool If TRUE, will hide the window titlebar.
*/
/datum/tgui_window/proc/initialize(inline_assets = list())
log_tgui(client, "[id]/initialize")
/datum/tgui_window/proc/initialize(
inline_assets = list(),
inline_html = "",
fancy = FALSE)
log_tgui(client, "[id]/initiailize ([src])")
if(!client)
return
src.inline_assets = inline_assets
src.fancy = fancy
status = TGUI_WINDOW_LOADING
fatally_errored = FALSE
message_queue = null
// Build window options
var/options = "file=[id].html;can_minimize=0;auto_format=0;"
// Remove titlebar and resize handles for a fancy window
if(client.prefs.tgui_fancy)
if(fancy)
options += "titlebar=0;can_resize=0;"
else
options += "titlebar=1;can_resize=1;"
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
// Process inline assets
var/inline_styles = ""
var/inline_scripts = ""
// Inject inline assets
var/inline_assets_str = ""
for(var/datum/asset/asset in inline_assets)
var/mappings = asset.get_url_mappings()
for(var/name in mappings)
var/url = mappings[name]
// Not urlencoding since asset strings are considered safe
// Not encoding since asset strings are considered safe
if(copytext(name, -4) == ".css")
inline_styles += "<link rel=\"stylesheet\" type=\"text/css\" href=\"[url]\">\n"
inline_assets_str += "Byond.loadCss('[url]', true);\n"
else if(copytext(name, -3) == ".js")
inline_scripts += "<script type=\"text/javascript\" defer src=\"[url]\"></script>\n"
asset.send()
html = replacetextEx(html, "<!-- tgui:styles -->\n", inline_styles)
html = replacetextEx(html, "<!-- tgui:scripts -->\n", inline_scripts)
inline_assets_str += "Byond.loadJs('[url]', true);\n"
asset.send(client)
if(length(inline_assets_str))
inline_assets_str = "<script>\n" + inline_assets_str + "</script>\n"
html = replacetextEx(html, "<!-- tgui:assets -->\n", inline_assets_str)
// Inject custom HTML
html = replacetextEx(html, "<!-- tgui:html -->\n", inline_html)
// Open the window
client << browse(html, "window=[id];[options]")
// Detect whether the control is a browser
is_browser = winexists(client, id) == "BROWSER"
// Instruct the client to signal UI when the window is closed.
winset(client, id, "on-close=\"uiclose [id]\"")
if(!is_browser)
winset(client, id, "on-close=\"uiclose [id]\"")
/**
* public
@@ -126,6 +144,28 @@
locked = FALSE
locked_by = null
/**
* public
*
* Subscribes the datum to consume window messages on a specified proc.
*
* Note, that this supports only one subscriber, because code for that
* is simpler and therefore faster. If necessary, this can be rewritten
* to support multiple subscribers.
*/
/datum/tgui_window/proc/subscribe(datum/object, delegate)
subscriber_object = object
subscriber_delegate = delegate
/**
* public
*
* Unsubscribes the datum. Do not forget to call this when cleaning up.
*/
/datum/tgui_window/proc/unsubscribe(datum/object)
subscriber_object = null
subscriber_delegate = null
/**
* public
*
@@ -157,6 +197,7 @@
client << browse(null, "window=[id]")
if(!logout && client)
winset(client, null, "mapwindow.map.focus=true")
/**
* public
*
@@ -166,25 +207,40 @@
* required payload list Message payload
* optional force bool Send regardless of the ready status.
*/
/datum/tgui_window/proc/send_message(type, list/payload, force)
/datum/tgui_window/proc/send_message(type, payload, force)
if(!client)
return
var/message = json_encode(list(
"type" = type,
"payload" = payload,
))
// Strip #255/improper.
message = replacetext(message, "\proper", "")
message = replacetext(message, "\improper", "")
// Pack for sending via output()
message = url_encode(message)
var/message = TGUI_CREATE_MESSAGE(type, payload)
// Place into queue if window is still loading
if(!force && status != TGUI_WINDOW_READY)
if(!message_queue)
message_queue = list()
message_queue += list(message)
return
client << output(message, "[id].browser:update")
client << output(message, is_browser \
? "[id]:update" \
: "[id].browser:update")
/**
* public
*
* Sends a raw payload to tgui window.
*
* required message string JSON+urlencoded blob to send.
* optional force bool Send regardless of the ready status.
*/
/datum/tgui_window/proc/send_raw_message(message, force)
if(!client)
return
// Place into queue if window is still loading
if(!force && status != TGUI_WINDOW_READY)
if(!message_queue)
message_queue = list()
message_queue += list(message)
return
client << output(message, is_browser \
? "[id]:update" \
: "[id].browser:update")
/**
* public
@@ -192,16 +248,18 @@
* Makes an asset available to use in tgui.
*
* required asset datum/asset
*
* return bool - TRUE if any assets had to be sent to the client
*/
/datum/tgui_window/proc/send_asset(datum/asset/asset)
if(!client || !asset)
return
sent_assets |= list(asset)
. = asset.send(client)
if(istype(asset, /datum/asset/spritesheet))
var/datum/asset/spritesheet/spritesheet = asset
send_message("asset/stylesheet", spritesheet.css_filename())
send_message("asset/mappings", asset.get_url_mappings())
sent_assets += list(asset)
asset.send(client)
/**
* private
@@ -212,34 +270,56 @@
if(!client || !message_queue)
return
for(var/message in message_queue)
client << output(message, "[id].browser:update")
client << output(message, is_browser \
? "[id]:update" \
: "[id].browser:update")
message_queue = null
/**
* private
*
* Callback for handling incoming tgui messages.
*/
/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
switch(type)
if("ready")
// Status can be READY if user has refreshed the window.
if(status == TGUI_WINDOW_READY)
// Resend the assets
for(var/asset in sent_assets)
send_asset(asset)
status = TGUI_WINDOW_READY
if("log")
if(href_list["fatal"])
fatally_errored = TRUE
/datum/tgui_window/proc/on_message(type, payload, href_list)
// Status can be READY if user has refreshed the window.
if(type == "ready" && status == TGUI_WINDOW_READY)
// Resend the assets
for(var/asset in sent_assets)
send_asset(asset)
// Mark this window as fatally errored which prevents it from
// being suspended.
if(type == "log" && href_list["fatal"])
fatally_errored = TRUE
// Mark window as ready since we received this message from somewhere
if(status != TGUI_WINDOW_READY)
status = TGUI_WINDOW_READY
flush_message_queue()
// Pass message to UI that requested the lock
if(locked && locked_by)
locked_by.on_message(type, payload, href_list)
flush_message_queue()
return
var/prevent_default = locked_by.on_message(type, payload, href_list)
if(prevent_default)
return
// Pass message to the subscriber
else if(subscriber_object)
var/prevent_default = call(
subscriber_object,
subscriber_delegate)(type, payload, href_list)
if(prevent_default)
return
// If not locked, handle these message types
switch(type)
if("ping")
send_message("pingReply", payload)
if("suspend")
close(can_be_suspended = TRUE)
if("close")
close(can_be_suspended = FALSE)
if("openLink")
client << link(href_list["url"])
if("cacheReloaded")
// Reinitialize
initialize(inline_assets = inline_assets, fancy = fancy)
// Resend the assets
for(var/asset in sent_assets)
send_asset(asset)
+6
View File
@@ -40,6 +40,12 @@
-->
<div class="commit sansserif">
<h2 class="date">20 June 2021</h2>
<h3 class="author">MoondancerPony updated:</h3>
<ul class="changes bgimages16">
<li class="experiment">Hopefully made cooking recipe selection/calculation much faster on the backend.</li>
</ul>
<h2 class="date">22 May 2021</h2>
<h3 class="author">None updated:</h3>
<ul class="changes bgimages16">
+4
View File
@@ -5573,3 +5573,7 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- maptweak: Move Syringe Gun from grenade crate onto the table, remove extra box.
- rscadd: "/\U0001F191"
- rscadd: 'Resolves #10274'
2021-06-20:
MoondancerPony:
- experiment: Hopefully made cooking recipe selection/calculation much faster on
the backend.
+37
View File
@@ -0,0 +1,37 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# wip (For works in progress)
# tweak
# soundadd
# sounddel
# rscadd (general adding of nice things)
# rscdel (general deleting of nice things)
# imageadd
# imagedel
# maptweak
# spellcheck (typo fixes)
# experiment
#################################
# Your name.
author: MoondancerPony
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
changes:
- bugfix: "Foods will no longer have improper grammar and capitalisation when eaten with utensils."
- tweak: "Refactors utensils to use weakrefs rather than stored strings; also does a bunch of code cleanup."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

+38 -4
View File
@@ -37224,7 +37224,14 @@ aa
/area/talon_v2/engineering/star_store)
"fd" = (
/obj/structure/table/rack/shelf/steel,
/obj/item/clothing/shoes/magboots,
/obj/item/clothing/head/beret/talon,
/obj/item/clothing/head/beret/talon,
/obj/item/clothing/head/beret/talon,
/obj/item/clothing/head/beret/talon,
/obj/item/clothing/suit/storage/hooded/wintercoat/talon,
/obj/item/clothing/suit/storage/hooded/wintercoat/talon,
/obj/item/clothing/suit/storage/hooded/wintercoat/talon,
/obj/item/clothing/suit/storage/hooded/wintercoat/talon,
/turf/simulated/floor/tiled/techfloor,
/area/talon_v2/secure_storage)
"ff" = (
@@ -41267,6 +41274,15 @@ aa
/obj/structure/lattice,
/turf/space,
/area/space)
"tn" = (
/obj/structure/table/rack/shelf/steel,
/obj/machinery/recharger/wallcharger{
pixel_x = 5;
pixel_y = 24
},
/obj/item/weapon/storage/backpack/dufflebag/talon,
/turf/simulated/floor/tiled/techfloor,
/area/talon_v2/armory)
"to" = (
/obj/machinery/light{
dir = 4
@@ -45897,6 +45913,13 @@ aa
},
/turf/simulated/floor/tiled/techmaint,
/area/talon_v2/central_hallway/fore)
"IX" = (
/obj/structure/table/rack/shelf/steel,
/obj/item/clothing/shoes/magboots,
/obj/item/clothing/shoes/magboots,
/obj/item/clothing/shoes/magboots,
/turf/simulated/floor/tiled/techfloor,
/area/talon_v2/secure_storage)
"IY" = (
/obj/structure/cable/green{
d1 = 1;
@@ -49772,6 +49795,17 @@ aa
"WJ" = (
/turf/simulated/wall/shull,
/area/talon_v2/crew_quarters/eng_room)
"WK" = (
/obj/structure/table/woodentable,
/obj/item/clothing/accessory/talon{
pixel_x = 4;
pixel_y = 7
},
/obj/item/clothing/head/caphat/talon{
pixel_x = -5
},
/turf/simulated/floor/wood,
/area/talon_v2/crew_quarters/cap_room)
"WM" = (
/obj/machinery/atmospherics/unary/engine/bigger{
dir = 1
@@ -58381,7 +58415,7 @@ Dd
ZF
KC
Oj
Pm
tn
nK
gg
SX
@@ -58518,7 +58552,7 @@ AR
XG
zv
XG
ln
IX
Dd
lW
KC
@@ -59486,7 +59520,7 @@ NV
oC
oC
NV
gm
WK
gm
Nq
Ht
@@ -7,3 +7,8 @@
display_name = "helmet, colored pilot (Pilot)"
path = /obj/item/clothing/head/pilot_vr/alt
allowed_roles = list("Pilot")
/datum/gear/head/pilot/talon
display_name = "helmet, ITV pilot (Pilot)"
path = /obj/item/clothing/head/pilot_vr/talon
allowed_roles = list("Pilot")
+37
View File
@@ -25023,6 +25023,43 @@
/obj/structure/window/reinforced{
dir = 1
},
<<<<<<< HEAD
||||||| parent of 30b7ea7639... Merge pull request #10652 from Enzo-Leon/medical
/obj/structure/cable/green{
d2 = 8;
icon_state = "0-8"
},
/obj/structure/disposalpipe/segment{
dir = 4
},
/obj/machinery/power/apc{
name = "south bump";
pixel_y = -28;
req_access = list(67)
},
/obj/effect/floor_decal/borderfloorwhite,
/obj/effect/floor_decal/corner/paleblue/border,
/obj/effect/floor_decal/borderfloorwhite/corner2,
/obj/effect/floor_decal/corner/paleblue/bordercorner2,
=======
/obj/structure/cable/green{
d2 = 8;
icon_state = "0-8"
},
/obj/structure/disposalpipe/segment{
dir = 4
},
/obj/machinery/power/apc{
name = "south bump";
nightshift_setting = 0;
pixel_y = -28;
req_access = list(67)
},
/obj/effect/floor_decal/borderfloorwhite,
/obj/effect/floor_decal/corner/paleblue/border,
/obj/effect/floor_decal/borderfloorwhite/corner2,
/obj/effect/floor_decal/corner/paleblue/bordercorner2,
>>>>>>> 30b7ea7639... Merge pull request #10652 from Enzo-Leon/medical
/turf/simulated/floor/tiled/white,
/area/tether/surfacebase/medical/uppersouthstairwell)
"aPs" = (
+23 -8
View File
@@ -132,6 +132,7 @@
/datum/alt_title/talon_security
title = "Talon Security"
//////////////////////TALON OUTFITS//////////////////////
/decl/hierarchy/outfit/job/talon_captain
name = OUTFIT_JOB_NAME("Talon Captain")
@@ -142,11 +143,11 @@
l_ear = /obj/item/device/radio/headset/talon
glasses = /obj/item/clothing/glasses/sunglasses
uniform = /obj/item/clothing/under/rank/captain
uniform = /obj/item/clothing/under/rank/talon/command
shoes = /obj/item/clothing/shoes/brown
backpack = /obj/item/weapon/storage/backpack/captain
satchel_one = /obj/item/weapon/storage/backpack/satchel/cap
messenger_bag = /obj/item/weapon/storage/backpack/messenger/com
backpack = /obj/item/weapon/storage/backpack/talon
satchel_one = /obj/item/weapon/storage/backpack/satchel/talon
messenger_bag = /obj/item/weapon/storage/backpack/messenger/talon
/decl/hierarchy/outfit/job/talon_pilot
name = OUTFIT_JOB_NAME("Talon Pilot")
@@ -159,11 +160,15 @@
l_ear = /obj/item/device/radio/headset/talon
shoes = /obj/item/clothing/shoes/black
uniform = /obj/item/clothing/under/rank/pilot1/no_webbing
head = /obj/item/clothing/head/pilot_vr/talon
uniform = /obj/item/clothing/under/rank/talon/pilot
suit = /obj/item/clothing/suit/storage/toggle/bomber/pilot
gloves = /obj/item/clothing/gloves/fingerless
glasses = /obj/item/clothing/glasses/fakesunglasses/aviator
uniform_accessories = list(/obj/item/clothing/accessory/storage/webbing/pilot1 = 1)
backpack = /obj/item/weapon/storage/backpack/talon
satchel_one = /obj/item/weapon/storage/backpack/satchel/talon
messenger_bag = /obj/item/weapon/storage/backpack/messenger/talon
/decl/hierarchy/outfit/job/talon_doctor
name = OUTFIT_JOB_NAME("Talon Doctor")
@@ -178,10 +183,13 @@
backpack = /obj/item/weapon/storage/backpack/medic
satchel_one = /obj/item/weapon/storage/backpack/satchel/med
messenger_bag = /obj/item/weapon/storage/backpack/messenger/med
uniform = /obj/item/clothing/under/rank/medical
uniform = /obj/item/clothing/under/rank/talon/proper
suit = /obj/item/clothing/suit/storage/toggle/labcoat
l_hand = /obj/item/weapon/storage/firstaid/regular
r_pocket = /obj/item/device/flashlight/pen
backpack = /obj/item/weapon/storage/backpack/talon
satchel_one = /obj/item/weapon/storage/backpack/satchel/talon
messenger_bag = /obj/item/weapon/storage/backpack/messenger/talon
/decl/hierarchy/outfit/job/talon_security
name = OUTFIT_JOB_NAME("Talon Security")
@@ -198,8 +206,11 @@
backpack = /obj/item/weapon/storage/backpack/security
satchel_one = /obj/item/weapon/storage/backpack/satchel/sec
messenger_bag = /obj/item/weapon/storage/backpack/messenger/sec
uniform = /obj/item/clothing/under/rank/security
uniform = /obj/item/clothing/under/rank/talon/security
l_pocket = /obj/item/device/flash
backpack = /obj/item/weapon/storage/backpack/talon
satchel_one = /obj/item/weapon/storage/backpack/satchel/talon
messenger_bag = /obj/item/weapon/storage/backpack/messenger/talon
/decl/hierarchy/outfit/job/talon_engineer
name = OUTFIT_JOB_NAME("Talon Engineer")
@@ -217,5 +228,9 @@
backpack = /obj/item/weapon/storage/backpack/industrial
satchel_one = /obj/item/weapon/storage/backpack/satchel/eng
messenger_bag = /obj/item/weapon/storage/backpack/messenger/engi
uniform = /obj/item/clothing/under/rank/atmospheric_technician
uniform = /obj/item/clothing/under/rank/talon/basic
belt = /obj/item/weapon/storage/belt/utility/atmostech
backpack = /obj/item/weapon/storage/backpack/talon
satchel_one = /obj/item/weapon/storage/backpack/satchel/talon
messenger_bag = /obj/item/weapon/storage/backpack/messenger/talon
+1 -1
View File
@@ -1,6 +1,6 @@
/.yarn/**
/**/node_modules
/**/*.bundle.*
/**/*.chunk.*
/**/*.hot-update.*
/packages/inferno/**
/packages/tgui/public/shim-*.js
+39 -10
View File
@@ -1,14 +1,43 @@
rules:
## Enforce a maximum cyclomatic complexity allowed in a program
complexity: [error, { max: 25 }]
# complexity: [warn, { max: 25 }]
## Enforce consistent brace style for blocks
brace-style: [error, stroustrup, { allowSingleLine: false }]
# brace-style: [warn, stroustrup, { allowSingleLine: false }]
## Enforce the consistent use of either backticks, double, or single quotes
quotes: [error, single, {
avoidEscape: true,
allowTemplateLiterals: true,
}]
react/jsx-closing-bracket-location: [error, {
selfClosing: after-props,
nonEmpty: after-props,
}]
# quotes: [warn, single, {
# avoidEscape: true,
# allowTemplateLiterals: true,
# }]
# react/jsx-closing-bracket-location: [warn, {
# selfClosing: after-props,
# nonEmpty: after-props,
# }]
# react/display-name: warn
## Radar
## ------------------------------------------------------
# radar/cognitive-complexity: warn
radar/max-switch-cases: warn
radar/no-all-duplicated-branches: warn
radar/no-collapsible-if: warn
radar/no-collection-size-mischeck: warn
radar/no-duplicate-string: warn
radar/no-duplicated-branches: warn
radar/no-element-overwrite: warn
radar/no-extra-arguments: warn
radar/no-identical-conditions: warn
radar/no-identical-expressions: warn
radar/no-identical-functions: warn
radar/no-inverted-boolean-check: warn
radar/no-one-iteration-loop: warn
radar/no-redundant-boolean: warn
radar/no-redundant-jump: warn
radar/no-same-line-conditional: warn
radar/no-small-switch: warn
radar/no-unused-collection: warn
radar/no-use-of-empty-return-value: warn
radar/no-useless-catch: warn
radar/prefer-immediate-return: warn
radar/prefer-object-literal: warn
radar/prefer-single-boolean-return: warn
radar/prefer-while: warn
+16 -15
View File
@@ -1,6 +1,7 @@
parser: babel-eslint
root: true
parser: '@typescript-eslint/parser'
parserOptions:
ecmaVersion: 2019
ecmaVersion: 2020
sourceType: module
ecmaFeatures:
jsx: true
@@ -8,9 +9,8 @@ env:
es6: true
browser: true
node: true
globals:
Byond: readonly
plugins:
- radar
- react
settings:
react:
@@ -19,7 +19,6 @@ rules:
## Possible Errors
## ----------------------------------------
## Enforce “for” loop update clause moving the counter in the right
## direction.
# for-direction: error
@@ -114,7 +113,7 @@ rules:
## Require return statements to either always or never specify values
# consistent-return: error
## Enforce consistent brace style for all control statements
curly: [error, all]
curly: [error, multi-line]
## Require default cases in switch statements
# default-case: error
## Enforce default parameters to be last
@@ -279,7 +278,8 @@ rules:
no-shadow-restricted-names: error
## Disallow the use of undeclared variables unless mentioned
## in /*global*/ comments
no-undef: error
## NOTE: Pointless when TypeScript can check for this
# no-undef: error
## Disallow initializing variables to undefined
no-undef-init: error
## Disallow the use of undefined as an identifier
@@ -369,11 +369,12 @@ rules:
# max-depth: error
## Enforce a maximum line length
max-len: [error, {
code: 120, # Bump to 140 if this is still too in the way
code: 120,
## Ignore imports
ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
ignoreUrls: true,
ignoreRegExpLiterals: true,
ignoreStrings: true,
}]
## Enforce a maximum number of lines per file
# max-lines: error
@@ -506,7 +507,7 @@ rules:
## Require braces around arrow function bodies
# arrow-body-style: error
## Require parentheses around arrow function arguments
arrow-parens: [error, as-needed]
# arrow-parens: [error, as-needed]
## Enforce consistent spacing before and after the arrow in arrow functions
arrow-spacing: [error, { before: true, after: true }]
## Require super() calls in constructors
@@ -582,7 +583,7 @@ rules:
## Rule enforces consistent usage of destructuring assignment in component
# react/destructuring-assignment: [error, always, { ignoreClassFields: true }]
## Prevent missing displayName in a React component definition
react/display-name: error
# react/display-name: error
## Forbid certain props on Components
# react/forbid-component-props: error
## Forbid certain props on DOM Nodes
@@ -683,11 +684,11 @@ rules:
## expressions.
# react/jsx-child-element-spacing: error
## Validate closing bracket location in JSX (fixable)
react/jsx-closing-bracket-location: [error, {
## NOTE: Not really sure about enforcing this one
selfClosing: false,
nonEmpty: after-props,
}]
# react/jsx-closing-bracket-location: [error, {
# ## NOTE: Not really sure about enforcing this one
# selfClosing: false,
# nonEmpty: after-props,
# }]
## Validate closing tag location in JSX (fixable)
react/jsx-closing-tag-location: error
## Enforce or disallow newlines inside of curly braces in JSX attributes and
+3
View File
@@ -17,3 +17,6 @@ bin/tgui text eol=lf
## Treat bundles as binary and ignore them during conflicts
*.bundle.* binary merge=tgui-merge-bundle
*.chunk.* binary merge=tgui-merge-bundle
.yarn/releases/**/* binary
.yarn/plugins/**/* binary
+16 -3
View File
@@ -1,7 +1,20 @@
## NPM garbage
node_modules
*.log
package-lock.json
/packages/tgui/public/.tmp/**/*
/packages/tgui/public/**/*.hot-update.*
/packages/tgui/public/**/*.map
## Yarn stuff
/.pnp.*
/.yarn/*
!/.yarn/releases
!/.yarn/plugins
!/.yarn/sdks
!/.yarn/versions
!/.yarn/lock.yml
## Build artifacts
/public/.tmp/**/*
## Previously ignored locations that are kept to avoid confusing git
## while transitioning to a new project structure.
/packages/tgui/public/**
+12
View File
@@ -0,0 +1,12 @@
arrowParens: always
bracketSpacing: true
endOfLine: lf
jsxBracketSameLine: true
jsxSingleQuote: false
printWidth: 120
proseWrap: preserve
quoteProps: preserve
semi: true
singleQuote: true
tabWidth: 2
trailingComma: es5
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/lib/api.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/lib/api.js your application uses
module.exports = absRequire(`eslint/lib/api.js`);
+6
View File
@@ -0,0 +1,6 @@
{
"name": "eslint",
"version": "7.21.0-pnpify",
"main": "./lib/api.js",
"type": "commonjs"
}
+5
View File
@@ -0,0 +1,5 @@
# This file is automatically generated by PnPify.
# Manual changes will be lost!
integrations:
- vscode
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsc your application uses
module.exports = absRequire(`typescript/bin/tsc`);
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsserver your application uses
module.exports = absRequire(`typescript/bin/tsserver`);
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsc.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsc.js your application uses
module.exports = absRequire(`typescript/lib/tsc.js`);
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
const moduleWrapper = tsserver => {
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes is much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = pnpApi.resolveVirtual(str);
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
str = resolved;
}
}
str = str.replace(/\\/g, `/`)
str = str.replace(/^\/?/, `/`);
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
if (str.match(/\.zip\//)) {
str = `${isVSCode ? `^` : ``}zip:${str}`;
}
}
return str;
}
function fromEditorPath(str) {
return process.platform === `win32`
? str.replace(/^\^?zip:\//, ``)
: str.replace(/^\^?zip:/, ``);
}
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let isVSCode = false;
return Object.assign(Session.prototype, {
onMessage(/** @type {string} */ message) {
const parsedMessage = JSON.parse(message)
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
parsedMessage.arguments.hostInfo === `vscode`
) {
isVSCode = true;
}
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
return typeof value === `string` ? fromEditorPath(value) : value;
}));
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsserver.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/typescript.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/typescript.js your application uses
module.exports = absRequire(`typescript/lib/typescript.js`);
+6
View File
@@ -0,0 +1,6 @@
{
"name": "typescript",
"version": "4.2.3-pnpify",
"main": "./lib/typescript.js",
"type": "commonjs"
}
+17
View File
@@ -0,0 +1,17 @@
enableScripts: false
logFilters:
- code: YN0004
level: discard
- code: YN0062
level: discard
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
preferAggregateCacheInfo: true
preferInteractive: true
yarnPath: .yarn/releases/yarn-2.4.1.cjs
+68 -51
View File
@@ -37,30 +37,41 @@ to translate concepts between old and new tgui, read this
You will need these programs to start developing in tgui:
- [Node v12.13+](https://nodejs.org/en/download/)
- [Yarn v1.19+](https://yarnpkg.com/en/docs/install)
- [MSys2](https://www.msys2.org/) (optional)
- [Node v12.18.3+](https://nodejs.org/en/download/)
- [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) (optional)
- [Git Bash](https://git-scm.com/downloads)
or [MSys2](https://www.msys2.org/) (optional)
> MSys2 closely replicates a unix-like environment which is necessary for
> the `bin/tgui` script to run. It comes with a robust "mintty" terminal
> emulator which is better than any standard Windows shell, it supports
> "git" out of the box (almost like Git for Windows, but better), has
> a "pacman" package manager, and you can install a text editor like "vim"
> for a full boomer experience.
**DO NOT install Chocolatey if Node installer asks you to!**
## Usage
**For MSys2, Git Bash, WSL, Linux or macOS users:**
**For Git Bash, MSys2, WSL, Linux or macOS users:**
First and foremost, change your directory to `tgui`.
Change your directory to `tgui`.
Run `bin/tgui --install-git-hooks` (optional) to install merge drivers
which will assist you in conflict resolution when rebasing your branches.
Run `bin/tgui --install-git-hooks` to install merge drivers which will
assist you in conflict resolution when rebasing your branches. Only has
to be done once.
Run one of the following:
Run `bin/tgui` with any of the options listed below.
- `bin/tgui` - build the project in production mode.
- `bin/tgui --dev` - launch a development server.
**For Windows CMD or PowerShell users:**
If you haven't opened the console already, you can do that by holding
Shift and right clicking on the `tgui` folder, then pressing
either `Open command window here` or `Open PowerShell window here`.
Run `.\bin\tgui.bat` with any of the options listed below.
> If using PowerShell, you will receive errors if trying to run
> `.\bin\tgui.ps1`, because default Windows policy does not allow direct
> execution of PS1 scripts. Run `.\bin\tgui.bat` instead.
**Available commands:**
- `bin/tgui` - Build the project in production mode.
- `bin/tgui --dev` - Launch a development server.
- tgui development server provides you with incremental compilation,
hot module replacement and logging facilities in all running instances
of tgui. In short, this means that you will instantly see changes in the
@@ -70,36 +81,24 @@ Run one of the following:
in the lobby. Start tgui dev server, and once it has finished building,
press F5 on any tgui window. You'll know that it's hooked correctly if
you see a green bug icon in titlebar and data gets dumped to the console.
- `bin/tgui --dev --reload` - reload byond cache once.
- `bin/tgui --dev --debug` - run server with debug logging enabled.
- `bin/tgui --dev --no-hot` - disable hot module replacement (helps when
- `bin/tgui --dev --reload` - Reload byond cache once.
- `bin/tgui --dev --debug` - Run server with debug logging enabled.
- `bin/tgui --dev --no-hot` - Disable hot module replacement (helps when
doing development on IE8).
- `bin/tgui --lint` - show problems with the code.
- `bin/tgui --lint --fix` - auto-fix problems with the code.
- `bin/tgui --analyze` - run a bundle analyzer.
- `bin/tgui --clean` - clean up project repo.
- `bin/tgui [webpack options]` - build the project with custom webpack
- `bin/tgui --lint` - Show problems with the code.
- `bin/tgui --fix` - Auto-fix problems with the code.
- `bin/tgui --test` - Run tests.
- `bin/tgui --analyze` - Run a bundle analyzer.
- `bin/tgui --clean` - Clean up project repo.
- `bin/tgui [webpack options]` - Build the project with custom webpack
options.
**For everyone else:**
**For virgins:**
If you haven't opened the console already, you can do that by holding
Shift and right clicking on the `tgui` folder, then pressing
either `Open command window here` or `Open PowerShell window here`.
You can double-click these batch files to achieve the same thing:
Run `yarn install` to install npm dependencies, then one of the following:
- `yarn run build` - build the project in production mode.
- `yarn run watch` - launch a development server.
- `yarn run lint` - show problems with the code.
- `yarn run lint --fix` - auto-fix problems with the code.
- `yarn run analyze` - run a bundle analyzer.
We also got some batch files in store, for those who don't like fiddling
with the console:
- `bin/tgui-build.bat` - build the project in production mode.
- `bin/tgui-dev-server.bat` - launch a development server.
- `bin\tgui.bat` - Build the project in production mode.
- `bin\tgui-dev-server.bat` - Launch a development server.
> Remember to always run a full build before submitting a PR. It creates
> a compressed javascript bundle which is then referenced from DM code.
@@ -108,21 +107,36 @@ with the console:
## Troubleshooting
**Development server is crashing**
Make sure path to your working directory does not contain spaces or special
unicode characters. If so, move codebase to a location which does not contain
spaces or unicode characters.
This is a known issue with Yarn Berry, and fix is going to happen someday.
**Development server doesn't find my BYOND cache!**
This happens if your Documents folder in Windows has a custom location, for
example in `E:\Libraries\Documents`. Development server has no knowledge
of these non-standard locations, therefore you have to run the dev server
with an additional environmental variable, with a full path to BYOND cache.
example in `E:\Libraries\Documents`. Development server tries its best to find
this non-standard location (searches for a Windows Registry key), but it can
fail. You have to run the dev server with an additional environmental
variable, with a full path to BYOND cache.
```
export BYOND_CACHE="E:/Libraries/Documents/BYOND/cache"
bin/tgui --dev
BYOND_CACHE="E:/Libraries/Documents/BYOND/cache"
```
Note that in Windows, you have to go through Advanced System Settings,
System Properties and then open Environment Variables window to do the
same thing. You may need to reboot after this.
**Webpack errors out with some cryptic messages!**
> Example: `No template for dependency: PureExpressionDependency`
Webpack stores its cache on disk since tgui 4.3, and it is very sensitive
to build configuration. So if you update webpack, or share the same cache
directory between development and production build, it will start
hallucinating.
To fix this kind of problem, run `bin/tgui --clean` and try again.
## Developer Tools
@@ -181,8 +195,11 @@ See: [Component Reference](docs/component-reference.md).
## License
All code is licensed with the parent license of *tgstation*, **AGPL-3.0**.
Source code is covered by /tg/station's parent license - **AGPL-3.0**
(see the main [README](../README.md)), unless otherwise indicated.
See the main [README](../README.md) for more details.
Some files are annotated with a copyright header, which explicitly states
the copyright holder and license of the file. Most of the core tgui
source code is available under the **MIT** license.
The Authors retain all copyright to their respective work here submitted.
+43
View File
@@ -0,0 +1,43 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
const createBabelConfig = options => {
const { mode, presets = [], plugins = [] } = options;
return {
presets: [
[require.resolve('@babel/preset-typescript'), {
allowDeclareFields: true,
}],
[require.resolve('@babel/preset-env'), {
modules: 'commonjs',
useBuiltIns: 'entry',
corejs: '3.10',
spec: false,
loose: true,
targets: [],
}],
...presets,
],
plugins: [
[require.resolve('@babel/plugin-proposal-class-properties'), {
loose: true,
}],
require.resolve('@babel/plugin-transform-jscript'),
require.resolve('babel-plugin-inferno'),
require.resolve('babel-plugin-transform-remove-console'),
require.resolve('common/string.babel-plugin.cjs'),
...plugins,
],
};
};
module.exports = api => {
api.cache(true);
const mode = process.env.NODE_ENV;
return createBabelConfig({ mode });
};
module.exports.createBabelConfig = createBabelConfig;
+78 -24
View File
@@ -1,4 +1,7 @@
#!/bin/bash
#!/usr/bin/env bash
## Copyright (c) 2020 Aleksej Komarov
## SPDX-License-Identifier: MIT
set -e
shopt -s globstar
shopt -s expand_aliases
@@ -23,8 +26,19 @@ fi
base_dir="$(dirname "$(tgui-realpath "${0}")")/.."
base_dir="$(tgui-realpath "${base_dir}")"
## Add locally installed node programs to path
PATH="${PATH}:node_modules/.bin"
## Make use of nvm if it exists
if [[ -e "${HOME}/.nvm/nvm.sh" ]]; then
source "${HOME}/.nvm/nvm.sh"
fi
## Fall back to running Yarn from the repo
if ! hash yarn 2>/dev/null; then
yarn_releases=("${base_dir}"/.yarn/releases/yarn-*.cjs)
yarn_release="${yarn_releases[0]}"
yarn() {
node "${yarn_release}" "${@}"
}
fi
## Functions
@@ -38,32 +52,54 @@ task-install() {
## Runs webpack
task-webpack() {
cd "${base_dir}/packages/tgui"
webpack "${@}"
cd "${base_dir}"
yarn run webpack-cli "${@}"
}
## Runs a development server
task-dev-server() {
cd "${base_dir}/packages/tgui-dev-server"
exec node --experimental-modules index.js "${@}"
cd "${base_dir}"
yarn node packages/tgui-dev-server/index.esm.js "${@}"
}
## Run a linter through all packages
task-eslint() {
task-lint() {
cd "${base_dir}"
eslint ./packages "${@}"
yarn run tsc
echo "tgui: type check passed"
yarn run eslint packages --ext .js,.cjs,.ts,.tsx "${@}"
echo "tgui: eslint check passed"
}
task-test() {
cd "${base_dir}"
yarn run jest
}
## Mr. Proper
task-clean() {
cd "${base_dir}"
rm -rf packages/tgui/public/.tmp
## Build artifacts
rm -rf public/.tmp
rm -f public/*.map
rm -f public/*.chunk.*
rm -f public/*.bundle.*
rm -f public/*.hot-update.*
## Yarn artifacts
rm -rf .yarn/cache
rm -rf .yarn/unplugged
rm -rf .yarn/webpack
rm -f .yarn/build-state.yml
rm -f .yarn/install-state.gz
rm -f .yarn/install-target
rm -f .pnp.js
## NPM artifacts
rm -rf **/node_modules
rm -f **/package-lock.json
}
## Validates current build against the build stored in git
## VOREStation Addition Start
task-validate-build() {
cd "${base_dir}"
local diff
@@ -75,6 +111,7 @@ task-validate-build() {
fi
echo "tgui: build is ok"
}
## VOREStation Addition End
## Installs merge drivers and git hooks
task-install-git-hooks() {
@@ -118,16 +155,6 @@ if [[ ${1} == "--install-git-hooks" ]]; then
exit 0
fi
## Continuous integration scenario
if [[ ${1} == "--ci" ]]; then
task-clean
task-install
task-eslint
task-webpack --mode=production
task-validate-build
exit 0
fi
if [[ ${1} == "--clean" ]]; then
task-clean
exit 0
@@ -140,24 +167,44 @@ if [[ ${1} == "--dev" ]]; then
exit 0
fi
## VOREStation Addition Start
## Continuous integration scenario
if [[ ${1} == "--ci" ]]; then
task-clean
task-install
task-lint
task-test
task-webpack --mode=production
task-validate-build
exit 0
fi
## VOREStation Addition End
if [[ ${1} == '--lint' ]]; then
shift 1
task-install
task-eslint "${@}"
task-lint "${@}"
exit 0
fi
if [[ ${1} == '--lint-harder' ]]; then
shift 1
task-install
task-eslint -c .eslintrc-harder.yml "${@}"
task-lint -c .eslintrc-harder.yml "${@}"
exit 0
fi
if [[ ${1} == '--fix' ]]; then
shift 1
task-install
task-eslint --fix "${@}"
task-lint --fix "${@}"
exit 0
fi
if [[ ${1} == '--test' ]]; then
shift 1
task-install
task-test "${@}"
exit 0
fi
@@ -169,9 +216,16 @@ if [[ ${1} == '--analyze' ]]; then
fi
## Make a production webpack build
if [[ ${1} == '--build' ]]; then
task-install
task-webpack --mode=production
exit 0
fi
## Make a production webpack build + Run eslint
if [[ -z ${1} ]]; then
task-install
task-eslint
task-lint --fix
task-webpack --mode=production
exit 0
fi
-5
View File
@@ -1,5 +0,0 @@
@echo off
cd "%~dp0\.."
call yarn install
call yarn run build
timeout /t 9
+8 -3
View File
@@ -1,4 +1,9 @@
@echo off
cd "%~dp0\.."
call yarn install
call yarn run watch
rem Copyright (c) 2020 Aleksej Komarov
rem SPDX-License-Identifier: MIT
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --dev %*
rem Pause if launched in a separate shell unless initiated from powershell
echo %PSModulePath% | findstr %USERPROFILE% >NUL
if %errorlevel% equ 0 exit 0
echo %cmdcmdline% | find /i "/c"
if %errorlevel% equ 0 pause
+9
View File
@@ -0,0 +1,9 @@
@echo off
rem Copyright (c) 2020 Aleksej Komarov
rem SPDX-License-Identifier: MIT
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" %*
rem Pause if launched in a separate shell unless initiated from powershell
echo %PSModulePath% | findstr %USERPROFILE% >NUL
if %errorlevel% equ 0 exit 0
echo %cmdcmdline% | find /i "/c"
if %errorlevel% equ 0 pause
+147
View File
@@ -0,0 +1,147 @@
## Copyright (c) 2020 Aleksej Komarov
## SPDX-License-Identifier: MIT
## Initial set-up
## --------------------------------------------------------
## Enable strict mode and stop of first cmdlet error
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
## Validates exit code of external commands
function Throw-On-Native-Failure {
if (-not $?) {
exit 1
}
}
## Normalize current directory
$basedir = Split-Path $MyInvocation.MyCommand.Path
$basedir = Resolve-Path "$($basedir)\.."
Set-Location $basedir
[Environment]::CurrentDirectory = $basedir
## Functions
## --------------------------------------------------------
function yarn {
$YarnRelease = Get-ChildItem -Filter ".yarn\releases\yarn-*.cjs" | Select-Object -First 1
node ".yarn\releases\$YarnRelease" @Args
Throw-On-Native-Failure
}
function Remove-Quiet {
Remove-Item -ErrorAction SilentlyContinue @Args
}
function task-install {
yarn install
}
## Runs webpack
function task-webpack {
yarn run webpack-cli @Args
}
## Runs a development server
function task-dev-server {
yarn node "packages/tgui-dev-server/index.esm.js" @Args
}
## Run a linter through all packages
function task-lint {
yarn run tsc
Write-Output "tgui: type check passed"
yarn run eslint packages --ext ".js,.cjs,.ts,.tsx" @Args
Write-Output "tgui: eslint check passed"
}
function task-test {
yarn run jest
}
## Mr. Proper
function task-clean {
## Build artifacts
Remove-Quiet -Recurse -Force "public\.tmp"
Remove-Quiet -Force "public\*.map"
Remove-Quiet -Force "public\*.hot-update.*"
## Yarn artifacts
Remove-Quiet -Recurse -Force ".yarn\cache"
Remove-Quiet -Recurse -Force ".yarn\unplugged"
Remove-Quiet -Recurse -Force ".yarn\webpack"
Remove-Quiet -Force ".yarn\build-state.yml"
Remove-Quiet -Force ".yarn\install-state.gz"
Remove-Quiet -Force ".yarn\install-target"
Remove-Quiet -Force ".pnp.js"
## NPM artifacts
Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force
Remove-Quiet -Force "package-lock.json"
}
## Main
## --------------------------------------------------------
if ($Args.Length -gt 0) {
if ($Args[0] -eq "--clean") {
task-clean
exit 0
}
if ($Args[0] -eq "--dev") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-dev-server @Rest
exit 0
}
if ($Args[0] -eq "--lint") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-lint @Rest
exit 0
}
if ($Args[0] -eq "--lint-harder") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-lint -c ".eslintrc-harder.yml" @Rest
exit 0
}
if ($Args[0] -eq "--fix") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-lint --fix @Rest
exit 0
}
if ($Args[0] -eq "--test") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-test @Rest
exit 0
}
## Analyze the bundle
if ($Args[0] -eq "--analyze") {
task-install
task-webpack --mode=production --analyze
exit 0
}
}
## Make a production webpack build
if ($Args.Length -eq 0) {
task-install
task-lint
task-webpack --mode=production
exit 0
}
## Run webpack with custom flags
task-install
task-webpack @Args
+213 -54
View File
@@ -28,6 +28,7 @@ Make sure to add new items to this list if you document new components.
- [`Grid`](#grid)
- [`Grid.Column`](#gridcolumn)
- [`Icon`](#icon)
- [`Icon.Stack`](#iconstack)
- [`Input`](#input)
- [`Knob`](#knob)
- [`LabeledControls`](#labeledcontrols)
@@ -39,8 +40,10 @@ Make sure to add new items to this list if you document new components.
- [`NoticeBox`](#noticebox)
- [`NumberInput`](#numberinput)
- [`ProgressBar`](#progressbar)
- [`RoundGauge`](#roundgauge)
- [`Section`](#section)
- [`Slider`](#slider)
- [`Stack`](#stack)
- [`Table`](#table)
- [`Table.Row`](#tablerow)
- [`Table.Cell`](#tablecell)
@@ -183,6 +186,7 @@ all available horizontal space.
- `bold: boolean` - Make text bold.
- `italic: boolean` - Make text italic.
- `nowrap: boolean` - Stops text from wrapping.
- `preserveWhitespace: boolean` - Preserves line-breaks and spacing in text.
- `textAlign: string` - Align text inside the box.
- `left` (default)
- `center`
@@ -217,17 +221,13 @@ Buttons allow users to take actions, and make choices, with a single click.
- `selected: boolean` - Activates the button (gives it a green color).
- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
over the button.
- `tooltipPosition: string` - Position of the tooltip.
- `top` - Show tooltip above the button.
- `bottom` (default) - Show tooltip below the button.
- `left` - Show tooltip on the left of the button.
- `right` - Show tooltip on the right of the button.
- `tooltipPosition?: string` - Position of the tooltip. See [`Popper`](#Popper) for valid options.
- `ellipsis: boolean` - If button width is constrained, button text will
be truncated with an ellipsis. Be careful however, because this prop breaks
the baseline alignment.
- `title: string` - A native browser tooltip, which appears when hovering
over the button.
- `content/children: any` - Content to render inside the button.
- `children: any` - Content to render inside the button.
- `onClick: function` - Called when element is clicked.
### `Button.Checkbox`
@@ -241,12 +241,12 @@ A ghetto checkbox, made entirely using existing Button API.
### `Button.Confirm`
A button with a an extra confirmation step, using native button component.
A button with an extra confirmation step, using native button component.
**Props:**
- See inherited props: [Button](#button)
- `confirmMessage: string` - Text to display after first click; defaults to "Confirm?"
- `confirmContent: string` - Text to display after first click; defaults to "Confirm?"
- `confirmColor: string` - Color to display after first click; defaults to "bad"
### `Button.Input`
@@ -258,7 +258,7 @@ commit, while escape cancels.
**Props:**
- See inherited props: [Box](#box)
- `fluid`: fill availible horizontal space
- `fluid`: fill available horizontal space
- `onCommit: (e, value) => void`: function that is called after the user
defocuses the input or presses enter
- `currentValue: string`: default string to display when the input is shown
@@ -359,12 +359,16 @@ and displays selected entry.
**Props:**
- See inherited props: [Box](#box)
- See inherited props: [Icon](#icon)
- `options: string[]` - An array of strings which will be displayed in the
dropdown when open
- `selected: string` - Currently selected entry
- `width: number` - Width of dropdown button and resulting menu
- `over: boolean` - dropdown renders over instead of below
- `color: string` - color of dropdown button
- `over: boolean` - Dropdown renders over instead of below
- `color: string` - Color of dropdown button
- `nochevron: boolean` - Whether or not the arrow on the right hand side of the dropdown button is visible
- `noscroll: boolean` - Whether or not the dropdown menu should have a scroll bar
- `displayText: string` - Text to always display in place of the selected text
- `onClick: (e) => void` - Called when dropdown button is clicked
- `onSelected: (value) => void` - Called when a value is picked from the list, `value` is the value that was picked
@@ -384,26 +388,26 @@ to the left, and certain elements to the right:
```jsx
<Flex>
<Flex.Item>
<Flex.Item grow={1}>
Button description
</Flex.Item>
<Flex.Item grow={1} />
<Flex.Item>
<Button content="Perform an action" />
<Button>
Perform an action
</Button>
</Flex.Item>
</Flex>
```
Flex item with `grow` property serves as a "filler", to separate the other
two flex items as far as possible from each other.
Flex item with `grow` property will grow to take all available empty space,
while flex items without grow will take the minimum amount of space. This
effectively places the last flex item to the very end of the flex container.
**Props:**
- See inherited props: [Box](#box)
- `spacing: number` - Spacing between flex items, in integer units
(1 unit - 0.5em). Does not directly relate to a flex css property
(adds a modifier class under the hood), and only integer numbers are
supported.
- ~~`spacing: number`~~ - **Removed in tgui 4.3**,
use [Stack](#stack) instead.
- `inline: boolean` - Makes flexbox container inline, with similar behavior
to an `inline` property on a `Box`.
- `direction: string` - This establishes the main-axis, thus defining the
@@ -450,16 +454,16 @@ when they overflow the line.
- `order: number` - By default, flex items are laid out in the source order.
However, the order property controls the order in which they appear in the
flex container.
- `grow: number` - This defines the ability for a flex item to grow if
necessary. It accepts a unitless value that serves as a proportion. It
- `grow: number | boolean` - This defines the ability for a flex item to grow
if necessary. It accepts a unitless value that serves as a proportion. It
dictates what amount of the available space inside the flex container the
item should take up. This number is unit-less and is relative to other
siblings.
- `shrink: number` - This defines the ability for a flex item to shrink
if necessary. Inverse of `grow`.
- `basis: string` - This defines the default size of an element before any
flex-related calculations are done. Has to be a length (e.g. `20%`, `5rem`),
an `auto` or `content` keyword.
- `shrink: number | boolean` - This defines the ability for a flex item to
shrink if necessary. Inverse of `grow`.
- `basis: number | string` - This defines the default size of an element
before any flex-related calculations are done. Has to be a length
(e.g. `20%`, `5rem`), an `auto` or `content` keyword.
- **Important:** IE11 flex is buggy, and auto width/height calculations
can sometimes end up in a circular dependency. This usually happens, when
working with tables inside flex (they have wacky internal widths and such).
@@ -527,6 +531,22 @@ Fractional numbers are supported.
- `spin: boolean` - Whether an icon should be spinning. Good for load
indicators.
### `Icon.Stack`
Renders children icons on top of each other in order to make your own icon.
```jsx
<Icon.Stack>
<Icon name="pen" />
<Icon name="slash" />
</Icon.Stack>
```
**Props:**
- See inherited props: [Box](#box)
- `children: Icon` - Icons to stack.
### `Input`
A basic text input, which allow users to enter text into a UI.
@@ -625,7 +645,9 @@ to perform some sort of action), there is a way to do that:
<LabeledList.Item
label="Item"
buttons={(
<Button content="Click me!" />
<Button>
Click me!
</Button>
)}>
Content
</LabeledList.Item>
@@ -643,7 +665,7 @@ to perform some sort of action), there is a way to do that:
- `label: string` - Item label.
- `color: string` - Sets the color of the text.
- `buttons: any` - Buttons to render aside the content.
- `content/children: any` - Content of this labeled item.
- `children: any` - Content of this labeled item.
### `LabeledList.Divider`
@@ -718,6 +740,16 @@ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
when you drag the input up and down, on release and on manual editing.
### `Popper`
Popper lets you position elements so that they don't go out of the bounds of the window. See [popper.js](https://popper.js.org/) for more information.
**Props:**
- `popperContent: InfernoNode` - The content that will be put inside the popper.
- `options?: { ... }` - An object of options to pass to `createPopper`. See [https://popper.js.org/docs/v2/constructors/#options], but the one you want most is `placement`. Valid placements are "bottom", "top", "left", and "right". You can affix "-start" and "-end" to achieve something like top left or top right respectively. You can also use "auto" (with an optional "-start" or "-end"), where a best fit will be chosen.
- `additionalStyles: { ... }` - A map of CSS styles to add to the element that will contain the popper.
### `ProgressBar`
Progress indicators inform users about the status of ongoing processes.
@@ -748,7 +780,39 @@ percentage and how filled the bar is.
- `ranges: { color: [from, to] }` - Applies a `color` to the progress bar
based on whether the value lands in the range between `from` and `to`.
- `color: string` - Color of the progress bar.
- `content/children: any` - Content to render inside the progress bar.
- `children: any` - Content to render inside the progress bar.
### `RoundGauge`
The RoundGauge component provides a visual representation of a single metric, as well as being capable of showing informational or cautionary boundaries related to that metric.
```jsx
<RoundGauge
size={1.75}
value={tankPressure}
minValue={0}
maxValue={pressureLimit}
alertAfter={pressureLimit * 0.70}
ranges={{
"good": [0, pressureLimit * 0.70],
"average": [pressureLimit * 0.70, pressureLimit * 0.85],
"bad": [pressureLimit * 0.85, pressureLimit],
}}
format={formatPressure} />
```
The alert on the gauge is optional, and will only be shown if the `alertAfter` prop is defined. When defined, the alert will begin to flash the respective color upon which the needle currently rests, as defined in the `ranges` prop.
**Props:**
- See inherited props: [Box](#box)
- `value: number` - The current value of the metric.
- `minValue: number` (default: 0) - The lower bound of the guage.
- `maxValue: number` (default: 1) - The upper bound of the guage.
- `ranges: { color: [from, to] }` (default: `{ "good": [0, 1] }`) - Provide regions of the guage to color between two specified values of the metric.
- `alertAfter: number` (optional) - When provided, will cause an alert symbol on the gauge to begin flashing in the color upon which the needle currently rest, as defined in `ranges`.
- `format: function(value) => string` (optional) - When provided, will be used to format the value of the metric for display.
- `size: number` (default: 1) - When provided scales the gauge.
### `Section`
@@ -773,18 +837,25 @@ If you want to have a button on the right side of an section title
<Section
title="Cargo"
buttons={(
<Button content="Send shuttle" />
<Button>
Send shuttle
</Button>
)}>
Here you can order supply crates.
</Section>
```
**New:** Sections can now be nested, and will automatically font size of the
header according to their nesting level. Previously this was done via `level`
prop, but now it is automatically calculated.
- See inherited props: [Box](#box)
- `title: string` - Title of the section.
- `level: number` - Section level in hierarchy. Default is 1, higher number
means deeper level of nesting. Must be an integer number.
- `buttons: any` - Buttons to render aside the section title.
- `content/children: any` - Content of this section.
- `fill: boolean` - If true, fills all available vertical space.
- `fitted: boolean` - If true, removes all section padding.
- `scrollable: boolean` - Shows or hides the scrollbar.
- `children: any` - Content of this section.
### `Slider`
@@ -821,6 +892,76 @@ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
when you drag the input up and down, on release and on manual editing.
### `Stack`
A higher-level component, that is based on [Flex](#flex). The main difference
from `Flex`, is that this component automatically adds spacing between
all stack items, reducing the boilerplate that you have to write!
Consists of two elements: `<Stack>` and `<Stack.Item>`.
Stacks can be vertical by adding a `vertical` property.
**Example:**
```jsx
<Stack>
<Stack.Item grow>
Button description
</Stack.Item>
<Stack.Item>
<Button>
Perform an action
</Button>
</Stack.Item>
</Stack>
```
**Example of a high-level window layout:**
Stacks can be used for high level window layout.
Make sure to use the `fill` property.
```jsx
<Window>
<Window.Content>
<Stack fill>
<Stack.Item>
<Section fill>
Sidebar
</Section>
</Stack.Item>
<Stack.Item grow>
<Stack fill vertical>
<Stack.Item grow>
<Section fill scrollable>
Main content
</Section>
</Stack.Item>
<Stack.Item>
<Section>
Bottom pane
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
```
**Props:**
- See inherited props: [Flex](#flex)
- `fill: boolean` - If set, stack will fill all available height.
- `vertical: boolean` - If set, stack will work in vertical mode.
### `Stack.Item`
**Props:**
- See inherited props: [Flex.Item](#flexitem)
### `Table`
A straight forward mapping to a standard html table, which is slightly
@@ -893,25 +1034,41 @@ Notice that tabs do not contain state. It is your job to track the selected
tab, handle clicks and place tab content where you need it. In return, you get
a lot of flexibility in regards to how you can layout your tabs.
Tabs also support a vertical configuration. This is usually paired with a
[Flex](#flex) component to render tab content to the right.
Tabs also support a vertical configuration. This is usually paired with
[Stack](#stack) to render tab content to the right.
```jsx
<Flex>
<Flex.Item>
<Stack>
<Stack.Item>
<Tabs vertical>
...
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
</Stack.Item>
<Stack.Item grow={1} basis={0}>
Tab content.
</Flex.Item>
</Flex>
</Stack.Item>
</Stack>
```
If you need to combine a tab section with other elements, or if you want to
add scrollable functionality to tabs, pair them with the [Section](#section)
component:
```jsx
<Section fill fitted scrollable width="128px">
<Tabs vertical>
...
</Tabs>
... other things ...
</Section>
```
**Props:**
- See inherited props: [Box](#box)
- `fluid: boolean` - If true, tabs will take all available horizontal space.
- `fill: boolean` - Similarly to `fill` on [Section](#section), tabs will fill
all available vertical space. Only makes sense in a vertical configuration.
- `vertical: boolean` - Use a vertical configuration, where tabs will be
stacked vertically.
- `children: Tab[]` - This component only accepts tabs as its children.
@@ -942,18 +1099,17 @@ it is recommended to use that prop instead.
Usage:
```jsx
<Box position="relative">
Sample text.
<Tooltip
position="bottom"
content="Box tooltip" />
</Box>
<Tooltip position="bottom" content="Box tooltip">
<Box position="relative">
Sample text.
</Box>
</Tooltip>
```
**Props:**
- `position: string` - Tooltip position.
- `content/children: string` - Content of the tooltip. Must be a plain string.
- `position?: string` - Tooltip position. See [`Popper`](#Popper) for valid options. Defaults to "auto".
- `content: string` - Content of the tooltip. Must be a plain string.
Fragments or other elements are **not** supported.
## `tgui/layouts`
@@ -967,9 +1123,7 @@ it in one way or another.
Example:
```jsx
<Window
theme="hackerman"
resizable>
<Window theme="hackerman">
<Window.Content scrollable>
Hello, world!
</Window.Content>
@@ -978,11 +1132,14 @@ Example:
**Props:**
- See inherited props: [Box](#box)
- `className: string` - Applies a CSS class to the element.
- `theme: string` - A name of the theme.
- For a list of themes, see `packages/tgui/styles/themes`.
- `title: string` - Window title.
- `resizable: boolean` - Controls resizability of the window.
- `width: number` - Window width.
- `height: number` - Window height.
- `canClose: boolean` - Controls the ability to close the window.
- `children: any` - Child elements, which are rendered directly inside the
window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
they should be put as direct childs of a Window, otherwise you should be
@@ -995,6 +1152,8 @@ Can be scrollable.
**Props:**
- See inherited props: [Box](#box)
- `className: string` - Applies a CSS class to the element.
- `fitted: boolean` - If true, removes all padding.
- `scrollable: boolean` - Shows or hides the scrollbar.
- `children: any` - Main content of your window.
+332
View File
@@ -0,0 +1,332 @@
# Converting old tgui interfaces to tgui-next
This guide is going to assume you already know roughly how tgui-next works, how to make new uis, etc. It's mostly aimed at helping translate concepts between tgui and tgui-next, and clarify some confusing parts of the transition.
## Backend
Backend in almost every case does not require any changes. In particularly heavy ui cases, something to be aware of is the new `ui_static_data()` proc. This proc allows you to split some data sent to the interface off into data that will only be sent on ui initialize and when manually updated by elsewhere in the code. Useful for things like cargo where you have a very large set of mostly identical code.
Keep in mind that for uis where *all* data doesn't need to be live updating, you can just toggle off autoupdate for the ui instead of messing with static data.
## Frontend
The very first thing to note is the name of the `ract` file containing the old interface. Whatever the name is (minus the extension) is going to be what the route key is going to be.
One thing I like to do before starting work on a conversion is screenshot what the old interface looks like so I have something to reference to make sure that the styling can line up as well.
## General syntax changes
Ractive has a fairly different templating syntax from React.
### `data`
You likely already know that React data inserts look like this
```jsx
{data.example_data}
```
Ractive looks very similar, the only real difference is that React uses one paranthesis instead of two.
```ractive
{{data.example_data}}
```
However, you may occasionally come across data inserts that instead of referencing the `data` var or things contained within it instead reference `adata`. `adata` was short for animated data, and was used for smooth number animations in interfaces. instead of having a seperate data structure for this. tgui-next instead uses a component, which is `AnimatedNumber`.
`AnimatedNumber` is used like this
```jsx
<AnimatedNumber value={data.example_data}/>
```
Make sure you don't forget to import it.
### Conditionals
Ractive conditionals look very different from React conditionals.
A ractive `if` (only render if result of expression is true) looks like this
```ractive
{{#if data.condition}}
<span>Example Render</span>
{{/if}}
```
The equivalent React would be
```jsx
{!!data.condition && (
<Fragment>Example Render</Fragment>
)}
```
This might look a bit intimidating compared to the reactive part but it's not as complicated as it seems:
1. A new jsx context is opened with `{}`
2. jsx contexts like this always render whatever the return value is, so we can use `&&` to return a value we want. `&&` returns the last true value (or not "falsey" because this is js).
3. jsx tags are never "falsey", so a conditioned paired with a jsx tag will mean the condition being true will continue on and return the tag. `()` is just used to contain the tag
4. The `!!` is not a special operator, it is a literal double negation. This is because most `false` values coming from byond are going to actually be `0`, which would be rendered if the condition is false. Negating `0` returns `true`, negating `true` returns `false`, which isn't rendered.
5. `Fragment` is actually a true "dead tag". It's similar to `span` in that it just contains things without providing functionality, but it's unwrapped before the final render and children of it are injected into its parent. In a case where you only need to render text without any styling, it's probably better to just return a string literal (`"Example Render"`), but this was just to illustrate that you can put any tag in this expression.
You don't really need to know all this to understand how to use it, but I find it helps with understanding when things go wrong.
Ractive conditionals can have an `else` as well
```ractive
{{#if data.condition}}
value
{{else}}
other value
{{/if}}
```
Similarly to the previous example, just add a `||` operator to handle the
"falsy" condition:
```jsx
{!!data.condition && (
<Fragment>
value
</Fragment>
) || (
<Fragment>
other value
</Fragment>
)}
```
There's also our good old friend - the ternary:
```jsx
{data.condition ? 'value' : 'other value'}
```
Keep in mind you can also use tags here like the conditional example,
and you can mix string literals, values, and tags as well.
```jsx
{data.is_robot ? (
<Button content="Robot Button"/>
) : 'Not a robot'}
```
### Loops
Ractive has loops for iterating over data and inserting something for each
member of an array or object
```
{{#each data.list_of_foo}}
foo {{number}} is here.
{{/each}}
```
This didn't care whether the data was an array or an object, and members of each entry of the loop were "unwrapped" so to say. `{{number}}` in that example is referring to the `{{number}}` value on the entry of the list for that iterate.
The React equivalent to this is going to be `map`.
_AN IMPORTANT DISTINCTION HERE IS THAT NOW WE CARE WHETHER THIS IS AN OBJECT OR AN ARRAY BEING ACTED ON._
Objects are represented by `{}`, arrays by `[]`
"How can I tell?" you may ask. It's fairly simple, associated lists on the byond side are going to be turned into objects when they get json converted, normal lists are going to be turned into arrays.
`list("bla", "blo")` would become `["bla", "blo"]` and `list("foo" = 1, "bar" = 2)` would become `{"foo": 1, "bar": 2}`
First things first, above the `return` of the function you're making the interface in, you're going to want to add something like this
```jsx
const things = data.things || [];
```
This ensures that you'll never be reading a null entry by mistake. Substitute `{}` for objects as appropriate.
If it's an array, you'll want to do this in the template
```jsx
{things.map(thing => (
<Fragment>
Thing {thing.number} is here!
</Fragment>
))}
```
`map` is a function that calls a passed function (a lambda) on each entry, and returns the value. You should already know that returned tags and values (except `false`) get rendered, so that's how it's rendering each time.
A lambda is what's known as an anonymous function, it's a function that doesn't have a name that's only used for a specific usage. `map` wants a function that has one parameter, so we define one parameter then use `=>` to say the parameter has to do with the following block.
`parameter => ()` is just a shorthand for `parameter => {return();}`
This is quite a bit higher concept than ractive's each statements, so feel free to look around and ~~copy paste~~ learn from how other interfaces use this.
Now for objects, there's a genuinely pretty gross syntax here. We apoligize, it's related to ie8 compatibility nonsense.
```jsx
{map((value, key) => (
<Fragment>
Key is {key}, value is {value}
</Fragment>
))(fooObject)}
```
Again, sorry for this syntax. `fooObject` would be the object being iterated on, value would be the value of the iterated entry on the list, and key would be the key. the naming of value and key isn't important here, but knowing that it goes `value`, `key` in that order is important.
It is sometimes better to preemptively convert an object to array before
the big return statement, like this:
```jsx
const fooArray = map((value, key) => {
return { key, value };
})(fooObject);
```
Or if you just want to discard all keys, this will also work nicely:
```jsx
const fooArray = toArray(fooObject);
```
Also occasionally you'd see an else:
```
{{#each data.potentially_empty_list}}
Thing "{{name}}" is in this list!
{{else}}
None found!
{{/each}}
```
This would iterate using the first contents each time, or display the second option if the list was empty.
To do a similar thing in JSX, just check if array is empty like this:
```jsx
{fooArray.length === 0 && 'fooArray is empty.'}
{fooArray.map(foo => (
<Fragment>
Foo is {foo}
</Fragment>
))}
```
### Extra Stuff
I'll put some extra stuff here when I think of it.
## Components
This will be a reference of tgui components and the tgui-next equivalent.
### `ui-display`
Equivalent of `<ui-display>` is `<Section>`
```
<ui-display title="Status">
Contents
</ui-display>
```
becomes
```jsx
<Section title="Status">
Contents
</Section>
```
A feature sometimes used is if `ui-display` has the `button` property, it will contain a `partial` command. This becomes the `buttons` property on `Section`:
```
<ui-display title="Status" button>
{{#partial button}}
<ui-button /> // lots more button bullshit here
{{/partial}}
Contents
</ui-display>
```
becomes
```jsx
<Section
title="Status"
buttons={(
<Button />
)}>
Contents
</Section>
```
### `ui-section`
Very important to note `ui-section` is NOT the equivalent of `Section`
`<ui-section>` does not have a direct equivalent, but the closest equivalent is `<LabeledList>`
```
<ui-section label="power">
No Power
</ui-section>
<ui-section label="connection">
No Connection
</ui-section>
```
becomes
```jsx
<LabeledList>
<LabeledList.Item label="power">
No Power
</LabeledList.Item>
<LabeledList.Item label="connection">
No Connection
</LabeledList.Item>
</LabeledList>
```
Important to note that `LabeledList.Item` has `buttons` as well.
Also good to know that if you need the contents of a `LabeledList.Item` to be colored, you can just set the `color` prop on it instead of putting a `span` inside it.
### `ui-notice`
`<ui-notice>` has a direct equivalent in `<NoticeBox>`
```
<ui-notice>
Notice stuff!
</ui-notice>
```
becomes
```jsx
<NoticeBox>
Notice stuff!
</NoticeBox>
```
### `ui-button`
The equivalent of `ui-button` is `Button` but it works quite a bit differently.
```
<ui-button
state='{{data.condition ? "disabled" : null}}'
action="ui_action"
params={param: value}>
Click
</ui-button>
```
becomes
```jsx
<Button
content="Click"
disabled={data.condition}
onClick={() => act('ui_action', {
param: value,
})}/>
```
+24 -16
View File
@@ -5,7 +5,7 @@
Basic tgui backend code consists of the following vars and procs:
```
tgui_interact(mob/user, ui_key)
tgui_interact(mob/user, datum/tgui/ui)
tgui_data(mob/user)
tgui_act(action, params)
tgui_state()
@@ -37,10 +37,10 @@ powerful interactions for embedded objects or remote access.
Let's start with a very basic hello world.
```dm
/obj/machinery/my_machine/tgui_interact(mob/user, datum/tgui/ui = null)
/obj/machinery/my_machine/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "MyMachine", name)
ui = new(user, src, "MyMachine")
ui.open()
```
@@ -53,7 +53,7 @@ the `try_update_ui`), so we accept an existing UI to update.
Inside the `if(!ui)` block (which means we are creating a new UI), we choose our
template, title, and size; we can also set various options like `style` (for
themes), or autoupdate. These options will be elaborated on later (as will
`ui_state`s).
`tgui_state`s).
After `tgui_interact`, we need to define `tgui_data`. This just returns a list of
data for our object to use. Let's imagine our object has a few vars:
@@ -76,8 +76,9 @@ input. The input's `action` and `params` are passed to the proc.
```dm
/obj/machinery/my_machine/tgui_act(action, params)
if(..())
return TRUE
. = ..()
if(.)
return
if(action == "change_color")
var/new_color = params["color"]
if(!(color in allowed_coors))
@@ -88,13 +89,20 @@ input. The input's `action` and `params` are passed to the proc.
```
The `..()` (parent call) is very important here, as it is how we check that the
user is allowed to use this interface (to avoid so-called href exploits). It is
also very important to clamp and sanitize all input here. Always assume the user
is attempting to exploit the game.
user is allowed to use this interface (to avoid so-called href exploits). When
any event has been handled `..()` will return `TRUE`. It is important to clamp
and sanitize all input here. Always assume the user is attempting to exploit the
game.
When `..()` has returned `TRUE` your interface can safely assume that the user's
action has been handled already by some parent proc and you should not continue
to handle this, instead preserving and returning the parent proc's return value.
Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI
that this input caused an update. This is especially important for UIs that do
not auto-update, as otherwise the user will never see their change.
that this input has been handled. When `tgui_act` eventually returns, a value of
`TRUE` indicates that the input has been handled and that the UI should update.
This is important for UIs that do not auto-update, as otherwise the user will
not be able to see the interface update based on thier actions.
### Frontend
@@ -156,7 +164,7 @@ Here are the key variables you get from a `useBackend(context)` function:
interface and who uses it, BYOND refs to various objects, and so forth.
You are rarely going to use it, but sometimes it can be used to your
advantage when doing complex UIs.
- `data` is the data returned from `tgui_data` and `tgui_static_data` procs in
- `data` is the data returned from `tgui_data` and `ui_static_data` procs in
your DM code. Pretty straight forward.
- Note, that javascript doesn't have associative arrays, so when you
return an associative list from DM, it will be available in `data` as a
@@ -166,7 +174,7 @@ your DM code. Pretty straight forward.
are not available on it. Always prefer returning clean arrays from your
code, since arrays are easier to work with in javascript!
- `act(name, params)` is a function, which you can call to dispatch an action
to your DM code. It will be processed in `ui_act` proc. Action name will be
to your DM code. It will be processed in `tgui_act` proc. Action name will be
available in `params["action"]`, mixed together with the rest of parameters
you have passed in `params` object.
@@ -292,10 +300,10 @@ here's what you need (note that you'll probably be forced to clean your shit up
upon code review):
```dm
/obj/copypasta/tgui_interact(mob/user, datum/tgui/ui = null)
/obj/copypasta/tgui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CopyPasta")
ui = new(user, src, "copypasta")
ui.open()
/obj/copypasta/tgui_data(mob/user)
@@ -305,7 +313,7 @@ upon code review):
/obj/copypasta/tgui_act(action, params)
if(..())
return TRUE
return
switch(action)
if("copypasta")
var/newvar = params["var"]
+23
View File
@@ -0,0 +1,23 @@
## Jest
You can now write and run unit tests in tgui.
It's quite simple: create a file ending in `.test.ts` or `.spec.ts` (usually with the same filename as the file you're testing), and create a test case:
```js
test('something', () => {
expect('a').toBe('a');
});
```
To run the tests, type the following into the terminal:
```
bin/tgui --test
```
There is an example test in `packages/common/react.spec.ts`.
You can read more about Jest here: https://jestjs.io/docs/en/getting-started
Note, that there is still no real solution to test UIs for now, even though a lot of the support is here (jest + jsdom). That will come later.
+158
View File
@@ -0,0 +1,158 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
declare global {
// Webpack asset modules.
// Should match extensions used in webpack config.
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.jpg' {
const content: string;
export default content;
}
declare module '*.svg' {
const content: string;
export default content;
}
type ByondType = {
/**
* True if javascript is running in BYOND.
*/
IS_BYOND: boolean;
/**
* True if browser is IE8 or lower.
*/
IS_LTE_IE8: boolean;
/**
* True if browser is IE9 or lower.
*/
IS_LTE_IE9: boolean;
/**
* True if browser is IE10 or lower.
*/
IS_LTE_IE10: boolean;
/**
* True if browser is IE11 or lower.
*/
IS_LTE_IE11: boolean;
/**
* Makes a BYOND call.
*
* If path is empty, this will trigger a Topic call.
* You can reference a specific object by setting the "src" parameter.
*
* See: https://secure.byond.com/docs/ref/skinparams.html
*/
call(path: string, params: object): void;
/**
* Makes an asynchronous BYOND call. Returns a promise.
*/
callAsync(path: string, params: object): Promise<any>;
/**
* Makes a Topic call.
*
* You can reference a specific object by setting the "src" parameter.
*/
topic(params: object): void;
/**
* Runs a command or a verb.
*/
command(command: string): void;
/**
* Retrieves all properties of the BYOND skin element.
*
* Returns a promise with a key-value object containing all properties.
*/
winget(id: string): Promise<object>;
/**
* Retrieves all properties of the BYOND skin element.
*
* Returns a promise with a key-value object containing all properties.
*/
winget(id: string, propName: '*'): Promise<object>;
/**
* Retrieves an exactly one property of the BYOND skin element,
* as defined in `propName`.
*
* Returns a promise with the value of that property.
*/
winget(id: string, propName: string): Promise<any>;
/**
* Retrieves multiple properties of the BYOND skin element,
* as defined in the `propNames` array.
*
* Returns a promise with a key-value object containing listed properties.
*/
winget(id: string, propNames: string[]): Promise<object>;
/**
* Assigns properties to BYOND skin elements.
*/
winset(props: object): void;
/**
* Assigns properties to the BYOND skin element.
*/
winset(id: string, props: object): void;
/**
* Sets a property on the BYOND skin element to a certain value.
*/
winset(id: string, propName: string, propValue: any): void;
/**
* Parses BYOND JSON.
*
* Uses a special encoding to preverse Infinity and NaN.
*/
parseJson(text: string): any;
/**
* Loads a stylesheet into the document.
*/
loadCss(url: string): void;
/**
* Loads a script into the document.
*/
loadJs(url: string): void;
};
/**
* Object that provides access to Byond Skin API and is available in
* any tgui application.
*/
const Byond: ByondType;
interface Window {
/**
* ID of the Byond window this script is running on.
* Should be used as a parameter to winget/winset.
*/
__windowId__: string;
Byond: ByondType;
}
}
export {};

Some files were not shown because too many files have changed in this diff Show More