From a6fef33fdaf282eb038e2054fc3e9dfab7837ca5 Mon Sep 17 00:00:00 2001 From: Kara Date: Mon, 11 Sep 2023 23:11:06 -0700 Subject: [PATCH] Port various pages (#1) --- README.md | 2 +- book.toml | 11 +- src/SUMMARY.md | 4 + src/en/assets/images/tips-markup-example.png | Bin 0 -> 37309 bytes .../codebase-info/conventions.md | 578 +++++++++++++++++- .../codebase-info/pull-request-guidelines.md | 111 +++- .../tips/writing-guidebook-entries.md | 184 +++++- .../tips/yaml-crash-course.md | 175 +++++- .../space-station-14/chemistry/metabolism.md | 46 ++ .../space-station-14/chemistry/reactions.md | 46 ++ src/en/space-station-14/chemistry/reagents.md | 66 ++ .../chemistry/solution-containers.md | 64 ++ 12 files changed, 1279 insertions(+), 8 deletions(-) create mode 100644 src/en/assets/images/tips-markup-example.png create mode 100644 src/en/space-station-14/chemistry/metabolism.md create mode 100644 src/en/space-station-14/chemistry/reactions.md create mode 100644 src/en/space-station-14/chemistry/reagents.md create mode 100644 src/en/space-station-14/chemistry/solution-containers.md diff --git a/README.md b/README.md index 4908a5c03..fa4bdc990 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To port a page, you'll need write access to `docs.spacestation14.io` (old wiki.j etc text example ``` `````` -7. Add a redirect for the original wiki.js link to the new `src/SUMMARY.md` link in `book.toml`--follow the example +7. Add a redirect for the original wiki.js link to the new `src/SUMMARY.md` link in `book.toml`--follow the example. You'll need to add `/index.html` after the wiki.js link for it to redirect properly. ## Screenshots diff --git a/book.toml b/book.toml index 7c4c7f846..58f108564 100644 --- a/book.toml +++ b/book.toml @@ -44,6 +44,11 @@ warning-policy = "ignore" # false-positives like hell with absolute links & late # Redirects [output.html.redirect] -"/en/getting-started/how-do-i-code" = "en/general-development/setup/howdoicode.md" -"/en/getting-started/dev-setup" = "en/general-development/setup/setting-up-a-development-environment.md" -"/en/getting-started/git" = "en/general-development/setup/git-for-the-ss14-developer.md" \ No newline at end of file +"/en/getting-started/how-do-i-code/index.html" = "en/general-development/setup/howdoicode.html" +"/en/getting-started/dev-setup/index.html" = "en/general-development/setup/setting-up-a-development-environment.html" +"/en/getting-started/git/index.html" = "en/general-development/setup/git-for-the-ss14-developer.html" +"/en/content/yaml/index.html" = "en/general-development/tips/yaml-crash-course.html" +"/en/getting-started/pr-guideline/index.html" = "en/general-development/codebase-info/pull-request-guidelines.html" +"/en/getting-started/conventions/index.html" = "/en/general-development/codebase-info/conventions.html" +"/en/content/chemistry/index.html" = "/en/space-station-14/chemistry.html" +"/en/content/writing-guidebook-entries/index.html" = "/en/general-development/tips/writing-guidebook-entries.html" \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d54557222..738eca3b4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -92,6 +92,10 @@ Space Station 14 - [Mapping Checklist](en/space-station-14/mapping/mapping-checklist.md) - [Mapping Sins](en/space-station-14/mapping/mapping-sins.md) - [Chemistry](en/space-station-14/chemistry.md) + - [Solution Containers](en/space-station-14/chemistry/solution-containers.md) + - [Reagents](en/space-station-14/chemistry/reagents.md) + - [Metabolism](en/space-station-14/chemistry/metabolism.md) + - [Reactions](en/space-station-14/chemistry/reactions.md) - [Construction](en/space-station-14/construction.md) - [Destructible](en/space-station-14/destructible.md) - [Device Network](en/space-station-14/device-network.md) diff --git a/src/en/assets/images/tips-markup-example.png b/src/en/assets/images/tips-markup-example.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5610ea47345651c082b8072f11fc0d7d8574ea GIT binary patch literal 37309 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�e*!$X_PicN}(-_y}gUyEZj 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 Keep in mind that some older areas of the codebase might not follow these conventions. These should be refactored in the future to follow them. All new code should try to follow these conventions as closely as possible. +{.is-info} + +## File Layout + +1. Start with [using directives](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive) at the top of the file. + +2. All classes should be explicitly namespaced. Use [file-scoped namespaces](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces), e.g. a single `namespace Content.Server.Atmos.EntitySystems;` before any class definitions instead of `namespace Content.Server.Atmos.EntitySystems { /* class here */ }`. + +3. Always put all fields and auto-properties before any methods in a class definition. + +## Comments + +- Comment code at a high level to explain *what* the code is doing, and more importantly, *why* code is doing what it is doing. + +- When documenting classes, structs, methods, properties/fields, and class members, use [XML docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/) + +### Why Not What + +Some folks blindly adhere to "comment the why, not the what" and think that "code should be self-documenting and comments a last resort". Below we present a few examples that we hope will change your mind. + +#### Example 1 + +```C# + float fractionalPressureChange = Atmospherics.R * (outlet.Air.Temperature / outlet.Air.Volume + inlet.Air.Temperature / inlet.Air.Volume); +``` + +All of the variables are named in a self-documenting way (*R* gets a pass because that is the ideal gas constant, and physics conventions existed long before computers, so this is following convention). Obviously, the comment should *not* be: + +```C# + // Take R and multiply it by the ratio of outlet temperature divided by outlet air volume and add it to ... + float fractionalPressureChange = Atmospherics.R * (outlet.Air.Temperature / outlet.Air.Volume + inlet.Air.Temperature / inlet.Air.Volume); +``` + +Because this only explains what the code is literally doing, which you could have gathered from any cusory reading of the code. **However, you still have absolutely no idea what this code is doing and why**, even though the code is self-documenting. + +You don't know where this magic formula came from, what it's trying to accomplish, or even if the formula is correct. Therefore, this needs to be documented: + + +```C# + // We want moles transferred to be proportional to the pressure difference, i.e. + // dn/dt = G*P + + // To solve this we need to write dn in terms of P. Since PV=nRT, dP/dn=RT/V. + // This assumes that the temperature change from transferring dn moles is negligible. + // Since we have P=Pi-Po, then dP/dn = dPi/dn-dPo/dn = R(Ti/Vi - To/Vo): + float dPdn = Atmospherics.R * (outlet.Air.Temperature / outlet.Air.Volume + inlet.Air.Temperature / inlet.Air.Volume); +``` + +#### Example 2 + +```C# + if (HasComp(uid)) + { + return; + } + + // more stuff +``` + +Obviously, this code skips "more stuff" if the entity represented by *uid* already has a MindContainerComponent. This code is as self-documenting as it gets, it literally just returns early if there is a MindContainer. What needs to be documented is *why* this code needs to skip *uid*s that already have a MindContainerComponent: + + +```C# + // Don't let players who drink cognizine be eligible for a ghost takeover + if (HasComp(uid)) +``` + +## Methods + +### Line breaks of parameter/argument lists + +If you're defining a function and the parameter declarations are so long they don't fit on a single line, break them apart so you have **one parameter per line**. Some leeway is granted for closely tied parameter pairs like X/Y coordinates and pointer/length in C APIs. + +Bad: + +```cs +public void CopyTo(ISerializationManager serializationManager, SortedDictionary source, ref SortedDictionary target, + SerializationHookContext hookCtx, ISerializationContext? context = null) +``` + +Good: + +```cs +public void CopyTo( + ISerializationManager serializationManager, + SortedDictionary source, + ref SortedDictionary target, + SerializationHookContext hookCtx, + ISerializationContext? context = null) +``` + +## Strings and Identifiers + +Human-readable text should never be used as an identifier or vice versa. That means no putting human-readable text (result of localization functions) in a dictionary key, comparing with `==`, etc... + +This avoids spaghetti when these inevitably have to be decoupled for various reasons, and avoids inefficiency and bugs from comparing human-readable strings. + +### Invariant comparisons on human-readable strings + +If you're doing something like a filter/search dialog, use `CurrentCulture` comparisons over human-readable strings. Do not use invariant cultures. + +## Properties + +In a property setter, the value of the property should always literally become the `value` given. None of this: + +```cs +public string Name +{ + get => _name; + private set => _name = Loc.GetString(value); +} +``` + +## Constants and CVars +If you have a specific value such as an integer you should generally make it either: +* a constant (const) if it's never meant to be changed +* a CVar if it's meant to be configured + +This is so it is clear to others what it is. This is especially true if the same value is used in multiple places to make the code more maintainable. + +## Prototypes + +### Prototype data-fields +Don't cache prototypes, use prototypeManager to index them when they are needed. You can store them by their ID. When using data-fields that involve prototype ID strings, use custom type serializers. For example, a data-field for a list of prototype IDs should use something like: +```csharp= +[DataField("exampleTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] +public List ExampleTypes = new(); +``` + +### Enums vs Prototypes +The usage of enums for in-game types is *heavily discouraged*. +You should always use prototypes over enums. +Example: In-game tool "kinds" or "types" should use prototypes instead of enums. + +## Resources + +### Sounds +When specifying sound data fields, use `SoundSpecifier`. + + + +
+ C# code example (click to expand) + +```csharp= +[DataField("sound", required: true)] +public SoundSpecifier Sound { get; } = default!; +``` + +
+ +
+ YAML prototype example (click to expand) + +```yml= +# You can specify a specific sound file like this +- type: MyComponent + sound: + path: /Audio/path/to/my/sound.ogg + +# But this works, too! +- type: MyOtherComponent + sound: /Audio/path/to/my/sound.ogg + +# You can only specify a sound collection like this +- type: AnotherComponent + sound: + collection: MySoundCollection + +``` + +
+ +### Sprites and Textures +When specifying sprite or texture data fields, use `SpriteSpecifier`. + +
+ C# code example (click to expand) + +```csharp= +[DataField("icon")] +public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid; +``` + +
+ +
+ YAML prototype example (click to expand) + +```yml= +# You can specify a specific texture file like this +- type: MyComponent + icon: /Textures/path/to/my/texture.png + +# You can specify an rsi sprite like this +- type: MyOtherComponent + icon: + sprite: /Textures/path/to/my/sprite.rsi + state: MySpriteState +``` + +
+ +
+ RSI meta.json (click to expand) + +- The order of fields should be `version -> license -> copyright -> size -> states`. +- JSON should not be minified, and should follow normal JSON quality guideliens (egyptian brackets, etc) + +Example: + +```json +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "GitHub @PJB3005", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "hello", + "flags": {}, + "directions": 4, + "delays": [ + [1, 1, 1], + [2, 3, 4], + [3, 4, 5], + [4, 5, 6] + ] + } + ] +} +``` +
+ +### EntityUid in Logs +When using `EntityUid` in admin logs, use the `IEntityManager.ToPrettyString(EntityUid)` method. + +
+ Admin log with entities example (click to expand) + +```csharp= +// If you're in an entity system... +_adminLogs.Add(LogType.MyLog, LogImpact.Medium, $"{ToPrettyString(uid)} did something!"); + +// If you're not in an entity system... +_adminLogs.Add(LogType.MyLog, LogImpact.Medium, $"{entityManager.ToPrettyString(uid)} did something!"); +``` + +
+ +### Optional Entities +If you need to pass "optional" entities around, you should use a nullable `EntityUid` for this. +Never use `EntityUid.Invalid` to denote the abscence of `EntityUid`, always use `null` and nullability so we have compile-time checks. +e.g. `EntityUid? uid` + +## Components + +### Component data access modifiers +All data in components should be public. + +### Component property setters +You may not have setters with any logic whatsoever in properties. Instead, you should create a setter method in your entity system, and apply the `[Friend(...)]` attribute to the component so only that system can modify it. +Your component may use properties with setter logic for *ViewVariables integration* (until we have a better system for that) + +### Component access restrictions +The `[Access(...)]` attribute allows you to specify which types can read or modify data in your class, while prohibiting every other type from modifying it. + +Components should specify their access restrictions whenever possible, usually only allowing the entity systems that wrap them to modify their data. + +### Shared Component inheritance +If a shared component is inherited by server and client-side counterparts, it should be marked as *abstract*. + +## Entity Systems + +### Game logic +Game logic should *always* go in entity systems, not components. +Components should *only* hold data. + +### Proxy Methods +When possible, try using the `EntitySystem` [proxy methods](https://github.com/space-wizards/RobustToolbox/blob/master/Robust.Shared/GameObjects/EntitySystem.Proxy.cs) instead of using the `EntityManager` property. + +
+ Examples (click to expand) + +```csharp= +// Without proxy methods... +EntityManager.GetComponent(uid).EntityName; + +// With proxy methods +Name(uid); + +// Without proxy methods... +EntityManager.GetComponent(uid).Coordinates; + +// With proxy methods +Transform(uid).Coordinates; +``` + +
+ +### Public API Method Signature +All public Entity System API Methods that deal with entities and game logic should *always* follow a very specific structure. + +All relevant `EntityUid` should come first. +Next, any arguments you want should come afterwards. +And finally, all the components you need from the entity or entities should come last. +The component arguments shall be nullable, and default to `null`. + +The first thing you should do in your method's body should then be calling `Resolve` for the entity UID and components. + +
+ Example (click to expand) + +```csharp= +public void SetCount(EntityUid uid, int count, StackComponent? stack = null) +{ + // This call below will set "stack" to the correct instance if it's null. + // If all components were resolved to an instance or were non-null, it returns true. + if(!Resolve(uid, ref stack)) + return; // If the component wasn't found, this will log an error by default. + + // Logic here! +} +``` + +
+ +The `Resolve` helper performs a few useful checks for you. In `DEBUG`, it checks whether the component reference passed (if not null) is actually owned by the entity specified. + +This helper will also log an error by default if the entity is missing any of the components that you attempted to resolve. +This error logging can be disabled by passing `false` to the helper's `logMissing` argument. You may want to disable the error logging for resolving optional components, `TryX` pattern methods, etc. + +Please note that the `Resolve` helper also has overloads for resolving 2, 3 or even 4 components at once. +If you want to resolve components for multiple entities, or you want to resolve more than 4 components at once for a given entity, you'll need to perform multiple `Resolve` calls. + +### Extension Methods + +Extension methods (those with an explicit `this` for the first argument) should never be used on any classes directly related to simulation--that means `EntityUid`, components, or entity systems. Extension methods on `EntityUid` are used throughout the codebase, however this is bad practice and should be replaced with entity system public methods instead. + +### Dependencies On Other Systems +Inside an entity system, prefer a system dependency instead of resolving the system using the IoCManager. For example, instead of: + +```csharp= +var random = IoCManager.Resolve(); +random.Prob(0.1f); +``` + +Add an entity system dependency: + +```csharp= +[Dependency] private readonly IRobustRandom _random = default!; +_random.Prob(0.1f); +``` + +## Events + +### Method Events vs Entity System Methods +Method Events are events that you raise when you want to perform a certain action. Example: +```csharp= +// This would change the damage on the entity by 10. +RaiseLocalEvent(uid, new ChangeDamageEvent(10)); +``` +On the other hand, Entity System Methods are methods you call on systems to perform an action. +```csharp= +// This would change the damage on the entity by 10. +EntitySystem.Get().ChangeDamage(uid, 10); +``` + +Method Events are *prohibited*, always use Entity System Methods instead. +There's an exception to this, however. + +You may use Method Events as long as they're wrapped by an Entity System Method. +In the example above, this would mean that `DamageableSystem.ChangeDamage()` would internally raise the `ChangeDamageEvent`, which would then by handled by any subscriptors... + +### Event naming +- Always suffix your events with `Event`. +Example: `DamagedEvent`, `AnchorAttemptEvent`... + +- Always name your event handler like this: `OnXEvent` +Example: `OnDamagedEvent`, `OnAnchorAttemptEvent`... + +### Struct by-ref events +Events should always be structs, not classes, and should always be raised by ref. If possible it should also be readonly if applicable. +They should also have the [ByRefEvent] attribute. + +In practice this will look like the following: +```cs + var ev = new MyEvent(); + RaiseLocalEvent(ref ev); +``` + +### C\# Events vs EventBus Events +The EventBus should generally be used over C# events where possible. C# events can leak, especially when used with components which can be created or removed at any time. + +C# events should be used for out-of-simulation events, such as UI events. +Remember to *always* unsubscribe from them, however! + +### Async vs Events +For things such as DoAfter, always use events instead of async. + +Async for any game simulation code should be avoided at all costs, as it's generally virulent, cannot be serialized (in the case of DoAfter, for example), and usually causes icky code. +Events, on the other hand, tie in nicely with the rest of the game's architecture, and although they aren't as convenient to code, they are definitely way more lightweight. + +## UI + +### XAML and C#-defined UIs +You should always use XAML over UIs defined entirely in C# code. +Extending existing C#-defined UIs is fine, but they should be converted eventually. + +## Performance + +### Iterator Methods vs returning collections +Always use [iterator methods](https://docs.microsoft.com/en-us/dotnet/csharp/iterators) over creating a new collection and returning it in your method. + +Keep in mind, however, that iterator methods allocate a lot of memory. +If you need to reduce allocations as much as possible, use struct iterators. + +### Sealed Classes +Your class must be marked as either `abstract`, `static`, `sealed` or `[Virtual]`. This is to avoid accidentally making classes inheritable when the shouldn't be and can improve performance slightly when accessing or invoking virtual members. + +Use `sealed` if the class shouldn't be inherited, `[Virtual]` for the normal C# behavior (it mutes the compiler warning), `static` for classes that don't need to be instantiated, or `abstract` if it's meant for being inherited but not meant to be instantiated by itself. + +### Events over updates +Where possible you should always have your system run code in response to an event rather than updating every tick. Your code may only take up 0.5% of CPU time but when 100 systems do this it's unnecessary. + +### Variable capture +When using [lambdas](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions) or [local functions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions) be sure to **avoid variable captures**. + +If you're adding a method that takes in a [Func delegate](https://docs.microsoft.com/en-us/dotnet/api/system.func-2), be sure to have an overload that **allows the caller to pass in custom data** to it. + +
+ Example of what not to do (click to expand) + +```csharp= +void DoSomething(EntityUid otherEntity) +{ + // This is BAD. It will allocate on the heap a lot. + var predicate = (EntityUid uid) + => uid == otherEntity; + + // This method doesn't allow us to pass custom data, + // so we're forced to do a costly variable capture. + MethodWithPredicate(predicate); +} + +void MethodWithPredicate(Func predicate) +{ + // We do something with the predicate here... +} +``` + +
+ +
+ Example of what to do (click to expand) + +```csharp= +void DoSomething(EntityUid otherEntity) +{ + // This is good and much more performant than the example before. + var predicate = (EntityUid uid, EntityUid otherUid) + => uid == otherUid; + + // Pass our custom data to this method. + MethodWithPredicate(predicate, otherEntity); +} + +// This method allows you to pass custom data into the predicate. +void MethodWithPredicate(Func predicate, TState state) +{ + // We do something with the predicate here, making sure to pass "state" to it... +} +``` + +
+ +## Naming + +### Shared types +Shared types should only be prefixed with `Shared` if and only if there are server and/or client inherited types with the same name. + +Example: +- If `FooComponent` only exists in shared, it doesn't need a prefix. +- If `BarComponent` exists in shared, server and client, the shared type should be prefixed with shared: `SharedBarComponent`. + +## Physics + +### Anchoring + +Always use `TransformComponent` anchoring. +You may use `PhysicsComponent` static body anchoring but *only* if you know what you're doing and you can defend your choice over transform anchoring. + +# YAML Conventions + +- Every component `- type` should be together without any empty newlines separating them +- Separate prototypes with one empty newline. +- `name:` and `description:` fields should never have quotations unless punctuation in the name/description requires the use of them, then you will use ''. For example: +```yaml + name: 'Spessman's Smokes packet' + description: 'A label on the packaging reads, 'Wouldn't a slow death make a change?'' +``` +- Dont specify textures in abstract prototypes/parents. +- You should declare the first prototype block in this order: `type` > `abstract` > `parent` > `id` > `name` > `description` > `components.` +- New components should not have an indent when added to the `components:` section. + This + ```yaml= + components: + - type: Sprite + state: + ``` + Not this + ```yaml= + components: + - type: Sprite + state: + ``` +- When it makes sense, place more generalized/engine components near the top of the components list and more specific components near the bottom of the list. For example, + ```yaml= + components: + - type: Sprite # Engine-specific + - type: Physics + - type: Anchorable # Content, but generalized + - type: Emitter # A component for a specific type of item + `` + +### YAML and data-field naming +`PascalCase` is used for IDs and component names. +Everything else, even prototype type names, uses `camelCase`. +`prefix.Something` should NEVER be used for IDs. + +## Entities + +Please ensure you structure entities with components as follows for easier YAML readability: + +``` + type: entity + parent: Base + id: + name: + abstract: + components: + +``` + +### Entity Prototype suffixes + +Use `suffix` in prototypes, this it's a spawn-menu-only suffix that allows you to distinguish what prototypes are, without modifying the actual prototype name. You can use it like this: +![](https://i.imgur.com/epkPR3Y.png) + +And results in this: +![](https://i.imgur.com/JigMCuu.png) + +# Localization +Every player-facing string ever needs to be localized. + +### Localization ID naming +- Localization IDs are always `kebab-case` and should never contain capital letters. +- Localization IDs should be specific as possible, to avoid clashing with other IDs. + This + ```ftl= + antag-traitor-user-was-traitor-message = ... + ``` + Not this + ```ftl= + traitor-message = ... \ No newline at end of file diff --git a/src/en/general-development/codebase-info/pull-request-guidelines.md b/src/en/general-development/codebase-info/pull-request-guidelines.md index b286b0d8a..9db8234dc 100644 --- a/src/en/general-development/codebase-info/pull-request-guidelines.md +++ b/src/en/general-development/codebase-info/pull-request-guidelines.md @@ -1,3 +1,112 @@ # Pull Request Guidelines -{{#template ../../templates/porting.md}} \ No newline at end of file +Following these guidelines will make contributing much easier, and will help your PRs be merged much faster by making them easy to review. + +- If you're unfamiliar with the Git workflow, please read [our git guide](../setup/git-for-the-ss14-developer.md) and ask as many questions as you need in #howdoicode. +- Do not use the GitHub web editor to make pull requests. You are required to make sure your code compiles and you've tested your changes in-game before making the PR. **PRs created through the Github web editor are liable to be closed at a maintainer's discretion, as they have not been tested locally and cannot meet these requirements.** Repeated submission of PRs made through the web editor may result in a repository ban. +- Please **re-read your code preview on GitHub** before submitting a PR. If it's obvious to a reviewer it includes your last PR (i.e. PR2 includes PR1's changes), or that there are accidental changes, then it should be obvious to you. +- If your PR adds a new feature, you should provide a screenshot or video of it functioning properly ingame. This not only makes reviewing easier, but also makes writing the progress report easier. +- Avoid adding miscellaneous additional changes to a PR, e.g. changing the heat resistance of a pair of gloves alongside your PR adding a new gun. +- Avoid making lots of formatting changes in a file if you change very few lines in it. It makes the review significantly more difficult to parse what actually changed and can generate conflicts for other PRs. +- If you do make refactoring/formatting changes, make them separate commits from actual content/feature/functionality changes so that they are easier to review. +- Large refactors that touch a lot of other systems belong in a separate refactor-only PR with no content changes +- If you move a file to a different folder and/or namespace, put that in its own commit when possible to make it easier to tell what got changed in a file and what was just moved. +- Please have some familiarity with [C# conventions](https://docs.microsoft.com/en-us/dotnet/csharp/) (if working with C#) and [our own conventions](./conventions.md). Try to read how other parts of the codebase are formatted for a general idea. +- Try to split your PR into smaller ones where it makes sense to do so. This makes it significantly easier to read and can lead to faster reviews. It's also usually easier for you, and means you will receive earlier feedback and can avoid spending time making changes that have to be reworked. +- Avoid force-pushing to your branch. This makes all reviews show as 'outdated', even if they have not been addressed yet, which makes it much harder for us. +- When you're addressing reviews, click 'Resolve conversation' on GitHub once your revised code has been pushed. +- If you have questions about reviews that were submitted on your PR (or code questions in general, of course), feel free to ask for clarification on GitHub or Discord in #howdoicode. + +## Reviews + +We get around 700 PRs a month and only have a small number of active maintainers. All maintainers are volunteers and maintainer availability is very variable. Depending on the size and importance of your PR, it could take up to a few weeks before someone gets around to reviewing it. Responding to any changes may also take some time. Please be patient. + +Anyone is welcome to review PRs. Reviews from other contributors can be just as valuable as reviews from maintainers, and often mean that PRs can be merged faster and can help relieve the worload for maintainers. If you are waiting for a review it might be a good idea to find another contributor in a similar position so that you can mutually review each other's PRs. Reading other people's PRs and thinking critically about how you would have written the code can also be a useful learning tool. + +### Asking for reviews +Please do not simply post your PR in our discord channels without context simply to ask for reviews. However, if your PR hasn't been reviewed by anyone and it has been a month, feel free to ping those listed here on GitHub or Discord. These aren't all of our maintainers, but they're currently the most active when it comes to reviews. + +**For content reviews:** +- mirrorcult#9528, @mirrorcult on GitHub +- metalgearsloth#7697, @metalgearsloth +- ElectroSR#9529, @ElectroJr +- /tmp/moony#0029, @moonheart08 + +**For engine reviews:** +- metalgearsloth#7697, @metalgearsloth +- PJB#3005, @PJB3005 +- ElectroSR#9529, @ElectroJr +- mirrorcult#9528, @mirrorcult + +## Adding screenshots/videos to your PR +PRs which make ingame changes (adding clothing, items, new features, etc) are required to have media attached that showcase the changes or the PR will not be merged, in accordance with our PR guidelines. Small fixes/refactors are exempt. If you include media in your pull request, we may potentially use it in the SS14 progress reports, with clear credit given. + +Use screenshot software like Window's built in snipping tool, ShareX, Lightshot, or recording software like ShareX (gif), ScreenToGif, or OBS (cross platform). +If you're unsure whether your PR will require media, ask a maintainer. + +## Changelog +Changelog entries help make players aware of new features or changes to existing features. + +### Changelog Template +The Github PR template contains the following changelog that you can use to format your changelog entry so that it is automatically updated in-game: + +``` +:cl: +- add: Added fun! +- remove: Removed fun! +- tweak: Changed fun! +- fix: Fixed fun! +``` + +By default, changes are credited to your Github username. If you would like your name to appear differently in-game, add a string on the same line as the `:cl:` with the name that you would like to use. + +Each entry is either an `add`, `remove`, `tweak`, or `fix`. There can be multiple entires in each category. These set the change log icon and do not show up in the change log text. + +Maintainers may, at their discretion, add, modify, or remove a change log entry that you suggest. + +### Writing An Effective Changelog +The Changelog is for *players* to be aware of new features and changes that could affect how they play the game. It is *not* designed for maintainers, admins, or server operators (these should be in the PR description). + +When writing your changelog entries, please follow these guidelines: + +1. **Log entries should be complete, gramatically-correct sentences.** They should begin with a capital letter and end in a period. + + - Not so good: "fixed reflected projectiles dealing stamina damage" This sentence does not begin with a capital letter, does not end with a period. + + - Not so good: "Wide attacks no longer cost stamina, deal weapon damage." Dangling clause after the comma. + + - Not so good: "There is now more structures that can be made out of web" Missing a period at the end of the sentence, and since "more structures" is plural, the correct verb conjugation is "are". But this entire sentence could be revised using the active voice, e.g. "More structures can now be made out of web" + + - Not so good: "A craft for cloth consisting of silk." This is not a complete sentence. + +2. **Log only changes with significant in-game impact.** This may include new features or changes or tweaks to existing features that affect balance. Minor changes to object apperances and descriptions typically do *not* affect how you would play the game. Changelog entries for major sprite updates are appropriate. + + - Good: "The R&D server can be deconstructed. Be warned: this resets all unlocked technology, points, and the current discipline." Without the Changelog entry, players may not know that R&D servers can now be deconstructed. It also provides them enough warning about losing technology so that they don't accidentally get surprised. + + - Not so good: "Adjusted pickaxe inhand sprites and added sprites for wielded pickaxes." You would see the changes when you decided to wield a pickaxe. Knowing that the pickaxes look different wouldn't change your traitor strategy. + + - Not so good: "Changed the plating sprite to be a little less blue." Same reason as above. + + - Not so good: "Changed sprites for some medipens." + +3. **Use the present, active voice.** + + - Not so good: "HAMTR mech has been added." + - Good: "The roboticist can now build the HAMTR mech." Revising this sentence in the active voice resulted in a more engaging sentence and included more relevant detail. + + - Not so good: "Added candy bowls for waiting lines." Who is doing the adding? + - Good: "Candy bowls can now be found near waiting lines." The subject is now "candy bowls". Each sentence has a subject and a verb. + +4. **Be concise.** Players should be able to understand the jist of the changes by skimming the Changelog. If they need more information, they can consult the guidebook. Avoid spamming multiple related changes across several different lines. If several security weapons were rebalanced, just say that to make players aware. + + - Not so good: "Central has distributed a new subversion of the standard particle accelerators. Nothing exciting, but they have brought back the old wiring layout. Apparently some of the newer versions were having firmware issues and it was more reliable. Keep on eye on it while it''s running will you? We don''t want an intern disabling the safeties and frying their face off." Do you understand what changed? Even the author thinks the change is "nothing exciting." + +5. **Avoid technical jargon.** + + - Not so good: "Fixed microwaves defaulting to 5 seconds when the ui said instant." What is a *ui*? It could be improved by using the accepted abbreviation, "UI". Aside: Is instant really instant now, or does it just default to 5 seconds? + +6. **Set the appropriate tone.** + + - Not so good: "Can you believe it? Arachnid re-rework just dropped! Check the PR for more details" + + - Not so good: "Arachnids have new sprites for being creampied." *crampied* has another, unfortunate meaning that undermines the professional tone of a Changelog entry. \ No newline at end of file diff --git a/src/en/general-development/tips/writing-guidebook-entries.md b/src/en/general-development/tips/writing-guidebook-entries.md index d84c5ada7..c6be06cea 100644 --- a/src/en/general-development/tips/writing-guidebook-entries.md +++ b/src/en/general-development/tips/writing-guidebook-entries.md @@ -1,3 +1,185 @@ # Writing Guidebook Entries -{{#template ../../templates/porting.md}} \ No newline at end of file +The guidebook is a powerful tool for communicating more obscure in-game information to players without forcing them to travel to an external wiki. Moving forward, most information should be moved from the wiki to the guidebook in some form. + +This guide explains how to write a guidebook entry and set it up in game as well as provides helpful tips for creating good quality entries. Afterwards, if you want to learn how to make a pull request for your new entry, check out [Git for the SS14 Developer](../setup/git-for-the-ss14-developer.md). + +Guide entries are made of two parts, the `.xml` file for the contents of the guide itself and the YAML prototype which defines metadata about the guide itself. We will first go over the file which makes up the contents of the guide. + +## Writing Guides + +All guide entries are stored in the `/Resources/ServerInfo/Guidebook/` path in the main repository. The file structure of the guides themselves should roughly correspond to the structure of the entries themselves, though this is not required. The most important aspect is just making sure the files are roughly organized. + +The entries themselves are essentially plain text files with some additional tags that are used for styling. The only part of an entry that is required is the `` tag. + +Thus, the simplest entry you can make looks like this: +```xml + + +``` + +Any text written in the bounds of the tag will be displayed plainly on the guide. But, if you write a guide with only plain text, you will write an incredibly boring guide that will make my eyes glaze over and make me keel over and die. + +To alleviate my oncoming death, consider using the (small) variety of markdown tags that are supported: +- `#` creates a title +- `##` creates a heading +- `-` creates a list entry +- `[color=hex][/color]` colors the text inside the tags with the specified hex color. + +### Example +Here is an example guidebook entry, `magboots.xml` +```xml + +# Magboots: The man, the myth, the legend +We know magboots are amazing, but just how amazing? The answer is quite clear: [color=#ff0000]extremely.[/color] + +## Behold + + + + +## Additionally +Magboots are also cool for the following reasons: +- They come in multiple colors. +- They make me feel fuzzy and warm. +- They make sure I don't fly off into space. + +You cannot beat the hustle of the magboots. + +``` + +And this is how it appears in game: +![markup-example.png](../../assets/images/tips-markup-example.png) + +## Custom Guidebook Controls +These are custom controls that can be used to add unique visuals or behavior to a guide. Some are more useful than others, but consider using some or all of them to add more visual interest and more specific information to a guide. + +### Box +The `` tag can be used to center part of a guide. This isn't very useful for text, but can be used in conjunction with other tags to create a more visually appealing page. + +It has the following properties: +- `Orientation`: The orientation the items inside the box will be laid out in. Either "Vertical" or "Horizontal" +- `HorizontalAlignment`: How the box will be placed horizontally on the page. Can be "Stretch", "Left", "Center", or "Right" +- `VerticalAlignment`: How the box will be placed vertically on the page. Can be "Stretch", "Top", "Center", or "Bottom" + +### CommandButton +The `` tag allows you to embed a button in a guidebook. Clicking the button will execute a command. This may seem useless, but there are many useful client-side commands, such as ones that open menus. + +Note, it's important to not use these for admin-only commands, as the average players is likely to be confused by this. + +It has the following properties: +- `Text`: The text that will be displayed inside the button. Supports locale strings +- `Command`: The full command, including arguments. This is what's run when the button is pressed. + +### GuideEntityEmbed +The `` tag allows you to embed in-game prototypes into a guide. Depending on how it's configured, you can even interact with them and examine them. + +It has the following properties: +- `Entity`: The prototype ID for the entity that will be displayed. +- `Caption`: A caption displayed underneath the entity. Defaults to the entity's name. +- `Scale`: A value for scaling the size of the embedded entity. `2` will result in twice the size and `0.5` will result in half the size. +- `Interactive`: Whether or not the embedded entity can be interacted with. +- `Rotation`: What angle the entity is rotated at. Useful if you want to display a particular fashionable side sprite. Defaults to facing south. +- `Init`: Whether or not the embedded entity is map-initialized. Defaults to true. + +> **Emo's helpful tips:** +> If you are not seeing specific examine text or interactions on your embedded entity, it's likely because your logic is on the server. +> +> All entities in the guidebook are client-side only, meaning that if you want certain examine text to be visible or have a certain appearance, it must be defined in either shared or client code. + +### GuideReagentEmbed +The `` tag creates a small descriptive box about a given reagent. It includes the name, description, physical description, recipes (if it exists), and effects (if any). These can be used if you want to provide common chems and their recipes in relevant places. For example, placing the embed for Space Cleaner in the janitor guide. + +It has the following properties: +- `Reagent`: The prototype ID for the reagent the embed will use. + +### GuideReagentGroupEmbed +The `` tag is quite similar to the proceeding ``. The difference between them is that this one is meant to show an entire category of reagents, instead of a single one. This allows a guide to always contain all of the reagents of a given category without having to be regularly updated. + +An important thing to note is that, while the list it generates is alphabetically sorted, it is also quite long. This means that it's important to put it at the bottom of your guide as to not obscure information. + +It has the following properties: +- `Group`: The group that all the reagents will have. This should correspond to a value on the `group` field of `ReagentPrototype`. + +## Creating Entries + +Now that you've created a file with all of your content, you need to make an entry for it to display in game. Entries are prototypes found in the `/Resources/Prototypes/Guidebook/` folder. Like before, try to group guides and their children together so that they can be easily found. + +Each entry consists of a single prototype with a few different variables you can set. Here is an example prototype for our entry we just wrote: +```yaml +- type: guideEntry + id: Magboots + name: guide-entry-magboots + text: "/ServerInfo/Guidebook/magboots.xml" + priority: 10 + children: + - Radio +``` + +To make sure that it appears in the guidebook, you'll need to also add it as the child to another entry. + +> **Emo's helpful tips:** +> Now that you have written your entry created the prototype, you can now open up the guidebook and view it. +> +> Guide entries support hot-reloading, which means that you can modify the file while your local server is running, close the guidebook, reopen it, and see your changes. + +All of these fields are pretty basic, so let's step through them one by one. + +### id +This is simply a unique prototype Id. Just make sure it roughly corresponds to your guide's name. + +### name +This is a the name which appears in the file view sidebar of the guidebook. Importantly, it's a locale string, which is used for translation. It's also the only part of a guide entry that needs to have a locale string. + +You can learn more about localization [here](../../tutorials/super-simple-14/fluent-and-localization.md). + +### text +This is just a file path to the entry, starting from the `/Resources/` directory. + +### priority +This is a numeric value for sorting top-level guides. Higher values will appear first. + +This is not used if the guides are the children of another guide; in that scenario, they are sorted by the order listed in the `children` field. + +### children +This is a list of all other guide entries that appear below this one in the guide sidebar. The items in this list must correspond to the `id`s of other guide entries. + +## In-Game Integration + +A helpful way to make guidebook entries more visible to players is by utilizing the `GuideHelpComponent`. When entities with this component are examined, a small question mark box will appear in the bottom right of the examine window and, when clicked, open up the entry specified on the component. + +This is extremely helpful for new players and helps people quickly navigate to relevant guides. + +Simply add the component onto relevant prototypes and add the relevant guide ids to the `guides` datafield on the component. + +Here's an example: +```yml +- type: entity + id: BaseStockPart + name: stock part + parent: BaseItem + description: What? + abstract: true + components: + - type: Sprite + netsync: false + sprite: Objects/Misc/stock_parts.rsi + - type: Item + size: 1 + #this is the part you add + - type: GuideHelp + guides: + - MachineUpgrading #this is the guide that is opened +``` + +## Best Practices + +Here are some general tips for writing good guides: +- Keep titles clear and concise. Players don't want to search around for what they need. +- Keep entries short. You can always add child entries if you want to elaborate more on the topic. +- Use boxes, embedded entities, and text colors to give entries visual interest. + - A commonly used color for emphasis is `#a4885c`. +- Refrain from including specific advice and "meta" strategies. The guide should be an impartial source of information. +- Articles should be written in a neutral tone. +- Encourage interacting with the guide. + - If your embedded entities support it, suggesting that the player examines the entity to learn more is a helpful way of communicating information and teaching players. diff --git a/src/en/general-development/tips/yaml-crash-course.md b/src/en/general-development/tips/yaml-crash-course.md index 14a1ef347..dd055bf76 100644 --- a/src/en/general-development/tips/yaml-crash-course.md +++ b/src/en/general-development/tips/yaml-crash-course.md @@ -1,3 +1,176 @@ # YAML Crash Course -{{#template ../../templates/porting.md}} \ No newline at end of file +Because apparently there aren't any good ones. + +SS14 uses YAML for prototype definitions. It's like JSON but actually supposed to be written by marginally-intelligent carbon-based lifeforms like you and me. + +## Comments + +Alright first of all, you can make a single line comment by putting a `#` somewhere, everything after that is flat ignored. Example: + +```yml +foo: bar # Look a comment. +``` + +## Data Types + +For our purposes, YAML has like 3 data types: strings, lists and dictionaries. If you know enough about YAML to know that this isn't true, then scroll down to the bottom for an explanation. + +### String + +Defining a string is easy, you just put it there. + +```yml +hello +``` + +Well done you're a YAML master you defined a string with the contents `hello`. + +In a lot of cases you want to use special characters that YAML might pick up such as `:`. In that case you should wrap your string in quotes: + +```yml +"hello: hi" +``` + +Still one string. + +Double quotes (`"`) allow escape sequences such as our friend `\n` (newline), single quotes (`'`) do not. + +### List + +Defining a list is done by just putting a `-` in front of something else like a string: + +```yml +- A +- B +- C +``` + +That defines a list with 3 entries: `A`, `B` and `C`. Wow! +The values are just regular strings and you can pull whatever shenanigans you want: quotes or whatever. + +> **Ygg's Terrible tips:** +> You can also define YAML inline. Instead of above you can also write: +> ```yml +> [A, B, C] +> ``` +> **Inline YAML is by convention discouraged**, especially for defining components list in entity. It's usually used to denote a simple single item list e.g. +> ```yaml +> x: [ DoAct ] +> ``` + + +### Dictionary + +And finally we have dictionaries, which is just a key/value mapping. So you can do `A` is equal to `B` and `C` equal to `D`. +Just use colons (`:`) to define one like so: + +```yml +A: B +C: D +``` + +Woosh. +The values here (both key and value!) are ALSO regular strings, so you can do this too: + +```yml +"A": "B" +"C": "D" +``` + +The marvels of modern technology. + +> **Ygg's Terrible tips:** +> Much like lists, dictionaries can also be defined inline. +> ```yml +> { A: B, C: D} +> ``` +> If you're going: "Wait a minute, inline YAML is just JSON." You're correct. YAML is superset of JSON, and you can actually just parse JSON files as YAML directly if you wanted. + +## Nesting Things + +Hey it turns out that instead of strings, you can use dictionaries and lists in place of things too. + +It gets *kinda* complicated here but if you just do what's sane formatting wise you're fine. + +When you have a list, you can put a dictionary in an entry by indenting it. So you can do the following: + +```yml +# Like this, first key on the same line. +- A: B + X: "Y" +``` + +Really it just makes sense. + +Likewise you can put a list inside a dictionary, but in this case the list *has* to start on the next line: + +```yml +A: +- "X" +- "Y" +- "Z" + +B: +- "U" +- "V" +- "W" +``` + +Easy. + +And yay you can mix and match: + +```admonish warning +Don't expect these prototype examples to be something up-to-date for SS14. It's just here to show syntax. +``` + +```yml + +# First, this entire thing is stored in a massive list. That's why there's a -. +# We're looking at one entry, a dictionary. +- type: entity # Simple key/value pairs. + id: SMES + name: SMES + description: Stores power in its super-magnetic cells + components: + # AHA! A list in a key. Wow! + # This list ALSO stores dictionaries. + - type: Sprite + sprite: Buildings/smes.rsi + scale: 2, 2 + layers: + - state: smes + - state: smes-display + shader: unshaded + # Input lights. + - shader: unshaded + state: smes-oc0 + # Charge meter. + - visible: false + shader: unshaded + state: smes-og1 + # Output lights. + - shader: unshaded + state: smes-op0 + - type: Icon + sprite: Buildings/smes.rsi + state: smes +``` + +## Notes + +YAML actually has a lot more data types. YAML is also a mess. There's like 10+ ways to define strings. The entire spec is 100+ pages long and overengineered. + +> **Ygg's Terrible tips:** +> Speaking of data types: +> ```yml +> behaviors: +> - !type: HeartBehavior {} +> ``` +> What does the `!type` map to? +> A class in SpaceStation14 and/or RobustToolbox. +> In C#. +> This of course trips up any YAML validator that tries to find the YAML tag schema for it. Be careful when auto-formatting `.yml` Resources. + +SS14 does not use direct object deserialization, and `YamlDotNet` (the library we use to parse YAML) is nice enough to treat scalars as strings only. It does not try to parse strings as numbers or whatever when using the "YAML to LINQ" API (What I like to call "the sane one that isn't completely useless for practical use"). Parsing of numbers is handled by our own C# code on the spot, so if the code expects a boolean it'll treat `true` and `false` correctly, if it expects a string it'll just see it as the string. diff --git a/src/en/space-station-14/chemistry/metabolism.md b/src/en/space-station-14/chemistry/metabolism.md new file mode 100644 index 000000000..2ba30d924 --- /dev/null +++ b/src/en/space-station-14/chemistry/metabolism.md @@ -0,0 +1,46 @@ +# Metabolism + +Metabolism in SS14 is very different to metabolism in SS13. In SS13, reagents simply had a proc `on_mob_life` that was called to determine what should happen. Our system is much more modular and allows for things like different organs metabolizing different reagents, and species-specific metabolisms, as well as overdoses or underdoses without any hassle. + +## Organs & Metabolizers + +In SS14, organs are entities with a `MechanismComponent` (mechanism just being a catch-all term for 'organ'.) Organs also have other components that give them decoupled behavior; `StomachComponent`, `CirculatorComponent`, `RespiratorComponent`, and so on. However, the important one here is `MetabolizerComponent.` + +Metabolizers (organs with that component) define a list of 'metabolism groups' that they metabolize. 'Metabolism groups' are things like 'Narcotic', or 'Food', or 'Medicine'. This allows us to define how organs metabolize different chemicals. + +This is on `OrganHumanStomach`. Meaning, the stomach processes any reagents with a `Food` metabolism, or a `Drink` metabolism: + +```yml + - type: Metabolizer + # mm yummy + maxReagents: 3 + metabolizerTypes: [Human] + groups: + - id: Food + - id: Drink +``` + +Metabolizers can only process so many reagents at once, to avoid the common situation in SS13 where stacking a ton of poisons on one entity would damage it significantly more than just one concentrated poison. + +Metabolism rate is defined on the organ. Generally, its once per second, and chems are taken from the bloodstream. + +## Species + +Defining how different species react to different chemicals is quite easy. Metabolizers are tagged with 'organ types', which is essentially a species marker, but localized to the organ (so if you swap out your heart for a gorilla heart, you'll metabolize medicines/poisons/etc in the same way a gorilla does). Then, you can use the `OrganType` condition to check. Here's an example in the codebase, of theobromine (a chemical found in chocolate): + +```yml + metabolisms: + Poison: + effects: + - !type:HealthChange + conditions: + - !type:ReagentThreshold + min: 1 + - !type:OrganType + type: Animal # Applying damage to the mobs with lower metabolism capabilities + damage: + types: + Poison: 4 +``` + +As the comment suggests, to humans this chemical does nothing, but to animals (things with animal organs) it can be deadly. \ No newline at end of file diff --git a/src/en/space-station-14/chemistry/reactions.md b/src/en/space-station-14/chemistry/reactions.md new file mode 100644 index 000000000..1a6e03587 --- /dev/null +++ b/src/en/space-station-14/chemistry/reactions.md @@ -0,0 +1,46 @@ +# Reactions + +Reaction prototypes define recipes with ratios of reagents required to cause a chemical reaction. Below are the reaction yaml config values: + +`type`: The prototype type. Should always be `reaction`. +`id`: Unique id used by the game to identify the reaction. In `PasacalCase` +`reactants`: A list of reactants required for the reaction to occur. Each reactant specifies it’s ratio and if it’s a catalyst. See the example below for more info. +`products`: A list of reagents created as a result of the reaction. They will be add to the solution container the reaction occurs in if there is room. +`effects`: A list of effects and their properties. Each effect corresponds to a C# class that implements IReactionEffect. See the example below for more info. + +```yaml +- type: reaction + id: PotassiumExplosion + reactants: + Water: + amount: 1 + Potassium: + amount: 1 + effects: # Reaction effects + - !type:ExplosionReactionEffect + # Ranges used when 1 potassium + 1 water react (A unit reaction) + devastationRange: 0.05 + heavyImpactRange: 0.1 + lightImpactRange: 0.15 + flashRange: 0.2 + scaled: true # Scaled proportionally to amount of potassium and water + maxScale: 30 # Explosion strength stops scaling at 30 potassium + 30 water + +``` + +Here’s another example. This one has a catalyst, and some chemical products, but no reaction effect. Note that reactants are only catalysts if directly specified, and will be consumed otherwise. + +```yaml +- type: reaction + id: PolytrinicAcid + reactants: + SulfuricAcid: + amount: 1 + Chlorine: + amount: 1 + catalyst: True # False if not specified + Potassium: + amount: 1 + products: + PolytrinicAcid: 3 +``` \ No newline at end of file diff --git a/src/en/space-station-14/chemistry/reagents.md b/src/en/space-station-14/chemistry/reagents.md new file mode 100644 index 000000000..683f906c3 --- /dev/null +++ b/src/en/space-station-14/chemistry/reagents.md @@ -0,0 +1,66 @@ +# Reagents + +Reagents are substances which comprise solution and which can react to create new Reagents. + +Their definition consists of: +`type`: The component name. Should always be `reagent`. +`id`: Unique id for this reagent. Used in reactions and other locations to identify the reagents. For example `Iodine` or `WeldingFuel`. They are `PascalCase`. +`name`: Id with spaces for displaying in tooltips. E.g. `iodine` and `welding fuel`. +`parent`: What reagent to inherit properties from. Used for things like BaseDrink. +`desc`: A human friendly description of the chemical. +`color`: Hex color of the reagent. Used for mixing and chemical display in containers. +`spritePath`: A sprite specifying the icon used for the reagent, starting from `Textures/Objects/Consumable/Drinks`. +`physicalDesc`: Vague desscription potentially useful when identifying it. +`boilingPoint`: Temperature in Celsius, at which reagent goes from liquid into gas. +`meltingPoint`: Temperature in Celsius, at which reagent goes from solid into liquid. +`metabolisms`: A dict of MetabolismGroup->List\ to apply when the reagent is consumed. + +## Example of reagent definition + +```yaml +- type: reagent + id: Lemonade + name: lemonade + parent: BaseDrink + desc: Drink using lemon juice, water, and a sweetener such as cane sugar or honey. + physicalDesc: tart + color: "#FFFF00" + spritePath: lemonadeglass.rsi + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 +``` + +For more info see: [SS14 Content: Reagent folder](https://github.com/space-wizards/space-station-14/tree/master/Resources/Prototypes/Reagents) or [SS14 Content:ReagentPrototype.cs](https://github.com/space-wizards/space-station-14/blob/ca50a5f9934a399826306659f298f0098251e4eb/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs) + +## Reagent Effects & Conditions + +`ReagentEffect`s are described in C#. For example, `SatiateThirst` and `HealthChange`. This allows you to easily reuse code that has similar effects. Many things use reagent effects--metabolisms, plant metabolisms, and reagent entity reactions (touch/injection-based effects). `ReagentEffect`s also have a `prob` field, which is the chance that the effect will run from 0 to 1. The reason these are so generalized is because many things that need the data can use them. + +`ReagentEffectCondition`s are attached to each effect in the metabolizer list. By default, there are none; so they're always ran. However, you can easily add custom behavior to determine when an effect will actually occur. For instance, `ReagentThreshold` allows you to very easily define overdose behavior. Here's Methamphetamine: + +```yml + metabolisms: + Poison: + effects: + - !type:HealthChange + damage: + types: + Poison: 2.5 + - !type:HealthChange + conditions: + - !type:ReagentThreshold + min: 10 + damage: + types: + Poison: 4 # this is added to the base damage of the meth. + Narcotic: + effects: + - !type:MovespeedModifier + walkSpeedModifier: 1.3 + sprintSpeedModifier: 1.3 +``` + +This means that the overdose behavior HealthChange only occurs when there is at minimum 10u in your system. It has two different groups, 'Poison' and 'Narcotic'. These are both metabolized by the heart, generally. \ No newline at end of file diff --git a/src/en/space-station-14/chemistry/solution-containers.md b/src/en/space-station-14/chemistry/solution-containers.md new file mode 100644 index 000000000..1f89c7846 --- /dev/null +++ b/src/en/space-station-14/chemistry/solution-containers.md @@ -0,0 +1,64 @@ +# Solution Containers + +To make an entity a solution container, give add a `SolutionContainerManager`. For example here is an empty stomach solution: +```yaml + - type: SolutionContainerManager + solutions: + stomach: + maxVol: 200 +``` + +and a full drink solution: + +```yaml + - type: SolutionContainerManager + solutions: + drink: + maxVol: 20 + reagents: + - ReagentId: Cola + Quantity: 20 +``` + +`type`: The component type. Should always be `SolutionContainerManager`. +`solutions`: A map of solution name and the `Solution` described below. + +Solutions have several fields, all of them optional. +`maxVol`: The maximum volume of solution it can hold. Once reagent Quantity sum reaches `maxVol`, no more reagents can be added. It's full. +`reagents`: List of `Reagent`s it should hold by default. Each `Reagent` has a string `ReagentId` and `Quantity` which is a FixedPoint2 (basically a float limited to two decimal places). + +## Capabilities and specifying target solution + +With introduction of `SolutionContainerManager` there is no longer a simple 1:1 mapping of entity and list of reagents. +Each `SolutionContainerManager` can contain any number of solutions. To solve this problem instead of Capabilities flag, a set of Capability-like components are introduced. Each such capability has a a string `solution` that tells `SolutionContainerManager` which solution it targets. + +Here is a list of them: +- `DrainableSolutionComponent` for solutions that can be easily removed through any reagent container. E.g. draining water from a water tank +- `DrawableSolutionComponent` for solutions that can be drawn with syringes. E.g. humans or syringe bottles with rubber caps. +- `ExaminableSolutionComponent` for solutions that can be examined by hand. +- `FitsInDispenserComponent` notes that the component fits in dispenser and tells ReagentDispenser/ChemMaster which solution to target. +- `InjectableSolutionComponent` for solutions that can be injected into with syringes. +- `RefillableSolutionComponent` for solutions that can be added easily. + +Other components like `DrinkComponent` or `FoodComponent` may have special predefined solutions they target and expect to see. For example a `DrinkComponent` will try to create `drink` solution if there isn't an easily accessed `DrainableSolutionComponent` already available. + +## Example of Solution +Here is a full example of an entity with some Solutions: + +```yaml +- type: entity + id: DrinkColaCan + name: space cola + description: A refreshing beverage. + components: + - type: Sprite + sprite: Objects/Consumable/Drinks/cola.rsi + - type: SolutionContainerManager + solutions: + drink: + reagents: + - ReagentId: Cola + Quantity: 20 + maxVol: 20 + - type: Drink +```