Merge branch 'master' into upstream-merge-10704
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]")
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'"]>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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]!"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
..()
|
||||
@@ -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)
|
||||
..()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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">
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -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")
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
/.yarn/**
|
||||
/**/node_modules
|
||||
/**/*.bundle.*
|
||||
/**/*.chunk.*
|
||||
/**/*.hot-update.*
|
||||
/packages/inferno/**
|
||||
/packages/tgui/public/shim-*.js
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/**
|
||||
|
||||
@@ -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
|
||||
@@ -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`);
|
||||
@@ -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`);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "7.21.0-pnpify",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# This file is automatically generated by PnPify.
|
||||
# Manual changes will be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
@@ -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`);
|
||||
@@ -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`);
|
||||
@@ -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`);
|
||||
@@ -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`));
|
||||
@@ -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`);
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"version": "4.2.3-pnpify",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
cd "%~dp0\.."
|
||||
call yarn install
|
||||
call yarn run build
|
||||
timeout /t 9
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
})}/>
|
||||
```
|
||||
@@ -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"]
|
||||
|
||||
@@ -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.
|
||||
@@ -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 {};
|
||||