Merge remote-tracking branch 'upstream/master' into replicators

This commit is contained in:
SatinIsle
2024-09-27 11:39:35 +01:00
106 changed files with 7881 additions and 991 deletions
+44 -17
View File
@@ -8,31 +8,58 @@ env:
BASENAME: "vorestation"
jobs:
file_tests:
run_linters:
name: Run Linters
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Ensure +x on CI directory
run: |
chmod -R +x ./tools/ci
- name: Install Tools
run: |
bash tools/ci/install_build_deps.sh
# Caches
- name: Restore Yarn cache
if: "${{ contains(github.event.pull_request.labels.*.name, 'Type: TGUI Bundle') }}"
uses: actions/cache@v4
with:
path: tgui/.yarn/cache
key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }}
key: ${{ runner.os }}-yarn-${{ hashFiles('tgui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Run Tests
${{ runner.os }}-yarn-
- name: Restore Node cache
uses: actions/cache@v4
with:
path: ~/.nvm
key: ${{ runner.os }}-node-${{ hashFiles('dependencies.sh') }}
restore-keys: |
${{ runner.os }}-node-
- name: Restore Bootstrap cache
uses: actions/cache@v4
with:
path: tools/bootstrap/.cache
key: ${{ runner.os }}-bootstrap-${{ hashFiles('tools/requirements.txt') }}
restore-keys: |
${{ runner.os }}-bootstrap-
- name: Restore Rust cache
uses: actions/cache@v4
with:
path: ~/.cargo
key: ${{ runner.os }}-rust-${{ hashFiles('tools/ci/ci_dependencies.sh')}}
restore-keys: |
${{ runner.os }}-rust-
- name: Restore Cutter cache
uses: actions/cache@v4
with:
path: tools/icon_cutter/cache
key: ${{ runner.os }}-cutter-${{ hashFiles('dependencies.sh') }}
# End Caches
- name: Install Tools
run: |
tools/ci/validate_files.sh
tools/ci/build_tgui.sh
pip3 install setuptools
bash tools/ci/install_node.sh
bash tools/ci/install_ripgrep.sh
tools/bootstrap/python -c ''
- name: Run Grep Checks
run: tools/ci/validate_files.sh
- name: Run TGUI Checks
run: tools/build/build --ci lint tgui-test
dreamchecker:
name: DreamChecker
@@ -68,7 +95,7 @@ jobs:
map: ['tether', 'stellar_delight', 'groundbase']
# name: Integration Tests (${{ matrix.map }})
name: Integration Tests
# needs: ['file_tests', 'dreamchecker']
# needs: ['run_linters', 'dreamchecker']
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
@@ -119,7 +146,7 @@ jobs:
tests_successful:
name: Integration Tests
needs: ['file_tests', 'dreamchecker', 'unit_tests']
needs: ['run_linters', 'dreamchecker', 'unit_tests']
runs-on: ubuntu-20.04
steps:
- name: Report Success
+3
View File
@@ -31,3 +31,6 @@ temp.dmi
node_modules/
package-lock.json
# CBT Things
.cache
+1
View File
@@ -0,0 +1 @@
v20.13.0
+55 -9
View File
@@ -4,13 +4,59 @@
// 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
}
{
"type": "byond",
"request": "launch",
"name": "Launch DreamSeeker",
"preLaunchTask": "Build All",
"dmb": "${workspaceFolder}/${command:CurrentDMB}"
},
{
"type": "byond",
"request": "launch",
"name": "Launch DreamSeeker (low memory mode)",
"preLaunchTask": "Build All (low memory mode)",
"dmb": "${workspaceFolder}/${command:CurrentDMB}"
},
{
"type": "byond",
"request": "launch",
"name": "Launch DreamDaemon",
"preLaunchTask": "Build All",
"dmb": "${workspaceFolder}/${command:CurrentDMB}",
"dreamDaemon": true
},
{
"type": "byond",
"request": "launch",
"name": "Launch DreamDaemon (low memory mode)",
"preLaunchTask": "Build All (low memory mode)",
"dmb": "${workspaceFolder}/${command:CurrentDMB}",
"dreamDaemon": true
},
{
"name": "Debug External Libraries",
"type": "cppvsdbg",
"request": "launch",
"program": "${command:dreammaker.returnDreamDaemonPath}",
"cwd": "${workspaceRoot}",
"args": [
"${command:dreammaker.getFilenameDmb}",
"-trusted"
],
"preLaunchTask": "Build All"
},
{
"name": "Debug External Libraries (low memory mode)",
"type": "cppvsdbg",
"request": "launch",
"program": "${command:dreammaker.returnDreamDaemonPath}",
"cwd": "${workspaceRoot}",
"args": [
"${command:dreammaker.getFilenameDmb}",
"-trusted"
],
"preLaunchTask": "Build All (low memory mode)"
}
]
}
}
+95 -31
View File
@@ -1,6 +1,53 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "process",
"command": "tools/build/build",
"windows": {
"command": ".\\tools\\build\\build.bat"
},
"options": {
"env": {
"DM_EXE": "${config:dreammaker.byondPath}"
}
},
"problemMatcher": [
"$dreammaker",
"$tsc",
"$eslint-stylish"
],
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": "dm: reparse",
"label": "Build All"
},
{
"type": "process",
"command": "tools/build/build",
"args": ["-DLOWMEMORYMODE"],
"windows": {
"command": ".\\tools\\build\\build.bat",
"args": ["-DLOWMEMORYMODE"]
},
"options": {
"env": {
"DM_EXE": "${config:dreammaker.byondPath}"
}
},
"problemMatcher": [
"$dreammaker",
"$tsc",
"$eslint-stylish"
],
"group": {
"kind": "build"
},
"dependsOn": "dm: reparse",
"label": "Build All (low memory mode)"
},
{
"type": "dreammaker",
"dme": "vorestation.dme",
@@ -10,11 +57,16 @@
"group": "build",
"label": "dm: build - vorestation.dme"
},
{
"command": "${command:dreammaker.reparse}",
"group": "build",
"label": "dm: reparse"
},
{
"type": "shell",
"command": "tgui/bin/tgui",
"command": "bin/tgui-build",
"windows": {
"command": ".\\tgui\\bin\\tgui.bat"
"command": ".\\bin\\tgui-build.cmd"
},
"problemMatcher": [
"$tsc",
@@ -25,43 +77,55 @@
},
{
"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"
},
{
"type": "shell",
"command": "tgui/bin/tgui",
"command": "bin/tgui-dev",
"windows": {
"command": ".\\tgui\\bin\\tgui-prettybuild.bat"
"command": ".\\bin\\tgui-dev.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: prettybuild"
},
{
"type": "shell",
"command": "bin/tgui --dev",
"windows": {
"command": ".\\bin\\tgui.bat --dev"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"options": {
"cwd": ".\\tgui\\",
},
"group": "build",
"label": "tgui: dev server"
},
{
"type": "shell",
"command": "bin/tgui-bench",
"windows": {
"command": ".\\bin\\tgui-bench.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: bench"
},
{
"type": "shell",
"command": "bin/tgui-sonar",
"windows": {
"command": ".\\bin\\tgui-sonar.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: sonar"
},
{
"type": "shell",
"command": "bin/tgfont",
"windows": {
"command": ".\\bin\\tgfont.cmd"
},
"problemMatcher": [
"$tsc",
"$eslint-stylish"
],
"group": "build",
"label": "tgui: rebuild tgfont"
}
]
}
+3 -3
View File
@@ -54,11 +54,11 @@ This will take a while to download, but it provides an easier method for updatin
First-time installation should be fairly straightforward. First, you'll need BYOND installed. You can get it from [here](http://www.byond.com/).
This is a sourcecode-only release, so the next step is to compile the server files. Open vorestation.dme by double-clicking it, open the Build menu, and click compile. This'll take a little while, and if everything's done right you'll get a message like this:
**The quick way**. Find `bin/server.cmd` in this folder and double click it to automatically build and host the server on port 1337.
saving vorestation.dmb (DEBUG mode)
**The long way**. Find `bin/build.cmd` in this folder, and double click it to initiate the build. It consists of multiple steps and might take around 1-5 minutes to compile. If it closes, it means it has finished its job. You can then [setup the server](.github/guides/RUNNING_A_SERVER.md) normally by opening `tgstation.dmb` in DreamDaemon.
vorestation.dmb - 0 errors, 0 warnings
**Building vorestation in DreamMaker directly is deprecated and might produce errors**, such as `'tgui.bundle.js': cannot find file`.
If you see any errors or warnings, something has gone wrong - possibly a corrupt download or the files extracted wrong, or a code issue on the main repo. Ask on IRC.
+2 -2
View File
@@ -6,6 +6,6 @@ export NODE_VERSION=20
# Byond Major
export BYOND_MAJOR=515
# Byond Minor
export BYOND_MINOR=1630
export BYOND_MINOR=1637
# Macro Count
export MACRO_COUNT=4
export MACRO_COUNT=8
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error build %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error clean-all %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error server %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error dm-test %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error tg-font %*
+3
View File
@@ -0,0 +1,3 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-bench %*
pause
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui tgui-lint tgui-test %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-dev %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-sonar %*
+2
View File
@@ -0,0 +1,2 @@
@echo off
call "%~dp0\tools\build\build.bat" --wait-on-error build %*
+3
View File
@@ -25,6 +25,9 @@
* Cooldown system based on an datum-level associative lazylist using timers.
*/
// random cooldowns
#define COOLDOWN_VENOM_MILKING "venom_milking"
// admin verb cooldowns
#define COOLDOWN_INTERNET_SOUND "internet_sound"
+1 -1
View File
@@ -172,7 +172,7 @@ SUBSYSTEM_DEF(media_tracks)
sort_tracks()
return
to_chat(C, "<span class='warning>Couldn't find a track matching the specified parameters.</span>")
to_chat(C, "<span class='warning'>Couldn't find a track matching the specified parameters.</span>")
/datum/controller/subsystem/media_tracks/proc/add_track(var/mob/user, var/new_url, var/new_title, var/new_duration, var/new_artist, var/new_genre, var/new_secret, var/new_lobby)
if(!check_rights(R_DEBUG|R_FUN))
+16 -4
View File
@@ -356,7 +356,10 @@ var/global/list/PDA_Manifest = list()
G.fields["p_stat"] = "Active"
G.fields["m_stat"] = "Stable"
G.fields["sex"] = gender2text(H.gender)
G.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit
if(H.species.name == SPECIES_HANNER)
G.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]"
else
G.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]"
G.fields["home_system"] = H.home_system
G.fields["birthplace"] = H.birthplace
G.fields["citizenship"] = H.citizenship
@@ -367,7 +370,10 @@ var/global/list/PDA_Manifest = list()
//Medical Record
var/datum/data/record/M = CreateMedicalRecord(H.real_name, id, hidden)
M.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit
if(H.species.name == SPECIES_HANNER)
M.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]"
else
M.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]"
M.fields["b_type"] = H.b_type
M.fields["blood_reagent"] = H.species.blood_reagents
M.fields["b_dna"] = H.dna.unique_enzymes
@@ -381,7 +387,10 @@ var/global/list/PDA_Manifest = list()
//Security Record
var/datum/data/record/S = CreateSecurityRecord(H.real_name, id, hidden)
S.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit
if(H.species.name == SPECIES_HANNER)
S.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]"
else
S.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]"
if(H.get_FBP_type())
S.fields["brain_type"] = H.get_FBP_type()
else
@@ -407,7 +416,10 @@ var/global/list/PDA_Manifest = list()
L.fields["b_dna"] = H.dna.unique_enzymes
L.fields["enzymes"] = H.dna.SE // Used in respawning
L.fields["identity"] = H.dna.UI // "
L.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit
if(H.species.name == SPECIES_HANNER)
L.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]"
else
L.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]"
L.fields["home_system"] = H.home_system
L.fields["birthplace"] = H.birthplace
L.fields["citizenship"] = H.citizenship
+30
View File
@@ -252,3 +252,33 @@
name = "Leotard"
icon_state = "leotard"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt
name = "Feminine T-Shirt"
icon_state = "t-shirt_full"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt_midriff
name = "Feminine T-Shirt (midriff)"
icon_state = "t-shirt_midriff"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt_shoulders
name = "Feminine T-Shirt (black with shoulders)"
icon_state = "t-shirt_full_shoulders"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt_midriff_shoulders
name = "Feminine T-Shirt (midriff, black with shoulders)"
icon_state = "t-shirt_midriff_shoulders"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt_shoulders_text
name = "Feminine T-Shirt (black with shoulders and text)"
icon_state = "t-shirt_full_shoulders_text"
has_color = TRUE
/datum/category_item/underwear/undershirt/tshirt_midriff_shoulders_text
name = "Feminine T-Shirt (midriff, black with shoulders and text)"
icon_state = "t-shirt_midriff_shoulders_text"
has_color = TRUE
+1 -1
View File
@@ -441,7 +441,7 @@
qdel(T)
return
to_chat(C, "<span class='warning>Couldn't find a track matching the specified parameters.</span>")
to_chat(C, "<span class='warning'>Couldn't find a track matching the specified parameters.</span>")
/obj/machinery/media/jukebox/ghost/vv_get_dropdown()
. = ..()
@@ -29,6 +29,12 @@
to_chat(usr,"<span class='danger'>The target's mind is too complex to be affected!</span>")
return
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.resleeve_lock && usr.ckey != H.resleeve_lock)
to_chat(src, span_danger("[H] cannot be impersonated!"))
return
if(M.stat == DEAD) //Are they dead?
to_chat(usr,"<span class='warning'>A warning pops up on the device, informing you that [M] is dead, and, as such, the mind transfer can not be done.</span>")
return
@@ -74,7 +74,10 @@
/mob/living/carbon/human/set_id_info(var/obj/item/weapon/card/id/id_card)
..()
id_card.age = age
id_card.species = "[custom_species ? "[custom_species] ([species.name])" : species.name]"
if(species.name == SPECIES_HANNER)
id_card.species = "[custom_species ? "[custom_species]" : species.name]"
else
id_card.species = "[custom_species ? "[custom_species] ([species.name])" : species.name]"
id_card.sex = capitalize(name_gender())
/obj/item/weapon/card/id/tgui_data(mob/user)
+12 -1
View File
@@ -84,7 +84,7 @@
.["target"]["availalbe_channels"] = availalbe_channels
// Components
.["target"]["components"] = get_components()
.["cell"] = target.cell?.name
.["cell"] = list("name" = target.cell?.name, "charge" = target.cell?.charge, "maxcharge" = target.cell?.maxcharge)
.["cell_options"] = get_cells()
// Access
.["id_icon"] = icon2html(target.idcard, user, sourceonly=TRUE)
@@ -368,6 +368,17 @@
if(istype(C, /datum/robot_component/cell))
target.cell = null
return TRUE
if("adjust_cell_charge")
target.cell.charge = text2num(params["charge"])
return TRUE
if("adjust_brute")
var/datum/robot_component/C = locate(params["component"])
C.brute_damage = text2num(params["damage"])
return TRUE
if("adjust_electronics")
var/datum/robot_component/C = locate(params["component"])
C.electronics_damage = text2num(params["damage"])
return TRUE
if("add_access")
target.idcard.access += text2num(params["access"])
return TRUE
@@ -555,60 +555,17 @@ Talon pin
)
gear_tweaks += new/datum/gear_tweak/path(insignia)
/datum/gear/accessory/plushie
display_name = "Plushie Selection"
description = "A single plushie of your selection!"
path = /obj/item/toy/plushie/nymph
/datum/gear/accessory/belt_selection
display_name = "belts (selection, colorable)"
path = /obj/item/clothing/accessory/belt
/datum/gear/accessory/plushie/New()
/datum/gear/accessory/belt_selection/New()
..()
var/plushie = list(
"Nymph" = /obj/item/toy/plushie/nymph,
"Mouse" = /obj/item/toy/plushie/mouse,
"Kitten" = /obj/item/toy/plushie/kitten,
"Lizard" = /obj/item/toy/plushie/lizard,
"Black cat" = /obj/item/toy/plushie/black_cat,
"Black fox" = /obj/item/toy/plushie/black_fox,
"Blue fox" = /obj/item/toy/plushie/blue_fox,
"Carp plushie" = /obj/random/carp_plushie,
"Coffee fox" = /obj/item/toy/plushie/coffee_fox,
"Corgi" = /obj/item/toy/plushie/corgi,
"Crimson fox" = /obj/item/toy/plushie/crimson_fox,
"Deer" = /obj/item/toy/plushie/deer,
"Girly corgi" = /obj/item/toy/plushie/girly_corgi,
"Grey cat" = /obj/item/toy/plushie/grey_cat,
"Marble fox" = /obj/item/toy/plushie/marble_fox,
"Octopus" = /obj/item/toy/plushie/octopus,
"Orange cat" = /obj/item/toy/plushie/orange_cat,
"Orange fox" = /obj/item/toy/plushie/orange_fox,
"Pink fox" = /obj/item/toy/plushie/pink_fox,
"Purple fox" = /obj/item/toy/plushie/purple_fox,
"Red fox" = /obj/item/toy/plushie/red_fox,
"Robo corgi" = /obj/item/toy/plushie/robo_corgi,
"Siamese cat" = /obj/item/toy/plushie/siamese_cat,
"Spider" = /obj/item/toy/plushie/spider,
"Tabby cat" = /obj/item/toy/plushie/tabby_cat,
"Tuxedo cat" = /obj/item/toy/plushie/tuxedo_cat,
"White cat" = /obj/item/toy/plushie/white_cat,
"Lizard" = /obj/item/toy/plushie/lizardplushie,
"Kobold" = /obj/item/toy/plushie/lizardplushie/kobold,
"Resh" = /obj/item/toy/plushie/lizardplushie/resh,
"Slime" = /obj/item/toy/plushie/slimeplushie,
"Box" = /obj/item/toy/plushie/box,
"Robot" = /obj/item/toy/plushie/borgplushie,
"Medihound" = /obj/item/toy/plushie/borgplushie/medihound,
"Scrubpuppy" = /obj/item/toy/plushie/borgplushie/scrubpuppy,
"Foxbear" = /obj/item/toy/plushie/foxbear,
"Operative" = /obj/item/toy/plushie/nukeplushie,
"Otter" = /obj/item/toy/plushie/otter,
"Vox" = /obj/item/toy/plushie/vox,
"Shark" = /obj/item/toy/plushie/shark,
"Sec Drake" = /obj/item/toy/plushie/borgplushie/drake/sec,
"Med Drake" = /obj/item/toy/plushie/borgplushie/drake/med,
"Sci Drake" = /obj/item/toy/plushie/borgplushie/drake/sci,
"Jani Drake" = /obj/item/toy/plushie/borgplushie/drake/jani,
"Eng Drake" = /obj/item/toy/plushie/borgplushie/drake/eng,
"Mine Drake" = /obj/item/toy/plushie/borgplushie/drake/mine,
"Trauma Drake" = /obj/item/toy/plushie/borgplushie/drake/trauma,
var/list/wristband_lists = list(
"Thin Belt" = /obj/item/clothing/accessory/belt,
"Thick Belt" = /obj/item/clothing/accessory/belt/thick,
"Strap Belt" = /obj/item/clothing/accessory/belt/strap,
"Studded Belt" = /obj/item/clothing/accessory/belt/studded
)
gear_tweaks += new/datum/gear_tweak/path(plushie)
gear_tweaks += new/datum/gear_tweak/path(wristband_lists)
gear_tweaks += gear_tweak_free_color_choice
@@ -185,7 +185,9 @@ Talon jumpsuit
"high skirt"=/obj/item/clothing/under/skirt/colorable/high,
"swept skirt"=/obj/item/clothing/under/skirt/colorable/swept,
"jumper skirt"=/obj/item/clothing/under/skirt/colorable/jumper,
"jumper dress"=/obj/item/clothing/under/skirt/colorable/jumperdress
"jumper dress"=/obj/item/clothing/under/skirt/colorable/jumperdress,
"short skirt"=/obj/item/clothing/under/skirt/colorable/short,
"short skirt (split)"=/obj/item/clothing/under/skirt/colorable/short_split
)
gear_tweaks += list(new/datum/gear_tweak/path(skirts), gear_tweak_free_color_choice)
@@ -1181,3 +1181,31 @@
flags_inv = HIDEHOLSTER
to_chat(user, "You adjust your poncho.")
update_clothing_icon()
/obj/item/clothing/accessory/belt
name = "Thin Belt"
desc = "A thin belt for holding your pants up."
icon = 'icons/inventory/accessory/item.dmi'
icon_override = 'icons/inventory/accessory/mob.dmi'
icon_state = "belt_thin"
item_state = "belt_thin"
slot_flags = SLOT_TIE | SLOT_BELT
slot = ACCESSORY_SLOT_DECOR
/obj/item/clothing/accessory/belt/thick
name = "Thick Belt"
desc = "A thick belt for holding your pants up."
icon_state = "belt_thick"
item_state = "belt_thick"
/obj/item/clothing/accessory/belt/strap
name = "Strap Belt"
desc = "A belt with no bucklet for holding your pants up."
icon_state = "belt_strap"
item_state = "belt_strap"
/obj/item/clothing/accessory/belt/studded
name = "Studded Belt"
desc = "A studded belt for holding your pants up and looking cool."
icon_state = "belt_studded"
item_state = "belt_studded"
@@ -524,6 +524,20 @@
item_state = "skirt_jumperdress"
worn_state = "skirt_jumperdress"
/obj/item/clothing/under/skirt/colorable/short
name = "short skirt"
desc = "A far too short pleated skirt."
icon_state = "skirt_short"
item_state = "skirt_short"
worn_state = "skirt_short"
/obj/item/clothing/under/skirt/colorable/short_split
name = "short skirt (split)"
desc = "A far too short pleated skirt with an open split down one side."
icon_state = "skirt_short_split"
item_state = "skirt_short_split"
worn_state = "skirt_short_split"
// Gwen Beedell's clown outfit
/obj/item/clothing/under/stripeddungarees
@@ -621,6 +621,10 @@
/obj/item/clothing/accessory/wristbandcollection/trans = 5,
/obj/item/clothing/accessory/wristbandcollection/ace = 5,
/obj/item/clothing/accessory/wristband_spiked = 5,
/obj/item/clothing/accessory/belt = 5,
/obj/item/clothing/accessory/belt/thick = 5,
/obj/item/clothing/accessory/belt/strap = 5,
/obj/item/clothing/accessory/belt/studded = 5,
/obj/item/weapon/storage/wallet = 5,
/obj/item/weapon/storage/wallet/poly = 5,
/obj/item/weapon/storage/wallet/womens = 5,
@@ -762,6 +766,10 @@
/obj/item/clothing/accessory/wristbandcollection/trans = 30,
/obj/item/clothing/accessory/wristbandcollection/ace = 30,
/obj/item/clothing/accessory/wristband_spiked = 20,
/obj/item/clothing/accessory/belt = 50,
/obj/item/clothing/accessory/belt/thick = 50,
/obj/item/clothing/accessory/belt/strap = 50,
/obj/item/clothing/accessory/belt/studded = 50,
/obj/item/weapon/storage/wallet = 50,
/obj/item/weapon/storage/wallet/poly = 50,
/obj/item/weapon/storage/wallet/womens = 50,
@@ -909,6 +917,8 @@
/obj/item/clothing/under/ascetic = 5,
/obj/item/clothing/under/dress/white3 = 5,
/obj/item/clothing/under/skirt/pleated = 5,
/obj/item/clothing/under/skirt/colorable/short = 5,
/obj/item/clothing/under/skirt/colorable/short_split = 5,
/obj/item/clothing/under/dress/darkred = 5,
/obj/item/clothing/under/dress/redeveninggown = 5,
/obj/item/clothing/under/dress/red_swept_dress = 5,
@@ -1093,6 +1103,8 @@
/obj/item/clothing/under/ascetic = 50,
/obj/item/clothing/under/dress/white3 = 50,
/obj/item/clothing/under/skirt/pleated = 50,
/obj/item/clothing/under/skirt/colorable/short = 50,
/obj/item/clothing/under/skirt/colorable/short_split = 50,
/obj/item/clothing/under/dress/darkred = 50,
/obj/item/clothing/under/dress/redeveninggown = 50,
/obj/item/clothing/under/dress/red_swept_dress = 50,
@@ -2125,6 +2137,8 @@
/obj/item/clothing/under/skirt/red = 5,
/obj/item/clothing/under/skirt/denim = 5,
/obj/item/clothing/under/skirt/pleated = 5,
/obj/item/clothing/under/skirt/colorable/short = 5,
/obj/item/clothing/under/skirt/colorable/short_split = 5,
/obj/item/clothing/under/skirt/outfit/plaid_blue = 5,
/obj/item/clothing/under/skirt/outfit/plaid_red = 5,
/obj/item/clothing/under/skirt/outfit/plaid_purple = 5,
@@ -77,7 +77,7 @@
string += "-\a [CI.container.label(null, CI.combine_target)], [report_progress(CI)]</br>"
return string
else
to_chat(user, "<span class='notice>'It is empty.</span>")
to_chat(user, "<span class='notice'>It is empty.</span>")
/obj/machinery/appliance/proc/report_progress_tgui(datum/cooking_item/CI)
if(!CI || !CI.max_cookwork)
@@ -812,4 +812,4 @@
return
food_safety = !food_safety
to_chat(usr, "<span class = 'notice'>You flip \the [src]'s safe mode switch. Safe mode is now [food_safety ? "on" : "off"].</span>")
to_chat(usr, "<span class = 'notice'>You flip \the [src]'s safe mode switch. Safe mode is now [food_safety ? "on" : "off"].</span>")
@@ -8,7 +8,6 @@
transmutation and energy sharing abilities not unlike they lleill, but considerably more limited. Unlike \
other shapeshifter species, such as proteans or prometheans, Hanner have a typical humanoid set of organs and can not regenerate."
flags = NO_SCAN
color_mult = 1
appearance_flags = HAS_SKIN_COLOR | HAS_EYE_COLOR | HAS_HAIR_COLOR | HAS_UNDERWEAR | HAS_LIPS
spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED | SPECIES_WHITELIST_SELECTABLE
@@ -130,7 +130,7 @@
for(var/mob/living/carbon/human/M in mob_list)
if(M.z != user.z || get_dist(user,M) > 10)
continue
if(istype(M) && M.resleeve_lock && M.ckey != M.resleeve_lock)
if(!M.allow_mimicry)
continue
targets |= M
@@ -210,11 +210,6 @@
if(pred.prey_controlled)
to_chat(prey, "<span class='warning'>\The [pred] is already dominated, and cannot be controlled at this time.</span>")
return
if(ishuman(pred))
var/mob/living/carbon/human/h = pred
if(h.resleeve_lock && ckey != h.resleeve_lock)
to_chat(src, "<span class='warning'>\The [h] cannot be impersonated!</span>")
return
if(tgui_alert(prey, "You are attempting to take over [pred], are you sure? Ensure that their preferences align with this kind of play.", "Take Over Predator",list("No","Yes")) != "Yes")
return
to_chat(prey, "<span class='notice'>You attempt to exert your control over \the [pred]...</span>")
@@ -99,7 +99,7 @@
/mob/living/simple_mob/vore/morph/proc/assume(atom/movable/target)
var/mob/living/carbon/human/humantarget = target
if(istype(humantarget) && humantarget.resleeve_lock && ckey != humantarget.resleeve_lock)
if(istype(humantarget) && !humantarget.allow_mimicry)
to_chat(src, "<span class='warning'>[target] cannot be impersonated!</span>")
return
if(morphed)
@@ -272,12 +272,9 @@
var/mob/living/L = tgui_input_list(src, "Select a mob to take over:", "Take Over Prey", possible_mobs)
if(!L)
return
// Adding a ishuman check here, since silicon mobs don't have a resleeve_lock from what I can tell.
if(ishuman(L))
var/mob/living/carbon/human/M = L
if(M.resleeve_lock && ckey != M.resleeve_lock)
to_chat(src, "<span class='warning'>\The [M] cannot be impersonated!</span>")
return
if(!L.allow_mimicry)
to_chat(src, "<span class='warning'>\The [L] cannot be impersonated!</span>")
return
if(tgui_alert(src, "You selected [L] to attempt to take over. Are you sure?", "Take Over Prey",list("No","Yes")) == "Yes")
log_admin("[key_name_admin(src)] offered [L] to swap bodies as a morph.")
if(tgui_alert(L, "\The [src] has elected to attempt to take over your body and control you. Is this something you will allow to happen?", "Allow Morph To Take Over",list("No","Yes")) == "Yes")
@@ -36,6 +36,7 @@
noisy = client.prefs_vr.noisy
selective_preference = client.prefs_vr.selective_preference
eating_privacy_global = client.prefs_vr.eating_privacy_global
allow_mimicry = client.prefs_vr.allow_mimicry
drop_vore = client.prefs_vr.drop_vore
stumble_vore = client.prefs_vr.stumble_vore
@@ -52,7 +52,8 @@ When editing the list, please try and keep similar probabilities near each other
prob(25); "[info] Emergency Fire Doors seal breaches and keep active fires out. Please do not open them without good reason.",
prob(25); "[info] The kitchen's Oven can fit multiple ingredients in one slot if you pull the baking tray out first. This is required for most recipes, and the Grille and Deep Frier work the same way!",
prob(10); "[info] You can keep a single item between rounds using secure lockboxes! Beware! You can only store 1 item across all characters! To find these lockboxes, feel free to ask over radio!",
prob(10); "[info] Not every hostile NPC you encounter while mining or exploring need to be defeated. Sometimes, it's better to avoid or run away from them. For example, star-treaders are slow and weak but have lots of HP - it is better to just run away."
prob(10); "[info] Not every hostile NPC you encounter while mining or exploring need to be defeated. Sometimes, it's better to avoid or run away from them. For example, star-treaders are slow and weak but have lots of HP - it is better to just run away.",
prob(10); "[info] Clicking on a venomous animal/person with a closed beaker will express their venom into the beaker!",
)
if("roleplay")
@@ -18,6 +18,7 @@
unacidable = TRUE //glass doesn't dissolve in acid
drop_sound = 'sound/items/drop/bottle.ogg'
pickup_sound = 'sound/items/pickup/bottle.ogg'
description_info = "Clicking on a venomous animal (or person) with the lid closed will express their venom into the beaker!"
var/label_text = ""
@@ -85,15 +86,16 @@
if(force && !(flags & NOBLUDGEON) && user.a_intent == I_HURT)
return ..()
// If the container is *closed* we do snake milking!~
if(!is_open_container() && isliving(M))
return attempt_snake_milking(user, M)
if(standard_feed_mob(user, M))
return
return 0
/obj/item/weapon/reagent_containers/glass/standard_feed_mob(var/mob/user, var/mob/target)
if(!is_open_container())
to_chat(user, "<span class='notice'>You need to open \the [src] first.</span>")
return 1
if(user.a_intent == I_HURT)
return 1
return ..()
@@ -101,8 +103,33 @@
/obj/item/weapon/reagent_containers/glass/self_feed_message(var/mob/user)
to_chat(user, "<span class='notice'>You swallow a gulp from \the [src].</span>")
/obj/item/weapon/reagent_containers/glass/proc/attempt_snake_milking(mob/living/user, mob/living/target)
var/reagent
var/amount
if(target.trait_injection_selected)
reagent = target.trait_injection_selected
amount = target.trait_injection_amount
else if(istype(target, /mob/living/simple_mob/animal/giant_spider))
var/mob/living/simple_mob/animal/giant_spider/spider = target
reagent = spider.poison_type
amount = spider.poison_per_bite
if(!reagent || !amount)
to_chat(user, span_warning("[target] does not have venom you can express. Open the beaker to drink from it."))
return TRUE
if(TIMER_COOLDOWN_RUNNING(target, COOLDOWN_VENOM_MILKING))
user.visible_message(span_warning("[user] attempts to express venom from [target], but nothing happens."), span_warning("[target] had their venom expressed too recently, try again later."))
return TRUE
TIMER_COOLDOWN_START(target, COOLDOWN_VENOM_MILKING, 30 SECONDS)
user.visible_message(span_notice("[user] expresses venom from [target]."))
reagents.add_reagent(reagent, amount)
return TRUE
/obj/item/weapon/reagent_containers/glass/afterattack(var/obj/target, var/mob/user, var/proximity)
if(!is_open_container() || !proximity) //Is the container open & are they next to whatever they're clicking?
if(!proximity || !is_open_container()) //Is the container open & are they next to whatever they're clicking?
return 1 //If not, do nothing.
for(var/type in can_be_placed_into) //Is it something it can be placed into?
if(istype(target, type))
+3 -2
View File
@@ -112,10 +112,11 @@
//Person OOCly doesn't want people impersonating them
locked = ckeylock
//Prevent people from printing restricted and whitelisted species
var/datum/species/S = GLOB.all_species["[M.dna.species]"]
if(S)
toocomplex = (S.spawn_flags & SPECIES_IS_WHITELISTED) || (S.spawn_flags & SPECIES_IS_RESTRICTED)
// Force ckey locking if species is whitelisted
if((S.spawn_flags & SPECIES_IS_WHITELISTED) || (S.spawn_flags & SPECIES_IS_RESTRICTED))
locked = TRUE
//General stuff about them
synthetic = M.isSynthetic()
+17 -2
View File
@@ -33,7 +33,11 @@
var/datum/dna2/record/R = current_project.mydna
var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species)
if(current_project.locked)
H.resleeve_lock = current_project.ckey
if(current_project.ckey)
H.resleeve_lock = current_project.ckey
else
// Ensure even body scans without an attached ckey respect locking
H.resleeve_lock = "@badckey"
//Fix the external organs
for(var/part in current_project.limb_data)
@@ -180,6 +184,7 @@
occupant = null
if(locked)
locked = 0
update_icon()
return
return
@@ -189,6 +194,12 @@
return 100 * ((occupant.health + abs(config.health_threshold_dead)) / (occupant.maxHealth + abs(config.health_threshold_dead)))
return 0
/obj/machinery/clonepod/transhuman/examine(mob/user, infix, suffix)
. = ..()
if(occupant)
var/completion = get_completion()
. += "Progress: [round(completion)]% [chat_progress_bar(round(completion), TRUE)]"
//Synthetic version
/obj/machinery/transhuman/synthprinter
name = "SynthFab 3000"
@@ -282,7 +293,11 @@
var/datum/dna2/record/R = current_project.mydna
var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species)
if(current_project.locked)
H.resleeve_lock = current_project.ckey
if(current_project.ckey)
H.resleeve_lock = current_project.ckey
else
// Ensure even body scans without an attached ckey respect locking
H.resleeve_lock = "@badckey"
//Fix the external organs
for(var/part in current_project.limb_data)
+60 -46
View File
@@ -1,5 +1,64 @@
//Health bars in the game window would be pretty challenging and I don't know how to do that, so I thought this would be a good alternative
// Generates the progress bar text
/proc/chat_progress_bar(percentage, add_color = FALSE)
switch(percentage)
if(100)
. = "|▓▓▓▓▓▓▓▓▓▓|"
if(95 to 99)
. = "|▓▓▓▓▓▓▓▓▓▒|"
if(90 to 94)
. = "|▓▓▓▓▓▓▓▓▓░|"
if(85 to 89)
. = "|▓▓▓▓▓▓▓▓▒░|"
if(80 to 84)
. = "|▓▓▓▓▓▓▓▓░░|"
if(75 to 79)
. = "|▓▓▓▓▓▓▓▒░░|"
if(70 to 74)
. = "|▓▓▓▓▓▓▓░░░|"
if(65 to 69)
. = "|▓▓▓▓▓▓▒░░░|"
if(60 to 64)
. = "|▓▓▓▓▓▓░░░░|"
if(55 to 59)
. = "|▓▓▓▓▓▒░░░░|"
if(50 to 54)
. = "|▓▓▓▓▓░░░░░|"
if(45 to 49)
. = "|▓▓▓▓▒░░░░░|"
if(40 to 44)
. = "|▓▓▓▓░░░░░░|"
if(35 to 39)
. = "|▓▓▓▒░░░░░░|"
if(30 to 34)
. = "|▓▓▓░░░░░░░|"
if(25 to 29)
. = "|▓▓▒░░░░░░░|"
if(20 to 24)
. = "|▓▓░░░░░░░░|"
if(15 to 19)
. = "|▓▒░░░░░░░░|"
if(10 to 14)
. = "|▓░░░░░░░░░|"
if(5 to 9)
. = "|▒░░░░░░░░░|"
if(0)
. = "|░░░░░░░░░░|"
else
. = "!░░░░░░░░░░!"
if(add_color)
switch(percentage)
if(75 to 100)
. = span_green(.)
if(50 to 75)
. = span_yellow(.)
if(25 to 50)
. = span_orange(.)
else
. = span_red(.)
/mob/living/proc/chat_healthbar(var/mob/living/reciever, onExamine = FALSE, override = FALSE)
if(!reciever) //No one to send it to, don't bother
return
@@ -26,52 +85,7 @@
ourpercent = round(ourpercent)
switch(ourpercent) //I thought about trying to do this in a more automated way but my brain isn't very large so enjoy my stupid switch statement
if(100)
ourbar = "|▓▓▓▓▓▓▓▓▓▓|"
if(95 to 99)
ourbar = "|▓▓▓▓▓▓▓▓▓▒|"
if(90 to 94)
ourbar = "|▓▓▓▓▓▓▓▓▓░|"
if(85 to 89)
ourbar = "|▓▓▓▓▓▓▓▓▒░|"
if(80 to 84)
ourbar = "|▓▓▓▓▓▓▓▓░░|"
if(75 to 79)
ourbar = "|▓▓▓▓▓▓▓▒░░|"
if(70 to 74)
ourbar = "|▓▓▓▓▓▓▓░░░|"
if(65 to 69)
ourbar = "|▓▓▓▓▓▓▒░░░|"
if(60 to 64)
ourbar = "|▓▓▓▓▓▓░░░░|"
if(55 to 59)
ourbar = "|▓▓▓▓▓▒░░░░|"
if(50 to 54)
ourbar = "|▓▓▓▓▓░░░░░|"
if(45 to 49)
ourbar = "|▓▓▓▓▒░░░░░|"
if(40 to 44)
ourbar = "|▓▓▓▓░░░░░░|"
if(35 to 39)
ourbar = "|▓▓▓▒░░░░░░|"
if(30 to 34)
ourbar = "|▓▓▓░░░░░░░|"
if(25 to 29)
ourbar = "|▓▓▒░░░░░░░|"
if(20 to 24)
ourbar = "|▓▓░░░░░░░░|"
if(15 to 19)
ourbar = "|▓▒░░░░░░░░|"
if(10 to 14)
ourbar = "|▓░░░░░░░░░|"
if(5 to 9)
ourbar = "|▒░░░░░░░░░|"
if(0)
ourbar = "|░░░░░░░░░░|"
else
ourbar = "!░░░░░░░░░░!"
ourbar = chat_progress_bar(ourpercent, FALSE)
ourbar = "[ourbar] [which_var] - [src.name]"
if(stat == UNCONSCIOUS)
+3
View File
@@ -259,6 +259,7 @@
P.digest_pain = src.digest_pain
P.stumble_vore = src.stumble_vore
P.eating_privacy_global = src.eating_privacy_global
P.allow_mimicry = src.allow_mimicry
P.nutrition_message_visible = src.nutrition_message_visible
P.nutrition_messages = src.nutrition_messages
@@ -312,6 +313,7 @@
food_vore = P.food_vore
digest_pain = P.digest_pain
eating_privacy_global = P.eating_privacy_global
allow_mimicry = P.allow_mimicry
nutrition_message_visible = P.nutrition_message_visible
nutrition_messages = P.nutrition_messages
@@ -1212,6 +1214,7 @@
dispvoreprefs += "<b>Can be stepped on/over:</b> [step_mechanics_pref ? "Allowed" : "Disallowed"]<br>"
dispvoreprefs += "<b>Can be picked up:</b> [pickup_pref ? "Allowed" : "Disallowed"]<br>"
dispvoreprefs += "<b>Global Vore Privacy is:</b> [eating_privacy_global ? "Subtle" : "Loud"]<br>"
dispvoreprefs += "<b>Allow Mimicry:</b> [allow_mimicry ? "Yes" : "No"]<br>"
user << browse("<html><head><title>Vore prefs: [src]</title></head><body><center>[dispvoreprefs]</center></body></html>", "window=[name]mvp;size=300x400;can_resize=1;can_minimize=0")
onclose(user, "[name]")
return
+1
View File
@@ -27,6 +27,7 @@
var/selective_preference = DM_DEFAULT // Preference for selective bellymode
var/text_warnings = TRUE // Allows us to dismiss the text limit warning messages after viewing it once per round
var/eating_privacy_global = FALSE // Makes eating attempt/success messages only reach for subtle range if true, overwritten by belly-specific var
var/allow_mimicry = TRUE // Allows mimicking their character
var/allow_mind_transfer = FALSE //Allows ones mind to be taken over or swapped
var/nutrition_message_visible = TRUE
var/list/nutrition_messages = list(
+5
View File
@@ -54,6 +54,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
var/permit_healbelly = TRUE
var/noisy = FALSE
var/eating_privacy_global = FALSE //Makes eating attempt/success messages only reach for subtle range if true, overwritten by belly-specific var
var/allow_mimicry = TRUE
// These are 'modifier' prefs, do nothing on their own but pair with drop_prey/drop_pred settings.
var/drop_vore = TRUE
@@ -196,6 +197,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
weight_message_visible = json_from_file["weight_message_visible"]
weight_messages = json_from_file["weight_messages"]
eating_privacy_global = json_from_file["eating_privacy_global"]
allow_mimicry = json_from_file["allow_mimicry"]
vore_sprite_color = json_from_file["vore_sprite_color"]
allow_mind_transfer = json_from_file["allow_mind_transfer"]
@@ -254,6 +256,8 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
weight_message_visible = TRUE
if(isnull(eating_privacy_global))
eating_privacy_global = FALSE
if(isnull(allow_mimicry))
allow_mimicry = TRUE
if(isnull(nutrition_messages))
nutrition_messages = list(
"They are starving! You can hear their stomach snarling from across the room!",
@@ -329,6 +333,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
"weight_message_visible" = weight_message_visible,
"weight_messages" = weight_messages,
"eating_privacy_global" = eating_privacy_global,
"allow_mimicry" = allow_mimicry,
"vore_sprite_color" = vore_sprite_color,
)
+7
View File
@@ -347,6 +347,7 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
"weight_message_visible" = host.weight_message_visible,
"weight_messages" = host.weight_messages,
"eating_privacy_global" = host.eating_privacy_global,
"allow_mimicry" = host.allow_mimicry,
)
return data
@@ -535,6 +536,12 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
host.eating_privacy_global = host.eating_privacy_global
unsaved_changes = TRUE
return TRUE
if("toggle_mimicry")
host.allow_mimicry = !host.allow_mimicry
if(host.client.prefs_vr)
host.client.prefs_vr.allow_mimicry = host.allow_mimicry
unsaved_changes = TRUE
return TRUE
if("toggle_devour")
host.devourable = !host.devourable
if(host.client.prefs_vr)
+1
View File
@@ -249,6 +249,7 @@
new_mob.nutrition_message_visible = nutrition_message_visible
new_mob.allow_spontaneous_tf = allow_spontaneous_tf
new_mob.eating_privacy_global = eating_privacy_global
new_mob.allow_mimicry = allow_mimicry
new_mob.text_warnings = text_warnings
new_mob.allow_mind_transfer = allow_mind_transfer
+1
View File
@@ -114,6 +114,7 @@ mewchild - Vox
milu - Xenochimera
mrsebbi - Xenochimera
mullins111 - Black-Eyed Shadekin
murchant - Teppi
natje - Xenochimera
nerdass - Protean
newyorks - Protean
+1
View File
@@ -13,6 +13,7 @@ h0lysquirr3l - clown
joey4298 - mime
lm40 - clown
losstinspess - mime
murchant - mime
pastelprincedan - mime
pastelprincedan - clown
radishfriend - clown
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
#Project dependencies file
#Final authority on what's required to fully build the project
# byond version
export BYOND_MAJOR=515
export BYOND_MINOR=1637
#rust_g git tag
export RUST_G_VERSION=3.3.0
#node version
export NODE_VERSION_LTS=20.13.0
# compatiblility mode MUST work with windows 7
export NODE_VERSION_COMPAT=20.2.0
# SpacemanDMM git tag
export SPACEMAN_DMM_VERSION=suite-1.8
# Python version for mapmerge and other tools
export PYTHON_VERSION=3.8.10
#dreamluau repo
export DREAMLUAU_REPO="tgstation/dreamluau"
#dreamluau git tag
export DREAMLUAU_VERSION=0.1.2
#hypnagogic repo
export CUTTER_REPO=spacestation13/hypnagogic
#hypnagogic git tag
export CUTTER_VERSION=v4.0.0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 69 KiB

@@ -318,7 +318,6 @@
master_tag = "aerostat_n_n_airlock";
pixel_x = 24;
pixel_y = 24;
tag = "aerostat_n_n_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/north)
@@ -924,7 +923,6 @@
master_tag = "aerostat_s_s_airlock";
pixel_x = -24;
pixel_y = -24;
tag = "aerostat_s_s_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/south)
@@ -1969,7 +1967,6 @@
dir = 8;
master_tag = "aerostat_n_e_airlock";
pixel_x = 32;
tag = "aerostat_n_e_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/north)
@@ -8673,7 +8670,6 @@
dir = 8;
master_tag = "aerostat_s_e_airlock";
pixel_x = 24;
tag = "aerostat_s_e_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/south)
@@ -11336,7 +11332,6 @@
dir = 4;
master_tag = "aerostat_n_w_airlock";
pixel_x = -32;
tag = "aerostat_n_w_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/north)
@@ -13479,7 +13474,6 @@
dir = 4;
master_tag = "aerostat_s_w_airlock";
pixel_x = -24;
tag = "aerostat_s_w_sensint"
},
/turf/simulated/floor/tiled/techfloor,
/area/offmap/aerostat/inside/airlock/south)
-1
View File
@@ -13,7 +13,6 @@
*.md text eol=lf
*.bat text eol=lf
yarn.lock text eol=lf
bin/tgui text eol=lf
## Treat bundles as binary
*.bundle.* binary
+73 -114
View File
@@ -4,21 +4,15 @@
tgui is a robust user interface framework of /tg/station.
tgui is very different from most UIs you will encounter in BYOND programming.
It is heavily reliant on Javascript and web technologies as opposed to DM.
If you are familiar with NanoUI (a library which can be found on almost
every other SS13 codebase), tgui should be fairly easy to pick up.
tgui is very different from most UIs you will encounter in BYOND programming. It is heavily reliant on Javascript and web technologies as opposed to DM. If you are familiar with NanoUI (a library which can be found on almost every other SS13 codebase), tgui should be fairly easy to pick up.
## Learn tgui
People come to tgui from different backgrounds and with different
learning styles. Whether you prefer a more theoretical or a practical
approach, we hope youll find this section helpful.
People come to tgui from different backgrounds and with different learning styles. Whether you prefer a more theoretical or a practical approach, we hope youll find this section helpful.
### Practical Tutorial
If you are completely new to frontend and prefer to **learn by doing**,
start with our [practical tutorial](docs/tutorial-and-examples.md).
If you are completely new to frontend and prefer to **learn by doing**, start with our [practical tutorial](docs/tutorial-and-examples.md).
### Guides
@@ -26,9 +20,7 @@ This project uses React. Take your time to read the guide:
- [React guide](https://react.dev/learn)
If you were already familiar with an older, Ractive-based tgui, and want
to translate concepts between old and new tgui, read this
[interface conversion guide](docs/converting-old-tgui-interfaces.md).
If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this [interface conversion guide](docs/converting-old-tgui-interfaces.md).
### Other Documentation
@@ -39,97 +31,84 @@ to translate concepts between old and new tgui, read this
## Pre-requisites
You will need these programs to start developing in tgui:
If you are using the tooling provided in this repo, everything is included! Feel free to skip this step.
- [Node v16.13+](https://nodejs.org/en/download/)
However, if you want finer control over the installation or build process, you will need these:
- [Node v20.2+](https://nodejs.org/en/download/)
- **LTS** release is recommended instead of latest
- [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) (optional)
- **DO NOT install Chocolatey if Node installer asks you to!**
- [Yarn v4.1.1+](https://yarnpkg.com/getting-started/install)
- You can run `npm install -g yarn` to install it.
- [Git Bash](https://git-scm.com/downloads)
or [MSys2](https://www.msys2.org/) (optional)
**DO NOT install Chocolatey if Node installer asks you to!**
## Usage
**For Git Bash, MSys2, WSL, Linux or macOS users:**
**Via provided cmd scripts (Windows)**:
Change your directory to `tgui`.
- `bin/tgui-build` - Build tgui in production mode and run a full suite of code checks.
- `bin/tgui-dev` - Launch a development server.
- `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-sonar` - Analyze code with SonarQube.
- `bin/tgui-bench` - Run benchmarks.
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.
> To open a CMD or PowerShell window in any open folder, right click **while holding Shift** on any free space in the folder, then click on either `Open command window here` or `Open PowerShell window here`.
Run `bin/tgui` with any of the options listed below.
**Via Juke Build (cross-platform)**:
**For Windows CMD or PowerShell users:**
- `tools/build/build tgui` - Build tgui in production mode.
- `tools/build/build tgui-dev` - Build tgui in production mode.
- `tools/build/build tgui-dev --reload` - Reload byond cache once.
- `tools/build/build tgui-dev --debug` - Run server with debug logging enabled.
- `tools/build/build tgui-dev --no-hot` - Disable hot module replacement (helps when doing development on IE8).
- `tools/build/build tgui-lint` - Show (and auto-fix) problems with the code.
- `tools/build/build tgui-sonar` - Analyze code with SonarQube.
- `tools/build/build tgui-test` - Run unit and integration tests.
- `tools/build/build tgui-analyze` - Run a bundle analyzer.
- `tools/build/build tgui-bench` - Run benchmarks.
- `tools/build/build tgui-clean` - Clean up tgui folder.
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`.
> With Juke Build, you can run multiple targets together, e.g.:
>
> ```
> tools/build/build tgui tgui-lint tgui-tsc tgui-test
> ```
Run `.\bin\tgui.bat` with any of the options listed below.
**Via Yarn (cross-platform)**:
> 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.
Run `yarn install` once to install tgui dependencies.
**Available commands:**
- `yarn tgui:build` - Build tgui in production mode.
- `yarn tgui:build [options]` - Build tgui with custom webpack options.
- `yarn tgui:dev` - Launch a development server.
- `yarn tgui:dev --reload` - Reload byond cache once.
- `yarn tgui:dev --debug` - Run server with debug logging enabled.
- `yarn tgui:dev --no-hot` - Disable hot module replacement (helps when doing development on IE8).
- `yarn tgui:lint` - Show (and auto-fix) problems with the code.
- `yarn tgui:sonar` - Analyze code with SonarQube.
- `yarn tgui:tsc` - Check code with TypeScript compiler.
- `yarn tgui:test` - Run unit and integration tests.
- `yarn tgui:analyze` - Run a bundle analyzer.
- `yarn tgui:bench` - Run benchmarks.
- `yarn tgfont:build` - Build icon fonts.
- `yarn tgui-polyfill:build` - Build polyfills. You need to run it when updating any of the static (numbered) polyfills.
- `bin/tgui` - (Recommended build option) Build the project in production mode.
- `bin/tgui --pretty` - Check/Prettify all files inside of the packages folder (Mainly Interfaces/Components). Build the project in production mode afterwards.
- `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
game as you code it. Very useful, highly recommended.
- In order to use it, you should start the game server first, connect to it
and wait until the world has been properly loaded and you are no longer
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
doing development on IE8).
- `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.
## Important Memo
**For virgins:**
You can double-click these batch files to achieve the same thing:
- `bin\tgui.bat` - (Recommended build option) Build the project in production mode.
- `bin\tgui-prettybuild.bat` - Prettify all the files inside the packages folder. Build the project in production mode afterwards.
- `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.
> We prefer to keep it version controlled, so that people could build the
> game just by using Dream Maker.
Remember to always run a full build of tgui before submitting a PR, because it comes with the full suite of CI checks, and runs much faster on your computer than on GitHub servers. It will save you some time and possibly a few broken commits! Address the issues that are reported by the tooling as much as possible, because maintainers will beat you with a ruler and force you to address them anyway (unless it's a false positive or something unfixable).
## 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.
Make sure path to your working directory does not contain spaces, special unicode characters, exclamation marks or any other special symbols. If so, move codebase to a location which does not contain these characters.
This is a known issue with Yarn Berry, and fix is going to happen someday.
This is a known issue with Yarn (and some other tools, like Webpack), and fix is going to happen eventually.
**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 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.
This happens if your Documents folder in Windows has a custom location, for 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.
```
BYOND_CACHE="E:/Libraries/Documents/BYOND/cache"
@@ -139,14 +118,11 @@ BYOND_CACHE="E:/Libraries/Documents/BYOND/cache"
> 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.
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
## Dev Server Tools
When developing with `tgui-dev-server`, you will have access to certain
development only features.
@@ -157,7 +133,7 @@ logs and time spent on rendering. Use this information to optimize your
code, and try to keep re-renders below 16ms.
**Kitchen Sink.**
Press `F12` to open the KitchenSink interface. This interface is a
Press `F12` or click the green bug to open the KitchenSink interface. This interface is a
playground to test various tgui components.
**Layout Debugger.**
@@ -168,12 +144,13 @@ together, and can reveal certain layout bugs which are not normally visible.
## Browser Developer Tools
To debug TGUI interfaces with browser-style developer tools, there exists a utility
that Microsoft bundles with Windows to debug any Internet Explorer/Trident-using interface,
that Microsoft bundles with Windows called IEChooser/F12 to debug any Internet Explorer/Trident-using interface,
which BYOND uses.
This provides invaluable tools such as a local console, a DOM viewer, an interactive debugger, and more.
The 64-bit version that we use is located at `%windir%\SysWOW64\F12\IEChooser.exe`.
You can access the `IEChooser.exe` by pressing <kbd>Win + R</kbd>, then typing `f12`, then pressing enter.
To manually go there: 64-bit version that we use is located at `%windir%\SysWOW64\F12\IEChooser.exe`.
There's also a 32-bit one in `system32\`.
Simply launch the application after you've opened a TGUI window, and choose the .html name.
@@ -185,42 +162,24 @@ so you'll need to restart it if it disconnects from the window.
## Project Structure
- `/packages` - Each folder here represents a self-contained Node module.
- `/packages/common` - Helper functions
- `/packages/common` - Helper functions that are used throughout all packages.
- `/packages/tgui/index.js` - Application entry point.
- `/packages/tgui/components` - Basic UI building blocks.
- `/packages/tgui/interfaces` - Actual in-game interfaces.
Interface takes data via the `state` prop and outputs an html-like stucture,
which you can build using existing UI components.
- `/packages/tgui/layouts` - Root level UI components, that affect the final
look and feel of the browser window. They usually hold various window
elements, like the titlebar and resize handlers, and control the UI theme.
- `/packages/tgui/routes.js` - This is where tgui decides which interface to
pull and render.
- `/packages/tgui/layout.js` - A root-level component, holding the
window elements, like the titlebar, buttons, resize handlers. Calls
`routes.js` to decide which component to render.
- `/packages/tgui/layouts` - Root level UI components, that affect the final look and feel of the browser window. These hold various window elements, like the titlebar and resize handlers, and control the UI theme.
- `/packages/tgui/routes.js` - This is where tgui decides which interface to pull and render.
- `/packages/tgui/styles/main.scss` - CSS entry point.
- `/packages/tgui/styles/functions.scss` - Useful SASS functions.
Stuff like `lighten`, `darken`, `luminance` are defined here.
- `/packages/tgui/styles/atomic` - Atomic CSS classes.
These are very simple, tiny, reusable CSS classes which you can use and
combine to change appearance of your elements. Keep them small.
- `/packages/tgui/styles/components` - CSS classes which are used
in UI components. These stylesheets closely follow the
[BEM](https://en.bem.info/methodology/) methodology.
- `/packages/tgui/styles/interfaces` - Custom stylesheets for your interfaces.
Add stylesheets here if you really need a fine control over your UI styles.
- `/packages/tgui/styles/functions.scss` - Useful SASS functions. Stuff like `lighten`, `darken`, `luminance` are defined here.
- `/packages/tgui/styles/atomic` - Atomic CSS classes. These are very simple, tiny, reusable CSS classes which you can use and combine to change appearance of your elements. Keep them small.
- `/packages/tgui/styles/components` - CSS classes which are used in UI components. These stylesheets closely follow the [BEM](https://en.bem.info/methodology/) methodology.
- `/packages/tgui/styles/interfaces` - Custom stylesheets for your interfaces. Add stylesheets here if you really need a fine control over your UI styles.
- `/packages/tgui/styles/layouts` - Layout-related styles.
- `/packages/tgui/styles/themes` - Contains all the various themes you can
use in tgui. Each theme must be registered in `webpack.config.js` file.
- `/packages/tgui/styles/themes` - Contains themes that you can use in tgui. Each theme must be registered in `/packages/tgui/index.js` file.
## License
Source code is covered by /tg/station's parent license - **AGPL-3.0**
(see the main [README](../README.md)), unless otherwise indicated.
Source code is covered by /tg/station's parent license - **AGPL-3.0** (see the main [README](../README.md)), unless otherwise indicated.
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.
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.
-235
View File
@@ -1,235 +0,0 @@
#!/usr/bin/env bash
## Copyright (c) 2020 Aleksej Komarov
## SPDX-License-Identifier: MIT
set -e
shopt -s globstar
shopt -s expand_aliases
## Initial set-up
## --------------------------------------------------------
## Returns an absolute path to file
alias tgui-realpath="readlink -f"
## Fallbacks for GNU readlink
## Detecting GNU coreutils http://stackoverflow.com/a/8748344/319952
if ! readlink --version >/dev/null 2>&1; then
if hash greadlink 2>/dev/null; then
alias tgui-realpath="greadlink -f"
else
alias tgui-realpath="perl -MCwd -le 'print Cwd::abs_path(shift)'"
fi
fi
## Find a canonical path to project root
base_dir="$(dirname "$(tgui-realpath "${0}")")/.."
base_dir="$(tgui-realpath "${base_dir}")"
## 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
## --------------------------------------------------------
## Installs node modules
task-install() {
cd "${base_dir}"
yarn install
}
## Runs webpack
task-webpack() {
cd "${base_dir}"
yarn run webpack-cli "${@}"
}
## Runs a development server
task-dev-server() {
cd "${base_dir}"
yarn node --experimental-modules packages/tgui-dev-server/index.js "${@}"
}
## Run a linter through all packages
task-lint() {
cd "${base_dir}"
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}"
## 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.*
## 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
diff="$(git diff packages/tgui/public/tgui.bundle.*)"
if [[ -n ${diff} ]]; then
echo "Error: our build differs from the build committed into git."
echo "Please rebuild tgui."
exit 1
fi
echo "tgui: build is ok"
}
## VOREStation Addition End
## Installs merge drivers and git hooks
task-install-git-hooks() {
cd "${base_dir}"
local git_root
local git_base_dir
git_root="$(git rev-parse --show-toplevel)"
git_base_dir="${base_dir/${git_root}/.}"
git config --replace-all merge.tgui-merge-bundle.driver \
"${git_base_dir}/bin/tgui --merge=bundle %O %A %B %L"
echo "tgui: Merge drivers have been successfully installed!"
}
## Bundle merge driver
task-merge-bundle() {
local file_ancestor="${1}"
local file_current="${2}"
local file_other="${3}"
local conflict_marker_size="${4}"
echo "tgui: Discarding a local tgui build"
## Do nothing (file_current will be merged and is what we want to keep).
exit 0
}
## Main
## --------------------------------------------------------
if [[ ${1} == "--merge"* ]]; then
if [[ ${1} == "--merge=bundle" ]]; then
shift 1
task-merge-bundle "${@}"
fi
echo "Unknown merge strategy: ${1}"
exit 1
fi
if [[ ${1} == "--install-git-hooks" ]]; then
shift 1
task-install-git-hooks
exit 0
fi
if [[ ${1} == "--clean" ]]; then
task-clean
exit 0
fi
if [[ ${1} == "--dev" ]]; then
shift
task-install
task-dev-server "${@}"
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-lint "${@}"
exit 0
fi
if [[ ${1} == '--lint-harder' ]]; then
shift 1
task-install
task-lint -c .eslintrc-harder.yml "${@}"
exit 0
fi
if [[ ${1} == '--fix' ]]; then
shift 1
task-install
task-lint --fix "${@}"
exit 0
fi
if [[ ${1} == '--test' ]]; then
shift 1
task-install
task-test "${@}"
exit 0
fi
## Analyze the bundle
if [[ ${1} == '--analyze' ]]; then
task-install
task-webpack --mode=production --analyze
exit 0
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-lint --fix
task-webpack --mode=production
exit 0
fi
## Run webpack with custom flags
task-install
task-webpack "${@}"
-9
View File
@@ -1,9 +0,0 @@
@echo off
rem Copyright (c) 2020 Aleksej Komarov
rem SPDX-License-Identifier: MIT
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --bench %*
rem Pause if launched in a separate shell unless initiated from powershell
echo %PSModulePath% | findstr %USERPROFILE% >NUL
if %errorlevel% equ 0 exit 0
echo %cmdcmdline% | find /i "/c"
if %errorlevel% equ 0 pause
-9
View File
@@ -1,9 +0,0 @@
@echo off
rem Copyright (c) 2020 Aleksej Komarov
rem SPDX-License-Identifier: MIT
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --dev %*
rem Pause if launched in a separate shell unless initiated from powershell
echo %PSModulePath% | findstr %USERPROFILE% >NUL
if %errorlevel% equ 0 exit 0
echo %cmdcmdline% | find /i "/c"
if %errorlevel% equ 0 pause
-9
View File
@@ -1,9 +0,0 @@
@echo off
rem Copyright (c) 2020 Aleksej Komarov
rem SPDX-License-Identifier: MIT
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --pretty %*
rem Pause if launched in a separate shell unless initiated from powershell
echo %PSModulePath% | findstr %USERPROFILE% >NUL
if %errorlevel% equ 0 exit 0
echo %cmdcmdline% | find /i "/c"
if %errorlevel% equ 0 pause
-9
View File
@@ -1,9 +0,0 @@
@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
-177
View File
@@ -1,177 +0,0 @@
## 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 --experimental-modules "packages/tgui-dev-server/index.js" @Args
}
function task-bench {
yarn tgui:bench @Args
}
function task-prettier {
yarn tgui:prettier @Args
}
function task-prettify {
yarn prettier --write packages @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.*"
## 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
}
if ($Args[0] -eq "--pretty") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-prettify
task-prettier
task-lint
task-webpack --mode=production
exit 0
}
## Analyze the bundle
if ($Args[0] -eq "--analyze") {
task-install
task-webpack --mode=production --analyze
exit 0
}
if ($Args[0] -eq "--bench") {
$Rest = $Args | Select-Object -Skip 1
task-install
task-bench --wait-on-error
exit 0
}
}
## Make a production webpack build
if ($Args.Length -eq 0) {
task-install
task-prettier
task-lint
task-webpack --mode=production
exit 0
}
## Run webpack with custom flags
task-install
task-webpack @Args
+1 -1
View File
@@ -3,7 +3,7 @@
"name": "tgfont",
"version": "1.0.0",
"scripts": {
"build": "node mkdist.cjs && fantasticon --config config.cjs"
"tgfont:build": "node mkdist.cjs && fantasticon --config config.cjs"
},
"dependencies": {
"fantasticon": "^1.2.3"
@@ -1,9 +1,11 @@
import { toFixed } from 'common/math';
import { capitalize } from 'common/string';
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import {
Box,
Button,
Collapsible,
Divider,
Dropdown,
Flex,
@@ -11,24 +13,26 @@ import {
Image,
Input,
Section,
Slider,
Stack,
} from 'tgui/components';
import { NoSpriteWarning } from '../components';
import { prepareSearch } from '../functions';
import { Cell, Component, Target } from '../types';
import { Cell, Component, InstalledCell, Target } from '../types';
export const ModifyRobotComponent = (props: {
target: Target;
cell: string | null;
cell: InstalledCell;
cells: Record<string, Cell>;
}) => {
const { act } = useBackend();
const { target, cell, cells } = props;
const [searchComponentReplaceText, setSearchComponentReplaceText] =
useState<string>('');
const [searchComponentRemoveText, setSearchComponentRemoveText] =
useState<string>('');
const [selectedCell, setSelectedCell] = useState<string>(cell || '');
const [selectedCell, setSelectedCell] = useState<string>(cell.name || '');
const cell_options = Object.keys(cells) as Array<string>;
return (
@@ -46,7 +50,7 @@ export const ModifyRobotComponent = (props: {
buttonIcon="arrows-spin"
celltype={cells[selectedCell]?.path}
selected_cell={selectedCell}
cell={cell || undefined}
cell={cell.name || undefined}
/>
</Flex.Item>
<Flex.Item width="30%">
@@ -65,13 +69,32 @@ export const ModifyRobotComponent = (props: {
<Stack.Item>
<Section title="Power cell">
Current cell:{' '}
{cell ? (
capitalize(cell)
{cell.name ? (
capitalize(cell.name)
) : (
<Box inline color="red">
No cell installed!
</Box>
)}
<Slider
stepPixelSize={5}
format={(value) => toFixed(value, 2)}
disabled={!cell.charge}
minValue={0}
maxValue={100}
value={((cell.charge || 0) / (cell.maxcharge || 1)) * 100}
onChange={(e, value) =>
act('adjust_cell_charge', {
charge: (value / 100) * (cell.maxcharge || 0),
})
}
>
<Flex>
<Flex.Item>Current charge</Flex.Item>
<Flex.Item grow />
<Flex.Item>{cell.charge}</Flex.Item>
</Flex>
</Slider>
<Dropdown
width="100%"
selected={selectedCell}
@@ -92,6 +115,57 @@ export const ModifyRobotComponent = (props: {
</Stack.Item>
</Stack>
</Section>
<Section scrollable fill>
<Collapsible title="Badmin">
{target.components.map((component, i) => {
return (
<Collapsible title={capitalize(component.name)} key={i}>
<Slider
color="red"
disabled={component.installed !== 1}
minValue={0}
maxValue={component.max_damage * 5 || 0}
value={component.brute_damage || 0}
onChange={(e, value) =>
act('adjust_brute', {
component: component.ref,
damage: value,
})
}
>
<Flex>
<Flex.Item>Brute damage</Flex.Item>
<Flex.Item grow />
<Flex.Item>{component.brute_damage}</Flex.Item>
</Flex>
</Slider>
<Slider
color="orange"
key={i}
disabled={component.installed !== 1}
minValue={0}
maxValue={component.max_damage * 5 || 0}
value={component.electronics_damage || 0}
onChange={(e, value) =>
act('adjust_electronics', {
component: component.ref,
damage: value,
})
}
>
<Flex>
<Flex.Item>Electronics damage</Flex.Item>
<Flex.Item grow />
<Flex.Item>
{component.electronics_damage}
</Flex.Item>
</Flex>
</Slider>
</Collapsible>
);
})}
</Collapsible>
</Section>
</Stack.Item>
</Stack>
</Flex.Item>
@@ -5,7 +5,7 @@ export type Data = {
target: Target | null;
all_robots: DropdownEntry[];
model_options: string[] | null;
cell: string | null;
cell: InstalledCell;
cell_options: Record<string, Cell>;
id_icon: string;
access_options: Access[] | undefined;
@@ -98,6 +98,12 @@ export type PKA = {
max_capacity: number;
};
export type InstalledCell = {
name: string | null;
charge: number | null;
maxcharge: number | null;
};
export type Cell = {
path: string;
charge: number;
@@ -1,6 +1,7 @@
import { BooleanLike } from 'common/react';
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Box, Button, LabeledList, Section } from 'tgui/components';
import { Box, Button, Icon, LabeledList, Section } from 'tgui/components';
type Data = {
owner: string;
@@ -19,13 +20,43 @@ type category = {
ref: string;
};
const specialIconColors = {
'Enable Flashlight': '#0f0',
'Disable Flashlight': '#f00',
};
export const pda_main_menu = (props) => {
const { act, data } = useBackend<Data>();
const [showTransition, setShowTransition] = useState('');
const startProgram = (program: category) => {
if (
program.name.startsWith('Enable') ||
program.name.startsWith('Disable')
) {
// Special case, instant
act('StartProgram', { program: program.ref });
return;
}
setShowTransition(program.icon);
setTimeout(() => {
setShowTransition('');
act('StartProgram', { program: program.ref });
}, 200);
};
const { owner, ownjob, idInserted, categories, pai, notifying, apps } = data;
return (
<>
{showTransition && (
<Box className="Pda__Transition">
<Icon name={showTransition} size={4} />
</Box>
)}
<Box>
<LabeledList>
<LabeledList.Item label="Owner" color="average">
@@ -57,8 +88,13 @@ export const pda_main_menu = (props) => {
key={app.ref}
icon={app.ref in notifying ? app.notify_icon : app.icon}
iconSpin={app.ref in notifying}
iconColor={
app.name in specialIconColors
? specialIconColors[app.name]
: null
}
color={app.ref in notifying ? 'red' : 'transparent'}
onClick={() => act('StartProgram', { program: app.ref })}
onClick={() => startProgram(app)}
>
{app.name}
</Button>
@@ -41,6 +41,7 @@ export const VoreUserPreferences = (props: {
nutrition_message_visible,
weight_message_visible,
eating_privacy_global,
allow_mimicry,
} = prefs;
const preferences = {
@@ -425,6 +426,19 @@ export const VoreUserPreferences = (props: {
disabled: 'Global Vore Privacy: Loud',
},
},
allow_mimicry: {
action: 'toggle_mimicry',
test: allow_mimicry,
tooltip: {
main: 'Allows some creatures to mimick your apperance.',
enable: ' Click here to allow mimicry.',
disable: ' Click here to forbid mimicry.',
},
content: {
enabled: 'Allow Mimicry: Yes',
disabled: 'Allow Mimicry: No',
},
},
};
return (
@@ -118,6 +118,9 @@ export const VoreUserPreferencesMechanical = (props: {
<Flex.Item basis="32%" grow={1}>
<VoreUserPreferenceItem spec={preferences.eating_privacy_global} />
</Flex.Item>
<Flex.Item basis="32%" grow={1}>
<VoreUserPreferenceItem spec={preferences.allow_mimicry} />
</Flex.Item>
</Flex>
</Section>
);
@@ -172,6 +172,7 @@ export type prefData = {
weight_message_visible: BooleanLike;
weight_messages: string[];
eating_privacy_global: BooleanLike;
allow_mimicry: BooleanLike;
vore_sprite_color: { stomach: string; 'taur belly': string };
vore_sprite_multiply: { stomach: BooleanLike; 'taur belly': BooleanLike };
};
@@ -203,6 +204,7 @@ export type localPrefs = {
examine_nutrition: preferenceData;
examine_weight: preferenceData;
eating_privacy_global: preferenceData;
allow_mimicry: preferenceData;
};
export type preferenceData = {
@@ -0,0 +1,37 @@
.Pda__Transition {
display: flex;
justify-content: center;
align-items: center;
// Fill positioned parent
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
// Dim
animation: dim 0.4s forwards;
}
.Pda__Transition > i {
animation: center-scale 0.4s forwards;
transform-origin: center center;
}
@keyframes dim {
from {
background: rgba(0, 0, 0, 0);
}
to {
background: rgba(0, 0, 0, 0.5);
}
}
@keyframes center-scale {
from {
transform: scale(1);
}
to {
transform: scale(4);
}
}
+1
View File
@@ -58,6 +58,7 @@
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
@include meta.load-css('./interfaces/NuclearBomb.scss');
@include meta.load-css('./interfaces/Paper.scss');
@include meta.load-css('./interfaces/Pda.scss');
@include meta.load-css('./interfaces/Roulette.scss');
@include meta.load-css('./interfaces/Safe.scss');
@include meta.load-css('./interfaces/TachyonArray.scss');
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+73
View File
@@ -0,0 +1,73 @@
#!/bin/sh
# bootstrap/node
#
# Node-finding script for all `sh` environments, including Linux, MSYS2,
# Git for Windows, and GitHub Desktop. Invokable from CLI or automation.
#
# If a node.exe installed by `node_.ps1` is present, it will be used.
# Otherwise, this script requires a system `node` to be provided.
set -e
# Convenience variables
Bootstrap="$(dirname "$0")"
Cache="$Bootstrap/.cache"
if [ "$TG_BOOTSTRAP_CACHE" ]; then
Cache="$TG_BOOTSTRAP_CACHE"
fi
OldPWD="$PWD"
cd "$Bootstrap/../.."
. ./dependencies.sh # sets NODE_VERSION_LTS
cd "$OldPWD"
NodeVersion="$NODE_VERSION_LTS"
NodeFullVersion="node-v$NodeVersion-win-x64"
NodeDir="$Cache/$NodeFullVersion"
NodeExe="$NodeDir/node.exe"
is_vendored="1"
# If a bootstrapped Node is not present, search on $PATH.
if [ "$(uname)" = "Linux" ] || [ ! -f "$NodeExe" ]; then
if [ "$TG_BOOTSTRAP_NODE_LINUX" ]; then
NodeFullVersion="node-v$NodeVersion-linux-x64"
NodeDir="$Cache/$NodeFullVersion/bin"
NodeExe="$NodeDir/node"
if [ ! -f "$NodeExe" ]; then
mkdir -p "$Cache"
Archive="$(realpath "$Cache/node-v$NodeVersion.tar.gz")"
curl "https://nodejs.org/download/release/v$NodeVersion/$NodeFullVersion.tar.gz" -o "$Archive"
(cd "$Cache" && tar xf "$Archive")
fi
elif command -v node >/dev/null 2>&1; then
NodeExe="node"
is_vendored="0"
else
echo
if command -v apt-get >/dev/null 2>&1; then
# Ubuntu advice
echo "Please install Node using your system's package manager:"
echo " sudo apt-get install nodejs"
elif uname | grep -q MSYS; then
# MSYS2 (not packaged) or Git for Windows advice
echo "Please run bootstrap/node.bat instead of bootstrap/node once"
echo "to install Node automatically, or install it from https://nodejs.org/"
elif command -v pacman >/dev/null 2>&1; then
# Arch advice
echo "Please install Node using your system's package manager:"
echo " sudo pacman -S nodejs"
else
# Generic advice
echo "Please install Node from https://nodejs.org/ or using your system's package manager."
fi
echo
exit 1
fi
fi
# Invoke Node with all command-line arguments
if [ "$is_vendored" = "1" ]; then
PATH="$(readlink -f "$NodeDir"):$PATH"
echo "Using vendored Node $("$NodeExe" --version)"
else
echo "Using system-wide Node $("$NodeExe" --version)"
fi
exec "$NodeExe" "$@"
+20
View File
@@ -0,0 +1,20 @@
@echo off
set NODE_SKIP_PLATFORM_CHECK=1
call powershell -NoLogo -ExecutionPolicy Bypass -File "%~dp0\node_.ps1" Download-Node
for /f "tokens=* USEBACKQ" %%s in (`
call powershell -NoLogo -ExecutionPolicy Bypass -File "%~dp0\node_.ps1" Get-Path
`) do (
set "PATH=%%s;%PATH%"
)
where node.exe >nul 2>nul
if %errorlevel% == 0 (
echo | set /p printed_str="Using vendored Node "
call node.exe --version
call node.exe %*
goto exit_with_last_error_level
)
echo "node.bat: Failed to bootstrap Node!"
%COMSPEC% /c exit 1
:exit_with_last_error_level
if not %errorlevel% == 0 %COMSPEC% /c exit %errorlevel% >nul
+71
View File
@@ -0,0 +1,71 @@
## bootstrap/node_.ps1
## Downloads a Node version to a cache directory and invokes it.
$ErrorActionPreference = "Stop"
function Extract-Variable {
param([string] $Path, [string] $Key)
foreach ($Line in Get-Content $Path) {
if ($Line.StartsWith("export $Key=")) {
return $Line.Substring("export $Key=".Length)
}
}
throw "Couldn't find value for $Key in $Path"
}
function Download-Node {
if (Test-Path $NodeTarget -PathType Leaf) {
return
}
Write-Output "Downloading Node v$NodeVersion (may take a while)"
New-Item $NodeTargetDir -ItemType Directory -ErrorAction silentlyContinue | Out-Null
$WebClient = New-Object Net.WebClient
$WebClient.DownloadFile($NodeSource, "$NodeTarget.downloading")
Rename-Item "$NodeTarget.downloading" $NodeTarget
}
## Convenience variables
$BaseDir = Split-Path $script:MyInvocation.MyCommand.Path
$Cache = "$BaseDir\.cache"
if ($Env:TG_BOOTSTRAP_CACHE) {
$Cache = $Env:TG_BOOTSTRAP_CACHE
}
# Get OS version
[int]$OSMajor = (Get-WmiObject -Class Win32_OperatingSystem).Version.Split(".")[0]
# Set Node version based on OS version
if ($OSMajor -lt 10) {
# Anything under Windows 10
$NodeVersion = Extract-Variable -Path "$BaseDir\..\..\dependencies.sh" -Key "NODE_VERSION_COMPAT"
}
else {
$NodeVersion = Extract-Variable -Path "$BaseDir\..\..\dependencies.sh" -Key "NODE_VERSION_LTS"
}
$NodeSource = "https://nodejs.org/download/release/v$NodeVersion/win-x64/node.exe"
$NodeTargetDir = "$Cache\node-v$NodeVersion-x64"
$NodeTarget = "$NodeTargetDir\node.exe"
## Just print the path and exit
if ($Args.length -eq 1 -and $Args[0] -eq "Get-Path") {
Write-Output "$NodeTargetDir"
exit 0
}
## Just download node and exit
if ($Args.length -eq 1 -and $Args[0] -eq "Download-Node") {
Download-Node
exit 0
}
## Download node
Download-Node
## Set PATH so that recursive calls find it
$Env:PATH = "$NodeTargetDir;$ENV:Path"
## Invoke Node with all command-line arguments
$ErrorActionPreference = "Continue"
& "$NodeTarget" @Args
exit $LastExitCode
+118
View File
@@ -0,0 +1,118 @@
#!/bin/sh
# bootstrap/python
#
# Python-finding script for all `sh` environments, including Linux, MSYS2,
# Git for Windows, and GitHub Desktop. Invokable from CLI or automation.
#
# If a python.exe installed by `python_.ps1` is present, it will be used.
# Otherwise, this script requires a system `python3` and `pip` to be provided,
# and will create a standard virtualenv in which to install `requirements.txt`.
set -e
# Convenience variables
Bootstrap="$(dirname "$0")"
Sdk="$(dirname "$Bootstrap")"
Cache="$Bootstrap/.cache"
if [ "$TG_BOOTSTRAP_CACHE" ]; then
Cache="$TG_BOOTSTRAP_CACHE"
fi
OldPWD="$PWD"
cd "$Bootstrap/../.."
. ./dependencies.sh # sets PYTHON_VERSION
cd "$OldPWD"
PythonVersion="$PYTHON_VERSION"
PythonDir="$Cache/python-$PythonVersion"
PythonExe="$PythonDir/python.exe"
Log="$Cache/last-command.log"
# If a portable Python for Windows is not present, search on $PATH.
if [ "$(uname)" = "Linux" ] || [ ! -f "$PythonExe" ]; then
# Strip the "App Execution Aliases" from $PATH. Even if the user installed
# Python using the Windows Store on purpose, these aliases always generate
# "Permission denied" errors when sh.exe tries to invoke them.
PATH=$(echo "$PATH" | tr ":" "\n" | grep -v "AppData/Local/Microsoft/WindowsApps" | tr "\n" ":")
# Try to find a Python executable.
if command -v python3 >/dev/null 2>&1; then
PythonExe=python3
elif command -v python >/dev/null 2>&1; then
PythonExe=python
elif command -v py >/dev/null 2>&1; then
PythonExe="py -3"
else
echo
if command -v apt-get >/dev/null 2>&1; then
echo "Please install Python using your system's package manager:"
echo " sudo apt-get install python3 python3-pip"
elif [ "$(uname -o)" = "Msys" ]; then
echo "Please run tools/bootstrap/python.bat instead of tools/bootstrap/python once to"
echo "install Python automatically, or install it from https://www.python.org/downloads/"
# TODO: give MSYS pacman advice?
elif command -v pacman >/dev/null 2>&1; then
echo "Please install Python using your system's package manager:"
echo " sudo pacman -S python python-pip"
else
echo "Please install Python from https://www.python.org/downloads/ or using your system's package manager."
fi
echo
exit 1
fi
# Create a venv and activate it
PythonDir="$Cache/venv"
if [ ! -d "$PythonDir" ]; then
echo "Creating virtualenv..."
"$PythonExe" -m venv "$PythonDir"
fi
if [ -f "$PythonDir/bin/python" ]; then
PythonExe="$PythonDir/bin/python"
elif [ -f "$PythonDir/scripts/python3.exe" ]; then
PythonExe="$PythonDir/scripts/python3.exe";
else
echo "bootstrap/python failed to find the python executable inside its virtualenv"
exit 1
fi
fi
# Use pip to install our requirements
if [ ! -f "$PythonDir/requirements.txt" ] || [ "$(b2sum < "$Sdk/requirements.txt")" != "$(b2sum < "$PythonDir/requirements.txt")" ]; then
echo "Updating dependencies..."
"$PythonExe" -m pip install -U wheel
"$PythonExe" -m pip install -U pip -r "$Sdk/requirements.txt"
cp "$Sdk/requirements.txt" "$PythonDir/requirements.txt"
echo "---"
fi
# Verify version and deduce the path separator
PythonMajor=${PythonVersion%%.*}
PythonMinor=${PythonVersion#*.}
PythonMinor=${PythonMinor%.*}
PATHSEP=$("$PythonExe" - "$PythonMajor" "$PythonMinor" <<'EOF'
import sys, os
if sys.version_info.major != int(sys.argv[1]) or sys.version_info.minor < int(sys.argv[2]):
print("Error: Python ", sys.argv[1], ".", sys.argv[2], " or later is required, but you have:\n", sys.version, sep="", file=sys.stderr)
exit(1)
print(os.pathsep)
EOF
)
# Cheap shell function if tee.exe is not available
if ! command -v tee >/dev/null 2>&1; then
tee() {
# Fudge: assume $1 is always "-a"
while read -r line; do
echo "$line" >> "$2"
echo "$line"
done
}
fi
# Invoke python with all command-line arguments
export PYTHONPATH="$Sdk$PATHSEP${PYTHONPATH:-}"
mkdir -p "$Cache"
printf '%s\n' "$PythonExe" "$@" > "$Log"
printf -- '---\n' >> "$Log"
exec 4>&1
exitstatus=$({ { set +e; "$PythonExe" -u "$@" 2>&1 3>&-; printf %s $? >&3; } 4>&- | tee -a "$Log" 1>&4; } 3>&1)
exec 4>&-
exit "$exitstatus"
+2
View File
@@ -0,0 +1,2 @@
@echo off
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\python_.ps1" %*
+108
View File
@@ -0,0 +1,108 @@
# bootstrap/python_.ps1
#
# Python bootstrapping script for Windows.
#
# Automatically downloads a portable edition of a pinned Python version to
# a cache directory, installs Pip, installs `requirements.txt`, and then invokes
# Python.
#
# The underscore in the name is so that typing `bootstrap/python` into
# PowerShell finds the `.bat` file first, which ensures this script executes
# regardless of ExecutionPolicy.
$host.ui.RawUI.WindowTitle = "starting :: python $args"
$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Add-Type -AssemblyName System.IO.Compression.FileSystem
function ExtractVersion {
param([string] $Path, [string] $Key)
foreach ($Line in Get-Content $Path) {
if ($Line.StartsWith("export $Key=")) {
return $Line.Substring("export $Key=".Length)
}
}
throw "Couldn't find value for $Key in $Path"
}
# Convenience variables
$Bootstrap = Split-Path $script:MyInvocation.MyCommand.Path
$Tools = Split-Path $Bootstrap
$Cache = "$Bootstrap/.cache"
if ($Env:TG_BOOTSTRAP_CACHE) {
$Cache = $Env:TG_BOOTSTRAP_CACHE
}
$PythonVersion = ExtractVersion -Path "$Bootstrap/../../dependencies.sh" -Key "PYTHON_VERSION"
$PythonDir = "$Cache/python-$PythonVersion"
$PythonExe = "$PythonDir/python.exe"
$Log = "$Cache/last-command.log"
# Download and unzip a portable version of Python
if (!(Test-Path $PythonExe -PathType Leaf)) {
$host.ui.RawUI.WindowTitle = "Downloading Python $PythonVersion..."
New-Item $Cache -ItemType Directory -ErrorAction silentlyContinue | Out-Null
$Archive = "$Cache/python-$PythonVersion-embed.zip"
Invoke-WebRequest `
"https://www.python.org/ftp/python/$PythonVersion/python-$PythonVersion-embed-amd64.zip" `
-OutFile $Archive `
-ErrorAction Stop
[System.IO.Compression.ZipFile]::ExtractToDirectory($Archive, $PythonDir)
$PythonVersionArray = $PythonVersion.Split(".")
$PythonVersionString = "python$($PythonVersionArray[0])$($PythonVersionArray[1])"
Write-Output "Generating PATH descriptor."
New-Item "$Cache/$PythonVersionString._pth" | Out-Null
Set-Content "$Cache/$PythonVersionString._pth" "$PythonVersionString.zip`n.`n..\..\..`nimport site`n"
# Copy a ._pth file without "import site" commented, so pip will work
Copy-Item "$Cache/$PythonVersionString._pth" $PythonDir `
-ErrorAction Stop
Remove-Item $Archive
}
# Install pip
if (!(Test-Path "$PythonDir/Scripts/pip.exe")) {
$host.ui.RawUI.WindowTitle = "Downloading Pip..."
Invoke-WebRequest "https://bootstrap.pypa.io/get-pip.py" `
-OutFile "$Cache/get-pip.py" `
-ErrorAction Stop
& $PythonExe "$Cache/get-pip.py" --no-warn-script-location
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Remove-Item "$Cache/get-pip.py" `
-ErrorAction Stop
}
# Use pip to install our requirements
if (!(Test-Path "$PythonDir/requirements.txt") -or ((Get-FileHash "$Tools/requirements.txt").hash -ne (Get-FileHash "$PythonDir/requirements.txt").hash)) {
$host.ui.RawUI.WindowTitle = "Updating dependencies..."
& $PythonExe -m pip install -U pip -r "$Tools/requirements.txt"
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Copy-Item "$Tools/requirements.txt" "$PythonDir/requirements.txt"
Write-Output "`n---`n"
}
# Invoke python with all command-line arguments
Write-Output $PythonExe | Out-File -Encoding utf8 $Log
[System.String]::Join([System.Environment]::NewLine, $args) | Out-File -Encoding utf8 -Append $Log
Write-Output "---" | Out-File -Encoding utf8 -Append $Log
$host.ui.RawUI.WindowTitle = "python $args"
$ErrorActionPreference = "Continue"
& $PythonExe -u $args 2>&1 | ForEach-Object {
$str = "$_"
if ($_.GetType() -eq [System.Management.Automation.ErrorRecord]) {
$str = $str.TrimEnd("`r`n")
}
$str | Out-File -Encoding utf8 -Append $Log
$str | Out-Host
}
exit $LastExitCode
+10
View File
@@ -0,0 +1,10 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
+54
View File
@@ -0,0 +1,54 @@
# /tg/station build script
This build script is the recommended way to compile the game, including not only the DM code but also the JavaScript and any other dependencies.
- VSCode:
a) Press `Ctrl+Shift+B` to build.
b) Press `F5` to build and run with debugger attached.
- Windows:
a) Double-click `BUILD.bat` in the repository root to build (will wait for a key press before it closes).
b) Double-click `tools/build/build.bat` to build (will exit as soon as it finishes building).
- Linux:
a) Run `tools/build/build` from the repository root.
The script will skip build steps whose inputs have not changed since the last run.
## Getting list of available targets
You can get a list of all targets that you can build by running the following command:
```
tools/build/build --help
```
## Dependencies
- On Windows, `build.bat` will automatically install a private (vendored) copy of Node.
- On Linux, install Node using your package manager or from <https://nodejs.org/en/download/>.
- On Linux, unless using tgs4 or later you will need to compile rust-g on the server and obtain a .so file, for instructions see https://github.com/tgstation/rust-g
## Why?
We used to include compiled versions of the tgui JavaScript code in the Git repository so that the project could be compiled using BYOND only. These pre-compiled files tended to have merge conflicts for no good reason. Using a build script lets us avoid this problem, while keeping builds convenient for people who are not modifying tgui.
This build script is based on [Juke Build](https://github.com/stylemistake/juke-build) - follow the link to read the documentation for the project and understand how it works and how to contribute.
## Named byond versions
If you have byond installations in non-standard locations or want to use few versions at once for testing you can fill in `dm_versions.json` file in this directory.
Entries with "default" set to true will be used BEFORE byond installations in standard locations. Otherwise you need to pass `--dm-version={name}` to the build script to choose version you want to use.
Example dm_versions.json file:
```
[
{
"name": "515.1594",
"path": "C:\\Repo\\ByondVersions\\515.1594_byond\\byond\\bin\\dm.exe",
"default": false
},
{
"name": "funny_version",
"path": "D:\\Repo\\ByondVersions\\514.1589_byond\\byond\\bin\\dm.exe",
"default": true
}
]
```
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
set -e
cd "$(dirname "$0")"
exec ../bootstrap/node build.js "$@"
+2
View File
@@ -0,0 +1,2 @@
@echo off
"%~dp0\..\bootstrap\node.bat" --experimental-modules "%~dp0\build.js" %*
+504
View File
@@ -0,0 +1,504 @@
#!/usr/bin/env node
/**
* Build script for /tg/station 13 codebase.
*
* This script uses Juke Build, read the docs here:
* https://github.com/stylemistake/juke-build
*/
import fs from 'fs';
import https from 'https';
import { env } from 'process';
import Juke from './juke/index.js';
import { DreamDaemon, DreamMaker, NamedVersionFile } from './lib/byond.js';
import { yarn } from './lib/yarn.js';
const TGS_MODE = process.env.CBT_BUILD_MODE === 'TGS';
Juke.chdir('../..', import.meta.url);
Juke.setup({ file: import.meta.url }).then((code) => {
// We're using the currently available quirk in Juke Build, which
// prevents it from exiting on Windows, to wait on errors.
if (code !== 0 && process.argv.includes('--wait-on-error')) {
Juke.logger.error('Please inspect the error and close the window.');
return;
}
if (TGS_MODE) {
// workaround for ESBuild process lingering
// Once https://github.com/privatenumber/esbuild-loader/pull/354 is merged and updated to, this can be removed
setTimeout(() => process.exit(code), 10000);
}
else {
process.exit(code);
}
});
const DME_NAME = 'vorestation';
// Stores the contents of dependencies.sh as a key value pair
// Best way I could figure to get ahold of this stuff
const dependencies = fs.readFileSync('dependencies.sh', 'utf8')
.split("\n")
.map((statement) => statement.replace("export", "").trim())
.filter((value) => !(value == "" || value.startsWith("#")))
.map((statement) => statement.split("="))
.reduce((acc, kv_pair) => {
acc[kv_pair[0]] = kv_pair[1];
return acc
}, {})
// Canonical path for the cutter exe at this moment
const getCutterPath = () => {
const ver = dependencies.CUTTER_VERSION;
const suffix = process.platform === 'win32' ? '.exe' : '';
const file_ver = ver.split('.').join('-');
return `tools/icon_cutter/cache/hypnagogic${file_ver}${suffix}`;
};
const cutter_path = getCutterPath();
export const DefineParameter = new Juke.Parameter({
type: 'string[]',
alias: 'D',
});
export const PortParameter = new Juke.Parameter({
type: 'string',
alias: 'p',
});
export const DmVersionParameter = new Juke.Parameter({
type: 'string',
});
export const CiParameter = new Juke.Parameter({ type: 'boolean' });
export const ForceRecutParameter = new Juke.Parameter({
type: 'boolean',
name: "force-recut",
});
export const SkipIconCutter = new Juke.Parameter({
type: 'boolean',
name: "skip-icon-cutter",
});
export const WarningParameter = new Juke.Parameter({
type: 'string[]',
alias: 'W',
});
export const NoWarningParameter = new Juke.Parameter({
type: 'string[]',
alias: 'I',
});
export const CutterTarget = new Juke.Target({
onlyWhen: () => {
const files = Juke.glob(cutter_path);
return files.length == 0;
},
executes: async () => {
const repo = dependencies.CUTTER_REPO;
const ver = dependencies.CUTTER_VERSION;
const suffix = process.platform === 'win32' ? '.exe' : '';
const download_from = `https://github.com/${repo}/releases/download/${ver}/hypnagogic${suffix}`
await download_file(download_from, cutter_path);
if(process.platform !== 'win32') {
await Juke.exec("chmod", [
'+x',
cutter_path,
]);
}
},
});
async function download_file(url, file) {
return new Promise((resolve, reject) => {
let file_stream = fs.createWriteStream(file);
https.get(url, function(response) {
if (response.statusCode === 302) {
file_stream.close();
download_file(response.headers.location, file)
.then((value) => resolve());
return;
}
if (response.statusCode !== 200) {
Juke.logger.error(`Failed to download ${url}: Status ${response.statusCode}`);
file_stream.close();
reject()
return
}
response.pipe(file_stream);
// after download completed close filestream
file_stream.on("finish", () => {
file_stream.close();
resolve()
});
}).on("error", (err) => {
file_stream.close();
Juke.rm(download_into);
Juke.logger.error(`Failed to download ${url}: ${err.message}`);
reject()
});
});
}
export const IconCutterTarget = new Juke.Target({
parameters: [ForceRecutParameter],
dependsOn: () => [
CutterTarget,
],
inputs: ({ get }) => {
const standard_inputs = [
`icons/**/*.png.toml`,
`icons/**/*.dmi.toml`,
`cutter_templates/**/*.toml`,
cutter_path,
]
// Alright we're gonna search out any existing toml files and convert
// them to their matching .dmi or .png file
const existing_configs = [
...Juke.glob(`icons/**/*.png.toml`),
...Juke.glob(`icons/**/*.dmi.toml`),
];
return [
...standard_inputs,
...existing_configs.map((file) => file.replace('.toml', '')),
]
},
outputs: ({ get }) => {
if(get(ForceRecutParameter))
return [];
const folders = [
...Juke.glob(`icons/**/*.png.toml`),
...Juke.glob(`icons/**/*.dmi.toml`),
];
return folders
.map((file) => file.replace(`.png.toml`, '.dmi'))
.map((file) => file.replace(`.dmi.toml`, '.png'));
},
executes: async () => {
// Don't run icon cutter, we don't use it and it's not compiled for ubuntu 20.04
// await Juke.exec(cutter_path, [
// '--dont-wait',
// '--templates',
// 'cutter_templates',
// 'icons',
// ]);
},
});
export const DmMapsIncludeTarget = new Juke.Target({
executes: async () => {
const folders = [
...Juke.glob('_maps/map_files/**/modular_pieces/*.dmm'),
...Juke.glob('_maps/RandomRuins/**/*.dmm'),
...Juke.glob('_maps/RandomZLevels/**/*.dmm'),
...Juke.glob('_maps/shuttles/**/*.dmm'),
...Juke.glob('_maps/templates/**/*.dmm'),
];
const content = folders
.map((file) => file.replace('_maps/', ''))
.map((file) => `#include "${file}"`)
.join('\n') + '\n';
fs.writeFileSync('_maps/templates.dm', content);
},
});
export const DmTarget = new Juke.Target({
parameters: [DefineParameter, DmVersionParameter, WarningParameter, NoWarningParameter, SkipIconCutter],
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
!get(SkipIconCutter) && IconCutterTarget,
],
inputs: [
'_maps/map_files/generic/**',
'maps/**/*.dm',
'code/**',
'html/**',
'icons/**',
'interface/**',
'sound/**',
`${DME_NAME}.dme`,
NamedVersionFile,
],
outputs: ({ get }) => {
if (get(DmVersionParameter)) {
return []; // Always rebuild when dm version is provided
}
return [
`${DME_NAME}.dmb`,
`${DME_NAME}.rsc`,
]
},
executes: async ({ get }) => {
await DreamMaker(`${DME_NAME}.dme`, {
defines: ['CBT', ...get(DefineParameter)],
warningsAsErrors: get(WarningParameter).includes('error'),
ignoreWarningCodes: get(NoWarningParameter),
namedDmVersion: get(DmVersionParameter),
});
},
});
export const DmTestTarget = new Juke.Target({
parameters: [DefineParameter, DmVersionParameter, WarningParameter, NoWarningParameter],
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
IconCutterTarget,
],
executes: async ({ get }) => {
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
await DreamMaker(`${DME_NAME}.test.dme`, {
defines: ['CBT', 'CIBUILDING', ...get(DefineParameter)],
warningsAsErrors: get(WarningParameter).includes('error'),
ignoreWarningCodes: get(NoWarningParameter),
namedDmVersion: get(DmVersionParameter),
});
Juke.rm('data/logs/ci', { recursive: true });
const options = {
dmbFile : `${DME_NAME}.test.dmb`,
namedDmVersion: get(DmVersionParameter),
}
await DreamDaemon(
options,
'-close', '-trusted', '-verbose',
'-params', 'log-directory=ci'
);
Juke.rm('*.test.*');
try {
const cleanRun = fs.readFileSync('data/logs/ci/clean_run.lk', 'utf-8');
console.log(cleanRun);
}
catch (err) {
Juke.logger.error('Test run was not clean, exiting');
throw new Juke.ExitCode(1);
}
},
});
export const AutowikiTarget = new Juke.Target({
parameters: [DefineParameter, DmVersionParameter, WarningParameter, NoWarningParameter],
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
IconCutterTarget,
],
outputs: [
'data/autowiki_edits.txt',
],
executes: async ({ get }) => {
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
await DreamMaker(`${DME_NAME}.test.dme`, {
defines: ['CBT', 'AUTOWIKI', ...get(DefineParameter)],
warningsAsErrors: get(WarningParameter).includes('error'),
ignoreWarningCodes: get(NoWarningParameter),
namedDmVersion: get(DmVersionParameter),
});
Juke.rm('data/autowiki_edits.txt');
Juke.rm('data/autowiki_files', { recursive: true });
Juke.rm('data/logs/ci', { recursive: true });
const options = {
dmbFile: `${DME_NAME}.test.dmb`,
namedDmVersion: get(DmVersionParameter),
}
await DreamDaemon(
options,
'-close', '-trusted', '-verbose',
'-params', 'log-directory=ci',
);
Juke.rm('*.test.*');
if (!fs.existsSync('data/autowiki_edits.txt')) {
Juke.logger.error('Autowiki did not generate an output, exiting');
throw new Juke.ExitCode(1);
}
},
})
export const YarnTarget = new Juke.Target({
parameters: [CiParameter],
inputs: [
'tgui/.yarn/+(cache|releases|plugins|sdks)/**/*',
'tgui/**/package.json',
'tgui/yarn.lock',
],
outputs: [
'tgui/.yarn/install-target',
],
executes: ({ get }) => yarn('install', get(CiParameter) && '--immutable'),
});
export const TgFontTarget = new Juke.Target({
dependsOn: [YarnTarget],
inputs: [
'tgui/.yarn/install-target',
'tgui/packages/tgfont/**/*.+(js|cjs|svg)',
'tgui/packages/tgfont/package.json',
],
outputs: [
'tgui/packages/tgfont/dist/tgfont.css',
'tgui/packages/tgfont/dist/tgfont.eot',
'tgui/packages/tgfont/dist/tgfont.woff2',
],
executes: async () => {
await yarn('tgfont:build');
fs.copyFileSync('tgui/packages/tgfont/dist/tgfont.css', 'tgui/packages/tgfont/static/tgfont.css');
fs.copyFileSync('tgui/packages/tgfont/dist/tgfont.eot', 'tgui/packages/tgfont/static/tgfont.eot');
fs.copyFileSync('tgui/packages/tgfont/dist/tgfont.woff2', 'tgui/packages/tgfont/static/tgfont.woff2');
}
});
export const TguiTarget = new Juke.Target({
dependsOn: [YarnTarget],
inputs: [
'tgui/.yarn/install-target',
'tgui/webpack.config.js',
'tgui/**/package.json',
'tgui/packages/**/*.+(js|cjs|ts|tsx|jsx|scss)',
],
outputs: [
'tgui/public/tgui.bundle.css',
'tgui/public/tgui.bundle.js',
'tgui/public/tgui-panel.bundle.css',
'tgui/public/tgui-panel.bundle.js',
'tgui/public/tgui-say.bundle.css',
'tgui/public/tgui-say.bundle.js',
],
executes: () => yarn('tgui:build'),
});
export const TguiEslintTarget = new Juke.Target({
parameters: [CiParameter],
dependsOn: [YarnTarget],
executes: ({ get }) => yarn('tgui:lint', !get(CiParameter) && '--fix'),
});
export const TguiPrettierTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: () => yarn('tgui:prettier'),
});
export const TguiSonarTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: () => yarn('tgui:sonar'),
});
export const TguiTscTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: () => yarn('tgui:tsc'),
});
export const TguiTestTarget = new Juke.Target({
parameters: [CiParameter],
dependsOn: [YarnTarget],
executes: ({ get }) => yarn(`tgui:test-${get(CiParameter) ? 'ci' : 'simple'}`),
});
export const TguiLintTarget = new Juke.Target({
dependsOn: [YarnTarget, TguiPrettierTarget, TguiEslintTarget, TguiTscTarget],
});
export const TguiDevTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: ({ args }) => yarn('tgui:dev', ...args),
});
export const TguiAnalyzeTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: () => yarn('tgui:analyze'),
});
export const TguiBenchTarget = new Juke.Target({
dependsOn: [YarnTarget],
executes: () => yarn('tgui:bench'),
});
export const TestTarget = new Juke.Target({
dependsOn: [DmTestTarget, TguiTestTarget],
});
export const LintTarget = new Juke.Target({
dependsOn: [TguiLintTarget],
});
export const BuildTarget = new Juke.Target({
dependsOn: [TguiTarget, DmTarget],
});
export const ServerTarget = new Juke.Target({
parameters: [DmVersionParameter, PortParameter],
dependsOn: [BuildTarget],
executes: async ({ get }) => {
const port = get(PortParameter) || '1337';
const options = {
dmbFile: `${DME_NAME}.dmb`,
namedDmVersion: get(DmVersionParameter),
}
await DreamDaemon(options, port, '-trusted');
},
});
export const AllTarget = new Juke.Target({
dependsOn: [TestTarget, LintTarget, BuildTarget],
});
export const TguiCleanTarget = new Juke.Target({
executes: async () => {
Juke.rm('tgui/public/.tmp', { recursive: true });
Juke.rm('tgui/public/*.map');
Juke.rm('tgui/public/*.{chunk,bundle,hot-update}.*');
Juke.rm('tgui/packages/tgfont/dist', { recursive: true });
Juke.rm('tgui/.yarn/{cache,unplugged,webpack}', { recursive: true });
Juke.rm('tgui/.yarn/build-state.yml');
Juke.rm('tgui/.yarn/install-state.gz');
Juke.rm('tgui/.yarn/install-target');
Juke.rm('tgui/.pnp.*');
},
});
export const CleanTarget = new Juke.Target({
dependsOn: [TguiCleanTarget],
executes: async () => {
Juke.rm('*.{dmb,rsc}');
Juke.rm('_maps/templates.dm');
},
});
/**
* Removes more junk at the expense of much slower initial builds.
*/
export const CleanAllTarget = new Juke.Target({
dependsOn: [CleanTarget],
executes: async () => {
Juke.logger.info('Cleaning up data/logs');
Juke.rm('data/logs', { recursive: true });
Juke.logger.info('Cleaning up global yarn cache');
await yarn('cache', 'clean', '--all');
},
});
/**
* Prepends the defines to the .dme.
* Does not clean them up, as this is intended for TGS which
* clones new copies anyway.
*/
const prependDefines = (...defines) => {
const dmeContents = fs.readFileSync(`${DME_NAME}.dme`);
const textToWrite = defines.map(define => `#define ${define}\n`);
fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`);
};
export const TgsTarget = new Juke.Target({
dependsOn: [TguiTarget],
executes: async () => {
Juke.logger.info('Prepending TGS define');
prependDefines('TGS');
},
});
export default TGS_MODE ? TgsTarget : BuildTarget;
+248
View File
@@ -0,0 +1,248 @@
// Generated by dts-bundle-generator v5.9.0
/// <reference types="node" />
import _chalk from 'chalk';
import { SpawnOptionsWithoutStdio } from 'child_process';
import EventEmitter from 'events';
/**
* Change the current working directory of the Node.js process.
*
* Second argument is a file (or directory), relative to which chdir will be
* performed. This is usually `import.meta.url`.
*/
export declare const chdir: (directory: string, relativeTo?: string | undefined) => void;
export declare const logger: {
log: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
action: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
info: (...args: unknown[]) => void;
debug: (...args: unknown[]) => void;
};
export declare type ParameterType = (string | string[] | number | number[] | boolean | boolean[]);
export declare type StringType = ("string" | "string[]" | "number" | "number[]" | "boolean" | "boolean[]");
export declare type TypeByString<T extends StringType> = (T extends "string" ? string : T extends "string[]" ? string[] : T extends "number" ? number : T extends "number[]" ? number[] : T extends "boolean" ? boolean : T extends "boolean[]" ? boolean[] : never);
export declare type ParameterConfig<T extends StringType> = {
/**
* Parameter name, as it would be used in CLI.
*/
name?: string;
/**
* Parameter type, one of:
* - `string`
* - `string[]`
* - `number`
* - `number[]`
* - `boolean`
* - `boolean[]`
*/
type: T;
/**
* Short flag for use in CLI, can only be a single character.
*/
alias?: string;
};
export interface Parameter<T extends ParameterType = ParameterType> {
type: StringType;
name?: string;
alias?: string;
__internalType?: T;
isString(): this is Parameter<string | string[]>;
isNumber(): this is Parameter<number | number[]>;
isBoolean(): this is Parameter<boolean | boolean[]>;
isArray(): this is Parameter<string[] | number[] | boolean[]>;
toKebabCase(): string | undefined;
toConstCase(): string | undefined;
toCamelCase(): string | undefined;
}
export declare type ParameterCtor = {
new <T extends StringType>(config: ParameterConfig<T>): Parameter<TypeByString<T>>;
};
export declare const Parameter: ParameterCtor;
export declare type ParameterCreator = <T extends StringType>(config: ParameterConfig<T>) => Parameter<TypeByString<T>>;
export declare const createParameter: ParameterCreator;
export declare type ExecutionContext = {
/** Get parameter value. */
get: <T extends ParameterType>(parameter: Parameter<T>) => (T extends Array<unknown> ? T : T | null);
args: string[];
};
export declare type BooleanLike = boolean | null | undefined;
export declare type WithExecutionContext<R> = (context: ExecutionContext) => R | Promise<R>;
export declare type WithOptionalExecutionContext<R> = R | WithExecutionContext<R>;
export declare type DependsOn = WithOptionalExecutionContext<(Target | BooleanLike)[]>;
export declare type ExecutesFn = WithExecutionContext<unknown>;
export declare type OnlyWhenFn = WithExecutionContext<BooleanLike>;
export declare type FileIo = WithOptionalExecutionContext<(string | BooleanLike)[]>;
export declare type TargetConfig = {
/**
* Target name. This parameter is required.
*/
name?: string;
/**
* Dependencies for this target. They will be ran before executing this
* target, and may run in parallel.
*/
dependsOn?: DependsOn;
/**
* Function that is delegated to the execution engine for building this
* target. It is normally an async function, which accepts a single
* argument - execution context (contains `get` for interacting with
* parameters).
*
* @example
* executes: async ({ get }) => {
* console.log(get(Parameter));
* },
*/
executes?: ExecutesFn;
/**
* Files that are consumed by this target.
*/
inputs?: FileIo;
/**
* Files that are produced by this target. Additionally, they are also
* touched every time target finishes executing in order to stop
* this target from re-running.
*/
outputs?: FileIo;
/**
* Parameters that are local to this task. Can be retrieved via `get`
* in the executor function.
*/
parameters?: Parameter[];
/**
* Target will run only when this function returns true. It accepts a
* single argument - execution context.
*/
onlyWhen?: OnlyWhenFn;
};
export declare class Target {
name?: string;
dependsOn: DependsOn;
executes?: ExecutesFn;
inputs: FileIo;
outputs: FileIo;
parameters: Parameter[];
onlyWhen?: OnlyWhenFn;
constructor(target: TargetConfig);
}
export declare type TargetCreator = (target: TargetConfig) => Target;
export declare const createTarget: TargetCreator;
export declare type RunnerConfig = {
targets?: Target[];
default?: Target;
parameters?: Parameter[];
singleTarget?: boolean;
};
export declare const runner: {
config: RunnerConfig;
targets: Target[];
parameters: Parameter[];
workers: Worker[];
configure(config: RunnerConfig): void;
start(): Promise<number>;
};
declare class Worker {
readonly target: Target;
readonly context: ExecutionContext;
readonly dependsOn: Target[];
dependencies: Set<Target>;
generator?: AsyncGenerator;
emitter: EventEmitter;
hasFailed: boolean;
constructor(target: Target, context: ExecutionContext, dependsOn: Target[]);
resolveDependency(target: Target): void;
rejectDependency(target: Target): void;
start(): void;
onFinish(fn: () => void): void;
onFail(fn: () => void): void;
private debugLog;
private process;
}
export declare class ExitCode extends Error {
code: number | null;
signal: string | null;
constructor(code: number | null, signal?: string | null);
}
export declare type ExecOptions = SpawnOptionsWithoutStdio & {
/**
* If `true`, this exec call will not pipe its output to stdio.
* @default false
*/
silent?: boolean;
/**
* Throw an exception on non-zero exit code.
* @default true
*/
throw?: boolean;
};
export declare type ExecReturn = {
/** Exit code of the program. */
code: number | null;
/** Signal received by the program which caused it to exit. */
signal: NodeJS.Signals | null;
/** Output collected from `stdout` */
stdout: string;
/** Output collected from `stderr` */
stderr: string;
/** A combined output collected from `stdout` and `stderr`. */
combined: string;
};
export declare const exec: (executable: string, args?: string[], options?: ExecOptions) => Promise<ExecReturn>;
/**
* Unix style pathname pattern expansion.
*
* Perform a search matching a specified pattern according to the rules of
* the `glob` npm package. Path can be either absolute or relative, and can
* contain shell-style wildcards. Broken symlinks are included in the results
* (as in the shell). Whether or not the results are sorted depends on the
* file system.
*
* @returns A possibly empty list of file paths.
*/
export declare const glob: (globPath: string) => string[];
export declare type RmOptions = {
/**
* If true, perform a recursive directory removal.
*/
recursive?: boolean;
/**
* If true, exceptions will be ignored if file or directory does not exist.
*/
force?: boolean;
};
/**
* Removes files and directories (synchronously). Supports globs.
*/
export declare const rm: (path: string, options?: RmOptions) => void;
export declare const chalk: _chalk.Chalk & _chalk.ChalkFunction & {
supportsColor: false | _chalk.ColorSupport;
Level: _chalk.Level;
Color: ("black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright") | ("bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright");
ForegroundColor: "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright";
BackgroundColor: "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright";
Modifiers: "bold" | "reset" | "dim" | "italic" | "underline" | "inverse" | "hidden" | "strikethrough" | "visible";
stderr: _chalk.Chalk & {
supportsColor: false | _chalk.ColorSupport;
};
};
export declare type SetupConfig = {
file: string;
/**
* If true, CLI will only accept a single target to run and will receive all
* passed arguments as is (not only flags).
*/
singleTarget?: boolean;
};
/**
* Configures Juke Build and starts executing targets.
*
* @param config Juke Build configuration.
* @returns Exit code of the whole runner process.
*/
export declare const setup: (config: SetupConfig) => Promise<number>;
export declare const sleep: (time: number) => Promise<unknown>;
export {};
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
{
"private": true,
"type": "commonjs"
}
+209
View File
@@ -0,0 +1,209 @@
import fs from 'fs';
import path from 'path';
import Juke from '../juke/index.js';
import { regQuery } from './winreg.js';
/**
* Cached path to DM compiler
*/
let dmPath;
const getDmPath = async (namedVersion) => {
// Use specific named version
if(namedVersion) {
return getNamedByondVersionPath(namedVersion);
}
if (dmPath) {
return dmPath;
}
dmPath = await (async () => {
// Search in array of paths
const paths = [
...((process.env.DM_EXE && process.env.DM_EXE.split(',')) || []),
...getDefaultNamedByondVersionPath(),
'C:\\Program Files\\BYOND\\bin\\dm.exe',
'C:\\Program Files (x86)\\BYOND\\bin\\dm.exe',
['reg', 'HKLM\\Software\\Dantom\\BYOND', 'installpath'],
['reg', 'HKLM\\SOFTWARE\\WOW6432Node\\Dantom\\BYOND', 'installpath'],
];
const isFile = path => {
try {
return fs.statSync(path).isFile();
}
catch (err) {
return false;
}
};
for (let path of paths) {
// Resolve a registry key
if (Array.isArray(path)) {
const [type, ...args] = path;
path = await regQuery(...args);
}
if (!path) {
continue;
}
// Check if path exists
if (isFile(path)) {
return path;
}
if (isFile(path + '/dm.exe')) {
return path + '/dm.exe';
}
if (isFile(path + '/bin/dm.exe')) {
return path + '/bin/dm.exe';
}
}
// Default paths
return (
process.platform === 'win32' && 'dm.exe'
|| 'DreamMaker'
);
})();
return dmPath;
};
const getNamedByondVersionPath = (namedVersion) =>{
const all_entries = getAllNamedDmVersions(true)
const map_entry = all_entries.find(x => x.name === namedVersion);
if(map_entry === undefined){
Juke.logger.error(`No named byond version with name "${namedVersion}" found.`);
throw new Juke.ExitCode(1);
}
return map_entry.path;
}
const getDefaultNamedByondVersionPath = () =>{
const all_entries = getAllNamedDmVersions(false)
const map_entry = all_entries.find(x => x.default == true);
if(map_entry === undefined)
return []
return [map_entry.path];
}
/** @type {[{ name, path, default }]} */
let namedDmVersionList;
export const NamedVersionFile = "tools/build/dm_versions.json"
const getAllNamedDmVersions = (throw_on_fail) => {
if(!namedDmVersionList){
if(!fs.existsSync(NamedVersionFile)){
if(throw_on_fail){
Juke.logger.error(`No byond version map file found.`);
throw new Juke.ExitCode(1);
}
namedDmVersionList = []
return namedDmVersionList;
}
try{
namedDmVersionList = JSON.parse(fs.readFileSync(NamedVersionFile));
}
catch(err){
if(throw_on_fail){
Juke.logger.error(`Failed to parse byond version map file. ${err}`);
throw new Juke.ExitCode(1);
}
namedDmVersionList = []
return namedDmVersionList;
}
}
return namedDmVersionList;
}
/**
* @param {string} dmeFile
* @param {{
* defines?: string[];
* warningsAsErrors?: boolean;
* namedDmVersion?: string;
* }} options
*/
export const DreamMaker = async (dmeFile, options = {}) => {
if(options.namedDmVersion !== null){
Juke.logger.info('Using named byond version:', options.namedDmVersion);
}
const dmPath = await getDmPath(options.namedDmVersion);
// Get project basename
const dmeBaseName = dmeFile.replace(/\.dme$/, '');
// Make sure output files are writable
const testOutputFile = (name) => {
try {
fs.closeSync(fs.openSync(name, 'r+'));
}
catch (err) {
if (err && err.code === 'ENOENT') {
return;
}
if (err && err.code === 'EBUSY') {
Juke.logger.error(`File '${name}' is locked by the DreamDaemon process.`);
Juke.logger.error(`Stop the currently running server and try again.`);
throw new Juke.ExitCode(1);
}
throw err;
}
};
const testDmVersion = async (dmPath) => {
const execReturn = await Juke.exec(dmPath, [], { silent: true, throw: false });
const version = execReturn.combined.match(`DM compiler version (\\d+)\\.(\\d+)`)
if(version == null){
Juke.logger.error(`Unexpected DreamMaker return, ensure "${dmPath}" is correct DM path.`)
throw new Juke.ExitCode(1);
}
const requiredMajorVersion = 515;
const requiredMinorVersion = 1597 // First with -D switch functionality
const major = Number(version[1]);
const minor = Number(version[2]);
if(major < requiredMajorVersion || major == requiredMajorVersion && minor < requiredMinorVersion){
Juke.logger.error(`${requiredMajorVersion}.${requiredMinorVersion} or later DM version required. Version ${major}.${minor} found at: ${dmPath}`)
throw new Juke.ExitCode(1);
}
}
await testDmVersion(dmPath);
testOutputFile(`${dmeBaseName}.dmb`);
testOutputFile(`${dmeBaseName}.rsc`);
const runWithWarningChecks = async (dmPath, args) => {
const execReturn = await Juke.exec(dmPath, args);
if(options.warningsAsErrors){
const ignoredWarningCodes = options.ignoreWarningCodes ?? [];
if(ignoredWarningCodes.length > 0 ){
Juke.logger.info('Ignored warning codes:', ignoredWarningCodes.join(', '));
}
const base_regex = '\\d+:warning( \\([a-z_]*\\))?:'
const with_ignores = `\\d+:warning( \\([a-z_]*\\))?:(?!(${ignoredWarningCodes.map(x => `.*${x}.*$`).join('|')}))`
const reg = ignoredWarningCodes.length > 0 ? new RegExp(with_ignores, "m") : new RegExp(base_regex,"m")
if (options.warningsAsErrors && execReturn.combined.match(reg)) {
Juke.logger.error(`Compile warnings treated as errors`);
throw new Juke.ExitCode(2);
}
}
return execReturn;
}
// Compile
const { defines } = options;
if (defines && defines.length > 0) {
Juke.logger.info('Using defines:', defines.join(', '));
}
await runWithWarningChecks(dmPath, [...defines.map(def => `-D${def}`), dmeFile]);
};
/**
* @param {{
* dmbFile: string;
* namedDmVersion?: string;
* }} options
*/
export const DreamDaemon = async (options, ...args) => {
const dmPath = await getDmPath(options.namedDmVersion);
const baseDir = path.dirname(dmPath);
const ddExeName = process.platform === 'win32' ? 'dreamdaemon.exe' : 'DreamDaemon';
const ddExePath = baseDir === '.' ? ddExeName : path.join(baseDir, ddExeName);
return Juke.exec(ddExePath, [options.dmbFile, ...args]);
};
+42
View File
@@ -0,0 +1,42 @@
/**
* Tools for dealing with Windows Registry bullshit.
*
* Adapted from `tgui/packages/tgui-dev-server/winreg.js`.
*
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { exec } from 'child_process';
import { promisify } from 'util';
export const regQuery = async (path, key) => {
if (process.platform !== 'win32') {
return null;
}
try {
const command = `reg query "${path}" /v ${key}`;
const { stdout } = await promisify(exec)(command);
const keyPattern = ` ${key} `;
const indexOfKey = stdout.indexOf(keyPattern);
if (indexOfKey === -1) {
return null;
}
const indexOfEol = stdout.indexOf('\r\n', indexOfKey);
if (indexOfEol === -1) {
return null;
}
const indexOfValue = stdout.indexOf(
' ',
indexOfKey + keyPattern.length);
if (indexOfValue === -1) {
return null;
}
const value = stdout.substring(indexOfValue + 4, indexOfEol);
return value;
}
catch (err) {
return null;
}
};
+16
View File
@@ -0,0 +1,16 @@
import Juke from '../juke/index.js';
let yarnPath;
export const yarn = (...args) => {
if (!yarnPath) {
yarnPath = Juke.glob('./tgui/.yarn/releases/*.cjs')[0]
.replace('/tgui/', '/');
}
return Juke.exec('node', [
yarnPath,
...args.filter((arg) => typeof arg === 'string'),
], {
cwd: './tgui',
});
};
+4
View File
@@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}
Regular → Executable
View File
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
#Project dependencies file
#Contains versions of programs that we might need to install for CI purposes - do not add anything here that is REQUIRED to run the project, this is just for CI.
export RIPGREP_VERSION=14.0.3
Regular → Executable
+4 -5
View File
@@ -25,12 +25,11 @@ elif grep -q '#include[[:space:]]\"maps\\groundbase\\groundbase.dm\"' $BASENAME.
fi
# Compile a copy of the codebase, and print errors as Github Actions annotations
DreamMaker $BASENAME.dme > compile.log
tools/build/build --ci dm -DCIBUILDING -DCITESTING
exitVal=$?
cat compile.log
if [ $exitVal -gt 0 ]; then
sed -E -n 's/^(.+?\.dm):([0-9]+):(error|warning): (.+)$/::\3 file=\1,line=\2::\4/gp' < compile.log
fi
# if [ $exitVal -gt 0 ]; then
# sed -E -n 's/^(.+?\.dm):([0-9]+):(error|warning): (.+)$/::\3 file=\1,line=\2::\4/gp'
# fi
# Compile failed on map_test
if [ $exitVal -gt 0 ] && [ $TEST_DEFINE = "MAP_TEST" ]; then
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
set -euo pipefail
source _build_dependencies.sh
source ~/.nvm/nvm.sh
nvm install $NODE_VERSION
nvm use $NODE_VERSION
npm install --location=global yarn
pip install --user PyYaml -q
pip install --user beautifulsoup4 -q
Regular → Executable
View File
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
set -euo pipefail
source dependencies.sh
if [[ -e ~/.nvm/nvm.sh ]]; then
source ~/.nvm/nvm.sh
nvm install $NODE_VERSION_COMPAT
nvm use $NODE_VERSION_COMPAT
fi
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail
source tools/ci/ci_dependencies.sh
cargo install ripgrep --features pcre2 --version $RIPGREP_VERSION

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