Merge remote-tracking branch 'upstream/master' into replicators
@@ -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
|
||||
|
||||
@@ -31,3 +31,6 @@ temp.dmi
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# CBT Things
|
||||
.cache
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error build %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error clean-all %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error server %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error dm-test %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error tg-font %*
|
||||
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-bench %*
|
||||
pause
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui tgui-lint tgui-test %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-dev %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-sonar %*
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call "%~dp0\tools\build\build.bat" --wait-on-error build %*
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ mewchild - Vox
|
||||
milu - Xenochimera
|
||||
mrsebbi - Xenochimera
|
||||
mullins111 - Black-Eyed Shadekin
|
||||
murchant - Teppi
|
||||
natje - Xenochimera
|
||||
nerdass - Protean
|
||||
newyorks - Protean
|
||||
|
||||
@@ -13,6 +13,7 @@ h0lysquirr3l - clown
|
||||
joey4298 - mime
|
||||
lm40 - clown
|
||||
losstinspess - mime
|
||||
murchant - mime
|
||||
pastelprincedan - mime
|
||||
pastelprincedan - clown
|
||||
radishfriend - clown
|
||||
|
||||
@@ -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
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 113 KiB |
|
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 you’ll 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 you’ll 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.
|
||||
|
||||
@@ -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 "${@}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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" "$@"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\python_.ps1" %*
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
exec ../bootstrap/node build.js "$@"
|
||||
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
"%~dp0\..\bootstrap\node.bat" --experimental-modules "%~dp0\build.js" %*
|
||||
@@ -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;
|
||||
@@ -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 {};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -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]);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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',
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source tools/ci/ci_dependencies.sh
|
||||
|
||||
cargo install ripgrep --features pcre2 --version $RIPGREP_VERSION
|
||||