Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Upstream 25.01.2025 #1067

Merged
merged 79 commits into from
Jan 25, 2025
Merged

Merge Upstream 25.01.2025 #1067

merged 79 commits into from
Jan 25, 2025

Conversation

ss220app[bot]
Copy link

@ss220app ss220app bot commented Jan 25, 2025

This pull request merges upstream/master. Resolve possible conflicts manually and make sure all the changes are applied correctly.

Changelog

🆑 tgstation
admin: Добавлена новая функция в Lua-скриптинге, SS13.check_tick, для предотвращения чрезмерной нагрузки в процессе выполнения циклов и подобных операций.
qol: pAI теперь могут отозвать свою кандидатуру в любой момент.
fix: Исправлены некоторые ошибки в интерфейсе malf AI: один синий экран, лишнее двоеточие и проблемы с отображением модулей.
refactor: Cytology Vatbeast теперь basic mobы (более продвинутый ИИ), пожалуйста, сообщайте о любом необычном поведении.
admin: Добавлена роль Space Dragon в бан панель.
balance: Темные волшебники теперь телепортируются при атаке, но с большей вероятностью могут атаковать своих союзников.
qol: Улучшенно визуальное оформление для информации из вербов Who, Adminwho и Show Server Revision.
fix: Исправлена бесконтрольная мультипликация слаймов ксенобиологии, которая могла приводить к появлению сотен этих существ.
add: Добавлена локация outpost 31, айсбокс-руина, а также связанные с ней мобы, мегафауна и лут. Никаких спойлеров — ищите сами.
balance: ИИ теперь могут осматривать(examine) объекты.
fix: Дуллаханы могут осматривать (examine) объекты через свою голову.
qol: Удалено произвольно установленное ограничение в 10 максимальных выделений (highlights) в ТГчате.
fix: Исправлена ошибка, из-за которой одноразовые спектральные инструменты теряли свои способности до завершения "скелетизации" цели.
add: Добавлен очень редкий "жуткий" суффикс для предметов из мифрила и RPG события волшебника.
fix: Объекты с параметром throw_range 0 теперь корректно блокируются бейсбольными битами.
qol: Срывание привязей (tethers) теперь также удаляет их маяки.
qol: Теперь вы можете разрезать привязи, к которым вы прикреплены, даже находясь в движении.
qol: Привязи теперь рвутся при втягивании перчаток или отключении вашего MODsuit.
fix: Исправлены проблемы со "стеканием" привязей.
qol: Палитра действий теперь не исчезает, если у вас есть активные "плавающие" способности. (относится к HUD способностей)
fix: Словесные фобии теперь должны всегда применяться, а не пропускаться.
del: Удален станционный трейт дефицита продукции в торговых автоматах.
fix: Химические реакции теперь могут достигать минимально необходимой температуры/уровня pH.
code: Улучшен код для отладки хим-диспенсера.
balance: Фобии теперь стали чуть менее пагубными, однако их эффекты усиливаются, если вы дольше находитесь вблизи объекта страха.
balance: Карпофобы теперь боятся всех рыб.
del: Фобия еретиков удалена, так как она практически не использовалась.
fix: Stargazer, форма вознесения еретика пути Космоса, снова может корректно управляться.
code: Базовые мобы-питомцы теперь могут нацеливаться на объекты или на объекты и стены, используя две новые стратегии поиска цели.
qol: Эфириалы снова могут питаться вином.
fix: Окно TGUI Say слегка изменено для чистого внешнего вида и более плавной работы.
fix: Меню настроек персонажа было значительно переработано и теперь должно открываться быстрее.
fix: Исправлена опечатка в пути к файлу cavesound3.ogg.
balance: Если вы одновременно обладаете чертами, которые облегчают и затрудняют получение ранений, они будут компенсировать друг друга.
refactor: Заменён алгоритм bubble sort на timsort для списка камер, глобальные proc'и перенесены в cameranet датум.
add: Отдел снабжения теперь может заказывать боевые патроны для дробовика через вкладку Imports по завышенной цене.
balance: На черном рынке можно заполучить обычные виды дроби, но иногда она может быть сомнительного качества.
balance: Дробь теперь больше сосредоточена на нанесении ранений вместо прямого урона. Пули для дробовика (slugs) теперь имеют большее бронепробитие, но меньший урон.
balance: Дробовик типа "Bulldog" в остальном остался без изменений по части урона, но получил соответствующие вышеуказанные бонусы.
map: Модульная система руины комм-агента на Icebox обновлена.
qol: В меню черт характера добавлена строка поиска.
fix: IED теперь можно создавать, используя трубы, вырванные из земли.
fix: ID глав теперь сохраняют эффект указателя (жирный указатель и тд.), если вставлены в ПДА или отображены в качестве главной карты в бумажнике.
/:cl:

Summary by Sourcery

Update character preferences, quirks, and jobs pages to use server preferences. Add two new fish, the mossglob and babbelfish, to the heretic rift. Add a new AI-uplink brain that allows AIs to control a body with no organic internal organs. Modularize the comms agent ruin on Icebox. Add a search bar to the quirk menu. Allow IEDs to be crafted using pipes ripped from the ground. Fix issues with tethers, xenobio slime multiplication, malf AI screen, and other bugs.

New Features:

  • Added two new fish, the mossglob and babbelfish, to the heretic rift.
  • Added a new AI-uplink brain that allows AIs to control a body with no organic internal organs.
  • Added a new function in Lua scripting, SS13.check_tick, to avoid causing too much lag during loops and such.
  • Added outpost 31, the icebox ruin. Also its associated mobs, megafauna, and loot.
  • Added a very rare spooky suffix for mythril items and the wizard RPG event.
  • Cargo can order lethal shotgun shells from the Imports tab, at a premium.

Bug Fixes:

  • Fixed some bugs in the malf AI screen: one bluescreen, an extra colon, modules view.
  • Fixes uncapped xenobio slime multiplication which can easily result in hundreds of slimes.
  • Fixed single-use spectral instruments losing their powers before skeletonizing anyone.
  • Fixed tether stacking issues.
  • Fixed typo in path for cavesound3.ogg.
  • Objects with throw_range 0 now properly parried with baseball bats.
  • Word phobias should always apply now, rather than occasionally being missed.
  • Chemical reactions can now go all the way to their min required temp/ph.
  • Dullahans can also examine through their head again.
  • The Stargazer, a cosmic heretic's ascension, can be properly commanded once more.
  • Head IDs now keep the large pointer effect when slotted into a PDA or displayed as the front ID in a wallet.

Tests:

  • Integration tests now use a cached version of BYOND.
  • CI suite now collects data and sets up caches for other tasks.

tgstation-ci bot and others added 30 commits January 23, 2025 00:26
## About The Pull Request

This adds a new function for Lua scripts: `SS13.check_tick()` - it acts
pretty much exactly like the `CHECK_TICK` macro in DM.

It accepts a single argument, a boolean, which if true - it will act
like `CHECK_TICK_HIGH_PRIORITY` instead.

## Why It's Good For The Game

this function is a repeated code pattern in lua scripts, so having it in
the SS13 lua library is useful.

## Changelog
:cl:
admin: Added a new function in Lua scripting, SS13.check_tick, to avoid
causing too much lag during loops and such.
/:cl:
## About The Pull Request
This PR adds a "withdraw" button to the pAI candidacy menu, which
immediately (or near-immediately, given there's a small delay between
tgui refreshes) removes your information from the pAI device's candidate
download screen.

(The below video was recorded on a local instance of Monkestation, but
I've verified that it works exactly the same on tgstation proper too.)


https://github.com/user-attachments/assets/8508f17f-db61-4ae9-bdc8-b4214489b8b6

## Why It's Good For The Game
Being able to withdraw your candidacy means that, should you need to go
AFK or otherwise leave the game for an extended period of time, your
candidate details won't clog up the candidate download screen. Plus, you
won't frustrate players who download a pAI, only to find that they're
AFK.

## Changelog

:cl:MichiRecRoom
qol: pAIs can now withdraw their candidacy at any time. 
/:cl:
## About The Pull Request
1. Removed an errant :: in objectives list
2. Fixed height of modules selector, this fixes tgstation#89071
3. Fixed the categories loop, this fixes tgstation#86881
4. Did some stylistic code changes
5. Made a common ui component to reduce code duplication
6. Fixed a typo from a previous pr

>[!Note]
> This does not address tgstation#85999, which seems to be an issue on the DM
side.

<details>
<summary>pics</summary>

tgstation#89071 fixed
![Screenshot 2025-01-14
173954](https://github.com/user-attachments/assets/997ee3ff-4283-4b0c-a9b2-f05b68bb6fd2)

tgstation#86881 fixed
![Screenshot 2025-01-14
175903](https://github.com/user-attachments/assets/a12f493b-fc58-49aa-b9bf-a8162cc794a6)

</details>

## Why It's Good For The Game
Better ui, bug fixes

## Changelog
:cl:
fix: Fixed some bugs in the malf ai screen: one bluescreen, an extra
colon, modules view
/:cl:
## About The Pull Request
Removes uselocalstate from library admin. Did some stylistic changes as
per usual, broke the code into multiple files
## Why It's Good For The Game
useLocalstate is deprecated, I want it out of the codebase
## Changelog
N/A
## About The Pull Request

Another pretty easy conversion; this mob doesn't really do anything
except melee people and become rideable after you feed it cheese fries.
Changes in behaviour are that it will now seek out cheese fries by
itself if they happen to be lying around, and if it stays mad at you for
10 seconds it will use its powerful tentacle slap ability to hurl you
across the room, probably breaking a bone. Really just don't approach
this thing if you don't have cheese fries.

This mob basically only exists from cytology so I would be surprised if
it has existed in more than one round in the past four months.

## Why It's Good For The Game

I'm working down the remaining simple animals and a lot of them turn out
to still be quick fixes.

## Changelog

:cl:
refactor: The Cytology Vatbeast now uses the basic mob framework, please
report any unusual behaviour.
/:cl:
## About The Pull Request

adds Space Dragon, which is missing, to the banning panel as it should
already be there
## Why It's Good For The Game

makes it easier for admins
## Changelog
:cl:
admin: added Space Dragon role to the banning panel
/:cl:
## About The Pull Request

Simple animal Dark Wizard => Basic mob Dark Wizard.
This mob is I think used in literally one ruin, and very occasionally
spawned in deathmatch.
They don't really do anything except shoot you with slowing projectiles
and hit you with sticks.

I gave them a version of blink with a longer cooldown that they use when
attacked in melee range purely to make them very marginally more
wizard-like, and they will also now try to back away from you while
shooting you instead of running towards you.

They will also retaliate against anyone who attacks them including as a
response to friendly fire from other Dark Wizards, because I think it is
funny if they have very little loyalty to each other and that's a fun
thing to trigger when you are playing Doom.

Finally, we have a component called "revenge ability" which is mostly a
standin for AI responses to getting attacked. I made all existing uses
of it turn off if you're controlled by a sapient player who can hit
those buttons themselves, because they can choose when they want to use
those abilities themselves.

## Why It's Good For The Game

The march of progress is almost complete

## Changelog

:cl:
refactor: refactored some code
balance: Dark Wizards now teleport when attacked, but are more likely to
turn on their allies
/:cl:
…Revision verbs (tgstation#89099)

## About The Pull Request


![image](https://github.com/user-attachments/assets/44018d96-c88a-479b-9db0-d87b0b68b92c)

## Why It's Good For The Game

eyecandy is nice :3

## Changelog
:cl:
qol: Restyled the Who, Adminwho, and Show Server Revision verbs to be
prettier.
/:cl:
…ation#88860)

## About The Pull Request

I swear it's the last one


![image](https://github.com/user-attachments/assets/4572975f-5621-4b1d-9cbf-a3e923eac516)

### Added two new fishes to the rift pool:

![image](https://github.com/user-attachments/assets/0688d0d3-b9a1-4a98-ad61-8e47964acbc9)
#### The __mossglob__ is the Fisherman's Bane. The apex of evil. The
be-all-end-all in fisher destruction.
It is haunted. It deals toxic damage. It throws itself around. It's
coated in a deadly and hallucinogenic compound. Its mossy coating is
slippery. It revives itself. It throws itself out of aquariums. Best of
all, it is extremely easy to catch.
How do you deal with it? Well, probably by not fishing in a portal to
hell. Otherwise... good luck?
Suiciding into it empowers it by 15% and seals you inside. JOIN THE
MOSS!



![image](https://github.com/user-attachments/assets/50fb0695-08e7-4c2e-b223-ceba27dd7ffd)
#### The __babbelfish__ is a strange sort of predator, a psychic fish.
It casts a psychic aura near itself, ~~disturbing people~~ (nvm lol the
demoralizer datum is bad), killing fish nearby and then eating their
corpses.

When it dies, it emits an awesome psychic wail, which will instantly
kill all fish in audible range and severely incapacitate psy-sensitive
humans:
I can't play the ogg here but credits to grungus for it

![image](https://github.com/user-attachments/assets/657f293a-e43f-4e4a-8366-dd8f32dbb2fa)

![image](https://github.com/user-attachments/assets/adedd978-a0ff-4521-88eb-6a232cbaa177)
There is also a secret, secondary function of the babbelfish: Splitting
one in half (a terrible idea) and shoving it inside your ears will
unlock your full psychic potential, granting you psychic resistance and
grant you the ability to either understand or speak every single
language, at a terrible cost.

#### ARMS

Failing the fishing minigame while fishing in a heretical rift will now
cause the rift to tear your arm (and its fishing rod) off your joints
and greedily slurp it up. The Mansus does not care for losers. (Getting
bored and walking away while the minigame is up also counts as failure.)

However, these missing items can, in fact, be fished back up, which also
includes arms -and- heads lost normally to the rift! Not only that, but
you're able to fish up random arms of any type, presumably from other
fools across time and space.

#### This PR probably shouldn't be merged until the bug that causes
finite fish counts to not be finite is fixed. Infinite fire sharks are
bad enough...

Added ABSTRACT flag to profound_fisher fake rod.

objectify() now works with instances of objects.

Apparently snuck in a random-ass refactor to smoker lungs.

Psychic resistance now prevents the instadeath from trying to
telekinetically grasp at a opened rift.

Hallucinogenic fish with a stinger now inject their hallucinogenic
toxins.

I woudl like to preemptively apologize to ghommie
## Why It's Good For The Game

__Mossglob__
I think the game's missing a fish that's just extremely dangerous to be
around, the piscine equivalent to radioactive waste. You can't bin or
tank it, because it flies off. You can't kill it, because it's
atmos-proof and revives itself anyway. Trying to keep it on a table to
turn into disgusting mold 'slices' is a challenge in and of itself. This
fish will (not) make people think twice about fishing in hell, and give
another reason for security and command to give PSAs to not interact
with the rifts across space and time around the station, which I think
is wonderful.

__Babbelfish__
This fish punishes sloppy fishermen who hold up their catch and then
store it inside their bag for the poor fish to slowly asphixiate to
death in. The fish griefing that will happen from it will be
_wonderful_.

The organ thing is a clear reference to HHG, but it has its own twist.
You can speak all languages, or understand all languages... but rarely
both. It'll make for some silly situations where people just 'make
strange noises' at you or try to act as translator for, say, ashwalkers
or xenomorphs while being completely clueless as to what anyone is
actually saying.

__ARMS__

Arms. Arms arms. Someone asked me if rifts let you fish up arms and i
said ___IT DOES NOW___.
## Changelog
:cl:
Ghommie, carlarc, grungus
add: Added two new fish to heretic rift fishing.
add: You can now fish up arms, heads, and other items lost to heretic
rifts!
admin: objectify() now works with instances of objects. Mark a player,
then an object, and use those marks to call that global proc and you can
turn people into pre-existing items.
add: Psychic resistance now prevents the instadeath from trying to
telekinetically grasp at a opened rift.
/:cl:

---------

Co-authored-by: Ghom <[email protected]>
… over xenobio (tgstation#88935)

## About The Pull Request

Basically if there's too many slimes on a single turf, the slime will
not split.

The define I set is 2 (which can change) so you can still reasonably
spam a few monkeys inside a 3x3 cell and expect the slimes to split.

Overcrowding isn't really meant to stop people from doing xenobio like
this, it's to stop instances where people add way too many monkeys and
in 30 minutes everyone's clients turn into a powerpoint when entering
xenobiology. Basically to prevent this from happening.

```
ADMIN LOG: '(thesharkenning)'/(white wolf) deleted all instances of type or subtype of /mob/living/basic/slime (979 instances deleted) 
ADMIN LOG: '(thesharkenning)'/(white wolf) deleted all instances of type or subtype of /mob/living/basic/slime (641 instances deleted) 
ADMIN LOG: '(thesharkenning)'/(white wolf) deleted all instances of type or subtype of /mob/living/basic/slime (962 instances deleted)
```

## Why It's Good For The Game

I'm sure it's hell on the server too, but it's definitely something that
makes byond cry. You don't need a whole lot of time: just a lot of
monkeys and a single slime.

## Changelog

:cl:
fix: Fixes uncapped xenobio slime multiplication which can easily result
in hundreds of slimes.
/:cl:
…8714)

## About The Pull Request
<details>
<summary> expand to spoil the fun of exploring something for yourself
</summary>

firstly, the new ruin: outpost 31
its layout is vaguely based off an official map of the Outpost 31 from
the Thing movie but i ran out of space halfway


![image](https://github.com/user-attachments/assets/6db1aa9f-40d3-4693-897b-01e32b3ee1d2)

the boss drops a keycard for the storage room that you cant get in
otherwise, containing its own special item, and other stuff probably
useful for crew
crusher loot: trophy that heals you on each hit

the ruin is guarded by like 3 flesh blobs, very resilient (and slow)
masses of flesh that deal 3 brute damage, not harmful in melee but WILL
attempt to grab and devour/assimilate you which is FAR more lethal



https://github.com/user-attachments/assets/542cc6d0-f4ee-4598-9677-a03170c6c1c3



Boss: The Thing (with creative liberties otherwise this thing would
instakill you if it was true to source material)
difficulty: medium apparently idk mining jesus beat it with 400ms or so
HP: 1800
It is a much higher ranking changeling than those infiltrating SS13
It has 3 phases, 600hp each. Depleting its phase health will turn it
invincible and it will heal back half in 10 seconds. In order to prevent
this, the two Molecular Accelerators must be overloaded by interacting
with them to blast the changeling with deadly scifi magic or whatever
they do, forcing it to shed its form further and go to the next phase.
Not necessary for phase 3 because it literally just dies then

it focuses mostly on meleeing you and making certain tiles impassable
for you with 1hp tendrils, all attacks are telegraphed so theres no dumb
instakills here

it alternates between aoe abilities and abilities 

melee behavior:
- if too far, charge at target (charges twice on phase 3)
- too close, shriek (unavailable in phase 1) (technically AOE but its
more like a melee ability you know??)
- otherwise just try to melee

Shriek: if the player is too close emit a confusing shriek that makes
them confused and drop items

aoe behavior (phase 2, 3 only):
1: Puts 4 tendrils in a line cardinally
2: Puts tendrils around itself
3. Puts a patch of tendrils around and under the target, 3x3 in phase 3
4. Phase 3 only - spits patches of acid into the air that hurt when
stepped on

_(crusher is hard ok)_


https://github.com/user-attachments/assets/cbb98209-d3f0-470d-b0e8-4e310c5b709c



unique megafauna loot for this boss is like 1 AI-Uplink brain
its like a BORIS module but for humans i think you can figure out what
that means
while in a human shell they cannot roll non-malf midrounds and cannot be
converted, and cannot be mindswapped
the human MUST have all robotic organs (minus tongue because its not in
the exosuit fab and that kinda sucks to get)
will undeploy if polymorphed



https://github.com/user-attachments/assets/abcc277a-995a-4fa7-b980-0549b6b7cf52



</details>

## Why It's Good For The Game

icebox is severely lacking in actual good ruins (fuck that one fountain
ruin)
i feel that the loot given by megafauna has been and still apparently is
exclusively to make the victor more powerful, which kinda sucks because
thats just powergaming???? the loot of this boss is more crewsided,
specifically aiding the AI in a VERY limited quantity (1), so its not
anything good for powergamers, good for crew if the AI is not rogue

## Changelog
:cl:
add: outpost 31, the icebox ruin. Also its associated mobs, and
megafauna, and loot. Im not spoiling anything, find it yourself.
/:cl:

---------

Co-authored-by: Ben10Omintrix <[email protected]>
## About The Pull Request

AIs can now examine things that don't have a specific shiftclick
interaction, which is solely APCs and airlocks.
Also fixes Dullahans not being able to examine through their head,
because that's a thing I found accidentally.

## Why It's Good For The Game

AIs only being able to examine things near its core has always been very
annoying, and you're currently able to examine things through security
cameras anyways so it's not like we're consistent on how much details
the cameras are able to see. This at least makes it a consistent "yes,
you can examine through cameras".
AIs also don't really have enough ShiftClick interactions to block all
of examine just for their existence.

## Changelog

:cl:
balance: AIs can now examine through their eye.
fix: Dullahans can also examine through their head again.
/:cl:

---------

Co-authored-by: SmArtKar <[email protected]>
…89166)

## About The Pull Request

this removes the arbitrary maximum of 10 chat highlights. no clue why
this was limited in the first place.

## Why It's Good For The Game

it is my god-given right to have a morbillion chat highlights and make
IE on my own computer slow to a crawl doing so

## Changelog
:cl:
qol: Removed the arbitrary limit of 10 maximum highlights.
/:cl:
…tion#88489)

## About The Pull Request
The spooky element is quite old with a lot of single-letter variables.
Had too many species typechecks, and there's an issue that's been
bothering me, so I had to bring the code a bit up to date.

Furthermore the element wasn't used anywhere but on a couple of very
rare instruments, so I've been thinking a likewise very rare fantasy
suffix (mythril and wizard rpg event) would've been cool.

## Why It's Good For The Game
This will fix tgstation#88474. I believe the single-use versions of the spectral
instruments should be spent once someone is skeletonized, not before. It
was my fault for not noticing it earlier.

## Changelog

:cl:
fix: Fixed single-use spectral instruments losing their powers before
skeletonizing anyone.
add: A very rare spooky suffix for mythril items and the wizard RPG
event.
/:cl:

---------

Co-authored-by: Jacquerel <[email protected]>
…(boulders) (tgstation#89168)

## About The Pull Request


Set the minimum flight distance to 3 tiles for objects hit by baseball
bat parry,
so instead of the boulder being sucked into wielder when the baseball
bat hits it, it now bounces back a little.

## Why It's Good For The Game

Closes tgstation#87877
## Changelog
:cl:
fix: objects with throw_range 0 now properly parried with baseball bats
/:cl:
tgstation-ci bot and others added 14 commits January 24, 2025 10:43
…ve global procs to `cameranet` datum (tgstation#89087)

## About The Pull Request

Replace bubble sort with timsort for cameralist
Move cameralist related global procs to `cameranet` datum
Other minor code cleanup things

## Why It's Good For The Game

Cameralist reads for UIs are now more performant

## Changelog

:cl:
refactor: replace bubble sort with timsort for cameralist, move global
procs to `cameranet` datum
/:cl:
## About The Pull Request
Modularizes the comms agent ruin on icebox
only includes the actual comms agent's office part because i am tired ok
<details>
<summary>comms_luxury</summary>

(![image](https://github.com/user-attachments/assets/703d0dab-ba57-4e94-babf-9b09c4b90d86))
</details>

<details>
<summary>comms_cheap</summary>

![image](https://github.com/user-attachments/assets/cf6ad70e-d527-41b5-b90e-b2968794dc18)
</details>

and comms_standard is the previous office that was in the non
modularized map
## Why It's Good For The Game
modularized things are unique and good
## Changelog
:cl:
map: modularizes the comms agent ruin on icebox
/:cl:
## About The Pull Request

Adds a search bar to the quirks menu


![image](https://github.com/user-attachments/assets/c31f68a1-642a-4099-98a5-e516f5150514)

## Why It's Good For The Game

I hate endless scroll lists. Also I want to learn tgui. 

## Changelog



:cl:
qol: Quirk menu now has a search bar
/:cl:
…or PDA (tgstation#89177)

## About The Pull Request

Closes tgstation#88618
Head IDs now keep the large pointer effect when slotted into a PDA or
displayed as the front ID in a wallet

## Changelog
:cl:
fix: Head IDs now keep the large pointer effect when slotted into a PDA
or displayed as the front ID in a wallet
/:cl:
Copy link

sourcery-ai bot commented Jan 25, 2025

Reviewer's Guide by Sourcery

This pull request merges a large number of changes from upstream, including new features, bug fixes, refactors, and balance adjustments. The changes span multiple areas of the codebase, including UI, game logic, and content.

Class diagram for updated PreferencesMenu components

classDiagram
    class PreferencesMenuData {
        +character_preferences
        +selected_quirks
        +active_slot
    }

    class FeatureValueInput {
        +feature: Feature
        +featureId: string
        +shrink?: boolean
        +value: unknown
        +handleSetValue()
    }

    class FeatureDropdownInput {
        +serverData
        +disabled
        +buttons
        +handleSetValue()
        +value
        +populateOptions()
    }

    class QuirksPage {
        +selectedQuirks: string[]
        +searchQuery: string
        +handleQuirkSelection()
        +renderQuirkList()
    }

    FeatureValueInput --> PreferencesMenuData
    FeatureDropdownInput --> PreferencesMenuData
    QuirksPage --> PreferencesMenuData
Loading

Class diagram for new fish types and organs

classDiagram
    class Fish {
        +fish_id: string
        +status: number
        +size: number
        +weight: number
        +set_status()
    }

    class Mossglob {
        +health: number
        +death_text: string
        +fish_traits: list
        +suicide_act()
        +get_force_rank()
    }

    class Babbelfish {
        +moron_inside: mob
        +psy_wail()
        +fishes_die_twice()
        +check_loc()
    }

    class BabbelfishEars {
        +organ_traits: list
        +healing_factor: number
        +bound_component
        +on_mob_insert()
        +on_mob_remove()
    }

    Fish <|-- Mossglob
    Fish <|-- Babbelfish
    BabbelfishEars --|> Organ
Loading

State diagram for phobia system changes

stateDiagram-v2
    [*] --> Normal
    Normal --> Phobia: Trigger encountered
    Phobia --> Intensified: Prolonged exposure
    Intensified --> Normal: Trigger removed
    Phobia --> Normal: Trigger removed

    state Phobia {
        [*] --> Initial
        Initial --> Debuff: Apply effects
        Debuff --> CheckType: Check phobia type
        CheckType --> Fish: Fish phobia
        CheckType --> Blood: Blood phobia
        CheckType --> Other: Other phobias
    }

    state Intensified {
        [*] --> IncreasedEffects
        IncreasedEffects --> MaxDebuff
    }
Loading

File-Level Changes

Change Details Files
Added a new function in Lua scripting, SS13.check_tick, to prevent excessive lag during loops.
  • Added SS13.check_tick function to prevent excessive lag during loops.
lua/SS13_base.lua
pAIs can now withdraw their candidacy at any time.
  • Added a withdraw button to the pAI candidacy UI.
  • Added logic to allow pAIs to withdraw their candidacy.
code/controllers/subsystem/pai.dm
tgui/packages/tgui/interfaces/PaiSubmit.tsx
Fixed bugs in the malf AI screen, including a bluescreen, an extra colon, and module display issues.
  • Fixed a bluescreen error in the malf AI screen.
  • Removed an extra colon from the malf AI screen.
  • Fixed issues with module display in the malf AI screen.
tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
The Cytology Vatbeast now uses the basic mob framework.
  • Refactored the Cytology Vatbeast to use the basic mob framework.
  • Added a new AI controller for the Vatbeast.
code/modules/mob/living/simple_animal/hostile/vatbeast.dm
code/modules/mob/living/basic/cytology/vatbeast.dm
Added Space Dragon role to the banning panel.
  • Added Space Dragon role to the banning panel.
code/modules/admin/sql_ban_system.dm
Refactored some code.
  • Refactored some code.
Dark Wizards now teleport when attacked, but are more likely to turn on their allies.
  • Dark Wizards now teleport when attacked.
  • Dark Wizards are now more likely to attack their allies.
code/_globalvars/phobias.dm
Restyled the Who, Adminwho, and Show Server Revision verbs to improve their visual appearance.
  • Restyled the Who verb to be prettier.
  • Restyled the Adminwho verb to be prettier.
  • Restyled the Show Server Revision verb to be prettier.
code/modules/client/verbs/who.dm
Fixed uncapped xenobio slime multiplication which could result in hundreds of slimes.
  • Fixed uncapped xenobio slime multiplication.
code/modules/mob/living/basic/slime/actions.dm
Added outpost 31, the icebox ruin, along with associated mobs, megafauna, and loot.
  • Added outpost 31, the icebox ruin.
  • Added associated mobs, megafauna, and loot for outpost 31.
code/modules/mining/lavaland/megafauna_loot.dm
code/game/area/areas/ruins/icemoon.dm
code/datums/ruins/icemoon.dm
AIs can now examine through their eye.
  • AIs can now examine through their eye.
code/modules/mob/living/silicon/ai/freelook/eye.dm
code/_onclick/ai.dm
Dullahans can also examine through their head again.
  • Dullahans can also examine through their head again.
code/modules/mob/living/silicon/ai/freelook/eye.dm
code/_onclick/ai.dm
Removed the arbitrary limit of 10 maximum highlights.
  • Removed the arbitrary limit of 10 maximum highlights.
tgui/packages/tgui-panel/settings/TextHighlight.tsx
Fixed single-use spectral instruments losing their powers before skeletonizing anyone.
  • Fixed single-use spectral instruments losing their powers before skeletonizing anyone.
code/datums/elements/spooky.dm
Added a very rare spooky suffix for mythril items and the wizard RPG event.
  • Added a very rare spooky suffix for mythril items and the wizard RPG event.
code/datums/elements/spooky.dm
code/datums/components/fantasy/suffixes.dm
Objects with throw_range 0 are now properly parried with baseball bats.
  • Objects with throw_range 0 are now properly parried with baseball bats.
code/game/objects/items/weaponry.dm
Snapping tethers now also removes their beacons.
  • Snapping tethers now also removes their beacons.
code/modules/mod/modules/modules_engineering.dm
code/datums/components/tether.dm
You can now cut tethers that you're attached to while in motion.
  • You can now cut tethers that you're attached to while in motion.
code/modules/mod/modules/modules_engineering.dm
code/datums/components/tether.dm
Tethers now snap when you retract your gloves or disable your MODsuit.
  • Tethers now snap when you retract your gloves or disable your MODsuit.
code/modules/mod/modules/modules_engineering.dm
code/datums/components/tether.dm
Fixed tether stacking issues.
  • Fixed tether stacking issues.
code/modules/mod/modules/modules_engineering.dm
code/datums/components/tether.dm
Action palette no longer disappears while you have floating actions.
  • Action palette no longer disappears while you have floating actions.
code/_onclick/hud/action_button.dm
Word phobias should always apply now, rather than occasionally being missed.
  • Word phobias should always apply now, rather than occasionally being missed.
code/datums/brain_damage/phobia.dm
Removed the vending products shortage station trait.
  • Removed the vending products shortage station trait.
code/modules/vending/_vending.dm
code/datums/station_traits/negative_traits.dm
Chemical reactions can now reach their minimum required temperature/pH.
  • Chemical reactions can now reach their minimum required temperature/pH.
code/modules/reagents/chemistry/equilibrium.dm
code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
Improved code for debug chem master.
  • Improved code for debug chem master.
code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
Phobias are now somewhat less debilitating, although their effects get worse the longer you are near a fear trigger.
  • Phobias are now somewhat less debilitating.
  • Phobia effects get worse the longer you are near a fear trigger.
code/datums/brain_damage/phobia.dm
code/datums/mood_events/generic_negative_events.dm
Carphobes are now afraid of all fish.
  • Carphobes are now afraid of all fish.
code/_globalvars/phobias.dm
Removed the Heretic phobia, as it is now basically unused.
  • Removed the Heretic phobia.
code/_globalvars/phobias.dm
code/datums/brain_damage/phobia.dm
The Stargazer, a cosmic heretic's ascension, can be properly commanded once more.
  • The Stargazer, a cosmic heretic's ascension, can be properly commanded once more.
code/modules/mob/living/basic/heretic/star_gazer.dm
Basic mob pets can now target objects, or objects and walls by using two new targeting strategies.
  • Basic mob pets can now target objects.
  • Basic mob pets can now target objects and walls.
code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm
Ethereals can now live off wine again.
  • Ethereals can now live off wine again.
code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
TGUI Say window has been tweaked slightly to look and run cleanly.
  • TGUI Say window has been tweaked slightly to look and run cleanly.
tgui/packages/tgui-say/TguiSay.tsx
tgui/packages/tgui-say/styles/main.scss
tgui/packages/tgui-say/styles/button.scss
tgui/packages/tgui-say/styles/content.scss
tgui/packages/tgui-say/styles/dragzone.scss
tgui/packages/tgui-say/styles/textarea.scss
tgui/packages/tgui-say/styles/window.scss
The character preferences menu has been greatly refactored, hopefully opening faster.
  • The character preferences menu has been greatly refactored, hopefully opening faster.
tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dropdowns.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/tts_voice.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui_style.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/fps.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skin_tone.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/randomization.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/index.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/types.ts
tgui/packages/tgui/interfaces/PreferencesMenu/useRandomToggleState.ts
tgui/packages/tgui/interfaces/PreferencesMenu/useServerPrefs.ts
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/names.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/JobsPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/SpeciesPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/index.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ItemDisplay.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/loadout/ModifyPanel.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/DeleteCharacterPopup.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferences/KeybindingsPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferences/GamePreferencesPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/base.ts
tgui/packages/tgui/interfaces/PreferencesMenu/AntagsPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ghost.tsx
Fixed a typo in the path to cavesound3.ogg.
  • Fixed a typo in the path to cavesound3.ogg.
code/game/area/areas/ruins/icemoon.dm
Simultaneously having traits that both make you easier and harder to wound will now act like you have neither, resulting in you taking the normal amount of wound damage.
  • Simultaneously having traits that both make you easier and harder to wound will now act like you have neither, resulting in you taking the normal amount of wound damage.
code/datums/status_effects/wound_effects.dm
Replaced bubble sort with timsort for cameralist, and moved global procs to cameranet datum.
  • Replaced bubble sort with timsort for cameralist.
  • Moved global procs to cameranet datum.
code/modules/mob/living/silicon/ai/freelook/cameranet.dm
Cargo can order lethal shotgun shells from the Imports tab, at a premium.
  • Cargo can order lethal shotgun shells from the Imports tab, at a premium.
code/modules/cargo/packs/imports.dm
The black market source for buckshot can get normal types of buckshot, but some of it can be a bit dodgy.
  • The black market source for buckshot can get normal types of buckshot, but some of it can be a bit dodgy.
code/modules/cargo/markets/market_items/weapons.dm
code/modules/projectiles/ammunition/ballistic/shotgun.dm
code/modules/projectiles/projectile/bullets/shotgun.dm
Buckshot is now more wound focused over damage. Slugs are now more armor penetration focused over damage.
  • Buckshot is now more wound focused over damage.
  • Slugs are now more armor penetration focused over damage.
code/modules/projectiles/ammunition/ballistic/shotgun.dm
code/modules/projectiles/projectile/bullets/shotgun.dm
The bulldog shotgun remains otherwise unchanged damage wise, but does gain the above extra bonuses.
  • The bulldog shotgun remains otherwise unchanged damage wise, but does gain the above extra bonuses.
code/modules/projectiles/ammunition/ballistic/shotgun.dm
code/modules/projectiles/projectile/bullets/shotgun.dm
Modularized the comms agent ruin on icebox.
  • Modularized the comms agent ruin on icebox.
code/game/area/areas/ruins/icemoon.dm
Added a search bar to the quirk menu.
  • Added a search bar to the quirk menu.
tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx
IEDs can now also be crafted using pipes you have ripped up from the ground.
  • IEDs can now also be crafted using pipes you have ripped up from the ground.
code/modules/crafting/improvised.dm
Head IDs now keep the large pointer effect when slotted into a PDA or displayed as the front ID in a wallet.
  • Head IDs now keep the large pointer effect when slotted into a PDA or displayed as the front ID in a wallet.
code/game/objects/items/cards_ids.dm
Added two new fish to heretic rift fishing.
  • Added two new fish to heretic rift fishing.
code/modules/fishing/fish/types/rift.dm
You can now fish up arms, heads, and other items lost to heretic rifts!
  • You can now fish up arms, heads, and other items lost to heretic rifts!
code/modules/fishing/sources/source_types.dm
code/modules/fishing/fish/types/rift.dm
objectify() now works with instances of objects. Mark a player, then an object, and use those marks to call that global proc and you can turn people into pre-existing items.
  • objectify() now works with instances of objects.
code/modules/admin/verbs/adminfun.dm
Psychic resistance now prevents the instadeath from trying to telekinetically grasp at a opened rift.
  • Psychic resistance now prevents the instadeath from trying to telekinetically grasp at a opened rift.
code/modules/fishing/sources/source_types.dm
Added a new AI-uplink brain that allows AIs to control robotic bodies.
  • Added a new AI-uplink brain that allows AIs to control robotic bodies.
code/modules/mining/lavaland/megafauna_loot.dm
code/datums/components/tether.dm

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions bot added TGUI Добавление или изменение существующего интерфейса на базе фреймворка TGUI 🔉 Звук Нам скорее всего нравится как это звучит. 🖌️ Спрайты Вы заработали свою миска-рис и кошко-жена. Партия гордится вами! 🗺️ Изменение Карты В этом ПРе затронут файл не станционной карты. Может и не один. 🎸 Инструменты Мы выдаем себя за реальное сообщество разработчиков. 🙏 Слияние с восходящим потоком О великий восходящий поток, спасибо что приносишь нам свои дары контента и багфиксов labels Jan 25, 2025
@ss220app ss220app bot added the 📜 CL валиден Этот чейнджлог будет успешно опубликован label Jan 25, 2025
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have skipped reviewing this pull request. It seems to have been created by a bot (hey, ss220app[bot]!). We assume it knows what it's doing!

Copy link

github-actions bot commented Jan 25, 2025

This PR causes following conflicts on translate branch:

code/modules/antagonists/heretic/influences.dm
++<<<<<<< HEAD
 +	visible_message(span_userdanger("Psychic tendrils lash out from [src], psychically grabbing onto [user]'s psychically sensitive mind and tearing [user.p_their()] head off!"))
++||||||| afae02264af
++	to_chat(human_user, span_userdanger("Eldritch energy lashes out, piercing your fragile mind, tearing it to pieces!"))
++	human_user.ghostize()
++=======
+ 	to_chat(human_user, span_userdanger("Миситческая энергия пронзает ваш хрупкий разум, разрывая его на куски!"))
+ 	human_user.ghostize()
++>>>>>>> origin/translate
tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx
++<<<<<<< HEAD:tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx
 +  const randomizationOfMainFeatures = getRandomization(
 +    Object.fromEntries(mainFeatures),
 +    serverData,
 +    randomBodyEnabled,
++||||||| afae02264af:tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
++            <Stack height={`${CLOTHING_SIDEBAR_ROWS * CLOTHING_CELL_SIZE}px`}>
++              <Stack.Item>
++                <Stack vertical fill>
++                  <Stack.Item>
++                    <CharacterControls
++                      gender={data.character_preferences.misc.gender}
++                      handleOpenSpecies={props.openSpecies}
++                      handleRotate={() => {
++                        act('rotate');
++                      }}
++                      setGender={createSetPreference(act, 'gender')}
++                      showGender={
++                        currentSpeciesData ? !!currentSpeciesData.sexes : true
++                      }
++                    />
++                  </Stack.Item>
+ 
 -            {deleteCharacterPopupOpen && (
 -              <DeleteCharacterPopup
 -                close={() => setDeleteCharacterPopupOpen(false)}
 -              />
 -            )}
++                  <Stack.Item grow>
++                    <CharacterPreview
++                      height="100%"
++                      id={data.character_preview_view}
++                    />
++                  </Stack.Item>
++
++                  <Stack.Item position="relative">
++                    <NameInput
++                      name={data.character_preferences.names[data.name_to_use]}
++                      handleUpdateName={createSetPreference(
++                        act,
++                        data.name_to_use,
++                      )}
++                      openMultiNameInput={() => {
++                        setMultiNameInputOpen(true);
++                      }}
++                    />
++                  </Stack.Item>
++                </Stack>
++              </Stack.Item>
++
++              <Stack.Item width={`${CLOTHING_CELL_SIZE * 2 + 15}px`}>
++                <Stack height="100%" vertical wrap>
++                  {mainFeatures.map(([clothingKey, clothing]) => {
++                    const catalog =
++                      serverData &&
++                      (serverData[clothingKey] as FeatureChoicedServerData & {
++                        name: string;
++                      });
++
++                    return (
++                      catalog && (
++                        <Stack.Item key={clothingKey} mt={0.5} px={0.5}>
++                          <MainFeature
++                            catalog={catalog}
++                            currentValue={clothing}
++                            isOpen={currentClothingMenu === clothingKey}
++                            handleClose={() => {
++                              setCurrentClothingMenu(null);
++                            }}
++                            handleOpen={() => {
++                              setCurrentClothingMenu(clothingKey);
++                            }}
++                            handleSelect={createSetPreference(act, clothingKey)}
++                            randomization={
++                              randomizationOfMainFeatures[clothingKey]
++                            }
++                            setRandomization={createSetRandomization(
++                              act,
++                              clothingKey,
++                            )}
++                          />
++                        </Stack.Item>
++                      )
++                    );
++                  })}
++                </Stack>
++              </Stack.Item>
+ 
++              <Stack.Item grow basis={0}>
++                <Stack vertical fill>
++                  <PreferenceList
++                    act={act}
++                    randomizations={getRandomization(
++                      contextualPreferences,
++                      serverData,
++                      randomBodyEnabled,
++                    )}
++                    preferences={contextualPreferences}
++                    maxHeight="auto"
++                  />
++
++                  <PreferenceList
++                    act={act}
++                    randomizations={getRandomization(
++                      nonContextualPreferences,
++                      serverData,
++                      randomBodyEnabled,
++                    )}
++                    preferences={nonContextualPreferences}
++                    maxHeight="auto"
++                  >
++                    <Box my={0.5}>
++                      <Button
++                        color="red"
++                        disabled={
++                          Object.values(data.character_profiles).filter(
++                            (name) => name,
++                          ).length < 2
++                        } // check if existing chars more than one
++                        onClick={() => setDeleteCharacterPopupOpen(true)}
++                      >
++                        Delete Character
++                      </Button>
++                    </Box>
++                  </PreferenceList>
++                </Stack>
++              </Stack.Item>
++            </Stack>
++          </>
++        );
++      }}
++    />
++=======
+             <Stack height={`${CLOTHING_SIDEBAR_ROWS * CLOTHING_CELL_SIZE}px`}>
+               <Stack.Item>
+                 <Stack vertical fill>
+                   <Stack.Item>
+                     <CharacterControls
+                       gender={data.character_preferences.misc.gender}
+                       handleOpenSpecies={props.openSpecies}
+                       handleRotate={() => {
+                         act('rotate');
+                       }}
+                       setGender={createSetPreference(act, 'gender')}
+                       showGender={
+                         currentSpeciesData ? !!currentSpeciesData.sexes : true
+                       }
+                     />
+                   </Stack.Item>
+ 
+                   <Stack.Item grow>
+                     <CharacterPreview
+                       height="100%"
+                       id={data.character_preview_view}
+                     />
+                   </Stack.Item>
+ 
+                   <Stack.Item position="relative">
+                     <NameInput
+                       name={data.character_preferences.names[data.name_to_use]}
+                       handleUpdateName={createSetPreference(
+                         act,
+                         data.name_to_use,
+                       )}
+                       openMultiNameInput={() => {
+                         setMultiNameInputOpen(true);
+                       }}
+                     />
+                   </Stack.Item>
+                 </Stack>
+               </Stack.Item>
+ 
+               <Stack.Item width={`${CLOTHING_CELL_SIZE * 2 + 15}px`}>
+                 <Stack height="100%" vertical wrap>
+                   {mainFeatures.map(([clothingKey, clothing]) => {
+                     const catalog =
+                       serverData &&
+                       (serverData[clothingKey] as FeatureChoicedServerData & {
+                         name: string;
+                       });
+ 
+                     return (
+                       catalog && (
+                         <Stack.Item key={clothingKey} mt={0.5} px={0.5}>
+                           <MainFeature
+                             catalog={catalog}
+                             currentValue={clothing}
+                             isOpen={currentClothingMenu === clothingKey}
+                             handleClose={() => {
+                               setCurrentClothingMenu(null);
+                             }}
+                             handleOpen={() => {
+                               setCurrentClothingMenu(clothingKey);
+                             }}
+                             handleSelect={createSetPreference(act, clothingKey)}
+                             randomization={
+                               randomizationOfMainFeatures[clothingKey]
+                             }
+                             setRandomization={createSetRandomization(
+                               act,
+                               clothingKey,
+                             )}
+                           />
+                         </Stack.Item>
+                       )
+                     );
+                   })}
+                 </Stack>
+               </Stack.Item>
+ 
+               <Stack.Item grow basis={0}>
+                 <Stack vertical fill>
+                   <PreferenceList
+                     act={act}
+                     randomizations={getRandomization(
+                       contextualPreferences,
+                       serverData,
+                       randomBodyEnabled,
+                     )}
+                     preferences={contextualPreferences}
+                     maxHeight="auto"
+                   />
+ 
+                   <PreferenceList
+                     act={act}
+                     randomizations={getRandomization(
+                       nonContextualPreferences,
+                       serverData,
+                       randomBodyEnabled,
+                     )}
+                     preferences={nonContextualPreferences}
+                     maxHeight="auto"
+                   >
+                     <Box my={0.5}>
+                       <Button
+                         color="red"
+                         disabled={
+                           Object.values(data.character_profiles).filter(
+                             (name) => name,
+                           ).length < 2
+                         } // check if existing chars more than one
+                         onClick={() => setDeleteCharacterPopupOpen(true)}
+                       >
+                         Удалить персонажа
+                       </Button>
+                     </Box>
+                   </PreferenceList>
+                 </Stack>
+               </Stack.Item>
+             </Stack>
+           </>
+         );
+       }}
+     />
++>>>>>>> origin/translate:tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx
++<<<<<<< HEAD:tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/QuirksPage.tsx
 +                act('remove_quirk', { quirk: quirk.name });
 +              }}
 +              quirks={quirks
 +                .filter(([quirkName, _]) => {
 +                  return selectedQuirks.indexOf(quirkName) !== -1;
 +                })
 +                .map(([quirkName, quirk]) => {
 +                  return [
 +                    quirkName,
 +                    {
 +                      ...quirk,
 +                      failTooltip: getReasonToNotRemove(quirkName),
 +                    },
 +                  ];
 +                })}
 +              serverData={server_data}
 +              randomBodyEnabled={randomBodyEnabled}
 +            />
 +          </Stack.Item>
 +        </Stack>
 +      </Stack.Item>
 +    </Stack>
++||||||| afae02264af:tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx
++          if (quirk.value > 0) {
++            if (
++              maxPositiveQuirks !== -1 &&
++              positiveQuirks >= maxPositiveQuirks
++            ) {
++              return "You can't have any more positive quirks!";
++            } else if (pointsEnabled && balance + quirk.value > 0) {
++              return 'You need a negative quirk to balance this out!';
++            }
+           }
+ 
 -          if (selectedQuirk.value > 0) {
 -            positiveQuirks += 1;
++          const selectedQuirkNames = selectedQuirks.map((quirkKey) => {
++            return quirkInfo[quirkKey].name;
++          });
++
++          for (const blacklist of quirkBlacklist) {
++            if (blacklist.indexOf(quirk.name) === -1) {
++              continue;
++            }
++
++            for (const incompatibleQuirk of blacklist) {
++              if (
++                incompatibleQuirk !== quirk.name &&
++                selectedQuirkNames.indexOf(incompatibleQuirk) !== -1
++              ) {
++                return `This is incompatible with ${incompatibleQuirk}!`;
++              }
++            }
+           }
+ 
 -          balance += selectedQuirk.value;
 -        }
++          return undefined;
++        };
+ 
 -        const getReasonToNotAdd = (quirkName: string) => {
++        const getReasonToNotRemove = (quirkName: string) => {
+           const quirk = quirkInfo[quirkName];
+ 
++          if (pointsEnabled && balance - quirk.value > 0) {
++            return 'You need to remove a positive quirk first!';
++          }
++
++          return undefined;
++        };
++
++        return (
++          <Stack fill>
++            <Stack.Item basis="50%">
++              <Stack vertical fill align="center">
++                <Stack.Item>
++                  {maxPositiveQuirks > 0 ? (
++                    <Box fontSize="1.3em">Positive Quirks</Box>
++                  ) : (
++                    <Box mt={pointsEnabled ? 3.4 : 0} />
++                  )}
++                </Stack.Item>
++
++                <Stack.Item>
++                  {maxPositiveQuirks > 0 ? (
++                    <StatDisplay>
++                      {positiveQuirks} / {maxPositiveQuirks}
++                    </StatDisplay>
++                  ) : (
++                    <Box mt={pointsEnabled ? 3.4 : 0} />
++                  )}
++                </Stack.Item>
++
++                <Stack.Item>
++                  <Box as="b" fontSize="1.6em">
++                    Available Quirks
++                  </Box>
++                </Stack.Item>
++
++                <Stack.Item grow width="100%">
++                  <QuirkList
++                    selected={false}
++                    onClick={(quirkName, quirk) => {
++                      if (getReasonToNotAdd(quirkName) !== undefined) {
++                        return;
++                      }
++
++                      setSelectedQuirks(selectedQuirks.concat(quirkName));
++
++                      act('give_quirk', { quirk: quirk.name });
++                    }}
++                    quirks={quirks
++                      .filter(([quirkName, _]) => {
++                        return selectedQuirks.indexOf(quirkName) === -1;
++                      })
++                      .map(([quirkName, quirk]) => {
++                        return [
++                          quirkName,
++                          {
++                            ...quirk,
++                            failTooltip: getReasonToNotAdd(quirkName),
++                          },
++                        ];
++                      })}
++                    serverData={server_data}
++                    randomBodyEnabled={randomBodyEnabled}
++                  />
++                </Stack.Item>
++              </Stack>
++            </Stack.Item>
++
++            <Stack.Item align="center">
++              <Icon name="exchange-alt" size={1.5} ml={2} mr={2} />
++            </Stack.Item>
++
++            <Stack.Item basis="50%">
++              <Stack vertical fill align="center">
++                <Stack.Item>
++                  {pointsEnabled ? (
++                    <Box fontSize="1.3em">Quirk Balance</Box>
++                  ) : (
++                    <Box mt={maxPositiveQuirks > 0 ? 3.4 : 0} />
++                  )}
++                </Stack.Item>
++
++                <Stack.Item>
++                  {pointsEnabled ? (
++                    <StatDisplay>{balance}</StatDisplay>
++                  ) : (
++                    <Box mt={maxPositiveQuirks > 0 ? 3.4 : 0} />
++                  )}
++                </Stack.Item>
++
++                <Stack.Item>
++                  <Box as="b" fontSize="1.6em">
++                    Current Quirks
++                  </Box>
++                </Stack.Item>
++
++                <Stack.Item grow width="100%">
++                  <QuirkList
++                    selected
++                    onClick={(quirkName, quirk) => {
++                      if (getReasonToNotRemove(quirkName) !== undefined) {
++                        return;
++                      }
++
++                      setSelectedQuirks(
++                        selectedQuirks.filter(
++                          (otherQuirk) => quirkName !== otherQuirk,
++                        ),
++                      );
++
++                      act('remove_quirk', { quirk: quirk.name });
++                    }}
++                    quirks={quirks
++                      .filter(([quirkName, _]) => {
++                        return selectedQuirks.indexOf(quirkName) !== -1;
++                      })
++                      .map(([quirkName, quirk]) => {
++                        return [
++                          quirkName,
++                          {
++                            ...quirk,
++                            failTooltip: getReasonToNotRemove(quirkName),
++                          },
++                        ];
++                      })}
++                    serverData={server_data}
++                    randomBodyEnabled={randomBodyEnabled}
++                  />
++                </Stack.Item>
++              </Stack>
++            </Stack.Item>
++          </Stack>
++        );
++      }}
++    />
++=======
+           if (quirk.value > 0) {
+             if (
+               maxPositiveQuirks !== -1 &&
+               positiveQuirks >= maxPositiveQuirks
+             ) {
+               return 'Нельзя иметь больше положительных черт!';
+             } else if (pointsEnabled && balance + quirk.value > 0) {
+               return 'Необходим баланс с отрицательными чертами!';
+             }
+           }
+ 
+           const selectedQuirkNames = selectedQuirks.map((quirkKey) => {
+             return quirkInfo[quirkKey].name;
+           });
+ 
+           for (const blacklist of quirkBlacklist) {
+             if (blacklist.indexOf(quirk.name) === -1) {
+               continue;
+             }
+ 
+             for (const incompatibleQuirk of blacklist) {
+               if (
+                 incompatibleQuirk !== quirk.name &&
+                 selectedQuirkNames.indexOf(incompatibleQuirk) !== -1
+               ) {
+                 return `Черта несовместима с ${incompatibleQuirk}!`;
+               }
+             }
+           }
+ 
+           return undefined;
+         };
+ 
+         const getReasonToNotRemove = (quirkName: string) => {
+           const quirk = quirkInfo[quirkName];
+ 
+           if (pointsEnabled && balance - quirk.value > 0) {
+             return 'Сначала вам нужно убрать позитивную черту!';
+           }
+ 
+           return undefined;
+         };
+ 
+         return (
+           <Stack fill>
+             <Stack.Item basis="50%">
+               <Stack vertical fill align="center">
+                 <Stack.Item>
+                   {maxPositiveQuirks > 0 ? (
+                     <Box fontSize="1.3em">Положительные черты</Box>
+                   ) : (
+                     <Box mt={pointsEnabled ? 3.4 : 0} />
+                   )}
+                 </Stack.Item>
+ 
+                 <Stack.Item>
+                   {maxPositiveQuirks > 0 ? (
+                     <StatDisplay>
+                       {positiveQuirks} / {maxPositiveQuirks}
+                     </StatDisplay>
+                   ) : (
+                     <Box mt={pointsEnabled ? 3.4 : 0} />
+                   )}
+                 </Stack.Item>
+ 
+                 <Stack.Item>
+                   <Box as="b" fontSize="1.6em">
+                     Доступные черты
+                   </Box>
+                 </Stack.Item>
+ 
+                 <Stack.Item grow width="100%">
+                   <QuirkList
+                     selected={false}
+                     onClick={(quirkName, quirk) => {
+                       if (getReasonToNotAdd(quirkName) !== undefined) {
+                         return;
+                       }
+ 
+                       setSelectedQuirks(selectedQuirks.concat(quirkName));
+ 
+                       act('give_quirk', { quirk: quirk.name });
+                     }}
+                     quirks={quirks
+                       .filter(([quirkName, _]) => {
+                         return selectedQuirks.indexOf(quirkName) === -1;
+                       })
+                       .map(([quirkName, quirk]) => {
+                         return [
+                           quirkName,
+                           {
+                             ...quirk,
+                             failTooltip: getReasonToNotAdd(quirkName),
+                           },
+                         ];
+                       })}
+                     serverData={server_data}
+                     randomBodyEnabled={randomBodyEnabled}
+                   />
+                 </Stack.Item>
+               </Stack>
+             </Stack.Item>
+ 
+             <Stack.Item align="center">
+               <Icon name="exchange-alt" size={1.5} ml={2} mr={2} />
+             </Stack.Item>
+ 
+             <Stack.Item basis="50%">
+               <Stack vertical fill align="center">
+                 <Stack.Item>
+                   {pointsEnabled ? (
+                     <Box fontSize="1.3em">Баланс черт</Box>
+                   ) : (
+                     <Box mt={maxPositiveQuirks > 0 ? 3.4 : 0} />
+                   )}
+                 </Stack.Item>
+ 
+                 <Stack.Item>
+                   {pointsEnabled ? (
+                     <StatDisplay>{balance}</StatDisplay>
+                   ) : (
+                     <Box mt={maxPositiveQuirks > 0 ? 3.4 : 0} />
+                   )}
+                 </Stack.Item>
+ 
+                 <Stack.Item>
+                   <Box as="b" fontSize="1.6em">
+                     Текущие черты
+                   </Box>
+                 </Stack.Item>
+ 
+                 <Stack.Item grow width="100%">
+                   <QuirkList
+                     selected
+                     onClick={(quirkName, quirk) => {
+                       if (getReasonToNotRemove(quirkName) !== undefined) {
+                         return;
+                       }
+ 
+                       setSelectedQuirks(
+                         selectedQuirks.filter(
+                           (otherQuirk) => quirkName !== otherQuirk,
+                         ),
+                       );
+ 
+                       act('remove_quirk', { quirk: quirk.name });
+                     }}
+                     quirks={quirks
+                       .filter(([quirkName, _]) => {
+                         return selectedQuirks.indexOf(quirkName) !== -1;
+                       })
+                       .map(([quirkName, quirk]) => {
+                         return [
+                           quirkName,
+                           {
+                             ...quirk,
+                             failTooltip: getReasonToNotRemove(quirkName),
+                           },
+                         ];
+                       })}
+                     serverData={server_data}
+                     randomBodyEnabled={randomBodyEnabled}
+                   />
+                 </Stack.Item>
+               </Stack>
+             </Stack.Item>
+           </Stack>
+         );
+       }}
+     />
++>>>>>>> origin/translate:tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/SpeciesPage.tsx
++<<<<<<< HEAD:tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/SpeciesPage.tsx
 +        <Button icon="arrow-left" onClick={props.handleClose}>
 +          Go Back
 +        </Button>
++||||||| afae02264af:tgui/packages/tgui/interfaces/PreferencesMenu/SpeciesPage.tsx
++        <Button
++          icon="arrow-left"
++          onClick={props.handleClose}
++          content="Go Back"
++        />
++=======
+         <Button icon="arrow-left" onClick={props.handleClose} content="Назад" />
++>>>>>>> origin/translate:tgui/packages/tgui/interfaces/PreferencesMenu/SpeciesPage.tsx
++<<<<<<< HEAD:tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/SpeciesPage.tsx
 +    <SpeciesPageInner
 +      handleClose={props.closeSpecies}
 +      species={serverData.species}
++||||||| afae02264af:tgui/packages/tgui/interfaces/PreferencesMenu/SpeciesPage.tsx
++    <ServerPreferencesFetcher
++      render={(serverData) => {
++        if (serverData) {
++          return (
++            <SpeciesPageInner
++              handleClose={props.closeSpecies}
++              species={serverData.species}
++            />
++          );
++        } else {
++          return <Box>Loading species...</Box>;
++        }
++      }}
++=======
+     <ServerPreferencesFetcher
+       render={(serverData) => {
+         if (serverData) {
+           return (
+             <SpeciesPageInner
+               handleClose={props.closeSpecies}
+               species={serverData.species}
+             />
+           );
+         } else {
+           return <Box>Загрузка видов...</Box>;
+         }
+       }}
++>>>>>>> origin/translate:tgui/packages/tgui/interfaces/PreferencesMenu/SpeciesPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferenceWindow.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferences/KeybindingsPage.tsx
++<<<<<<< HEAD:tgui/packages/tgui/interfaces/PreferencesMenu/GamePreferences/KeybindingsPage.tsx
 +            <Button.Confirm onClick={() => act('reset_all_keybinds')}>
 +              Reset all keybindings
 +            </Button.Confirm>
++||||||| afae02264af:tgui/packages/tgui/interfaces/PreferencesMenu/KeybindingsPage.tsx
++            <Button.Confirm
++              content="Reset all keybindings"
++              onClick={() => act('reset_all_keybinds')}
++            />
++=======
+             <Button.Confirm
+               content="Сбросить все привязки клавиш"
+               onClick={() => act('reset_all_keybinds')}
+             />
++>>>>>>> origin/translate:tgui/packages/tgui/interfaces/PreferencesMenu/KeybindingsPage.tsx
tgui/packages/tgui/interfaces/PreferencesMenu/names.tsx
tgui/packages/tgui/interfaces/Techweb.jsx

@Gaxeer Gaxeer merged commit 12fd8b6 into master Jan 25, 2025
24 checks passed
ss220app bot added a commit that referenced this pull request Jan 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🎸 Инструменты Мы выдаем себя за реальное сообщество разработчиков. 🖌️ Спрайты Вы заработали свою миска-рис и кошко-жена. Партия гордится вами! 🙏 Слияние с восходящим потоком О великий восходящий поток, спасибо что приносишь нам свои дары контента и багфиксов 📜 CL валиден Этот чейнджлог будет успешно опубликован 🔉 Звук Нам скорее всего нравится как это звучит. TGUI Добавление или изменение существующего интерфейса на базе фреймворка TGUI 🗺️ Изменение Карты В этом ПРе затронут файл не станционной карты. Может и не один.
Projects
None yet
Development

Successfully merging this pull request may close these issues.