diff --git a/.github/workflows/build_milla.yml b/.github/workflows/build_milla.yml index df1856c59df..ca269a33dda 100644 --- a/.github/workflows/build_milla.yml +++ b/.github/workflows/build_milla.yml @@ -15,10 +15,18 @@ jobs: runs-on: ubuntu-latest steps: + - id: create_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + + - run: echo "GH_TOKEN=${{ steps.create_token.outputs.token }}" >> "$GITHUB_ENV" + - run: echo "FAIL_NOTIFIED=false" >> "$GITHUB_ENV" + - name: Like the comment env: BASE_REPOSITORY: ${{ github.repository }} - GH_TOKEN: ${{ github.token }} run: | gh api \ --method POST \ @@ -43,13 +51,13 @@ jobs: with: repository: ${{ env.PR_REPO }} ref: ${{ env.PR_BRANCH }} - token: ${{ github.token }} + token: ${{ env.GH_TOKEN }} - name: Build MILLA env: BASE_BRANCH: ${{ github.event.repository.default_branch }} BASE_REPOSITORY: ${{ github.repository }} - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ env.GH_TOKEN }} run: | # Get the code. git config user.name paradisess13[bot] @@ -74,20 +82,10 @@ jobs: cp target/i686-unknown-linux-gnu/release/libmilla.so ../tools/ci/libmilla_ci.so cp target/i686-pc-windows-gnu/release/milla.dll ../milla.dll - # Check if a workflow file would be modified by the merge (permissions prevent pushes if so) - latest_workflow_commit=$(git log -n 1 --pretty=format:"%H" upstream/$BASE_BRANCH -- .github/workflows) - if ! git branch --contains $latest_workflow_commit | grep -q "$(git rev-parse --abbrev-ref HEAD)"; then - gh pr comment ${{ github.event.issue.html_url }} --body "GitHub Actions can not push to this branch as workflow files have been changed since your branch was last updated. Please update your branch past https://github.com/$BASE_REPOSITORY/commit/$latest_workflow_commit before using this command again." - echo "FAIL_NOTIFIED=true" >> "$GITHUB_ENV" - exit 1 - fi - git commit -a -m "Build MILLA" --allow-empty git push origin - name: Notify Failure if: failure() && env.FAIL_NOTIFIED != 'true' - env: - GH_TOKEN: ${{ github.token }} run: | gh pr comment ${{ github.event.issue.html_url }} -b 'Building MILLA failed, see the action run log for details: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25df92cd348..cd7f7667f24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,8 @@ jobs: python tools/ci/illegal_dme_files.py ${GITHUB_WORKSPACE} python tools/ci/define_sanity.py python tools/ci/restrict_file_types.py + python tools/ci/verify_sql_version.py + python tools/ci/no_duplicate_definitions.py python -m tools.ci.check_icon_conflicts python -m tools.ci.check_icon_dupenames python -m tools.maplint.source --github diff --git a/.github/workflows/merge_upstream_master.yml b/.github/workflows/merge_upstream_master.yml index 9806851fa7c..152bbf2ca06 100644 --- a/.github/workflows/merge_upstream_master.yml +++ b/.github/workflows/merge_upstream_master.yml @@ -25,12 +25,14 @@ jobs: - run: echo "FAIL_NOTIFIED=false" >> "$GITHUB_ENV" - name: Like the comment + env: + BASE_REPOSITORY: ${{ github.repository }} run: | gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - /repos/ParadiseSS13/Paradise/issues/comments/${{ github.event.comment.id }}/reactions \ + /repos/$BASE_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \ -f content='+1' - name: PR Data diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm index 51db92f2ef4..5648c4bd0b8 100644 --- a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm +++ b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm @@ -528,7 +528,7 @@ id = "syndbaseigniter"; pixel_y = 20 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "de" = ( /obj/machinery/light{ @@ -1064,7 +1064,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction{ dir = 4 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "fP" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, @@ -1860,7 +1860,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 10 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "kh" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ @@ -2541,7 +2541,7 @@ /obj/machinery/atmospherics/air_sensor{ autolink_id = "syndiebase_mix_sensor" }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "nI" = ( /obj/machinery/atmospherics/binary/pump{ @@ -3350,7 +3350,7 @@ autolink_id = "syndiebase_mix_out"; dir = 8 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "sF" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ @@ -7440,7 +7440,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 5 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "QB" = ( /obj/machinery/optable, @@ -7740,7 +7740,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 9 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "Su" = ( /obj/structure/table, @@ -7988,7 +7988,7 @@ autolink_id = "syndiebase_mix_in"; dir = 8 }, -/turf/simulated/floor/engine/air, +/turf/simulated/floor/engine/vacuum, /area/ruin/unpowered/syndicate_space_base/atmos) "Tp" = ( /obj/machinery/light{ diff --git a/_maps/map_files/generic/Lavaland.dmm b/_maps/map_files/generic/Lavaland.dmm index 03e670f9f51..952d3a3964a 100644 --- a/_maps/map_files/generic/Lavaland.dmm +++ b/_maps/map_files/generic/Lavaland.dmm @@ -3314,6 +3314,12 @@ icon_state = "darkfull" }, /area/mine/outpost/airlock) +"iU" = ( +/obj/structure/shuttle/engine/propulsion/burst{ + dir = 8 + }, +/turf/simulated/wall/mineral/titanium/interior, +/area/lavaland/surface/outdoors) "iW" = ( /turf/simulated/floor/plasteel{ dir = 4; @@ -5760,6 +5766,14 @@ icon_state = "darkred" }, /area/mine/laborcamp/security) +"wb" = ( +/obj/structure/girder, +/turf/simulated/floor/plating{ + nitrogen = 14; + oxygen = 8; + temperature = 500 + }, +/area/lavaland/surface/outdoors) "wd" = ( /obj/structure/stone_tile/cracked{ dir = 4 @@ -6320,6 +6334,12 @@ }, /turf/simulated/floor/catwalk, /area/mine/outpost/hallway/west) +"zm" = ( +/obj/structure/closet/walllocker/emerglocker/north, +/obj/item/storage/toolbox/mechanical, +/obj/item/flashlight/flare, +/turf/simulated/floor/mineral/titanium/blue, +/area/lavaland/surface/outdoors) "zn" = ( /obj/structure/lattice/catwalk/mining, /obj/effect/decal/cleanable/dirt, @@ -7819,6 +7839,10 @@ "IC" = ( /turf/simulated/wall, /area/mine/outpost/hallway/east) +"ID" = ( +/obj/structure/door_assembly/door_assembly_silver, +/turf/simulated/floor/mineral/titanium/blue, +/area/lavaland/surface/outdoors) "IE" = ( /obj/machinery/atmospherics/unary/vent_pump/on, /obj/structure/sign/poster/official/help_others{ @@ -7984,6 +8008,18 @@ /obj/effect/decal/cleanable/dirt, /turf/simulated/floor/plating/asteroid/basalt/lava_land_surface, /area/lavaland/surface/outdoors) +"JD" = ( +/obj/structure/grille/broken, +/obj/structure/grille/broken, +/obj/effect/mob_spawn/human/corpse/skeleton, +/obj/effect/decal/cleanable/glass, +/obj/item/shard, +/turf/simulated/floor/plating{ + nitrogen = 14; + oxygen = 8; + temperature = 500 + }, +/area/lavaland/surface/outdoors) "JF" = ( /obj/effect/spawner/window/reinforced/grilled, /obj/machinery/door/poddoor/preopen{ @@ -8443,6 +8479,14 @@ icon_state = "darkyellowcorners" }, /area/mine/outpost/hallway/west) +"MQ" = ( +/obj/item/stack/ore/iron, +/turf/simulated/floor/plating{ + nitrogen = 14; + oxygen = 8; + temperature = 500 + }, +/area/lavaland/surface/outdoors) "MS" = ( /obj/structure/stone_tile{ dir = 1 @@ -8952,6 +8996,14 @@ icon_state = "darkfull" }, /area/mine/laborcamp/security) +"PZ" = ( +/obj/structure/grille/broken, +/turf/simulated/floor/plating{ + nitrogen = 14; + oxygen = 8; + temperature = 500 + }, +/area/lavaland/surface/outdoors) "Qa" = ( /obj/structure/closet, /obj/effect/spawner/lootdrop/maintenance, @@ -9759,6 +9811,13 @@ icon_state = "tranquillite" }, /area/mine/outpost/cafeteria) +"VE" = ( +/obj/item/mounted/frame/intercom, +/obj/item/stack/cable_coil{ + amount = 1 + }, +/turf/simulated/floor/mineral/titanium/blue, +/area/lavaland/surface/outdoors) "VF" = ( /obj/machinery/atmospherics/pipe/simple/visible/cyan{ dir = 6 @@ -9926,6 +9985,9 @@ /obj/machinery/atmospherics/pipe/simple/hidden, /turf/simulated/floor/catwalk, /area/mine/laborcamp/security) +"Wy" = ( +/turf/simulated/wall/mineral/titanium, +/area/lavaland/surface/outdoors) "WD" = ( /obj/structure/lattice/catwalk/mining, /obj/structure/railing{ @@ -12443,9 +12505,9 @@ an an an an -an -an -an +KQ +Aw +Aw an an an @@ -12461,8 +12523,8 @@ an an an an -an -an +Aw +Aw an an an @@ -12478,8 +12540,8 @@ an an an an -an -an +Aw +Aw an an an @@ -12700,9 +12762,9 @@ an an an an -an -an -an +ab +ab +ab an an an @@ -12957,9 +13019,9 @@ an an an an -an -an -an +iU +ID +iU an an an @@ -13214,9 +13276,9 @@ an an an an -an -an -an +Wy +zm +PZ an an an @@ -13471,9 +13533,9 @@ an an an an -an -an -an +MQ +VE +Wy an an an @@ -13728,8 +13790,8 @@ an an an an -an -an +wb +JD an an an diff --git a/_maps/map_files/generic/centcomm.dmm b/_maps/map_files/generic/centcomm.dmm index a5a7a216fc8..95e4b026505 100644 --- a/_maps/map_files/generic/centcomm.dmm +++ b/_maps/map_files/generic/centcomm.dmm @@ -725,6 +725,7 @@ dir = 6; color = "#63009c" }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/wood, /area/wizard_station) "cO" = ( @@ -1369,6 +1370,7 @@ dir = 4; color = "#63009c" }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/wood, /area/wizard_station) "fp" = ( @@ -1432,6 +1434,7 @@ /obj/structure/chair/sofa/right{ color = "#63009c" }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/wood, /area/wizard_station) "fx" = ( @@ -5407,7 +5410,6 @@ /turf/simulated/floor/mineral/plastitanium/red, /area/shuttle/assault_pod) "sh" = ( -/obj/effect/spawner/window/reinforced, /obj/machinery/door/poddoor/shutters{ density = 0; dir = 2; @@ -5416,6 +5418,7 @@ name = "Privacy Shutters"; opacity = 0 }, +/obj/effect/spawner/window/shuttle, /turf/simulated/floor/mineral/titanium/blue, /area/shuttle/trade/sol) "sj" = ( @@ -8159,6 +8162,7 @@ name = "wizard council throne"; dir = 1 }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/carpet/red, /area/wizard_station) "CY" = ( @@ -12077,6 +12081,7 @@ /obj/structure/chair/sofa{ color = "#63009c" }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/wood, /area/wizard_station) "Qm" = ( @@ -14263,6 +14268,7 @@ /obj/structure/chair/comfy/purp{ name = "wizard council throne" }, +/obj/effect/landmark/spawner/roundstart_observer, /turf/simulated/floor/carpet/red, /area/wizard_station) "Yp" = ( diff --git a/_maps/map_files/stations/boxstation.dmm b/_maps/map_files/stations/boxstation.dmm index 4e73c461e3a..412826b1377 100644 --- a/_maps/map_files/stations/boxstation.dmm +++ b/_maps/map_files/stations/boxstation.dmm @@ -53839,7 +53839,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 5 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "eiv" = ( /obj/effect/spawner/window/reinforced/plasma/grilled, @@ -59935,7 +59935,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 4 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "gZn" = ( /obj/effect/spawner/random_spawners/cobweb_left_rare, @@ -63601,7 +63601,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 10 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "iUF" = ( /obj/structure/cable{ @@ -64813,7 +64813,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 9 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "jzT" = ( /obj/machinery/door/airlock/command{ @@ -69802,7 +69802,7 @@ /area/station/maintenance/apmaint2) "lVB" = ( /obj/machinery/atmospherics/unary/passive_vent, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "lVK" = ( /obj/machinery/door/airlock/maintenance, @@ -73550,7 +73550,7 @@ id = "toxinsigniter"; pixel_x = -20 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "nTm" = ( /obj/machinery/sparker{ @@ -74563,7 +74563,7 @@ dir = 6 }, /obj/effect/landmark/spawner/nukedisc_respawn, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "opg" = ( /obj/item/seeds/potato, @@ -76565,7 +76565,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction{ dir = 1 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "plJ" = ( /obj/structure/cable{ @@ -77521,7 +77521,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 6 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "pMv" = ( /obj/machinery/light_switch{ diff --git a/_maps/map_files/stations/cerestation.dmm b/_maps/map_files/stations/cerestation.dmm index 3a6041bfbf5..817cd6ed17c 100644 --- a/_maps/map_files/stations/cerestation.dmm +++ b/_maps/map_files/stations/cerestation.dmm @@ -21636,7 +21636,7 @@ /obj/machinery/atmospherics/unary/outlet_injector/on{ dir = 4 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "clR" = ( /obj/machinery/atmospherics/pipe/simple/visible{ @@ -21840,7 +21840,7 @@ /obj/machinery/light{ dir = 1 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "cnm" = ( /obj/structure/sign/poster/random{ @@ -62432,7 +62432,7 @@ /obj/machinery/atmospherics/unary/vent_pump/siphon/on{ dir = 4 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "mQh" = ( /obj/machinery/firealarm{ @@ -64492,7 +64492,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 6 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "nyv" = ( /obj/machinery/door/airlock/maintenance{ @@ -70056,7 +70056,7 @@ /obj/machinery/igniter{ id = "Incinerator" }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "pow" = ( /obj/machinery/economy/vending/artvend, @@ -83107,7 +83107,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 5 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "txQ" = ( /obj/machinery/atmospherics/unary/vent_pump/on, @@ -95114,7 +95114,7 @@ /area/station/maintenance/port) "xbh" = ( /obj/machinery/atmospherics/pipe/simple/heat_exchanging, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "xbm" = ( /obj/structure/disposalpipe/trunk, diff --git a/_maps/map_files/stations/deltastation.dmm b/_maps/map_files/stations/deltastation.dmm index 1f1054c9016..b9bbeef06f0 100644 --- a/_maps/map_files/stations/deltastation.dmm +++ b/_maps/map_files/stations/deltastation.dmm @@ -50104,7 +50104,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 6 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "cKw" = ( /obj/structure/table, @@ -51924,7 +51924,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 10 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "cRz" = ( /obj/structure/girder, @@ -71280,7 +71280,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 4 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "hsE" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ @@ -72445,7 +72445,7 @@ /area/station/engineering/control) "ieu" = ( /obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "iev" = ( /obj/machinery/door/window/classic/reversed{ @@ -74635,7 +74635,7 @@ /obj/machinery/atmospherics/unary/passive_vent{ dir = 1 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "jAs" = ( /obj/machinery/door/firedoor, @@ -79593,19 +79593,6 @@ icon_state = "dark" }, /area/station/security/prison/cell_block) -"mBa" = ( -/obj/effect/spawner/window/reinforced, -/obj/structure/cable{ - d1 = 4; - d2 = 8; - icon_state = "4-8" - }, -/obj/structure/cable{ - d2 = 8; - icon_state = "0-8" - }, -/turf/simulated/floor/plating, -/area/station/engineering/smes) "mBy" = ( /obj/structure/bed, /obj/item/bedsheet/green, @@ -86252,7 +86239,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 9 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "qwZ" = ( /obj/machinery/door/poddoor{ @@ -90537,7 +90524,7 @@ /obj/machinery/atmospherics/pipe/simple/heat_exchanging{ dir = 5 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "sMn" = ( /obj/structure/table/glass, @@ -95131,7 +95118,7 @@ /obj/machinery/atmospherics/air_sensor{ autolink_id = "burn_sensor" }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "vBT" = ( /obj/machinery/door/window/brigdoor{ @@ -97380,7 +97367,7 @@ autolink_id = "air_in"; dir = 1 }, -/turf/simulated/floor/engine, +/turf/simulated/floor/engine/vacuum, /area/station/science/toxins/mixing) "wSw" = ( /obj/machinery/economy/vending/cola, @@ -127352,7 +127339,7 @@ bwP fsO oNV ucB -mBa +fsO clv bQr bQr diff --git a/code/__DEFINES/particle_defines.dm b/code/__DEFINES/particle_defines.dm new file mode 100644 index 00000000000..b261cd4f98d --- /dev/null +++ b/code/__DEFINES/particle_defines.dm @@ -0,0 +1,7 @@ +// These are used to convery what kind of debris should spawn from being hit by something +#define DEBRIS_SPARKS "spark" +#define DEBRIS_WOOD "wood" +#define DEBRIS_ROCK "rock" +#define DEBRIS_GLASS "glass" +#define DEBRIS_LEAF "leaf" +#define DEBRIS_SNOW "snow" diff --git a/code/__HELPERS/iconprocs.dm b/code/__HELPERS/iconprocs.dm index 533d66d52d3..e3e948f64f1 100644 --- a/code/__HELPERS/iconprocs.dm +++ b/code/__HELPERS/iconprocs.dm @@ -445,9 +445,9 @@ GLOBAL_LIST_EMPTY(bicon_cache) * * frame - what frame of the icon_state's animation for the icon being used * * moving - whether or not to use a moving state for the given icon * * sourceonly - if TRUE, only generate the asset and send back the asset url, instead of tags that display the icon to players - * * extra_clases - string of extra css classes to use when returning the icon string + * * extra_classes - string of extra css classes to use when returning the icon string */ -/proc/icon2html(atom/thing, client/target, icon_state, dir = SOUTH, frame = 1, moving = FALSE, sourceonly = FALSE, extra_classes = null) +/proc/icon2asset(atom/thing, client/target, icon_state, dir = SOUTH, frame = 1, moving = FALSE, sourceonly = FALSE, extra_classes = null) if(!thing) return @@ -516,17 +516,15 @@ GLOBAL_LIST_EMPTY(bicon_cache) SSassets.transport.register_asset(key, rsc_ref, file_hash, icon_path) for(var/client_target in targets) SSassets.transport.send_assets(client_target, key) - if(sourceonly) - return SSassets.transport.get_asset_url(key) - return "" + return key -/// Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. -/proc/costly_icon2html(thing, target, sourceonly = FALSE) +/// Costlier version of icon2asset() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. +/proc/costly_icon2asset(thing, target, sourceonly = FALSE) if(!thing) return if(isicon(thing)) - return icon2html(thing, target) + return icon2asset(thing, target) var/icon/I = getFlatIcon(thing) - return icon2html(I, target, sourceonly = sourceonly) + return icon2asset(I, target) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 1b20fdc1a89..1f69a92d3f8 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -346,35 +346,23 @@ new_text += copytext(text, i, i+1) return new_text -//This proc strips html properly, but it's not lazy like the other procs. -//This means that it doesn't just remove < and > and call it a day. -//Also limit the size of the input, if specified. -/proc/strip_html_properly(input, max_length = MAX_MESSAGE_LEN, allow_lines = 0) +/// Strips HTML tags (and only tags) from the input. +/// The result may still include HTML entities, like ' for ' +/proc/strip_html_tags(input, max_length = MAX_MESSAGE_LEN, allow_lines = 0) if(!input) - return - var/opentag = 1 //These store the position of < and > respectively. - var/closetag = 1 - while(1) - opentag = findtext(input, "<") - closetag = findtext(input, ">") - if(closetag && opentag) - if(closetag < opentag) - input = copytext(input, (closetag + 1)) - else - input = copytext(input, 1, opentag) + copytext(input, (closetag + 1)) - else if(closetag || opentag) - if(opentag) - input = copytext(input, 1, opentag) - else - input = copytext(input, (closetag + 1)) - else - break + return "" + var/static/regex/tags = regex("<\[^>]*>", "g") + if(!tags) + tags = regex("<\[^>]*>", "g") + input = tags.Replace(input, "") if(max_length) input = copytext_char(input, 1, max_length) - return sanitize(input, allow_lines ? list("\t" = " ") : list("\n" = " ", "\t" = " ")) + if(allow_lines) + return sanitize_simple(input, list("\t" = " ")) + return sanitize_simple(input, list("\n" = " ", "\t" = " ")) -/proc/trim_strip_html_properly(input, max_length = MAX_MESSAGE_LEN, allow_lines = 0) - return trim(strip_html_properly(input, max_length, allow_lines)) +/proc/trim_strip_html_tags(input, max_length = MAX_MESSAGE_LEN, allow_lines = 0) + return trim(strip_html_tags(input, max_length, allow_lines)) //Used in preferences' SetFlavorText and human's set_flavor verb //Previews a string of len or less length @@ -742,11 +730,6 @@ return null -// Removes HTML tags, preserving text -/proc/strip_html_tags(the_text) - var/static/regex/html_replacer = regex("<\[^>]*>", "g") - return html_replacer.Replace(the_text, "") - /proc/starts_with_vowel(text) var/start_char = copytext(text, 1, 2) switch(lowertext(start_char)) diff --git a/code/_globalvars/mapping_vars.dm b/code/_globalvars/mapping_vars.dm index 1aa65439ad0..cff1c4251b6 100644 --- a/code/_globalvars/mapping_vars.dm +++ b/code/_globalvars/mapping_vars.dm @@ -38,6 +38,7 @@ GLOBAL_LIST_EMPTY(nukedisc_respawn) GLOBAL_LIST_EMPTY(ninjastart) GLOBAL_LIST_EMPTY(carplist) //list of all carp-spawn landmarks GLOBAL_LIST_EMPTY(syndicateofficer) +GLOBAL_LIST_EMPTY(roundstart_observer_start) //List of preloaded templates GLOBAL_LIST_EMPTY(map_templates) diff --git a/code/controllers/subsystem/SSstatpanel.dm b/code/controllers/subsystem/SSstatpanel.dm index e584162e566..08840da9541 100644 --- a/code/controllers/subsystem/SSstatpanel.dm +++ b/code/controllers/subsystem/SSstatpanel.dm @@ -112,11 +112,10 @@ SUBSYSTEM_DEF(statpanels) /// Set the atoms we're meant to display var/datum/object_window_info/obj_window = istype(target.obj_window) ? target.obj_window : new(target) obj_window.atoms_to_show = atoms_to_display - START_PROCESSING(SSobj_tab_items, obj_window) - refresh_client_obj_view(target) + refresh_client_obj_view(target, obj_window.min_index, obj_window.max_index) -/datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh) - var/list/turf_items = return_object_images(refresh) +/datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh, min_index = 0, max_index = 30) + var/list/turf_items = return_object_images(refresh, min_index, max_index) if(!length(turf_items) || !refresh.mob?.listed_turf) return refresh.stat_panel.send_message("update_listedturf", turf_items) @@ -125,7 +124,7 @@ SUBSYSTEM_DEF(statpanels) /// Returns all our ready object tab images /// Returns a list in the form list(list(object_name, object_ref, loaded_image), ...) -/datum/controller/subsystem/statpanels/proc/return_object_images(client/load_from) +/datum/controller/subsystem/statpanels/proc/return_object_images(client/load_from, min_index, max_index) // You might be inclined to think that this is a waste of cpu time, since we // A: Double iterate over atoms in the build case, or // B: Generate these lists over and over in the refresh case @@ -137,10 +136,18 @@ SUBSYSTEM_DEF(statpanels) return list() var/datum/object_window_info/obj_window = load_from.obj_window + if(!obj_window) + return list() var/list/already_seen = obj_window.atoms_to_images var/list/to_make = obj_window.atoms_to_imagify var/list/turf_items = list() + var/i = 0 for(var/atom/turf_item as anything in obj_window.atoms_to_show) + // Limit what we send to the client's rendered section. + i++ + if(i <= min_index || i > max_index) + continue + // First, we fill up the list of refs to display // If we already have one, just use that var/existing_image = already_seen[turf_item] @@ -148,12 +155,17 @@ SUBSYSTEM_DEF(statpanels) continue // We already have it. Success! if(existing_image) - turf_items[++turf_items.len] = list("[turf_item.name]", turf_item.UID(), existing_image) + turf_items["[i]"] = list("[turf_item.name]", turf_item.UID(), SSassets.transport.get_asset_url(existing_image), existing_image) continue // Now, we're gonna queue image generation out of those refs to_make += turf_item already_seen[turf_item] = OBJ_IMAGE_LOADING obj_window.RegisterSignal(turf_item, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/datum/object_window_info, viewing_atom_deleted), override = TRUE) // we reset cache if anything in it gets deleted + turf_items["total"] = i + obj_window.min_index = min_index + obj_window.max_index = max_index + if(length(to_make)) + START_PROCESSING(SSobj_tab_items, obj_window) return turf_items #undef OBJ_IMAGE_LOADING @@ -219,6 +231,10 @@ SUBSYSTEM_DEF(statpanels) var/client/parent /// Are we currently tracking a turf? var/actively_tracking = FALSE + /// The minimum index currently sent to the client. + var/min_index = 0 + /// The maximum index currently sent to the client. + var/max_index = 30 /datum/object_window_info/New(client/parent) . = ..() @@ -245,9 +261,9 @@ SUBSYSTEM_DEF(statpanels) var/generated_string if(ismob(thing) || length(thing.overlays) > 2) - generated_string = costly_icon2html(thing, parent, sourceonly=TRUE) + generated_string = costly_icon2asset(thing, parent) else - generated_string = icon2html(thing, parent, sourceonly=TRUE) + generated_string = icon2asset(thing, parent) newly_seen[thing] = generated_string if(TICK_CHECK) @@ -257,7 +273,7 @@ SUBSYSTEM_DEF(statpanels) // If we've not cut yet, do it now if(index) to_make.Cut(1, index + 1) - SSstatpanels.refresh_client_obj_view(parent) + SSstatpanels.refresh_client_obj_view(parent, min_index, max_index) if(!length(to_make)) return PROCESS_KILL @@ -302,6 +318,9 @@ SUBSYSTEM_DEF(statpanels) if(listed_turf) client.stat_panel.send_message("create_listedturf", listed_turf.name) client.obj_window.start_turf_tracking() + client.obj_window.min_index = 0 + client.obj_window.max_index = 30 + SSstatpanels.set_turf_examine_tab(client, src) else client.stat_panel.send_message("remove_listedturf") client.obj_window.stop_turf_tracking() diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm index c08699c48ee..e641ed030c4 100644 --- a/code/controllers/subsystem/SSticker.dm +++ b/code/controllers/subsystem/SSticker.dm @@ -852,17 +852,17 @@ SUBSYSTEM_DEF(ticker) if(length(SSticker.mode.blob_overminds)) switch(outcome) if(ROUND_END_NUCLEAR) - SSblackbox.record_feedback("tally", "Biohazard nuclear victories", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob nuclear victories", 1, "Blob") if(ROUND_END_CREW_TRANSFER) - SSblackbox.record_feedback("tally", "Biohazard survives to normal round end", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob survives to normal round end", 1, "Blob") if(ROUND_END_FORCED) - SSblackbox.record_feedback("tally", "Biohazard survives to admin round end", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob survives to admin round end", 1, "Blob") else switch(outcome) if(ROUND_END_NUCLEAR) - SSblackbox.record_feedback("tally", "Biohazard dies station nuked", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob dies station nuked", 1, "Blob") if(ROUND_END_CREW_TRANSFER) - SSblackbox.record_feedback("tally", "Biohazard dies normal end", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob dies normal end", 1, "Blob") if(ROUND_END_FORCED) - SSblackbox.record_feedback("tally", "Biohazard dies admin round end", 1, "Blob") + SSblackbox.record_feedback("tally", "Blob dies admin round end", 1, "Blob") diff --git a/code/controllers/subsystem/tickets/SStickets.dm b/code/controllers/subsystem/tickets/SStickets.dm index 7cf79bae006..2949d9b6bfe 100644 --- a/code/controllers/subsystem/tickets/SStickets.dm +++ b/code/controllers/subsystem/tickets/SStickets.dm @@ -738,7 +738,7 @@ UI STUFF for(var/datum/ticket_response/TR in T.ticket_responses) var/list/this_response = list() this_response["ckey"] = TR.response_user - this_response["text"] = strip_html_tags(TR.response_text) // Dont want to save HTML tags in the thing + this_response["text"] = html_decode(strip_html_tags(TR.response_text)) // Dont want to save HTML stuff to the DB this_response["time"] = TR.response_time raw_responses += list(this_response) diff --git a/code/datums/components/debris.dm b/code/datums/components/debris.dm new file mode 100644 index 00000000000..fd1f058fa79 --- /dev/null +++ b/code/datums/components/debris.dm @@ -0,0 +1,93 @@ +/* + * In this file you can find the particle component for bullet hits + * Originally from https://github.com/tgstation/TerraGov-Marine-Corps/pull/12752 + */ + +/particles/debris + icon = 'icons/effects/particles/generic_particles.dmi' + width = 500 + height = 500 + count = 10 + spawning = 10 + lifespan = 0.5 SECONDS + fade = 0.3 SECONDS + drift = generator("circle", 0, 7) + scale = 0.3 + velocity = list(50, 0) + friction = generator("num", 0.1, 0.15) + spin = generator("num", -20, 20) + +/particles/impact_smoke + icon = 'icons/effects/effects.dmi' + icon_state = "smoke" + width = 500 + height = 500 + count = 20 + spawning = 20 + lifespan = 0.8 SECONDS + fade = 10 SECONDS + grow = 0.1 + scale = 0.2 + spin = generator("num", -20, 20) + velocity = list(50, 0) + friction = generator("num", 0.1, 0.5) + +/datum/component/debris + /// Icon state of debris when impacted by a projectile + var/debris + /// Velocity of debris particles + var/debris_velocity = -15 + /// Amount of debris particles + var/debris_amount = 8 + /// Scale of particle debris + var/debris_scale = 0.7 + +/datum/component/debris/Initialize(_debris_icon_state, _debris_velocity = -15, _debris_amount = 8, _debris_scale = 0.7) + . = ..() + debris = _debris_icon_state + debris_velocity = _debris_velocity + debris_amount = _debris_amount + debris_scale = _debris_scale + RegisterSignal(parent, COMSIG_ATOM_BULLET_ACT, PROC_REF(register_for_impact)) + +/datum/component/debris/Destroy(force) + . = ..() + if(parent) + UnregisterSignal(parent, COMSIG_ATOM_BULLET_ACT) + +/datum/component/debris/proc/register_for_impact(datum/source, obj/item/projectile/proj) + SIGNAL_HANDLER // COMSIG_ATOM_BULLET_ACT + INVOKE_ASYNC(src, PROC_REF(on_impact), proj) + +/datum/component/debris/proc/on_impact(obj/item/projectile/P) + var/angle = !isnull(P.Angle) ? P.Angle : round(get_angle(P.starting, parent), 1) + var/x_component = sin(angle) * debris_velocity + var/y_component = cos(angle) * debris_velocity + var/x_component_smoke = sin(angle) * -15 + var/y_component_smoke = cos(angle) * -15 + + var/obj/effect/abstract/particle_holder/debris_visuals + var/obj/effect/abstract/particle_holder/smoke_visuals + var/position_offset = rand(-6, 6) + + smoke_visuals = new(parent, /particles/impact_smoke) + smoke_visuals.particles.position = list(position_offset, position_offset) + smoke_visuals.particles.velocity = list(x_component_smoke, y_component_smoke) + + if(debris && P.damage_type == BRUTE) + debris_visuals = new(parent, /particles/debris) + debris_visuals.particles.position = generator("circle", position_offset, position_offset) + debris_visuals.particles.velocity = list(x_component, y_component) + debris_visuals.layer = ABOVE_OBJ_LAYER + 0.02 + debris_visuals.particles.icon_state = debris + debris_visuals.particles.count = debris_amount + debris_visuals.particles.spawning = debris_amount + debris_visuals.particles.scale = debris_scale + smoke_visuals.layer = ABOVE_OBJ_LAYER + 0.01 + + addtimer(CALLBACK(src, PROC_REF(remove_ping), src, smoke_visuals, debris_visuals), 0.5 SECONDS) + +/datum/component/debris/proc/remove_ping(hit, obj/effect/abstract/particle_holder/smoke_visuals, obj/effect/abstract/particle_holder/debris_visuals) + QDEL_NULL(smoke_visuals) + if(debris_visuals) + QDEL_NULL(debris_visuals) diff --git a/code/datums/discord/discord_manager.dm b/code/datums/discord/discord_manager.dm index ef4aec4ae2b..006922315c1 100644 --- a/code/datums/discord/discord_manager.dm +++ b/code/datums/discord/discord_manager.dm @@ -84,7 +84,7 @@ GLOBAL_DATUM_INIT(discord_manager, /datum/discord_manager, new()) else alerttext = "| **NO MENTORS ONLINE**" - var/message = "[content] [alerttext][add_ping ? handle_mentor_ping() : ""]" + var/message = "[html_decode(strip_html_tags(content))] [alerttext][add_ping ? handle_mentor_ping() : ""]" var/datum/discord_webhook_payload/dwp = new() dwp.webhook_content = "**\[[GLOB.configuration.system.instance_id]]** [message]" diff --git a/code/datums/diseases/pierrot_throat.dm b/code/datums/diseases/pierrot_throat.dm index 054e93868ba..89dd88ee35f 100644 --- a/code/datums/diseases/pierrot_throat.dm +++ b/code/datums/diseases/pierrot_throat.dm @@ -42,6 +42,9 @@ // Semi-permanent clown mask while in last stage of infection if(locate(/obj/item/clothing/mask/gas/clown_hat) in H) return + if(!istype(H)) // Xenos don't have masks. They can still feel silly though + return + if(!H.has_organ_for_slot(SLOT_HUD_WEAR_MASK) || !H.canUnEquip(H.get_item_by_slot(SLOT_HUD_WEAR_MASK))) return diff --git a/code/datums/diseases/wizarditis.dm b/code/datums/diseases/wizarditis.dm index ab0d665b5a6..2bbb33bf4d4 100644 --- a/code/datums/diseases/wizarditis.dm +++ b/code/datums/diseases/wizarditis.dm @@ -58,6 +58,8 @@ /datum/disease/wizarditis/proc/spawn_wizard_clothes() var/mob/living/carbon/human/H = affected_mob + if(!istype(H)) + return // Woe, wizard xeno upon ye // Which slots can we replace? var/list/eligible_slot_IDs = new diff --git a/code/datums/elements/strippable.dm b/code/datums/elements/strippable.dm index 668f723fe60..25d31342532 100644 --- a/code/datums/elements/strippable.dm +++ b/code/datums/elements/strippable.dm @@ -277,6 +277,9 @@ /// A lazy list of user mobs to a list of strip menu keys that they're interacting with var/list/interactions + /// Associated list of "[icon][icon_state]" = base64 representation of icon. Used for PERFORMANCE. + var/static/list/base64_cache = list() + /datum/strip_menu/New(atom/movable/owner, datum/element/strippable/strippable) . = ..() src.owner = owner @@ -341,7 +344,10 @@ LAZYINITLIST(result) - result["icon"] = icon2base64(icon(item.icon, item.icon_state, dir = SOUTH, frame = 1, moving = FALSE)) + var/key = "[item.icon],[item.icon_state]" + if(!(key in base64_cache)) + base64_cache[key] = icon2base64(icon(item.icon, item.icon_state, dir = SOUTH, frame = 1, moving = FALSE)) + result["icon"] = base64_cache[key] result["name"] = item.name var/real_alts = item_data.get_alternate_actions(owner, user) diff --git a/code/datums/spell_handler/vampire.dm b/code/datums/spell_handler/vampire.dm index d22a4576bee..837976a6130 100644 --- a/code/datums/spell_handler/vampire.dm +++ b/code/datums/spell_handler/vampire.dm @@ -36,8 +36,8 @@ return var/datum/antagonist/vampire/vampire = user.mind.has_antag_datum(/datum/antagonist/vampire) - - vampire.bloodusable -= calculate_blood_cost(vampire) + var/blood_cost = calculate_blood_cost(vampire) + vampire.subtract_usable_blood(blood_cost) /datum/spell_handler/vampire/proc/calculate_blood_cost(datum/antagonist/vampire/vampire) var/blood_cost_modifier = 1 + vampire.nullified / 100 diff --git a/code/datums/uplink_items/uplink_general.dm b/code/datums/uplink_items/uplink_general.dm index a58bbf324dd..9f8b899c38c 100644 --- a/code/datums/uplink_items/uplink_general.dm +++ b/code/datums/uplink_items/uplink_general.dm @@ -388,7 +388,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) /datum/uplink_item/explosives/plastic_explosives name = "Composition C-4" - desc = "C-4 is plastic explosive of the common variety Composition C. Reliably destroys the object it's placed on, assuming it isn't bomb resistant. Does not stick to crewmembers. Will only destroy station floors if placed directly on it. It has a modifiable timer with a minimum setting of 10 seconds." + desc = "C-4 is plastic explosive of the common variety Composition C. Reliably destroys the object it's placed on, assuming it isn't bomb resistant. Remarkably good for disposing bodies, or tired crewmates. Will only destroy station floors if placed directly on it. It has a modifiable timer with a minimum setting of 10 seconds." reference = "C4" item = /obj/item/grenade/plastic/c4 cost = 5 diff --git a/code/datums/uplink_items/uplink_nuclear.dm b/code/datums/uplink_items/uplink_nuclear.dm index bb9fd71fe62..88c88ff0923 100644 --- a/code/datums/uplink_items/uplink_nuclear.dm +++ b/code/datums/uplink_items/uplink_nuclear.dm @@ -663,6 +663,7 @@ /datum/uplink_item/stealthy_tools/chameleon/nuke reference = "NCHAM" + item = /obj/item/storage/box/syndie_kit/chameleon/nuke cost = 30 excludefrom = list(UPLINK_TYPE_TRAITOR, UPLINK_TYPE_SIT) diff --git a/code/datums/uplink_items/uplink_traitor.dm b/code/datums/uplink_items/uplink_traitor.dm index 99ce40fe217..3a601fa24f9 100644 --- a/code/datums/uplink_items/uplink_traitor.dm +++ b/code/datums/uplink_items/uplink_traitor.dm @@ -468,7 +468,16 @@ While the mask is active, your voice will sound unrecognizable to others." reference = "CVMM" item = /obj/item/clothing/mask/gas/voice_modulator/chameleon - cost = 8 + cost = 5 + excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST) + +/datum/uplink_item/stealthy_tools/voice_changer + name = "Chameleon Voice Changer Mask" + desc = "A syndicate gas mask equipped with chameleon technology and a voice changer for disguising your voice. \ + Use it to impersonate or obfuscate your identity when talking and make nobody the wiser!" + reference = "CVCM" + item = /obj/item/clothing/mask/chameleon/voice_change + cost = 10 excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST) /datum/uplink_item/stealthy_tools/silicon_cham_suit @@ -692,10 +701,10 @@ /datum/uplink_item/stealthy_tools/chameleon name = "Chameleon Kit" desc = "A set of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more! \ - Due to budget cuts, the shoes don't provide protection against slipping. The set comes with a complementary chameleon stamp." + Due to budget cuts, the shoes don't provide protection against slipping." reference = "CHAM" item = /obj/item/storage/box/syndie_kit/chameleon - cost = 20 + cost = 10 excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST) /datum/uplink_item/stealthy_tools/syndigaloshes diff --git a/code/defines/procs/announcer_datum.dm b/code/defines/procs/announcer_datum.dm index 055430b2736..13ffbda1304 100644 --- a/code/defines/procs/announcer_datum.dm +++ b/code/defines/procs/announcer_datum.dm @@ -45,7 +45,7 @@ GLOBAL_DATUM_INIT(major_announcement, /datum/announcer, new(config_type = /datum var/message_sound2 = new_sound2 ? sound(new_sound2) : null if(!msg_sanitized) - message = trim_strip_html_properly(message, allow_lines = TRUE) + message = html_encode(message) var/datum/language/message_language = GLOB.all_languages[msg_language ? msg_language : language] diff --git a/code/game/area/areas/ruins/lavaland_areas.dm b/code/game/area/areas/ruins/lavaland_areas.dm index 0fc875a8c4a..6bf480d9d4e 100644 --- a/code/game/area/areas/ruins/lavaland_areas.dm +++ b/code/game/area/areas/ruins/lavaland_areas.dm @@ -18,7 +18,7 @@ icon_state = "yellow" /area/ruin/powered/golem_ship - name = "Free Golem Landing" + name = "Free Golem Ship" icon_state = "yellow" /area/ruin/powered/greed diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm index 8ec860cbf36..60425f96e47 100644 --- a/code/game/gamemodes/cult/runes.dm +++ b/code/game/gamemodes/cult/runes.dm @@ -356,6 +356,8 @@ structure_check() searches for nearby cultist structures required for the invoca H.uncuff() H.Silence(6 SECONDS) //Prevent "HALP MAINT CULT" before you realise you're converted + if(H.reagents?.has_reagent("holywater")) + H.reagents.del_reagent("holywater") // Also prevent fill stomach with holy water and "forgot" about it after converting var/obj/item/melee/cultblade/dagger/D = new(get_turf(src)) if(H.equip_to_slot_if_possible(D, SLOT_HUD_IN_BACKPACK, FALSE, TRUE)) diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm index 6ed2761e5c4..c988d1709eb 100644 --- a/code/game/gamemodes/nuclear/pinpointer.dm +++ b/code/game/gamemodes/nuclear/pinpointer.dm @@ -96,15 +96,15 @@ /obj/item/pinpointer/proc/scandisk() if(!the_disk) - the_disk = locate() + the_disk = locate() in GLOB.poi_list /obj/item/pinpointer/proc/scanbomb() if(!syndicate) if(!the_bomb) - the_bomb = locate() + the_bomb = locate() in GLOB.poi_list else if(!the_s_bomb) - the_s_bomb = locate() + the_s_bomb = locate() in GLOB.poi_list /obj/item/pinpointer/proc/point_at_target(atom/target) if(!target) diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm index d175fcbf6ad..d56cf4ad226 100644 --- a/code/game/gamemodes/wizard/spellbook.dm +++ b/code/game/gamemodes/wizard/spellbook.dm @@ -65,8 +65,7 @@ return FALSE /datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user, obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = locate() - if(!(user in A.contents)) + if(!istype(get_area(user), /area/wizard_station)) to_chat(user, "You can only refund spells at the wizard lair.") return -1 if(!S) //This happens when the spell's source is from another spellbook, from loadouts, or adminery, this create a new template temporary spell @@ -1152,7 +1151,7 @@ /obj/item/spellbook/oneuse/random/initialize() . = ..() - var/static/banned_spells = list(/obj/item/spellbook/oneuse/mime, /obj/item/spellbook/oneuse/mime/fingergun, /obj/item/spellbook/oneuse/mime/fingergun/fake, /obj/item/spellbook/oneuse/mime/greaterwall) + var/static/list/banned_spells = typesof(/obj/item/spellbook/oneuse/mime, /obj/item/spellbook/oneuse/emp) var/real_type = pick(subtypesof(/obj/item/spellbook/oneuse) - banned_spells) new real_type(loc) qdel(src) diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 854e307fd60..4b254125135 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -255,7 +255,7 @@ Nuke_request(input, ui.user) to_chat(ui.user, "Request sent.") log_game("[key_name(ui.user)] has requested the nuclear codes from Centcomm") - GLOB.major_announcement.Announce("The codes for the on-station nuclear self-destruct have been requested by [ui.user]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested", 'sound/AI/nuke_codes.ogg') + GLOB.major_announcement.Announce("The codes for the on-station nuclear self-destruction device have been requested by [ui.user]. Confirmation or denial of this request will be sent shortly.", "Nuclear Self Destruct Codes Requested", 'sound/AI/nuke_codes.ogg') centcomm_message_cooldown = world.time + 6000 // 10 minutes setMenuState(ui.user, COMM_SCREEN_MAIN) @@ -562,7 +562,7 @@ return if(!sanitized) - reason = trim_strip_html_properly(reason, allow_lines = TRUE) + reason = trim_strip_html_tags(reason, allow_lines = TRUE) SSshuttle.requestEvac(user, reason) log_game("[key_name(user)] has called the shuttle.") diff --git a/code/game/machinery/computer/message_monitor.dm b/code/game/machinery/computer/message_monitor.dm index 5394a21bd30..0ff58f33386 100644 --- a/code/game/machinery/computer/message_monitor.dm +++ b/code/game/machinery/computer/message_monitor.dm @@ -396,8 +396,7 @@ //Select Your Name if("Sender") - customsender = input("Please enter the sender's name.") - customsender = trim_strip_html_properly(customsender) + customsender = clean_input("Please enter the sender's name.") //Select Receiver if("Recepient") @@ -416,13 +415,11 @@ //Enter custom job if("RecJob") - customjob = input("Please enter the sender's job.") - customjob = trim_strip_html_properly(customjob) + customjob = clean_input("Please enter the sender's job.") //Enter message if("Message") - custommessage = input("Please enter your message.") - custommessage = trim_strip_html_properly(custommessage) + custommessage = clean_input("Please enter your message.") //Send message if("Send") diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index 63b1325a260..040bd8b1359 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -27,6 +27,10 @@ //The list of directions to block a projectile from var/list/directional_list = list() +/obj/structure/barricade/Initialize(mapload) + . = ..() + AddComponent(/datum/component/debris, DEBRIS_WOOD, -20, 10) + /obj/structure/barricade/deconstruct(disassembled = TRUE) if(!(flags & NODECONSTRUCT)) make_debris() diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index 4dced61d7c9..80b8263d2e1 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -45,6 +45,9 @@ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters ME.attach(src) +/obj/mecha/combat/marauder/add_cell() + cell = new /obj/item/stock_parts/cell/bluespace(src) + /obj/mecha/combat/marauder/ares name = "Ares" desc = "Heavy-duty, combat exosuit, adapted from rejected early versions of the Marauder to serve as a biohazard containment exosuit. This model, albeit rare, can be found among civilian populations." @@ -85,9 +88,6 @@ force = 80 max_equip = 8 -/obj/mecha/combat/marauder/seraph/add_cell() - cell = new /obj/item/stock_parts/cell/bluespace(src) - /obj/mecha/combat/marauder/seraph/loaded/Initialize(mapload) . = ..() //Let it equip whatever is needed. var/obj/item/mecha_parts/mecha_equipment/ME diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm index 1d74ec9bb0d..cbf3f46175a 100644 --- a/code/game/objects/effects/effects.dm +++ b/code/game/objects/effects/effects.dm @@ -148,3 +148,67 @@ /obj/effect/decal/proc/on_scoop() return + +/// These effects can be added to anything to hold particles, which is useful because Byond only allows a single particle per atom +/obj/effect/abstract/particle_holder + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = ABOVE_ALL_MOB_LAYER + vis_flags = VIS_INHERIT_PLANE + invisibility = FALSE + ///typepath of the last location we're in, if it's different when moved then we need to update vis contents + var/last_attached_location_type + /// The main item we're attached to at the moment, particle holders hold particles for something + var/atom/movable/parent + /// The mob that is holding our item + var/mob/holding_parent + +/obj/effect/abstract/particle_holder/Initialize(mapload, particle_path = null) + . = ..() + if(!loc) + stack_trace("particle holder was created with no loc!") + return INITIALIZE_HINT_QDEL + parent = loc + + if(ismovable(parent)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_qdel)) + + particles = new particle_path + update_visual_contents(parent) + +/obj/effect/abstract/particle_holder/Destroy(force) + if(parent) + UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) + QDEL_NULL(particles) + parent.vis_contents -= src + return ..() + +///signal called when parent is moved +/obj/effect/abstract/particle_holder/proc/on_move(atom/movable/attached, atom/oldloc, direction) + SIGNAL_HANDLER + if(parent.loc.type != last_attached_location_type) + update_visual_contents(attached) + +///signal called when parent is deleted +/obj/effect/abstract/particle_holder/proc/on_qdel(atom/movable/attached, force) + SIGNAL_HANDLER + attached.vis_contents -= src + qdel(src)//our parent is gone and we need to be as well + +///logic proc for particle holders, aka where they move. +///subtypes of particle holders can override this for particles that should always be turf level or do special things when repositioning. +///this base subtype has some logic for items, as the loc of items becomes mobs very often hiding the particles +/obj/effect/abstract/particle_holder/proc/update_visual_contents(atom/movable/attached_to) + // Remove old + if(holding_parent && !(QDELETED(holding_parent))) + holding_parent.vis_contents -= src + + // Add new + if(isitem(attached_to) && ismob(attached_to.loc)) //special case we want to also be emitting from the mob + var/mob/particle_mob = attached_to.loc + last_attached_location_type = attached_to.loc + particle_mob.vis_contents += src + + // Readd to ourselves + attached_to.vis_contents |= src diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index a977b08c726..a87d2909654 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -235,6 +235,14 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/newplayer_start) //Without this you sp /obj/effect/landmark/spawner/nuke_code name = "nukecode" +/obj/effect/landmark/spawner/roundstart_observer + name = "Roundstart Observer" + icon_state = "spooky" + +/obj/effect/landmark/spawner/roundstart_observer/Initialize(mapload) + spawner_list = GLOB.roundstart_observer_start + return ..() + /obj/effect/landmark/Destroy() GLOB.landmarks_list -= src ..() @@ -414,7 +422,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/newplayer_start) //Without this you sp name = "Warden" icon_state = "Warden" - /obj/effect/landmark/start/set_tag() tag = "start*[name]" diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index 016f9af8e51..df9ba215193 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -149,7 +149,8 @@ /obj/item/clothing/under/chameleon = 2, /obj/item/stamp/chameleon = 2, /obj/item/clothing/shoes/chameleon/noslip = 5, - /obj/item/clothing/mask/chameleon = 2, + /obj/item/clothing/mask/chameleon = 4, + /obj/item/clothing/mask/chameleon/voice_change = 2, /obj/item/clothing/mask/gas/voice_modulator = 2, /obj/item/clothing/mask/gas/voice_modulator/chameleon = 2, /obj/item/dnascrambler = 1, diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 5a7cd55cbdc..683b0eac149 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -10,7 +10,6 @@ * Toy Nuke * Card Deck * Therapy dolls - * Toddler doll * Inflatable duck * Foam armblade * Mini Gibber @@ -438,14 +437,6 @@ item_state = "egg3" // It's the green egg in items_left/righthand item_color = "green" -/obj/item/toddler - icon_state = "toddler" - name = "toddler" - desc = "This baby looks almost real. Wait, did it just burp?" - force = 5 - w_class = WEIGHT_CLASS_BULKY - slot_flags = SLOT_FLAG_BACK - //This should really be somewhere else but I don't know where. w/e diff --git a/code/game/objects/items/weapons/cash.dm b/code/game/objects/items/weapons/cash.dm index 4881050991e..ecdd65fec53 100644 --- a/code/game/objects/items/weapons/cash.dm +++ b/code/game/objects/items/weapons/cash.dm @@ -1,8 +1,8 @@ /obj/item/stack/spacecash name = "1 Credit" desc = "Money money money." - icon = 'icons/goonstation/objects/money.dmi' - icon_state = "cashgreen" + icon = 'icons/obj/money.dmi' + icon_state = "cash" hitsound = "swing_hit" force = 1 throwforce = 1 @@ -38,18 +38,18 @@ /obj/item/stack/spacecash/update_icon_state() name = "[amount] Credit[amount > 1 ? "s" : ""]" - if(amount >= 1 && amount < 10) - icon_state = "cashgreen" - else if(amount >= 10 && amount < 50) - icon_state = "spacecash" - else if(amount >= 50 && amount < 500) - icon_state = "cashblue" + if(amount >= 1 && amount < 50) + icon_state = "cash" + else if(amount >= 50 && amount < 100) + icon_state = "cash2" + else if(amount >= 100 && amount < 500) + icon_state = "cash3" else if(amount >= 500 && amount < 1000) - icon_state = "cashindi" - else if(amount >= 1000 && amount <= 10000) - icon_state = "cashpurp" + icon_state = "cash4" + else if(amount >= 1000 && amount < 10000) + icon_state = "cash5" else - icon_state = "cashrbow" + icon_state = "cash6" /obj/item/stack/spacecash/c5 amount = 5 diff --git a/code/game/objects/items/weapons/caution.dm b/code/game/objects/items/weapons/caution.dm index 8c0a3ba13e3..6790eab4376 100644 --- a/code/game/objects/items/weapons/caution.dm +++ b/code/game/objects/items/weapons/caution.dm @@ -67,7 +67,7 @@ if(r) r.droplimb(0, DROPLIMB_SHARP) -/obj/item/stack/cyborg_mine //Malf module +/obj/item/stack/caution/proximity_sign/malf //Malf module name = "proximity mine dispenser" icon = 'icons/obj/janitor.dmi' icon_state = "caution" diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm index dd5df9c4122..2306002dc48 100644 --- a/code/game/objects/items/weapons/explosives.dm +++ b/code/game/objects/items/weapons/explosives.dm @@ -63,25 +63,25 @@ det_time = newtime to_chat(user, "Timer set for [det_time] seconds.") -/obj/item/grenade/plastic/afterattack(atom/movable/AM, mob/user, flag) +/obj/item/grenade/plastic/afterattack(mob/AM, mob/user, flag) if(!flag) return - if(iscarbon(AM)) - to_chat(user, "You can't get the [src] to stick to [AM]!") + if(ismob(AM) && AM.stat == CONSCIOUS) + to_chat(user, "You can't get the [src] to stick to [AM]! Perhaps if [AM] was asleep or dead you could attach it?") return if(isobserver(AM)) to_chat(user, "Your hand just phases through [AM]!") return to_chat(user, "You start planting [src].[isnull(nadeassembly) ? " The timer is set to [det_time]..." : ""]") - if(do_after(user, 50 * toolspeed, target = AM)) + if(do_after(user, 5 SECONDS * toolspeed, target = AM)) if(!user.unEquip(src)) return target = AM loc = null if(notify_admins) - message_admins("[ADMIN_LOOKUPFLW(user)] planted [src.name] on [target.name] at ([target.x],[target.y],[target.z] - JMP) with [det_time] second fuse", 0, 1) + message_admins("[ADMIN_LOOKUPFLW(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z] - JMP) with [det_time] second fuse", 0, 1) log_game("[key_name(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z]) with [det_time] second fuse") AddComponent(/datum/component/persistent_overlay, image_overlay, target) diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index dbc5d3f5ced..7d76006723e 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -112,7 +112,7 @@ /obj/item/storage/backpack/clown/syndie/populate_contents() new /obj/item/clothing/under/rank/civilian/clown(src) new /obj/item/clothing/shoes/magboots/clown(src) - new /obj/item/clothing/mask/chameleon(src) + new /obj/item/clothing/mask/chameleon/voice_change(src) new /obj/item/radio/headset/headset_service(src) new /obj/item/pda/clown(src) new /obj/item/storage/box/survival(src) diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm index 600550157ce..153dfffef92 100644 --- a/code/game/objects/items/weapons/storage/bags.dm +++ b/code/game/objects/items/weapons/storage/bags.dm @@ -39,7 +39,7 @@ storage_slots = 30 max_combined_w_class = 30 can_hold = list() // any - cant_hold = list(/obj/item/disk/nuclear) + cant_hold = list(/obj/item/disk/nuclear, /obj/item/grown/bananapeel/traitorpeel) /obj/item/storage/bag/trash/proc/update_weight() if(!length(contents)) @@ -213,6 +213,7 @@ /obj/item/grown, /obj/item/food/snacks/grown/ash_flora, /obj/item/food/snacks/honeycomb) + cant_hold = list(/obj/item/grown/bananapeel/traitorpeel) resistance_flags = FLAMMABLE /obj/item/storage/bag/plants/portaseeder diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm index 7788121da5d..b3a24992c50 100644 --- a/code/game/objects/items/weapons/storage/uplink_kits.dm +++ b/code/game/objects/items/weapons/storage/uplink_kits.dm @@ -144,7 +144,7 @@ /obj/item/clothing/gloves/combat, // accounted in belt + toolbox /obj/item/flashlight/emp) // 4TC -/// 168TC + Telekinesis +/// 170TC + Telekinesis /obj/item/storage/box/syndie_kit/bundle/darklord name = "Dark Lord Bundle" desc = "Turn your anger into hate and your hate into suffering with a mix of energy swords and magical powers. DO IT." @@ -155,7 +155,7 @@ /obj/item/dnainjector/telemut/darkbundle, // ?TC /obj/item/clothing/suit/hooded/chaplain_hoodie, // 0TC /obj/item/clothing/glasses/meson/engine/tray, // 0TC - /obj/item/clothing/mask/chameleon, // 8TC + /obj/item/clothing/mask/chameleon/voice_change, // 10TC /obj/item/card/id/syndicate, // 10TC /obj/item/clothing/shoes/chameleon/noslip, // 10TC /obj/item/encryptionkey/syndicate) // 10TC @@ -403,11 +403,26 @@ new /obj/item/clothing/shoes/chameleon(src) new /obj/item/clothing/glasses/chameleon(src) new /obj/item/clothing/head/chameleon(src) - new /obj/item/clothing/mask/chameleon(src) new /obj/item/storage/backpack/chameleon(src) new /obj/item/radio/headset/chameleon(src) - new /obj/item/stamp/chameleon(src) new /obj/item/pda/chameleon(src) + new /obj/item/clothing/mask/chameleon(src) + +/obj/item/storage/box/syndie_kit/chameleon/nuke + name = "operative's chameleon kit" + +/obj/item/storage/box/syndie_kit/chameleon/nuke/populate_contents() + new /obj/item/clothing/under/chameleon(src) + new /obj/item/clothing/suit/chameleon(src) + new /obj/item/clothing/gloves/chameleon(src) + new /obj/item/clothing/shoes/chameleon(src) + new /obj/item/clothing/glasses/chameleon(src) + new /obj/item/clothing/head/chameleon(src) + new /obj/item/storage/backpack/chameleon(src) + new /obj/item/radio/headset/chameleon(src) + new /obj/item/pda/chameleon(src) + new /obj/item/stamp/chameleon(src) + new /obj/item/clothing/mask/chameleon/voice_change(src) /obj/item/storage/box/syndie_kit/dart_gun name = "dart gun kit" diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 1c0bc9cd224..1c3a997a220 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -14,6 +14,10 @@ var/metalUsed = 2 //used to determine amount returned in deconstruction var/metal_type = /obj/item/stack/sheet/metal +/obj/structure/girder/Initialize(mapload) + . = ..() + AddComponent(/datum/component/debris, DEBRIS_SPARKS, -20, 10) + /obj/structure/girder/examine(mob/user) . = ..() switch(state) diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm index 788c451338a..ec197bb5c3d 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -27,6 +27,7 @@ . = ..() initial_state = icon_state air_update_turf(1) + AddComponent(/datum/component/debris, DEBRIS_SPARKS, -20, 10) /obj/structure/mineral_door/Destroy() density = FALSE @@ -211,3 +212,7 @@ resistance_flags = FLAMMABLE max_integrity = 200 rad_insulation = RAD_VERY_LIGHT_INSULATION + +/obj/structure/mineral_door/wood/Initialize() + . = ..() + AddComponent(/datum/component/debris, DEBRIS_WOOD, -20, 10) diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm index 0cb28240dea..a090a32cbcf 100644 --- a/code/game/turfs/simulated/minerals.dm +++ b/code/game/turfs/simulated/minerals.dm @@ -42,6 +42,7 @@ var/turf/T = get_step(src, dir) if(istype(T, /turf/simulated/mineral/random)) Spread(T) + AddComponent(/datum/component/debris, DEBRIS_ROCK, -20, 10, 1) /turf/simulated/mineral/proc/Spread(turf/T) T.ChangeTurf(type) diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index d4c41a5fd55..680cd80c053 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -61,6 +61,7 @@ underlay_appearance.icon_state = fixed_underlay["icon_state"] fixed_underlay = string_assoc_list(fixed_underlay) underlays += underlay_appearance + AddComponent(/datum/component/debris, DEBRIS_SPARKS, -20, 10, 1) /turf/simulated/wall/BeforeChange() for(var/obj/effect/overlay/wall_rot/WR in src) diff --git a/code/game/turfs/simulated/walls_mineral.dm b/code/game/turfs/simulated/walls_mineral.dm index c5a0cad1ea5..e667cfb2314 100644 --- a/code/game/turfs/simulated/walls_mineral.dm +++ b/code/game/turfs/simulated/walls_mineral.dm @@ -2,10 +2,10 @@ name = "mineral wall" desc = "This shouldn't exist" icon_state = "" - var/last_event = 0 - var/active = FALSE smoothing_flags = SMOOTH_BITMASK canSmoothWith = null + var/last_event = 0 + var/active = FALSE /turf/simulated/wall/mineral/shuttleRotate(rotation) return //This override is needed to properly rotate the object when on a shuttle that is rotated. diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 384e9648b34..1ee1e1ad547 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -104,12 +104,8 @@ if(handle_spam_prevention(msg, MUTE_ADMINHELP, OOC_COOLDOWN)) return - //clean the message if it's not sent by a high-rank admin - if(!check_rights(R_SERVER|R_DEBUG,0)) - msg = sanitize_simple(copytext_char(msg, 1, MAX_MESSAGE_LEN)) - if(!msg) - return - else + // Let high-rank admins use advanced pencode. + if(check_rights(R_SERVER|R_DEBUG, 0)) msg = admin_pencode_to_html(msg) var/send_span diff --git a/code/modules/antagonists/changeling/powers/summon_spiders.dm b/code/modules/antagonists/changeling/powers/summon_spiders.dm index 3ef00c46c0b..abbd67db1b4 100644 --- a/code/modules/antagonists/changeling/powers/summon_spiders.dm +++ b/code/modules/antagonists/changeling/powers/summon_spiders.dm @@ -51,6 +51,7 @@ venom_per_bite = 3 speak_chance = 0 wander = 0 + gold_core_spawnable = NO_SPAWN /// To check and gib the spider when dead, then remove only one of the counter for the changeling owner var/gibbed = FALSE diff --git a/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm b/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm index 65799fc1043..029ffa7a8cb 100644 --- a/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm +++ b/code/modules/antagonists/traitor/contractor/items/contractor_kit.dm @@ -54,7 +54,7 @@ new /obj/item/clothing/under/chameleon(src) new /obj/item/clothing/suit/chameleon(src) new /obj/item/clothing/head/chameleon(src) - new /obj/item/clothing/mask/chameleon(src) + new /obj/item/clothing/mask/chameleon/voice_change(src) new /obj/item/card/id/syndicate(src) new /obj/item/storage/fancy/cigarettes/cigpack_syndicate(src) new /obj/item/lighter/zippo(src) diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm index d0846411bff..dea34b67cad 100644 --- a/code/modules/antagonists/vampire/vamp_datum.dm +++ b/code/modules/antagonists/vampire/vamp_datum.dm @@ -228,7 +228,7 @@ RESTRICT_TYPE(/datum/antagonist/vampire) return if(bloodusable >= 10) //burn through your blood to tank the light for a little while to_chat(owner.current, "The starlight saps your strength!") - bloodusable -= 10 + subtract_usable_blood(10) vamp_burn(10) else //You're in trouble, get out of the sun NOW to_chat(owner.current, "Your body is turning to ash, get out of the light now!") @@ -278,6 +278,13 @@ RESTRICT_TYPE(/datum/antagonist/vampire) REMOVE_TRAIT(owner.current, TRAIT_GOTTAGONOTSOFAST, VAMPIRE_TRAIT) owner.current.alpha = 204 // 255 * 0.80 +/** + * Handles unique drain ID checks and increases vampire's total and usable blood by blood_amount. Checks for ability upgrades. + * + * Arguments: + ** C: victim [/mob/living/carbon] that is being drained form. + ** blood_amount: amount of blood to add to vampire's usable and total pools. + */ /datum/antagonist/vampire/proc/adjust_blood(mob/living/carbon/C, blood_amount = 0) if(C) var/unique_suck_id = C.UID() @@ -293,6 +300,15 @@ RESTRICT_TYPE(/datum/antagonist/vampire) if(S.action) S.action.UpdateButtons() +/** + * Safely subtract vampire's bloodusable. Clamped between 0 and bloodtotal. + * + * Arguments: + ** blood_amount: amount of blood to subtract. + */ +/datum/antagonist/vampire/proc/subtract_usable_blood(blood_amount) + bloodusable = clamp(bloodusable - blood_amount, 0, bloodtotal) + /datum/antagonist/vampire/proc/vamp_burn(burn_chance) if(prob(burn_chance) && owner.current.health >= 50) switch(owner.current.health) diff --git a/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm b/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm index 39b7869ec86..3aeda9ce3f0 100644 --- a/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm +++ b/code/modules/antagonists/vampire/vampire_powers/dantalion_powers.dm @@ -33,7 +33,7 @@ handle_enthrall(user, target) var/datum/spell_handler/vampire/V = custom_handler var/blood_cost = V.calculate_blood_cost(vampire) - vampire.bloodusable -= blood_cost //we take the blood after enthralling, not before + vampire.subtract_usable_blood(blood_cost) //we take the blood after enthralling, not before else revert_cast(user) to_chat(user, "You or your target moved.") diff --git a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm index 220ba634df7..d0e39dff30f 100644 --- a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm +++ b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm @@ -199,7 +199,7 @@ var/datum/spell_handler/vampire/V = custom_handler var/datum/antagonist/vampire/vampire = user.mind.has_antag_datum(/datum/antagonist/vampire) var/blood_cost = V.calculate_blood_cost(vampire) - vampire.bloodusable -= blood_cost + vampire.subtract_usable_blood(blood_cost) start_turf = null should_recharge_after_cast = FALSE diff --git a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm index 0b1473ee8d8..691eed6ece6 100644 --- a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm +++ b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm @@ -181,7 +181,7 @@ var/datum/spell_handler/vampire/V = custom_handler var/datum/antagonist/vampire/vampire = user.mind.has_antag_datum(/datum/antagonist/vampire) var/blood_cost = V.calculate_blood_cost(vampire) - vampire.bloodusable = clamp(vampire.bloodusable - blood_cost, 0, vampire.bloodusable)// Vampires get a coupon if they have less than the normal blood cost + vampire.subtract_usable_blood(blood_cost)// Vampires get a coupon if they have less than the normal blood cost /proc/shadow_to_animation(turf/start_turf, turf/end_turf, mob/user) var/x_difference = end_turf.x - start_turf.x diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index 4d6b7e987a1..17a9e5818bb 100644 --- a/code/modules/assembly/voice.dm +++ b/code/modules/assembly/voice.dm @@ -29,7 +29,7 @@ if(listening) if(findtext(msg, "")) - recorded = strip_html_properly(msg) + recorded = strip_html_tags(msg) else recorded = msg recorded_type = type diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 6330afcfa70..66d1d4ad21e 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1230,6 +1230,18 @@ if("Set-Tab") stat_tab = payload["tab"] SSstatpanels.immediate_send_stat_data(src) + if("Listedturf-Scroll") + if(payload["min"] == payload["max"]) + // Not properly loaded yet, send the default set. + SSstatpanels.refresh_client_obj_view(src) + else + SSstatpanels.refresh_client_obj_view(src, payload["min"], payload["max"]) + // Uncomment to enable log_debug in stat panel code. + // Disabled normally due to HREF exploit concerns. + //if("Statpanel-Debug") + // log_debug(payload) + if("Resend-Asset") + SSassets.transport.send_assets(src, list(payload)) if("Debug-Stat-Entry") var/stat_item = locateUID(payload["stat_item_uid"]) if(!check_rights(R_DEBUG | R_VIEWRUNTIMES) || !stat_item) diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm index 31cfd405f5c..541e55faba3 100644 --- a/code/modules/client/preference/preferences_toggles.dm +++ b/code/modules/client/preference/preferences_toggles.dm @@ -301,7 +301,7 @@ disable_message = "You will no longer see runechat." blackbox_message = "Toggle Runechat" -/datum/preference_toggle/toggle_runechat +/datum/preference_toggle/toggle_ghost_death_notifs name = "Toggle Ghost Death Notifications" description = "Toggle a notification when a player dies" preftoggle_bitflag = PREFTOGGLE_2_DEATHMESSAGE diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index 0bc12e7c74b..713ef6f8c4d 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -469,8 +469,6 @@ "Grey" = 'icons/mob/clothing/species/grey/mask.dmi' ) - var/obj/item/voice_changer/voice_changer - var/datum/action/item_action/chameleon/change/chameleon_action /obj/item/clothing/mask/chameleon/Initialize(mapload) @@ -482,10 +480,7 @@ chameleon_action.chameleon_blacklist = list() chameleon_action.initialize_disguises() - voice_changer = new(src) - /obj/item/clothing/mask/chameleon/Destroy() - QDEL_NULL(voice_changer) QDEL_NULL(chameleon_action) return ..() @@ -497,6 +492,23 @@ . = ..() chameleon_action.emp_randomise(INFINITY) +/obj/item/clothing/mask/chameleon/voice_change + name = "gas mask" + desc = "A face-covering mask that can be connected to an air supply. While good for concealing your identity, it isn't good for blocking gas flow." + icon_state = "swat" + item_state = "swat" + + var/obj/item/voice_changer/voice_changer + +/obj/item/clothing/mask/chameleon/voice_change/Destroy() + QDEL_NULL(voice_changer) + return ..() + +/obj/item/clothing/mask/chameleon/voice_change/Initialize(mapload) + . = ..() + + voice_changer = new(src) + /obj/item/clothing/shoes/chameleon name = "black shoes" icon_state = "black" diff --git a/code/modules/economy/merch_items.dm b/code/modules/economy/merch_items.dm index 1951ab3cd0d..4b2c97487d5 100644 --- a/code/modules/economy/merch_items.dm +++ b/code/modules/economy/merch_items.dm @@ -124,12 +124,6 @@ cost = 500 category = MERCH_CAT_TOY -/datum/merch_item/baby - name = "Toddler" - desc = "This baby looks almost real. Wait, did it just burp?" - typepath = /obj/item/toddler - cost = 500 - category = MERCH_CAT_TOY /datum/merch_item/flag_slime name = "Slime People Flag" diff --git a/code/modules/events/blob/blob_structures/blob_core.dm b/code/modules/events/blob/blob_structures/blob_core.dm index 4d1f4f8a784..826f4155f39 100644 --- a/code/modules/events/blob/blob_structures/blob_core.dm +++ b/code/modules/events/blob/blob_structures/blob_core.dm @@ -38,6 +38,8 @@ /obj/structure/blob/core/Destroy() if(overmind) + if(overmind.mind) + SSticker.mode.blob_overminds -= overmind.mind overmind.blob_core = null overmind = null STOP_PROCESSING(SSobj, src) diff --git a/code/modules/events/blob/overmind.dm b/code/modules/events/blob/overmind.dm index 026712d5ec2..12919ff6944 100644 --- a/code/modules/events/blob/overmind.dm +++ b/code/modules/events/blob/overmind.dm @@ -116,7 +116,7 @@ status_tab_data[++status_tab_data.len] = list("Power Stored:", "[blob_points]/[max_blob_points]") /mob/camera/blob/Move(NewLoc, Dir = 0) - var/obj/structure/blob/B = locate() in range("3x3", NewLoc) + var/obj/structure/blob/B = locate() in range(3, NewLoc) if(B) loc = NewLoc else diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index 250a6089130..bc65ffb4d85 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -141,7 +141,7 @@ GLOBAL_LIST_EMPTY(event_last_fired) new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Mundane News", /datum/event/mundane_news, 300), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Vermin Infestation",/datum/event/infestation, 100, list(ASSIGNMENT_JANITOR = 100)), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Sentience", /datum/event/sentience, 50), - new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 0, list(ASSIGNMENT_ENGINEER = 30, ASSIGNMENT_GARDENER = 50)), + new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Wallrot", /datum/event/wallrot, 0, list(ASSIGNMENT_ENGINEER = 30)), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Fungal Growth", /datum/event/wallrot/fungus, 50, list(ASSIGNMENT_CHEMIST = 50)), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Koi School", /datum/event/carp_migration/koi, 80), new /datum/event_meta(EVENT_LEVEL_MUNDANE, "Camera Failure", /datum/event/camera_failure, 100, list(ASSIGNMENT_ENGINEER = 10)), diff --git a/code/modules/events/event_procs.dm b/code/modules/events/event_procs.dm index d0af4971ec2..9949617058a 100644 --- a/code/modules/events/event_procs.dm +++ b/code/modules/events/event_procs.dm @@ -64,6 +64,7 @@ active_with_role["Cyborg"] = 0 active_with_role["Janitor"] = 0 active_with_role["Botanist"] = 0 + active_with_role["Chemist"] = 0 active_with_role["Any"] = length(GLOB.player_list) for(var/mob/M in GLOB.player_list) @@ -105,6 +106,9 @@ if(M.mind.assigned_role == "Botanist") active_with_role["Botanist"]++ + if(M.mind.assigned_role == "Chemist") + active_with_role["Chemist"]++ + return active_with_role /datum/event/proc/num_players() diff --git a/code/modules/events/traders.dm b/code/modules/events/traders.dm index b0eb67f4df5..684772392d1 100644 --- a/code/modules/events/traders.dm +++ b/code/modules/events/traders.dm @@ -27,9 +27,9 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) /datum/traders/nian) if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) - GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been denied docking permission due to the heightened security alert aboard [station_name()].", "Trader Shuttle Docking Request Refused") + GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been denied docking permission due to the heightened security alert aboard [station_name()].", "Trader Shuttle Docking Request Refused", 'sound/AI/traderdeny.ogg') return - GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been granted docking permission at [station_name()] arrivals port 4.", "Trader Shuttle Docking Request Accepted") + GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been granted docking permission at [station_name()] arrivals port 4.", "Trader Shuttle Docking Request Accepted", 'sound/AI/tradergranted.ogg') /datum/event/traders/start() @@ -48,7 +48,7 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) /datum/traders/nian) if(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) - GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been denied docking permission due to the heightened security alert aboard [station_name()].", "Trader Shuttle Docking Request Refused") + GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been denied docking permission due to the heightened security alert aboard [station_name()].", "Trader Shuttle Docking Request Refused", 'sound/AI/traderdeny.ogg') // if the docking request was refused, fire another major event in 60 seconds var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MAJOR] EC.next_event_time = world.time + (60 * 10) @@ -102,7 +102,7 @@ GLOBAL_LIST_INIT(unused_trade_stations, list("sol")) greet_trader(M, T) success_spawn = TRUE if(success_spawn) - GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been granted docking permission at [station_name()] arrivals port 4.", "Trader Shuttle Docking Request Accepted") + GLOB.minor_announcement.Announce("A trading shuttle from [T.trader_location] has been granted docking permission at [station_name()] arrivals port 4.", "Trader Shuttle Docking Request Accepted", 'sound/AI/tradergranted.ogg') else GLOB.unused_trade_stations += station // Return the station to the list of usable stations. diff --git a/code/modules/food_and_drinks/food/foods/ethnic.dm b/code/modules/food_and_drinks/food/foods/ethnic.dm index b620811936b..d388f2c0692 100644 --- a/code/modules/food_and_drinks/food/foods/ethnic.dm +++ b/code/modules/food_and_drinks/food/foods/ethnic.dm @@ -135,35 +135,43 @@ // Middle Eastern // ////////////////////// -/obj/item/food/snacks/human/kabob - name = "-kabob" - icon_state = "kabob" - desc = "Human meat, on a stick." - trash = /obj/item/stack/rods - filling_color = "#A85340" - list_reagents = list("nutriment" = 8) - -/obj/item/food/snacks/monkeykabob - name = "meat-kabob" - icon_state = "kabob" +/obj/item/food/snacks/meatkebab + name = "meat-kebab" desc = "Delicious meat, on a stick." + icon_state = "kebab" trash = /obj/item/stack/rods filling_color = "#A85340" list_reagents = list("nutriment" = 8) goal_difficulty = FOOD_GOAL_NORMAL -/obj/item/food/snacks/tofukabob - name = "tofu-kabob" - icon_state = "kabob" +/obj/item/food/snacks/syntikebab + name = "synthimeat-kebab" + desc = "Delicious synthetic meat, on a stick." + icon_state = "kebab" + trash = /obj/item/stack/rods + filling_color = "#A85340" + list_reagents = list("nutriment" = 8) + +/obj/item/food/snacks/human/kebab + name = "-kebab" + desc = "Human meat, on a stick." + icon_state = "kebab" + trash = /obj/item/stack/rods + filling_color = "#A85340" + list_reagents = list("nutriment" = 8) + +/obj/item/food/snacks/tofukebab + name = "tofu-kebab" desc = "Vegan meat, on a stick." + icon_state = "kebab" trash = /obj/item/stack/rods filling_color = "#FFFEE0" list_reagents = list("nutriment" = 8) goal_difficulty = FOOD_GOAL_NORMAL -/obj/item/food/snacks/picoss_kabob - name = "picoss-kabob" - desc = "A popular Moghes streetfood consisting of vinegar-marinated space carp on a skewer with onion and chillis." +/obj/item/food/snacks/picoss_kebab + name = "picoss-kebab" + desc = "A popular Moghes street food consisting of vinegar-marinated space carp on a skewer with onion and chillis." icon_state = "picoss_skewer" trash = /obj/item/stack/rods list_reagents = list("protein" = 8, "vitamin" = 4, "vinegar" = 1, "capsaicin" = 1) diff --git a/code/modules/food_and_drinks/recipes/recipes_grill.dm b/code/modules/food_and_drinks/recipes/recipes_grill.dm index 00a341c5d13..c80132bf269 100644 --- a/code/modules/food_and_drinks/recipes/recipes_grill.dm +++ b/code/modules/food_and_drinks/recipes/recipes_grill.dm @@ -69,7 +69,7 @@ reagents = list("psilocybin" = 5, "sugar" = 10) items = list( /obj/item/food/snacks/dough, - /obj/item/food/snacks/dough, + /obj/item/food/snacks/dough ) result = /obj/item/food/snacks/rofflewaffles @@ -77,14 +77,14 @@ items = list( /obj/item/food/snacks/breadslice, /obj/item/food/snacks/breadslice, - /obj/item/food/snacks/cheesewedge, + /obj/item/food/snacks/cheesewedge ) result = /obj/item/food/snacks/grilledcheese /datum/recipe/grill/sausage items = list( /obj/item/food/snacks/meatball, - /obj/item/food/snacks/cutlet, + /obj/item/food/snacks/cutlet ) result = /obj/item/food/snacks/sausage @@ -92,7 +92,7 @@ reagents = list("flour" = 10) items = list( /obj/item/food/snacks/egg, - /obj/item/food/snacks/carpmeat, + /obj/item/food/snacks/carpmeat ) result = /obj/item/food/snacks/fishfingers @@ -113,7 +113,7 @@ /obj/item/food/snacks/egg, /obj/item/food/snacks/egg, /obj/item/food/snacks/cheesewedge, - /obj/item/food/snacks/cheesewedge, + /obj/item/food/snacks/cheesewedge ) result = /obj/item/food/snacks/omelette @@ -124,39 +124,39 @@ ) result = /obj/item/food/snacks/wingfangchu -/datum/recipe/grill/human/kabob +/datum/recipe/grill/human/kebab items = list( /obj/item/stack/rods, /obj/item/food/snacks/meat/human, - /obj/item/food/snacks/meat/human, + /obj/item/food/snacks/meat/human ) - result = /obj/item/food/snacks/human/kabob + result = /obj/item/food/snacks/human/kebab -/datum/recipe/grill/monkeykabob - items = list( - /obj/item/stack/rods, - /obj/item/food/snacks/meat/monkey, - /obj/item/food/snacks/meat/monkey, - ) - result = /obj/item/food/snacks/monkeykabob - -/datum/recipe/grill/syntikabob +/datum/recipe/grill/syntikebab items = list( /obj/item/stack/rods, /obj/item/food/snacks/meat/syntiflesh, - /obj/item/food/snacks/meat/syntiflesh, + /obj/item/food/snacks/meat/syntiflesh ) - result = /obj/item/food/snacks/monkeykabob + result = /obj/item/food/snacks/syntikebab -/datum/recipe/grill/tofukabob +/datum/recipe/grill/meatkeb // Do not put this above the other meat kebab variants or it will override them. + items = list( + /obj/item/stack/rods, + /obj/item/food/snacks/meat, + /obj/item/food/snacks/meat + ) + result = /obj/item/food/snacks/meatkebab + +/datum/recipe/grill/tofukebab items = list( /obj/item/stack/rods, /obj/item/food/snacks/tofu, - /obj/item/food/snacks/tofu, + /obj/item/food/snacks/tofu ) - result = /obj/item/food/snacks/tofukabob + result = /obj/item/food/snacks/tofukebab -/datum/recipe/grill/picoss_kabob +/datum/recipe/grill/picoss_kebab reagents = list("vinegar" = 5) items = list( /obj/item/food/snacks/carpmeat, @@ -165,10 +165,10 @@ /obj/item/food/snacks/grown/chili, /obj/item/stack/rods ) - result = /obj/item/food/snacks/picoss_kabob + result = /obj/item/food/snacks/picoss_kebab -/datum/recipe/grill/picoss_kabob/make_food(obj/container) - var/obj/item/food/snacks/picoss_kabob/being_cooked = ..() +/datum/recipe/grill/picoss_kebab/make_food(obj/container) + var/obj/item/food/snacks/picoss_kebab/being_cooked = ..() being_cooked.reagents.del_reagent("carpotoxin") return being_cooked @@ -272,7 +272,7 @@ /obj/item/food/snacks/shrimp, /obj/item/food/snacks/shrimp, /obj/item/food/snacks/shrimp, - /obj/item/stack/rods, + /obj/item/stack/rods ) result = /obj/item/food/snacks/shrimp_skewer @@ -281,7 +281,7 @@ items = list( /obj/item/food/snacks/salmonmeat, /obj/item/food/snacks/salmonmeat, - /obj/item/stack/rods, + /obj/item/stack/rods ) result = /obj/item/food/snacks/fish_skewer diff --git a/code/modules/hydroponics/fermenting_barrel.dm b/code/modules/hydroponics/fermenting_barrel.dm index 71c296d900b..cad77d337e2 100644 --- a/code/modules/hydroponics/fermenting_barrel.dm +++ b/code/modules/hydroponics/fermenting_barrel.dm @@ -13,6 +13,7 @@ /obj/structure/fermenting_barrel/Initialize() create_reagents(300) //Bluespace beakers, but without the portability or efficiency in circuits. + AddComponent(/datum/component/debris, DEBRIS_WOOD, -20, 10) . = ..() /obj/structure/fermenting_barrel/examine(mob/user) diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index ffa0bb3e4c0..d95a3e22862 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -205,9 +205,7 @@ add_attack_logs(user, target, "[what_done] ([reagent_str] | [genes_str])") -/obj/item/food/snacks/grown/extinguish_light(force = FALSE) - if(!force) - return +/obj/item/food/snacks/grown/extinguish_light(force) if(seed.get_gene(/datum/plant_gene/trait/glow/shadow)) return set_light(0) diff --git a/code/modules/hydroponics/hydroponics_tray.dm b/code/modules/hydroponics/hydroponics_tray.dm index c6ef764b2dc..4bd228afbbb 100644 --- a/code/modules/hydroponics/hydroponics_tray.dm +++ b/code/modules/hydroponics/hydroponics_tray.dm @@ -315,6 +315,11 @@ update_icon() +/obj/machinery/hydroponics/extinguish_light(force) + if(!force) + return + set_light(0) + /obj/machinery/hydroponics/update_overlays() . = ..() if(self_sustaining && !istype(src, /obj/machinery/hydroponics/soil)) diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index 2a03b5ced24..a8dd6585c65 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -85,8 +85,6 @@ desc = "Hostile Environment Cross-Kinetic Helmet: A helmet designed to withstand the wide variety of hazards from Lavaland. It wasn't enough for its last owner." icon_state = "hostile_env" item_state = "hostile_env" - body_parts_covered = HEAD - flags = BLOCKHAIR w_class = WEIGHT_CLASS_NORMAL max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT heat_protection = HEAD diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm index 3a3419db0e2..46331272516 100644 --- a/code/modules/mining/lavaland/ash_flora.dm +++ b/code/modules/mining/lavaland/ash_flora.dm @@ -167,6 +167,10 @@ harvest_message_high = "You finish mining the rock." delete_on_harvest = TRUE +/obj/structure/flora/ash/rock/Initialize(mapload) + . = ..() + AddComponent(/datum/component/debris, DEBRIS_ROCK, -20, 10) + /obj/structure/flora/ash/rock/style_2 icon_state = "basalt2" diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index f5f6e0e1ec1..f3bc2e39564 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -174,16 +174,16 @@ /mob/proc/hear_sleep(message) var/heard = "" if(prob(15)) - message = strip_html_properly(message) + message = html_decode(strip_html_tags(message)) var/list/punctuation = list(",", "!", ".", ";", "?") var/list/messages = splittext(message, " ") if(length(messages) > 0) var/R = rand(1, length(messages)) var/heardword = messages[R] if(copytext(heardword,1, 1) in punctuation) - heardword = copytext(heardword,2) + heardword = html_encode(copytext(heardword, 2)) if(copytext(heardword,-1) in punctuation) - heardword = copytext(heardword,1,length(heardword)) + heardword = html_encode(copytext(heardword, 1, length(heardword))) heard = "...You hear something about... '[heardword]'..." else heard = "...You almost hear something......" diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index f1af846f40d..8ea0776852e 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -311,7 +311,7 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key) return name /mob/living/whisper(message as text) - message = trim_strip_html_properly(message) + message = trim_strip_html_tags(message) //parse the language code and consume it var/list/message_pieces = parse_languages(message) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 1b20fa56aa4..58345615762 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -517,7 +517,7 @@ ) emag_override_modules = list(/obj/item/reagent_containers/spray/cyborg_lube) emag_modules = list(/obj/item/reagent_containers/spray/cyborg_facid, /obj/item/malfbroom) - malf_modules = list(/obj/item/stack/cyborg_mine) + malf_modules = list(/obj/item/stack/caution/proximity_sign/malf) special_rechargables = list( /obj/item/lightreplacer, /obj/item/reagent_containers/spray/cyborg_lube, diff --git a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm index d87d9647fbf..9ca58f640a4 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/basilisk.dm @@ -96,7 +96,7 @@ light_color = LIGHT_COLOR_LAVA projectiletype = /obj/item/projectile/temp/basilisk/magmawing crusher_loot = /obj/item/crusher_trophy/blaster_tubes/magma_wing - crusher_drop_mod = 60 + crusher_drop_mod = 100 //These things are extremely rare (1/133 per spawner). You shouldn't have to hope for another stroke of luck to get it's trophy after finding it /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/icewing name = "icewing watcher" @@ -110,7 +110,7 @@ projectiletype = /obj/item/projectile/temp/basilisk/icewing butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/bone = 1) //No sinew; the wings are too fragile to be usable crusher_loot = /obj/item/crusher_trophy/watcher_wing/ice_wing - crusher_drop_mod = 30 + crusher_drop_mod = 100 //These things are extremely rare (1/400 per spawner). You shouldn't have to hope for another stroke of luck to get it's trophy after finding it /obj/item/projectile/temp/basilisk/magmawing name = "scorching blast" diff --git a/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm index 6d5ed62d05e..cd401129be2 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining/goliath.dm @@ -112,7 +112,7 @@ loot = list(/obj/item/stack/sheet/animalhide/goliath_hide) //A throwback to the asteroid days butcher_results = list(/obj/item/food/snacks/monstermeat/goliath= 2, /obj/item/stack/sheet/bone = 2) crusher_loot = /obj/item/crusher_trophy/goliath_tentacle/ancient - crusher_drop_mod = 30 + crusher_drop_mod = 100 //These things are rare (1/100 per spawner). You shouldn't have to hope for another stroke of luck to get it's trophy after finding it wander = FALSE var/list/cached_tentacle_turfs var/turf/last_location diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index f1b21c4e32d..9e354111a88 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -185,9 +185,14 @@ to_chat(src, "As you observed [period_human_readable], you can freely toggle antag-hud without losing respawnability.") observer.started_as_observer = 1 close_spawn_windows() - var/obj/O = locate("landmark*Observer-Start") + var/obj/spawn_point + if(SSticker.current_state < GAME_STATE_PLAYING) + spawn_point = pick(GLOB.roundstart_observer_start) + else + spawn_point = locate("landmark*Observer-Start") + to_chat(src, "Now teleporting.") - observer.forceMove(O.loc) + observer.forceMove(get_turf(spawn_point)) observer.timeofdeath = world.time // Set the time of death so that the respawn timer works correctly. client.prefs.active_character.update_preview_icon(1) observer.icon = client.prefs.active_character.preview_icon diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index ec07906e81c..7035e9cf2d6 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -200,7 +200,7 @@ var/obj/item/paper/crumpled/P = new(loc) P.name = name if(info) // Something written on the paper. - /*var/new_text = strip_html_properly(info, MAX_PAPER_MESSAGE_LEN, TRUE) // Don't want HTML stuff getting gibberished. + /*var/new_text = strip_html_tags(info, MAX_PAPER_MESSAGE_LEN, TRUE) // Don't want HTML stuff getting gibberished. P.info = Gibberish(new_text, 100)*/ P.info = "Whatever was once written here has been made completely illegible by a combination of chew marks and saliva." message_ending = ", the drool making it an unreadable mess!" diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index 94e39bc7f68..d9879a9f46e 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -112,7 +112,7 @@ //WT550// /obj/item/gun/projectile/automatic/wt550 - name = "WT-550 PDW" + name = "\improper WT-550 PDW" desc = "An outdated personal defense weapon utilized by law enforcement. Chambered in 4.6x30mm." icon_state = "wt550" item_state = "wt550" diff --git a/code/modules/reagents/chemistry/reagents/medicine.dm b/code/modules/reagents/chemistry/reagents/medicine.dm index 9ed4d2a450c..0c1b82d9d0c 100644 --- a/code/modules/reagents/chemistry/reagents/medicine.dm +++ b/code/modules/reagents/chemistry/reagents/medicine.dm @@ -1020,7 +1020,10 @@ if(ishuman(M) && prob(5)) var/mob/living/carbon/human/H = M if(!H.get_int_organ(/obj/item/organ/internal/bone_tumor)) - new/obj/item/organ/internal/bone_tumor(H) + if(isslimeperson(H)) + new /obj/item/organ/internal/bone_tumor/slime_tumor(H) + else + new /obj/item/organ/internal/bone_tumor(H) return ..() diff --git a/code/modules/ruins/ghost_bar.dm b/code/modules/ruins/ghost_bar.dm index f2061ec251a..d212ddb88b7 100644 --- a/code/modules/ruins/ghost_bar.dm +++ b/code/modules/ruins/ghost_bar.dm @@ -1,3 +1,5 @@ +GLOBAL_LIST_EMPTY(occupants_by_key) + /obj/effect/mob_spawn/human/alive/ghost_bar name = "ghastly rejuvenator" mob_name = "ghost bar occupant" @@ -83,6 +85,11 @@ implant.insert(H) log_game("[ckey] has entered the ghost bar") playsound(src, 'sound/machines/wooden_closet_open.ogg', 50) + var/mob/old_mob = GLOB.occupants_by_key["[H.ckey]"] + if(old_mob) + qdel(old_mob) + GLOB.occupants_by_key["[H.ckey]"] = H + RegisterSignal(H, COMSIG_PARENT_QDELETING, PROC_REF(clear_references_to_owner)) /obj/effect/mob_spawn/human/alive/ghost_bar/proc/equip_item(mob/living/carbon/human/H, path, slot) var/obj/item/I = new path(H) @@ -90,6 +97,10 @@ H.speaks_ooc = TRUE return I +/obj/effect/mob_spawn/human/alive/ghost_bar/proc/clear_references_to_owner(mob/mob_to_obliterate) + SIGNAL_HANDLER // COMSIG_PARENT_QDELETING + GLOB.occupants_by_key -= mob_to_obliterate.ckey + /obj/structure/ghost_bar_cryopod name = "returning sarcophagus" desc = "Returns you back to the world of the dead." diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 3bb670b1876..6fdcf267a14 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -177,11 +177,11 @@ return CARGO_PREVENT_SHUTTLE var/sellable = SEND_SIGNAL(src, COMSIG_CARGO_CHECK_SELL, AM) manifest.items_to_sell[AM] = sellable - if(top_level && !(sellable & COMSIG_CARGO_IS_SECURED)) - manifest.loose_cargo = TRUE - if(sellable & COMSIG_CARGO_SELL_PRIORITY) - if(top_level) - return CARGO_OK + if(top_level) + if(!AM.anchored && !(sellable & COMSIG_CARGO_IS_SECURED)) + manifest.loose_cargo = TRUE + return CARGO_OK + if(found_priority || sellable & COMSIG_CARGO_SELL_PRIORITY) return CARGO_HAS_PRIORITY return CARGO_OK @@ -220,9 +220,6 @@ if(E.is_cleanable()) return CARGO_OK - if(AM.anchored && !istype(AM, /obj/mecha/working)) - return CARGO_SKIP_ATOM - return CARGO_OK @@ -253,7 +250,7 @@ SSeconomy.sold_atoms += "[AM.name](mess)" qdel_atoms += AM continue - else if(!(sellable & COMSIG_CARGO_SELL_SKIP)) + else if(!AM.anchored && !(sellable & COMSIG_CARGO_SELL_SKIP)) manifest.sent_trash = TRUE rescue_atoms += AM @@ -784,7 +781,7 @@ /datum/economy/simple_seller/mechs/check_sell(obj/docking_port/mobile/supply/S, atom/movable/AM) if(istype(AM, /obj/mecha/working)) - return COMSIG_CARGO_SELL_NORMAL | COMSIG_CARGO_IS_SECURED + return COMSIG_CARGO_SELL_NORMAL // Skip mech parts to avoid complaining about them. diff --git a/code/modules/station_goals/secondary/random_bulk_reagent.dm b/code/modules/station_goals/secondary/random_bulk_reagent.dm index 62585e95e4c..8dc35f4fd58 100644 --- a/code/modules/station_goals/secondary/random_bulk_reagent.dm +++ b/code/modules/station_goals/secondary/random_bulk_reagent.dm @@ -67,16 +67,11 @@ return copy /datum/secondary_goal_progress/random_bulk_reagent/update(atom/movable/AM, datum/economy/cargo_shuttle_manifest/manifest = null) - // Not a reagent container? Ignore. - if(!istype(AM, /obj/item/reagent_containers)) - return - // Not in a matching personal crate? Ignore. if(!check_personal_crate(AM)) return - var/obj/item/reagent_containers/container = AM - var/amount = container.reagents?.get_reagent_amount(initial(reagent_type.id)) + var/amount = AM.reagents?.get_reagent_amount(initial(reagent_type.id)) if(!amount) return sent += amount diff --git a/code/modules/station_goals/secondary/science/random_ripley.dm b/code/modules/station_goals/secondary/science/random_ripley.dm index 8398a523fbf..113b5c4601b 100644 --- a/code/modules/station_goals/secondary/science/random_ripley.dm +++ b/code/modules/station_goals/secondary/science/random_ripley.dm @@ -56,9 +56,8 @@ return copy /datum/secondary_goal_progress/random_ripley/update(atom/movable/AM, datum/economy/cargo_shuttle_manifest/manifest = null) - var/datum/component/label/goal/label = AM.GetComponent(/datum/component/label/goal) // Not labelled for this goal? Ignore. - if(!istype(label) || label.label_name != goal_requester) + if(!check_personal_crate(AM)) return if(!istype(AM, /obj/mecha/working/ripley)) return diff --git a/code/modules/station_goals/secondary/secondary_goal_tracker.dm b/code/modules/station_goals/secondary/secondary_goal_tracker.dm index 10327763f7b..498c4215920 100644 --- a/code/modules/station_goals/secondary/secondary_goal_tracker.dm +++ b/code/modules/station_goals/secondary/secondary_goal_tracker.dm @@ -98,12 +98,16 @@ return FALSE /datum/secondary_goal_progress/proc/check_personal_crate(atom/movable/AM) + // Accept stuff that is properly labelled with a hand labeller. + var/datum/component/label/goal/label = AM.GetComponent(/datum/component/label/goal) + if(istype(label)) + return !goal_requester || label.label_name == goal_requester + + // Accept stuff in matching personal crates. var/obj/structure/closet/crate/secure/personal/PC = get_atom_on_turf(AM, /obj/structure/closet/crate/secure/personal) if(!istype(PC)) return FALSE - if(goal_requester && PC.registered_name != goal_requester) - return FALSE - return TRUE + return !goal_requester || PC.registered_name == goal_requester /datum/secondary_goal_progress/proc/three_way_reward(datum/economy/cargo_shuttle_manifest/manifest, department, department_account, reward, message) SSblackbox.record_feedback("nested tally", "secondary goals", 1, list(goal_name, "payments made")) diff --git a/code/modules/station_goals/secondary/variety_reagent.dm b/code/modules/station_goals/secondary/variety_reagent.dm index ffccf8c8912..19cff7feb5b 100644 --- a/code/modules/station_goals/secondary/variety_reagent.dm +++ b/code/modules/station_goals/secondary/variety_reagent.dm @@ -45,27 +45,22 @@ return copy /datum/secondary_goal_progress/variety_reagent/update(atom/movable/AM, datum/economy/cargo_shuttle_manifest/manifest = null) - // Not a reagent container? Ignore. - if(!istype(AM, /obj/item/reagent_containers)) - return - // Not in a matching personal crate? Ignore. if(!check_personal_crate(AM)) return - var/obj/item/reagent_containers/container = AM // No reagents? Ignore. - if(!container.reagents.reagent_list) + if(!AM.reagents?.reagent_list) return - var/datum/reagent/reagent = container.reagents?.get_master_reagent() + var/datum/reagent/reagent = AM.reagents.get_master_reagent() // Make sure it's for our department. if(!reagent || reagent.goal_department != department) return // Isolated reagents only, please. - if(length(container.reagents.reagent_list) != 1) + if(length(AM.reagents.reagent_list) != 1) if(!manifest) return COMSIG_CARGO_SELL_WRONG SSblackbox.record_feedback("nested tally", "secondary goals", 1, list(goal_name, "mixed reagents")) diff --git a/code/modules/surgery/organs/bone_tumor.dm b/code/modules/surgery/organs/bone_tumor.dm index 98d070f39bd..6a90f4035e1 100644 --- a/code/modules/surgery/organs/bone_tumor.dm +++ b/code/modules/surgery/organs/bone_tumor.dm @@ -27,3 +27,7 @@ for(var/obj/item/organ/internal/I in other_groin_organs) I.receive_damage(rand(5, 15) + tumor_damage_modifier) + +/obj/item/organ/internal/bone_tumor/slime_tumor + name = "crystalized slime jelly" + diff --git a/html/statbrowser.css b/html/statbrowser.css index 11eda40d03d..d78fe76184a 100644 --- a/html/statbrowser.css +++ b/html/statbrowser.css @@ -158,19 +158,24 @@ td { pointer-events: none; } -.link { - display: inline; +.link, +.listedturf_link { background: none; border: none; - padding: 7px 14px; color: black; text-decoration: none; cursor: pointer; font-size: 13px; +} + +.link { + display: inline; + padding: 7px 14px; margin: 2px 2px; } -.link:hover { +.link:hover, +.listedturf_link:hover { text-decoration: underline; } @@ -225,7 +230,8 @@ body.dark { color: #b2c4dd; } -.dark .link { +.dark .link, +.dark .listedturf_link{ color: #abc6ec; } @@ -268,7 +274,8 @@ body.ntos { color: #b2c4dd; } -.ntos .link { +.ntos .link, +.ntos .listedturf_link{ color: #abc6ec; } @@ -311,7 +318,8 @@ body.paradise { color: #dec5bd; } -.paradise .link { +.paradise .link, +.paradise .listedturf_link{ color: #edc1b2; } @@ -354,6 +362,7 @@ body.syndicate { color: #debdbd; } -.syndicate .link { +.syndicate .link, +.syndicate .listedturf_link { color: #edb2b2; } diff --git a/html/statbrowser.js b/html/statbrowser.js index 7fa1a1f8bcc..c44d424a1b3 100644 --- a/html/statbrowser.js +++ b/html/statbrowser.js @@ -14,6 +14,13 @@ if (!String.prototype.trim) { }; } +// For sending BYOND debug logs ----------------------------------------------- +// If you use this, you'll need to uncomment the Statpanel-Debug message +// handling, currently in code/modules/client/client_procs.dm +function log_debug(data) { + Byond.sendMessage("Statpanel-Debug", JSON.stringify(data)); +} + // Status panel implementation ------------------------------------------------ var status_tab_parts = [["Loading...", ""]]; var current_tab = null; @@ -22,14 +29,20 @@ var href_token = null; var verb_tabs = []; var verbs = [["", ""]]; // list with a list inside var permanent_tabs = []; // tabs that won't be cleared by wipes -var turfcontents = []; +var turf_row_inner_height = 33; +var turf_row_outer_height = 35; +var turf_rows = {}; +var turf_incomplete_rows = {}; +var turf_size = 0; +var turf_image_errors = {}; +var turfcontents = {"total": 0}; var turfname = ""; +var imageFirstRetryDelay = 50; var imageRetryDelay = 500; var imageRetryLimit = 50; var menu = document.getElementById('menu'); var under_menu = document.getElementById('under_menu'); var statcontentdiv = document.getElementById('statcontent'); -var storedimages = []; var split_admin_tabs = false; // Any BYOND commands that could result in the client's focus changing go through this @@ -371,79 +384,217 @@ function draw_mc() { document.getElementById("statcontent").appendChild(table); } -function iconError(e) { - if(current_tab != turfname) { +function listedturf_add_row(table, table_index, true_index) { + let row = table.insertRow(table_index); + row.style.height = turf_row_inner_height + "px" + row.style.padding = "0px" + row.style.margin = "0px" + turf_rows[true_index] = row; + turf_incomplete_rows[true_index] = true_index + 1; +} + +function listedturf_fill_row(row, item_index) { + let object_info = turfcontents["" + item_index]; + if(!object_info) { + return false; + } + + let cell = document.createElement("td"); + cell.style.height = turf_row_inner_height + "px" + cell.style.padding = "0px" + cell.style.margin = "0px" + row.appendChild(cell) + + var button = document.createElement("div"); + button.className = "listedturf_link"; + var clickcatcher = ""; + button.onmousedown = function (object_info) { + // The outer function is used to close over a fresh "object_info" + // variable, rather than every onmousedown getting the "object_info" + // of the last entry. + return function (e) { + e.preventDefault(); + clickcatcher = "?src=" + object_info[1]; + switch (e.button) { + case 1: + clickcatcher += ";statpanel_item_click=middle" + break; + case 2: + clickcatcher += ";statpanel_item_click=right" + break; + default: + clickcatcher += ";statpanel_item_click=left" + } + if (e.shiftKey) { + clickcatcher += ";statpanel_item_shiftclick=1"; + } + if (e.ctrlKey) { + clickcatcher += ";statpanel_item_ctrlclick=1"; + } + if (e.altKey) { + clickcatcher += ";statpanel_item_altclick=1"; + } + window.location.href = clickcatcher; + } + }(object_info); + cell.appendChild(button); + + let img = document.createElement("img"); + img.id = object_info[1]; + img.src = object_info[2]; + img.style.verticalAlign = "middle"; + img.onerror = function (object_info) { + return function () { + let delay = imageRetryDelay; + if (!turf_image_errors[object_info[3]]) { + turf_image_errors[object_info[3]] = 0; + delay = imageFirstRetryDelay; + } + turf_image_errors[object_info[3]]++; + if (turf_image_errors[object_info[3]] > imageRetryLimit) { + return; + } + + Byond.sendMessage("Resend-Asset", object_info[3]); + setTimeout(function () { + // Use the failure count as a cachebreaker to force-reload. + let img = document.getElementById(object_info[1]); + img.src = object_info[2] + "?" + turf_image_errors[object_info[3]]; + }, imageRetryDelay); + } + }(object_info); + button.appendChild(img); + + var label = document.createElement("span"); + label.style.marginLeft = "5px"; + label.textContent = object_info[0]; + button.appendChild(label); + + return true; +} + +function listedturf_fill_all() { + for(let i in turf_incomplete_rows) { + let item_index = turf_incomplete_rows[i]; + if(!turf_rows[i] || listedturf_fill_row(turf_rows[i], item_index)) { + delete turf_incomplete_rows[i]; + } + } +} + +var suppress_next_scroll_message = false; +/* We keep a sliding "window" of listedturf items loded. On scroll, we add and + * remove table rows to maintain that window, and update the size of the + * padding row at the top of the table to keep them in the right spot. + */ +function listedturf_scrolled() { + let top_edge = document.documentElement.scrollTop; + let height = document.documentElement.clientHeight; + let bottom_edge = top_edge + height; + let total = document.documentElement.scrollHeight; + let table = document.getElementById("listedturf_table"); + let padding = document.getElementById("listedturf_padding"); + + if (!turf_rows.initialized) { + turf_rows = { + initialized: true, + min_row: 0, + max_row: 0, + }; + } + + if (turf_size === 0) { return; } - setTimeout(function () { - var node = e.target; - var current_attempts = Number(node.getAttribute("data-attempts")) || 0 - if (current_attempts > imageRetryLimit) { - return; + + let desired_min_row = Math.min(turf_size, Math.max(0, Math.floor(top_edge / turf_row_outer_height) - 10)); + let desired_max_row = Math.min(turf_size, desired_min_row + Math.ceil(height / turf_row_outer_height) + 21); + padding.style.height = (desired_min_row * turf_row_outer_height) + "px"; + if(desired_min_row == turf_rows.min_row && desired_max_row == turf_rows.max_row) { + listedturf_fill_all(); + suppress_next_scroll_message = false; + return; + } + + if (desired_min_row < turf_rows.min_row) { + for (let i = desired_min_row; i < turf_rows.min_row; i++) { + listedturf_add_row(table, i - desired_min_row + 1, i); } - var src = node.src; - node.src = null; - node.src = src + '#' + current_attempts; - node.setAttribute("data-attempts", current_attempts + 1) - draw_listedturf(); - }, imageRetryDelay); + } else if (desired_min_row > turf_rows.min_row) { + for (let i = turf_rows.min_row; i < desired_min_row && i < turf_rows.max_row; i++) { + if(turf_rows[i]) { + turf_rows[i].remove(); + delete turf_rows[i]; + } + } + } + turf_rows.min_row = desired_min_row; + + padding.style.height = turf_rows.min_row * turf_row_outer_height + "px" + + + if (desired_max_row < turf_rows.max_row) { + for (let i = Math.max(desired_max_row, turf_rows.min_row); i < turf_rows.max_row; i++) { + if(turf_rows[i]) { + turf_rows[i].remove(); + delete turf_rows[i]; + } + } + } else if (desired_max_row > turf_rows.max_row) { + for (let i = Math.max(turf_rows.min_row, turf_rows.max_row); i < desired_max_row; i++) { + listedturf_add_row(table, i - turf_rows.min_row + 1, i); + } + } + turf_rows.max_row = desired_max_row; + + listedturf_fill_all(); + + if (!suppress_next_scroll_message) { + Byond.sendMessage("Listedturf-Scroll", {"min": turf_rows.min_row, "max": turf_rows.max_row}) + } + suppress_next_scroll_message = false; } function draw_listedturf() { - statcontentdiv.textContent = ""; - var table = document.createElement("table"); - for (var i = 0; i < turfcontents.length; i++) { - var part = turfcontents[i]; - if (storedimages[part[1]] == null && part[2]) { - var img = document.createElement("img"); - img.src = part[2]; - img.id = part[1]; - storedimages[part[1]] = part[2]; - img.onerror = iconError; - table.appendChild(img); - } else { - var img = document.createElement("img"); - img.onerror = iconError; - img.src = storedimages[part[1]]; - img.id = part[1]; - table.appendChild(img); - } - var b = document.createElement("div"); - var clickcatcher = ""; - b.className = "link"; - b.onmousedown = function (part) { - // The outer function is used to close over a fresh "part" variable, - // rather than every onmousedown getting the "part" of the last entry. - return function (e) { - e.preventDefault(); - clickcatcher = "?src=" + part[1]; - switch (e.button) { - case 1: - clickcatcher += ";statpanel_item_click=middle" - break; - case 2: - clickcatcher += ";statpanel_item_click=right" - break; - default: - clickcatcher += ";statpanel_item_click=left" - } - if (e.shiftKey) { - clickcatcher += ";statpanel_item_shiftclick=1"; - } - if (e.ctrlKey) { - clickcatcher += ";statpanel_item_ctrlclick=1"; - } - if (e.altKey) { - clickcatcher += ";statpanel_item_altclick=1"; - } - window.location.href = clickcatcher; - } - }(part); - b.textContent = part[0]; - table.appendChild(b); - table.appendChild(document.createElement("br")); + if(document.getElementById("listedturf_div")) { + let div = document.getElementById("listedturf_div"); + div.style.height = (turf_row_outer_height * turf_size) + "px"; + suppress_next_scroll_message = true; + listedturf_scrolled(); + return } - document.getElementById("statcontent").appendChild(table); + + statcontentdiv.textContent = ""; + turf_rows = {}; + window.onscroll = function() { listedturf_scrolled(); }; + + let div = document.createElement("div"); + div.id = "listedturf_div"; + div.style.height = (turf_row_outer_height * turf_size) + "px"; + document.getElementById("statcontent").appendChild(div); + + let table = document.createElement("table"); + table.id = "listedturf_table"; + table.style.width = "100%" + table.style.height = "100%" + div.appendChild(table) + + let padding = document.createElement("tr"); + padding.id = "listedturf_padding"; + padding.style.height = "0px"; + padding.style.padding = "0px" + padding.style.margin = "0px" + table.appendChild(padding); + + let end_flex = document.createElement("tr"); + end_flex.id = "listedturf_end_flex"; + end_flex.style.height = "100%"; + end_flex.style.padding = "0px" + end_flex.style.margin = "0px" + table.appendChild(end_flex); + + suppress_next_scroll_message = true; + listedturf_scrolled(); } function remove_listedturf() { @@ -452,6 +603,14 @@ function remove_listedturf() { if (current_tab == turfname) { tab_change("Status"); } + if(document.getElementById("listedturf_div")) { + document.getElementById("listedturf_div").remove(); + } + turf_rows = {}; + turf_incomplete_rows = {}; + turf_size = 0; + turfcontents = {"total": 0}; + turfname = ""; } function remove_mc() { @@ -738,6 +897,7 @@ Byond.subscribeTo('remove_mc_tab', function (removeHref) { Byond.subscribeTo('update_listedturf', function (TC) { turfcontents = TC; + turf_size = TC["total"]; if (current_tab == turfname) { draw_listedturf(); } diff --git a/icons/_nanomaps/Delta_nanomap_z1.png b/icons/_nanomaps/Delta_nanomap_z1.png index dc2d2d09aa3..5c13b4e1761 100644 Binary files a/icons/_nanomaps/Delta_nanomap_z1.png and b/icons/_nanomaps/Delta_nanomap_z1.png differ diff --git a/icons/effects/particles/generic_particles.dmi b/icons/effects/particles/generic_particles.dmi new file mode 100644 index 00000000000..e322afcebbe Binary files /dev/null and b/icons/effects/particles/generic_particles.dmi differ diff --git a/icons/goonstation/objects/money.dmi b/icons/goonstation/objects/money.dmi deleted file mode 100644 index c4f6508f025..00000000000 Binary files a/icons/goonstation/objects/money.dmi and /dev/null differ diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi index c16c36946ca..c451ab64e09 100644 Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ diff --git a/icons/obj/money.dmi b/icons/obj/money.dmi new file mode 100644 index 00000000000..13e6186cec9 Binary files /dev/null and b/icons/obj/money.dmi differ diff --git a/icons/obj/stock_parts.dmi b/icons/obj/stock_parts.dmi index 8eed649cb05..c69525ba74f 100644 Binary files a/icons/obj/stock_parts.dmi and b/icons/obj/stock_parts.dmi differ diff --git a/icons/ui_icons/emoji.dmi b/icons/ui_icons/emoji.dmi index b0adcba15e5..a937141a940 100644 Binary files a/icons/ui_icons/emoji.dmi and b/icons/ui_icons/emoji.dmi differ diff --git a/paradise.dme b/paradise.dme index b8bc61904dc..427bca5bcd2 100644 --- a/paradise.dme +++ b/paradise.dme @@ -92,6 +92,7 @@ #include "code\__DEFINES\muzzle_flash.dm" #include "code\__DEFINES\nanomob_defines.dm" #include "code\__DEFINES\newscaster_defines.dm" +#include "code\__DEFINES\particle_defines.dm" #include "code\__DEFINES\pda.dm" #include "code\__DEFINES\pipes.dm" #include "code\__DEFINES\power_defines.dm" @@ -402,6 +403,7 @@ #include "code\datums\components\corpse_description.dm" #include "code\datums\components\cult_held_body.dm" #include "code\datums\components\deadchat_control.dm" +#include "code\datums\components\debris.dm" #include "code\datums\components\decal.dm" #include "code\datums\components\defibrillator.dm" #include "code\datums\components\ducttape.dm" diff --git a/sound/AI/nuke_codes.ogg b/sound/AI/nuke_codes.ogg index b71935968bd..f50b14edff7 100644 Binary files a/sound/AI/nuke_codes.ogg and b/sound/AI/nuke_codes.ogg differ diff --git a/sound/AI/traderdeny.ogg b/sound/AI/traderdeny.ogg new file mode 100644 index 00000000000..b4d215468a7 Binary files /dev/null and b/sound/AI/traderdeny.ogg differ diff --git a/sound/AI/tradergranted.ogg b/sound/AI/tradergranted.ogg new file mode 100644 index 00000000000..4c8d8791ff4 Binary files /dev/null and b/sound/AI/tradergranted.ogg differ diff --git a/tools/ci/no_duplicate_definitions.py b/tools/ci/no_duplicate_definitions.py new file mode 100644 index 00000000000..7d4f829fcc6 --- /dev/null +++ b/tools/ci/no_duplicate_definitions.py @@ -0,0 +1,59 @@ +import glob +import os +import re +import sys +import time +from collections import namedtuple, defaultdict + +Failure = namedtuple("Failure", ["filename", "lineno", "message"]) +Location = namedtuple("Location", ["filename", "lineno"]) + +RED = "\033[0;31m" +GREEN = "\033[0;32m" +BLUE = "\033[0;34m" +NC = "\033[0m" # No Color + +def print_error(message: str, filename: str, line_number: int): + if os.getenv("GITHUB_ACTIONS") == "true": # We're on github, output in a special format. + print(f"::error file={filename},line={line_number},title=Restricted Type in File::{filename}:{line_number}: {RED}{message}{NC}") + else: + print(f"{filename}:{line_number}: {RED}{message}{NC}") + +if __name__ == "__main__": + print("no_duplicate_definitions started") + + exit_code = 0 + start = time.time() + + dm_files = glob.glob("**/*.dm", recursive=True) + + if len(sys.argv) > 1: + dm_files = sys.argv[1:] + + all_failures = [] + + all_types = defaultdict(list) + + for code_filepath in dm_files: + with open(code_filepath, encoding="UTF-8") as code: + filename = code_filepath.split(os.path.sep)[-1] + + definition_matcher = re.compile(r'^(\/[\w][\w/]*?)(?: *\/[/*].*)?$') + for idx, line in enumerate(code): + if(rematch_result := re.search(definition_matcher, line)): + typepath = rematch_result.group(1) + if(not typepath): + print_error("Failed to find a type, despite matching regex. If this happens, this CI is probably broken.", code_filepath, idx + 1) + continue + all_types[typepath].append(Location(code_filepath, idx + 1)) + + for key, value_list in all_types.items(): + if len(value_list) > 1: + for location in value_list: + print_error(f"Found a duplicate definition of {key}.", location.filename, location.lineno) + exit_code = 1 + + end = time.time() + print(f"no_duplicate_definitions tests completed in {end - start:.2f}s\n") + + sys.exit(exit_code) diff --git a/tools/ci/verify_sql_version.py b/tools/ci/verify_sql_version.py new file mode 100644 index 00000000000..19c262c4d66 --- /dev/null +++ b/tools/ci/verify_sql_version.py @@ -0,0 +1,106 @@ +import os +import re +import sys +import time +from collections import namedtuple +import tomllib +from typing import Tuple, Union +from pathlib import Path + +RED = "\033[0;31m" +GREEN = "\033[0;32m" +BLUE = "\033[0;34m" +NC = "\033[0m" # No Color + +def print_error(message: str, filename: str = None): + if os.getenv("GITHUB_ACTIONS") == "true": # We're on github, output in a special format. + if filename != None: + print(f"::error file={filename},title=SQL Version mismatch::{filename}: {RED}{message}{NC}") + else: + print(f"::error title=SQL Version mismatch:: {RED}{message}{NC}") + else: + if filename != None: + print(f"{filename}: {RED}{message}{NC}") + else: + print(f"ERROR: {RED}{message}{NC}") + +def get_sql_folder_version() -> Tuple[Union[str, None], int]: + folder_path = Path("SQL/updates") + highest_file = None + highest_version = 0 + all_versions = [0] + + for file_path in folder_path.iterdir(): + if file_path.is_file(): + filename = file_path.name + search_result = re.search(r"(\d+)-(\d+)\.", filename) + if search_result is None: + continue + old_version = int(search_result.group(1)) + new_version = int(search_result.group(2)) + if (old_version + 1) != new_version: + print_error(f"Missing SQL version update detected, {old_version}-{new_version}.sql should be {old_version}-{old_version+1}.sql") + if new_version > highest_version: + highest_version = new_version + highest_file = filename + all_versions.append(new_version) + + for num in range(highest_version): + if num not in all_versions: + print_error(f"Missing SQL update for version {num}") + + return highest_file, highest_version + +if __name__ == "__main__": + print("verify_sql_version started") + + exit_code = 0 + start = time.time() + + config_path = "./config/example/config.toml" + define_path = "./code/__DEFINES/misc_defines.dm" + if os.path.exists(config_path): + with open(config_path, "rb") as config: + config_data = tomllib.load(config) + if not ("database_configuration" in config_data): + print_error(f"File containing config doesn't have a database_configuration section.", config_path) + exit_code = 1 + elif "sql_version" in config_data["database_configuration"]: + config_sql = config_data["database_configuration"]["sql_version"] + else: + print_error(f"No default SQL version set in {config_path}.", config_path) + exit_code = 1 + else: + print_error(f"File containing config the SQL version does not exist ({config_path}).") + exit_code = 1 + + if os.path.exists(define_path): + with open(define_path, "r") as define: + define_data = define.read() + if search_result := re.search(r"#define SQL_VERSION (\d+)", define_data): + define_sql = int(search_result.group(1)) + else: + print_error(f"No byond define for SQL found in {define_path}.", define_path) + exit_code = 1 + else: + print_error(f"File containing the byond define for the SQL version does not exist ({define_path}).") + exit_code = 1 + + updates_file, updates_folder_sql = get_sql_folder_version() + + if updates_folder_sql <= 0 or not isinstance(updates_folder_sql, int) or updates_file is None: + print_error("Failed to find a proper updates folder SQL version.") + exit_code = 1 + elif(exit_code == 0): + if config_sql != updates_folder_sql: + print_error(f"Updates file SQL version ({updates_folder_sql}) does not match the config SQL version ({config_sql}).", config_path) + exit_code = 1 + if define_sql != updates_folder_sql: + print_error(f"Updates file SQL version ({updates_folder_sql}) does not match the byond define SQL version ({define_sql}).", define_path) + exit_code = 1 + + end = time.time() + print(f"verify_sql_version tests completed in {end - start:.2f}s\n") + + sys.exit(exit_code) +