From a1bcaf58973d1b15ced827ee50bae724339d1ec0 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 30 May 2024 14:39:07 +0300 Subject: [PATCH 01/84] Localize tips (#28285) * localize tip * localizzedDataset --- Content.Server/Tips/TipsSystem.cs | 4 +- Resources/Locale/en-US/tips.ftl | 134 ++++++++++++++++++++++++ Resources/Prototypes/Datasets/tips.yml | 138 +------------------------ 3 files changed, 139 insertions(+), 137 deletions(-) create mode 100644 Resources/Locale/en-US/tips.ftl diff --git a/Content.Server/Tips/TipsSystem.cs b/Content.Server/Tips/TipsSystem.cs index 168f11de8f..2f7edd45d6 100644 --- a/Content.Server/Tips/TipsSystem.cs +++ b/Content.Server/Tips/TipsSystem.cs @@ -185,11 +185,11 @@ public sealed class TipsSystem : EntitySystem private void AnnounceRandomTip() { - if (!_prototype.TryIndex(_tipsDataset, out var tips)) + if (!_prototype.TryIndex(_tipsDataset, out var tips)) return; var tip = _random.Pick(tips.Values); - var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip)); + var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", Loc.GetString(tip))); if (_random.Prob(_tipTippyChance)) { diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl new file mode 100644 index 0000000000..52912d1ba4 --- /dev/null +++ b/Resources/Locale/en-US/tips.ftl @@ -0,0 +1,134 @@ +tips-dataset-1 = If you're on fire, you can click the alert on the right of your screen to stop, drop, and roll. +tips-dataset-2 = You can view and edit all keybindings used in-game at any time through the Options menu. +tips-dataset-3 = You can access the in-game guidebook through the escape menu, or by pressing Numpad 0 by default. +tips-dataset-4 = Some entities ingame have guidebook entries associated with them, which you can view by examining the entity and clicking the question mark icon. +tips-dataset-5 = Artifacts have the ability to gain permanent effects for some triggered nodes, including becoming an intercom or an extremely efficient generator. +tips-dataset-6 = You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway. +tips-dataset-7 = Some plants, such as galaxy thistle, can be ground up into extremely useful and potent medicines. +tips-dataset-8 = Mopping up puddles and draining them into other containers conserves the reagents found in the puddle. +tips-dataset-9 = Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood. +tips-dataset-10 = Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it. +tips-dataset-11 = Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants. +tips-dataset-12 = Fire extinguishers can be loaded with any reagent in the game. +tips-dataset-13 = Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam. +tips-dataset-14 = Remember to touch grass in between playing Space Station 14 every once in a while. +tips-dataset-15 = You can use the Activate in World keybind, E by default, to interact with objects while your hands are full, or without picking them up. +tips-dataset-16 = Common sense goes a long way to avoiding conflict. +tips-dataset-17 = Every other player in game is a human being as well. +tips-dataset-18 = When running the Singularity, make sure to check on it periodically. If sabotaged, it could put the entire station at risk. +tips-dataset-19 = If the Singularity is up, make sure to refuel the radiation collectors once in a while. +tips-dataset-20 = Chemicals don't react while inside the ChemMaster's buffer. +tips-dataset-21 = Don't anger the bartender by throwing their glasses! Politely place them on the table by tapping Q. +tips-dataset-22 = You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements--or even coming to a complete stop. +tips-dataset-23 = Dexalin, Dexalin Plus, and Epinephrine will all purge heartbreaker toxin from your bloodstream while metabolizing. +tips-dataset-24 = Every crewmember comes with an emergency medipen in their survival box containing epinephrine and tranexamic acid. +tips-dataset-25 = The AME is a high-priority target and is easily sabotaged. Make sure to set up the Singularity or Solars so that you don't run out of power if it blows. +tips-dataset-26 = During a teslaloose, make sure to get rid of all electronic items so you don't become a target. +tips-dataset-27 = You can add labels to any item, including food or pill canisters, using a hand labeller. +tips-dataset-28 = Riot armor is significantly more powerful against opponents that aren't using guns compared to regular armor. +tips-dataset-29 = As a ghost, you can use the Verb Menu to orbit around and follow any entity in game automatically. +tips-dataset-30 = As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others. +tips-dataset-31 = As a Traitor, the syndicate encryption key can be used to communicate through a secure channel with any other traitors who have purchased it. +tips-dataset-32 = As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways - security can compromise syndicate communications as well! +tips-dataset-33 = As a Traitor, the syndicate toolbox is extremely versatile. For only 2 telecrystals, you can get a full set of tools to help you in an emergency, insulated combat gloves and a syndicate gas mask. +tips-dataset-34 = As a Traitor, never underestimate the web vest. It may not provide space protection, but its cheap cost and robust protection makes it handy for protecting against trigger-happy foes. +tips-dataset-35 = As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed. +tips-dataset-36 = As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you. +tips-dataset-37 = As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach. +tips-dataset-38 = As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it. +tips-dataset-39 = As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction. +tips-dataset-40 = As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient! +tips-dataset-41 = As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire. +tips-dataset-42 = As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan. +tips-dataset-43 = As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger! +tips-dataset-44 = As an antagonist, be mindful of the power of destroying telecommunications. It'll be a lot harder for people to call you out if they can't do so effectively! +tips-dataset-45 = You can examine your headset to see which radio channels you have available and how to speak in them. +tips-dataset-46 = As a Salvage Specialist, always carry a GPS on you and take note of the station's coordinates in case your salvage is lost to space. +tips-dataset-47 = As a Salvage Specialist, you can use your proto-kinetic accelerator to move yourself in space when in a pinch. Just be wary that it isn't very effective. +tips-dataset-48 = As a Salvage Specialist, never forget to mine ore! Ore can be sold to cargo for a pretty penny, be used for construction, and also be used by Scientists for fancy technology. +tips-dataset-49 = As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently! +tips-dataset-50 = As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator. +tips-dataset-52 = As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials. +tips-dataset-53 = As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner. +tips-dataset-54 = As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to. +tips-dataset-55 = As a Cargo Technician, if you have a surplus of cash try gambling! Sometimes you gain more money than you begin with. +tips-dataset-56 = As a Cargo Technician, remember that you can order guns in an emergency! The extra firepower can often be the difference between you and your fellow crewmembers living or dying. +tips-dataset-57 = As the Bartender, you can use a circular saw on your shotgun to make it easier to store. +tips-dataset-58 = As the Bartender, try experimenting with unique drinks. Have you tried to make demon's blood yet? +tips-dataset-59 = As a Botanist, you can mutate and crossbreed plants together to create more potent produce that also has higher yields. +tips-dataset-60 = As the Clown, spice your gimmicks up! Nobody likes a one-trick pony. +tips-dataset-61 = As the Clown, if you lose your banana peels and soap, you can still slip people with your PDA! Honk! +tips-dataset-62 = As the Chef, your knife can act as a weapon in an emergency. +tips-dataset-63 = As the Chef, you can sneak liquids into your foods. As a traitor, putting a little bit of amatoxin or other poison can greatly annoy the crew! +tips-dataset-64 = As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor. +tips-dataset-65 = As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime. +tips-dataset-66 = As a Security Officer, communicate and coordinate with your fellow officers using the security radio channel to avoid confusion. +tips-dataset-67 = As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time! +tips-dataset-68 = As a Detective, you can chase criminals more effectively by using fingerprint fiber data and DNA obtained from forensic scans of objects the perpetrator likely interacted with. +tips-dataset-69 = As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. +tips-dataset-70 = As an Atmospheric Technician, try to resist the temptation of making canister bombs for Nuclear Operatives, unless you're in a last-ditch scenario. They often lead to large amounts of unnecessary friendly fire! +tips-dataset-71 = As an Engineer, you can repair cracked windows by using a lit welding tool on them while not in combat mode. +tips-dataset-72 = As an Engineer, you can electrify grilles by placing powered cables beneath them. +tips-dataset-73 = As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it! +tips-dataset-74 = As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation. +tips-dataset-75 = As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about. +tips-dataset-76 = As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe. +tips-dataset-77 = As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny. +tips-dataset-78 = As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back! +tips-dataset-79 = As a Scientist, you can try random things on an artifact while the scanner is on cooldown to speed up the point extraction process significantly. +tips-dataset-80 = As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor! +tips-dataset-81 = As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member. +tips-dataset-82 = As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit! +tips-dataset-83 = As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch. +tips-dataset-84 = As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine. +tips-dataset-85 = As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. However it has a battery, and if it drains too quickly for your taste you can ask science to print a better battery for you! +tips-dataset-86 = As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide? +tips-dataset-87 = As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients. +tips-dataset-88 = Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins! +tips-dataset-89 = You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people--it's a much better experience! +tips-dataset-90 = If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together! +tips-dataset-91 = The station's nuke is invincible. Go find the disk instead of trying to destroy it. +tips-dataset-92 = Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using. +tips-dataset-93 = We were all new once, be patient and guide new players, especially those playing intern roles, in the right direction. +tips-dataset-94 = Firesuits, winter coats and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all. +tips-dataset-95 = In an emergency, you can always rely on firesuits and emergency EVA suits; they will always spawn in their respective lockers. They might be awkward to move around in, but can easily save your life in a dangerous situation. +tips-dataset-96 = In an emergency, remember that you can craft improvised weapons! A baseball bat or spear could easily mean the difference between deterring an attacker or perishing from the hands of one. +tips-dataset-97 = Spears can be tipped with chemicals, and will inject a few units every time you hit someone with them directly. +tips-dataset-98 = You can make spears with reinforced glass, plasma glass, or uranium glass shards to enhance their damage. +tips-dataset-99 = Thrown spears deal extra damage! Beware, however, as throwing them too much will end up breaking them. +tips-dataset-100 = All forms of toxin damage are fairly difficult to treat, and usually involve the use of chemicals or other inconvenient methods. You can use this to your advantage in combat. +tips-dataset-101 = You can throw crafted bolas at people to slow them down, letting you follow up on them for an easier kill or getaway. +tips-dataset-102 = You can put napalm in a backpack water tank to make a flamethrower. +tips-dataset-103 = Some jobs have alternate uniforms in their respective drobe vendors. Don't be afraid to try out a new look! +tips-dataset-104 = Speed is almost everything in combat. Using hardsuits just for their armor is usually a terrible idea unless the resistances it provides are geared towards combat, or you're not planning to go head-first into the fray. +tips-dataset-105 = Just because a job can't be a traitor at the beginning of a round doesn't mean that they'll never be a traitor. +tips-dataset-106 = Syndicate gas masks will both provide welding protection and block flashes. Think twice before trying to flash a Nuclear Operative! +tips-dataset-107 = Demoman takes skill. +tips-dataset-108 = You can spray a fire extinguisher, throw items or fire a gun while floating through space to give yourself a minor boost. Simply fire opposite to where you want to go. +tips-dataset-109 = You can drag other players onto yourself to open the strip menu, allowing you to remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others. +tips-dataset-110 = You can climb onto a table by dragging yourself onto one. +tips-dataset-111 = You can move an item out of the way by dragging it, and then holding CTRL + right click and moving your mouse into the direction you want it to go. +tips-dataset-112 = When dealing with security, you can often get your sentence negated entirely through cooperation and deception. +tips-dataset-113 = Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them. +tips-dataset-114 = Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away. +tips-dataset-115 = Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding. +tips-dataset-116 = Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly. +tips-dataset-117 = In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze. +tips-dataset-118 = You can use sharp objects to butcher clothes or animals in the right click context menu. This includes glass shards. +tips-dataset-119 = Most explosives have an adjustable timer that you can set in the right click menu. This includes grenade penguins! +tips-dataset-120 = You can stun grenade penguins, which can bide valuable time for you to kill them. +tips-dataset-121 = You can click on the names of items to pick them up in the right click menu, instead of hovering over the item and then selecting pick up. +tips-dataset-122 = Space Station 14 is open source! If there's a change you want to make, or a simple item you want to add, then try contributing to the game. It's not as hard as you'd think it is. +tips-dataset-123 = In a pinch, you can throw drinks or other reagent containers behind you to create a spill that can slip people chasing you. +tips-dataset-124 = Some weapons, such as knives & shivs, have a fast attack speed. +tips-dataset-125 = The jaws of life can be used to open powered doors. +tips-dataset-126 = If you're not a human, you can drink blood to heal back some of your blood volume, albeit very inefficiently. +tips-dataset-127 = If you're a human, don't drink blood! It makes you sick and you'll begin to take damage. +tips-dataset-128 = There is a chemical metabolism limit that limits the amount of reagents of a certain type you can digest at once. Certain species have higher metabolism limits, such as slimes. +tips-dataset-129 = Welding without proper eye protection can cause eye damage, which must be cured with oculine. +tips-dataset-130 = Zombies are very vulnerable to heat damage, making welding tools and laser guns extremely effective against them. +tips-dataset-131 = You can weld glass shards into glass sheets. +tips-dataset-132 = By right clicking on a player, and then clicking the heart icon, you can quickly examine them to check for injuries or how badly they're bleeding. You can also do this to yourself. +tips-dataset-133 = Monkeys and kobolds have a rare chance to be sentient. Ook! +tips-dataset-134 = You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them. +tips-dataset-135 = Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands. diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml index 88dc09c220..b710d69fab 100644 --- a/Resources/Prototypes/Datasets/tips.yml +++ b/Resources/Prototypes/Datasets/tips.yml @@ -1,137 +1,5 @@ -- type: dataset +- type: localizedDataset id: Tips values: - - "If you're on fire, you can click the alert on the right of your screen to stop, drop, and roll." - - "You can view and edit all keybindings used in-game at any time through the Options menu." - - "You can access the in-game guidebook through the escape menu, or by pressing Numpad 0 by default." - - "Some entities ingame have guidebook entries associated with them, which you can view by examining the entity and clicking the question mark icon." - - "Artifacts have the ability to gain permanent effects for some triggered nodes, including becoming an intercom or an extremely efficient generator." - - "You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway." - - "Some plants, such as galaxy thistle, can be ground up into extremely useful and potent medicines." - - "Mopping up puddles and draining them into other containers conserves the reagents found in the puddle." - - "Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood." - - "Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it." - - "Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants." - - "Fire extinguishers can be loaded with any reagent in the game." - - "Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam." - - "Remember to touch grass in between playing Space Station 14 every once in a while." - - "You can use the Activate in World keybind, E by default, to interact with objects while your hands are full, or without picking them up." - - "Common sense goes a long way to avoiding conflict." - - "Every other player in game is a human being as well." - - "When running the Singularity, make sure to check on it periodically. If sabotaged, it could put the entire station at risk." - - "If the Singularity is up, make sure to refuel the radiation collectors once in a while." - - "Chemicals don't react while inside the ChemMaster's buffer." - - "Don't anger the bartender by throwing their glasses! Politely place them on the table by tapping Q." - - "You can hold SPACE by default to slow the movement of the shuttle when piloting, to allow for precise movements--or even coming to a complete stop." - - "Dexalin, Dexalin Plus, and Epinephrine will all purge heartbreaker toxin from your bloodstream while metabolizing." - - "Every crewmember comes with an emergency medipen in their survival box containing epinephrine and tranexamic acid." - - "The AME is a high-priority target and is easily sabotaged. Make sure to set up the Singularity or Solars so that you don't run out of power if it blows." - - "During a teslaloose, make sure to get rid of all electronic items so you don't become a target." - - "You can add labels to any item, including food or pill canisters, using a hand labeller." - - "Riot armor is significantly more powerful against opponents that aren't using guns compared to regular armor." - - "As a ghost, you can use the Verb Menu to orbit around and follow any entity in game automatically." - - "As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others." - - "As a Traitor, the syndicate encryption key can be used to communicate through a secure channel with any other traitors who have purchased it." - - "As a Traitor, compromising important communications channels like security or engineering can give valuable intelligence. Be aware that this goes in both ways - security can compromise syndicate communications as well!" - - "As a Traitor, the syndicate toolbox is extremely versatile. For only 2 telecrystals, you can get a full set of tools to help you in an emergency, insulated combat gloves and a syndicate gas mask." - - "As a Traitor, never underestimate the web vest. It may not provide space protection, but its cheap cost and robust protection makes it handy for protecting against trigger-happy foes." - - "As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed." - - "As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you." - - "As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach." - - "As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it." - - "As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction." - - "As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient!" - - "As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire." - - "As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan." - - "As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger!" - - "As an antagonist, be mindful of the power of destroying telecommunications. It'll be a lot harder for people to call you out if they can't do so effectively!" - - "You can examine your headset to see which radio channels you have available and how to speak in them." - - "As a Salvage Specialist, always carry a GPS on you and take note of the station's coordinates in case your salvage is lost to space." - - "As a Salvage Specialist, you can use your proto-kinetic accelerator to move yourself in space when in a pinch. Just be wary that it isn't very effective." - - "As a Salvage Specialist, never forget to mine ore! Ore can be sold to cargo for a pretty penny, be used for construction, and also be used by Scientists for fancy technology." - - "As a Salvage Specialist, try asking science for a tethergun. It can be used to grab items off of salvage wrecks extremely efficiently!" - - "As a Salvage Specialist, try asking science for a grappling hook. It can be used to propel yourself onto wrecks, or if stuck in space you don't have to rely on the proto-kinetic accelerator." - - "As a Salvage Specialist, consider cooperating with the Cargo Technicians. They can order you a wide variety of useful items, including ones that may be hard to get otherwise, such laser guns and shuttle building materials." - - "As a Cargo Technician, consider asking science for a Ripley APLU. When paired with a hydraulic clamp, you can grab valuable maintenance objects like fuel tanks much more easily, and make deliveries in a swift manner." - - "As a Cargo Technician, try to maintain a surplus of materials. They are extremely useful for Scientists and Station Engineers to have immediate access to." - - "As a Cargo Technician, if you have a surplus of cash try gambling! Sometimes you gain more money than you begin with." - - "As a Cargo Technician, remember that you can order guns in an emergency! The extra firepower can often be the difference between you and your fellow crewmembers living or dying." - - "As the Bartender, you can use a circular saw on your shotgun to make it easier to store." - - "As the Bartender, try experimenting with unique drinks. Have you tried to make demon's blood yet?" - - "As a Botanist, you can mutate and crossbreed plants together to create more potent produce that also has higher yields." - - "As the Clown, spice your gimmicks up! Nobody likes a one-trick pony." - - "As the Clown, if you lose your banana peels and soap, you can still slip people with your PDA! Honk!" - - "As the Chef, your knife can act as a weapon in an emergency." - - "As the Chef, you can sneak liquids into your foods. As a traitor, putting a little bit of amatoxin or other poison can greatly annoy the crew!" - - "As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor." - - "As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime." - - "As a Security Officer, communicate and coordinate with your fellow officers using the security radio channel to avoid confusion." - - "As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time!" - - "As a Detective, you can chase criminals more effectively by using fingerprint fiber data and DNA obtained from forensic scans of objects the perpetrator likely interacted with." - - "As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches." - - "As an Atmospheric Technician, try to resist the temptation of making canister bombs for Nuclear Operatives, unless you're in a last-ditch scenario. They often lead to large amounts of unnecessary friendly fire!" - - "As an Engineer, you can repair cracked windows by using a lit welding tool on them while not in combat mode." - - "As an Engineer, you can electrify grilles by placing powered cables beneath them." - - "As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it!" - - "As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation." - - "As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about." - - "As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe." - - "As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny." - - "As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back!" - - "As a Scientist, you can try random things on an artifact while the scanner is on cooldown to speed up the point extraction process significantly." - - "As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor!" - - "As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member." - - "As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit!" - - "As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch." - - "As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine." - - "As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. However it has a battery, and if it drains too quickly for your taste you can ask science to print a better battery for you!" - - "As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide?" - - "As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients." - - "Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins!" - - "You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people--it's a much better experience!" - - "If there's something you need from another department, try asking! This game isn't singleplayer and you'd be surprised what you can get accomplished together!" - - "The station's nuke is invincible. Go find the disk instead of trying to destroy it." - - "Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using." - - "We were all new once, be patient and guide new players, especially those playing intern roles, in the right direction." - - "Firesuits, winter coats and emergency EVA suits offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all." - - "In an emergency, you can always rely on firesuits and emergency EVA suits; they will always spawn in their respective lockers. They might be awkward to move around in, but can easily save your life in a dangerous situation." - - "In an emergency, remember that you can craft improvised weapons! A baseball bat or spear could easily mean the difference between deterring an attacker or perishing from the hands of one." - - "Spears can be tipped with chemicals, and will inject a few units every time you hit someone with them directly." - - "You can make spears with reinforced glass, plasma glass, or uranium glass shards to enhance their damage." - - "Thrown spears deal extra damage! Beware, however, as throwing them too much will end up breaking them." - - "All forms of toxin damage are fairly difficult to treat, and usually involve the use of chemicals or other inconvenient methods. You can use this to your advantage in combat." - - "You can throw crafted bolas at people to slow them down, letting you follow up on them for an easier kill or getaway." - - "You can put napalm in a backpack water tank to make a flamethrower." - - "Some jobs have alternate uniforms in their respective drobe vendors. Don't be afraid to try out a new look!" - - "Speed is almost everything in combat. Using hardsuits just for their armor is usually a terrible idea unless the resistances it provides are geared towards combat, or you're not planning to go head-first into the fray." - - "Just because a job can't be a traitor at the beginning of a round doesn't mean that they'll never be a traitor." - - "Syndicate gas masks will both provide welding protection and block flashes. Think twice before trying to flash a Nuclear Operative!" - - "Demoman takes skill." - - "You can spray a fire extinguisher, throw items or fire a gun while floating through space to give yourself a minor boost. Simply fire opposite to where you want to go." - - "You can drag other players onto yourself to open the strip menu, allowing you to remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others." - - "You can climb onto a table by dragging yourself onto one." - - "You can move an item out of the way by dragging it, and then holding CTRL + right click and moving your mouse into the direction you want it to go." - - "When dealing with security, you can often get your sentence negated entirely through cooperation and deception." - - "Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them." - - "Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away." - - "Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding." - - "Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly." - - "In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze." - - "You can use sharp objects to butcher clothes or animals in the right click context menu. This includes glass shards." - - "Most explosives have an adjustable timer that you can set in the right click menu. This includes grenade penguins!" - - "You can stun grenade penguins, which can bide valuable time for you to kill them." - - "You can click on the names of items to pick them up in the right click menu, instead of hovering over the item and then selecting pick up." - - "Space Station 14 is open source! If there's a change you want to make, or a simple item you want to add, then try contributing to the game. It's not as hard as you'd think it is." - - "In a pinch, you can throw drinks or other reagent containers behind you to create a spill that can slip people chasing you." - - "Some weapons, such as knives & shivs, have a fast attack speed." - - "The jaws of life can be used to open powered doors." - - "If you're not a human, you can drink blood to heal back some of your blood volume, albeit very inefficiently." - - "If you're a human, don't drink blood! It makes you sick and you'll begin to take damage." - - "There is a chemical metabolism limit that limits the amount of reagents of a certain type you can digest at once. Certain species have higher metabolism limits, such as slimes." - - "Welding without proper eye protection can cause eye damage, which must be cured with oculine." - - "Zombies are very vulnerable to heat damage, making welding tools and laser guns extremely effective against them." - - "You can weld glass shards into glass sheets." - - "By right clicking on a player, and then clicking the heart icon, you can quickly examine them to check for injuries or how badly they're bleeding. You can also do this to yourself." - - "Monkeys and kobolds have a rare chance to be sentient. Ook!" - - "You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them." - - "Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands." + prefix: tips-dataset- + count: 135 From d6f1f0ac1cba76e469848026359e24eede7d5602 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 30 May 2024 07:49:12 -0400 Subject: [PATCH 02/84] Convert story generation to use LocalizedDatasets (#28402) Converted story generation to use LocalizedDatasets --- .../Prototypes/StoryTemplatePrototype.cs | 4 +- .../Prototypes/Datasets/story_generation.yml | 266 +++--------------- 2 files changed, 35 insertions(+), 235 deletions(-) diff --git a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs index 7f6afacccc..948c7b2dc0 100644 --- a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs +++ b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs @@ -24,10 +24,10 @@ public sealed partial class StoryTemplatePrototype : IPrototype /// /// Dictionary containing the name of each variable to pass to the template and the ID of the - /// from which a random entry will be selected as its value. + /// from which a random entry will be selected as its value. /// For example, name: book_character will pick a random entry from the book_character /// dataset which can then be used in the template by {$name}. /// [DataField] - public Dictionary> Variables { get; } = default!; + public Dictionary> Variables { get; } = default!; } diff --git a/Resources/Prototypes/Datasets/story_generation.yml b/Resources/Prototypes/Datasets/story_generation.yml index 1a461c7596..9f5741c433 100644 --- a/Resources/Prototypes/Datasets/story_generation.yml +++ b/Resources/Prototypes/Datasets/story_generation.yml @@ -1,266 +1,66 @@ -- type: dataset +- type: localizedDataset id: BookTypes values: - - story-gen-book-type1 - - story-gen-book-type2 - - story-gen-book-type3 - - story-gen-book-type4 - - story-gen-book-type5 - - story-gen-book-type6 - - story-gen-book-type7 - - story-gen-book-type8 - - story-gen-book-type9 - - story-gen-book-type10 - - story-gen-book-type11 - - story-gen-book-type12 + prefix: story-gen-book-type + count: 12 -- type: dataset +- type: localizedDataset id: BookGenres values: - - story-gen-book-genre1 - - story-gen-book-genre2 - - story-gen-book-genre3 - - story-gen-book-genre4 - - story-gen-book-genre5 - - story-gen-book-genre6 - - story-gen-book-genre7 - - story-gen-book-genre8 - - story-gen-book-genre9 - - story-gen-book-genre10 - - story-gen-book-genre11 - - story-gen-book-genre12 - - story-gen-book-genre13 - - story-gen-book-genre14 + prefix: story-gen-book-genre + count: 14 -- type: dataset +- type: localizedDataset id: BookHintAppearances values: - - story-gen-book-appearance1 - - story-gen-book-appearance2 - - story-gen-book-appearance3 - - story-gen-book-appearance4 - - story-gen-book-appearance5 - - story-gen-book-appearance6 - - story-gen-book-appearance7 - - story-gen-book-appearance8 - - story-gen-book-appearance9 - - story-gen-book-appearance10 - - story-gen-book-appearance11 - - story-gen-book-appearance12 - - story-gen-book-appearance13 - - story-gen-book-appearance14 - - story-gen-book-appearance15 - - story-gen-book-appearance16 - - story-gen-book-appearance17 - - story-gen-book-appearance18 - - story-gen-book-appearance19 - - story-gen-book-appearance20 - - story-gen-book-appearance21 - - story-gen-book-appearance22 - - story-gen-book-appearance23 - - story-gen-book-appearance24 - - story-gen-book-appearance25 - - story-gen-book-appearance26 - - story-gen-book-appearance27 + prefix: story-gen-book-appearance + count: 27 -- type: dataset +- type: localizedDataset id: BookCharacters values: - - story-gen-book-character1 - - story-gen-book-character2 - - story-gen-book-character3 - - story-gen-book-character4 - - story-gen-book-character5 - - story-gen-book-character6 - - story-gen-book-character7 - - story-gen-book-character8 - - story-gen-book-character9 - - story-gen-book-character10 - - story-gen-book-character11 - - story-gen-book-character12 - - story-gen-book-character13 - - story-gen-book-character14 - - story-gen-book-character15 - - story-gen-book-character16 - - story-gen-book-character17 - - story-gen-book-character18 - - story-gen-book-character19 - - story-gen-book-character20 - - story-gen-book-character21 - - story-gen-book-character22 - - story-gen-book-character23 - - story-gen-book-character24 - - story-gen-book-character25 - - story-gen-book-character26 - - story-gen-book-character27 - - story-gen-book-character28 - - story-gen-book-character29 - - story-gen-book-character30 - - story-gen-book-character31 - - story-gen-book-character32 - - story-gen-book-character33 - - story-gen-book-character34 - - story-gen-book-character35 - - story-gen-book-character36 - - story-gen-book-character37 - - story-gen-book-character38 - - story-gen-book-character39 - - story-gen-book-character40 + prefix: story-gen-book-character + count: 40 -- type: dataset +- type: localizedDataset id: BookCharacterTraits values: - - story-gen-book-character-trait1 - - story-gen-book-character-trait2 - - story-gen-book-character-trait3 - - story-gen-book-character-trait4 - - story-gen-book-character-trait5 - - story-gen-book-character-trait6 - - story-gen-book-character-trait7 - - story-gen-book-character-trait8 - - story-gen-book-character-trait9 - - story-gen-book-character-trait10 - - story-gen-book-character-trait11 - - story-gen-book-character-trait12 - - story-gen-book-character-trait13 - - story-gen-book-character-trait14 - - story-gen-book-character-trait15 - - story-gen-book-character-trait16 - - story-gen-book-character-trait17 - - story-gen-book-character-trait18 - - story-gen-book-character-trait19 - - story-gen-book-character-trait20 - - story-gen-book-character-trait21 - - story-gen-book-character-trait22 - - story-gen-book-character-trait23 - - story-gen-book-character-trait24 + prefix: story-gen-book-character-trait + count: 24 -- type: dataset +- type: localizedDataset id: BookEvents values: - - story-gen-book-event1 - - story-gen-book-event2 - - story-gen-book-event3 - - story-gen-book-event4 - - story-gen-book-event5 - - story-gen-book-event6 - - story-gen-book-event7 - - story-gen-book-event8 - - story-gen-book-event9 - - story-gen-book-event10 - - story-gen-book-event11 - - story-gen-book-event12 - - story-gen-book-event13 - - story-gen-book-event14 - - story-gen-book-event15 - - story-gen-book-event16 - - story-gen-book-event17 - - story-gen-book-event18 - - story-gen-book-event19 - - story-gen-book-event20 - - story-gen-book-event21 - - story-gen-book-event22 - - story-gen-book-event23 - - story-gen-book-event24 + prefix: story-gen-book-event + count: 24 -- type: dataset +- type: localizedDataset id: BookActions values: - - story-gen-book-action1 - - story-gen-book-action2 - - story-gen-book-action3 - - story-gen-book-action4 - - story-gen-book-action5 - - story-gen-book-action6 - - story-gen-book-action7 - - story-gen-book-action8 - - story-gen-book-action9 - - story-gen-book-action10 - - story-gen-book-action11 - - story-gen-book-action12 + prefix: story-gen-book-action + count: 12 -- type: dataset +- type: localizedDataset id: BookActionTraits values: - - story-gen-book-action-trait1 - - story-gen-book-action-trait2 - - story-gen-book-action-trait3 - - story-gen-book-action-trait4 - - story-gen-book-action-trait5 - - story-gen-book-action-trait6 - - story-gen-book-action-trait7 - - story-gen-book-action-trait8 - - story-gen-book-action-trait9 - - story-gen-book-action-trait10 - - story-gen-book-action-trait11 - - story-gen-book-action-trait12 - - story-gen-book-action-trait13 + prefix: story-gen-book-action-trait + count: 13 -- type: dataset +- type: localizedDataset id: BookLocations values: - - story-gen-book-location1 - - story-gen-book-location2 - - story-gen-book-location3 - - story-gen-book-location4 - - story-gen-book-location5 - - story-gen-book-location6 - - story-gen-book-location7 - - story-gen-book-location8 - - story-gen-book-location9 - - story-gen-book-location10 - - story-gen-book-location11 - - story-gen-book-location12 - - story-gen-book-location13 - - story-gen-book-location14 - - story-gen-book-location15 - - story-gen-book-location16 - - story-gen-book-location17 - - story-gen-book-location18 - - story-gen-book-location19 - - story-gen-book-location20 - - story-gen-book-location21 - - story-gen-book-location22 - - story-gen-book-location23 - - story-gen-book-location24 - - story-gen-book-location25 - - story-gen-book-location26 - - story-gen-book-location27 - - story-gen-book-location28 - - story-gen-book-location29 - - story-gen-book-location30 - - story-gen-book-location31 - - story-gen-book-location32 - - story-gen-book-location33 - - story-gen-book-location34 + prefix: story-gen-book-location + count: 34 -- type: dataset +- type: localizedDataset id: BookStoryElements values: - - story-gen-book-element1 - - story-gen-book-element2 - - story-gen-book-element3 - - story-gen-book-element4 - - story-gen-book-element5 - - story-gen-book-element6 - - story-gen-book-element7 - - story-gen-book-element8 - - story-gen-book-element9 + prefix: story-gen-book-element + count: 9 -- type: dataset +- type: localizedDataset id: BookStoryElementTraits values: - - story-gen-book-element-trait1 - - story-gen-book-element-trait2 - - story-gen-book-element-trait3 - - story-gen-book-element-trait4 - - story-gen-book-element-trait5 - - story-gen-book-element-trait6 - - story-gen-book-element-trait7 - - story-gen-book-element-trait8 - - story-gen-book-element-trait9 - - story-gen-book-element-trait10 - - story-gen-book-element-trait11 - - story-gen-book-element-trait12 - - story-gen-book-element-trait13 + prefix: story-gen-book-element-trait + count: 13 From 98446cb0616be2b2e89144293122f6ff9bff7d50 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Thu, 30 May 2024 12:08:42 -0400 Subject: [PATCH 03/84] Convert advertisements to use localized datasets (#28400) * Convert advertisements to use LocalizedDatasets * File consolidation * Arcade machines too --- .../Components/AdvertiseComponent.cs | 8 +- .../Components/SpeakOnUIClosedComponent.cs | 6 +- .../EntitySystems/AdvertiseSystem.cs | 2 +- .../EntitySystems/SpeakOnUIClosedSystem.cs | 7 +- .../Advertise/MessagePackPrototype.cs | 14 - .../vending-machines/vending-machine.ftl | 2 +- .../Arcade/Advertisements/blockgame.yml | 16 -- .../Arcade/Advertisements/spacevillain.yml | 18 -- .../Catalog/Arcade/Goodbyes/blockgame.yml | 15 - .../Catalog/Arcade/Goodbyes/spacevillain.yml | 15 - .../Catalog/Arcade/advertisements.yml | 11 + .../Prototypes/Catalog/Arcade/goodbyes.yml | 11 + .../VendingMachines/Advertisements/ammo.yml | 13 - .../Advertisements/atmosdrobe.yml | 6 - .../Advertisements/bardrobe.yml | 5 - .../Advertisements/boozeomat.yml | 22 -- .../Advertisements/cargodrobe.yml | 6 - .../VendingMachines/Advertisements/chang.yml | 8 - .../Advertisements/chefdrobe.yml | 6 - .../Advertisements/chefvend.yml | 12 - .../Advertisements/chemdrobe.yml | 6 - .../VendingMachines/Advertisements/cigs.yml | 15 - .../Advertisements/clothesmate.yml | 10 - .../VendingMachines/Advertisements/coffee.yml | 17 -- .../VendingMachines/Advertisements/cola.yml | 11 - .../Advertisements/condiments.yml | 9 - .../Advertisements/curadrobe.yml | 6 - .../Advertisements/detdrobe.yml | 6 - .../Advertisements/dinnerware.yml | 13 - .../Advertisements/discount.yml | 12 - .../VendingMachines/Advertisements/donut.yml | 8 - .../Advertisements/engidrobe.yml | 8 - .../Advertisements/fatextractor.yml | 9 - .../VendingMachines/Advertisements/games.yml | 13 - .../Advertisements/genedrobe.yml | 5 - .../Advertisements/happyhonk.yml | 13 - .../Advertisements/hydrobe.yml | 7 - .../Advertisements/janidrobe.yml | 6 - .../Advertisements/lawdrobe.yml | 11 - .../Advertisements/magivend.yml | 14 - .../Advertisements/medidrobe.yml | 6 - .../Advertisements/megaseed.yml | 9 - .../Advertisements/nanomed.yml | 12 - .../Advertisements/nutrimax.yml | 10 - .../Advertisements/robodrobe.yml | 7 - .../Advertisements/scidrobe.yml | 6 - .../Advertisements/secdrobe.yml | 8 - .../Advertisements/sectech.yml | 8 - .../Advertisements/smartfridge.yml | 11 - .../VendingMachines/Advertisements/snack.yml | 18 -- .../Advertisements/sovietsoda.yml | 9 - .../Advertisements/syndiedrobe.yml | 34 --- .../Advertisements/theater.yml | 9 - .../Advertisements/vendomat.yml | 10 - .../Advertisements/virodrobe.yml | 6 - .../VendingMachines/Goodbyes/boozeomat.yml | 7 - .../VendingMachines/Goodbyes/chang.yml | 6 - .../VendingMachines/Goodbyes/chefvend.yml | 8 - .../Catalog/VendingMachines/Goodbyes/cigs.yml | 7 - .../VendingMachines/Goodbyes/coffee.yml | 8 - .../Catalog/VendingMachines/Goodbyes/cola.yml | 8 - .../VendingMachines/Goodbyes/discount.yml | 12 - .../VendingMachines/Goodbyes/donut.yml | 8 - .../VendingMachines/Goodbyes/games.yml | 8 - .../VendingMachines/Goodbyes/generic.yml | 4 - .../VendingMachines/Goodbyes/happyhonk.yml | 8 - .../VendingMachines/Goodbyes/lawdrobe.yml | 8 - .../VendingMachines/Goodbyes/nutrimax.yml | 6 - .../VendingMachines/Goodbyes/sectech.yml | 7 - .../VendingMachines/Goodbyes/snack.yml | 10 - .../VendingMachines/Goodbyes/sovietsoda.yml | 7 - .../VendingMachines/Goodbyes/syndiedrobe.yml | 9 - .../VendingMachines/advertisements.yml | 257 ++++++++++++++++++ .../Catalog/VendingMachines/goodbyes.yml | 101 +++++++ 74 files changed, 392 insertions(+), 662 deletions(-) delete mode 100644 Content.Shared/Advertise/MessagePackPrototype.cs delete mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml delete mode 100644 Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/advertisements.yml create mode 100644 Resources/Prototypes/Catalog/Arcade/goodbyes.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml delete mode 100644 Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/advertisements.yml create mode 100644 Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml diff --git a/Content.Server/Advertise/Components/AdvertiseComponent.cs b/Content.Server/Advertise/Components/AdvertiseComponent.cs index 3d617e3a34..94650234bb 100644 --- a/Content.Server/Advertise/Components/AdvertiseComponent.cs +++ b/Content.Server/Advertise/Components/AdvertiseComponent.cs @@ -1,12 +1,12 @@ using Content.Server.Advertise.EntitySystems; -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; namespace Content.Server.Advertise.Components; /// /// Makes this entity periodically advertise by speaking a randomly selected -/// message from a specified MessagePack into local chat. +/// message from a specified dataset into local chat. /// [RegisterComponent, Access(typeof(AdvertiseSystem))] public sealed partial class AdvertiseComponent : Component @@ -33,10 +33,10 @@ public sealed partial class AdvertiseComponent : Component public bool Prewarm = true; /// - /// The identifier for the advertisements pack prototype. + /// The identifier for the advertisements dataset prototype. /// [DataField(required: true)] - public ProtoId Pack { get; private set; } + public ProtoId Pack { get; private set; } /// /// The next time an advertisement will be said. diff --git a/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs index 2a663b7f89..99d0080d7f 100644 --- a/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs +++ b/Content.Server/Advertise/Components/SpeakOnUIClosedComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; namespace Content.Server.Advertise.Components; @@ -11,10 +11,10 @@ namespace Content.Server.Advertise.Components; public sealed partial class SpeakOnUIClosedComponent : Component { /// - /// The identifier for the message pack prototype containing messages to be spoken by this entity. + /// The identifier for the dataset prototype containing messages to be spoken by this entity. /// [DataField(required: true)] - public ProtoId Pack { get; private set; } + public ProtoId Pack { get; private set; } /// /// Is this component active? If false, no messages will be spoken. diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs index 28fa01628f..7f2e128183 100644 --- a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -62,7 +62,7 @@ public sealed class AdvertiseSystem : EntitySystem return; if (_prototypeManager.TryIndex(advert.Pack, out var advertisements)) - _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true); + _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Values)), InGameICChatType.Speak, hideChat: true); } public override void Update(float frameTime) diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs index 232b4b7eda..a0a709e5fa 100644 --- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Advertise.Components; using Content.Server.Chat.Systems; -using Content.Server.UserInterface; -using Content.Shared.Advertise; +using Content.Shared.Dataset; using Robust.Shared.Prototypes; using Robust.Shared.Random; using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent; @@ -39,10 +38,10 @@ public sealed partial class SpeakOnUIClosedSystem : EntitySystem if (!entity.Comp.Enabled) return false; - if (!_prototypeManager.TryIndex(entity.Comp.Pack, out MessagePackPrototype? messagePack)) + if (!_prototypeManager.TryIndex(entity.Comp.Pack, out var messagePack)) return false; - var message = Loc.GetString(_random.Pick(messagePack.Messages), ("name", Name(entity))); + var message = Loc.GetString(_random.Pick(messagePack.Values), ("name", Name(entity))); _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, true); entity.Comp.Flag = false; return true; diff --git a/Content.Shared/Advertise/MessagePackPrototype.cs b/Content.Shared/Advertise/MessagePackPrototype.cs deleted file mode 100644 index f7495d7e46..0000000000 --- a/Content.Shared/Advertise/MessagePackPrototype.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Advertise; - -[Serializable, Prototype("messagePack")] -public sealed partial class MessagePackPrototype : IPrototype -{ - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - - [DataField] - public List Messages { get; private set; } = []; -} diff --git a/Resources/Locale/en-US/vending-machines/vending-machine.ftl b/Resources/Locale/en-US/vending-machines/vending-machine.ftl index 637a9c7568..cb1dd3502f 100644 --- a/Resources/Locale/en-US/vending-machines/vending-machine.ftl +++ b/Resources/Locale/en-US/vending-machines/vending-machine.ftl @@ -1,3 +1,3 @@ -vending-machine-thanks = Thanks for using { $name }! +vending-machine-thanks-1 = Thanks for using { $name }! vending-machine-flavor-left = Request refills at cargo vending-machine-flavor-right = v1.1 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml deleted file mode 100644 index 7fb0367295..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/blockgame.yml +++ /dev/null @@ -1,16 +0,0 @@ -- type: messagePack - id: BlockGameAds - messages: - - advertisement-block-game-1 - - advertisement-block-game-2 - - advertisement-block-game-3 - - advertisement-block-game-4 - - advertisement-block-game-5 - - advertisement-block-game-6 - - advertisement-block-game-7 - - advertisement-block-game-8 - - advertisement-block-game-9 - - advertisement-block-game-10 - - advertisement-block-game-11 - - advertisement-block-game-12 - - advertisement-block-game-13 diff --git a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml deleted file mode 100644 index 7c94ab94f6..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Advertisements/spacevillain.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: messagePack - id: SpaceVillainAds - messages: - - advertisement-space-villain-1 - - advertisement-space-villain-2 - - advertisement-space-villain-3 - - advertisement-space-villain-4 - - advertisement-space-villain-5 - - advertisement-space-villain-6 - - advertisement-space-villain-7 - - advertisement-space-villain-8 - - advertisement-space-villain-9 - - advertisement-space-villain-10 - - advertisement-space-villain-11 - - advertisement-space-villain-12 - - advertisement-space-villain-13 - - advertisement-space-villain-14 - - advertisement-space-villain-15 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml deleted file mode 100644 index 460e8d13bf..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Goodbyes/blockgame.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: BlockGameGoodbyes - messages: - - thankyou-block-game-1 - - thankyou-block-game-2 - - thankyou-block-game-3 - - thankyou-block-game-4 - - thankyou-block-game-5 - - thankyou-block-game-6 - - thankyou-block-game-7 - - thankyou-block-game-8 - - thankyou-block-game-9 - - thankyou-block-game-10 - - thankyou-block-game-11 - - thankyou-block-game-12 diff --git a/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml b/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml deleted file mode 100644 index 09016afec3..0000000000 --- a/Resources/Prototypes/Catalog/Arcade/Goodbyes/spacevillain.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: SpaceVillainGoodbyes - messages: - - thankyou-space-villain-1 - - thankyou-space-villain-2 - - thankyou-space-villain-3 - - thankyou-space-villain-4 - - thankyou-space-villain-5 - - thankyou-space-villain-6 - - thankyou-space-villain-7 - - thankyou-space-villain-8 - - thankyou-space-villain-9 - - thankyou-space-villain-10 - - thankyou-space-villain-11 - - thankyou-space-villain-12 diff --git a/Resources/Prototypes/Catalog/Arcade/advertisements.yml b/Resources/Prototypes/Catalog/Arcade/advertisements.yml new file mode 100644 index 0000000000..47ecfb60b8 --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/advertisements.yml @@ -0,0 +1,11 @@ +- type: localizedDataset + id: BlockGameAds + values: + prefix: advertisement-block-game- + count: 13 + +- type: localizedDataset + id: SpaceVillainAds + values: + prefix: advertisement-space-villain- + count: 15 diff --git a/Resources/Prototypes/Catalog/Arcade/goodbyes.yml b/Resources/Prototypes/Catalog/Arcade/goodbyes.yml new file mode 100644 index 0000000000..48b45eb40e --- /dev/null +++ b/Resources/Prototypes/Catalog/Arcade/goodbyes.yml @@ -0,0 +1,11 @@ +- type: localizedDataset + id: BlockGameGoodbyes + values: + prefix: thankyou-block-game- + count: 12 + +- type: localizedDataset + id: SpaceVillainGoodbyes + values: + prefix: thankyou-space-villain- + count: 12 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml deleted file mode 100644 index 7e089a2825..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/ammo.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: AmmoVendAds - messages: - - advertisement-ammo-1 - - advertisement-ammo-2 - - advertisement-ammo-3 - - advertisement-ammo-4 - - advertisement-ammo-5 - - advertisement-ammo-6 - - advertisement-ammo-7 - - advertisement-ammo-8 - - advertisement-ammo-9 - - advertisement-ammo-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml deleted file mode 100644 index 7946c06310..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/atmosdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: AtmosDrobeAds - messages: - - advertisement-atmosdrobe-1 - - advertisement-atmosdrobe-2 - - advertisement-atmosdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml deleted file mode 100644 index 0a21acf2cb..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/bardrobe.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: messagePack - id: BarDrobeAds - messages: - - advertisement-bardrobe-1 - - advertisement-bardrobe-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml deleted file mode 100644 index e6bcbbcb9c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/boozeomat.yml +++ /dev/null @@ -1,22 +0,0 @@ -- type: messagePack - id: BoozeOMatAds - messages: - - advertisement-boozeomat-1 - - advertisement-boozeomat-2 - - advertisement-boozeomat-3 - - advertisement-boozeomat-4 - - advertisement-boozeomat-5 - - advertisement-boozeomat-6 - - advertisement-boozeomat-7 - - advertisement-boozeomat-8 - - advertisement-boozeomat-9 - - advertisement-boozeomat-10 - - advertisement-boozeomat-11 - - advertisement-boozeomat-12 - - advertisement-boozeomat-13 - - advertisement-boozeomat-14 - - advertisement-boozeomat-15 - - advertisement-boozeomat-16 - - advertisement-boozeomat-17 - - advertisement-boozeomat-18 - - advertisement-boozeomat-19 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml deleted file mode 100644 index f2cd38f737..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cargodrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: CargoDrobeAds - messages: - - advertisement-cargodrobe-1 - - advertisement-cargodrobe-2 - - advertisement-cargodrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml deleted file mode 100644 index 911d467e7d..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chang.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: ChangAds - messages: - - advertisement-chang-1 - - advertisement-chang-2 - - advertisement-chang-3 - - advertisement-chang-4 - - advertisement-chang-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml deleted file mode 100644 index c71d8225e0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChefDrobeAds - messages: - - advertisement-chefdrobe-1 - - advertisement-chefdrobe-2 - - advertisement-chefdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml deleted file mode 100644 index d52fcf9894..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chefvend.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: ChefvendAds - messages: - - advertisement-chefvend-1 - - advertisement-chefvend-2 - - advertisement-chefvend-3 - - advertisement-chefvend-4 - - advertisement-chefvend-5 - - advertisement-chefvend-6 - - advertisement-chefvend-7 - - advertisement-chefvend-8 - - advertisement-chefvend-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml deleted file mode 100644 index 69a37de184..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/chemdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChemDrobeAds - messages: - - advertisement-chemdrobe-1 - - advertisement-chemdrobe-2 - - advertisement-chemdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml deleted file mode 100644 index db3d492c45..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cigs.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: messagePack - id: CigaretteMachineAds - messages: - - advertisement-cigs-1 - - advertisement-cigs-2 - - advertisement-cigs-3 - - advertisement-cigs-4 - - advertisement-cigs-5 - - advertisement-cigs-6 - - advertisement-cigs-7 - - advertisement-cigs-8 - - advertisement-cigs-9 - - advertisement-cigs-10 - - advertisement-cigs-11 - - advertisement-cigs-12 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml deleted file mode 100644 index dc109f8eb9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/clothesmate.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: ClothesMateAds - messages: - - advertisement-clothes-1 - - advertisement-clothes-2 - - advertisement-clothes-3 - - advertisement-clothes-4 - - advertisement-clothes-5 - - advertisement-clothes-6 - - advertisement-clothes-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml deleted file mode 100644 index 4781768cf0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/coffee.yml +++ /dev/null @@ -1,17 +0,0 @@ -- type: messagePack - id: HotDrinksMachineAds - messages: - - advertisement-coffee-1 - - advertisement-coffee-2 - - advertisement-coffee-3 - - advertisement-coffee-4 - - advertisement-coffee-5 - - advertisement-coffee-6 - - advertisement-coffee-7 - - advertisement-coffee-8 - - advertisement-coffee-9 - - advertisement-coffee-10 - - advertisement-coffee-11 - - advertisement-coffee-12 - - advertisement-coffee-13 - - advertisement-coffee-14 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml deleted file mode 100644 index d12d7eb92f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/cola.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: RobustSoftdrinksAds - messages: - - advertisement-cola-1 - - advertisement-cola-2 - - advertisement-cola-3 - - advertisement-cola-4 - - advertisement-cola-5 - - advertisement-cola-6 - - advertisement-cola-7 - - advertisement-cola-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml deleted file mode 100644 index d1d07df0c4..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/condiments.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: CondimentVendAds - messages: - - advertisement-condiment-1 - - advertisement-condiment-2 - - advertisement-condiment-3 - - advertisement-condiment-4 - - advertisement-condiment-5 - - advertisement-condiment-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml deleted file mode 100644 index 47523c6e93..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/curadrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: CuraDrobeAds - messages: - - advertisement-curadrobe-1 - - advertisement-curadrobe-2 - - advertisement-curadrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml deleted file mode 100644 index 50024ce3d9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/detdrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: DetDrobeAds - messages: - - advertisement-detdrobe-1 - - advertisement-detdrobe-2 - - advertisement-detdrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml deleted file mode 100644 index ac31dc075b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/dinnerware.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: DinnerwareAds - messages: - - advertisement-dinnerware-1 - - advertisement-dinnerware-2 - - advertisement-dinnerware-3 - - advertisement-dinnerware-4 - - advertisement-dinnerware-5 - - advertisement-dinnerware-6 - - advertisement-dinnerware-7 - - advertisement-dinnerware-8 - - advertisement-dinnerware-9 - - advertisement-dinnerware-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml deleted file mode 100644 index dce8646a60..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/discount.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: DiscountDansAds - messages: - - advertisement-discount-1 - - advertisement-discount-2 - - advertisement-discount-3 - - advertisement-discount-4 - - advertisement-discount-5 - - advertisement-discount-6 - - advertisement-discount-7 - - advertisement-discount-8 - - advertisement-discount-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml deleted file mode 100644 index 73766aa749..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/donut.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: DonutAds - messages: - - advertisement-donut-1 - - advertisement-donut-2 - - advertisement-donut-3 - - advertisement-donut-4 - - advertisement-donut-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml deleted file mode 100644 index ec92fbe4de..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/engidrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: EngiDrobeAds - messages: - - advertisement-engidrobe-1 - - advertisement-engidrobe-2 - - advertisement-engidrobe-3 - - advertisement-engidrobe-4 - - advertisement-engidrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml deleted file mode 100644 index 7619ea1856..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/fatextractor.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: FatExtractorFacts - messages: - - fat-extractor-fact-1 - - fat-extractor-fact-2 - - fat-extractor-fact-3 - - fat-extractor-fact-4 - - fat-extractor-fact-5 - - fat-extractor-fact-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml deleted file mode 100644 index 1348635e1f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/games.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: GoodCleanFunAds - messages: - - advertisement-goodcleanfun-1 - - advertisement-goodcleanfun-2 - - advertisement-goodcleanfun-3 - - advertisement-goodcleanfun-4 - - advertisement-goodcleanfun-5 - - advertisement-goodcleanfun-6 - - advertisement-goodcleanfun-7 - - advertisement-goodcleanfun-8 - - advertisement-goodcleanfun-9 - - advertisement-goodcleanfun-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml deleted file mode 100644 index 722388055b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/genedrobe.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: messagePack - id: GeneDrobeAds - messages: - - advertisement-genedrobe-1 - - advertisement-genedrobe-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml deleted file mode 100644 index e145ebcdac..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/happyhonk.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: messagePack - id: HappyHonkAds - messages: - - advertisement-happyhonk-1 - - advertisement-happyhonk-2 - - advertisement-happyhonk-3 - - advertisement-happyhonk-4 - - advertisement-happyhonk-5 - - advertisement-happyhonk-6 - - advertisement-happyhonk-7 - - advertisement-happyhonk-8 - - advertisement-happyhonk-9 - - advertisement-happyhonk-10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml deleted file mode 100644 index 5999c496f5..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/hydrobe.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: HyDrobeAds - messages: - - advertisement-hydrobe-1 - - advertisement-hydrobe-2 - - advertisement-hydrobe-3 - - advertisement-hydrobe-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml deleted file mode 100644 index 8310136bf8..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/janidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: JaniDrobeAds - messages: - - advertisement-janidrobe-1 - - advertisement-janidrobe-2 - - advertisement-janidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml deleted file mode 100644 index a948413abd..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/lawdrobe.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: LawDrobeAds - messages: - - advertisement-lawdrobe-1 - - advertisement-lawdrobe-2 - - advertisement-lawdrobe-3 - - advertisement-lawdrobe-4 - - advertisement-lawdrobe-5 - - advertisement-lawdrobe-6 - - advertisement-lawdrobe-7 - - advertisement-lawdrobe-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml deleted file mode 100644 index 896a3853e7..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/magivend.yml +++ /dev/null @@ -1,14 +0,0 @@ -- type: messagePack - id: MagiVendAds - messages: - - advertisement-magivend-1 - - advertisement-magivend-2 - - advertisement-magivend-3 - - advertisement-magivend-4 - - advertisement-magivend-5 - - advertisement-magivend-6 - - advertisement-magivend-7 - - advertisement-magivend-8 - - advertisement-magivend-9 - - advertisement-magivend-10 - - advertisement-magivend-11 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml deleted file mode 100644 index b7b055231b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/medidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: MediDrobeAds - messages: - - advertisement-medidrobe-1 - - advertisement-medidrobe-2 - - advertisement-medidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml deleted file mode 100644 index b6e6ae098e..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/megaseed.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: MegaSeedAds - messages: - - advertisement-megaseed-1 - - advertisement-megaseed-2 - - advertisement-megaseed-3 - - advertisement-megaseed-4 - - advertisement-megaseed-5 - - advertisement-megaseed-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml deleted file mode 100644 index a79a4c8a6c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nanomed.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: NanoMedAds - messages: - - advertisement-nanomed-1 - - advertisement-nanomed-2 - - advertisement-nanomed-3 - - advertisement-nanomed-4 - - advertisement-nanomed-5 - - advertisement-nanomed-6 - - advertisement-nanomed-7 - - advertisement-nanomed-8 - - advertisement-nanomed-9 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml deleted file mode 100644 index a3ade34960..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/nutrimax.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: NutriMaxAds - messages: - - advertisement-nutrimax-1 - - advertisement-nutrimax-2 - - advertisement-nutrimax-3 - - advertisement-nutrimax-4 - - advertisement-nutrimax-5 - - advertisement-nutrimax-6 - - advertisement-nutrimax-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml deleted file mode 100644 index 82ece7d763..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/robodrobe.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: RoboDrobeAds - messages: - - advertisement-robodrobe-1 - - advertisement-robodrobe-2 - - advertisement-robodrobe-3 - - advertisement-robodrobe-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml deleted file mode 100644 index f8b125073b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/scidrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: SciDrobeAds - messages: - - advertisement-scidrobe-1 - - advertisement-scidrobe-2 - - advertisement-scidrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml deleted file mode 100644 index 99c2c402de..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/secdrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: SecDrobeAds - messages: - - advertisement-secdrobe-1 - - advertisement-secdrobe-2 - - advertisement-secdrobe-3 - - advertisement-secdrobe-4 - - advertisement-secdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml deleted file mode 100644 index 0b32ed166d..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sectech.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: SecTechAds - messages: - - advertisement-sectech-1 - - advertisement-sectech-2 - - advertisement-sectech-3 - - advertisement-sectech-4 - - advertisement-sectech-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml deleted file mode 100644 index d92a95f70f..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/smartfridge.yml +++ /dev/null @@ -1,11 +0,0 @@ -- type: messagePack - id: SmartFridgeAds - messages: - - advertisement-smartfridge-1 - - advertisement-smartfridge-2 - - advertisement-smartfridge-3 - - advertisement-smartfridge-4 - - advertisement-smartfridge-5 - - advertisement-smartfridge-6 - - advertisement-smartfridge-7 - - advertisement-smartfridge-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml deleted file mode 100644 index 3d9b93f9c2..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/snack.yml +++ /dev/null @@ -1,18 +0,0 @@ -- type: messagePack - id: GetmoreChocolateCorpAds - messages: - - advertisement-snack-1 - - advertisement-snack-2 - - advertisement-snack-3 - - advertisement-snack-4 - - advertisement-snack-5 - - advertisement-snack-6 - - advertisement-snack-7 - - advertisement-snack-8 - - advertisement-snack-9 - - advertisement-snack-10 - - advertisement-snack-11 - - advertisement-snack-12 - - advertisement-snack-13 - - advertisement-snack-14 - - advertisement-snack-15 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml deleted file mode 100644 index 630de1e615..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/sovietsoda.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: BodaAds - messages: - - advertisement-sovietsoda-1 - - advertisement-sovietsoda-2 - - advertisement-sovietsoda-3 - - advertisement-sovietsoda-4 - - advertisement-sovietsoda-5 - - advertisement-sovietsoda-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml deleted file mode 100644 index dc89d04aa0..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/syndiedrobe.yml +++ /dev/null @@ -1,34 +0,0 @@ -- type: messagePack - id: SyndieDrobeAds - messages: - - advertisement-syndiedrobe-1 - - advertisement-syndiedrobe-2 - - advertisement-syndiedrobe-3 - - advertisement-syndiedrobe-4 - - advertisement-syndiedrobe-5 - - advertisement-syndiedrobe-6 - - advertisement-syndiedrobe-7 - - advertisement-syndiedrobe-8 - - advertisement-syndiedrobe-9 - - advertisement-syndiedrobe-10 - - advertisement-syndiedrobe-11 - - advertisement-syndiedrobe-12 - - advertisement-syndiedrobe-13 - - advertisement-syndiedrobe-14 - - advertisement-syndiedrobe-15 - - advertisement-syndiedrobe-16 - - advertisement-syndiedrobe-17 - - advertisement-syndiedrobe-18 - - advertisement-syndiedrobe-19 - - advertisement-syndiedrobe-20 - - advertisement-syndiedrobe-21 - - advertisement-syndiedrobe-22 - - advertisement-syndiedrobe-23 - - advertisement-syndiedrobe-24 - - advertisement-syndiedrobe-25 - - advertisement-syndiedrobe-26 - - advertisement-syndiedrobe-27 - - advertisement-syndiedrobe-28 - - advertisement-syndiedrobe-29 - - advertisement-syndiedrobe-30 - - advertisement-syndiedrobe-31 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml deleted file mode 100644 index 8b06cdb517..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/theater.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: AutoDrobeAds - messages: - - advertisement-theater-1 - - advertisement-theater-2 - - advertisement-theater-3 - - advertisement-theater-4 - - advertisement-theater-5 - - advertisement-theater-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml deleted file mode 100644 index 31c0e0c241..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/vendomat.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: VendomatAds - messages: - - advertisement-vendomat-1 - - advertisement-vendomat-2 - - advertisement-vendomat-3 - - advertisement-vendomat-4 - - advertisement-vendomat-5 - - advertisement-vendomat-6 - - advertisement-vendomat-7 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml deleted file mode 100644 index c48f9e7d2e..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Advertisements/virodrobe.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ViroDrobeAds - messages: - - advertisement-virodrobe-1 - - advertisement-virodrobe-2 - - advertisement-virodrobe-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml deleted file mode 100644 index e5c96887b9..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/boozeomat.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: BoozeOMatGoodbyes - messages: - - vending-machine-thanks - - thankyou-boozeomat-1 - - thankyou-boozeomat-2 - - thankyou-boozeomat-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml deleted file mode 100644 index a348d8fa9c..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chang.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: ChangGoodbyes - messages: - - vending-machine-thanks - - thankyou-chang-1 - - thankyou-chang-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml deleted file mode 100644 index 93249586e1..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/chefvend.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: ChefvendGoodbyes - messages: - - vending-machine-thanks - - thankyou-chefvend-1 - - thankyou-chefvend-2 - - thankyou-chefvend-3 - - thankyou-chefvend-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml deleted file mode 100644 index 108a34a472..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cigs.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: CigaretteMachineGoodbyes - messages: - - vending-machine-thanks - - thankyou-cigs-1 - - thankyou-cigs-2 - - thankyou-cigs-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml deleted file mode 100644 index 6f2aef01b8..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/coffee.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: HotDrinksMachineGoodbyes - messages: - - vending-machine-thanks - - thankyou-coffee-1 - - thankyou-coffee-2 - - thankyou-coffee-3 - - thankyou-coffee-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml deleted file mode 100644 index 76f00fddaf..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/cola.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: RobustSoftdrinksGoodbyes - messages: - - vending-machine-thanks - - thankyou-cola-1 - - thankyou-cola-2 - - thankyou-cola-3 - - thankyou-cola-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml deleted file mode 100644 index d3f7d89a93..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/discount.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: messagePack - id: DiscountDansGoodbyes - messages: - - vending-machine-thanks - - thankyou-discount-1 - - thankyou-discount-2 - - thankyou-discount-3 - - thankyou-discount-4 - - thankyou-discount-5 - - thankyou-discount-6 - - thankyou-discount-7 - - thankyou-discount-8 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml deleted file mode 100644 index 3c28741e3b..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/donut.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: DonutGoodbyes - messages: - - vending-machine-thanks - - thankyou-donut-1 - - thankyou-donut-2 - - thankyou-donut-3 - - thankyou-donut-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml deleted file mode 100644 index 5d842619bb..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/games.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: GoodCleanFunGoodbyes - messages: - - vending-machine-thanks - - thankyou-goodcleanfun-1 - - thankyou-goodcleanfun-2 - - thankyou-goodcleanfun-3 - - thankyou-goodcleanfun-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml deleted file mode 100644 index 98a2d7d17a..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/generic.yml +++ /dev/null @@ -1,4 +0,0 @@ -- type: messagePack - id: GenericVendGoodbyes - messages: - - vending-machine-thanks diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml deleted file mode 100644 index 7859d55463..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/happyhonk.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: HappyHonkGoodbyes - messages: - - vending-machine-thanks - - thankyou-happyhonk-1 - - thankyou-happyhonk-2 - - thankyou-happyhonk-3 - - thankyou-happyhonk-4 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml deleted file mode 100644 index 56678c1017..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/lawdrobe.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: messagePack - id: LawDrobeGoodbyes - messages: - - thankyou-lawdrobe-1 - - thankyou-lawdrobe-2 - - thankyou-lawdrobe-3 - - thankyou-lawdrobe-4 - - thankyou-lawdrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml deleted file mode 100644 index 3568ae3031..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/nutrimax.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: messagePack - id: NutriMaxGoodbyes - messages: - - vending-machine-thanks - - thankyou-nutrimax-1 - - thankyou-nutrimax-2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml deleted file mode 100644 index 8909693473..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sectech.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: SecTechGoodbyes - messages: - - vending-machine-thanks - - thankyou-sectech-1 - - thankyou-sectech-2 - - thankyou-sectech-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml deleted file mode 100644 index bd86e074c6..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/snack.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: messagePack - id: GetmoreChocolateCorpGoodbyes - messages: - - vending-machine-thanks - - thankyou-snack-1 - - thankyou-snack-2 - - thankyou-snack-3 - - thankyou-snack-4 - - thankyou-snack-5 - - thankyou-snack-6 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml deleted file mode 100644 index 01ab25a7ad..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/sovietsoda.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: messagePack - id: BodaGoodbyes - messages: - - vending-machine-thanks - - thankyou-sovietsoda-1 - - thankyou-sovietsoda-2 - - thankyou-sovietsoda-3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml deleted file mode 100644 index 1246eb3089..0000000000 --- a/Resources/Prototypes/Catalog/VendingMachines/Goodbyes/syndiedrobe.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: messagePack - id: SyndieDrobeGoodbyes - messages: - - vending-machine-thanks - - thankyou-syndiedrobe-1 - - thankyou-syndiedrobe-2 - - thankyou-syndiedrobe-3 - - thankyou-syndiedrobe-4 - - thankyou-syndiedrobe-5 diff --git a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml new file mode 100644 index 0000000000..6dbb60af6a --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml @@ -0,0 +1,257 @@ +- type: localizedDataset + id: AmmoVendAds + values: + prefix: advertisement-ammo- + count: 10 + +- type: localizedDataset + id: AtmosDrobeAds + values: + prefix: advertisement-atmosdrobe- + count: 3 + +- type: localizedDataset + id: BarDrobeAds + values: + prefix: advertisement-bardrobe- + count: 2 + +- type: localizedDataset + id: BoozeOMatAds + values: + prefix: advertisement-boozeomat- + count: 19 + +- type: localizedDataset + id: CargoDrobeAds + values: + prefix: advertisement-cargodrobe- + count: 3 + +- type: localizedDataset + id: ChangAds + values: + prefix: advertisement-chang- + count: 5 + +- type: localizedDataset + id: ChefDrobeAds + values: + prefix: advertisement-chefdrobe- + count: 3 + +- type: localizedDataset + id: ChefvendAds + values: + prefix: advertisement-chefvend- + count: 9 + +- type: localizedDataset + id: ChemDrobeAds + values: + prefix: advertisement-chemdrobe- + count: 3 + +- type: localizedDataset + id: CigaretteMachineAds + values: + prefix: advertisement-cigs- + count: 12 + +- type: localizedDataset + id: ClothesMateAds + values: + prefix: advertisement-clothes- + count: 7 + +- type: localizedDataset + id: HotDrinksMachineAds + values: + prefix: advertisement-coffee- + count: 14 + +- type: localizedDataset + id: RobustSoftdrinksAds + values: + prefix: advertisement-cola- + count: 8 + +- type: localizedDataset + id: CondimentVendAds + values: + prefix: advertisement-condiment- + count: 6 + +- type: localizedDataset + id: CuraDrobeAds + values: + prefix: advertisement-curadrobe- + count: 3 + +- type: localizedDataset + id: DetDrobeAds + values: + prefix: advertisement-detdrobe- + count: 3 + +- type: localizedDataset + id: DinnerwareAds + values: + prefix: advertisement-dinnerware- + count: 10 + +- type: localizedDataset + id: DiscountDansAds + values: + prefix: advertisement-discount- + count: 9 + +- type: localizedDataset + id: DonutAds + values: + prefix: advertisement-donut- + count: 5 + +- type: localizedDataset + id: EngiDrobeAds + values: + prefix: advertisement-engidrobe- + count: 5 + +- type: localizedDataset + id: FatExtractorFacts + values: + prefix: fat-extractor-fact- + count: 6 + +- type: localizedDataset + id: GoodCleanFunAds + values: + prefix: advertisement-goodcleanfun- + count: 10 + +- type: localizedDataset + id: GeneDrobeAds + values: + prefix: advertisement-genedrobe- + count: 2 + +- type: localizedDataset + id: HappyHonkAds + values: + prefix: advertisement-happyhonk- + count: 10 + +- type: localizedDataset + id: HyDrobeAds + values: + prefix: advertisement-hydrobe- + count: 4 + +- type: localizedDataset + id: JaniDrobeAds + values: + prefix: advertisement-janidrobe- + count: 3 + +- type: localizedDataset + id: LawDrobeAds + values: + prefix: advertisement-lawdrobe- + count: 8 + +- type: localizedDataset + id: MagiVendAds + values: + prefix: advertisement-magivend- + count: 11 + +- type: localizedDataset + id: MediDrobeAds + values: + prefix: advertisement-medidrobe- + count: 3 + +- type: localizedDataset + id: MegaSeedAds + values: + prefix: advertisement-megaseed- + count: 6 + +- type: localizedDataset + id: NanoMedAds + values: + prefix: advertisement-nanomed- + count: 9 + +- type: localizedDataset + id: NutriMaxAds + values: + prefix: advertisement-nutrimax- + count: 7 + +- type: localizedDataset + id: RoboDrobeAds + values: + prefix: advertisement-robodrobe- + count: 4 + +- type: localizedDataset + id: SciDrobeAds + values: + prefix: advertisement-scidrobe- + count: 3 + +- type: localizedDataset + id: SecDrobeAds + values: + prefix: advertisement-secdrobe- + count: 5 + +- type: localizedDataset + id: SecTechAds + values: + prefix: advertisement-sectech- + count: 5 + +- type: localizedDataset + id: SmartFridgeAds + values: + prefix: advertisement-smartfridge- + count: 8 + +- type: localizedDataset + id: GetmoreChocolateCorpAds + values: + prefix: advertisement-snack- + count: 15 + +- type: localizedDataset + id: BodaAds + values: + prefix: advertisement-sovietsoda- + count: 6 + +- type: localizedDataset + id: SyndieDrobeAds + values: + prefix: advertisement-syndiedrobe- + count: 31 + +- type: localizedDataset + id: AutoDrobeAds + values: + prefix: advertisement-theater- + count: 6 + +- type: localizedDataset + id: VendomatAds + values: + prefix: advertisement-vendomat- + count: 7 + +- type: localizedDataset + id: ViroDrobeAds + values: + prefix: advertisement-virodrobe- + count: 3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml b/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml new file mode 100644 index 0000000000..5a3d91db11 --- /dev/null +++ b/Resources/Prototypes/Catalog/VendingMachines/goodbyes.yml @@ -0,0 +1,101 @@ +- type: localizedDataset + id: BoozeOMatGoodbyes + values: + prefix: thankyou-boozeomat- + count: 3 + +- type: localizedDataset + id: ChangGoodbyes + values: + prefix: thankyou-chang- + count: 2 + +- type: localizedDataset + id: ChefvendGoodbyes + values: + prefix: thankyou-chefvend- + count: 4 + +- type: localizedDataset + id: CigaretteMachineGoodbyes + values: + prefix: thankyou-cigs- + count: 3 + +- type: localizedDataset + id: HotDrinksMachineGoodbyes + values: + prefix: thankyou-coffee- + count: 4 + +- type: localizedDataset + id: RobustSoftdrinksGoodbyes + values: + prefix: thankyou-cola- + count: 4 + +- type: localizedDataset + id: DiscountDansGoodbyes + values: + prefix: thankyou-discount- + count: 8 + +- type: localizedDataset + id: DonutGoodbyes + values: + prefix: thankyou-donut- + count: 4 + +- type: localizedDataset + id: GoodCleanFunGoodbyes + values: + prefix: thankyou-goodcleanfun- + count: 4 + +- type: localizedDataset + id: GenericVendGoodbyes + values: + prefix: vending-machine-thanks- + count: 1 + +- type: localizedDataset + id: HappyHonkGoodbyes + values: + prefix: thankyou-happyhonk- + count: 4 + +- type: localizedDataset + id: LawDrobeGoodbyes + values: + prefix: thankyou-lawdrobe- + count: 5 + +- type: localizedDataset + id: NutriMaxGoodbyes + values: + prefix: thankyou-nutrimax- + count: 2 + +- type: localizedDataset + id: SecTechGoodbyes + values: + prefix: thankyou-sectech- + count: 3 + +- type: localizedDataset + id: GetmoreChocolateCorpGoodbyes + values: + prefix: thankyou-snack- + count: 6 + +- type: localizedDataset + id: BodaGoodbyes + values: + prefix: thankyou-sovietsoda- + count: 3 + +- type: localizedDataset + id: SyndieDrobeGoodbyes + values: + prefix: thankyou-syndiedrobe- + count: 5 From 5a25d1cc4b77ed84242075b043ddc6e987dd17f4 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Thu, 30 May 2024 08:09:07 -0800 Subject: [PATCH 04/84] Make mapping actions slightly more useable (#28394) --- Resources/mapping_actions.yml | 157 ++++++++++++++++------------------ 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/Resources/mapping_actions.yml b/Resources/mapping_actions.yml index 9498c3062a..e7ab1b4bf3 100644 --- a/Resources/mapping_actions.yml +++ b/Resources/mapping_actions.yml @@ -1,6 +1,47 @@ +- action: !type:InstantActionComponent + icon: /Textures/Tiles/cropped_parallax.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileId: Space + assignments: + - 0: 8 + name: space +- action: !type:InstantActionComponent + icon: Interface/VerbIcons/delete.svg.192dpi.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + eraser: True + assignments: + - 0: 9 + name: action-name-mapping-erase +- action: !type:InstantActionComponent + icon: /Textures/Tiles/plating.png + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileId: Plating + assignments: + - 0: 7 + name: plating - action: !type:InstantActionComponent icon: - entity: ReinforcedWindow + entity: Grille keywords: [] checkCanInteract: False checkConsciousness: False @@ -9,10 +50,10 @@ temporary: True event: !type:StartPlacementActionEvent placementOption: SnapgridCenter - entityType: ReinforcedWindow + entityType: Grille assignments: - - 0: 3 - name: ReinforcedWindow + - 0: 4 + name: Grille - action: !type:InstantActionComponent icon: entity: WallSolid @@ -28,21 +69,6 @@ assignments: - 0: 0 name: WallSolid -- action: !type:InstantActionComponent - icon: - entity: WallReinforced - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: SnapgridCenter - entityType: WallReinforced - assignments: - - 0: 1 - name: WallReinforced - action: !type:InstantActionComponent icon: entity: Window @@ -58,6 +84,36 @@ assignments: - 0: 2 name: Window +- action: !type:InstantActionComponent + icon: + entity: WallReinforced + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WallReinforced + assignments: + - 0: 1 + name: WallReinforced +- action: !type:InstantActionComponent + icon: + entity: ReinforcedWindow + keywords: [] + checkCanInteract: False + checkConsciousness: False + clientExclusive: True + autoPopulate: False + temporary: True + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ReinforcedWindow + assignments: + - 0: 3 + name: ReinforcedWindow - action: !type:InstantActionComponent icon: entity: Firelock @@ -73,39 +129,6 @@ assignments: - 0: 5 name: Firelock -- action: !type:InstantActionComponent - icon: - entity: Grille - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: SnapgridCenter - entityType: Grille - assignments: - - 0: 4 - name: Grille -- action: !type:InstantActionComponent - icon: Interface/VerbIcons/delete.svg.192dpi.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - eraser: True - assignments: - - 0: 9 - - 1: 9 - - 2: 9 - - 4: 9 - - 5: 9 - - 6: 9 - name: action-name-mapping-erase - action: !type:InstantActionComponent icon: entity: GasPipeStraight @@ -1110,34 +1133,4 @@ assignments: - 0: 6 name: steel floor -- action: !type:InstantActionComponent - icon: /Textures/Tiles/plating.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: AlignTileAny - tileId: Plating - assignments: - - 0: 7 - - 1: 8 - - 2: 8 - name: plating -- action: !type:InstantActionComponent - icon: /Textures/Tiles/cropped_parallax.png - keywords: [] - checkCanInteract: False - checkConsciousness: False - clientExclusive: True - autoPopulate: False - temporary: True - event: !type:StartPlacementActionEvent - placementOption: AlignTileAny - tileId: Space - assignments: - - 0: 8 - name: space ... From 34486f7451465acd4d1e22d792f6fc07f2a25d14 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 10:25:07 -0700 Subject: [PATCH 05/84] Fix firelocks not requiring engineering access (#28415) --- .../Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index c098409f3a..d6815abcdc 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -105,6 +105,8 @@ arc: 360 - type: StaticPrice price: 150 + - type: AccessReader + access: [ [ "Engineering" ] ] - type: PryUnpowered pryModifier: 0.5 From 9163af46b63be9b697c05824d391e0e3f9ac4e70 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 17:26:14 +0000 Subject: [PATCH 06/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 7bbc12d0b9..6614ad8735 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: 778b - changes: - - message: Guns which use battery as magazines now display ammo. Like Svalinn pistol. - type: Fix - id: 6144 - time: '2024-03-13T11:13:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26009 - author: icekot8 changes: - message: Foxes are now neutral and deal 8 piercing damage @@ -3863,3 +3856,11 @@ id: 6643 time: '2024-05-30T07:32:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28405 +- author: ShadowCommander + changes: + - message: Fixed firelocks with warnings enabled opening immediately for characters + without engineering access. + type: Fix + id: 6644 + time: '2024-05-30T17:25:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28415 From 2459572ecc4ecc570ef3aeedf77fe00b93cc8ed6 Mon Sep 17 00:00:00 2001 From: Moony Date: Thu, 30 May 2024 15:45:16 -0500 Subject: [PATCH 07/84] Handheld teleporter portals now must start on the same grid. (#28423) Co-authored-by: moonheart08 --- .../Teleportation/HandTeleporterSystem.cs | 55 +++++++++++++------ .../teleportation/handheld-teleporter.ftl | 1 + 2 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 Resources/Locale/en-US/teleportation/handheld-teleporter.ftl diff --git a/Content.Server/Teleportation/HandTeleporterSystem.cs b/Content.Server/Teleportation/HandTeleporterSystem.cs index 3d988b0916..d4c6753c4b 100644 --- a/Content.Server/Teleportation/HandTeleporterSystem.cs +++ b/Content.Server/Teleportation/HandTeleporterSystem.cs @@ -1,7 +1,9 @@ using Content.Server.Administration.Logs; +using Content.Server.Popups; using Content.Shared.DoAfter; using Content.Shared.Database; using Content.Shared.Interaction.Events; +using Content.Shared.Popups; using Content.Shared.Teleportation.Components; using Content.Shared.Teleportation.Systems; using Robust.Server.Audio; @@ -18,6 +20,7 @@ public sealed class HandTeleporterSystem : EntitySystem [Dependency] private readonly LinkedEntitySystem _link = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doafter = default!; + [Dependency] private readonly PopupSystem _popup = default!; /// public override void Initialize() @@ -92,6 +95,16 @@ public sealed class HandTeleporterSystem : EntitySystem } else if (Deleted(component.SecondPortal)) { + if (xform.ParentUid != xform.GridUid) // Still, don't portal. + return; + + if (xform.ParentUid != Transform(component.FirstPortal!.Value).ParentUid) + { + // Whoops. Fizzle time. Crime time too because yippee I'm not refactoring this logic right now (I started to, I'm not going to.) + FizzlePortals(uid, component, user, true); + return; + } + var timeout = EnsureComp(user); timeout.EnteredPortal = null; component.SecondPortal = Spawn(component.SecondPortalPrototype, Transform(user).Coordinates); @@ -101,22 +114,32 @@ public sealed class HandTeleporterSystem : EntitySystem } else { - // Logging - var portalStrings = ""; - portalStrings += ToPrettyString(component.FirstPortal!.Value); - if (portalStrings != "") - portalStrings += " and "; - portalStrings += ToPrettyString(component.SecondPortal!.Value); - if (portalStrings != "") - _adminLogger.Add(LogType.EntityDelete, LogImpact.Low, $"{ToPrettyString(user):player} closed {portalStrings} with {ToPrettyString(uid)}"); - - // Clear both portals - QueueDel(component.FirstPortal!.Value); - QueueDel(component.SecondPortal!.Value); - - component.FirstPortal = null; - component.SecondPortal = null; - _audio.PlayPvs(component.ClearPortalsSound, uid); + FizzlePortals(uid, component, user, false); } } + + private void FizzlePortals(EntityUid uid, HandTeleporterComponent component, EntityUid user, bool instability) + { + // Logging + var portalStrings = ""; + portalStrings += ToPrettyString(component.FirstPortal); + if (portalStrings != "") + portalStrings += " and "; + portalStrings += ToPrettyString(component.SecondPortal); + if (portalStrings != "") + _adminLogger.Add(LogType.EntityDelete, LogImpact.Low, $"{ToPrettyString(user):player} closed {portalStrings} with {ToPrettyString(uid)}"); + + // Clear both portals + if (!Deleted(component.FirstPortal)) + QueueDel(component.FirstPortal.Value); + if (!Deleted(component.SecondPortal)) + QueueDel(component.SecondPortal.Value); + + component.FirstPortal = null; + component.SecondPortal = null; + _audio.PlayPvs(component.ClearPortalsSound, uid); + + if (instability) + _popup.PopupEntity(Loc.GetString("handheld-teleporter-instability-fizzle"), uid, user, PopupType.MediumCaution); + } } diff --git a/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl b/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl new file mode 100644 index 0000000000..28f526f0d5 --- /dev/null +++ b/Resources/Locale/en-US/teleportation/handheld-teleporter.ftl @@ -0,0 +1 @@ +handheld-teleporter-instability-fizzle = The portal fizzles as you try to place it, destroying both ends! From 9df7f5cd2f8739a26f8c51c2bf971aa0e10bbe74 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 30 May 2024 20:46:22 +0000 Subject: [PATCH 08/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6614ad8735..12aed60d45 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: icekot8 - changes: - - message: Foxes are now neutral and deal 8 piercing damage - type: Tweak - id: 6145 - time: '2024-03-13T15:02:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25992 - author: Mangohydra changes: - message: The lawyer now spawns with their own lawyer stamp for stamping very important @@ -3864,3 +3857,10 @@ id: 6644 time: '2024-05-30T17:25:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28415 +- author: moonheart08 + changes: + - message: The hand teleporter can no longer create portal links between grids. + type: Remove + id: 6645 + time: '2024-05-30T20:45:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28423 From c6b22fb01c159a3c1612860af49e862ada8f3bda Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 17:25:13 -0700 Subject: [PATCH 09/84] Fix TryGetActionData error triggering when run on deleting entities (#27839) * Fix TryGetActionData error triggering when run on deleting entities * Only get metadata once --- Content.Shared/Actions/SharedActionsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index c92d67ba81..bf86d2c1e4 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -112,7 +112,7 @@ public abstract class SharedActionsSystem : EntitySystem bool logError = true) { result = null; - if (!Exists(uid)) + if (uid == null || TerminatingOrDeleted(uid.Value)) return false; var ev = new GetActionDataEvent(); From abfa12e3152173715a369e6efa81c48079e91ded Mon Sep 17 00:00:00 2001 From: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Date: Thu, 30 May 2024 20:28:12 -0400 Subject: [PATCH 10/84] Create sale console computer board (#28425) --- .../Prototypes/Catalog/Fills/Lockers/heads.yml | 1 + .../Objects/Devices/Circuitboards/computer.yml | 13 ++++++++++++- .../Structures/Machines/Computers/computers.yml | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index e38d57e6d8..11863ea51d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -7,6 +7,7 @@ contents: - id: BoxFolderQmClipboard - id: CargoRequestComputerCircuitboard + - id: CargoSaleComputerCircuitboard - id: CargoShuttleComputerCircuitboard - id: CargoShuttleConsoleCircuitboard - id: SalvageShuttleConsoleCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 93aff069f0..d3c58a0fe7 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -84,6 +84,17 @@ - type: StaticPrice price: 750 +- type: entity + parent: BaseComputerCircuitboard + id: CargoSaleComputerCircuitboard + name: cargo sale computer board + description: A computer printed circuit board for a cargo sale computer. + components: + - type: Sprite + state: cpu_supply + - type: ComputerBoard + prototype: ComputerPalletConsole + - type: entity id: CargoBountyComputerCircuitboard parent: BaseComputerCircuitboard @@ -394,4 +405,4 @@ - type: Sprite state: cpu_science - type: ComputerBoard - prototype: ComputerRoboticsControl + prototype: ComputerRoboticsControl \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index b5c7a1a19c..abb12f9313 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -993,7 +993,7 @@ enum.CargoPalletConsoleUiKey.Sale: type: CargoPalletConsoleBoundUserInterface - type: Computer - board: CargoRequestComputerCircuitboard + board: CargoSaleComputerCircuitboard - type: PointLight radius: 1.5 energy: 1.6 @@ -1114,4 +1114,4 @@ - type: AccessReader # only used for dangerous things access: [["ResearchDirector"]] - type: Lock - unlockOnClick: false + unlockOnClick: false \ No newline at end of file From 75ef8e4fec7cb1e2395fe93138e6ebc592c106e7 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 31 May 2024 00:29:18 +0000 Subject: [PATCH 11/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 12aed60d45..17018395a2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Mangohydra - changes: - - message: The lawyer now spawns with their own lawyer stamp for stamping very important - documents. - type: Add - id: 6146 - time: '2024-03-13T15:05:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26012 - author: Errant changes: - message: Clothes with alternate sprites for vox once again show the alternate @@ -3864,3 +3856,10 @@ id: 6645 time: '2024-05-30T20:45:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28423 +- author: Ghagliiarghii + changes: + - message: Cargo Sale Computer now correctly uses its own computer board + type: Fix + id: 6646 + time: '2024-05-31T00:28:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28425 From d90cbee7f21e115b3b364bcb86c0085208c5b0d9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 31 May 2024 11:55:01 +1000 Subject: [PATCH 12/84] Fix verb categories shuffling (#28368) If it's an extra category we leave it in its default spot. --- .../Verbs/UI/VerbMenuUIController.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Content.Client/Verbs/UI/VerbMenuUIController.cs b/Content.Client/Verbs/UI/VerbMenuUIController.cs index c3fc8c8356..e9c3f90641 100644 --- a/Content.Client/Verbs/UI/VerbMenuUIController.cs +++ b/Content.Client/Verbs/UI/VerbMenuUIController.cs @@ -8,6 +8,7 @@ using Content.Shared.Verbs; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Collections; using Robust.Shared.Input; using Robust.Shared.Utility; @@ -115,6 +116,13 @@ namespace Content.Client.Verbs.UI private void FillVerbPopup(ContextMenuPopup popup) { HashSet listedCategories = new(); + var extras = new ValueList(ExtraCategories.Count); + + foreach (var cat in ExtraCategories) + { + extras.Add(cat.Text); + } + foreach (var verb in CurrentVerbs) { if (verb.Category == null) @@ -122,17 +130,15 @@ namespace Content.Client.Verbs.UI var element = new VerbMenuElement(verb); _context.AddElement(popup, element); } - else if (listedCategories.Add(verb.Category.Text)) + // Add the category if it's not an extra (this is to avoid shuffling if we're filling from server verbs response). + else if (!extras.Contains(verb.Category.Text) && listedCategories.Add(verb.Category.Text)) AddVerbCategory(verb.Category, popup); } - if (ExtraCategories != null) + foreach (var category in ExtraCategories) { - foreach (var category in ExtraCategories) - { - if (listedCategories.Add(category.Text)) - AddVerbCategory(category, popup); - } + if (listedCategories.Add(category.Text)) + AddVerbCategory(category, popup); } popup.InvalidateMeasure(); From 6b19d0a62c415302a2333b0d387529f074da04ad Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 31 May 2024 01:56:07 +0000 Subject: [PATCH 13/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 17018395a2..e54095c43c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Errant - changes: - - message: Clothes with alternate sprites for vox once again show the alternate - sprites, instead of the defaults. - type: Fix - id: 6147 - time: '2024-03-13T15:06:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25989 - author: Gyrandola changes: - message: Fireaxe and Shotgun cabinets can now be destroyed. @@ -3863,3 +3855,10 @@ id: 6646 time: '2024-05-31T00:28:12.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28425 +- author: metalgearsloth + changes: + - message: Fix verb categories shuffling around. + type: Fix + id: 6647 + time: '2024-05-31T01:55:01.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28368 From 7e8e145d075c96705d4678c237b2876225e0da45 Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 18:59:25 -0700 Subject: [PATCH 14/84] Reflow the player tab to show more of the names (#28029) --- .../Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml | 7 ++++--- .../Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml index 8ac90305ca..f9ed57792e 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml @@ -15,16 +15,17 @@ ClipText="True"/> - public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) + public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session?.AttachedEntity, def)) - { + if (checkPref && !HasPrimaryAntagPreference(session, def)) + return false; + + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def)) return false; - } MakeAntag(ent, session, def, ignoreSpawner); return true; @@ -338,16 +342,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem(); foreach (var session in sessions) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session.AttachedEntity, def)) + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def)) continue; - var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; - if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p))) + if (HasPrimaryAntagPreference(session, def)) { preferredList.Add(session); } - else if (def.FallbackRoles.Count != 0 && pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p))) + else if (HasFallbackAntagPreference(session, def)) { fallbackList.Add(session); } @@ -418,13 +420,13 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem Date: Fri, 31 May 2024 02:07:27 +0000 Subject: [PATCH 17/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 45634249a7..03741c1714 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: Surgery tools can be placed in medical belt. - type: Tweak - - message: Lantern can be placed in utility belt. - type: Tweak - - message: Hand labeler can be placed in utility and botanical belt - type: Tweak - id: 6149 - time: '2024-03-13T18:55:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26085 - author: wafehling changes: - message: The trading outpost now has dedicated buy-only and sell-only pallets. @@ -3863,3 +3852,11 @@ id: 6648 time: '2024-05-31T01:59:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28029 +- author: EmoGarbage404 + changes: + - message: Fixed latejoin antagonists not respecting antag preferences (for real + this time) + type: Fix + id: 6649 + time: '2024-05-31T02:06:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28430 From 54337911d3995075caf4bbc6b6d768a93e8c1d0a Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 14:28:11 +1200 Subject: [PATCH 18/84] Only auto-enable internals when necessary (#28248) * Only auto-enable internals when necessary * Add toxic gas check * Rename Any -> AnyPositive --- .../AtmosphereSystem.ExcitedGroup.cs | 2 +- .../Body/Systems/InternalsSystem.cs | 25 +++- Content.Server/Body/Systems/LungSystem.cs | 28 +++-- .../Body/Systems/RespiratorSystem.cs | 118 +++++++++++++++++- .../ReagentEffectConditions/OrganType.cs | 12 +- .../Damage/Systems/DamageOnLandSystem.cs | 2 +- .../Electrocution/ElectrocutionSystem.cs | 4 +- .../ExplosionSystem.Processing.cs | 2 +- .../Projectiles/ProjectileSystem.cs | 2 +- .../Station/Systems/StationSpawningSystem.cs | 2 +- .../EntitySystems/BluespaceLockerSystem.cs | 4 +- .../Weapons/Ranged/Systems/GunSystem.cs | 2 +- Content.Shared/Atmos/GasMixture.cs | 12 +- Content.Shared/Damage/DamageSpecifier.cs | 4 +- .../Station/SharedStationSpawningSystem.cs | 2 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 2 +- 16 files changed, 189 insertions(+), 34 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index c1b58f7a77..0d622f3067 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -106,7 +106,7 @@ namespace Content.Server.Atmos.EntitySystems if (tile?.Air == null) continue; - tile.Air.CopyFromMutable(combined); + tile.Air.CopyFrom(combined); InvalidateVisuals(ent, tile); } diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index c1e1de2baa..b79e083bd4 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -23,6 +23,7 @@ public sealed class InternalsSystem : EntitySystem [Dependency] private readonly GasTankSystem _gasTank = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly RespiratorSystem _respirator = default!; private EntityQuery _internalsQuery; @@ -38,15 +39,30 @@ public sealed class InternalsSystem : EntitySystem SubscribeLocalEvent>(OnGetInteractionVerbs); SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnStartingGear); + SubscribeLocalEvent(OnStartingGear); } - private void OnStartingGear(ref StartingGearEquippedEvent ev) + private void OnStartingGear(EntityUid uid, InternalsComponent component, ref StartingGearEquippedEvent args) { - if (!_internalsQuery.TryComp(ev.Entity, out var internals) || internals.BreathToolEntity == null) + if (component.BreathToolEntity == null) return; - ToggleInternals(ev.Entity, ev.Entity, force: false, internals); + if (component.GasTankEntity != null) + return; // already connected + + // Can the entity breathe the air it is currently exposed to? + if (_respirator.CanMetabolizeInhaledAir(uid)) + return; + + var tank = FindBestGasTank(uid); + if (tank == null) + return; + + // Could the entity metabolise the air in the linked gas tank? + if (!_respirator.CanMetabolizeGas(uid, tank.Value.Comp.Air)) + return; + + ToggleInternals(uid, uid, force: false, component); } private void OnGetInteractionVerbs( @@ -243,6 +259,7 @@ public sealed class InternalsSystem : EntitySystem public Entity? FindBestGasTank( Entity user) { + // TODO use _respirator.CanMetabolizeGas() to prioritize metabolizable gasses // Prioritise // 1. back equipped tanks // 2. exo-slot tanks diff --git a/Content.Server/Body/Systems/LungSystem.cs b/Content.Server/Body/Systems/LungSystem.cs index e83d3c32a2..7e58c24f7e 100644 --- a/Content.Server/Body/Systems/LungSystem.cs +++ b/Content.Server/Body/Systems/LungSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Chemistry.Components; using Content.Shared.Clothing; using Content.Shared.Inventory.Events; @@ -77,23 +78,32 @@ public sealed class LungSystem : EntitySystem if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution)) return; - foreach (var gas in Enum.GetValues()) + GasToReagent(lung.Air, solution); + _solutionContainerSystem.UpdateChemicals(lung.Solution.Value); + } + + private void GasToReagent(GasMixture gas, Solution solution) + { + foreach (var gasId in Enum.GetValues()) { - var i = (int) gas; - var moles = lung.Air[i]; + var i = (int) gasId; + var moles = gas[i]; if (moles <= 0) continue; + var reagent = _atmosphereSystem.GasReagents[i]; - if (reagent is null) continue; + if (reagent is null) + continue; var amount = moles * Atmospherics.BreathMolesToReagentMultiplier; solution.AddReagent(reagent, amount); - - // We don't remove the gas from the lung mix, - // that's the responsibility of whatever gas is being metabolized. - // Most things will just want to exhale again. } + } - _solutionContainerSystem.UpdateChemicals(lung.Solution.Value); + public Solution GasToReagent(GasMixture gas) + { + var solution = new Solution(); + GasToReagent(gas, solution); + return solution; } } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index a46294beb4..4e6c02edbd 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -1,16 +1,21 @@ using Content.Server.Administration.Logs; -using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Chemistry.ReagentEffectConditions; +using Content.Server.Chemistry.ReagentEffects; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Body.Components; +using Content.Shared.Body.Prototypes; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Mobs.Systems; using JetBrains.Annotations; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Server.Body.Systems; @@ -26,9 +31,12 @@ public sealed class RespiratorSystem : EntitySystem [Dependency] private readonly DamageableSystem _damageableSys = default!; [Dependency] private readonly LungSystem _lungSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly ChatSystem _chat = default!; + private static readonly ProtoId GasId = new("Gas"); + public override void Initialize() { base.Initialize(); @@ -109,7 +117,7 @@ public sealed class RespiratorSystem : EntitySystem // Inhale gas var ev = new InhaleLocationEvent(); - RaiseLocalEvent(uid, ref ev, broadcast: false); + RaiseLocalEvent(uid, ref ev); ev.Gas ??= _atmosSys.GetContainingMixture(uid, excite: true); @@ -164,6 +172,112 @@ public sealed class RespiratorSystem : EntitySystem _atmosSys.Merge(ev.Gas, outGas); } + /// + /// Check whether or not an entity can metabolize inhaled air without suffocating or taking damage (i.e., no toxic + /// gasses). + /// + public bool CanMetabolizeInhaledAir(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var ev = new InhaleLocationEvent(); + RaiseLocalEvent(ent, ref ev); + + var gas = ev.Gas ?? _atmosSys.GetContainingMixture(ent.Owner); + if (gas == null) + return false; + + return CanMetabolizeGas(ent, gas); + } + + /// + /// Check whether or not an entity can metabolize the given gas mixture without suffocating or taking damage + /// (i.e., no toxic gasses). + /// + public bool CanMetabolizeGas(Entity ent, GasMixture gas) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + var organs = _bodySystem.GetBodyOrganComponents(ent); + if (organs.Count == 0) + return false; + + gas = new GasMixture(gas); + var lungRatio = 1.0f / organs.Count; + gas.Multiply(MathF.Min(lungRatio * gas.Volume/Atmospherics.BreathVolume, lungRatio)); + var solution = _lungSystem.GasToReagent(gas); + + float saturation = 0; + foreach (var organ in organs) + { + saturation += GetSaturation(solution, organ.Comp.Owner, out var toxic); + if (toxic) + return false; + } + + return saturation > ent.Comp.UpdateInterval.TotalSeconds; + } + + /// + /// Get the amount of saturation that would be generated if the lung were to metabolize the given solution. + /// + /// + /// This assumes the metabolism rate is unbounded, which generally should be the case for lungs, otherwise we get + /// back to the old pulmonary edema bug. + /// + /// The reagents to metabolize + /// The entity doing the metabolizing + /// Whether or not any of the reagents would deal damage to the entity + private float GetSaturation(Solution solution, Entity lung, out bool toxic) + { + toxic = false; + if (!Resolve(lung, ref lung.Comp)) + return 0; + + if (lung.Comp.MetabolismGroups == null) + return 0; + + float saturation = 0; + foreach (var (id, quantity) in solution.Contents) + { + var reagent = _protoMan.Index(id.Prototype); + if (reagent.Metabolisms == null) + continue; + + if (!reagent.Metabolisms.TryGetValue(GasId, out var entry)) + continue; + + foreach (var effect in entry.Effects) + { + if (effect is HealthChange health) + toxic |= CanMetabolize(health) && health.Damage.AnyPositive(); + else if (effect is Oxygenate oxy && CanMetabolize(oxy)) + saturation += oxy.Factor * quantity.Float(); + } + } + + // TODO generalize condition checks + // this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture + // Applying actual reaction effects require a full ReagentEffectArgs struct. + bool CanMetabolize(ReagentEffect effect) + { + if (effect.Conditions == null) + return true; + + foreach (var cond in effect.Conditions) + { + if (cond is OrganType organ && !organ.Condition(lung, EntityManager)) + return false; + } + + return true; + } + + return saturation; + } + private void TakeSuffocationDamage(Entity ent) { if (ent.Comp.SuffocationCycles == 2) diff --git a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs index 4ae13b6a6e..986c3d79c8 100644 --- a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs +++ b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs @@ -25,9 +25,15 @@ namespace Content.Server.Chemistry.ReagentEffectConditions if (args.OrganEntity == null) return false; - if (args.EntityManager.TryGetComponent(args.OrganEntity.Value, out var metabolizer) - && metabolizer.MetabolizerTypes != null - && metabolizer.MetabolizerTypes.Contains(Type)) + return Condition(args.OrganEntity.Value, args.EntityManager); + } + + public bool Condition(Entity metabolizer, IEntityManager entMan) + { + metabolizer.Comp ??= entMan.GetComponentOrNull(metabolizer.Owner); + if (metabolizer.Comp != null + && metabolizer.Comp.MetabolizerTypes != null + && metabolizer.Comp.MetabolizerTypes.Contains(Type)) return ShouldHave; return !ShouldHave; } diff --git a/Content.Server/Damage/Systems/DamageOnLandSystem.cs b/Content.Server/Damage/Systems/DamageOnLandSystem.cs index 8f01e791ea..2e72e76e6d 100644 --- a/Content.Server/Damage/Systems/DamageOnLandSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnLandSystem.cs @@ -22,7 +22,7 @@ namespace Content.Server.Damage.Systems private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) { // Allow healing projectiles, forbid any that do damage: - if (ent.Comp.Damage.Any()) + if (ent.Comp.Damage.AnyPositive()) { args.Cancel("pacified-cannot-throw"); } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 8291e97efe..67e60c9de4 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -166,7 +166,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem if (!electrified.OnAttacked) return; - if (!_meleeWeapon.GetDamage(args.Used, args.User).Any()) + if (_meleeWeapon.GetDamage(args.Used, args.User).Empty) return; TryDoElectrifiedAct(uid, args.User, 1, electrified); @@ -183,7 +183,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem if (!component.CurrentLit || args.Used != args.User) return; - if (!_meleeWeapon.GetDamage(args.Used, args.User).Any()) + if (_meleeWeapon.GetDamage(args.Used, args.User).Empty) return; DoCommonElectrocution(args.User, uid, component.UnarmedHitShock, component.UnarmedHitStun, false); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index a93157a175..bce3dc21c2 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -396,7 +396,7 @@ public sealed partial class ExplosionSystem // don't raise BeforeExplodeEvent if the entity is completely immune to explosions var thisDamage = GetDamage(uid, prototype, originalDamage); - if (!thisDamage.Any()) + if (thisDamage.Empty) return; _toDamage.Add((uid, thisDamage)); diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index f8c8ef64b7..970536f42b 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -51,7 +51,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter)) { - if (modifiedDamage.Any() && !deleted) + if (modifiedDamage.AnyPositive() && !deleted) { _color.RaiseEffect(Color.Red, new List { target }, Filter.Pvs(target, entityManager: EntityManager)); } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 05f5cc58bc..f175565a5a 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -204,7 +204,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem } var gearEquippedEv = new StartingGearEquippedEvent(entity.Value); - RaiseLocalEvent(entity.Value, ref gearEquippedEv, true); + RaiseLocalEvent(entity.Value, ref gearEquippedEv); if (profile != null) { diff --git a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs index 838311c1aa..9da7606bcc 100644 --- a/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs +++ b/Content.Server/Storage/EntitySystems/BluespaceLockerSystem.cs @@ -109,7 +109,7 @@ public sealed class BluespaceLockerSystem : EntitySystem // Move contained air if (component.BehaviorProperties.TransportGas) { - entityStorageComponent.Air.CopyFromMutable(target.Value.storageComponent.Air); + entityStorageComponent.Air.CopyFrom(target.Value.storageComponent.Air); target.Value.storageComponent.Air.Clear(); } @@ -326,7 +326,7 @@ public sealed class BluespaceLockerSystem : EntitySystem // Move contained air if (component.BehaviorProperties.TransportGas) { - target.Value.storageComponent.Air.CopyFromMutable(entityStorageComponent.Air); + target.Value.storageComponent.Air.CopyFrom(entityStorageComponent.Air); entityStorageComponent.Air.Clear(); } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 986cac98dd..7247109e37 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -237,7 +237,7 @@ public sealed partial class GunSystem : SharedGunSystem { if (!Deleted(hitEntity)) { - if (dmg.Any()) + if (dmg.AnyPositive()) { _color.RaiseEffect(Color.Red, new List() { hitEntity }, Filter.Pvs(hitEntity, entityManager: EntityManager)); } diff --git a/Content.Shared/Atmos/GasMixture.cs b/Content.Shared/Atmos/GasMixture.cs index a676ed6720..0f1efba976 100644 --- a/Content.Shared/Atmos/GasMixture.cs +++ b/Content.Shared/Atmos/GasMixture.cs @@ -96,6 +96,11 @@ namespace Content.Shared.Atmos Volume = volume; } + public GasMixture(GasMixture toClone) + { + CopyFrom(toClone); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MarkImmutable() { @@ -197,9 +202,12 @@ namespace Content.Shared.Atmos } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFromMutable(GasMixture sample) + public void CopyFrom(GasMixture sample) { - if (Immutable) return; + if (Immutable) + return; + + Volume = sample.Volume; sample.Moles.CopyTo(Moles, 0); Temperature = sample.Temperature; } diff --git a/Content.Shared/Damage/DamageSpecifier.cs b/Content.Shared/Damage/DamageSpecifier.cs index 8ab9116a97..7f505b807f 100644 --- a/Content.Shared/Damage/DamageSpecifier.cs +++ b/Content.Shared/Damage/DamageSpecifier.cs @@ -43,7 +43,7 @@ namespace Content.Shared.Damage /// /// /// Note that this being zero does not mean this damage has no effect. Healing in one type may cancel damage - /// in another. Consider using or instead. + /// in another. Consider using or instead. /// public FixedPoint2 GetTotal() { @@ -60,7 +60,7 @@ namespace Content.Shared.Damage /// Differs from as a damage specifier might contain entries with zeroes. /// This also returns false if the specifier only contains negative values. /// - public bool Any() + public bool AnyPositive() { foreach (var value in DamageDict.Values) { diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index acf00f7c6b..f352c9db63 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -142,7 +142,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem if (raiseEvent) { var ev = new StartingGearEquippedEvent(entity); - RaiseLocalEvent(entity, ref ev, true); + RaiseLocalEvent(entity, ref ev); } } } diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 7fc440db47..2d9993096b 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -499,7 +499,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user); - if (damageResult != null && damageResult.Any()) + if (damageResult is {Empty: false}) { // If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage)) From 27e63f5919b36102683420cabd0152ef83e18122 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 14:44:35 +1200 Subject: [PATCH 19/84] Make tests automatically reset modified cvars (#28219) * Make tests automatically reset modified cvars * Fix bad return * A * Try Fix tests * clarify comment * update eng --- .../Pair/TestPair.Cvars.cs | 69 +++++++++++++++++++ .../Pair/TestPair.Recycle.cs | 3 + Content.IntegrationTests/Pair/TestPair.cs | 4 ++ Content.IntegrationTests/PoolManager.cs | 2 + .../Tests/GameRules/NukeOpsTest.cs | 2 - .../Administration/Systems/AdminSystem.cs | 13 ++++ 6 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Content.IntegrationTests/Pair/TestPair.Cvars.cs diff --git a/Content.IntegrationTests/Pair/TestPair.Cvars.cs b/Content.IntegrationTests/Pair/TestPair.Cvars.cs new file mode 100644 index 0000000000..81df31fc9a --- /dev/null +++ b/Content.IntegrationTests/Pair/TestPair.Cvars.cs @@ -0,0 +1,69 @@ +#nullable enable +using System.Collections.Generic; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Utility; + +namespace Content.IntegrationTests.Pair; + +public sealed partial class TestPair +{ + private readonly Dictionary _modifiedClientCvars = new(); + private readonly Dictionary _modifiedServerCvars = new(); + + private void OnServerCvarChanged(CVarChangeInfo args) + { + _modifiedServerCvars.TryAdd(args.Name, args.OldValue); + } + + private void OnClientCvarChanged(CVarChangeInfo args) + { + _modifiedClientCvars.TryAdd(args.Name, args.OldValue); + } + + internal void ClearModifiedCvars() + { + _modifiedClientCvars.Clear(); + _modifiedServerCvars.Clear(); + } + + /// + /// Reverts any cvars that were modified during a test back to their original values. + /// + public async Task RevertModifiedCvars() + { + await Server.WaitPost(() => + { + foreach (var (name, value) in _modifiedServerCvars) + { + if (Server.CfgMan.GetCVar(name).Equals(value)) + continue; + Server.Log.Info($"Resetting cvar {name} to {value}"); + Server.CfgMan.SetCVar(name, value); + } + + // I just love order dependent cvars + if (_modifiedServerCvars.TryGetValue(CCVars.PanicBunkerEnabled.Name, out var panik)) + Server.CfgMan.SetCVar(CCVars.PanicBunkerEnabled.Name, panik); + + }); + + await Client.WaitPost(() => + { + foreach (var (name, value) in _modifiedClientCvars) + { + if (Client.CfgMan.GetCVar(name).Equals(value)) + continue; + + var flags = Client.CfgMan.GetCVarFlags(name); + if (flags.HasFlag(CVar.REPLICATED) && flags.HasFlag(CVar.SERVER)) + continue; + + Client.Log.Info($"Resetting cvar {name} to {value}"); + Client.CfgMan.SetCVar(name, value); + } + }); + + ClearModifiedCvars(); + } +} diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index c0f4b3b745..8d1e425553 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -40,6 +40,8 @@ public sealed partial class TestPair : IAsyncDisposable TestMap = null; } + await RevertModifiedCvars(); + var usageTime = Watch.Elapsed; Watch.Restart(); await _testOut.WriteLineAsync($"{nameof(CleanReturnAsync)}: Test borrowed pair {Id} for {usageTime.TotalMilliseconds} ms"); @@ -132,6 +134,7 @@ public sealed partial class TestPair : IAsyncDisposable if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby) { await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round."); + Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false); Assert.That(gameTicker.DummyTicker, Is.False); Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true); await Server.WaitPost(() => gameTicker.RestartRound()); diff --git a/Content.IntegrationTests/Pair/TestPair.cs b/Content.IntegrationTests/Pair/TestPair.cs index 916a94c9c4..7ee5dbd55c 100644 --- a/Content.IntegrationTests/Pair/TestPair.cs +++ b/Content.IntegrationTests/Pair/TestPair.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Content.Server.GameTicking; using Content.Shared.Players; +using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Network; @@ -58,6 +59,9 @@ public sealed partial class TestPair (Server, ServerLogHandler) = await PoolManager.GenerateServer(settings, testOut); ActivateContext(testOut); + Client.CfgMan.OnCVarValueChanged += OnClientCvarChanged; + Server.CfgMan.OnCVarValueChanged += OnServerCvarChanged; + if (!settings.NoLoadTestPrototypes) await LoadPrototypes(testPrototypes!); diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 3b49ffcf84..21c93ea8ae 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -270,6 +270,8 @@ public static partial class PoolManager $"{nameof(GetServerClientPair)}: Retrieving pair {pair.Id} from pool took {poolRetrieveTime.TotalMilliseconds} ms"); await testOut.WriteLineAsync( $"{nameof(GetServerClientPair)}: Returning pair {pair.Id}"); + + pair.ClearModifiedCvars(); pair.Settings = poolSettings; pair.TestHistory.Add(currentTestName); pair.Watch.Restart(); diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index 3a77834b72..ea19a30005 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -50,7 +50,6 @@ public sealed class NukeOpsTest var invSys = server.System(); var factionSys = server.System(); - Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); server.CfgMan.SetCVar(CCVars.GridFill, true); // Initially in the lobby @@ -200,7 +199,6 @@ public sealed class NukeOpsTest } ticker.SetGamePreset((GamePresetPrototype?)null); - server.CfgMan.SetCVar(CCVars.GridFill, false); await pair.SetAntagPref("NukeopsCommander", false); await pair.CleanReturnAsync(); } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index b7ca4e915b..a1a6b33ebc 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -292,6 +292,19 @@ namespace Content.Server.Administration.Systems : _adminManager.ActiveAdmins; var hasAdmins = admins.Any(); + // TODO Fix order dependent Cvars + // Please for the sake of my sanity don't make cvars & order dependent. + // Just make a bool field on the system instead of having some cvars automatically modify other cvars. + // + // I.e., this: + // /sudo cvar game.panic_bunker.enabled true + // /sudo cvar game.panic_bunker.disable_with_admins true + // and this: + // /sudo cvar game.panic_bunker.disable_with_admins true + // /sudo cvar game.panic_bunker.enabled true + // + // should have the same effect, but currently setting the disable_with_admins can modify enabled. + if (hasAdmins && PanicBunker.DisableWithAdmins) { _config.SetCVar(CCVars.PanicBunkerEnabled, false); From 07866e47644e010733a2d1be025a27ba4b775bf3 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 31 May 2024 15:11:21 +1200 Subject: [PATCH 20/84] Prevent Identity.Name NRE exception (#28433) Fix Identity.Name exception --- Content.Client/Inventory/StrippableBoundUserInterface.cs | 5 ++++- Content.Shared/IdentityManagement/Identity.cs | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs index 33f38688ed..7e50eb1c68 100644 --- a/Content.Client/Inventory/StrippableBoundUserInterface.cs +++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs @@ -21,7 +21,6 @@ using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using static Content.Client.Inventory.ClientInventorySystem; using static Robust.Client.UserInterface.Control; @@ -53,9 +52,13 @@ namespace Content.Client.Inventory _inv = EntMan.System(); _cuffable = EntMan.System(); + // TODO update name when identity changes var title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan))); _strippingMenu = new StrippingMenu(title, this); _strippingMenu.OnClose += Close; + + // TODO use global entity + // BUIs are opened and closed while applying comp sates, so spawning entities here is probably not the best idea. _virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace); } diff --git a/Content.Shared/IdentityManagement/Identity.cs b/Content.Shared/IdentityManagement/Identity.cs index 20220e0326..2fb53e3d5a 100644 --- a/Content.Shared/IdentityManagement/Identity.cs +++ b/Content.Shared/IdentityManagement/Identity.cs @@ -15,7 +15,11 @@ public static class Identity /// public static string Name(EntityUid uid, IEntityManager ent, EntityUid? viewer=null) { - var uidName = ent.GetComponent(uid).EntityName; + var meta = ent.GetComponent(uid); + if (meta.EntityLifeStage <= EntityLifeStage.Initializing) + return meta.EntityName; // Identity component and such will not yet have initialized and may throw NREs + + var uidName = meta.EntityName; if (!ent.TryGetComponent(uid, out var identity)) return uidName; @@ -34,7 +38,7 @@ public static class Identity return uidName; } - return uidName + $" ({identName})"; + return $"{uidName} ({identName})"; } /// From dbcdefc5fdf2fba6559619ef0526729bcf73b19c Mon Sep 17 00:00:00 2001 From: Flesh <62557990+PolterTzi@users.noreply.github.com> Date: Fri, 31 May 2024 05:37:04 +0200 Subject: [PATCH 21/84] Fix trailing periods in some plant metabolism descriptions (#28428) fxied tpyos --- Resources/Locale/en-US/guidebook/chemistry/effects.ftl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl index 1dacebd134..ba005e6bf9 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl @@ -373,10 +373,10 @@ reagent-effect-guidebook-plant-diethylamine = { $chance -> [1] Increases *[other] increase - } the plant's lifespan and/or base health with 10% chance for each. + } the plant's lifespan and/or base health with 10% chance for each reagent-effect-guidebook-plant-robust-harvest = { $chance -> [1] Increases *[other] increase - } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance. + } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance From 3ed1ee6a5bd6a7fc3409bdc06079465a3f9d3cbf Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 30 May 2024 23:28:08 -0700 Subject: [PATCH 22/84] Add search filter to the admin menu player tab (#28030) --- .../UI/Bwoink/BwoinkWindow.xaml.cs | 15 ++- .../CustomControls/PlayerListControl.xaml.cs | 9 +- .../UI/Tabs/PlayerTab/PlayerTab.xaml | 26 ++--- .../UI/Tabs/PlayerTab/PlayerTab.xaml.cs | 108 +++++++++++++----- .../UI/Tabs/PlayerTab/PlayerTabEntry.xaml | 8 +- .../UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs | 26 +++-- .../UI/Tabs/PlayerTab/PlayerTabHeader.xaml | 3 +- .../UserInterface/Controls/ListContainer.cs | 39 +++++-- .../Controls/SearchListContainer.cs | 68 +++++++++++ .../Systems/Admin/AdminUIController.cs | 9 +- .../Locale/en-US/administration/bwoink.ftl | 2 + .../administration/ui/tabs/player-tab.ftl | 3 + 12 files changed, 236 insertions(+), 80 deletions(-) create mode 100644 Content.Client/UserInterface/Controls/SearchListContainer.cs diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs index f8d06f758f..999eba4d29 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs @@ -16,14 +16,17 @@ namespace Content.Client.Administration.UI.Bwoink Bwoink.ChannelSelector.OnSelectionChanged += sel => { - if (sel is not null) + if (sel is null) { - Title = $"{sel.CharacterName} / {sel.Username}"; + Title = Loc.GetString("bwoink-none-selected"); + return; + } - if (sel.OverallPlaytime != null) - { - Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; - } + Title = $"{sel.CharacterName} / {sel.Username}"; + + if (sel.OverallPlaytime != null) + { + Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; } }; diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs index fdf935d7c0..12522d552d 100644 --- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs +++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs @@ -20,7 +20,7 @@ namespace Content.Client.Administration.UI.CustomControls private List _playerList = new(); private readonly List _sortedPlayerList = new(); - public event Action? OnSelectionChanged; + public event Action? OnSelectionChanged; public IReadOnlyList PlayerInfo => _playerList; public Func? OverrideText; @@ -41,12 +41,19 @@ namespace Content.Client.Administration.UI.CustomControls PlayerListContainer.ItemPressed += PlayerListItemPressed; PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown; PlayerListContainer.GenerateItem += GenerateButton; + PlayerListContainer.NoItemSelected += PlayerListNoItemSelected; PopulateList(_adminSystem.PlayerList); FilterLineEdit.OnTextChanged += _ => FilterList(); _adminSystem.PlayerListChanged += PopulateList; BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)}; } + private void PlayerListNoItemSelected() + { + _selectedPlayer = null; + OnSelectionChanged?.Invoke(null); + } + private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data) { if (args == null || data is not PlayerListData {Info: var selectedPlayer}) diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml index 3071bf8358..25a96df1d3 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml @@ -1,21 +1,19 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> - Pii = 1 << 18, + /// + /// Lets you take moderator actions on the game server. + /// + Moderator = 1 << 19, + + /// + /// Lets you check currently online admins. + /// + AdminWho = 1 << 20, + + /// + /// Lets you set the color of your OOC name. + /// + NameColor = 1 << 21, + /// /// Dangerous host permissions like scsi. /// diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 42cc4668a9..b68a2b22df 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -90,12 +90,12 @@ - Flags: ADMIN Commands: - - delete - - kick - listplayers - tp - tpto - - respawn + +- Flags: FUN + Commands: - tippy - tip @@ -111,6 +111,12 @@ Commands: - spawn - cspawn + - delete + +- Flags: MODERATOR + Commands: + - kick + - respawn - Flags: HOST Commands: From 727a176ca80f65221d62dd0f4906120e24f9a4d6 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 08:15:50 +0000 Subject: [PATCH 57/84] Automatic changelog update --- Resources/Changelog/Admin.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 24fc59b3eb..2964e8c11b 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -272,5 +272,14 @@ Entries: id: 33 time: '2024-06-01T07:23:54.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/27776 +- author: ShadowCommander, EmoGarbage + changes: + - message: 'Added new permissions: moderator, adminwho, and namecolor' + type: Add + - message: Changed various command perms from admin to moderator & fun. + type: Tweak + id: 34 + time: '2024-06-01T08:14:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28451 Name: Admin Order: 1 From e3a66136bfad05d09028fe3196ce7221ba3fb2f1 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:35:14 -0700 Subject: [PATCH 58/84] Fix the client thinking it cannot shoot after mispredicting when it actually can (#28464) --- .../Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 6242312b07..784dd0793a 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -186,6 +186,7 @@ public abstract partial class SharedGunSystem !Paused(uid)) { gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified); + Dirty(uid, gunComp); } Dirty(uid, component); From 19be94c9ea184710218aaddff8c9b91237735042 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 05:08:31 -0700 Subject: [PATCH 59/84] Add job whitelist system (#28085) * Add job whitelist system * Address reviews * Fix name * Apply suggestions from code review Co-authored-by: Pieter-Jan Briers * cancinium --------- Co-authored-by: Pieter-Jan Briers --- Content.Client/Lobby/LobbyUIController.cs | 17 +- .../JobRequirementsManager.cs | 28 + Content.IntegrationTests/PoolManager.Cvars.cs | 1 + .../20240531011555_RoleWhitelist.Designer.cs | 1913 +++++++++++++++++ .../Postgres/20240531011555_RoleWhitelist.cs | 40 + .../PostgresServerDbContextModelSnapshot.cs | 31 + .../20240531011549_RoleWhitelist.Designer.cs | 1838 ++++++++++++++++ .../Sqlite/20240531011549_RoleWhitelist.cs | 40 + .../SqliteServerDbContextModelSnapshot.cs | 31 + Content.Server.Database/Model.cs | 20 + .../Commands/JobWhitelistCommands.cs | 214 ++ .../Administration/Managers/BanManager.cs | 8 +- .../Administration/Managers/IBanManager.cs | 4 +- Content.Server/Database/ServerDbBase.cs | 61 + Content.Server/Database/ServerDbManager.cs | 38 + Content.Server/Database/UserDbDataManager.cs | 50 +- Content.Server/Entry/EntryPoint.cs | 2 + .../Events/GetDisallowedJobsEvent.cs | 8 + .../GameTicking/Events/IsJobAllowedEvent.cs | 13 + .../GameTicking/GameTicker.Spawning.cs | 19 +- Content.Server/GameTicking/GameTicker.cs | 1 - Content.Server/IoC/ServerContentIoC.cs | 2 + .../JobWhitelist/JobWhitelistManager.cs | 114 + .../JobWhitelist/JobWhitelistSystem.cs | 83 + .../PlayTimeTrackingManager.cs | 9 +- .../PlayTimeTrackingSystem.cs | 30 +- .../Managers/ServerPreferencesManager.cs | 15 +- .../Events/StationJobsGetCandidatesEvent.cs | 8 + .../Systems/StationJobsSystem.Roundstart.cs | 8 +- .../Station/Systems/StationJobsSystem.cs | 2 +- Content.Shared/CCVar/CCVars.cs | 6 + .../Players/JobWhitelist/MsgJobWhitelist.cs | 33 + Content.Shared/Roles/JobPrototype.cs | 5 +- .../en-US/commands/job-whitelist-command.ftl | 20 + Resources/Locale/en-US/job/role-whitelist.ftl | 1 + 35 files changed, 4666 insertions(+), 47 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs create mode 100644 Content.Server/Administration/Commands/JobWhitelistCommands.cs create mode 100644 Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs create mode 100644 Content.Server/GameTicking/Events/IsJobAllowedEvent.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistManager.cs create mode 100644 Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs create mode 100644 Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs create mode 100644 Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs create mode 100644 Resources/Locale/en-US/commands/job-whitelist-command.ftl create mode 100644 Resources/Locale/en-US/job/role-whitelist.ftl diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index ae9196c110..f6a3eed962 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -22,7 +22,6 @@ using Robust.Client.UserInterface.Controllers; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; namespace Content.Client.Lobby; @@ -70,12 +69,9 @@ public sealed class LobbyUIController : UIController, IOnStateEntered - { - _profileEditor?.RefreshAntags(); - _profileEditor?.RefreshJobs(); - _profileEditor?.RefreshLoadouts(); - }); + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor()); + + _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor()); } private LobbyCharacterPreviewPanel? GetLobbyPreview() @@ -193,6 +189,13 @@ public sealed class LobbyUIController : UIController, IOnStateEntered _roles = new(); private readonly List _roleBans = new(); + private readonly List _jobWhitelists = new(); private ISawmill _sawmill = default!; @@ -36,6 +38,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager // Yeah the client manager handles role bans and playtime but the server ones are separate DEAL. _net.RegisterNetMessage(RxRoleBans); _net.RegisterNetMessage(RxPlayTime); + _net.RegisterNetMessage(RxJobWhitelist); _client.RunLevelChanged += ClientOnRunLevelChanged; } @@ -79,6 +82,13 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager Updated?.Invoke(); } + private void RxJobWhitelist(MsgJobWhitelist message) + { + _jobWhitelists.Clear(); + _jobWhitelists.AddRange(message.Whitelist); + Updated?.Invoke(); + } + public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; @@ -89,6 +99,9 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager return false; } + if (!CheckWhitelist(job, out reason)) + return false; + var player = _playerManager.LocalSession; if (player == null) return true; @@ -116,6 +129,21 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager return reason == null; } + public bool CheckWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = default; + if (!_cfg.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (job.Whitelisted && !_jobWhitelists.Contains(job.ID)) + { + reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted")); + return false; + } + + return true; + } + public TimeSpan FetchOverallPlaytime() { return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero; diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index aa6b4dffdc..5acd9d502c 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -21,6 +21,7 @@ public static partial class PoolManager (CCVars.NPCMaxUpdates.Name, "999999"), (CVars.ThreadParallelCount.Name, "1"), (CCVars.GameRoleTimers.Name, "false"), + (CCVars.GameRoleWhitelist.Name, "false"), (CCVars.GridFill.Name, "false"), (CCVars.PreloadGrids.Name, "false"), (CCVars.ArrivalsShuttles.Name, "false"), diff --git a/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..5032281dfe --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.Designer.cs @@ -0,0 +1,1913 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20240531011555_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs new file mode 100644 index 0000000000..1cd0d8df17 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240531011555_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "uuid", nullable: false), + role_id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index bbbd64d874..32a655f7f6 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -900,6 +900,22 @@ namespace Content.Server.Database.Migrations.Postgres b.ToTable("profile_role_loadout", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1623,6 +1639,19 @@ namespace Content.Server.Database.Migrations.Postgres b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1822,6 +1851,8 @@ namespace Content.Server.Database.Migrations.Postgres b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..531f013604 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.Designer.cs @@ -0,0 +1,1838 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20240531011549_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs new file mode 100644 index 0000000000..9d192fc685 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240531011549_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index d343f49f41..2c444c83eb 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -847,6 +847,22 @@ namespace Content.Server.Database.Migrations.Sqlite b.ToTable("profile_role_loadout", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1548,6 +1564,19 @@ namespace Content.Server.Database.Migrations.Sqlite b.Navigation("Profile"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1747,6 +1776,8 @@ namespace Content.Server.Database.Migrations.Sqlite b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 57dcc3fc6d..60935e8f06 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -40,6 +40,7 @@ namespace Content.Server.Database public DbSet AdminNotes { get; set; } = null!; public DbSet AdminWatchlists { get; set; } = null!; public DbSet AdminMessages { get; set; } = null!; + public DbSet RoleWhitelists { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -314,6 +315,13 @@ namespace Content.Server.Database .HasForeignKey(ban => ban.LastEditedById) .HasPrincipalKey(author => author.UserId) .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(w => w.Player) + .WithMany(p => p.JobWhitelists) + .HasForeignKey(w => w.PlayerUserId) + .HasPrincipalKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -530,6 +538,7 @@ namespace Content.Server.Database public List AdminServerBansLastEdited { get; set; } = null!; public List AdminServerRoleBansCreated { get; set; } = null!; public List AdminServerRoleBansLastEdited { get; set; } = null!; + public List JobWhitelists { get; set; } = null!; } [Table("whitelist")] @@ -1099,4 +1108,15 @@ namespace Content.Server.Database /// public bool Dismissed { get; set; } } + + [PrimaryKey(nameof(PlayerUserId), nameof(RoleId))] + public class RoleWhitelist + { + [Required, ForeignKey("Player")] + public Guid PlayerUserId { get; set; } + public Player Player { get; set; } = default!; + + [Required] + public string RoleId { get; set; } = default!; + } } diff --git a/Content.Server/Administration/Commands/JobWhitelistCommands.cs b/Content.Server/Administration/Commands/JobWhitelistCommands.cs new file mode 100644 index 0000000000..a84a11ef84 --- /dev/null +++ b/Content.Server/Administration/Commands/JobWhitelistCommands.cs @@ -0,0 +1,214 @@ +using System.Linq; +using Content.Server.Database; +using Content.Server.Players.JobWhitelist; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class JobWhitelistAddCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistadd"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (isWhitelisted) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.AddWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class GetJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + public override string Command => "jobwhitelistget"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length == 0) + { + shell.WriteError("This command needs at least one argument."); + shell.WriteLine(Help); + return; + } + + var player = string.Join(' ', args).Trim(); + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var whitelists = await _db.GetJobWhitelists(guid); + if (whitelists.Count == 0) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player))); + return; + } + + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for", + ("player", player), + ("jobs", string.Join(", ", whitelists)))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class RemoveJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistremove"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (!isWhitelisted) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.RemoveWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 3a05b934b2..68bd817026 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -73,7 +73,9 @@ public sealed class BanManager : IBanManager, IPostInjectInit public HashSet? GetRoleBans(NetUserId playerUserId) { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; + return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) + ? roleBans.Select(banDef => banDef.Role).ToHashSet() + : null; } private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) @@ -263,13 +265,13 @@ public sealed class BanManager : IBanManager, IPostInjectInit return $"Pardoned ban with id {banId}"; } - public HashSet? GetJobBans(NetUserId playerUserId) + public HashSet>? GetJobBans(NetUserId playerUserId) { if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) return null; return roleBans .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => ban.Role[JobPrefix.Length..]) + .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) .ToHashSet(); } #endregion diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index dafe3d35bd..b60e0a2535 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -2,8 +2,10 @@ using System.Collections.Immutable; using System.Net; using System.Threading.Tasks; using Content.Shared.Database; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Prototypes; namespace Content.Server.Administration.Managers; @@ -24,7 +26,7 @@ public interface IBanManager /// Reason for the ban public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, uint? minutes, NoteSeverity severity, string reason); public HashSet? GetRoleBans(NetUserId playerUserId); - public HashSet? GetJobBans(NetUserId playerUserId); + public HashSet>? GetJobBans(NetUserId playerUserId); /// /// Creates a job ban for the specified target, username or GUID diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index 8743a4b5db..be6c7196d5 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -14,9 +14,11 @@ using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; +using Content.Shared.Roles; using Microsoft.EntityFrameworkCore; using Robust.Shared.Enums; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Database @@ -1579,6 +1581,65 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id} #endregion + #region Job Whitelists + + public async Task AddJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var exists = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + + if (exists) + return false; + + var whitelist = new RoleWhitelist + { + PlayerUserId = player, + RoleId = job + }; + db.DbContext.RoleWhitelists.Add(whitelist); + await db.DbContext.SaveChangesAsync(); + return true; + } + + public async Task> GetJobWhitelists(Guid player, CancellationToken cancel) + { + await using var db = await GetDb(cancel); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Select(w => w.RoleId) + .ToListAsync(cancellationToken: cancel); + } + + public async Task IsJobWhitelisted(Guid player, ProtoId job) + { + await using var db = await GetDb(); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + } + + public async Task RemoveJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var entry = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .SingleOrDefaultAsync(); + + if (entry == null) + return false; + + db.DbContext.RoleWhitelists.Remove(entry); + await db.DbContext.SaveChangesAsync(); + return true; + } + + #endregion + // SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc. // Normalize DateTimes here so they're always Utc. Thanks. protected abstract DateTime NormalizeDatabaseTime(DateTime time); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 01d1526727..1983fe43d2 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -9,6 +9,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Preferences; +using Content.Shared.Roles; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; @@ -17,6 +18,7 @@ using Prometheus; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using LogLevel = Robust.Shared.Log.LogLevel; using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -290,6 +292,18 @@ namespace Content.Server.Database Task MarkMessageAsSeen(int id, bool dismissedToo); #endregion + + #region Job Whitelists + + Task AddJobWhitelist(Guid player, ProtoId job); + + + Task> GetJobWhitelists(Guid player, CancellationToken cancel = default); + Task IsJobWhitelisted(Guid player, ProtoId job); + + Task RemoveJobWhitelist(Guid player, ProtoId job); + + #endregion } public sealed class ServerDbManager : IServerDbManager @@ -869,6 +883,30 @@ namespace Content.Server.Database return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo)); } + public Task AddJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.AddJobWhitelist(player, job)); + } + + public Task> GetJobWhitelists(Guid player, CancellationToken cancel = default) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.GetJobWhitelists(player, cancel)); + } + + public Task IsJobWhitelisted(Guid player, ProtoId job) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.IsJobWhitelisted(player, job)); + } + + public Task RemoveJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.RemoveJobWhitelist(player, job)); + } + // Wrapper functions to run DB commands from the thread pool. // This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread. // For SQLite, this will also enable read parallelization (within limits). diff --git a/Content.Server/Database/UserDbDataManager.cs b/Content.Server/Database/UserDbDataManager.cs index c58c594dba..1aac86c129 100644 --- a/Content.Server/Database/UserDbDataManager.cs +++ b/Content.Server/Database/UserDbDataManager.cs @@ -1,8 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; -using Robust.Server.Player; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; @@ -19,11 +17,12 @@ namespace Content.Server.Database; /// public sealed class UserDbDataManager : IPostInjectInit { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly ILogManager _logManager = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; private readonly Dictionary _users = new(); + private readonly List _onLoadPlayer = []; + private readonly List _onFinishLoad = []; + private readonly List _onPlayerDisconnect = []; private ISawmill _sawmill = default!; @@ -51,8 +50,10 @@ public sealed class UserDbDataManager : IPostInjectInit data.Cancel.Cancel(); data.Cancel.Dispose(); - _prefs.OnClientDisconnected(session); - _playTimeTracking.ClientDisconnected(session); + foreach (var onDisconnect in _onPlayerDisconnect) + { + onDisconnect(session); + } } private async Task Load(ICommonSession session, CancellationToken cancel) @@ -62,12 +63,20 @@ public sealed class UserDbDataManager : IPostInjectInit // As such, this task must NOT throw a non-cancellation error! try { - await Task.WhenAll( - _prefs.LoadData(session, cancel), - _playTimeTracking.LoadData(session, cancel)); + var tasks = new List(); + foreach (var action in _onLoadPlayer) + { + tasks.Add(action(session, cancel)); + } + + await Task.WhenAll(tasks); cancel.ThrowIfCancellationRequested(); - _prefs.FinishLoad(session); + + foreach (var action in _onFinishLoad) + { + action(session); + } _sawmill.Verbose($"Load complete for user {session}"); } @@ -118,10 +127,31 @@ public sealed class UserDbDataManager : IPostInjectInit return _users[session.UserId].Task; } + public void AddOnLoadPlayer(OnLoadPlayer action) + { + _onLoadPlayer.Add(action); + } + + public void AddOnFinishLoad(OnFinishLoad action) + { + _onFinishLoad.Add(action); + } + + public void AddOnPlayerDisconnect(OnPlayerDisconnect action) + { + _onPlayerDisconnect.Add(action); + } + void IPostInjectInit.PostInject() { _sawmill = _logManager.GetSawmill("userdb"); } private sealed record UserData(CancellationTokenSource Cancel, Task Task); + + public delegate Task OnLoadPlayer(ICommonSession player, CancellationToken cancel); + + public delegate void OnFinishLoad(ICommonSession player); + + public delegate void OnPlayerDisconnect(ICommonSession player); } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 3cdf3bfe8e..234a2e4658 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -14,6 +14,7 @@ using Content.Server.Info; using Content.Server.IoC; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -107,6 +108,7 @@ namespace Content.Server.Entry _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs new file mode 100644 index 0000000000..cd15cfb8f8 --- /dev/null +++ b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public readonly record struct GetDisallowedJobsEvent(ICommonSession Player, HashSet> Jobs); diff --git a/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs new file mode 100644 index 0000000000..51969d61ea --- /dev/null +++ b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public struct IsJobAllowedEvent(ICommonSession player, ProtoId jobId, bool cancelled = false) +{ + public readonly ICommonSession Player = player; + public readonly ProtoId JobId = jobId; + public bool Cancelled = cancelled; +} diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 843122e0fe..22d368e57b 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -2,11 +2,11 @@ using System.Globalization; using System.Linq; using System.Numerics; using Content.Server.Administration.Managers; +using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; -using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Mind; using Content.Shared.Players; @@ -137,8 +137,14 @@ namespace Content.Server.GameTicking if (jobBans == null || jobId != null && jobBans.Contains(jobId)) return; - if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId)) - return; + if (jobId != null) + { + var ev = new IsJobAllowedEvent(player, new ProtoId(jobId)); + RaiseLocalEvent(ref ev); + if (ev.Cancelled) + return; + } + SpawnPlayer(player, character, station, jobId, lateJoin, silent); } @@ -181,10 +187,9 @@ namespace Content.Server.GameTicking } // Figure out job restrictions - var restrictedRoles = new HashSet(); - - var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player); - restrictedRoles.UnionWith(getDisallowed); + var restrictedRoles = new HashSet>(); + var ev = new GetDisallowedJobsEvent(player, restrictedRoles); + RaiseLocalEvent(ref ev); var jobBans = _banManager.GetJobBans(player.UserId); if (jobBans != null) diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index fa23312268..d9511309b9 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -19,7 +19,6 @@ using Content.Shared.Roles; using Robust.Server; using Robust.Server.GameObjects; using Robust.Server.GameStates; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Console; diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index f6800f7289..c6dfcadd38 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -13,6 +13,7 @@ using Content.Server.Info; using Content.Server.Maps; using Content.Server.MoMMI; using Content.Server.NodeContainer.NodeGroups; +using Content.Server.Players.JobWhitelist; using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Content.Server.ServerInfo; @@ -61,6 +62,7 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs new file mode 100644 index 0000000000..04289a4098 --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -0,0 +1,114 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared.CCVar; +using Content.Shared.Players.JobWhitelist; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Serilog; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistManager : IPostInjectInit +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; + + private readonly Dictionary> _whitelists = new(); + + public void Initialize() + { + _net.RegisterNetMessage(); + } + + private async Task LoadData(ICommonSession session, CancellationToken cancel) + { + var whitelists = await _db.GetJobWhitelists(session.UserId, cancel); + cancel.ThrowIfCancellationRequested(); + _whitelists[session.UserId] = whitelists.ToHashSet(); + } + + private void FinishLoad(ICommonSession session) + { + SendJobWhitelist(session); + } + + private void ClientDisconnected(ICommonSession session) + { + _whitelists.Remove(session.UserId); + } + + public async void AddWhitelist(NetUserId player, ProtoId job) + { + if (_whitelists.TryGetValue(player, out var whitelists)) + whitelists.Add(job); + + await _db.AddJobWhitelist(player, job); + + if (_player.TryGetSessionById(player, out var session)) + SendJobWhitelist(session); + } + + public bool IsAllowed(ICommonSession session, ProtoId job) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (!_prototypes.TryIndex(job, out var jobPrototype) || + !jobPrototype.Whitelisted) + { + return true; + } + + return IsWhitelisted(session.UserId, job); + } + + public bool IsWhitelisted(NetUserId player, ProtoId job) + { + if (!_whitelists.TryGetValue(player, out var whitelists)) + { + Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", + player, + job, + Environment.StackTrace); + return false; + } + + return whitelists.Contains(job); + } + + public async void RemoveWhitelist(NetUserId player, ProtoId job) + { + _whitelists.GetValueOrDefault(player)?.Remove(job); + await _db.RemoveJobWhitelist(player, job); + + if (_player.TryGetSessionById(new NetUserId(player), out var session)) + SendJobWhitelist(session); + } + + public void SendJobWhitelist(ICommonSession player) + { + var msg = new MsgJobWhitelist + { + Whitelist = _whitelists.GetValueOrDefault(player.UserId) ?? new HashSet() + }; + + _net.ServerSendMessage(msg, player.Channel); + } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } +} diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs new file mode 100644 index 0000000000..aaada99dea --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs @@ -0,0 +1,83 @@ +using System.Collections.Immutable; +using Content.Server.GameTicking.Events; +using Content.Server.Station.Events; +using Content.Shared.CCVar; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly JobWhitelistManager _manager = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + private ImmutableArray> _whitelistedJobs = []; + + public override void Initialize() + { + SubscribeLocalEvent(OnPrototypesReloaded); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); + + CacheJobs(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev) + { + if (ev.WasModified()) + CacheJobs(); + } + + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + for (var i = ev.Jobs.Count - 1; i >= 0; i--) + { + var jobId = ev.Jobs[i]; + if (_player.TryGetSessionById(ev.Player, out var player) && + !_manager.IsAllowed(player, jobId)) + { + ev.Jobs.RemoveSwap(i); + } + } + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!_manager.IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + foreach (var job in _whitelistedJobs) + { + if (!_manager.IsAllowed(ev.Player, job)) + ev.Jobs.Add(job); + } + } + + private void CacheJobs() + { + var builder = ImmutableArray.CreateBuilder>(); + foreach (var job in _prototypes.EnumeratePrototypes()) + { + if (job.Whitelisted) + builder.Add(job.ID); + } + + _whitelistedJobs = builder.ToImmutable(); + } +} diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs index c638c2f240..bfd6172f4c 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs @@ -54,7 +54,7 @@ public delegate void CalcPlayTimeTrackersCallback(ICommonSession player, HashSet /// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick). /// /// -public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager +public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit { [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerNetManager _net = default!; @@ -62,6 +62,7 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ITaskManager _task = default!; [Dependency] private readonly IRuntimeLog _runtimeLog = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; private ISawmill _sawmill = default!; @@ -445,4 +446,10 @@ public sealed class PlayTimeTrackingManager : ISharedPlaytimeManager /// public readonly HashSet DbTrackersDirty = new(); } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } } diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index e594e4de26..c3142a709a 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -4,7 +4,9 @@ using Content.Server.Administration.Managers; using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; +using Content.Server.GameTicking.Events; using Content.Server.Mind; +using Content.Server.Station.Events; using Content.Shared.CCVar; using Content.Shared.GameTicking; using Content.Shared.Mobs; @@ -12,7 +14,6 @@ using Content.Shared.Mobs.Components; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -50,6 +51,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem SubscribeLocalEvent(OnUnAFK); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPlayerJoinedLobby); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); _adminManager.OnPermsChanged += AdminPermsChanged; } @@ -174,6 +178,22 @@ public sealed class PlayTimeTrackingSystem : EntitySystem _tracking.QueueSendTimers(ev.PlayerSession); } + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + RemoveDisallowedJobs(ev.Player, ev.Jobs); + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player)); + } + public bool IsAllowed(ICommonSession player, string role) { if (!_prototypes.TryIndex(role, out var job) || @@ -190,9 +210,9 @@ public sealed class PlayTimeTrackingSystem : EntitySystem return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes); } - public HashSet GetDisallowedJobs(ICommonSession player) + public HashSet> GetDisallowedJobs(ICommonSession player) { - var roles = new HashSet(); + var roles = new HashSet>(); if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return roles; @@ -222,7 +242,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem return roles; } - public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) + public void RemoveDisallowedJobs(NetUserId userId, List> jobs) { if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return; @@ -239,7 +259,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem { var job = jobs[i]; - if (!_prototypes.TryIndex(job, out var jobber) || + if (!_prototypes.TryIndex(job, out var jobber) || jobber.Requirements == null || jobber.Requirements.Count == 0) continue; diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index e32af589e9..f7c15a2340 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -3,26 +3,21 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Content.Server.Database; -using Content.Server.Humanoid; using Content.Shared.CCVar; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Utility; - namespace Content.Server.Preferences.Managers { /// /// Sends before the client joins the lobby. /// Receives and at any time. /// - public sealed class ServerPreferencesManager : IServerPreferencesManager + public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit { [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; @@ -30,6 +25,7 @@ namespace Content.Server.Preferences.Managers [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = @@ -326,5 +322,12 @@ namespace Content.Server.Preferences.Managers public bool PrefsLoaded; public PlayerPreferences? Prefs; } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(OnClientDisconnected); + } } } diff --git a/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs new file mode 100644 index 0000000000..58d860b1e1 --- /dev/null +++ b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Events; + +[ByRefEvent] +public readonly record struct StationJobsGetCandidatesEvent(NetUserId Player, List> Jobs); diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index 4b7cd64961..c3c3865c7b 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Server.Administration.Managers; using Content.Server.Players.PlayTimeTracking; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.Network; @@ -342,8 +343,9 @@ public sealed partial class StationJobsSystem foreach (var (player, profile) in profiles) { var roleBans = _banManager.GetJobBans(player); - var profileJobs = profile.JobPriorities.Keys.ToList(); - _playTime.RemoveDisallowedJobs(player, ref profileJobs); + var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId(k)).ToList(); + var ev = new StationJobsGetCandidatesEvent(player, profileJobs); + RaiseLocalEvent(ref ev); List? availableJobs = null; @@ -354,7 +356,7 @@ public sealed partial class StationJobsSystem if (!(priority == selectedPriority || selectedPriority is null)) continue; - if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job)) + if (!_prototypeManager.TryIndex(jobId, out var job)) continue; if (weight is not null && job.Weight != weight.Value) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index debac8902e..3bfa815af1 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -428,7 +428,7 @@ public sealed partial class StationJobsSystem : EntitySystem /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet? disallowedJobs = null) + public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 5ca34945d8..f41e0a1e6f 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -225,6 +225,12 @@ namespace Content.Shared.CCVar public static readonly CVarDef GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED); + /// + /// If roles should be restricted based on whether or not they are whitelisted. + /// + public static readonly CVarDef + GameRoleWhitelist = CVarDef.Create("game.role_whitelist", true, CVar.SERVER | CVar.REPLICATED); + /// /// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect. /// diff --git a/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs new file mode 100644 index 0000000000..8347e2d706 --- /dev/null +++ b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs @@ -0,0 +1,33 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Players.JobWhitelist; + +public sealed class MsgJobWhitelist : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public HashSet Whitelist = new(); + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + var count = buffer.ReadVariableInt32(); + Whitelist.EnsureCapacity(count); + + for (var i = 0; i < count; i++) + { + Whitelist.Add(buffer.ReadString()); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.WriteVariableInt32(Whitelist.Count); + + foreach (var ban in Whitelist) + { + buffer.Write(ban); + } + } +} diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 34a8ce64bf..2959b56497 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -1,10 +1,8 @@ using Content.Shared.Access; using Content.Shared.Players.PlayTimeTracking; -using Content.Shared.Roles; using Content.Shared.StatusIcon; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Roles { @@ -116,6 +114,9 @@ namespace Content.Shared.Roles [DataField("extendedAccessGroups")] public IReadOnlyCollection> ExtendedAccessGroups { get; private set; } = Array.Empty>(); + + [DataField] + public bool Whitelisted; } /// diff --git a/Resources/Locale/en-US/commands/job-whitelist-command.ftl b/Resources/Locale/en-US/commands/job-whitelist-command.ftl new file mode 100644 index 0000000000..7fa20f03de --- /dev/null +++ b/Resources/Locale/en-US/commands/job-whitelist-command.ftl @@ -0,0 +1,20 @@ +cmd-jobwhitelist-job-does-not-exist = Job {$job} does not exist. +cmd-jobwhitelist-player-not-found = Player {$player} not found. +cmd-jobwhitelist-hint-player = [player] +cmd-jobwhitelist-hint-job = [job] + +cmd-jobwhitelistadd-desc = Lets a player play a whitelisted job. +cmd-jobwhitelistadd-help = Usage: jobwhitelistadd +cmd-jobwhitelistadd-already-whitelisted = {$player} is already whitelisted to play as {$jobId} .({$jobName}). +cmd-jobwhitelistadd-added = Added {$player} to the {$jobId} ({$jobName}) whitelist. + +cmd-jobwhitelistget-desc = Gets all the jobs that a player has been whitelisted for. +cmd-jobwhitelistget-help = Usage: jobwhitelistadd +cmd-jobwhitelistget-whitelisted-none = Player {$player} is not whitelisted for any jobs. +cmd-jobwhitelistget-whitelisted-for = "Player {$player} is whitelisted for: +{$jobs}" + +cmd-jobwhitelistremove-desc = Removes a player's ability to play a whitelisted job. +cmd-jobwhitelistremove-help = Usage: jobwhitelistadd +cmd-jobwhitelistremove-was-not-whitelisted = {$player} was not whitelisted to play as {$jobId} ({$jobName}). +cmd-jobwhitelistremove-removed = Removed {$player} from the whitelist for {$jobId} ({$jobName}). diff --git a/Resources/Locale/en-US/job/role-whitelist.ftl b/Resources/Locale/en-US/job/role-whitelist.ftl new file mode 100644 index 0000000000..3149f182b6 --- /dev/null +++ b/Resources/Locale/en-US/job/role-whitelist.ftl @@ -0,0 +1 @@ +role-not-whitelisted = You are not whitelisted to play this role. From be6f55a09013ce88d7b57518ff283df0977aded9 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:29:17 -0400 Subject: [PATCH 60/84] Clean up store system (#28463) --- .../Store/Ui/StoreBoundUserInterface.cs | 24 +++----- Content.Client/Store/Ui/StoreMenu.xaml.cs | 5 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 1 + .../Implants/SubdermalImplantSystem.cs | 1 + Content.Server/PDA/PdaSystem.cs | 8 ++- Content.Server/PDA/Ringer/RingerSystem.cs | 1 + .../Revenant/EntitySystems/RevenantSystem.cs | 1 + .../Store/Conditions/BuyBeforeCondition.cs | 1 + .../Store/Systems/StoreSystem.Command.cs | 4 +- .../Store/Systems/StoreSystem.Listings.cs | 11 +++- .../Store/Systems/StoreSystem.Refund.cs | 1 + .../Store/Systems/StoreSystem.Ui.cs | 9 +-- Content.Server/Store/Systems/StoreSystem.cs | 42 +------------- .../SurplusBundle/SurplusBundleComponent.cs | 13 +---- .../SurplusBundle/SurplusBundleSystem.cs | 57 ++++++++---------- .../Traitor/Uplink/UplinkComponent.cs | 7 +++ Content.Server/Traitor/Uplink/UplinkSystem.cs | 18 +----- .../Store/Components/StoreComponent.cs | 34 +++++------ Content.Shared/Store/StoreUi.cs | 14 ----- Resources/Locale/en-US/store/store.ftl | 3 + .../Catalog/Fills/Crates/syndicate.yml | 4 +- .../Entities/Objects/Devices/pda.yml | 2 +- .../Entities/Objects/Magic/books.yml | 6 +- .../Objects/Misc/subdermal_implants.yml | 3 +- .../Entities/Objects/Specific/syndicate.yml | 8 +-- Resources/Prototypes/Store/presets.yml | 58 +++++++++++-------- 26 files changed, 127 insertions(+), 209 deletions(-) create mode 100644 Content.Server/Traitor/Uplink/UplinkComponent.cs rename {Content.Server => Content.Shared}/Store/Components/StoreComponent.cs (72%) diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs index 88ad0e3de8..0010aedd96 100644 --- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs +++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs @@ -1,6 +1,7 @@ using Content.Shared.Store; using JetBrains.Annotations; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Client.Store.Ui; @@ -13,9 +14,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface [ViewVariables] private StoreMenu? _menu; - [ViewVariables] - private string _windowName = Loc.GetString("store-ui-default-title"); - [ViewVariables] private string _search = string.Empty; @@ -28,7 +26,9 @@ public sealed class StoreBoundUserInterface : BoundUserInterface protected override void Open() { - _menu = new StoreMenu(_windowName); + _menu = new StoreMenu(); + if (EntMan.TryGetComponent(Owner, out var store)) + _menu.Title = Loc.GetString(store.Name); _menu.OpenCentered(); _menu.OnClose += Close; @@ -64,25 +64,15 @@ public sealed class StoreBoundUserInterface : BoundUserInterface { base.UpdateState(state); - if (_menu == null) - return; - switch (state) { case StoreUpdateState msg: _listings = msg.Listings; - _menu.UpdateBalance(msg.Balance); + _menu?.UpdateBalance(msg.Balance); UpdateListingsWithSearchFilter(); - _menu.SetFooterVisibility(msg.ShowFooter); - _menu.UpdateRefund(msg.AllowRefund); - break; - case StoreInitializeState msg: - _windowName = msg.Name; - if (_menu != null && _menu.Window != null) - { - _menu.Window.Title = msg.Name; - } + _menu?.SetFooterVisibility(msg.ShowFooter); + _menu?.UpdateRefund(msg.AllowRefund); break; } } diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs index b7a2c285fe..388b31291c 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml.cs +++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs @@ -32,7 +32,7 @@ public sealed partial class StoreMenu : DefaultWindow private List _cachedListings = new(); - public StoreMenu(string name) + public StoreMenu() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -40,9 +40,6 @@ public sealed partial class StoreMenu : DefaultWindow WithdrawButton.OnButtonDown += OnWithdrawButtonDown; RefundButton.OnButtonDown += OnRefundButtonDown; SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text); - - if (Window != null) - Window.Title = name; } public void UpdateBalance(Dictionary, FixedPoint2> balance) diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index d6f1c3c619..1b62778d75 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -25,6 +25,7 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; using Content.Server.GameTicking.Components; +using Content.Shared.Store.Components; namespace Content.Server.GameTicking.Rules; diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index e8af08b2eb..88c5fb9459 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -20,6 +20,7 @@ using Robust.Shared.Random; using System.Numerics; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Store.Components; using Robust.Shared.Collections; using Robust.Shared.Map.Components; diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index d4934ee24e..43bf571eb4 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -8,6 +8,7 @@ using Content.Server.PDA.Ringer; using Content.Server.Station.Systems; using Content.Server.Store.Components; using Content.Server.Store.Systems; +using Content.Server.Traitor.Uplink; using Content.Shared.Access.Components; using Content.Shared.CartridgeLoader; using Content.Shared.Chat; @@ -15,6 +16,7 @@ using Content.Shared.Light; using Content.Shared.Light.Components; using Content.Shared.Light.EntitySystems; using Content.Shared.PDA; +using Content.Shared.Store.Components; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -152,7 +154,7 @@ namespace Content.Server.PDA var address = GetDeviceNetAddress(uid); var hasInstrument = HasComp(uid); - var showUplink = HasComp(uid) && IsUnlocked(uid); + var showUplink = HasComp(uid) && IsUnlocked(uid); UpdateStationName(uid, pda); UpdateAlertLevel(uid, pda); @@ -237,8 +239,8 @@ namespace Content.Server.PDA return; // check if its locked again to prevent malicious clients opening locked uplinks - if (TryComp(uid, out var store) && IsUnlocked(uid)) - _store.ToggleUi(msg.Actor, uid, store); + if (HasComp(uid) && IsUnlocked(uid)) + _store.ToggleUi(msg.Actor, uid); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) diff --git a/Content.Server/PDA/Ringer/RingerSystem.cs b/Content.Server/PDA/Ringer/RingerSystem.cs index 47ae41896e..e15dcfaa2b 100644 --- a/Content.Server/PDA/Ringer/RingerSystem.cs +++ b/Content.Server/PDA/Ringer/RingerSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.PDA; using Content.Shared.PDA.Ringer; using Content.Shared.Popups; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Network; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index c390432f3a..a05105662d 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Popups; using Content.Shared.Revenant; using Content.Shared.Revenant.Components; using Content.Shared.StatusEffect; +using Content.Shared.Store.Components; using Content.Shared.Stunnable; using Content.Shared.Tag; using Robust.Server.GameObjects; diff --git a/Content.Server/Store/Conditions/BuyBeforeCondition.cs b/Content.Server/Store/Conditions/BuyBeforeCondition.cs index 132f353439..3f0c2de2e1 100644 --- a/Content.Server/Store/Conditions/BuyBeforeCondition.cs +++ b/Content.Server/Store/Conditions/BuyBeforeCondition.cs @@ -1,6 +1,7 @@ using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.Store; +using Content.Shared.Store.Components; using Robust.Shared.Prototypes; namespace Content.Server.Store.Conditions; diff --git a/Content.Server/Store/Systems/StoreSystem.Command.cs b/Content.Server/Store/Systems/StoreSystem.Command.cs index d259da2c95..5ad361eb42 100644 --- a/Content.Server/Store/Systems/StoreSystem.Command.cs +++ b/Content.Server/Store/Systems/StoreSystem.Command.cs @@ -1,7 +1,9 @@ +using System.Linq; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Server.Administration; using Content.Shared.Administration; +using Content.Shared.Store.Components; using Robust.Shared.Console; namespace Content.Server.Store.Systems; @@ -58,7 +60,7 @@ public sealed partial class StoreSystem if (args.Length == 2 && NetEntity.TryParse(args[0], out var uidNet) && TryGetEntity(uidNet, out var uid)) { if (TryComp(uid, out var store)) - return CompletionResult.FromHintOptions(store.CurrencyWhitelist, ""); + return CompletionResult.FromHintOptions(store.CurrencyWhitelist.Select(p => p.ToString()), ""); } return CompletionResult.Empty; diff --git a/Content.Server/Store/Systems/StoreSystem.Listings.cs b/Content.Server/Store/Systems/StoreSystem.Listings.cs index a56d9640d3..10b53a7c94 100644 --- a/Content.Server/Store/Systems/StoreSystem.Listings.cs +++ b/Content.Server/Store/Systems/StoreSystem.Listings.cs @@ -1,5 +1,6 @@ -using Content.Server.Store.Components; using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Store.Systems; @@ -80,7 +81,11 @@ public sealed partial class StoreSystem /// What categories to filter by. /// The physial entity of the store. Can be null. /// The available listings. - public IEnumerable GetAvailableListings(EntityUid buyer, HashSet? listings, HashSet categories, EntityUid? storeEntity = null) + public IEnumerable GetAvailableListings( + EntityUid buyer, + HashSet? listings, + HashSet> categories, + EntityUid? storeEntity = null) { listings ??= GetAllListings(); @@ -117,7 +122,7 @@ public sealed partial class StoreSystem /// The listing itself. /// The categories to check through. /// If the listing was present in one of the categories. - public bool ListingHasCategory(ListingData listing, HashSet categories) + public bool ListingHasCategory(ListingData listing, HashSet> categories) { foreach (var cat in categories) { diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index 5a8be4be2b..4e823582e6 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -1,4 +1,5 @@ using Content.Server.Store.Components; +using Content.Shared.Store.Components; using Robust.Shared.Containers; namespace Content.Server.Store.Systems; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 0a1a8d19f3..983d68d0af 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -10,6 +10,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.Mind; using Content.Shared.Store; +using Content.Shared.Store.Components; using Content.Shared.UserInterface; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; @@ -82,16 +83,11 @@ public sealed partial class StoreSystem /// The person who if opening the store ui. Listings are filtered based on this. /// The store entity itself /// The store component being refreshed. - /// public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) { if (!Resolve(store, ref component)) return; - // TODO: Why is the state not being set unless this? - if (!_ui.HasUi(store, StoreUiKey.Key)) - return; - //this is the person who will be passed into logic for all listing filtering. if (user != null) //if we have no "buyer" for this update, then don't update the listings { @@ -259,7 +255,8 @@ public sealed partial class StoreSystem } //log dat shit. - _admin.Add(LogType.StorePurchase, LogImpact.Low, + _admin.Add(LogType.StorePurchase, + LogImpact.Low, $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}"); listing.PurchaseAmount++; //track how many times something has been purchased diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index e310194778..0fd92cfb96 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -5,11 +5,10 @@ using Content.Shared.Implants.Components; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; -using Content.Shared.Store; using JetBrains.Annotations; -using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Store.Components; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; @@ -44,7 +43,6 @@ public sealed partial class StoreSystem : EntitySystem private void OnMapInit(EntityUid uid, StoreComponent component, MapInitEvent args) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); component.StartingMap = Transform(uid).MapUid; } @@ -54,7 +52,6 @@ public sealed partial class StoreSystem : EntitySystem if (MetaData(uid).EntityLifeStage == EntityLifeStage.MapInitialized) { RefreshAllListings(component); - InitializeFromPreset(component.Preset, uid, component); } var ev = new StoreAddedEvent(); @@ -167,43 +164,6 @@ public sealed partial class StoreSystem : EntitySystem UpdateUserInterface(null, uid, store); return true; } - - /// - /// Initializes a store based on a preset ID - /// - /// The ID of a store preset prototype - /// - /// The store being initialized - public void InitializeFromPreset(string? preset, EntityUid uid, StoreComponent component) - { - if (preset == null) - return; - - if (!_proto.TryIndex(preset, out var proto)) - return; - - InitializeFromPreset(proto, uid, component); - } - - /// - /// Initializes a store based on a given preset - /// - /// The StorePresetPrototype - /// - /// The store being initialized - public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, StoreComponent component) - { - component.Preset = preset.ID; - component.CurrencyWhitelist.UnionWith(preset.CurrencyWhitelist); - component.Categories.UnionWith(preset.Categories); - if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset - TryAddCurrency(preset.InitialBalance, uid, component); - - if (_ui.HasUi(uid, StoreUiKey.Key)) - { - _ui.SetUiState(uid, StoreUiKey.Key, new StoreInitializeState(preset.StoreName)); - } - } } public sealed class CurrencyInsertAttemptEvent : CancellableEntityEventArgs diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs index 47ce68625a..120581cf82 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleComponent.cs @@ -1,6 +1,3 @@ -using Content.Shared.Store; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Server.Traitor.Uplink.SurplusBundle; /// @@ -12,14 +9,6 @@ public sealed partial class SurplusBundleComponent : Component /// /// Total price of all content inside bundle. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("totalPrice")] + [DataField] public int TotalPrice = 20; - - /// - /// The preset that will be used to get all the listings. - /// Currently just defaults to the basic uplink. - /// - [DataField("storePreset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string StorePreset = "StorePresetUplink"; } diff --git a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs index 5c0a56d346..759cad5ded 100644 --- a/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs +++ b/Content.Server/Traitor/Uplink/SurplusBundle/SurplusBundleSystem.cs @@ -3,76 +3,67 @@ using Content.Server.Storage.EntitySystems; using Content.Server.Store.Systems; using Content.Shared.FixedPoint; using Content.Shared.Store; -using Robust.Shared.Prototypes; +using Content.Shared.Store.Components; using Robust.Shared.Random; namespace Content.Server.Traitor.Uplink.SurplusBundle; public sealed class SurplusBundleSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly StoreSystem _store = default!; - private ListingData[] _listings = default!; - public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMapInit); - - SubscribeLocalEvent(OnInit); - } - - private void OnInit(EntityUid uid, SurplusBundleComponent component, ComponentInit args) - { - var storePreset = _prototypeManager.Index(component.StorePreset); - - _listings = _store.GetAvailableListings(uid, null, storePreset.Categories).ToArray(); - - Array.Sort(_listings, (a, b) => (int) (b.Cost.Values.Sum() - a.Cost.Values.Sum())); //this might get weird with multicurrency but don't think about it } private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args) { - FillStorage(uid, component); - } - - private void FillStorage(EntityUid uid, SurplusBundleComponent? component = null) - { - if (!Resolve(uid, ref component)) + if (!TryComp(uid, out var store)) return; - var cords = Transform(uid).Coordinates; + FillStorage((uid, component, store)); + } - var content = GetRandomContent(component.TotalPrice); + private void FillStorage(Entity ent) + { + var cords = Transform(ent).Coordinates; + var content = GetRandomContent(ent); foreach (var item in content) { - var ent = EntityManager.SpawnEntity(item.ProductEntity, cords); - _entityStorage.Insert(ent, uid); + var dode = Spawn(item.ProductEntity, cords); + _entityStorage.Insert(dode, ent); } } // wow, is this leetcode reference? - private List GetRandomContent(FixedPoint2 targetCost) + private List GetRandomContent(Entity ent) { var ret = new List(); - if (_listings.Length == 0) + + var listings = _store.GetAvailableListings(ent, null, ent.Comp2.Categories) + .OrderBy(p => p.Cost.Values.Sum()) + .ToList(); + + if (listings.Count == 0) return ret; var totalCost = FixedPoint2.Zero; var index = 0; - while (totalCost < targetCost) + while (totalCost < ent.Comp1.TotalPrice) { // All data is sorted in price descending order // Find new item with the lowest acceptable price // All expansive items will be before index, all acceptable after - var remainingBudget = targetCost - totalCost; - while (_listings[index].Cost.Values.Sum() > remainingBudget) + var remainingBudget = ent.Comp1.TotalPrice - totalCost; + while (listings[index].Cost.Values.Sum() > remainingBudget) { index++; - if (index >= _listings.Length) + if (index >= listings.Count) { // Looks like no cheap items left // It shouldn't be case for ss14 content @@ -82,8 +73,8 @@ public sealed class SurplusBundleSystem : EntitySystem } // Select random listing and add into crate - var randomIndex = _random.Next(index, _listings.Length); - var randomItem = _listings[randomIndex]; + var randomIndex = _random.Next(index, listings.Count); + var randomItem = listings[randomIndex]; ret.Add(randomItem); totalCost += randomItem.Cost.Values.Sum(); } diff --git a/Content.Server/Traitor/Uplink/UplinkComponent.cs b/Content.Server/Traitor/Uplink/UplinkComponent.cs new file mode 100644 index 0000000000..35f11ce9ef --- /dev/null +++ b/Content.Server/Traitor/Uplink/UplinkComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Traitor.Uplink; + +/// +/// This is used for identifying something as a hidden uplink and showing the UI. +/// +[RegisterComponent] +public sealed partial class UplinkComponent : Component; diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index 5670e28ec9..7c39f1ed66 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.PDA; using Content.Server.Store.Components; using Content.Shared.FixedPoint; using Content.Shared.Store; +using Content.Shared.Store.Components; namespace Content.Server.Traitor.Uplink { @@ -17,18 +18,6 @@ namespace Content.Server.Traitor.Uplink [ValidatePrototypeId] public const string TelecrystalCurrencyPrototype = "Telecrystal"; - /// - /// Gets the amount of TC on an "uplink" - /// Mostly just here for legacy systems based on uplink. - /// - /// - /// the amount of TC - public int GetTCBalance(StoreComponent component) - { - FixedPoint2? tcBalance = component.Balance.GetValueOrDefault(TelecrystalCurrencyPrototype); - return tcBalance?.Int() ?? 0; - } - /// /// Adds an uplink to the target /// @@ -37,7 +26,7 @@ namespace Content.Server.Traitor.Uplink /// The id of the storepreset /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. /// Whether or not the uplink was added successfully - public bool AddUplink(EntityUid user, FixedPoint2? balance, string uplinkPresetId = "StorePresetUplink", EntityUid? uplinkEntity = null) + public bool AddUplink(EntityUid user, FixedPoint2? balance, EntityUid? uplinkEntity = null) { // Try to find target item if (uplinkEntity == null) @@ -47,11 +36,10 @@ namespace Content.Server.Traitor.Uplink return false; } + EnsureComp(uplinkEntity.Value); var store = EnsureComp(uplinkEntity.Value); - _store.InitializeFromPreset(uplinkPresetId, uplinkEntity.Value, store); store.AccountOwner = user; store.Balance.Clear(); - if (balance != null) { store.Balance.Clear(); diff --git a/Content.Server/Store/Components/StoreComponent.cs b/Content.Shared/Store/Components/StoreComponent.cs similarity index 72% rename from Content.Server/Store/Components/StoreComponent.cs rename to Content.Shared/Store/Components/StoreComponent.cs index 0b7dbbea09..223f507971 100644 --- a/Content.Server/Store/Components/StoreComponent.cs +++ b/Content.Shared/Store/Components/StoreComponent.cs @@ -1,46 +1,41 @@ using Content.Shared.FixedPoint; -using Content.Shared.Store; using Robust.Shared.Audio; -using Robust.Shared.Map; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Server.Store.Components; +namespace Content.Shared.Store.Components; /// /// This component manages a store which players can use to purchase different listings /// through the ui. The currency, listings, and categories are defined in yaml. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class StoreComponent : Component { - /// - /// The default preset for the store. Is overriden by default values specified on the component. - /// - [DataField("preset", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Preset; + [DataField] + public LocId Name = "store-ui-default-title"; /// /// All the listing categories that are available on this store. /// The available listings are partially based on the categories. /// - [DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet Categories = new(); + [DataField] + public HashSet> Categories = new(); /// /// The total amount of currency that can be used in the store. /// The string represents the ID of te currency prototype, where the /// float is that amount. /// - [ViewVariables(VVAccess.ReadWrite), DataField("balance", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary Balance = new(); + [DataField] + public Dictionary, FixedPoint2> Balance = new(); /// /// The list of currencies that can be inserted into this store. /// - [ViewVariables(VVAccess.ReadOnly), DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet CurrencyWhitelist = new(); + [DataField] + public HashSet> CurrencyWhitelist = new(); /// /// The person who "owns" the store/account. Used if you want the listings to be fixed @@ -52,6 +47,7 @@ public sealed partial class StoreComponent : Component /// /// All listings, including those that aren't available to the buyer /// + [DataField] public HashSet Listings = new(); /// @@ -70,7 +66,7 @@ public sealed partial class StoreComponent : Component /// The total balance spent in this store. Used for refunds. /// [ViewVariables, DataField] - public Dictionary BalanceSpent = new(); + public Dictionary, FixedPoint2> BalanceSpent = new(); /// /// Controls if the store allows refunds @@ -95,7 +91,7 @@ public sealed partial class StoreComponent : Component /// /// The sound played to the buyer when a purchase is succesfully made. /// - [DataField("buySuccessSound")] + [DataField] public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg"); #endregion } diff --git a/Content.Shared/Store/StoreUi.cs b/Content.Shared/Store/StoreUi.cs index ee4da6991f..59cf1bbbc8 100644 --- a/Content.Shared/Store/StoreUi.cs +++ b/Content.Shared/Store/StoreUi.cs @@ -30,20 +30,6 @@ public sealed class StoreUpdateState : BoundUserInterfaceState } } -/// -/// initializes miscellaneous data about the store. -/// -[Serializable, NetSerializable] -public sealed class StoreInitializeState : BoundUserInterfaceState -{ - public readonly string Name; - - public StoreInitializeState(string name) - { - Name = name; - } -} - [Serializable, NetSerializable] public sealed class StoreRequestUpdateInterfaceMessage : BoundUserInterfaceMessage { diff --git a/Resources/Locale/en-US/store/store.ftl b/Resources/Locale/en-US/store/store.ftl index 997afedfc0..5c1a46339e 100644 --- a/Resources/Locale/en-US/store/store.ftl +++ b/Resources/Locale/en-US/store/store.ftl @@ -8,3 +8,6 @@ store-ui-traitor-warning = Operatives must lock their uplinks after use to avoid store-withdraw-button-ui = Withdraw {$currency} store-ui-button-out-of-stock = {""} (Out of Stock) store-not-account-owner = This {$store} is not bound to you! + +store-preset-name-uplink = Uplink +store-preset-name-spellbook = Spellbook diff --git a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml index 3f9e909c80..ba97af3925 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/syndicate.yml @@ -1,6 +1,6 @@ - type: entity id: CrateSyndicateSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate surplus crate description: Contains 50 telecrystals worth of completely random Syndicate items. It can be useless junk or really good. components: @@ -24,7 +24,7 @@ - type: entity id: CrateSyndicateSuperSurplusBundle - parent: CrateSyndicate + parent: [ CrateSyndicate, StorePresetUplink ] name: Syndicate super surplus crate description: Contains 125 telecrystals worth of completely random Syndicate items. components: diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index b76ca6a14a..b1a6ab0b8f 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -1,6 +1,6 @@ - type: entity abstract: true - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] #PDA's have uplinks so they have to inherit the data. id: BasePDA name: PDA description: Personal Data Assistant. diff --git a/Resources/Prototypes/Entities/Objects/Magic/books.yml b/Resources/Prototypes/Entities/Objects/Magic/books.yml index 554c5214c1..e47fa00c45 100644 --- a/Resources/Prototypes/Entities/Objects/Magic/books.yml +++ b/Resources/Prototypes/Entities/Objects/Magic/books.yml @@ -25,7 +25,7 @@ id: WizardsGrimoire name: wizards grimoire suffix: Wizard - parent: BaseItem + parent: [ BaseItem, StorePresetSpellbook ] components: - type: Sprite sprite: Objects/Misc/books.rsi @@ -46,7 +46,6 @@ - type: Store refundAllowed: true ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are @@ -55,12 +54,11 @@ id: WizardsGrimoireNoRefund name: wizards grimoire suffix: Wizard, No Refund - parent: WizardsGrimoire + parent: [ WizardsGrimoire, StorePresetSpellbook ] components: - type: Store refundAllowed: false ownerOnly: true # get your own tome! - preset: StorePresetSpellbook balance: WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index c92985c2cb..9690d0bdfe 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -143,7 +143,7 @@ - Cuffable # useless if you cant be cuffed - type: entity - parent: BaseSubdermalImplant + parent: [ BaseSubdermalImplant, StorePresetUplink ] id: UplinkImplant name: uplink implant description: This implant lets the user access a hidden Syndicate uplink at will. @@ -155,7 +155,6 @@ components: - Hands # prevent mouse buying grenade penguin since its not telepathic - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 - type: UserInterface diff --git a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml index 459beeef18..53d4f7953b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml @@ -48,7 +48,7 @@ # Uplinks - type: entity - parent: BaseItem + parent: [ BaseItem, StorePresetUplink ] id: BaseUplinkRadio name: syndicate uplink description: Suspiciously looking old radio... @@ -68,7 +68,6 @@ - type: ActivatableUI key: enum.StoreUiKey.Key - type: Store - preset: StorePresetUplink balance: Telecrystal: 0 @@ -78,7 +77,6 @@ suffix: 20 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 20 @@ -88,7 +86,6 @@ suffix: 25 TC components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 25 @@ -99,7 +96,6 @@ suffix: 40 TC, NukeOps components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 40 - type: Tag @@ -112,7 +108,6 @@ suffix: 60 TC, LoneOps components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 60 - type: Tag @@ -125,6 +120,5 @@ suffix: DEBUG components: - type: Store - preset: StorePresetUplink balance: Telecrystal: 99999 diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index 166c29fe41..762ed68921 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -1,29 +1,37 @@ -- type: storePreset +- type: entity id: StorePresetUplink - storeName: Uplink - categories: - - UplinkWeaponry - - UplinkAmmo - - UplinkExplosives - - UplinkChemicals - - UplinkDeception - - UplinkDisruption - - UplinkImplants - - UplinkAllies - - UplinkWearables - - UplinkJob - - UplinkPointless - currencyWhitelist: - - Telecrystal + abstract: true + components: + - type: Store + name: store-preset-name-uplink + categories: + - UplinkWeaponry + - UplinkAmmo + - UplinkExplosives + - UplinkChemicals + - UplinkDeception + - UplinkDisruption + - UplinkImplants + - UplinkAllies + - UplinkWearables + - UplinkJob + - UplinkPointless + currencyWhitelist: + - Telecrystal + balance: + Telecrystal: 0 -- type: storePreset +- type: entity id: StorePresetSpellbook - storeName: Spellbook - categories: - - SpellbookOffensive #Fireball, Rod Form - - SpellbookDefensive #Magic Missile, Wall of Force - - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph - - SpellbookEquipment #Battlemage Robes, Staff of Locker - - SpellbookEvents #Summon Weapons, Summon Ghosts - currencyWhitelist: + abstract: true + components: + - type: Store + name: store-preset-name-spellbook + categories: + - SpellbookOffensive #Fireball, Rod Form + - SpellbookDefensive #Magic Missile, Wall of Force + - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph + - SpellbookEquipment #Battlemage Robes, Staff of Locker + - SpellbookEvents #Summon Weapons, Summon Ghosts + currencyWhitelist: - WizCoin From 5bb0c4d6a23232103d9da97e7df9c4bbfce467cc Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:29:46 -0700 Subject: [PATCH 61/84] Fixed bug where ID card computer defaulted to the atmos as the job icon. (#28462) Fixed ID atmos bug! --- Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index 298912e7d5..82f6ebd8b5 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -27,6 +27,9 @@ namespace Content.Client.Access.UI private string? _lastJobTitle; private string? _lastJobProto; + // The job that will be picked if the ID doesn't have a job on the station. + private static ProtoId _defaultJob = "Passenger"; + public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List> accessLevels) { @@ -65,7 +68,6 @@ namespace Content.Client.Access.UI } JobPresetOptionButton.OnItemSelected += SelectJobPreset; - _accessButtons.Populate(accessLevels, prototypeManager); AccessLevelControlContainer.AddChild(_accessButtons); @@ -172,11 +174,15 @@ namespace Content.Client.Access.UI new List>()); var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype); - if (jobIndex >= 0) + // If the job index is < 0 that means they don't have a job registered in the station records. + // For example, a new ID from a box would have no job index. + if (jobIndex < 0) { - JobPresetOptionButton.SelectId(jobIndex); + jobIndex = _jobPrototypeIds.IndexOf(_defaultJob); } + JobPresetOptionButton.SelectId(jobIndex); + _lastFullName = state.TargetIdFullName; _lastJobTitle = state.TargetIdJobTitle; _lastJobProto = state.TargetIdJobPrototype; From 81a328c543d9d7194570f2d176354aea9554e2a5 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:30:52 +0000 Subject: [PATCH 62/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 2a808f3282..449f9bf43b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Reflected tranquilizer rounds no longer inject the character who reflected - them. - type: Fix - id: 6160 - time: '2024-03-15T13:57:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26141 - author: lzk228 changes: - message: Refill light replacer from box popup now shows properly. @@ -3853,3 +3845,11 @@ id: 6659 time: '2024-06-01T05:51:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28424 +- author: Beck Thompson + changes: + - message: ID computer will now select passenger as the default id icon instead + of atmospheric technician. + type: Fix + id: 6660 + time: '2024-06-01T17:29:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28462 From a29b6a6894f8197be392bfd1c90085b8ea80f86c Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 03:46:35 +1000 Subject: [PATCH 63/84] Clean up new HTNs tasks (#28469) * Clean up new HTNs tasks * Added docs to math operations --- .../NPC/HTN/Preconditions/KeyExistsPrecondition.cs | 7 ++++++- .../NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs | 6 +++++- .../HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs | 7 ++++--- .../HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs | 8 ++++++-- .../Preconditions/Math/KeyFloatGreaterPrecondition.cs | 8 ++++++-- .../HTN/Preconditions/Math/KeyFloatLessPrecondition.cs | 8 ++++++-- .../PrimitiveTasks/Operators/Math/AddFloatOperator.cs | 5 +++-- .../HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs | 5 +++-- .../PrimitiveTasks/Operators/Math/SetFloatOperator.cs | 5 +++-- .../Operators/Math/SetRandomFloatOperator.cs | 8 +++++--- .../NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs | 9 +++++++-- .../NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs | 7 ++++--- 12 files changed, 58 insertions(+), 25 deletions(-) diff --git a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs index 72c4e6367f..69e265f276 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs @@ -1,8 +1,13 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks for the presence of the value by the specified in the . +/// Returns true if there is a value. +/// public sealed partial class KeyExistsPrecondition : HTNPrecondition { - [DataField("key", required: true)] public string Key = string.Empty; + [DataField(required: true), ViewVariables] + public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) { diff --git a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs index c12663901c..8dc38e442a 100644 --- a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs @@ -1,8 +1,12 @@ namespace Content.Server.NPC.HTN.Preconditions; +/// +/// Checks if there is no value at the specified in the . +/// Returns true if there is no value. +/// public sealed partial class KeyNotExistsPrecondition : HTNPrecondition { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs index 8c7920e8be..2abb351272 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs @@ -1,16 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; /// -/// Checks for the presence of data in the blackboard and makes a comparison with the specified boolean +/// Checks if there is a bool value for the specified +/// in the and the specified value is equal to the . /// public sealed partial class KeyBoolEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public bool Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs index 802fdaf2b9..0f7e2cca2a 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is equal to the . +/// public sealed partial class KeyFloatEqualsPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs index 3a9ac36698..b3a27a7d5d 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is greater then . +/// public sealed partial class KeyFloatGreaterPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs index 5cd51d7a7c..c5eb35eebc 100644 --- a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs @@ -1,13 +1,17 @@ namespace Content.Server.NPC.HTN.Preconditions.Math; +/// +/// Checks if there is a float value for the specified +/// in the and the specified value is less then . +/// public sealed partial class KeyFloatLessPrecondition : HTNPrecondition { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string Key = string.Empty; - [DataField(required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public float Value; public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs index 00404517c9..f8ed20544e 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs @@ -4,13 +4,14 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Gets the key, and adds the value to that float +/// Added to float value for the +/// specified in the . /// public sealed partial class AddFloatOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs index a40b96798d..f168326af7 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a bool +/// Set to bool value for the +/// specified in the . /// public sealed partial class SetBoolOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs index 76842b431f..f9b5e54fe3 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs @@ -4,11 +4,12 @@ using System.Threading.Tasks; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Just sets a blackboard key to a float +/// Set to float value for the +/// specified in the . /// public sealed partial class SetFloatOperator : HTNOperator { - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs index 999756f1f7..edcab6baff 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs @@ -5,20 +5,22 @@ using Robust.Shared.Random; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; /// -/// Sets a random float from MinAmount to MaxAmount to blackboard +/// Set random float value between and +/// specified +/// in the . /// public sealed partial class SetRandomFloatOperator : HTNOperator { [Dependency] private readonly IRobustRandom _random = default!; - [DataField(required: true)] + [DataField(required: true), ViewVariables] public string TargetKey = string.Empty; [DataField, ViewVariables(VVAccess.ReadWrite)] public float MaxAmount = 1f; [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MinAmount = 0f; + public float MinAmount; public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs index d1c7d61915..558b1fc04d 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs @@ -20,7 +20,8 @@ public sealed partial class SayKeyOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) @@ -28,8 +29,12 @@ public sealed partial class SayKeyOperator : HTNOperator if (!blackboard.TryGetValue(Key, out var value, _entManager)) return HTNOperatorStatus.Failed; + var @string = value.ToString(); + if (@string is not { }) + return HTNOperatorStatus.Failed; + var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, value.ToString() ?? "Oh no...", InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + _chat.TrySendInGameICMessage(speaker, @string, InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); return base.Update(blackboard, frameTime); } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs index cf07831959..8a4c655a39 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -6,7 +6,7 @@ public sealed partial class SpeakOperator : HTNOperator { private ChatSystem _chat = default!; - [DataField("speech", required: true)] + [DataField(required: true)] public string Speech = string.Empty; /// @@ -18,14 +18,15 @@ public sealed partial class SpeakOperator : HTNOperator public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _chat = IoCManager.Resolve().GetEntitySystem(); + + _chat = sysManager.GetEntitySystem(); } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + return base.Update(blackboard, frameTime); } } From 09256cfaa572d326840edaea1dc0268cd9def5e3 Mon Sep 17 00:00:00 2001 From: AJCM-git <60196617+AJCM-git@users.noreply.github.com> Date: Sat, 1 Jun 2024 13:49:28 -0400 Subject: [PATCH 64/84] Makes machine parts stackable, removes unused field in stack prototypes (#28434) * Makes machine parts stacks, removes unused field in stack prototypes * forgor * Fix tests * Fixes lathe construction. Yes. This sucks but there's no better way that doesnt involve refactoring machine parts completely * detail * a --- .../Tests/MaterialArbitrageTest.cs | 2 +- .../Construction/MachineFrameSystem.cs | 59 +++++++++++--- Content.Server/Stack/StackSystem.cs | 2 +- .../Construction/MachinePartSystem.cs | 4 +- Content.Shared/Materials/MaterialPrototype.cs | 2 +- Content.Shared/Stacks/StackPrototype.cs | 19 ++--- .../Entities/Objects/Misc/machine_parts.yml | 8 ++ .../Entities/Objects/Tools/fulton.yml | 1 - .../Stacks/Materials/Sheets/glass.yml | 7 -- .../Stacks/Materials/Sheets/metal.yml | 3 - .../Stacks/Materials/Sheets/other.yml | 4 - .../Prototypes/Stacks/Materials/crystals.yml | 1 - .../Prototypes/Stacks/Materials/ingots.yml | 2 - .../Prototypes/Stacks/Materials/materials.yml | 13 ---- Resources/Prototypes/Stacks/Materials/ore.yml | 9 --- .../Prototypes/Stacks/Materials/parts.yml | 1 - .../Prototypes/Stacks/consumable_stacks.yml | 11 --- .../Prototypes/Stacks/engineering_stacks.yml | 2 - .../Prototypes/Stacks/floor_tile_stacks.yml | 76 +------------------ .../Prototypes/Stacks/medical_stacks.yml | 8 -- Resources/Prototypes/Stacks/power_stacks.yml | 3 - .../Prototypes/Stacks/science_stacks.yml | 19 ++++- 22 files changed, 88 insertions(+), 168 deletions(-) diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index 7f9c02fc13..ed1e554943 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -103,7 +103,7 @@ public sealed class MaterialArbitrageTest continue; var stackProto = protoManager.Index(materialStep.MaterialPrototypeId); - var spawnProto = protoManager.Index(stackProto.Spawn); + var spawnProto = protoManager.Index(stackProto.Spawn); if (!spawnProto.Components.ContainsKey(materialName) || !spawnProto.Components.TryGetValue(compositionName, out var compositionReg)) diff --git a/Content.Server/Construction/MachineFrameSystem.cs b/Content.Server/Construction/MachineFrameSystem.cs index 09d8d413ec..e20c36d849 100644 --- a/Content.Server/Construction/MachineFrameSystem.cs +++ b/Content.Server/Construction/MachineFrameSystem.cs @@ -59,23 +59,27 @@ public sealed class MachineFrameSystem : EntitySystem return; } - // Machine parts cannot currently satisfy stack/component/tag restrictions. Similarly stacks cannot satisfy - // component/tag restrictions. However, there is no reason this cannot be supported in the future. If this - // changes, then RegenerateProgress() also needs to be updated. - // + // If this changes in the future, then RegenerateProgress() also needs to be updated. // Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is // necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress(). + var stack = CompOrNull(args.Used); + var machinePart = CompOrNull(args.Used); + if (stack != null && machinePart != null) + { + if (TryInsertPartStack(uid, args.Used, component, machinePart, stack)) + args.Handled = true; + return; + } // Handle parts - if (TryComp(args.Used, out var machinePart)) + if (machinePart != null) { if (TryInsertPart(uid, args.Used, component, machinePart)) args.Handled = true; return; } - // Handle stacks - if (TryComp(args.Used, out var stack)) + if (stack != null) { if (TryInsertStack(uid, args.Used, component, stack)) args.Handled = true; @@ -191,6 +195,44 @@ public sealed class MachineFrameSystem : EntitySystem return true; } + /// Whether or not the function had any effect. Does not indicate success. + private bool TryInsertPartStack(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart, StackComponent stack) + { + if (!component.Requirements.ContainsKey(machinePart.PartType)) + return false; + + var progress = component.Progress[machinePart.PartType]; + var requirement = component.Requirements[machinePart.PartType]; + + var needed = requirement - progress; + if (needed <= 0) + return false; + + var count = stack.Count; + if (count < needed) + { + if (!_container.Insert(used, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += count; + return true; + } + + var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); + + if (splitStack == null) + return false; + + if (!_container.Insert(splitStack.Value, component.PartContainer)) + return true; + + component.Progress[machinePart.PartType] += needed; + if (IsComplete(component)) + _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); + + return true; + } + /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack) { @@ -328,8 +370,6 @@ public sealed class MachineFrameSystem : EntitySystem { if (TryComp(part, out var machinePart)) { - DebugTools.Assert(!HasComp(part)); - // Check this is part of the requirements... if (!component.Requirements.ContainsKey(machinePart.PartType)) continue; @@ -338,7 +378,6 @@ public sealed class MachineFrameSystem : EntitySystem component.Progress[machinePart.PartType] = 1; else component.Progress[machinePart.PartType]++; - continue; } diff --git a/Content.Server/Stack/StackSystem.cs b/Content.Server/Stack/StackSystem.cs index 001093a8dd..e34592b45f 100644 --- a/Content.Server/Stack/StackSystem.cs +++ b/Content.Server/Stack/StackSystem.cs @@ -51,7 +51,7 @@ namespace Content.Server.Stack // Get a prototype ID to spawn the new entity. Null is also valid, although it should rarely be picked... var prototype = _prototypeManager.TryIndex(stack.StackTypeId, out var stackType) - ? stackType.Spawn + ? stackType.Spawn.ToString() : Prototype(uid)?.ID; // Set the output parameter in the event instance to the newly split stack. diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 1a19040b41..359b58c881 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -87,9 +87,9 @@ namespace Content.Shared.Construction foreach (var (stackId, amount) in comp.MaterialIdRequirements) { var stackProto = _prototype.Index(stackId); + var defaultProto = _prototype.Index(stackProto.Spawn); - if (_prototype.TryIndex(stackProto.Spawn, out var defaultProto) && - defaultProto.TryGetComponent(out var physComp)) + if (defaultProto.TryGetComponent(out var physComp)) { foreach (var (mat, matAmount) in physComp.MaterialComposition) { diff --git a/Content.Shared/Materials/MaterialPrototype.cs b/Content.Shared/Materials/MaterialPrototype.cs index 905a2359d3..5adf13213e 100644 --- a/Content.Shared/Materials/MaterialPrototype.cs +++ b/Content.Shared/Materials/MaterialPrototype.cs @@ -29,7 +29,7 @@ namespace Content.Shared.Materials /// include which stack we should spawn by default. /// [DataField] - public ProtoId? StackEntity; + public EntProtoId? StackEntity; [DataField] public string Name = string.Empty; diff --git a/Content.Shared/Stacks/StackPrototype.cs b/Content.Shared/Stacks/StackPrototype.cs index 28b7da8f2a..f108419a9e 100644 --- a/Content.Shared/Stacks/StackPrototype.cs +++ b/Content.Shared/Stacks/StackPrototype.cs @@ -4,7 +4,7 @@ using Robust.Shared.Utility; namespace Content.Shared.Stacks; -[Prototype("stack")] +[Prototype] public sealed partial class StackPrototype : IPrototype { [ViewVariables] @@ -15,33 +15,26 @@ public sealed partial class StackPrototype : IPrototype /// Human-readable name for this stack type e.g. "Steel" /// /// This is a localization string ID. - [DataField("name")] + [DataField] public string Name { get; private set; } = string.Empty; /// /// An icon that will be used to represent this stack type. /// - [DataField("icon")] + [DataField] public SpriteSpecifier? Icon { get; private set; } /// /// The entity id that will be spawned by default from this stack. /// - [DataField("spawn", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Spawn { get; private set; } = string.Empty; + [DataField(required: true)] + public EntProtoId Spawn { get; private set; } = string.Empty; /// /// The maximum amount of things that can be in a stack. /// Can be overriden on /// if null, simply has unlimited max count. /// - [DataField("maxCount")] + [DataField] public int? MaxCount { get; private set; } - - /// - /// The size of an individual unit of this stack. - /// - [DataField("itemSize")] - public int? ItemSize; } - diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 62a63c80c3..37de294cce 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -9,6 +9,8 @@ sprite: Objects/Misc/stock_parts.rsi - type: Item size: Tiny + - type: Stack + count: 1 - type: entity id: CapacitorStockPart @@ -25,6 +27,8 @@ - type: Tag tags: - CapacitorStockPart + - type: Stack + stackType: Capacitor - type: entity id: MicroManipulatorStockPart @@ -38,6 +42,8 @@ - type: MachinePart part: Manipulator rating: 1 + - type: Stack + stackType: MicroManipulator - type: entity id: MatterBinStockPart @@ -51,3 +57,5 @@ - type: MachinePart part: MatterBin rating: 1 + - type: Stack + stackType: MatterBin diff --git a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml index 5255e5f303..cfd0b8f770 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/fulton.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/fulton.yml @@ -7,7 +7,6 @@ state: extraction_pack spawn: Fulton1 maxCount: 10 - itemSize: 2 # Entities - type: entity diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml index 0caffb301f..cd6aed7cdf 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/glass.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: glass } spawn: SheetGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedGlass @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rglass } spawn: SheetRGlass1 maxCount: 30 - itemSize: 1 - type: stack id: PlasmaGlass @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: pglass } spawn: SheetPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedPlasmaGlass @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: rpglass } spawn: SheetRPGlass1 maxCount: 30 - itemSize: 1 - type: stack id: UraniumGlass @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: uglass } spawn: SheetUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ReinforcedUraniumGlass @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: ruglass } spawn: SheetRUGlass1 maxCount: 30 - itemSize: 1 - type: stack id: ClockworkGlass @@ -52,4 +46,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/glass.rsi, state: cglass } spawn: SheetClockworkGlass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml index 77f750c205..7520130b9b 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/metal.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: steel } spawn: SheetSteel1 maxCount: 30 - itemSize: 1 - type: stack id: Plasteel @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: plasteel } spawn: SheetPlasteel1 maxCount: 30 - itemSize: 1 - type: stack id: Brass @@ -20,4 +18,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/metal.rsi, state: brass } spawn: SheetBrass1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml index 96f22ae656..565b1fc1ae 100644 --- a/Resources/Prototypes/Stacks/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Stacks/Materials/Sheets/other.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: paper } spawn: SheetPaper1 maxCount: 30 - itemSize: 1 - type: stack id: Plasma @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plasma } spawn: SheetPlasma1 maxCount: 30 - itemSize: 1 - type: stack id: Plastic @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: plastic } spawn: SheetPlastic1 maxCount: 30 - itemSize: 1 - type: stack id: Uranium @@ -28,4 +25,3 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/other.rsi, state: uranium } spawn: SheetUranium1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/crystals.yml b/Resources/Prototypes/Stacks/Materials/crystals.yml index 274f9c10ea..28f4ed70ca 100644 --- a/Resources/Prototypes/Stacks/Materials/crystals.yml +++ b/Resources/Prototypes/Stacks/Materials/crystals.yml @@ -3,4 +3,3 @@ name: telecrystal icon: Objects/Specific/Syndicate/telecrystal.rsi spawn: Telecrystal1 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ingots.yml b/Resources/Prototypes/Stacks/Materials/ingots.yml index 956523c92b..1fd67a096d 100644 --- a/Resources/Prototypes/Stacks/Materials/ingots.yml +++ b/Resources/Prototypes/Stacks/Materials/ingots.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: gold } spawn: IngotGold1 maxCount: 30 - itemSize: 1 - type: stack id: Silver @@ -12,4 +11,3 @@ icon: { sprite: "/Textures/Objects/Materials/ingots.rsi", state: silver } spawn: IngotSilver1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/materials.yml b/Resources/Prototypes/Stacks/Materials/materials.yml index cc963dde59..1157dc3f00 100644 --- a/Resources/Prototypes/Stacks/Materials/materials.yml +++ b/Resources/Prototypes/Stacks/Materials/materials.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Misc/monkeycube.rsi, state: cube } spawn: MaterialBiomass1 maxCount: 100 - itemSize: 1 - type: stack id: WoodPlank @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: wood } spawn: MaterialWoodPlank1 maxCount: 30 - itemSize: 1 - type: stack id: Cardboard @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cardboard } spawn: MaterialCardboard1 maxCount: 30 - itemSize: 1 - type: stack id: Cloth @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cloth } spawn: MaterialCloth1 maxCount: 30 - itemSize: 1 - type: stack id: Durathread @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: durathread } spawn: MaterialDurathread1 maxCount: 30 - itemSize: 1 - type: stack id: Diamond @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: diamond } spawn: MaterialDiamond1 maxCount: 30 - itemSize: 2 - type: stack id: Cotton @@ -52,7 +46,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: cotton } spawn: MaterialCotton1 maxCount: 30 - itemSize: 1 - type: stack id: Pyrotton @@ -60,7 +53,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: pyrotton } spawn: MaterialPyrotton1 maxCount: 30 - itemSize: 1 - type: stack id: Bananium @@ -68,7 +60,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bananium } spawn: MaterialBananium1 maxCount: 10 - itemSize: 2 - type: stack id: MeatSheets @@ -76,7 +67,6 @@ icon: { sprite: /Textures/Objects/Materials/Sheets/meaterial.rsi, state: meat } spawn: MaterialSheetMeat1 maxCount: 30 - itemSize: 1 - type: stack id: WebSilk @@ -84,7 +74,6 @@ icon: { sprite: /Textures/Objects/Materials/silk.rsi, state: icon } spawn: MaterialWebSilk1 maxCount: 50 - itemSize: 1 - type: stack id: Bones @@ -92,7 +81,6 @@ icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: bones} spawn: MaterialBones1 maxCount: 30 - itemSize: 1 - type: stack id: Gunpowder @@ -100,4 +88,3 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: MaterialGunpowder maxCount: 60 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/ore.yml b/Resources/Prototypes/Stacks/Materials/ore.yml index 2a95393c27..51254b5a7a 100644 --- a/Resources/Prototypes/Stacks/Materials/ore.yml +++ b/Resources/Prototypes/Stacks/Materials/ore.yml @@ -4,7 +4,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: gold } spawn: GoldOre1 maxCount: 30 - itemSize: 2 - type: stack id: SteelOre @@ -12,7 +11,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: iron } spawn: SteelOre1 maxCount: 30 - itemSize: 2 - type: stack id: PlasmaOre @@ -20,7 +18,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: plasma } spawn: PlasmaOre1 maxCount: 30 - itemSize: 2 - type: stack id: SilverOre @@ -28,7 +25,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: silver } spawn: SilverOre1 maxCount: 30 - itemSize: 2 - type: stack id: SpaceQuartz @@ -36,7 +32,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: spacequartz } spawn: SpaceQuartz1 maxCount: 30 - itemSize: 2 - type: stack id: UraniumOre @@ -44,7 +39,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: uranium } spawn: UraniumOre1 maxCount: 30 - itemSize: 2 - type: stack @@ -53,7 +47,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: bananium } spawn: BananiumOre1 maxCount: 30 - itemSize: 2 - type: stack id: Coal @@ -61,7 +54,6 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: coal } spawn: Coal1 maxCount: 30 - itemSize: 2 - type: stack id: SaltOre @@ -69,4 +61,3 @@ icon: { sprite: /Textures/Objects/Materials/ore.rsi, state: salt } spawn: Salt1 maxCount: 30 - itemSize: 2 diff --git a/Resources/Prototypes/Stacks/Materials/parts.yml b/Resources/Prototypes/Stacks/Materials/parts.yml index 947bbb1bf2..50ceb28757 100644 --- a/Resources/Prototypes/Stacks/Materials/parts.yml +++ b/Resources/Prototypes/Stacks/Materials/parts.yml @@ -4,4 +4,3 @@ icon: { sprite: /Textures/Objects/Materials/parts.rsi, state: rods } spawn: PartRodMetal1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/consumable_stacks.yml b/Resources/Prototypes/Stacks/consumable_stacks.yml index 2936772f08..e7feab7b52 100644 --- a/Resources/Prototypes/Stacks/consumable_stacks.yml +++ b/Resources/Prototypes/Stacks/consumable_stacks.yml @@ -5,7 +5,6 @@ name: pancake spawn: FoodBakedPancake maxCount: 3 - itemSize: 1 # Food Containers @@ -15,7 +14,6 @@ icon: { sprite: Objects/Consumable/Food/Baked/pizza.rsi, state: box } spawn: FoodBoxPizza maxCount: 30 - itemSize: 10 # Smokeables @@ -25,7 +23,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigpaper } spawn: PaperRolling maxCount: 5 - itemSize: 1 - type: stack id: CigaretteFilter @@ -33,7 +30,6 @@ icon: { sprite: /Textures/Objects/Consumable/Smokeables/Cigarettes/paper.rsi, state: cigfilter } spawn: CigaretteFilter maxCount: 5 - itemSize: 2 - type: stack id: GroundTobacco @@ -41,7 +37,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundTobacco maxCount: 5 - itemSize: 1 - type: stack id: GroundCannabis @@ -49,7 +44,6 @@ icon: { sprite: /Textures/Objects/Misc/reagent_fillings.rsi, state: powderpile } spawn: GroundCannabis maxCount: - itemSize: 1 - type: stack id: GroundCannabisRainbow @@ -57,7 +51,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: powderpile_rainbow } spawn: GroundCannabisRainbow maxCount: - itemSize: 1 - type: stack id: LeavesTobaccoDried @@ -65,7 +58,6 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesTobaccoDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisDried @@ -73,12 +65,9 @@ icon: { sprite: /Textures/Objects/Specific/Hydroponics/tobacco.rsi, state: dried } spawn: LeavesCannabisDried maxCount: 5 - itemSize: 5 - type: stack id: LeavesCannabisRainbowDried name: dried rainbow cannabis leaves icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: dried } spawn: LeavesCannabisRainbowDried - maxCount: 5 - itemSize: 5 diff --git a/Resources/Prototypes/Stacks/engineering_stacks.yml b/Resources/Prototypes/Stacks/engineering_stacks.yml index 77cc620402..b4550015dc 100644 --- a/Resources/Prototypes/Stacks/engineering_stacks.yml +++ b/Resources/Prototypes/Stacks/engineering_stacks.yml @@ -4,11 +4,9 @@ name: inflatable wall spawn: InflatableWallStack1 maxCount: 10 - itemSize: 1 - type: stack id: InflatableDoor name: inflatable door spawn: InflatableDoorStack1 maxCount: 4 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/floor_tile_stacks.yml b/Resources/Prototypes/Stacks/floor_tile_stacks.yml index c5e37013b8..c88786f0dc 100644 --- a/Resources/Prototypes/Stacks/floor_tile_stacks.yml +++ b/Resources/Prototypes/Stacks/floor_tile_stacks.yml @@ -3,469 +3,402 @@ name: steel tile spawn: FloorTileItemSteel maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMetalDiamond name: steel tile spawn: FloorTileItemMetalDiamond maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWood name: wood floor spawn: FloorTileItemWood maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWhite name: white tile spawn: FloorTileItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDark name: dark tile spawn: FloorTileItemDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileTechmaint name: techmaint floor spawn: FloorTileItemTechmaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFreezer name: freezer tile spawn: FloorTileItemFreezer maxCount: 30 - itemSize: 5 - type: stack id: FloorTileShowroom name: showroom tile spawn: FloorTileItemShowroom maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGCircuit name: green-circuit floor spawn: FloorTileItemGCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGold name: gold floor spawn: FloorTileItemGold maxCount: 30 - itemSize: 5 - type: stack id: FloorTileReinforced name: reinforced tile spawn: FloorTileItemReinforced maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMono name: mono tile spawn: FloorTileItemMono maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBrassFilled name: filled brass plate spawn: FloorTileItemBrassFilled maxCount: 30 - itemSize: 5 - + - type: stack id: FloorTileBrassReebe name: smooth brass plate spawn: FloorTileItemBrassReebe maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLino name: linoleum floor spawn: FloorTileItemLino maxCount: 30 - itemSize: 5 - type: stack id: FloorTileHydro name: hydro tile spawn: FloorTileItemHydro maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLime name: lime tile spawn: FloorTileItemLime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileDirty name: dirty tile spawn: FloorTileItemDirty maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleWhite name: white shuttle tile spawn: FloorTileItemShuttleWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlue name: blue shuttle tile spawn: FloorTileItemShuttleBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleOrange name: orange shuttle tile spawn: FloorTileItemShuttleOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttlePurple name: purple shuttle tile spawn: FloorTileItemShuttlePurple maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleRed name: red shuttle tile spawn: FloorTileItemShuttleRed maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleGrey name: grey shuttle tile spawn: FloorTileItemShuttleGrey maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackShuttleBlack name: black shuttle tile spawn: FloorTileItemShuttleBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackEighties name: eighties floor tile spawn: FloorTileItemEighties maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue name: blue arcade tile spawn: FloorTileItemArcadeBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeBlue2 name: blue arcade tile spawn: FloorTileItemArcadeBlue2 maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackArcadeRed name: red arcade tile spawn: FloorTileItemArcadeRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetRed name: red carpet tile spawn: FloorCarpetItemRed maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlack name: block carpet tile spawn: FloorCarpetItemBlack maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetBlue name: blue carpet tile spawn: FloorCarpetItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetGreen name: green carpet tile spawn: FloorCarpetItemGreen maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetOrange name: orange carpet tile spawn: FloorCarpetItemOrange maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetSkyBlue name: skyblue carpet tile spawn: FloorCarpetItemSkyBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPurple name: purple carpet tile spawn: FloorCarpetItemPurple maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetPink name: pink carpet tile spawn: FloorCarpetItemPink maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetCyan name: cyan carpet tile spawn: FloorCarpetItemCyan maxCount: 30 - itemSize: 5 - type: stack id: FloorCarpetWhite name: white carpet tile spawn: FloorCarpetItemWhite maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetClown name: clown carpet tile spawn: FloorTileItemCarpetClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackCarpetOffice name: office carpet tile spawn: FloorTileItemCarpetOffice maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackBoxing name: boxing ring tile spawn: FloorTileItemBoxing maxCount: 30 - itemSize: 5 - type: stack id: FloorTileStackGym name: gym floor tile spawn: FloorTileItemGym maxCount: 30 - itemSize: 5 - type: stack id: FloorTileElevatorShaft name: elevator shaft tile spawn: FloorTileItemElevatorShaft maxCount: 30 - itemSize: 5 - type: stack id: FloorTileRockVault name: rock vault tile spawn: FloorTileItemRockVault maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBlue name: blue floor tile spawn: FloorTileItemBlue maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMining name: mining floor tile spawn: FloorTileItemMining maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningDark name: dark mining floor tile spawn: FloorTileItemMiningDark maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMiningLight name: light mining floor tile spawn: FloorTileItemMiningLight maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBar name: item bar floor tile spawn: FloorTileItemBar maxCount: 30 - itemSize: 5 - type: stack id: FloorTileClown name: clown floor tile spawn: FloorTileItemClown maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMime name: mime floor tile spawn: FloorTileItemMime maxCount: 30 - itemSize: 5 - type: stack id: FloorTileKitchen name: kitchen floor tile spawn: FloorTileItemKitchen maxCount: 30 - itemSize: 5 - type: stack id: FloorTileLaundry name: laundry floor tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileConcrete name: concrete tile spawn: FloorTileItemGrayConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrayConcrete name: gray concrete tile spawn: FloorTileItemLaundry maxCount: 30 - itemSize: 5 - type: stack id: FloorTileOldConcrete name: old concrete tile spawn: FloorTileItemOldConcrete maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSilver name: silver floor tile spawn: FloorTileItemSilver maxCount: 30 - itemSize: 5 - type: stack id: FloorTileBCircuit name: bcircuit floor tile spawn: FloorTileItemBCircuit maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrass name: grass floor tile spawn: FloorTileItemGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGrassJungle name: grass jungle floor tile spawn: FloorTileItemGrassJungle maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSnow name: snow floor tile spawn: FloorTileItemSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodPattern name: wood pattern floor spawn: FloorTileItemWoodPattern maxCount: 30 - itemSize: 5 - type: stack id: FloorTileFlesh name: flesh floor spawn: FloorTileItemFlesh maxCount: 30 - itemSize: 5 - type: stack id: FloorTileSteelMaint name: steel maint floor spawn: FloorTileItemSteelMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileGratingMaint name: grating maint floor spawn: FloorTileItemGratingMaint maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWeb name: web tile spawn: FloorTileItemWeb maxCount: 30 - itemSize: 5 # Faux science tiles - type: stack @@ -473,38 +406,33 @@ name: astro-grass floor spawn: FloorTileItemAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileMowedAstroGrass name: mowed astro-grass floor spawn: FloorTileItemMowedAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileJungleAstroGrass name: jungle astro-grass floor spawn: FloorTileItemJungleAstroGrass maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroIce name: astro-ice floor spawn: FloorTileItemAstroIce maxCount: 30 - itemSize: 5 - type: stack id: FloorTileAstroSnow name: astro-snow floor spawn: FloorTileItemAstroSnow maxCount: 30 - itemSize: 5 - type: stack id: FloorTileWoodLarge name: large wood floor spawn: FloorTileItemWoodLarge - maxCount: 30 \ No newline at end of file + maxCount: 30 diff --git a/Resources/Prototypes/Stacks/medical_stacks.yml b/Resources/Prototypes/Stacks/medical_stacks.yml index 9d2b77ec93..7ad3c21634 100644 --- a/Resources/Prototypes/Stacks/medical_stacks.yml +++ b/Resources/Prototypes/Stacks/medical_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: ointment } spawn: Ointment maxCount: 10 - itemSize: 1 - type: stack id: AloeCream @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Specific/Hydroponics/aloe.rsi", state: cream } spawn: AloeCream maxCount: 10 - itemSize: 1 - type: stack id: Gauze @@ -20,7 +18,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Gauze maxCount: 10 - itemSize: 1 - type: stack id: Brutepack @@ -28,7 +25,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: gauze } spawn: Brutepack maxCount: 10 - itemSize: 1 - type: stack id: Bloodpack @@ -36,7 +32,6 @@ icon: { sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: bloodpack } spawn: Bloodpack maxCount: 10 - itemSize: 1 - type: stack id: MedicatedSuture @@ -44,7 +39,6 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: medicated-suture } spawn: MedicatedSuture maxCount: 10 - itemSize: 1 - type: stack id: RegenerativeMesh @@ -52,6 +46,4 @@ icon: {sprite: "/Textures/Objects/Specific/Medical/medical.rsi", state: regenerative-mesh} spawn: RegenerativeMesh maxCount: 10 - itemSize: 1 - diff --git a/Resources/Prototypes/Stacks/power_stacks.yml b/Resources/Prototypes/Stacks/power_stacks.yml index 5acf4819de..7f015659e9 100644 --- a/Resources/Prototypes/Stacks/power_stacks.yml +++ b/Resources/Prototypes/Stacks/power_stacks.yml @@ -4,7 +4,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coil-30 } spawn: CableApcStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableMV @@ -12,7 +11,6 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilmv-30 } spawn: CableMVStack1 maxCount: 30 - itemSize: 1 - type: stack id: CableHV @@ -20,4 +18,3 @@ icon: { sprite: "/Textures/Objects/Tools/cable-coils.rsi", state: coilhv-30 } spawn: CableHVStack1 maxCount: 30 - itemSize: 1 diff --git a/Resources/Prototypes/Stacks/science_stacks.yml b/Resources/Prototypes/Stacks/science_stacks.yml index 010a5dc659..bf58fad915 100644 --- a/Resources/Prototypes/Stacks/science_stacks.yml +++ b/Resources/Prototypes/Stacks/science_stacks.yml @@ -3,4 +3,21 @@ name: artifact fragment spawn: ArtifactFragment1 maxCount: 30 - itemSize: 5 \ No newline at end of file + +- type: stack + id: Capacitor + name: capacitor + spawn: CapacitorStockPart + maxCount: 10 + +- type: stack + id: MicroManipulator + name: micro manipulator + spawn: MicroManipulatorStockPart + maxCount: 10 + +- type: stack + id: MatterBin + name: matter bin + spawn: MatterBinStockPart + maxCount: 10 From 6501a5b180f61b4e5e5f05f7238a8f7a093dfe2d Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 1 Jun 2024 17:50:34 +0000 Subject: [PATCH 65/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 449f9bf43b..535a93e87e 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: Refill light replacer from box popup now shows properly. - type: Fix - id: 6161 - time: '2024-03-15T15:23:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26136 - author: FairlySadPanda changes: - message: Food and drink stocks in vending machines has been reduced to encourage @@ -3853,3 +3846,10 @@ id: 6660 time: '2024-06-01T17:29:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28462 +- author: AJCM-git + changes: + - message: Machine parts are now stackable + type: Tweak + id: 6661 + time: '2024-06-01T17:49:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28434 From 8c32072f67cbab2f69647ff1e78c3f62e705639a Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 12:00:31 -0700 Subject: [PATCH 66/84] Fix error when removing chasm falling component on a terminating entity (#28471) --- Content.Client/Chasm/ChasmFallingVisualsSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs index 4b04aa9dd7..ddcd509cb3 100644 --- a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs +++ b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs @@ -24,8 +24,11 @@ public sealed class ChasmFallingVisualsSystem : EntitySystem private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args) { - if (!TryComp(uid, out var sprite)) + if (!TryComp(uid, out var sprite) || + TerminatingOrDeleted(uid)) + { return; + } component.OriginalScale = sprite.Scale; From 5d970b0861d5c8327ba8e62fd3dd81333dc125af Mon Sep 17 00:00:00 2001 From: MilenVolf <63782763+MilenVolf@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:34:58 +0300 Subject: [PATCH 67/84] Station event component and system tweaks (#28331) * Make anomaly, artifact and gifts events announcement sound optional * Requested changes + Added new "GameRuleAfterAddedEvent" for StationEventSystem We need to call "Add" in "StationEventSystem" after others GameRule's in case if we need to change StationEventComponent variables. * Fix margins * Makes use of GameRuleComponent.Delay and remove station system handling of delays plus small cleanup * Fix merge --------- Co-authored-by: AJCM --- Content.Server/Chat/Systems/ChatSystem.cs | 4 +- .../Components/StationEventComponent.cs | 19 +- .../StationEvents/Events/AnomalySpawnRule.cs | 8 +- .../Events/BluespaceArtifactRule.cs | 8 +- .../StationEvents/Events/BreakerFlipRule.cs | 9 +- .../StationEvents/Events/CargoGiftsRule.cs | 8 +- .../StationEvents/Events/FalseAlarmRule.cs | 15 +- .../StationEvents/Events/GasLeakRule.cs | 4 +- .../Events/StationEventSystem.cs | 20 +- .../SurveillanceCameraMicrophoneSystem.cs | 4 +- .../station-events/events/anomaly-spawn.ftl | 2 +- .../Prototypes/GameRules/cargo_gifts.yml | 294 +++++++++--------- Resources/Prototypes/GameRules/events.yml | 79 +++-- 13 files changed, 244 insertions(+), 230 deletions(-) diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 531c4259ef..b79e16a8df 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -823,7 +823,7 @@ public sealed partial class ChatSystem : SharedChatSystem recipients.Add(player, new ICChatRecipientData(-1, true)); } - RaiseLocalEvent(new ExpandICChatRecipientstEvent(source, voiceGetRange, recipients)); + RaiseLocalEvent(new ExpandICChatRecipientsEvent(source, voiceGetRange, recipients)); return recipients; } @@ -868,7 +868,7 @@ public sealed partial class ChatSystem : SharedChatSystem /// This event is raised before chat messages are sent out to clients. This enables some systems to send the chat /// messages to otherwise out-of view entities (e.g. for multiple viewports from cameras). /// -public record ExpandICChatRecipientstEvent(EntityUid Source, float VoiceRange, Dictionary Recipients) +public record ExpandICChatRecipientsEvent(EntityUid Source, float VoiceRange, Dictionary Recipients) { } diff --git a/Content.Server/StationEvents/Components/StationEventComponent.cs b/Content.Server/StationEvents/Components/StationEventComponent.cs index cc17007d90..fdd1d3962e 100644 --- a/Content.Server/StationEvents/Components/StationEventComponent.cs +++ b/Content.Server/StationEvents/Components/StationEventComponent.cs @@ -24,6 +24,12 @@ public sealed partial class StationEventComponent : Component [DataField] public string? EndAnnouncement; + [DataField] + public Color StartAnnouncementColor = Color.Gold; + + [DataField] + public Color EndAnnouncementColor = Color.Gold; + [DataField] public SoundSpecifier? StartAudio; @@ -42,12 +48,6 @@ public sealed partial class StationEventComponent : Component [DataField] public int ReoccurrenceDelay = 30; - /// - /// How long after being added does the event start - /// - [DataField] - public TimeSpan StartDelay = TimeSpan.Zero; - /// /// How long the event lasts. /// @@ -75,13 +75,6 @@ public sealed partial class StationEventComponent : Component [DataField] public int? MaxOccurrences; - /// - /// When the station event starts. - /// - [DataField("startTime", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan StartTime; - /// /// When the station event ends. /// diff --git a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs index 96633834ee..cf66a7c052 100644 --- a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs +++ b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs @@ -1,6 +1,5 @@ using Content.Server.Anomaly; using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; @@ -12,11 +11,14 @@ public sealed class AnomalySpawnRule : StationEventSystem(uid, out var stationEvent)) + return; var str = Loc.GetString("anomaly-spawn-event-announcement", ("sighting", Loc.GetString($"anomaly-spawn-sighting-{RobustRandom.Next(1, 6)}"))); - ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5")); + stationEvent.StartAnnouncement = str; + + base.Added(uid, component, gameRule, args); } protected override void Started(EntityUid uid, AnomalySpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) diff --git a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs index b3ed10999e..aaa0f0cffc 100644 --- a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs @@ -1,5 +1,4 @@ using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Random; @@ -9,11 +8,14 @@ public sealed class BluespaceArtifactRule : StationEventSystem(uid, out var stationEvent)) + return; var str = Loc.GetString("bluespace-artifact-event-announcement", ("sighting", Loc.GetString(RobustRandom.Pick(component.PossibleSighting)))); - ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5")); + stationEvent.StartAnnouncement = str; + + base.Added(uid, component, gameRule, args); } protected override void Started(EntityUid uid, BluespaceArtifactRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) diff --git a/Content.Server/StationEvents/Events/BreakerFlipRule.cs b/Content.Server/StationEvents/Events/BreakerFlipRule.cs index 16d3fd8c95..ef28259389 100644 --- a/Content.Server/StationEvents/Events/BreakerFlipRule.cs +++ b/Content.Server/StationEvents/Events/BreakerFlipRule.cs @@ -1,5 +1,4 @@ using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Station.Components; @@ -15,10 +14,14 @@ public sealed class BreakerFlipRule : StationEventSystem(uid, out var stationEvent)) + return; var str = Loc.GetString("station-event-breaker-flip-announcement", ("data", Loc.GetString(Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")))); - ChatSystem.DispatchGlobalAnnouncement(str, playSound: false, colorOverride: Color.Gold); + stationEvent.StartAnnouncement = str; + + base.Added(uid, component, gameRule, args); + } protected override void Started(EntityUid uid, BreakerFlipRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) diff --git a/Content.Server/StationEvents/Events/CargoGiftsRule.cs b/Content.Server/StationEvents/Events/CargoGiftsRule.cs index c27cd30278..4cddf4674b 100644 --- a/Content.Server/StationEvents/Events/CargoGiftsRule.cs +++ b/Content.Server/StationEvents/Events/CargoGiftsRule.cs @@ -3,7 +3,6 @@ using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.GameTicking; using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Prototypes; @@ -18,11 +17,14 @@ public sealed class CargoGiftsRule : StationEventSystem protected override void Added(EntityUid uid, CargoGiftsRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { - base.Added(uid, component, gameRule, args); + if (!TryComp(uid, out var stationEvent)) + return; var str = Loc.GetString(component.Announce, ("sender", Loc.GetString(component.Sender)), ("description", Loc.GetString(component.Description)), ("dest", Loc.GetString(component.Dest))); - ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5")); + stationEvent.StartAnnouncement = str; + + base.Added(uid, component, gameRule, args); } /// diff --git a/Content.Server/StationEvents/Events/FalseAlarmRule.cs b/Content.Server/StationEvents/Events/FalseAlarmRule.cs index e5317a5449..115b6d905e 100644 --- a/Content.Server/StationEvents/Events/FalseAlarmRule.cs +++ b/Content.Server/StationEvents/Events/FalseAlarmRule.cs @@ -1,9 +1,7 @@ using System.Linq; using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using JetBrains.Annotations; -using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.StationEvents.Events; @@ -15,15 +13,16 @@ public sealed class FalseAlarmRule : StationEventSystem protected override void Started(EntityUid uid, FalseAlarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - base.Started(uid, component, gameRule, args); + if (!TryComp(uid, out var stationEvent)) + return; var allEv = _event.AllEvents().Select(p => p.Value).ToList(); var picked = RobustRandom.Pick(allEv); - if (picked.StartAnnouncement != null) - { - ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(picked.StartAnnouncement), playSound: false, colorOverride: Color.Gold); - } - Audio.PlayGlobal(picked.StartAudio, Filter.Broadcast(), true); + stationEvent.StartAnnouncement = picked.StartAnnouncement; + stationEvent.StartAudio = picked.StartAudio; + stationEvent.StartAnnouncementColor = picked.StartAnnouncementColor; + + base.Started(uid, component, gameRule, args); } } diff --git a/Content.Server/StationEvents/Events/GasLeakRule.cs b/Content.Server/StationEvents/Events/GasLeakRule.cs index 1221612171..c17f17a827 100644 --- a/Content.Server/StationEvents/Events/GasLeakRule.cs +++ b/Content.Server/StationEvents/Events/GasLeakRule.cs @@ -29,10 +29,10 @@ namespace Content.Server.StationEvents.Events component.LeakGas = RobustRandom.Pick(component.LeakableGases); // Was 50-50 on using normal distribution. var totalGas = RobustRandom.Next(component.MinimumGas, component.MaximumGas); - var startAfter = stationEvent.StartDelay; component.MolesPerSecond = RobustRandom.Next(component.MinimumMolesPerSecond, component.MaximumMolesPerSecond); - stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.TotalSeconds); + if (gameRule.Delay is {} startAfter) + stationEvent.EndTime = _timing.CurTime + TimeSpan.FromSeconds(totalGas / component.MolesPerSecond + startAfter.Next(RobustRandom)); } // Look technically if you wanted to guarantee a leak you'd do this in announcement but having the announcement diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index bd377ec16c..38390cedb1 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -1,14 +1,11 @@ -using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; using Content.Server.StationEvents.Components; using Content.Shared.Database; using Robust.Shared.Audio.Systems; -using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -20,7 +17,6 @@ namespace Content.Server.StationEvents.Events; public abstract class StationEventSystem : GameRuleSystem where T : IComponent { [Dependency] protected readonly IAdminLogManager AdminLogManager = default!; - [Dependency] protected readonly IMapManager MapManager = default!; [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly ChatSystem ChatSystem = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!; @@ -43,16 +39,12 @@ public abstract class StationEventSystem : GameRuleSystem where T : ICompo if (!TryComp(uid, out var stationEvent)) return; - AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {ToPrettyString(uid)}"); if (stationEvent.StartAnnouncement != null) - { - ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: Color.Gold); - } + ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: stationEvent.StartAnnouncementColor); Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true); - stationEvent.StartTime = Timing.CurTime + stationEvent.StartDelay; } /// @@ -86,9 +78,7 @@ public abstract class StationEventSystem : GameRuleSystem where T : ICompo AdminLogManager.Add(LogType.EventStopped, $"Event ended: {ToPrettyString(uid)}"); if (stationEvent.EndAnnouncement != null) - { - ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playSound: false, colorOverride: Color.Gold); - } + ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playSound: false, colorOverride: stationEvent.EndAnnouncementColor); Audio.PlayGlobal(stationEvent.EndAudio, Filter.Broadcast(), true); } @@ -108,11 +98,7 @@ public abstract class StationEventSystem : GameRuleSystem where T : ICompo if (!GameTicker.IsGameRuleAdded(uid, ruleData)) continue; - if (!GameTicker.IsGameRuleActive(uid, ruleData) && Timing.CurTime >= stationEvent.StartTime) - { - GameTicker.StartGameRule(uid, ruleData); - } - else if (stationEvent.EndTime != null && Timing.CurTime >= stationEvent.EndTime && GameTicker.IsGameRuleActive(uid, ruleData)) + if (stationEvent.EndTime != null && Timing.CurTime >= stationEvent.EndTime && GameTicker.IsGameRuleActive(uid, ruleData)) { GameTicker.EndGameRule(uid, ruleData); } diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index f411001bd3..1adab9930d 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -16,10 +16,10 @@ public sealed class SurveillanceCameraMicrophoneSystem : EntitySystem SubscribeLocalEvent(OnInit); SubscribeLocalEvent(RelayEntityMessage); SubscribeLocalEvent(CanListen); - SubscribeLocalEvent(OnExpandRecipients); + SubscribeLocalEvent(OnExpandRecipients); } - private void OnExpandRecipients(ExpandICChatRecipientstEvent ev) + private void OnExpandRecipients(ExpandICChatRecipientsEvent ev) { var xformQuery = GetEntityQuery(); var sourceXform = Transform(ev.Source); diff --git a/Resources/Locale/en-US/station-events/events/anomaly-spawn.ftl b/Resources/Locale/en-US/station-events/events/anomaly-spawn.ftl index 0e4b5f6e39..907c14ce56 100644 --- a/Resources/Locale/en-US/station-events/events/anomaly-spawn.ftl +++ b/Resources/Locale/en-US/station-events/events/anomaly-spawn.ftl @@ -1,4 +1,4 @@ -anomaly-spawn-event-announcement = Our readings have detected a dangerous interspacial anomaly. Please inform the research team of { $sighting }. +anomaly-spawn-event-announcement = Our readings have detected a dangerous interspacial anomaly. Please inform the research team about { $sighting }. anomaly-spawn-sighting-1 = low pulsating sounds heard throughout the station anomaly-spawn-sighting-2 = strange sources of light diff --git a/Resources/Prototypes/GameRules/cargo_gifts.yml b/Resources/Prototypes/GameRules/cargo_gifts.yml index 62ec96d6a6..c5a5251297 100644 --- a/Resources/Prototypes/GameRules/cargo_gifts.yml +++ b/Resources/Prototypes/GameRules/cargo_gifts.yml @@ -1,16 +1,30 @@ +- type: entity + id: CargoGiftsBase + parent: BaseGameRule + abstract: true + components: + - type: GameRule + delay: + min: 10 + max: 10 + - type: StationEvent + startColor: "#18abf5" + startAudio: + path: /Audio/Announcements/announce.ogg + - type: CargoGiftsRule + sender: cargo-gift-default-sender + - type: entity id: GiftsPizzaPartySmall - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - type: StationEvent weight: 5 - startDelay: 10 duration: 120 earliestStart: 20 - type: CargoGiftsRule description: cargo-gift-pizza-small - sender: cargo-gift-default-sender dest: cargo-gift-dest-bar gifts: FoodPizza: 1 # 4 pizzas @@ -20,191 +34,173 @@ - type: entity id: GiftsPizzaPartyLarge - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 2 - startDelay: 10 - duration: 240 - earliestStart: 20 - minimumPlayers: 40 - - type: CargoGiftsRule - description: cargo-gift-pizza-large - sender: cargo-gift-default-sender - dest: cargo-gift-dest-bar - gifts: - FoodPizzaLarge: 1 # 16 pizzas - FoodBarSupply: 1 - FoodSoftdrinksLarge: 1 + - type: StationEvent + weight: 2 + duration: 240 + earliestStart: 20 + minimumPlayers: 40 + - type: CargoGiftsRule + description: cargo-gift-pizza-large + dest: cargo-gift-dest-bar + gifts: + FoodPizzaLarge: 1 # 16 pizzas + FoodBarSupply: 1 + FoodSoftdrinksLarge: 1 - type: entity id: GiftsEngineering - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 5 - startDelay: 10 - duration: 240 - earliestStart: 30 - minimumPlayers: 10 - - type: CargoGiftsRule - description: cargo-gift-eng - sender: cargo-gift-default-sender - dest: cargo-gift-dest-eng - gifts: - EngineeringCableBulk: 1 - AirlockKit: 1 - MaterialSteel: 1 - MaterialPlasteel: 1 - MaterialGlass: 1 - CrateVendingMachineRestockEngineering: 1 + - type: StationEvent + weight: 5 + duration: 240 + earliestStart: 30 + minimumPlayers: 10 + - type: CargoGiftsRule + description: cargo-gift-eng + dest: cargo-gift-dest-eng + gifts: + EngineeringCableBulk: 1 + AirlockKit: 1 + MaterialSteel: 1 + MaterialPlasteel: 1 + MaterialGlass: 1 + CrateVendingMachineRestockEngineering: 1 - type: entity id: GiftsVendingRestock - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 4 - startDelay: 10 - duration: 120 - minimumPlayers: 40 - earliestStart: 30 - - type: CargoGiftsRule - description: cargo-gift-vending - sender: cargo-gift-default-sender - dest: cargo-gift-dest-supp - gifts: - CrateVendingMachineRestockHotDrinks: 3 - CrateVendingMachineRestockBooze: 1 - CrateVendingMachineRestockNutriMax: 1 - CrateVendingMachineRestockRobustSoftdrinks: 2 - CrateVendingMachineRestockVendomat: 1 - CrateVendingMachineRestockGetmoreChocolateCorp: 1 + - type: StationEvent + weight: 4 + duration: 120 + minimumPlayers: 40 + earliestStart: 30 + - type: CargoGiftsRule + description: cargo-gift-vending + dest: cargo-gift-dest-supp + gifts: + CrateVendingMachineRestockHotDrinks: 3 + CrateVendingMachineRestockBooze: 1 + CrateVendingMachineRestockNutriMax: 1 + CrateVendingMachineRestockRobustSoftdrinks: 2 + CrateVendingMachineRestockVendomat: 1 + CrateVendingMachineRestockGetmoreChocolateCorp: 1 - type: entity id: GiftsJanitor - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 6 - startDelay: 10 - duration: 120 - minimumPlayers: 30 - earliestStart: 20 - - type: CargoGiftsRule - description: cargo-gift-cleaning - sender: cargo-gift-default-sender - dest: cargo-gift-dest-janitor - gifts: - ServiceJanitorial: 2 - ServiceLightsReplacement: 2 - ServiceJanitorBiosuit: 1 + - type: StationEvent + weight: 6 + duration: 120 + minimumPlayers: 30 + earliestStart: 20 + - type: CargoGiftsRule + description: cargo-gift-cleaning + dest: cargo-gift-dest-janitor + gifts: + ServiceJanitorial: 2 + ServiceLightsReplacement: 2 + ServiceJanitorBiosuit: 1 - type: entity id: GiftsMedical - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 8 - startDelay: 10 - duration: 120 - earliestStart: 20 - minimumPlayers: 30 - - type: CargoGiftsRule - description: cargo-gift-medical-supply - sender: cargo-gift-default-sender - dest: cargo-gift-dest-med - gifts: - MedicalSupplies: 1 - MedicalChemistrySupplies: 1 - EmergencyBruteKit: 1 - EmergencyAdvancedKit: 1 - MedicalBiosuit: 1 + - type: StationEvent + weight: 8 + duration: 120 + earliestStart: 20 + minimumPlayers: 30 + - type: CargoGiftsRule + description: cargo-gift-medical-supply + dest: cargo-gift-dest-med + gifts: + MedicalSupplies: 1 + MedicalChemistrySupplies: 1 + EmergencyBruteKit: 1 + EmergencyAdvancedKit: 1 + MedicalBiosuit: 1 - type: entity id: GiftsSpacingSupplies - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 4 - startDelay: 10 - duration: 120 - earliestStart: 10 - minimumPlayers: 40 - - type: CargoGiftsRule - description: cargo-gift-space-protection - sender: cargo-gift-default-sender - dest: cargo-gift-dest-supp - gifts: - EmergencyInternalsLarge: 2 - EmergencyInflatablewall: 1 - EmergencyAdvancedKit: 1 - MedicalBiosuit: 1 - EmergencyO2Kit: 1 + - type: StationEvent + weight: 4 + duration: 120 + earliestStart: 10 + minimumPlayers: 40 + - type: CargoGiftsRule + description: cargo-gift-space-protection + dest: cargo-gift-dest-supp + gifts: + EmergencyInternalsLarge: 2 + EmergencyInflatablewall: 1 + EmergencyAdvancedKit: 1 + MedicalBiosuit: 1 + EmergencyO2Kit: 1 - type: entity id: GiftsFireProtection - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 4 - startDelay: 10 - duration: 120 - earliestStart: 20 - minimumPlayers: 40 - - type: CargoGiftsRule - description: cargo-gift-fire-protection - sender: cargo-gift-default-sender - dest: cargo-gift-dest-supp - gifts: - EmergencyFire: 2 - EmergencyBurnKit: 1 - EmergencyBruteKit: 1 + - type: StationEvent + weight: 4 + duration: 120 + earliestStart: 20 + minimumPlayers: 40 + - type: CargoGiftsRule + description: cargo-gift-fire-protection + dest: cargo-gift-dest-supp + gifts: + EmergencyFire: 2 + EmergencyBurnKit: 1 + EmergencyBruteKit: 1 - type: entity id: GiftsSecurityGuns - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 3 - startDelay: 10 - duration: 120 - earliestStart: 20 - minimumPlayers: 50 - - type: CargoGiftsRule - description: cargo-gift-security-guns - sender: cargo-gift-default-sender - dest: cargo-gift-dest-sec - gifts: - SecurityArmor: 3 - ArmorySmg: 1 - ArmoryShotgun: 1 - ArmoryLaser: 1 + - type: StationEvent + weight: 3 + duration: 120 + earliestStart: 20 + minimumPlayers: 50 + - type: CargoGiftsRule + description: cargo-gift-security-guns + dest: cargo-gift-dest-sec + gifts: + SecurityArmor: 3 + ArmorySmg: 1 + ArmoryShotgun: 1 + ArmoryLaser: 1 - type: entity id: GiftsSecurityRiot - parent: BaseGameRule + parent: CargoGiftsBase noSpawn: true components: - - type: StationEvent - weight: 4 - startDelay: 10 - duration: 120 - earliestStart: 20 - minimumPlayers: 50 - - type: CargoGiftsRule - description: cargo-gift-security-riot - sender: cargo-gift-default-sender - dest: cargo-gift-dest-sec - gifts: - SecurityRiot: 2 - SecurityRestraints: 2 - SecurityNonLethal: 2 + - type: StationEvent + weight: 4 + duration: 120 + earliestStart: 20 + minimumPlayers: 50 + - type: CargoGiftsRule + description: cargo-gift-security-riot + dest: cargo-gift-dest-sec + gifts: + SecurityRiot: 2 + SecurityRestraints: 2 + SecurityNonLethal: 2 diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 9dabc14a34..9f6e6a8125 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -1,22 +1,63 @@ +- type: entity + id: BaseStationEvent + parent: BaseGameRule + abstract: true + noSpawn: true + components: + - type: GameRule + delay: + min: 10 + max: 20 + +- type: entity + id: BaseStationEventShortDelay + parent: BaseGameRule + abstract: true + noSpawn: true + components: + - type: GameRule + delay: + min: 10 + max: 20 + +- type: entity + id: BaseStationEventLongDelay + parent: BaseGameRule + abstract: true + noSpawn: true + components: + - type: GameRule + delay: + min: 40 + max: 60 + - type: entity id: AnomalySpawn - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent + startAnnouncementColor: "#18abf5" + startAudio: + path: /Audio/Announcements/announce.ogg weight: 8 - startDelay: 30 duration: 35 - type: AnomalySpawnRule - type: entity id: BluespaceArtifact - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: + - type: GameRule + delay: + min: 30 + max: 30 - type: StationEvent + startAnnouncementColor: "#18abf5" + startAudio: + path: /Audio/Announcements/announce.ogg weight: 8 - startDelay: 30 duration: 35 - type: BluespaceArtifactRule @@ -148,7 +189,7 @@ - type: entity id: GasLeak - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent @@ -157,25 +198,23 @@ path: /Audio/Announcements/attention.ogg endAnnouncement: station-event-gas-leak-end-announcement weight: 8 - startDelay: 20 - type: GasLeakRule - type: entity id: KudzuGrowth - parent: BaseGameRule + parent: BaseStationEventLongDelay noSpawn: true components: - type: StationEvent earliestStart: 15 minimumPlayers: 15 weight: 7 - startDelay: 50 duration: 240 - type: KudzuGrowthRule - type: entity id: MeteorSwarm - parent: BaseGameRule + parent: BaseStationEventLongDelay noSpawn: true components: - type: StationEvent @@ -189,19 +228,17 @@ params: volume: -4 duration: null #ending is handled by MeteorSwarmRule - startDelay: 30 - type: MeteorSwarmRule - type: entity id: MouseMigration - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement startAudio: path: /Audio/Announcements/attention.ogg - startDelay: 10 earliestStart: 15 weight: 6 duration: 50 @@ -221,14 +258,13 @@ - type: entity id: CockroachMigration - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement startAudio: path: /Audio/Announcements/attention.ogg - startDelay: 10 weight: 6 duration: 50 - type: VentCrittersRule @@ -240,7 +276,7 @@ - type: entity id: PowerGridCheck - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent @@ -251,7 +287,6 @@ path: /Audio/Announcements/power_off.ogg params: volume: -4 - startDelay: 24 duration: 60 maxDuration: 120 - type: PowerGridCheckRule @@ -300,7 +335,7 @@ - type: entity id: VentClog - parent: BaseGameRule + parent: BaseStationEventLongDelay noSpawn: true components: - type: StationEvent @@ -310,20 +345,18 @@ earliestStart: 15 minimumPlayers: 15 weight: 5 - startDelay: 50 duration: 60 - type: VentClogRule - type: entity id: SlimesSpawn - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement startAudio: path: /Audio/Announcements/attention.ogg - startDelay: 10 earliestStart: 20 minimumPlayers: 15 weight: 5 @@ -339,14 +372,13 @@ - type: entity id: SpiderSpawn - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement startAudio: path: /Audio/Announcements/attention.ogg - startDelay: 10 earliestStart: 20 minimumPlayers: 15 weight: 5 @@ -358,14 +390,13 @@ - type: entity id: SpiderClownSpawn - parent: BaseGameRule + parent: BaseStationEventShortDelay noSpawn: true components: - type: StationEvent startAnnouncement: station-event-vent-creatures-start-announcement startAudio: path: /Audio/Announcements/attention.ogg - startDelay: 10 earliestStart: 20 minimumPlayers: 20 weight: 1.5 From ad1fc0cdba0f07f45ff18cee46b28738c226a8b4 Mon Sep 17 00:00:00 2001 From: Kevin Zheng Date: Sat, 1 Jun 2024 12:56:57 -0800 Subject: [PATCH 68/84] Adjust power monitor power display (#28487) --- Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs | 4 +++- .../Locale/en-US/components/power-monitoring-component.ftl | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs index 25a586a75d..1427df0515 100644 --- a/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs +++ b/Content.Client/Power/PowerMonitoringWindow.xaml.Widgets.cs @@ -102,7 +102,8 @@ public sealed partial class PowerMonitoringWindow button.ToolTip = Loc.GetString(name); // Update power value - button.PowerValue.Text = Loc.GetString("power-monitoring-window-value", ("value", entry.PowerValue)); + // Don't use SI prefixes, just give the number in W, so that it is readily apparent which consumer is using a lot of power. + button.PowerValue.Text = Loc.GetString("power-monitoring-window-button-value", ("value", Math.Round(entry.PowerValue).ToString("N0"))); } private void UpdateEntrySourcesOrLoads(BoxContainer masterContainer, BoxContainer currentContainer, PowerMonitoringConsoleEntry[]? entries, SpriteSpecifier.Texture icon) @@ -480,6 +481,7 @@ public sealed class PowerMonitoringButton : Button PowerValue = new Label() { HorizontalAlignment = HAlignment.Right, + Align = Label.AlignMode.Right, SetWidth = 72f, Margin = new Thickness(10, 0, 0, 0), ClipText = true, diff --git a/Resources/Locale/en-US/components/power-monitoring-component.ftl b/Resources/Locale/en-US/components/power-monitoring-component.ftl index e84c09e60d..9433c9ea9e 100644 --- a/Resources/Locale/en-US/components/power-monitoring-component.ftl +++ b/Resources/Locale/en-US/components/power-monitoring-component.ftl @@ -14,6 +14,7 @@ power-monitoring-window-total-sources = Total generator output power-monitoring-window-total-battery-usage = Total battery usage power-monitoring-window-total-loads = Total network loads power-monitoring-window-value = { POWERWATTS($value) } +power-monitoring-window-button-value = {$value} W power-monitoring-window-show-inactive-consumers = Show Inactive Consumers power-monitoring-window-show-cable-networks = Toggle cable networks From 07dfefc4a29927aa74cfd5c7e75c6cb344d713f6 Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:49:34 +1000 Subject: [PATCH 69/84] Hiding and clearing department prototype code (#28114) --- .../UI/BanPanel/BanPanel.xaml.cs | 2 +- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 34 ++++++++++++----- .../Conditions/BuyerDepartmentCondition.cs | 4 +- Content.Shared/Roles/DepartmentPrototype.cs | 37 +++++++++++-------- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index dc263d6055..588d62e560 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -147,7 +147,7 @@ public sealed partial class BanPanel : DefaultWindow var prototypeManager = IoCManager.Resolve(); foreach (var proto in prototypeManager.EnumeratePrototypes()) { - CreateRoleGroup(proto.ID, proto.Roles, proto.Color); + CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color); } CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes().Select(p => p.ID), Color.Red); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index a0e32a3a49..eb182c83ee 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -719,8 +719,17 @@ namespace Content.Client.Lobby.UI _jobPriorities.Clear(); var firstCategory = true; - var departments = _prototypeManager.EnumeratePrototypes().ToArray(); - Array.Sort(departments, DepartmentUIComparer.Instance); + // Get all displayed departments + var departments = new List(); + foreach (var department in _prototypeManager.EnumeratePrototypes()) + { + if (department.EditorHidden) + continue; + + departments.Add(department); + } + + departments.Sort(DepartmentUIComparer.Instance); var items = new[] { @@ -774,7 +783,7 @@ namespace Content.Client.Lobby.UI JobList.AddChild(category); } - var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId)) + var jobs = department.Roles.Select(jobId => _prototypeManager.Index(jobId)) .Where(job => job.SetPreference) .ToArray(); @@ -821,13 +830,15 @@ namespace Content.Client.Lobby.UI if (jobId == job.ID) { other.Select(selectedPrio); + continue; } - else if (selectedJobPrio == JobPriority.High && (JobPriority) other.Selected == JobPriority.High) - { - // Lower any other high priorities to medium. - other.Select((int) JobPriority.Medium); - Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium); - } + + if (selectedJobPrio != JobPriority.High || (JobPriority) other.Selected != JobPriority.High) + continue; + + // Lower any other high priorities to medium. + other.Select((int)JobPriority.Medium); + Profile = Profile?.WithJobPriority(jobId, JobPriority.Medium); } // TODO: Only reload on high change (either to or from). @@ -932,6 +943,11 @@ namespace Content.Client.Lobby.UI SetDirty(); ReloadPreview(); }; + + if (Profile is null) + return; + + UpdateJobPriorities(); } private void OnFlavorTextChange(string content) diff --git a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs index 4e5e504aec..ea8de4a9cc 100644 --- a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs +++ b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs @@ -43,7 +43,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Blacklist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) return false; } } @@ -56,7 +56,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Whitelist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) { found = true; break; diff --git a/Content.Shared/Roles/DepartmentPrototype.cs b/Content.Shared/Roles/DepartmentPrototype.cs index 024eca37fa..d6288bec90 100644 --- a/Content.Shared/Roles/DepartmentPrototype.cs +++ b/Content.Shared/Roles/DepartmentPrototype.cs @@ -1,28 +1,27 @@ using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Roles; [Prototype("department")] public sealed partial class DepartmentPrototype : IPrototype { - [IdDataField] public string ID { get; } = default!; + [IdDataField] + public string ID { get; } = string.Empty; /// - /// A description string to display in the character menu as an explanation of the department's function. + /// A description string to display in the character menu as an explanation of the department's function. /// - [DataField("description", required: true)] - public string Description = default!; + [DataField(required: true)] + public string Description = string.Empty; /// - /// A color representing this department to use for text. + /// A color representing this department to use for text. /// - [DataField("color", required: true)] - public Color Color = default!; + [DataField(required: true)] + public Color Color; - [ViewVariables(VVAccess.ReadWrite), - DataField("roles", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Roles = new(); + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List> Roles = new(); /// /// Whether this is a primary department or not. @@ -34,8 +33,14 @@ public sealed partial class DepartmentPrototype : IPrototype /// /// Departments with a higher weight sorted before other departments in UI. /// - [DataField("weight")] - public int Weight { get; private set; } = 0; + [DataField] + public int Weight { get; private set; } + + /// + /// Toggles the display of the department in the priority setting menu in the character editor. + /// + [DataField] + public bool EditorHidden; } /// @@ -50,14 +55,14 @@ public sealed class DepartmentUIComparer : IComparer { if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; var cmp = -x.Weight.CompareTo(y.Weight); - if (cmp != 0) - return cmp; - return string.Compare(x.ID, y.ID, StringComparison.Ordinal); + return cmp != 0 ? cmp : string.Compare(x.ID, y.ID, StringComparison.Ordinal); } } From da6a1bbdd4a0d69b101d2c85e858b0b3cbf7d552 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Jun 2024 03:03:51 +0200 Subject: [PATCH 70/84] Update Credits (#28493) Co-authored-by: PJBot --- Resources/Credits/GitHub.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index b7ec22c0b0..b3627580b1 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, KEEYNy, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, Subversionary, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, DuskyJay, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, j-giebel, Jackal298, Jackrost, jamessimo, janekvap, Jark255, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, joelhed, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTether, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem From 21bc3bfa22b6e234a8ae96c3332bffee6840a66f Mon Sep 17 00:00:00 2001 From: Tornado Tech <54727692+Tornado-Technology@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:11:19 +1000 Subject: [PATCH 71/84] Cleans up tag system (#28272) * Updated tag system * Added params methods * Fixed tag integration tests * Fixed params methods recursion * Revert has All/Any tag one argument realisation * Updated tag integration tests * Shit happens * Added individual List/HashSet methods, docs, tests --- Content.Client/Verbs/VerbSystem.cs | 3 +- Content.IntegrationTests/Tests/Tag/TagTest.cs | 161 +- .../Administration/Toolshed/TagCommand.cs | 11 +- .../Mech/Systems/MechAssemblySystem.cs | 3 +- .../Procedural/DungeonJob.PostGen.cs | 5 +- Content.Server/Procedural/DungeonJob.cs | 5 +- Content.Server/Procedural/DungeonSystem.cs | 4 + .../EntitySystems/RevenantSystem.Abilities.cs | 2 +- Content.Server/Spreader/SpreaderSystem.cs | 10 +- .../SharedChameleonClothingSystem.cs | 5 +- .../Conditions/NoWindowsInTile.cs | 3 +- .../EntitySystems/AnchorableSystem.cs | 11 +- .../MultipleTagsConstructionGraphStep.cs | 13 +- .../Pinpointer/SharedNavMapSystem.cs | 5 +- Content.Shared/Tag/TagComponent.cs | 16 +- Content.Shared/Tag/TagComponentState.cs | 15 - Content.Shared/Tag/TagPrototype.cs | 24 +- Content.Shared/Tag/TagSystem.cs | 1318 +++++++++-------- 18 files changed, 834 insertions(+), 780 deletions(-) delete mode 100644 Content.Shared/Tag/TagComponentState.cs diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index 2e6c5f5809..5f1f49e5fd 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -123,7 +123,6 @@ namespace Content.Client.Verbs if ((visibility & MenuVisibility.Invisible) == 0) { var spriteQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); for (var i = entities.Count - 1; i >= 0; i--) { @@ -131,7 +130,7 @@ namespace Content.Client.Verbs if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) || !spriteComponent.Visible || - _tagSystem.HasTag(entity, "HideContextMenu", tagQuery)) + _tagSystem.HasTag(entity, "HideContextMenu")) { entities.RemoveSwap(i); } diff --git a/Content.IntegrationTests/Tests/Tag/TagTest.cs b/Content.IntegrationTests/Tests/Tag/TagTest.cs index cbcdd1c6c6..e6cd2accaf 100644 --- a/Content.IntegrationTests/Tests/Tag/TagTest.cs +++ b/Content.IntegrationTests/Tests/Tag/TagTest.cs @@ -53,11 +53,13 @@ namespace Content.IntegrationTests.Tests.Tag EntityUid sTagDummy = default; TagComponent sTagComponent = null!; + Entity sTagEntity = default; await server.WaitPost(() => { sTagDummy = sEntityManager.SpawnEntity(TagEntityId, MapCoordinates.Nullspace); sTagComponent = sEntityManager.GetComponent(sTagDummy); + sTagEntity = new Entity(sTagDummy, sTagComponent); }); await server.WaitAssertion(() => @@ -130,49 +132,64 @@ namespace Content.IntegrationTests.Tests.Tag Assert.Multiple(() => { // Cannot add the starting tag again - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, StartingTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, StartingTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List { StartingTag, StartingTag }), Is.False); + Assert.That(tagSystem.AddTag(sTagEntity, StartingTag), Is.False); + + Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, StartingTag), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new List> { StartingTag, StartingTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new HashSet> { StartingTag, StartingTag }), Is.False); // Has the starting tag Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, StartingTag }), Is.True); // Does not have the added tag yet Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { AddedTag, AddedTag }), Is.False); // Has a combination of the two tags Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True); - Assert.That(tagSystem.HasAnyTag(sTagComponent, new List { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); // Does not have both tags Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, AddedTag), Is.False); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.False); // Cannot remove a tag that does not exist - Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, AddedTag), Is.False); - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.False); - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTag(sTagEntity, AddedTag), Is.False); + + Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new List> { AddedTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new HashSet> { AddedTag, AddedTag }), Is.False); }); // Can add the new tag - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.True); + Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.True); Assert.Multiple(() => { // Cannot add it twice - Assert.That(tagSystem.AddTag(sTagDummy, sTagComponent, AddedTag), Is.False); + Assert.That(tagSystem.AddTag(sTagEntity, AddedTag), Is.False); // Cannot add existing tags - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, StartingTag, AddedTag), Is.False); - Assert.That(tagSystem.AddTags(sTagDummy, sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, StartingTag, AddedTag), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new List> { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.AddTags(sTagEntity, new HashSet> { StartingTag, AddedTag }), Is.False); // Now has two tags Assert.That(sTagComponent.Tags, Has.Count.EqualTo(2)); @@ -180,65 +197,103 @@ namespace Content.IntegrationTests.Tests.Tag // Has both tags Assert.That(tagSystem.HasTag(sTagComponent, StartingTag), Is.True); Assert.That(tagSystem.HasTag(sTagComponent, AddedTag), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, StartingTag, StartingTag), Is.True); Assert.That(tagSystem.HasAllTags(sTagComponent, AddedTag, StartingTag), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { StartingTag, AddedTag }), Is.True); - Assert.That(tagSystem.HasAllTags(sTagComponent, new List { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new List> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAllTags(sTagComponent, new HashSet> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, StartingTag, AddedTag), Is.True); Assert.That(tagSystem.HasAnyTag(sTagComponent, AddedTag, StartingTag), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new List> { AddedTag, StartingTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { StartingTag, AddedTag }), Is.True); + Assert.That(tagSystem.HasAnyTag(sTagComponent, new HashSet> { AddedTag, StartingTag }), Is.True); }); Assert.Multiple(() => { // Remove the existing starting tag - Assert.That(tagSystem.RemoveTag(sTagDummy, sTagComponent, StartingTag), Is.True); + Assert.That(tagSystem.RemoveTag(sTagEntity, StartingTag), Is.True); // Remove the existing added tag - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, AddedTag, AddedTag), Is.True); + Assert.That(tagSystem.RemoveTags(sTagEntity, AddedTag, AddedTag), Is.True); }); Assert.Multiple(() => { // No tags left to remove - Assert.That(tagSystem.RemoveTags(sTagDummy, sTagComponent, new List { StartingTag, AddedTag }), Is.False); + Assert.That(tagSystem.RemoveTags(sTagEntity, new List> { StartingTag, AddedTag }), Is.False); // No tags left in the component Assert.That(sTagComponent.Tags, Is.Empty); }); -#if !DEBUG - return; + // It is run only in DEBUG build, + // as the checks are performed only in DEBUG build. +#if DEBUG + // Has single + Assert.Throws(() => { tagSystem.HasTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasTag(sTagComponent, UnregisteredTag); }); + + // HasAny entityUid methods + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // HasAny component methods + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAnyTag(sTagComponent, new HashSet> { UnregisteredTag }); }); + + // HasAll entityUid methods + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // HasAll component methods + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.HasAllTags(sTagComponent, new HashSet> { UnregisteredTag }); }); + + // RemoveTag single + Assert.Throws(() => { tagSystem.RemoveTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTag(sTagEntity, UnregisteredTag); }); + + // RemoveTags entityUid methods + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // RemoveTags entity methods + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.RemoveTags(sTagEntity, new HashSet> { UnregisteredTag }); }); + + // AddTag single + Assert.Throws(() => { tagSystem.AddTag(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTag(sTagEntity, UnregisteredTag); }); + + // AddTags entityUid methods + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.AddTags(sTagDummy, new HashSet> { UnregisteredTag }); }); + + // AddTags entity methods + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, UnregisteredTag, UnregisteredTag); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, new List> { UnregisteredTag }); }); + Assert.Throws(() => { tagSystem.AddTags(sTagEntity, new HashSet> { UnregisteredTag }); }); #endif - - // Single - Assert.Throws(() => - { - tagSystem.HasTag(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasTag(sTagComponent, UnregisteredTag); - }); - - // Any - Assert.Throws(() => - { - tagSystem.HasAnyTag(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasAnyTag(sTagComponent, UnregisteredTag); - }); - - // All - Assert.Throws(() => - { - tagSystem.HasAllTags(sTagDummy, UnregisteredTag); - }); - Assert.Throws(() => - { - tagSystem.HasAllTags(sTagComponent, UnregisteredTag); - }); }); await pair.CleanReturnAsync(); } diff --git a/Content.Server/Administration/Toolshed/TagCommand.cs b/Content.Server/Administration/Toolshed/TagCommand.cs index 1af2779766..e1cf53e1b1 100644 --- a/Content.Server/Administration/Toolshed/TagCommand.cs +++ b/Content.Server/Administration/Toolshed/TagCommand.cs @@ -1,6 +1,7 @@ using System.Linq; using Content.Shared.Administration; using Content.Shared.Tag; +using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; using Robust.Shared.Toolshed.Syntax; using Robust.Shared.Toolshed.TypeParsers; @@ -13,14 +14,14 @@ public sealed class TagCommand : ToolshedCommand private TagSystem? _tag; [CommandImplementation("list")] - public IEnumerable List([PipedArgument] IEnumerable ent) + public IEnumerable> List([PipedArgument] IEnumerable ent) { return ent.SelectMany(x => { if (TryComp(x, out var tags)) // Note: Cast is required for C# to figure out the type signature. - return (IEnumerable)tags.Tags; - return Array.Empty(); + return (IEnumerable>)tags.Tags; + return Array.Empty>(); }); } @@ -72,7 +73,7 @@ public sealed class TagCommand : ToolshedCommand ) { _tag ??= GetSys(); - _tag.AddTags(input, @ref.Evaluate(ctx)!); + _tag.AddTags(input, (IEnumerable>)@ref.Evaluate(ctx)!); return input; } @@ -92,7 +93,7 @@ public sealed class TagCommand : ToolshedCommand ) { _tag ??= GetSys(); - _tag.RemoveTags(input, @ref.Evaluate(ctx)!); + _tag.RemoveTags(input, (IEnumerable>)@ref.Evaluate(ctx)!); return input; } diff --git a/Content.Server/Mech/Systems/MechAssemblySystem.cs b/Content.Server/Mech/Systems/MechAssemblySystem.cs index e5b7bfaac3..c1fff819b4 100644 --- a/Content.Server/Mech/Systems/MechAssemblySystem.cs +++ b/Content.Server/Mech/Systems/MechAssemblySystem.cs @@ -14,6 +14,7 @@ namespace Content.Server.Mech.Systems; public sealed class MechAssemblySystem : EntitySystem { [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly TagSystem _tag = default!; /// public override void Initialize() @@ -44,7 +45,7 @@ public sealed class MechAssemblySystem : EntitySystem foreach (var (tag, val) in component.RequiredParts) { - if (!val && tagComp.Tags.Contains(tag)) + if (!val && _tag.HasTag(tagComp, tag)) { component.RequiredParts[tag] = true; _container.Insert(args.Used, component.PartsContainer); diff --git a/Content.Server/Procedural/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob.PostGen.cs index b326bcc378..cb9e64f04e 100644 --- a/Content.Server/Procedural/DungeonJob.PostGen.cs +++ b/Content.Server/Procedural/DungeonJob.PostGen.cs @@ -13,6 +13,7 @@ using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -24,13 +25,15 @@ public sealed partial class DungeonJob * Run after the main dungeon generation */ + private static readonly ProtoId WallTag = "Wall"; + private bool HasWall(MapGridComponent grid, Vector2i tile) { var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); while (anchored.MoveNext(out var uid)) { - if (_tagQuery.TryGetComponent(uid, out var tagComp) && tagComp.Tags.Contains("Wall")) + if (_tag.HasTag(uid.Value, WallTag)) return true; } diff --git a/Content.Server/Procedural/DungeonJob.cs b/Content.Server/Procedural/DungeonJob.cs index 8fecf1c9e8..bf2822ff42 100644 --- a/Content.Server/Procedural/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob.cs @@ -28,10 +28,10 @@ public sealed partial class DungeonJob : Job private readonly DecalSystem _decals; private readonly DungeonSystem _dungeon; private readonly EntityLookupSystem _lookup; + private readonly TagSystem _tag; private readonly TileSystem _tile; private readonly SharedMapSystem _maps; private readonly SharedTransformSystem _transform; - private EntityQuery _tagQuery; private readonly DungeonConfigPrototype _gen; private readonly int _seed; @@ -53,6 +53,7 @@ public sealed partial class DungeonJob : Job DecalSystem decals, DungeonSystem dungeon, EntityLookupSystem lookup, + TagSystem tag, TileSystem tile, SharedTransformSystem transform, DungeonConfigPrototype gen, @@ -72,10 +73,10 @@ public sealed partial class DungeonJob : Job _decals = decals; _dungeon = dungeon; _lookup = lookup; + _tag = tag; _tile = tile; _maps = _entManager.System(); _transform = transform; - _tagQuery = _entManager.GetEntityQuery(); _gen = gen; _grid = grid; diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 069508bcbb..36009896a2 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.GameTicking; using Content.Shared.Maps; using Content.Shared.Physics; using Content.Shared.Procedural; +using Content.Shared.Tag; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Console; @@ -31,6 +32,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem [Dependency] private readonly AnchorableSystem _anchorable = default!; [Dependency] private readonly DecalSystem _decals = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly SharedMapSystem _maps = default!; @@ -199,6 +201,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem _decals, this, _lookup, + _tag, _tile, _transform, gen, @@ -231,6 +234,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem _decals, this, _lookup, + _tag, _tile, _transform, gen, diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index fd7d15ea5d..670c64577a 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -246,7 +246,7 @@ public sealed partial class RevenantSystem foreach (var ent in lookup) { //break windows - if (tags.HasComponent(ent) && _tag.HasAnyTag(ent, "Window")) + if (tags.HasComponent(ent) && _tag.HasTag(ent, "Window")) { //hardcoded damage specifiers til i die. var dspec = new DamageSpecifier(); diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index fe14d86aa1..7de8a43d35 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -21,6 +21,7 @@ public sealed class SpreaderSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly TagSystem _tag = default!; /// /// Cached maximum number of updates per spreader prototype. This is applied per-grid. @@ -37,8 +38,7 @@ public sealed class SpreaderSystem : EntitySystem public const float SpreadCooldownSeconds = 1; - [ValidatePrototypeId] - private const string IgnoredTag = "SpreaderIgnore"; + private static readonly ProtoId IgnoredTag = "SpreaderIgnore"; /// public override void Initialize() @@ -189,7 +189,6 @@ public sealed class SpreaderSystem : EntitySystem var airtightQuery = GetEntityQuery(); var dockQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - var tagQuery = GetEntityQuery(); var blockedAtmosDirs = AtmosDirection.Invalid; // Due to docking ports they may not necessarily be opposite directions. @@ -212,7 +211,7 @@ public sealed class SpreaderSystem : EntitySystem // If we're on a blocked tile work out which directions we can go. if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || - tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag)) + _tag.HasTag(ent.Value, IgnoredTag)) { continue; } @@ -250,8 +249,7 @@ public sealed class SpreaderSystem : EntitySystem while (directionEnumerator.MoveNext(out var ent)) { - if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || - tagQuery.TryGetComponent(ent, out var tags) && tags.Tags.Contains(IgnoredTag)) + if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked || _tag.HasTag(ent.Value, IgnoredTag)) { continue; } diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index a447a54df1..fced03bfab 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -12,9 +12,10 @@ public abstract class SharedChameleonClothingSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly ClothingSystem _clothingSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly SharedItemSystem _itemSystem = default!; + [Dependency] private readonly TagSystem _tag = default!; public override void Initialize() { @@ -81,7 +82,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem return false; // check if it is marked as valid chameleon target - if (!proto.TryGetComponent(out TagComponent? tags, _factory) || !tags.Tags.Contains("WhitelistChameleon")) + if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, "WhitelistChameleon")) return false; // check if it's valid clothing diff --git a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs index be6bc2cfac..3ae3b59362 100644 --- a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs +++ b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs @@ -12,13 +12,12 @@ namespace Content.Shared.Construction.Conditions public bool Condition(EntityUid user, EntityCoordinates location, Direction direction) { var entManager = IoCManager.Resolve(); - var tagQuery = entManager.GetEntityQuery(); var sysMan = entManager.EntitySysManager; var tagSystem = sysMan.GetEntitySystem(); foreach (var entity in location.GetEntitiesInTile(LookupFlags.Static)) { - if (tagSystem.HasTag(entity, "Window", tagQuery)) + if (tagSystem.HasTag(entity, "Window")) return false; } diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index c041cf1ba0..e9ef053f62 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Content.Shared.Tag; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; @@ -32,16 +33,14 @@ public sealed partial class AnchorableSystem : EntitySystem [Dependency] private readonly TagSystem _tagSystem = default!; private EntityQuery _physicsQuery; - private EntityQuery _tagQuery; - public const string Unstackable = "Unstackable"; + public readonly ProtoId Unstackable = "Unstackable"; public override void Initialize() { base.Initialize(); _physicsQuery = GetEntityQuery(); - _tagQuery = GetEntityQuery(); SubscribeLocalEvent(OnInteractUsing, before: new[] { typeof(ItemSlotsSystem) }, after: new[] { typeof(SharedConstructionSystem) }); @@ -312,7 +311,7 @@ public sealed partial class AnchorableSystem : EntitySystem DebugTools.Assert(!Transform(uid).Anchored); // If we are unstackable, iterate through any other entities anchored on the current square - return _tagSystem.HasTag(uid, Unstackable, _tagQuery) && AnyUnstackablesAnchoredAt(location); + return _tagSystem.HasTag(uid, Unstackable) && AnyUnstackablesAnchoredAt(location); } public bool AnyUnstackablesAnchoredAt(EntityCoordinates location) @@ -327,10 +326,8 @@ public sealed partial class AnchorableSystem : EntitySystem while (enumerator.MoveNext(out var entity)) { // If we find another unstackable here, return true. - if (_tagSystem.HasTag(entity.Value, Unstackable, _tagQuery)) - { + if (_tagSystem.HasTag(entity.Value, Unstackable)) return true; - } } return false; diff --git a/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs b/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs index 668952dac2..07ba46946a 100644 --- a/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/MultipleTagsConstructionGraphStep.cs @@ -1,14 +1,15 @@ using Content.Shared.Tag; +using Robust.Shared.Prototypes; namespace Content.Shared.Construction.Steps { public sealed partial class MultipleTagsConstructionGraphStep : ArbitraryInsertConstructionGraphStep { [DataField("allTags")] - private List? _allTags; + private List>? _allTags; [DataField("anyTags")] - private List? _anyTags; + private List>? _anyTags; private static bool IsNullOrEmpty(ICollection? list) { @@ -21,16 +22,12 @@ namespace Content.Shared.Construction.Steps if (IsNullOrEmpty(_allTags) && IsNullOrEmpty(_anyTags)) return false; // Step is somehow invalid, we return. - // No tags at all. - if (!entityManager.TryGetComponent(uid, out TagComponent? tags)) - return false; - var tagSystem = entityManager.EntitySysManager.GetEntitySystem(); - if (_allTags != null && !tagSystem.HasAllTags(tags, _allTags)) + if (_allTags != null && !tagSystem.HasAllTags(uid, _allTags)) return false; // We don't have all the tags needed. - if (_anyTags != null && !tagSystem.HasAnyTag(tags, _anyTags)) + if (_anyTags != null && !tagSystem.HasAnyTag(uid, _anyTags)) return false; // We don't have any of the tags needed. // This entity is valid! diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 7c12321b5d..3ced5f3c9e 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using Content.Shared.Tag; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -24,7 +25,7 @@ public abstract class SharedNavMapSystem : EntitySystem [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!; - private readonly string[] _wallTags = ["Wall", "Window"]; + private static readonly ProtoId[] WallTags = {"Wall", "Window"}; private EntityQuery _doorQuery; public override void Initialize() @@ -58,7 +59,7 @@ public abstract class SharedNavMapSystem : EntitySystem if (_doorQuery.HasComp(uid)) return NavMapChunkType.Airlock; - if (_tagSystem.HasAnyTag(uid, _wallTags)) + if (_tagSystem.HasAnyTag(uid, WallTags)) return NavMapChunkType.Wall; return NavMapChunkType.Invalid; diff --git a/Content.Shared/Tag/TagComponent.cs b/Content.Shared/Tag/TagComponent.cs index b5b8a48a44..ad4240ba06 100644 --- a/Content.Shared/Tag/TagComponent.cs +++ b/Content.Shared/Tag/TagComponent.cs @@ -1,13 +1,11 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Robust.Shared.Prototypes; -namespace Content.Shared.Tag +namespace Content.Shared.Tag; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(TagSystem))] +public sealed partial class TagComponent : Component { - [RegisterComponent, NetworkedComponent, Access(typeof(TagSystem))] - public sealed partial class TagComponent : Component - { - [DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - [Access(typeof(TagSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends - public HashSet Tags = new(); - } + [DataField, ViewVariables, AutoNetworkedField] + public HashSet> Tags = new(); } diff --git a/Content.Shared/Tag/TagComponentState.cs b/Content.Shared/Tag/TagComponentState.cs deleted file mode 100644 index 9919aba108..0000000000 --- a/Content.Shared/Tag/TagComponentState.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Tag -{ - [Serializable, NetSerializable] - public sealed class TagComponentState : ComponentState - { - public TagComponentState(string[] tags) - { - Tags = tags; - } - - public string[] Tags { get; } - } -} diff --git a/Content.Shared/Tag/TagPrototype.cs b/Content.Shared/Tag/TagPrototype.cs index 2a06e22cf9..97f8c1af7d 100644 --- a/Content.Shared/Tag/TagPrototype.cs +++ b/Content.Shared/Tag/TagPrototype.cs @@ -1,17 +1,15 @@ using Robust.Shared.Prototypes; -namespace Content.Shared.Tag +namespace Content.Shared.Tag; + +/// +/// Prototype representing a tag in YAML. +/// Meant to only have an ID property, as that is the only thing that +/// gets saved in TagComponent. +/// +[Prototype("Tag")] +public sealed partial class TagPrototype : IPrototype { - /// - /// Prototype representing a tag in YAML. - /// Meant to only have an ID property, as that is the only thing that - /// gets saved in TagComponent. - /// - [Prototype("Tag")] - public sealed partial class TagPrototype : IPrototype - { - [ViewVariables] - [IdDataField] - public string ID { get; private set; } = default!; - } + [IdDataField, ViewVariables] + public string ID { get; } = string.Empty; } diff --git a/Content.Shared/Tag/TagSystem.cs b/Content.Shared/Tag/TagSystem.cs index 7bcb887a41..f1f620a694 100644 --- a/Content.Shared/Tag/TagSystem.cs +++ b/Content.Shared/Tag/TagSystem.cs @@ -1,11 +1,19 @@ -using System.Diagnostics; -using System.Linq; -using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Tag; +/// +/// The system that is responsible for working with tags. +/// Checking the existence of the only happens in DEBUG builds, +/// to improve performance, so don't forget to check it. +/// +/// +/// The methods to add or remove a list of tags have only an implementation with the type, +/// it's not much, but it takes away performance, +/// if you need to use them often, it's better to make a proper implementation, +/// you can read more HERE. +/// public sealed class TagSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; @@ -15,685 +23,693 @@ public sealed class TagSystem : EntitySystem public override void Initialize() { base.Initialize(); + _tagQuery = GetEntityQuery(); - SubscribeLocalEvent(OnTagGetState); - SubscribeLocalEvent(OnTagHandleState); #if DEBUG SubscribeLocalEvent(OnTagInit); +#endif } +#if DEBUG private void OnTagInit(EntityUid uid, TagComponent component, ComponentInit args) { foreach (var tag in component.Tags) { AssertValidTag(tag); } + } #endif + + /// + /// Tries to add a tag to an entity if the tag doesn't already exist. + /// + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool AddTag(EntityUid entityUid, ProtoId tag) + { + return AddTag((entityUid, EnsureComp(entityUid)), tag); } - - private void OnTagHandleState(EntityUid uid, TagComponent component, ref ComponentHandleState args) + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(EntityUid entityUid, params ProtoId[] tags) { - if (args.Current is not TagComponentState state) - return; + return AddTags(entityUid, (IEnumerable>)tags); + } - component.Tags.Clear(); + /// + /// Tries to add the given tags to an entity if the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(EntityUid entityUid, IEnumerable> tags) + { + return AddTags((entityUid, EnsureComp(entityUid)), tags); + } - foreach (var tag in state.Tags) + /// + /// Tries to add a tag to an entity if it has a + /// and the tag doesn't already exist. + /// + /// + /// true if it was added, false otherwise even if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool TryAddTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + AddTag((entityUid, component), tag); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool TryAddTags(EntityUid entityUid, params ProtoId[] tags) + { + return TryAddTags(entityUid, (IEnumerable>)tags); + } + + /// + /// Tries to add the given tags to an entity if it has a + /// and the tags don't already exist. + /// + /// + /// true if any tags were added, false otherwise even if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool TryAddTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + AddTags((entityUid, component), tags); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasTag(component, tag); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAllTags(EntityUid entityUid, ProtoId tag) => + HasTag(entityUid, tag); + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, params ProtoId[] tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, HashSet> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, List> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if all of the given tags have been added to an entity. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAllTags(component, tags); + } + + /// + /// Checks if a tag has been added to an entity. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAnyTag(EntityUid entityUid, ProtoId tag) => + HasTag(entityUid, tag); + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, params ProtoId[] tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, HashSet> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, List> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + HasAnyTag(component, tags); + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasTag(TagComponent component, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + return component.Tags.Contains(tag); + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAllTags(TagComponent component, ProtoId tag) => + HasTag(component, tag); + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, params ProtoId[] tags) + { + foreach (var tag in tags) { +#if DEBUG AssertValidTag(tag); - component.Tags.Add(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; } + + return true; } - private static void OnTagGetState(EntityUid uid, TagComponent component, ref ComponentGetState args) + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTagsArray(TagComponent component, ProtoId[] tags) { - var tags = new string[component.Tags.Count]; - var i = 0; - - foreach (var tag in component.Tags) + foreach (var tag in tags) { - tags[i] = tag; - i++; +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; } - args.State = new TagComponentState(tags); + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, List> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, HashSet> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if all of the given tags have been added to an component. + /// + /// + /// true if they all exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, IEnumerable> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!component.Tags.Contains(tag)) + return false; + } + + return true; + } + + /// + /// Checks if a tag has been added to an component. + /// + /// + /// true if it exists, false otherwise. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool HasAnyTag(TagComponent component, ProtoId tag) => + HasTag(component, tag); + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, params ProtoId[] tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, HashSet> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, List> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Checks if any of the given tags have been added to an component. + /// + /// + /// true if any of them exist, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent component, IEnumerable> tags) + { + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (component.Tags.Contains(tag)) + return true; + } + + return false; + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool RemoveTag(EntityUid entityUid, ProtoId tag) + { + return _tagQuery.TryComp(entityUid, out var component) && + RemoveTag((entityUid, component), tag); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(EntityUid entityUid, params ProtoId[] tags) + { + return RemoveTags(entityUid, (IEnumerable>)tags); + } + + /// + /// Tries to remove a tag from an entity if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(EntityUid entityUid, IEnumerable> tags) + { + return _tagQuery.TryComp(entityUid, out var component) && + RemoveTags((entityUid, component), tags); + } + + /// + /// Tries to add a tag if it doesn't already exist. + /// + /// + /// true if it was added, false if it already existed. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool AddTag(Entity entity, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (!entity.Comp.Tags.Add(tag)) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// + /// true if any tags were added, false if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(Entity entity, params ProtoId[] tags) + { + return AddTags(entity, (IEnumerable>)tags); + } + + /// + /// Tries to add the given tags if they don't already exist. + /// + /// + /// true if any tags were added, false if they all already existed. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool AddTags(Entity entity, IEnumerable> tags) + { + var update = false; + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (entity.Comp.Tags.Add(tag) && !update) + update = true; + } + + if (!update) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to remove a tag if it exists. + /// + /// + /// true if it was removed, false otherwise even if it didn't exist. + /// + /// + /// Thrown if no exists with the given id. + /// + public bool RemoveTag(Entity entity, ProtoId tag) + { +#if DEBUG + AssertValidTag(tag); +#endif + + if (!entity.Comp.Tags.Remove(tag)) + return false; + + Dirty(entity); + return true; + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// + /// true if any tag was removed, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(Entity entity, params ProtoId[] tags) + { + return RemoveTags(entity, (IEnumerable>)tags); + } + + /// + /// Tries to remove all of the given tags if they exist. + /// + /// + /// true if any tag was removed, false otherwise. + /// + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool RemoveTags(Entity entity, IEnumerable> tags) + { + var update = false; + foreach (var tag in tags) + { +#if DEBUG + AssertValidTag(tag); +#endif + if (entity.Comp.Tags.Remove(tag) && !update) + update = true; + } + + if (!update) + return false; + + Dirty(entity); + return true; } private void AssertValidTag(string id) { DebugTools.Assert(_proto.HasIndex(id), $"Unknown tag: {id}"); } - - /// - /// Tries to add a tag to an entity if the tag doesn't already exist. - /// - /// The entity to add the tag to. - /// The tag to add. - /// - /// true if it was added, false otherwise even if it already existed. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool AddTag(EntityUid entity, string id) - { - return AddTag(entity, EnsureComp(entity), id); - } - - /// - /// Tries to add the given tags to an entity if the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid entity, params string[] ids) - { - return AddTags(entity, EnsureComp(entity), ids); - } - - /// - /// Tries to add the given tags to an entity if the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid entity, IEnumerable ids) - { - return AddTags(entity, EnsureComp(entity), ids); - } - - /// - /// Tries to add a tag to an entity if it has a - /// and the tag doesn't already exist. - /// - /// The entity to add the tag to. - /// The tag to add. - /// - /// true if it was added, false otherwise even if it already existed. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool TryAddTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - AddTag(entity, component, id); - } - - /// - /// Tries to add the given tags to an entity if it has a - /// and the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool TryAddTags(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - AddTags(entity, component, ids); - } - - /// - /// Tries to add the given tags to an entity if it has a - /// and the tags don't already exist. - /// - /// The entity to add the tag to. - /// The tags to add. - /// - /// true if any tags were added, false otherwise even if they all already existed. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool TryAddTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - AddTags(entity, component, ids); - } - - /// - /// Checks if a tag has been added to an entity. - /// - /// The entity to check. - /// The tag to check for. - /// true if it exists, false otherwise. - /// - /// Thrown if no exists with the given id. - /// - public bool HasTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - HasTag(component, id); - } - - /// - /// Checks if a tag has been added to an entity. - /// - [Obsolete] - public bool HasTag(EntityUid entity, string id, EntityQuery tagQuery) - { - return tagQuery.TryGetComponent(entity, out var component) && - HasTag(component, id); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, string id) => HasTag(entity, id); - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, List ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if all of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(EntityUid entity, List> ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAllTags(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tag to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, string id) => HasTag(entity, id); - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, List ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, List> ids) - { - return TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Checks if any of the given tags have been added to an entity. - /// - /// The entity to check. - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - HasAnyTag(component, ids); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool RemoveTag(EntityUid entity, string id) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTag(entity, component, id); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// Thrown if one of the ids represents an unregistered . - /// - /// - public bool RemoveTags(EntityUid entity, params string[] ids) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTags(entity, component, ids); - } - - /// - /// Tries to remove a tag from an entity if it exists. - /// - /// The entity to remove the tag from. - /// The tag to remove. - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid entity, IEnumerable ids) - { - return _tagQuery.TryComp(entity, out var component) && - RemoveTags(entity, component, ids); - } - - /// - /// Tries to add a tag if it doesn't already exist. - /// - /// The tag to add. - /// true if it was added, false if it already existed. - /// - /// Thrown if no exists with the given id. - /// - public bool AddTag(EntityUid uid, TagComponent component, string id) - { - AssertValidTag(id); - var added = component.Tags.Add(id); - - if (added) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Tries to add the given tags if they don't already exist. - /// - /// The tags to add. - /// true if any tags were added, false if they all already existed. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid uid, TagComponent component, params string[] ids) - { - return AddTags(uid, component, ids.AsEnumerable()); - } - - /// - /// Tries to add the given tags if they don't already exist. - /// - /// The tags to add. - /// true if any tags were added, false if they all already existed. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool AddTags(EntityUid uid, TagComponent component, IEnumerable ids) - { - var count = component.Tags.Count; - - foreach (var id in ids) - { - AssertValidTag(id); - component.Tags.Add(id); - } - - if (component.Tags.Count > count) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Checks if a tag has been added. - /// - /// The tag to check for. - /// true if it exists, false otherwise. - /// - /// Thrown if no exists with the given id. - /// - public bool HasTag(TagComponent component, string id) - { - AssertValidTag(id); - return component.Tags.Contains(id); - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, params string[] ids) - { - return HasAllTags(component, ids.AsEnumerable()); - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tag to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, string id) => HasTag(component, id); - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, List ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - } - - return true; - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, IEnumerable ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - - } - - return true; - } - - /// - /// Checks if all of the given tags have been added. - /// - /// The tags to check for. - /// true if they all exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAllTags(TagComponent component, List> ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (!component.Tags.Contains(id)) - return false; - - } - - return true; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, params string[] ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - return true; - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tag to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, string id) => HasTag(component, id); - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, List ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - { - return true; - } - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent component, IEnumerable ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (component.Tags.Contains(id)) - { - return true; - } - } - - return false; - } - - /// - /// Checks if any of the given tags have been added. - /// - /// The tags to check for. - /// true if any of them exist, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool HasAnyTag(TagComponent comp, List> ids) - { - foreach (var id in ids) - { - AssertValidTag(id); - - if (comp.Tags.Contains(id)) - return true; - } - - return false; - } - - /// - /// Tries to remove a tag if it exists. - /// - /// - /// true if it was removed, false otherwise even if it didn't exist. - /// - /// - /// Thrown if no exists with the given id. - /// - public bool RemoveTag(EntityUid uid, TagComponent component, string id) - { - AssertValidTag(id); - - if (component.Tags.Remove(id)) - { - Dirty(uid, component); - return true; - } - - return false; - } - - /// - /// Tries to remove all of the given tags if they exist. - /// - /// The tags to remove. - /// - /// true if it was removed, false otherwise even if they didn't exist. - /// - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid uid, TagComponent component, params string[] ids) - { - return RemoveTags(uid, component, ids.AsEnumerable()); - } - - /// - /// Tries to remove all of the given tags if they exist. - /// - /// The tags to remove. - /// true if any tag was removed, false otherwise. - /// - /// Thrown if one of the ids represents an unregistered . - /// - public bool RemoveTags(EntityUid uid, TagComponent component, IEnumerable ids) - { - var count = component.Tags.Count; - - foreach (var id in ids) - { - AssertValidTag(id); - component.Tags.Remove(id); - } - - if (component.Tags.Count < count) - { - Dirty(uid, component); - return true; - } - - return false; - } } From 29e34cae350acde79412f3781c9fcfddbda8b949 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Sat, 1 Jun 2024 18:41:06 -0700 Subject: [PATCH 72/84] Disable rainbow overlay when reduced motion is enabled (#28496) Disable rainbow ovelray when reduced motion is enabled --- Content.Client/Drugs/RainbowOverlay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Content.Client/Drugs/RainbowOverlay.cs b/Content.Client/Drugs/RainbowOverlay.cs index e62b0dfa66..fb48c91010 100644 --- a/Content.Client/Drugs/RainbowOverlay.cs +++ b/Content.Client/Drugs/RainbowOverlay.cs @@ -1,7 +1,9 @@ +using Content.Shared.CCVar; using Content.Shared.Drugs; using Content.Shared.StatusEffect; using Robust.Client.Graphics; using Robust.Client.Player; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -10,6 +12,7 @@ namespace Content.Client.Drugs; public sealed class RainbowOverlay : Overlay { + [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -75,6 +78,10 @@ public sealed class RainbowOverlay : Overlay protected override void Draw(in OverlayDrawArgs args) { + // TODO disable only the motion part or ike's idea (single static frame of the overlay) + if (_config.GetCVar(CCVars.ReducedMotion)) + return; + if (ScreenTexture == null) return; From dce68e48e83d22d76c08bd33c27cd4e8597fa43d Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 01:42:12 +0000 Subject: [PATCH 73/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 535a93e87e..0c671f49ee 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: FairlySadPanda - changes: - - message: Food and drink stocks in vending machines has been reduced to encourage - people to use the kitchen and bar. - type: Tweak - id: 6162 - time: '2024-03-15T23:28:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25999 - author: Plykiya changes: - message: Ion storms now have a chance of happening from the start of shift. @@ -3853,3 +3845,11 @@ id: 6661 time: '2024-06-01T17:49:28.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28434 +- author: DrSmugleaf + changes: + - message: Disabled the seeing rainbows screen effect when reduced motion is enabled + in the options menu. + type: Tweak + id: 6662 + time: '2024-06-02T01:41:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28496 From d6ba166d3bb57f6f6438adee89ffdd7b20fb9635 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sat, 1 Jun 2024 20:10:24 -0700 Subject: [PATCH 74/84] Replace obsolete EntityWhitelist IsValid usages (#28465) * Replace obsolete whitelist is valid with whitelist system * Consistency * Fix logic * Bork * I figured out how to get whitelists on the client lol * test fail * woops * HELP ME FUNCTIONS * Fix errors * simplify --------- Co-authored-by: plykiya --- Content.Client/Chat/UI/EmotesMenu.xaml.cs | 9 +- .../UI/ConstructionMenuPresenter.cs | 6 +- .../Chat/Systems/ChatSystem.Emote.cs | 4 +- Content.Server/Chat/Systems/ChatSystem.cs | 2 + Content.Server/Gatherable/GatherableSystem.cs | 6 +- .../NPCImprintingOnSpawnBehaviourSystem.cs | 4 +- .../NPC/Systems/NPCUtilitySystem.cs | 4 +- .../Power/EntitySystems/ChargerSystem.cs | 4 +- .../EntitySystems/RevenantSystem.Abilities.cs | 8 +- .../Silicons/Borgs/BorgSystem.Modules.cs | 2 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 8 +- .../Storage/EntitySystems/PickRandomSystem.cs | 6 +- .../XenoArtifacts/ArtifactSystem.Nodes.cs | 11 ++- .../Containers/ItemSlot/ItemSlotsSystem.cs | 5 +- .../Damage/Systems/DamageContactsSystem.cs | 4 +- Content.Shared/Devour/SharedDevourSystem.cs | 4 +- .../Disposal/SharedDisposalUnitSystem.cs | 8 +- .../Implants/SharedImplanterSystem.cs | 5 +- .../Interaction/SmartEquipSystem.cs | 6 +- .../Materials/SharedMaterialStorageSystem.cs | 6 +- .../Systems/SharedChameleonProjectorSystem.cs | 6 +- .../Salvage/Fulton/SharedFultonSystem.cs | 4 +- .../Shuttles/Systems/SharedShuttleSystem.cs | 4 +- .../EntitySystems/MagnetPickupSystem.cs | 5 +- .../SharedEntityStorageSystem.cs | 4 +- .../EntitySystems/SharedStorageSystem.cs | 10 +-- .../Marker/SharedDamageMarkerSystem.cs | 4 +- .../Systems/SharedGunSystem.Ballistic.cs | 5 +- .../Systems/SharedGunSystem.Revolver.cs | 2 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 + .../Whitelist/EntityWhitelistSystem.cs | 84 +++++++++++++++++++ 31 files changed, 186 insertions(+), 56 deletions(-) diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index a26d319920..3340755343 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -1,7 +1,8 @@ -using System.Numerics; +using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Chat.Prototypes; using Content.Shared.Speech; +using Content.Shared.Whitelist; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; @@ -19,6 +20,7 @@ public sealed partial class EmotesMenu : RadialMenu [Dependency] private readonly ISharedPlayerManager _playerManager = default!; private readonly SpriteSystem _spriteSystem; + private readonly EntityWhitelistSystem _whitelistSystem; public event Action>? OnPlayEmote; @@ -28,6 +30,7 @@ public sealed partial class EmotesMenu : RadialMenu RobustXamlLoader.Load(this); _spriteSystem = _entManager.System(); + _whitelistSystem = _entManager.System(); var main = FindControl("Main"); @@ -37,8 +40,8 @@ public sealed partial class EmotesMenu : RadialMenu var player = _playerManager.LocalSession?.AttachedEntity; if (emote.Category == EmoteCategory.Invalid || emote.ChatTriggers.Count == 0 || - !(player.HasValue && (emote.Whitelist?.IsValid(player.Value, _entManager) ?? true)) || - (emote.Blacklist?.IsValid(player.Value, _entManager) ?? false)) + !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || + _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) continue; if (!emote.Available && diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 9a09436176..0c7912e0bc 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Client.UserInterface.Systems.MenuBar.Widgets; using Content.Shared.Construction.Prototypes; using Content.Shared.Tag; +using Content.Shared.Whitelist; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Placement; @@ -23,6 +24,7 @@ namespace Content.Client.Construction.UI /// internal sealed class ConstructionMenuPresenter : IDisposable { + [Dependency] private readonly EntityManager _entManager = default!; [Dependency] private readonly IEntitySystemManager _systemManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; @@ -30,6 +32,7 @@ namespace Content.Client.Construction.UI [Dependency] private readonly IPlayerManager _playerManager = default!; private readonly IConstructionMenuView _constructionView; + private readonly EntityWhitelistSystem _whitelistSystem; private ConstructionSystem? _constructionSystem; private ConstructionPrototype? _selected; @@ -78,6 +81,7 @@ namespace Content.Client.Construction.UI // This is a lot easier than a factory IoCManager.InjectDependencies(this); _constructionView = new ConstructionMenu(); + _whitelistSystem = _entManager.System(); // This is required so that if we load after the system is initialized, we can bind to it immediately if (_systemManager.TryGetEntitySystem(out var constructionSystem)) @@ -157,7 +161,7 @@ namespace Content.Client.Construction.UI if (_playerManager.LocalSession == null || _playerManager.LocalEntity == null - || (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value))) + || _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value)) continue; if (!string.IsNullOrEmpty(search)) diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index d120812b88..23e1517d75 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -81,9 +81,7 @@ public partial class ChatSystem bool ignoreActionBlocker = false ) { - if (!(emote.Whitelist?.IsValid(source, EntityManager) ?? true)) - return; - if (emote.Blacklist?.IsValid(source, EntityManager) ?? false) + if (_whitelistSystem.IsWhitelistFailOrNull(emote.Whitelist, source) || _whitelistSystem.IsBlacklistPass(emote.Blacklist, source)) return; if (!emote.Available && diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b79e16a8df..0fe9dcbc4d 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Players; using Content.Shared.Radio; using Content.Shared.Speech; +using Content.Shared.Whitelist; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -58,6 +59,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const int VoiceRange = 10; // how far voice goes in world units public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units diff --git a/Content.Server/Gatherable/GatherableSystem.cs b/Content.Server/Gatherable/GatherableSystem.cs index e24b0da593..d6a3be451b 100644 --- a/Content.Server/Gatherable/GatherableSystem.cs +++ b/Content.Server/Gatherable/GatherableSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Gatherable.Components; using Content.Shared.Interaction; using Content.Shared.Tag; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Prototypes; @@ -18,6 +19,7 @@ public sealed partial class GatherableSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -30,7 +32,7 @@ public sealed partial class GatherableSystem : EntitySystem private void OnAttacked(Entity gatherable, ref AttackedEvent args) { - if (gatherable.Comp.ToolWhitelist?.IsValid(args.Used, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.Used)) return; Gather(gatherable, args.User); @@ -41,7 +43,7 @@ public sealed partial class GatherableSystem : EntitySystem if (args.Handled || !args.Complex) return; - if (gatherable.Comp.ToolWhitelist?.IsValid(args.User, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.User)) return; Gather(gatherable, args.User); diff --git a/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs index cfd3b08c61..c3e0328037 100644 --- a/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs +++ b/Content.Server/NPC/Systems/NPCImprintingOnSpawnBehaviourSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Shared.NPC.Components; using Content.Shared.NPC.Systems; +using Content.Shared.Whitelist; using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Random; @@ -13,6 +14,7 @@ public sealed partial class NPCImprintingOnSpawnBehaviourSystem : SharedNPCImpri [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -27,7 +29,7 @@ public sealed partial class NPCImprintingOnSpawnBehaviourSystem : SharedNPCImpri foreach (var friend in friends) { - if (imprinting.Comp.Whitelist?.IsValid(friend) != false) + if (_whitelistSystem.IsWhitelistPassOrNull(imprinting.Comp.Whitelist, friend)) { AddImprintingTarget(imprinting, friend, imprinting.Comp); } diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index 2e8c628b50..ca74d71335 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Tools.Systems; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Whitelist; using Microsoft.Extensions.ObjectPool; using Robust.Server.Containers; using Robust.Shared.Prototypes; @@ -46,6 +47,7 @@ public sealed class NPCUtilitySystem : EntitySystem [Dependency] private readonly SolutionContainerSystem _solutions = default!; [Dependency] private readonly WeldableSystem _weldable = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _puddleQuery; private EntityQuery _xformQuery; @@ -249,7 +251,7 @@ public sealed class NPCUtilitySystem : EntitySystem return 0f; } - if (heldGun.Whitelist?.IsValid(targetUid, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(heldGun.Whitelist, targetUid)) { return 0f; } diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index 1ff13a24f2..038295eac1 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Containers; using System.Diagnostics.CodeAnalysis; using Content.Shared.Storage.Components; using Robust.Server.Containers; +using Content.Shared.Whitelist; namespace Content.Server.Power.EntitySystems; @@ -20,6 +21,7 @@ internal sealed class ChargerSystem : EntitySystem [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -208,7 +210,7 @@ internal sealed class ChargerSystem : EntitySystem if (!receiverComponent.Powered) return; - if (component.Whitelist?.IsValid(targetEntity, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, targetEntity)) return; if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery)) diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 670c64577a..c889d59f15 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -28,6 +28,7 @@ using Content.Shared.Revenant.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; using Robust.Shared.Map.Components; +using Content.Shared.Whitelist; namespace Content.Server.Revenant.EntitySystems; @@ -40,6 +41,7 @@ public sealed partial class RevenantSystem [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly TileSystem _tile = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private void InitializeAbilities() { @@ -331,10 +333,8 @@ public sealed partial class RevenantSystem foreach (var ent in _lookup.GetEntitiesInRange(uid, component.MalfunctionRadius)) { - if (component.MalfunctionWhitelist?.IsValid(ent, EntityManager) == false) - continue; - - if (component.MalfunctionBlacklist?.IsValid(ent, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(component.MalfunctionWhitelist, ent) || + _whitelistSystem.IsBlacklistPass(component.MalfunctionBlacklist, ent)) continue; _emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable. diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index cc57c34c47..5c600be3f6 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -267,7 +267,7 @@ public sealed partial class BorgSystem return false; } - if (component.ModuleWhitelist?.IsValid(module, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.ModuleWhitelist, module)) { if (user != null) Popup.PopupEntity(Loc.GetString("borg-module-whitelist-deny"), uid, user.Value); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 082e38921a..1ab7f5387f 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Roles; using Content.Shared.Silicons.Borgs; using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -53,6 +54,8 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [ValidatePrototypeId] public const string BorgJobId = "Borg"; @@ -104,9 +107,8 @@ public sealed partial class BorgSystem : SharedBorgSystem return; } - if (component.BrainEntity == null && - brain != null && - component.BrainWhitelist?.IsValid(used) != false) + if (component.BrainEntity == null && brain != null && + _whitelistSystem.IsWhitelistPassOrNull(component.BrainWhitelist, used)) { if (_mind.TryGetMind(used, out _, out var mind) && mind.Session != null) { diff --git a/Content.Server/Storage/EntitySystems/PickRandomSystem.cs b/Content.Server/Storage/EntitySystems/PickRandomSystem.cs index dbbe1dd778..f0e986e199 100644 --- a/Content.Server/Storage/EntitySystems/PickRandomSystem.cs +++ b/Content.Server/Storage/EntitySystems/PickRandomSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Database; using Content.Shared.Hands.EntitySystems; using Content.Shared.Storage; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Random; @@ -15,6 +16,7 @@ public sealed class PickRandomSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -30,7 +32,7 @@ public sealed class PickRandomSystem : EntitySystem var user = args.User; - var enabled = storage.Container.ContainedEntities.Any(item => comp.Whitelist?.IsValid(item, EntityManager) ?? true); + var enabled = storage.Container.ContainedEntities.Any(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item)); // alt-click / alt-z to pick an item args.Verbs.Add(new AlternativeVerb @@ -48,7 +50,7 @@ public sealed class PickRandomSystem : EntitySystem private void TryPick(EntityUid uid, PickRandomComponent comp, StorageComponent storage, EntityUid user) { - var entities = storage.Container.ContainedEntities.Where(item => comp.Whitelist?.IsValid(item, EntityManager) ?? true).ToArray(); + var entities = storage.Container.ContainedEntities.Where(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item)).ToArray(); if (!entities.Any()) return; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs index 895bb0217b..65aaabdf0e 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs @@ -1,15 +1,16 @@ using System.Linq; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Whitelist; using Content.Shared.Xenoarchaeology.XenoArtifacts; using JetBrains.Annotations; -using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager; namespace Content.Server.Xenoarchaeology.XenoArtifacts; public sealed partial class ArtifactSystem { + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private const int MaxEdgesPerNode = 4; private readonly HashSet _usedNodeIds = new(); @@ -81,7 +82,8 @@ public sealed partial class ArtifactSystem private string GetRandomTrigger(EntityUid artifact, ref ArtifactNode node) { var allTriggers = _prototype.EnumeratePrototypes() - .Where(x => (x.Whitelist?.IsValid(artifact, EntityManager) ?? true) && (!x.Blacklist?.IsValid(artifact, EntityManager) ?? true)).ToList(); + .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) && + _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList(); var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList(); var weights = GetDepthWeights(validDepth, node.Depth); @@ -95,7 +97,8 @@ public sealed partial class ArtifactSystem private string GetRandomEffect(EntityUid artifact, ref ArtifactNode node) { var allEffects = _prototype.EnumeratePrototypes() - .Where(x => (x.Whitelist?.IsValid(artifact, EntityManager) ?? true) && (!x.Blacklist?.IsValid(artifact, EntityManager) ?? true)).ToList(); + .Where(x => _whitelistSystem.IsWhitelistPassOrNull(x.Whitelist, artifact) && + _whitelistSystem.IsBlacklistFailOrNull(x.Blacklist, artifact)).ToList(); var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList(); var weights = GetDepthWeights(validDepth, node.Depth); diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 2e3f9ed461..48f4f07cbe 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; @@ -31,6 +32,7 @@ namespace Content.Shared.Containers.ItemSlots [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -266,8 +268,7 @@ namespace Content.Shared.Containers.ItemSlots if (slot.ContainerSlot == null) return false; - if ((!slot.Whitelist?.IsValid(usedUid) ?? false) || - (slot.Blacklist?.IsValid(usedUid) ?? false)) + if (_whitelistSystem.IsWhitelistFail(slot.Whitelist, usedUid) || _whitelistSystem.IsBlacklistPass(slot.Blacklist, usedUid)) { if (popup.HasValue && slot.WhitelistFailPopup.HasValue) _popupSystem.PopupClient(Loc.GetString(slot.WhitelistFailPopup), uid, popup.Value); diff --git a/Content.Shared/Damage/Systems/DamageContactsSystem.cs b/Content.Shared/Damage/Systems/DamageContactsSystem.cs index aec3d0766a..b08ef77fed 100644 --- a/Content.Shared/Damage/Systems/DamageContactsSystem.cs +++ b/Content.Shared/Damage/Systems/DamageContactsSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Damage.Components; +using Content.Shared.Whitelist; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -11,6 +12,7 @@ public sealed class DamageContactsSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -63,7 +65,7 @@ public sealed class DamageContactsSystem : EntitySystem if (HasComp(otherUid)) return; - if (component.IgnoreWhitelist?.IsValid(otherUid) ?? false) + if (_whitelistSystem.IsWhitelistFail(component.IgnoreWhitelist, otherUid)) return; var damagedByContact = EnsureComp(otherUid); diff --git a/Content.Shared/Devour/SharedDevourSystem.cs b/Content.Shared/Devour/SharedDevourSystem.cs index 3d73b14dd3..14047fba7d 100644 --- a/Content.Shared/Devour/SharedDevourSystem.cs +++ b/Content.Shared/Devour/SharedDevourSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.DoAfter; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -18,6 +19,7 @@ public abstract class SharedDevourSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -41,7 +43,7 @@ public abstract class SharedDevourSystem : EntitySystem /// protected void OnDevourAction(EntityUid uid, DevourerComponent component, DevourActionEvent args) { - if (args.Handled || component.Whitelist?.IsValid(args.Target, EntityManager) != true) + if (args.Handled || _whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Target)) return; args.Handled = true; diff --git a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs index c39139f9a5..9fdb4a6a80 100644 --- a/Content.Shared/Disposal/SharedDisposalUnitSystem.cs +++ b/Content.Shared/Disposal/SharedDisposalUnitSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.DragDrop; using Content.Shared.Emag.Systems; using Content.Shared.Item; using Content.Shared.Throwing; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -25,6 +26,7 @@ public abstract class SharedDisposalUnitSystem : EntitySystem [Dependency] protected readonly IGameTiming GameTiming = default!; [Dependency] protected readonly MetaDataSystem Metadata = default!; [Dependency] protected readonly SharedJointSystem Joints = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; protected static TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5); @@ -113,10 +115,8 @@ public abstract class SharedDisposalUnitSystem : EntitySystem if (!storable && !HasComp(entity)) return false; - if (component.Blacklist?.IsValid(entity, EntityManager) == true) - return false; - - if (component.Whitelist != null && component.Whitelist?.IsValid(entity, EntityManager) != true) + if (_whitelistSystem.IsBlacklistPass(component.Blacklist, entity) || + _whitelistSystem.IsWhitelistFail(component.Whitelist, entity)) return false; if (TryComp(entity, out var physics) && (physics.CanCollide) || storable) diff --git a/Content.Shared/Implants/SharedImplanterSystem.cs b/Content.Shared/Implants/SharedImplanterSystem.cs index d78522b56c..44803e721c 100644 --- a/Content.Shared/Implants/SharedImplanterSystem.cs +++ b/Content.Shared/Implants/SharedImplanterSystem.cs @@ -20,6 +20,7 @@ public abstract class SharedImplanterSystem : EntitySystem [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -105,8 +106,8 @@ public abstract class SharedImplanterSystem : EntitySystem protected bool CheckTarget(EntityUid target, EntityWhitelist? whitelist, EntityWhitelist? blacklist) { - return whitelist?.IsValid(target, EntityManager) != false && - blacklist?.IsValid(target, EntityManager) != true; + return _whitelistSystem.IsWhitelistPassOrNull(whitelist, target) && + _whitelistSystem.IsBlacklistFailOrNull(blacklist, target); } //Draw the implant out of the target diff --git a/Content.Shared/Interaction/SmartEquipSystem.cs b/Content.Shared/Interaction/SmartEquipSystem.cs index fb2bc3c460..bba294db28 100644 --- a/Content.Shared/Interaction/SmartEquipSystem.cs +++ b/Content.Shared/Interaction/SmartEquipSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.ActionBlocker; +using Content.Shared.ActionBlocker; using Content.Shared.Containers.ItemSlots; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -7,6 +7,7 @@ using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Player; @@ -25,6 +26,7 @@ public sealed class SmartEquipSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// public override void Initialize() @@ -182,7 +184,7 @@ public sealed class SmartEquipSystem : EntitySystem foreach (var slot in slots.Slots.Values) { if (!slot.HasItem - && (slot.Whitelist?.IsValid(handItem.Value, EntityManager) ?? true) + && _whitelistSystem.IsWhitelistPassOrNull(slot.Whitelist, handItem.Value) && slot.Priority > (toInsertTo?.Priority ?? int.MinValue)) { toInsertTo = slot; diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index b1de77d971..af815bdb2b 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Interaction; using Content.Shared.Interaction.Components; using Content.Shared.Stacks; +using Content.Shared.Whitelist; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -17,6 +18,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; /// /// Default volume for a sheet if the material's entity prototype has no material composition. @@ -121,7 +123,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!CanTakeVolume(uid, volume, component)) return false; - if (component.MaterialWhiteList != null && !component.MaterialWhiteList.Contains(materialId)) + if (component.MaterialWhiteList == null ? false : component.MaterialWhiteList.Contains(materialId)) return false; var amount = component.Storage.GetValueOrDefault(materialId); @@ -239,7 +241,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!Resolve(toInsert, ref material, ref composition, false)) return false; - if (storage.Whitelist?.IsValid(toInsert) == false) + if (_whitelistSystem.IsWhitelistFail(storage.Whitelist, toInsert)) return false; if (HasComp(toInsert)) diff --git a/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs index c1abfc526f..00096b7d40 100644 --- a/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs +++ b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Popups; using Robust.Shared.Serialization.Manager; using Robust.Shared.Prototypes; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Whitelist; namespace Content.Shared.Polymorph.Systems; @@ -18,6 +19,7 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly ISerializationManager _serMan = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -49,8 +51,8 @@ public abstract class SharedChameleonProjectorSystem : EntitySystem /// public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target) { - return (comp.Whitelist?.IsValid(target, EntityManager) == false) - || (comp.Blacklist?.IsValid(target, EntityManager) == true); + return _whitelistSystem.IsWhitelistFail(comp.Whitelist, target) + || _whitelistSystem.IsBlacklistPass(comp.Blacklist, target); } /// diff --git a/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs b/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs index b355ae5873..f94558b0b3 100644 --- a/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs +++ b/Content.Shared/Salvage/Fulton/SharedFultonSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Stacks; using Content.Shared.Verbs; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -30,6 +31,7 @@ public abstract partial class SharedFultonSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStackSystem _stack = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [ValidatePrototypeId] public const string EffectProto = "FultonEffect"; protected static readonly Vector2 EffectOffset = Vector2.Zero; @@ -176,7 +178,7 @@ public abstract partial class SharedFultonSystem : EntitySystem if (!CanFulton(targetUid)) return false; - if (component.Whitelist?.IsValid(targetUid, EntityManager) != true) + if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, targetUid)) return false; return true; diff --git a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs index d859d9f485..a382e943ff 100644 --- a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs +++ b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.UI.MapObjects; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Collision.Shapes; @@ -15,6 +16,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; [Dependency] protected readonly SharedTransformSystem XformSystem = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const float FTLRange = 512f; public const float FTLBufferRange = 8f; @@ -83,7 +85,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem if (HasComp(mapUid)) return false; - return destination.Whitelist?.IsValid(shuttleUid, EntityManager) != false; + return _whitelistSystem.IsWhitelistPassOrNull(destination.Whitelist, shuttleUid); } /// diff --git a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs index 03da2d09b0..7a8961485d 100644 --- a/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs +++ b/Content.Shared/Storage/EntitySystems/MagnetPickupSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Storage.Components; using Content.Shared.Inventory; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; @@ -16,6 +17,8 @@ public sealed class MagnetPickupSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + private static readonly TimeSpan ScanDelay = TimeSpan.FromSeconds(1); @@ -63,7 +66,7 @@ public sealed class MagnetPickupSystem : EntitySystem foreach (var near in _lookup.GetEntitiesInRange(uid, comp.Range, LookupFlags.Dynamic | LookupFlags.Sundries)) { - if (storage.Whitelist?.IsValid(near, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(storage.Whitelist, near)) continue; if (!_physicsQuery.TryGetComponent(near, out var physics) || physics.BodyStatus != BodyStatus.OnGround) diff --git a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs index 0576e46df4..bb49725e04 100644 --- a/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedEntityStorageSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Storage.Components; using Content.Shared.Tools.Systems; using Content.Shared.Verbs; using Content.Shared.Wall; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -45,6 +46,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem [Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly WeldableSystem _weldable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public const string ContainerName = "entity_storage"; @@ -432,7 +434,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem var targetIsMob = HasComp(toInsert); var storageIsItem = HasComp(container); - var allowedToEat = component.Whitelist?.IsValid(toInsert) ?? HasComp(toInsert); + var allowedToEat = component.Whitelist == null ? HasComp(toInsert) : _whitelistSystem.IsValid(component.Whitelist, toInsert); // BEFORE REPLACING THIS WITH, I.E. A PROPERTY: // Make absolutely 100% sure you have worked out how to stop people ending up in backpacks. diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index ee087901f3..d87b57bfc8 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -55,6 +55,7 @@ public abstract class SharedStorageSystem : EntitySystem [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; [Dependency] protected readonly UseDelaySystem UseDelay = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private EntityQuery _itemQuery; private EntityQuery _stackQuery; @@ -860,13 +861,8 @@ public abstract class SharedStorageSystem : EntitySystem return false; } - if (storageComp.Whitelist?.IsValid(insertEnt, EntityManager) == false) - { - reason = "comp-storage-invalid-container"; - return false; - } - - if (storageComp.Blacklist?.IsValid(insertEnt, EntityManager) == true) + if (_whitelistSystem.IsWhitelistFail(storageComp.Whitelist, insertEnt) || + _whitelistSystem.IsBlacklistPass(storageComp.Blacklist, insertEnt)) { reason = "comp-storage-invalid-container"; return false; diff --git a/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs index d1814020e6..eab638a9fc 100644 --- a/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs +++ b/Content.Shared/Weapons/Marker/SharedDamageMarkerSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Content.Shared.Projectiles; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; @@ -15,6 +16,7 @@ public abstract class SharedDamageMarkerSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { @@ -58,7 +60,7 @@ public abstract class SharedDamageMarkerSystem : EntitySystem if (!args.OtherFixture.Hard || args.OurFixtureId != SharedProjectileSystem.ProjectileFixture || component.Amount <= 0 || - component.Whitelist?.IsValid(args.OtherEntity, EntityManager) == false || + _whitelistSystem.IsWhitelistFail(component.Whitelist, args.OtherEntity) || !TryComp(uid, out var projectile) || projectile.Weapon == null) { diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 784dd0793a..1f9e6cee76 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -41,7 +41,10 @@ public abstract partial class SharedGunSystem private void OnBallisticInteractUsing(EntityUid uid, BallisticAmmoProviderComponent component, InteractUsingEvent args) { - if (args.Handled || component.Whitelist?.IsValid(args.Used, EntityManager) != true) + if (args.Handled) + return; + + if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Used)) return; if (GetBallisticShots(component) >= component.Capacity) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs index b8b00799c1..14aaff5bf7 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Revolver.cs @@ -89,7 +89,7 @@ public partial class SharedGunSystem public bool TryRevolverInsert(EntityUid revolverUid, RevolverAmmoProviderComponent component, EntityUid uid, EntityUid? user) { - if (component.Whitelist?.IsValid(uid, EntityManager) == false) + if (_whitelistSystem.IsWhitelistFail(component.Whitelist, uid)) return false; // If it's a speedloader try to get ammo from it. diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 4debc289be..47648a07ad 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -21,6 +21,7 @@ using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -63,6 +64,7 @@ public abstract partial class SharedGunSystem : EntitySystem [Dependency] protected readonly TagSystem TagSystem = default!; [Dependency] protected readonly ThrowingSystem ThrowingSystem = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; private const float InteractNextFire = 0.3f; private const double SafetyNextFire = 0.5; diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs index d73646b7e9..f311946cf9 100644 --- a/Content.Shared/Whitelist/EntityWhitelistSystem.cs +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -60,6 +60,90 @@ public sealed class EntityWhitelistSystem : EntitySystem return list.RequireAll; } + /// The following are a list of "helper functions" that are basically the same as each other + /// to help make code that uses EntityWhitelist a bit more readable because at the moment + /// it is quite clunky having to write out component.Whitelist == null ? true : _whitelist.IsValid(component.Whitelist, uid) + /// several times in a row and makes comparisons easier to read + + /// + /// Helper function to determine if Whitelist is not null and entity is on list + /// + public bool IsWhitelistPass(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return false; + + return IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is not null and entity is not on the list + /// + public bool IsWhitelistFail(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return false; + + return !IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is either null or the entity is on the list + /// + public bool IsWhitelistPassOrNull(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return true; + + return IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Whitelist is either null or the entity is not on the list + /// + public bool IsWhitelistFailOrNull(EntityWhitelist? whitelist, EntityUid uid) + { + if (whitelist == null) + return true; + + return !IsValid(whitelist, uid); + } + + /// + /// Helper function to determine if Blacklist is not null and entity is on list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistPass(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistPass(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is not null and entity is not on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistFail(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistFail(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is either null or the entity is on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistPassOrNull(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistPassOrNull(blacklist, uid); + } + + /// + /// Helper function to determine if Blacklist is either null or the entity is not on the list + /// Duplicate of equivalent Whitelist function + /// + public bool IsBlacklistFailOrNull(EntityWhitelist? blacklist, EntityUid uid) + { + return IsWhitelistFailOrNull(blacklist, uid); + } private void EnsureRegistrations(EntityWhitelist list) { From 21d0b1fd558f417c25e0587cf8d0186ec83a294e Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:58:33 -0400 Subject: [PATCH 75/84] Guidebook Tables (#28484) * PJB's cool table control (it probably doesn't work) * ok wait wrong file * Guidebook Tables --- Content.Client/Guidebook/Richtext/Box.cs | 3 + Content.Client/Guidebook/Richtext/ColorBox.cs | 49 +++ Content.Client/Guidebook/Richtext/Table.cs | 27 ++ .../UserInterface/Controls/TableContainer.cs | 285 ++++++++++++++++++ 4 files changed, 364 insertions(+) create mode 100644 Content.Client/Guidebook/Richtext/ColorBox.cs create mode 100644 Content.Client/Guidebook/Richtext/Table.cs create mode 100644 Content.Client/UserInterface/Controls/TableContainer.cs diff --git a/Content.Client/Guidebook/Richtext/Box.cs b/Content.Client/Guidebook/Richtext/Box.cs index ecf6cb21f7..6e18ad9c57 100644 --- a/Content.Client/Guidebook/Richtext/Box.cs +++ b/Content.Client/Guidebook/Richtext/Box.cs @@ -11,6 +11,9 @@ public sealed class Box : BoxContainer, IDocumentTag HorizontalExpand = true; control = this; + if (args.TryGetValue("Margin", out var margin)) + Margin = new Thickness(float.Parse(margin)); + if (args.TryGetValue("Orientation", out var orientation)) Orientation = Enum.Parse(orientation); else diff --git a/Content.Client/Guidebook/Richtext/ColorBox.cs b/Content.Client/Guidebook/Richtext/ColorBox.cs new file mode 100644 index 0000000000..84de300d6e --- /dev/null +++ b/Content.Client/Guidebook/Richtext/ColorBox.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.Guidebook.Richtext; + +[UsedImplicitly] +public sealed class ColorBox : PanelContainer, IDocumentTag +{ + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) + { + HorizontalExpand = true; + VerticalExpand = true; + control = this; + + if (args.TryGetValue("Margin", out var margin)) + Margin = new Thickness(float.Parse(margin)); + + if (args.TryGetValue("HorizontalAlignment", out var halign)) + HorizontalAlignment = Enum.Parse(halign); + else + HorizontalAlignment = HAlignment.Stretch; + + if (args.TryGetValue("VerticalAlignment", out var valign)) + VerticalAlignment = Enum.Parse(valign); + else + VerticalAlignment = VAlignment.Stretch; + + var styleBox = new StyleBoxFlat(); + if (args.TryGetValue("Color", out var color)) + styleBox.BackgroundColor = Color.FromHex(color); + + if (args.TryGetValue("OutlineThickness", out var outlineThickness)) + styleBox.BorderThickness = new Thickness(float.Parse(outlineThickness)); + else + styleBox.BorderThickness = new Thickness(1); + + if (args.TryGetValue("OutlineColor", out var outlineColor)) + styleBox.BorderColor = Color.FromHex(outlineColor); + else + styleBox.BorderColor = Color.White; + + PanelOverride = styleBox; + + return true; + } +} diff --git a/Content.Client/Guidebook/Richtext/Table.cs b/Content.Client/Guidebook/Richtext/Table.cs new file mode 100644 index 0000000000..b6923c3698 --- /dev/null +++ b/Content.Client/Guidebook/Richtext/Table.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Client.UserInterface.Controls; +using JetBrains.Annotations; +using Robust.Client.UserInterface; + +namespace Content.Client.Guidebook.Richtext; + +[UsedImplicitly] +public sealed class Table : TableContainer, IDocumentTag +{ + public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control) + { + HorizontalExpand = true; + control = this; + + if (!args.TryGetValue("Columns", out var columns) || !int.TryParse(columns, out var columnsCount)) + { + Logger.Error("Guidebook tag \"Table\" does not specify required property \"Columns.\""); + control = null; + return false; + } + + Columns = columnsCount; + + return true; + } +} diff --git a/Content.Client/UserInterface/Controls/TableContainer.cs b/Content.Client/UserInterface/Controls/TableContainer.cs new file mode 100644 index 0000000000..3e8d476001 --- /dev/null +++ b/Content.Client/UserInterface/Controls/TableContainer.cs @@ -0,0 +1,285 @@ +using System.Numerics; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.Controls; + +// This control is not part of engine because I quickly wrote it in 2 hours at 2 AM and don't want to deal with +// API stabilization and/or figuring out relation to GridContainer. +// Grid layout is a complicated problem and I don't want to commit another half-baked thing into the engine. +// It's probably sufficient for its use case (RichTextLabel tables for rules/guidebook). +// Despite that, it's still better comment the shit half of you write on a regular basis. +// +// EMO: thank you PJB i was going to kill myself. + +/// +/// Displays children in a tabular grid. Unlike , +/// properly handles layout constraints so putting word-wrapping in it should work. +/// +/// +/// All children are automatically laid out in columns. +/// The first control is in the top left, laid out per row from there. +/// +[Virtual] +public class TableContainer : Container +{ + private int _columns = 1; + + /// + /// The absolute minimum width a column can be forced to. + /// + /// + /// + /// If a column *asks* for less width than this (small contents), it can still be smaller. + /// But if it asks for more it cannot go below this width. + /// + /// + public float MinForcedColumnWidth { get; set; } = 50; + + // Scratch space used while calculating layout, cached to avoid regular allocations during layout pass. + private ColumnData[] _columnDataCache = []; + private RowData[] _rowDataCache = []; + + /// + /// How many columns should be displayed. + /// + public int Columns + { + get => _columns; + set + { + ArgumentOutOfRangeException.ThrowIfLessThan(value, 1, nameof(value)); + + _columns = value; + } + } + + protected override Vector2 MeasureOverride(Vector2 availableSize) + { + ResetCachedArrays(); + + // Do a first pass measuring all child controls as if they're given infinite space. + // This gives us a maximum width the columns want, which we use to proportion them later. + var columnIdx = 0; + foreach (var child in Children) + { + ref var column = ref _columnDataCache[columnIdx]; + + child.Measure(new Vector2(float.PositiveInfinity, float.PositiveInfinity)); + column.MaxWidth = Math.Max(column.MaxWidth, child.DesiredSize.X); + + columnIdx += 1; + if (columnIdx == _columns) + columnIdx = 0; + } + + // Calculate Slack and MinWidth for all columns. Also calculate sums for all columns. + var totalMinWidth = 0f; + var totalMaxWidth = 0f; + var totalSlack = 0f; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + column.MinWidth = Math.Min(column.MaxWidth, MinForcedColumnWidth); + column.Slack = column.MaxWidth - column.MinWidth; + + totalMinWidth += column.MinWidth; + totalMaxWidth += column.MaxWidth; + totalSlack += column.Slack; + } + + if (totalMaxWidth <= availableSize.X) + { + // We want less horizontal space than we're given. Huh, that's convenient. + // Just set assigned width to be however much they asked for. + // We could probably skip the second measure pass in this scenario, + // but that's just an optimization, so I don't care right now. + // + // There's probably a very clever way to make this behavior work with the else block of logic, + // just by fiddling with the math. + // I'm dumb, it's 4:30 AM. Yeah, I *started* at 2 AM. + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + column.AssignedWidth = column.MaxWidth; + } + } + else + { + // We don't have enough horizontal space, + // at least without causing *some* sort of word wrapping (assuming text contents). + // + // Assign horizontal space proportional to the wanted maximum size of the columns. + var assignableWidth = Math.Max(0, availableSize.X - totalMinWidth); + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + var slackRatio = column.Slack / totalSlack; + column.AssignedWidth = column.MinWidth + slackRatio * assignableWidth; + } + } + + // Go over controls for a second measuring pass, this time giving them their assigned measure width. + // This will give us a height to slot into per-row data. + // We still measure assuming infinite vertical space. + // This control can't properly handle being constrained on the Y axis. + columnIdx = 0; + var rowIdx = 0; + foreach (var child in Children) + { + ref var column = ref _columnDataCache[columnIdx]; + ref var row = ref _rowDataCache[rowIdx]; + + child.Measure(new Vector2(column.AssignedWidth, float.PositiveInfinity)); + row.MeasuredHeight = Math.Max(row.MeasuredHeight, child.DesiredSize.Y); + + columnIdx += 1; + if (columnIdx == _columns) + { + columnIdx = 0; + rowIdx += 1; + } + } + + // Sum up height of all rows to get final measured table height. + var totalHeight = 0f; + for (var r = 0; r < _rowDataCache.Length; r++) + { + ref var row = ref _rowDataCache[r]; + totalHeight += row.MeasuredHeight; + } + + return new Vector2(Math.Min(availableSize.X, totalMaxWidth), totalHeight); + } + + protected override Vector2 ArrangeOverride(Vector2 finalSize) + { + // TODO: Expand to fit given vertical space. + + // Calculate MinWidth and Slack sums again from column data. + // We could've cached these from measure but whatever. + var totalMinWidth = 0f; + var totalSlack = 0f; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + totalMinWidth += column.MinWidth; + totalSlack += column.Slack; + } + + // Calculate new width based on final given size, also assign horizontal positions of all columns. + var assignableWidth = Math.Max(0, finalSize.X - totalMinWidth); + var xPos = 0f; + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + + var slackRatio = column.Slack / totalSlack; + column.ArrangedWidth = column.MinWidth + slackRatio * assignableWidth; + column.ArrangedX = xPos; + + xPos += column.ArrangedWidth; + } + + // Do actual arrangement row-by-row. + var arrangeY = 0f; + for (var r = 0; r < _rowDataCache.Length; r++) + { + ref var row = ref _rowDataCache[r]; + + for (var c = 0; c < _columns; c++) + { + ref var column = ref _columnDataCache[c]; + var index = c + r * _columns; + + if (index >= ChildCount) // Quit early if we don't actually fill out the row. + break; + var child = GetChild(c + r * _columns); + + child.Arrange(UIBox2.FromDimensions(column.ArrangedX, arrangeY, column.ArrangedWidth, row.MeasuredHeight)); + } + + arrangeY += row.MeasuredHeight; + } + + return finalSize with { Y = arrangeY }; + } + + /// + /// Ensure cached array space is allocated to correct size and is reset to a clean slate. + /// + private void ResetCachedArrays() + { + // 1-argument Array.Clear() is not currently available in sandbox (added in .NET 6). + + if (_columnDataCache.Length != _columns) + _columnDataCache = new ColumnData[_columns]; + + Array.Clear(_columnDataCache, 0, _columnDataCache.Length); + + var rowCount = ChildCount / _columns; + if (ChildCount % _columns != 0) + rowCount += 1; + + if (rowCount != _rowDataCache.Length) + _rowDataCache = new RowData[rowCount]; + + Array.Clear(_rowDataCache, 0, _rowDataCache.Length); + } + + /// + /// Per-column data used during layout. + /// + private struct ColumnData + { + // Measure data. + + /// + /// The maximum width any control in this column wants, if given infinite space. + /// Maximum of all controls on the column. + /// + public float MaxWidth; + + /// + /// The minimum width this column may be given. + /// This is either or . + /// + public float MinWidth; + + /// + /// Difference between max and min width; how much this column can expand from its minimum. + /// + public float Slack; + + /// + /// How much horizontal space this column was assigned at measure time. + /// + public float AssignedWidth; + + // Arrange data. + + /// + /// How much horizontal space this column was assigned at arrange time. + /// + public float ArrangedWidth; + + /// + /// The horizontal position this column was assigned at arrange time. + /// + public float ArrangedX; + } + + private struct RowData + { + // Measure data. + + /// + /// How much height the tallest control on this row was measured at, + /// measuring for infinite vertical space but assigned column width. + /// + public float MeasuredHeight; + } +} From b44b159431630a918356e9625420e8d7fd8d468e Mon Sep 17 00:00:00 2001 From: eoineoineoin Date: Sun, 2 Jun 2024 05:07:41 +0100 Subject: [PATCH 76/84] Replace Matrix3 with System.Numerics.Matrix3x2 (#27443) Replace Matrix3 with Matrix3x2 --- .../SpawnExplosion/ExplosionDebugOverlay.cs | 16 ++++--- .../Atmos/Overlays/AtmosDebugOverlay.cs | 2 +- .../Atmos/Overlays/GasTileOverlay.cs | 4 +- .../Clickable/ClickableComponent.cs | 12 ++--- .../Decals/Overlays/DecalOverlay.cs | 3 +- .../Decals/Overlays/DecalPlacementOverlay.cs | 4 +- Content.Client/DoAfter/DoAfterOverlay.cs | 12 ++--- Content.Client/Explosion/ExplosionOverlay.cs | 5 +- Content.Client/Fluids/PuddleOverlay.cs | 7 +-- Content.Client/Maps/GridDraggingSystem.cs | 4 +- Content.Client/NPC/PathfindingSystem.cs | 8 ++-- .../NodeContainer/NodeVisualizationOverlay.cs | 2 +- .../Overlays/EntityHealthBarOverlay.cs | 12 ++--- .../StencilOverlay.RestrictedRange.cs | 6 +-- .../Overlays/StencilOverlay.Weather.cs | 8 ++-- Content.Client/Overlays/StencilOverlay.cs | 3 +- Content.Client/Paper/UI/StampLabel.xaml.cs | 2 +- Content.Client/Paper/UI/StampWidget.xaml.cs | 2 +- Content.Client/Pinpointer/UI/NavMapControl.cs | 6 +-- Content.Client/Popups/PopupOverlay.cs | 5 +- .../Systems/ShuttleSystem.EmergencyConsole.cs | 3 +- .../Shuttles/UI/BaseShuttleControl.xaml.cs | 7 +-- Content.Client/Shuttles/UI/NavScreen.xaml.cs | 3 +- .../Shuttles/UI/ShuttleDockControl.xaml.cs | 46 +++++++++---------- .../Shuttles/UI/ShuttleMapControl.xaml.cs | 26 +++++------ .../Shuttles/UI/ShuttleNavControl.xaml.cs | 24 +++++----- .../StatusIcon/StatusIconOverlay.cs | 10 ++-- .../Storage/Systems/StorageSystem.cs | 5 +- .../UserInterface/Controls/DirectionIcon.cs | 2 +- .../Controls/MapGridControl.xaml.cs | 2 +- Content.Client/Viewport/ScalingViewport.cs | 18 ++++---- .../Melee/MeleeWeaponSystem.Effects.cs | 2 +- .../Weapons/Ranged/ItemStatus/BulletRender.cs | 4 +- .../EntitySystems/ExplosionGridTileFlood.cs | 13 +++--- .../EntitySystems/ExplosionSystem.GridMap.cs | 12 ++--- .../ExplosionSystem.Processing.cs | 20 ++++---- .../EntitySystems/ExplosionSystem.TileFill.cs | 4 +- .../EntitySystems/ExplosionSystem.Visuals.cs | 3 +- .../Fluids/EntitySystems/AbsorbentSystem.cs | 3 +- .../GameTicking/GameTicker.Spawning.cs | 2 +- .../Medical/SuitSensors/SuitSensorSystem.cs | 5 +- .../Pathfinding/PathfindingSystem.Distance.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.Grid.cs | 2 +- .../NPC/Pathfinding/PathfindingSystem.cs | 2 +- .../Procedural/DungeonJob.PrefabDunGen.cs | 24 +++++----- .../Procedural/DungeonSystem.Rooms.cs | 16 +++---- .../Systems/RadiationSystem.GridCast.cs | 4 +- Content.Server/Salvage/FultonSystem.cs | 6 ++- .../Shuttles/Events/FTLStartedEvent.cs | 3 +- .../Shuttles/Systems/ArrivalsSystem.cs | 3 +- .../Shuttles/Systems/DockingSystem.Shuttle.cs | 16 +++---- .../Shuttles/Systems/ShuttleSystem.Impact.cs | 5 +- .../EntitySystems/GravityWellSystem.cs | 16 +++---- .../Weapons/Ranged/Systems/GunSystem.cs | 2 +- .../Components/ExplosionVisualsComponent.cs | 7 +-- Content.Shared/Item/SharedItemSystem.cs | 2 +- Content.Shared/Maps/TurfSystem.cs | 2 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 2 +- 58 files changed, 236 insertions(+), 215 deletions(-) diff --git a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs index eede3a6217..d60094ad89 100644 --- a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs +++ b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs @@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public MapId Map; private readonly Font _font; @@ -78,7 +78,8 @@ public sealed class ExplosionDebugOverlay : Overlay if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds); DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize); } @@ -86,7 +87,7 @@ public sealed class ExplosionDebugOverlay : Overlay private void DrawText( DrawingHandleScreen handle, Box2 gridBounds, - Matrix3 transform, + Matrix3x2 transform, Dictionary> tileSets, ushort tileSize) { @@ -103,7 +104,7 @@ public sealed class ExplosionDebugOverlay : Overlay if (!gridBounds.Contains(centre)) continue; - var worldCenter = transform.Transform(centre); + var worldCenter = Vector2.Transform(centre, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter); @@ -119,7 +120,7 @@ public sealed class ExplosionDebugOverlay : Overlay if (tileSets.TryGetValue(0, out var set)) { var epicenter = set.First(); - var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize); + var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24); var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}"; handle.DrawString(_font, screenCenter, text); @@ -148,11 +149,12 @@ public sealed class ExplosionDebugOverlay : Overlay if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2); handle.SetTransform(SpaceMatrix); DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawTiles( diff --git a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs index 6dfbc326ec..c85dbd2051 100644 --- a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs +++ b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs @@ -66,7 +66,7 @@ public sealed class AtmosDebugOverlay : Overlay DrawData(msg, handle); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawData(DebugMessage msg, diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index f4dc274a4e..17027525e5 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -190,7 +190,7 @@ namespace Content.Client.Atmos.Overlays var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(); state.drawHandle.SetTransform(worldMatrix); - var floatBounds = invMatrix.TransformBox(in state.WorldBounds).Enlarged(grid.TileSize); + var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); var localBounds = new Box2i( (int) MathF.Floor(floatBounds.Left), (int) MathF.Floor(floatBounds.Bottom), @@ -249,7 +249,7 @@ namespace Content.Client.Atmos.Overlays }); drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawMapOverlay( diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index cfbd1a99d6..6f75df4683 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -38,9 +38,9 @@ namespace Content.Client.Clickable renderOrder = sprite.RenderOrder; var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery); var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation); - bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; + bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; - var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix()); + Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix); // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites. var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive(); @@ -48,8 +48,8 @@ namespace Content.Client.Clickable Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); - var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos)); + var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); + var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix); // Check explicitly defined click-able bounds if (CheckDirBound(sprite, relativeRotation, localPos)) @@ -79,8 +79,8 @@ namespace Content.Client.Clickable // convert to layer-local coordinates layer.GetLayerDrawMatrix(dir, out var matrix); - var inverseMatrix = Matrix3.Invert(matrix); - var layerLocal = inverseMatrix.Transform(localPos); + Matrix3x2.Invert(matrix, out var inverseMatrix); + var layerLocal = Vector2.Transform(localPos, inverseMatrix); // Convert to image coordinates var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f); diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs index d9904ae80b..0de3301e58 100644 --- a/Content.Client/Decals/Overlays/DecalOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -113,7 +114,7 @@ namespace Content.Client.Decals.Overlays handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } } diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs index be277448ed..845bd7c03d 100644 --- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs @@ -51,7 +51,7 @@ public sealed class DecalPlacementOverlay : Overlay var handle = args.WorldHandle; handle.SetTransform(worldMatrix); - var localPos = invMatrix.Transform(mousePos.Position); + var localPos = Vector2.Transform(mousePos.Position, invMatrix); if (snap) { @@ -63,6 +63,6 @@ public sealed class DecalPlacementOverlay : Overlay var box = new Box2Rotated(aabb, rotation, localPos); handle.DrawTextureRect(_sprite.Frame0(decal.Sprite), box, color); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/DoAfter/DoAfterOverlay.cs b/Content.Client/DoAfter/DoAfterOverlay.cs index 45981159f0..dfbbf10891 100644 --- a/Content.Client/DoAfter/DoAfterOverlay.cs +++ b/Content.Client/DoAfter/DoAfterOverlay.cs @@ -56,8 +56,8 @@ public sealed class DoAfterOverlay : Overlay // If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid. const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var curTime = _timing.CurTime; @@ -91,9 +91,9 @@ public sealed class DoAfterOverlay : Overlay ? curTime - _meta.GetPauseTime(uid, meta) : curTime; - var worldMatrix = Matrix3.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); var offset = 0f; @@ -151,7 +151,7 @@ public sealed class DoAfterOverlay : Overlay } handle.UseShader(null); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } public Color GetProgressColor(float progress, float alpha = 1f) diff --git a/Content.Client/Explosion/ExplosionOverlay.cs b/Content.Client/Explosion/ExplosionOverlay.cs index 2d8c15f1b9..8cf7447a5d 100644 --- a/Content.Client/Explosion/ExplosionOverlay.cs +++ b/Content.Client/Explosion/ExplosionOverlay.cs @@ -48,7 +48,7 @@ public sealed class ExplosionOverlay : Overlay DrawExplosion(drawHandle, args.WorldBounds, visuals, index, xforms, textures); } - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); drawHandle.UseShader(null); } @@ -78,7 +78,8 @@ public sealed class ExplosionOverlay : Overlay if (visuals.SpaceTiles == null) return; - gridBounds = Matrix3.Invert(visuals.SpaceMatrix).TransformBox(worldBounds).Enlarged(2); + Matrix3x2.Invert(visuals.SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(worldBounds).Enlarged(2); drawHandle.SetTransform(visuals.SpaceMatrix); DrawTiles(drawHandle, gridBounds, index, visuals.SpaceTiles, visuals, visuals.SpaceTileSize, textures); diff --git a/Content.Client/Fluids/PuddleOverlay.cs b/Content.Client/Fluids/PuddleOverlay.cs index ac6661cfdd..a8c1d35510 100644 --- a/Content.Client/Fluids/PuddleOverlay.cs +++ b/Content.Client/Fluids/PuddleOverlay.cs @@ -1,4 +1,5 @@ -using Content.Shared.FixedPoint; +using System.Numerics; +using Content.Shared.FixedPoint; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Enums; @@ -73,7 +74,7 @@ public sealed class PuddleOverlay : Overlay } } - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawScreen(in OverlayDrawArgs args) @@ -99,7 +100,7 @@ public sealed class PuddleOverlay : Overlay if (!gridBounds.Contains(centre)) continue; - var screenCenter = _eyeManager.WorldToScreen(matrix.Transform(centre)); + var screenCenter = _eyeManager.WorldToScreen(Vector2.Transform(centre, matrix)); drawHandle.DrawString(_font, screenCenter, debugOverlayData.CurrentVolume.ToString(), Color.White); } diff --git a/Content.Client/Maps/GridDraggingSystem.cs b/Content.Client/Maps/GridDraggingSystem.cs index e82786847e..5e9250f0ce 100644 --- a/Content.Client/Maps/GridDraggingSystem.cs +++ b/Content.Client/Maps/GridDraggingSystem.cs @@ -101,7 +101,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid)) return; - StartDragging(gridUid, Transform(gridUid).InvWorldMatrix.Transform(mousePos.Position)); + StartDragging(gridUid, Vector2.Transform(mousePos.Position, Transform(gridUid).InvWorldMatrix)); } if (!TryComp(_dragging, out TransformComponent? xform)) @@ -116,7 +116,7 @@ public sealed class GridDraggingSystem : SharedGridDraggingSystem return; } - var localToWorld = xform.WorldMatrix.Transform(_localPosition); + var localToWorld = Vector2.Transform(_localPosition, xform.WorldMatrix); if (localToWorld.EqualsApprox(mousePos.Position, 0.01f)) return; diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs index 709601a57b..d3ae509152 100644 --- a/Content.Client/NPC/PathfindingSystem.cs +++ b/Content.Client/NPC/PathfindingSystem.cs @@ -223,7 +223,7 @@ namespace Content.Client.NPC foreach (var crumb in chunk.Value) { - var crumbMapPos = worldMatrix.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates)); + var crumbMapPos = Vector2.Transform(_system.GetCoordinate(chunk.Key, crumb.Coordinates), worldMatrix); var distance = (crumbMapPos - mouseWorldPos.Position).Length(); if (distance < nearestDistance) @@ -292,7 +292,7 @@ namespace Content.Client.NPC foreach (var poly in tile) { - if (poly.Box.Contains(invGridMatrix.Transform(mouseWorldPos.Position))) + if (poly.Box.Contains(Vector2.Transform(mouseWorldPos.Position, invGridMatrix))) { nearest = poly; break; @@ -488,7 +488,7 @@ namespace Content.Client.NPC if (neighborMap.MapId != args.MapId) continue; - neighborPos = invMatrix.Transform(neighborMap.Position); + neighborPos = Vector2.Transform(neighborMap.Position, invMatrix); } else { @@ -576,7 +576,7 @@ namespace Content.Client.NPC } } - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); } } } diff --git a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs index 691bcb41db..4e8a4a10ca 100644 --- a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs +++ b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs @@ -199,7 +199,7 @@ namespace Content.Client.NodeContainer } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); _gridIndex.Clear(); } diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 2b2ff14a22..758bb562f9 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -42,8 +42,8 @@ public sealed class EntityHealthBarOverlay : Overlay var xformQuery = _entManager.GetEntityQuery(); const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var query = _entManager.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, @@ -83,10 +83,10 @@ public sealed class EntityHealthBarOverlay : Overlay continue; var worldPosition = _transform.GetWorldPosition(xform); - var worldMatrix = Matrix3.CreateTranslation(worldPosition); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); @@ -115,7 +115,7 @@ public sealed class EntityHealthBarOverlay : Overlay handle.DrawRect(pixelDarken, Black.WithAlpha(128)); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } /// diff --git a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs index 9581fec37b..d29564caa9 100644 --- a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs +++ b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs @@ -7,7 +7,7 @@ namespace Content.Client.Overlays; public sealed partial class StencilOverlay { - private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix) + private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var renderScale = args.Viewport.RenderScale.X; @@ -16,7 +16,7 @@ public sealed partial class StencilOverlay var length = zoom.X; var bufferRange = MathF.Min(10f, rangeComp.Range); - var pixelCenter = invMatrix.Transform(rangeComp.Origin); + var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix); // Something something offset? var vertical = args.Viewport.Size.Y; @@ -44,7 +44,7 @@ public sealed partial class StencilOverlay worldHandle.DrawRect(localAABB, Color.White); }, Color.Transparent); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs index 31bc88af45..29ed157a79 100644 --- a/Content.Client/Overlays/StencilOverlay.Weather.cs +++ b/Content.Client/Overlays/StencilOverlay.Weather.cs @@ -10,7 +10,7 @@ public sealed partial class StencilOverlay { private List> _grids = new(); - private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix) + private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var mapId = args.MapId; @@ -32,7 +32,7 @@ public sealed partial class StencilOverlay foreach (var grid in _grids) { var matrix = _transform.GetWorldMatrix(grid, xformQuery); - Matrix3.Multiply(in matrix, in invMatrix, out var matty); + var matty = Matrix3x2.Multiply(matrix, invMatrix); worldHandle.SetTransform(matty); foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB)) @@ -52,7 +52,7 @@ public sealed partial class StencilOverlay }, Color.Transparent); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; @@ -62,7 +62,7 @@ public sealed partial class StencilOverlay worldHandle.UseShader(_protoManager.Index("StencilDraw").Instance()); _parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha)); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(null); } } diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs index e475dca759..78b1c4d2b1 100644 --- a/Content.Client/Overlays/StencilOverlay.cs +++ b/Content.Client/Overlays/StencilOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.Parallax; using Content.Client.Weather; using Content.Shared.Salvage; @@ -72,6 +73,6 @@ public sealed partial class StencilOverlay : Overlay } args.WorldHandle.UseShader(null); - args.WorldHandle.SetTransform(Matrix3.Identity); + args.WorldHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Paper/UI/StampLabel.xaml.cs b/Content.Client/Paper/UI/StampLabel.xaml.cs index 6a8eb5f98f..be6d52baea 100644 --- a/Content.Client/Paper/UI/StampLabel.xaml.cs +++ b/Content.Client/Paper/UI/StampLabel.xaml.cs @@ -50,7 +50,7 @@ public sealed partial class StampLabel : Label base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Paper/UI/StampWidget.xaml.cs b/Content.Client/Paper/UI/StampWidget.xaml.cs index a04508aeba..487e0732b4 100644 --- a/Content.Client/Paper/UI/StampWidget.xaml.cs +++ b/Content.Client/Paper/UI/StampWidget.xaml.cs @@ -53,7 +53,7 @@ public sealed partial class StampWidget : PanelContainer base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index f4d2f8e2fb..3c99a18818 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -218,7 +218,7 @@ public partial class NavMapControl : MapGridControl // Convert to a world position var unscaledPosition = (localPosition - MidPointVector) / MinimapScale; - var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset); + var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform)); // Find closest tracked entity in range var closestEntity = NetEntity.Invalid; @@ -401,7 +401,7 @@ public partial class NavMapControl : MapGridControl if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color); @@ -422,7 +422,7 @@ public partial class NavMapControl : MapGridControl if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale); diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs index fb6bb3bf56..77eeb611f5 100644 --- a/Content.Client/Popups/PopupOverlay.cs +++ b/Content.Client/Popups/PopupOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Examine; using Robust.Client.Graphics; using Robust.Client.Player; @@ -55,7 +56,7 @@ public sealed class PopupOverlay : Overlay if (args.ViewportControl == null) return; - args.DrawingHandle.SetTransform(Matrix3.Identity); + args.DrawingHandle.SetTransform(Matrix3x2.Identity); args.DrawingHandle.UseShader(_shader); var scale = _configManager.GetCVar(CVars.DisplayUIScale); @@ -90,7 +91,7 @@ public sealed class PopupOverlay : Overlay e => e == popup.InitialPos.EntityId || e == ourEntity, entMan: _entManager)) continue; - var pos = matrix.Transform(mapPos.Position); + var pos = Vector2.Transform(mapPos.Position, matrix); _controller.DrawPopup(popup, worldHandle, pos, scale); } } diff --git a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs index 7086fd0541..d5154a87be 100644 --- a/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs +++ b/Content.Client/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Shuttles.Events; using Content.Shared.Shuttles.Systems; using Robust.Client.Graphics; @@ -75,6 +76,6 @@ public sealed class EmergencyShuttleOverlay : Overlay args.WorldHandle.SetTransform(xform.WorldMatrix); args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100)); - args.WorldHandle.SetTransform(Matrix3.Identity); + args.WorldHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs index 8774c1dfb5..b50d8fa6b2 100644 --- a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs +++ b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Shuttles.Components; using Robust.Client.AutoGenerated; @@ -115,7 +116,7 @@ public partial class BaseShuttleControl : MapGridControl } } - protected void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, Entity grid, Color color, float alpha = 0.01f) + protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity grid, Color color, float alpha = 0.01f) { var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp); var minimapScale = MinimapScale; @@ -289,7 +290,7 @@ public partial class BaseShuttleControl : MapGridControl public float MinimapScale; public Vector2 MidPoint; - public Matrix3 Matrix; + public Matrix3x2 Matrix; public List Vertices; public Vector2[] ScaledVertices; @@ -297,7 +298,7 @@ public partial class BaseShuttleControl : MapGridControl public void Execute(int index) { var vert = Vertices[index]; - var adjustedVert = Matrix.Transform(vert); + var adjustedVert = Vector2.Transform(vert, Matrix); adjustedVert = adjustedVert with { Y = -adjustedVert.Y }; var scaledVert = ScalePosition(adjustedVert, MinimapScale, MidPoint); diff --git a/Content.Client/Shuttles/UI/NavScreen.xaml.cs b/Content.Client/Shuttles/UI/NavScreen.xaml.cs index b7b757ea48..91d95aaa04 100644 --- a/Content.Client/Shuttles/UI/NavScreen.xaml.cs +++ b/Content.Client/Shuttles/UI/NavScreen.xaml.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Shuttles.BUIStates; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; @@ -68,7 +69,7 @@ public sealed partial class NavScreen : BoxContainer } var (_, worldRot, worldMatrix) = _xformSystem.GetWorldPositionRotationMatrix(gridXform); - var worldPos = worldMatrix.Transform(gridBody.LocalCenter); + var worldPos = Vector2.Transform(gridBody.LocalCenter, worldMatrix); // Get the positive reduced angle. var displayRot = -worldRot.Reduced(); diff --git a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs index f03c440295..31f0eecad7 100644 --- a/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleDockControl.xaml.cs @@ -108,10 +108,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl var gridNent = EntManager.GetNetEntity(GridEntity); var mapPos = _xformSystem.ToMapCoordinates(_coordinates.Value); var ourGridMatrix = _xformSystem.GetWorldMatrix(gridXform.Owner); - var dockMatrix = Matrix3.CreateTransform(_coordinates.Value.Position, Angle.Zero); - Matrix3.Multiply(dockMatrix, ourGridMatrix, out var offsetMatrix); + var dockMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, Angle.Zero); + var worldFromDock = Matrix3x2.Multiply(dockMatrix, ourGridMatrix); - offsetMatrix = offsetMatrix.Invert(); + Matrix3x2.Invert(worldFromDock, out var offsetMatrix); // Draw nearby grids var controlBounds = PixelSizeBox; @@ -137,7 +137,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl continue; var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner); - Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty); + var matty = Matrix3x2.Multiply(gridMatrix, offsetMatrix); var color = _shuttles.GetIFFColor(grid.Owner, grid.Owner == GridEntity, component: iffComp); DrawGrid(handle, matty, grid, color); @@ -151,23 +151,23 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl if (ViewedDock == dock.Entity) continue; - var position = matty.Transform(dock.Coordinates.Position); + var position = Vector2.Transform(dock.Coordinates.Position, matty); - var otherDockRotation = Matrix3.CreateRotation(dock.Angle); + var otherDockRotation = Matrix3Helpers.CreateRotation(dock.Angle); var scaledPos = ScalePosition(position with {Y = -position.Y}); if (!controlBounds.Contains(scaledPos.Floored())) continue; // Draw the dock's collision - var collisionBL = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(-0.2f, -0.7f))); - var collisionBR = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(0.2f, -0.7f))); - var collisionTR = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(0.2f, -0.5f))); - var collisionTL = matty.Transform(dock.Coordinates.Position + - otherDockRotation.Transform(new Vector2(-0.2f, -0.5f))); + var collisionBL = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(-0.2f, -0.7f), otherDockRotation), matty); + var collisionBR = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(0.2f, -0.7f), otherDockRotation), matty); + var collisionTR = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(0.2f, -0.5f), otherDockRotation), matty); + var collisionTL = Vector2.Transform(dock.Coordinates.Position + + Vector2.Transform(new Vector2(-0.2f, -0.5f), otherDockRotation), matty); var verts = new[] { @@ -195,10 +195,10 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts, otherDockConnection); // Draw the dock itself - var dockBL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f)); - var dockBR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f)); - var dockTR = matty.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f)); - var dockTL = matty.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f)); + var dockBL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, -0.5f), matty); + var dockBR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, -0.5f), matty); + var dockTR = Vector2.Transform(dock.Coordinates.Position + new Vector2(0.5f, 0.5f), matty); + var dockTL = Vector2.Transform(dock.Coordinates.Position + new Vector2(-0.5f, 0.5f), matty); verts = new[] { @@ -308,14 +308,14 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl // Draw the dock's collision var invertedPosition = Vector2.Zero; invertedPosition.Y = -invertedPosition.Y; - var rotation = Matrix3.CreateRotation(-_angle.Value + MathF.PI); + var rotation = Matrix3Helpers.CreateRotation(-_angle.Value + MathF.PI); var ourDockConnection = new UIBox2( - ScalePosition(rotation.Transform(new Vector2(-0.2f, -0.7f))), - ScalePosition(rotation.Transform(new Vector2(0.2f, -0.5f)))); + ScalePosition(Vector2.Transform(new Vector2(-0.2f, -0.7f), rotation)), + ScalePosition(Vector2.Transform(new Vector2(0.2f, -0.5f), rotation))); var ourDock = new UIBox2( - ScalePosition(rotation.Transform(new Vector2(-0.5f, 0.5f))), - ScalePosition(rotation.Transform(new Vector2(0.5f, -0.5f)))); + ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)), + ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation))); var dockColor = Color.Magenta; var connectionColor = Color.Pink; diff --git a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs index 2f35a8dffd..8bd4a338cb 100644 --- a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs @@ -114,7 +114,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var beaconsOnly = EntManager.TryGetComponent(mapUid, out FTLDestinationComponent? destComp) && destComp.BeaconsOnly; - var mapTransform = Matrix3.CreateInverseTransform(Offset, Angle.Zero); + var mapTransform = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero); if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _)) { @@ -203,7 +203,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl /// /// /// - private List GetViewportMapObjects(Matrix3 matty, List mapObjects) + private List GetViewportMapObjects(Matrix3x2 matty, List mapObjects) { var results = new List(); var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale)); @@ -217,7 +217,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var mapCoords = _shuttles.GetMapCoordinates(mapObj); - var relativePos = matty.Transform(mapCoords.Position); + var relativePos = Vector2.Transform(mapCoords.Position, matty); relativePos = relativePos with { Y = -relativePos.Y }; var uiPosition = ScalePosition(relativePos); @@ -250,7 +250,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl DrawParallax(handle); var viewedMapUid = _mapManager.GetMapEntityId(ViewingMap); - var matty = Matrix3.CreateInverseTransform(Offset, Angle.Zero); + var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero); var realTime = _timing.RealTime; var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector); var viewportObjects = GetViewportMapObjects(matty, mapObjects); @@ -267,7 +267,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform); gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -296,7 +296,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl continue; } - var adjustedPos = matty.Transform(mapCoords.Position); + var adjustedPos = Vector2.Transform(mapCoords.Position, matty); var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y}); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f)); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false); @@ -319,7 +319,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds)) { - var localPos = matty.Transform(coords.Position); + var localPos = Vector2.Transform(coords.Position, matty); localPos = localPos with { Y = -localPos.Y }; var beaconUiPos = ScalePosition(localPos); var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true); @@ -360,7 +360,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner); gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -439,7 +439,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl var color = ftlFree ? Color.LimeGreen : Color.Magenta; - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -512,7 +512,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl /// /// Returns the beacons that intersect the viewport. /// - private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3 mapTransform, UIBox2i area) + private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3x2 mapTransform, UIBox2i area) { foreach (var mapO in mapObjs) { @@ -520,7 +520,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl continue; var beaconCoords = EntManager.GetCoordinates(beacon.Coordinates).ToMap(EntManager, _xformSystem); - var position = mapTransform.Transform(beaconCoords.Position); + var position = Vector2.Transform(beaconCoords.Position, mapTransform); var localPos = ScalePosition(position with {Y = -position.Y}); // If beacon not on screen then ignore it. @@ -557,7 +557,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl return mapObj; } - private bool TryGetBeacon(IEnumerable mapObjects, Matrix3 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) + private bool TryGetBeacon(IEnumerable mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) { // In pixels const float BeaconSnapRange = 32f; @@ -579,7 +579,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates)) continue; - var position = mapTransform.Transform(beaconCoords.Position); + var position = Vector2.Transform(beaconCoords.Position, mapTransform); var localPos = ScalePosition(position with {Y = -position.Y}); // If beacon not on screen then ignore it. diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 00ee6890b2..0b8720add2 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -137,10 +137,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var mapPos = _transform.ToMapCoordinates(_coordinates.Value); var offset = _coordinates.Value.Position; - var posMatrix = Matrix3.CreateTransform(offset, _rotation.Value); + var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value); var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId); - Matrix3.Multiply(posMatrix, ourEntMatrix, out var ourWorldMatrix); - var ourWorldMatrixInvert = ourWorldMatrix.Invert(); + var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix); + Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert); // Draw our grid in detail var ourGridId = xform.GridUid; @@ -148,7 +148,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl fixturesQuery.HasComponent(ourGridId.Value)) { var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value); - Matrix3.Multiply(in ourGridMatrix, in ourWorldMatrixInvert, out var matrix); + var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(ourGridId.Value, self: true); DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color); @@ -194,7 +194,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl continue; var gridMatrix = _transform.GetWorldMatrix(gUid); - Matrix3.Multiply(in gridMatrix, in ourWorldMatrixInvert, out var matty); + var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(grid, self: false, iff); // Others default: @@ -207,7 +207,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl { var gridBounds = grid.Comp.LocalAABB; - var gridCentre = matty.Transform(gridBody.LocalCenter); + var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty); gridCentre.Y = -gridCentre.Y; var distance = gridCentre.Length(); var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName), @@ -242,7 +242,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl } } - private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix) + private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix) { if (!ShowDocks) return; @@ -255,7 +255,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl foreach (var state in docks) { var position = state.Coordinates.Position; - var uiPosition = matrix.Transform(position); + var uiPosition = Vector2.Transform(position, matrix); if (uiPosition.Length() > (WorldRange * 2f) - DockScale) continue; @@ -264,10 +264,10 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl var verts = new[] { - matrix.Transform(position + new Vector2(-DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, DockScale)), - matrix.Transform(position + new Vector2(-DockScale, DockScale)), + Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix), + Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix), }; for (var i = 0; i < verts.Length; i++) diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index 56107cbc02..f1473bda7a 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -39,8 +39,8 @@ public sealed class StatusIconOverlay : Overlay var eyeRot = args.Viewport.Eye?.Rotation ?? default; var xformQuery = _entity.GetEntityQuery(); - var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1)); - var rotationMatrix = Matrix3.CreateRotation(-eyeRot); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(1, 1)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-eyeRot); var query = _entity.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta)) @@ -59,9 +59,9 @@ public sealed class StatusIconOverlay : Overlay if (icons.Count == 0) continue; - var worldMatrix = Matrix3.CreateTranslation(worldPos); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPos); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); var countL = 0; diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs index 8bf0dcd981..b80a855f98 100644 --- a/Content.Client/Storage/Systems/StorageSystem.cs +++ b/Content.Client/Storage/Systems/StorageSystem.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Linq; +using System.Numerics; using Content.Client.Animations; using Content.Shared.Hands; using Content.Shared.Storage; @@ -149,7 +150,7 @@ public sealed class StorageSystem : SharedStorageSystem } var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem); - var finalPos = TransformSystem.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos); + var finalPos = Vector2.Transform(finalMapPos, TransformSystem.GetInvWorldMatrix(initialCoords.EntityId)); _entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle); } diff --git a/Content.Client/UserInterface/Controls/DirectionIcon.cs b/Content.Client/UserInterface/Controls/DirectionIcon.cs index a6cc428091..c8fd63b43c 100644 --- a/Content.Client/UserInterface/Controls/DirectionIcon.cs +++ b/Content.Client/UserInterface/Controls/DirectionIcon.cs @@ -65,7 +65,7 @@ public sealed class DirectionIcon : TextureRect if (_rotation != null) { var offset = (-_rotation.Value).RotateVec(Size * UIScale / 2) - Size * UIScale / 2; - handle.SetTransform(Matrix3.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); + handle.SetTransform(Matrix3Helpers.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); } base.Draw(handle); diff --git a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs index f6b0929f3b..a10155f3e8 100644 --- a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs +++ b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs @@ -169,7 +169,7 @@ public partial class MapGridControl : LayoutContainer var inversePos = (value - MidPointVector) / MinimapScale; inversePos = inversePos with { Y = -inversePos.Y }; - inversePos = Matrix3.CreateTransform(Offset, Angle.Zero).Transform(inversePos); + inversePos = Vector2.Transform(inversePos, Matrix3Helpers.CreateTransform(Offset, Angle.Zero)); return inversePos; } diff --git a/Content.Client/Viewport/ScalingViewport.cs b/Content.Client/Viewport/ScalingViewport.cs index 1fa8f17161..ccd9636d77 100644 --- a/Content.Client/Viewport/ScalingViewport.cs +++ b/Content.Client/Viewport/ScalingViewport.cs @@ -277,8 +277,8 @@ namespace Content.Client.Viewport EnsureViewportCreated(); - var matrix = Matrix3.Invert(GetLocalToScreenMatrix()); - coords = matrix.Transform(coords); + Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix); + coords = Vector2.Transform(coords, matrix); return _viewport!.LocalToWorld(coords); } @@ -291,8 +291,8 @@ namespace Content.Client.Viewport EnsureViewportCreated(); - var matrix = Matrix3.Invert(GetLocalToScreenMatrix()); - coords = matrix.Transform(coords); + Matrix3x2.Invert(GetLocalToScreenMatrix(), out var matrix); + coords = Vector2.Transform(coords, matrix); var ev = new PixelToMapEvent(coords, this, _viewport!); _entityManager.EventBus.RaiseEvent(EventSource.Local, ref ev); @@ -311,16 +311,16 @@ namespace Content.Client.Viewport var matrix = GetLocalToScreenMatrix(); - return matrix.Transform(vpLocal); + return Vector2.Transform(vpLocal, matrix); } - public Matrix3 GetWorldToScreenMatrix() + public Matrix3x2 GetWorldToScreenMatrix() { EnsureViewportCreated(); return _viewport!.GetWorldToLocalMatrix() * GetLocalToScreenMatrix(); } - public Matrix3 GetLocalToScreenMatrix() + public Matrix3x2 GetLocalToScreenMatrix() { EnsureViewportCreated(); @@ -329,9 +329,9 @@ namespace Content.Client.Viewport if (scaleFactor.X == 0 || scaleFactor.Y == 0) // Basically a nonsense scenario, at least make sure to return something that can be inverted. - return Matrix3.Identity; + return Matrix3x2.Identity; - return Matrix3.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor); + return Matrix3Helpers.CreateTransform(GlobalPixelPosition + drawBox.TopLeft, 0, scaleFactor); } private void EnsureViewportCreated() diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index baac42d193..3e1a4b1906 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -81,7 +81,7 @@ public sealed partial class MeleeWeaponSystem case WeaponArcAnimation.None: var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform); var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos); - var newLocalPos = TransformSystem.GetInvWorldMatrix(xform.ParentUid).Transform(worldPos); + var newLocalPos = Vector2.Transform(worldPos, TransformSystem.GetInvWorldMatrix(xform.ParentUid)); TransformSystem.SetLocalPositionNoLerp(animationUid, newLocalPos, xform); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); diff --git a/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs index 8aea5d7ee6..e6cb596b94 100644 --- a/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs +++ b/Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs @@ -1,4 +1,4 @@ -using System.Numerics; +using System.Numerics; using Content.Client.Resources; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; @@ -53,7 +53,7 @@ public abstract class BaseBulletRenderer : Control { // Scale rendering in this control by UIScale. var currentTransform = handle.GetTransform(); - handle.SetTransform(Matrix3.CreateScale(new Vector2(UIScale)) * currentTransform); + handle.SetTransform(Matrix3Helpers.CreateScale(new Vector2(UIScale)) * currentTransform); var countPerRow = CountPerRow(Size.X); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs index 7db1f513f7..2ddcc052d8 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs @@ -14,7 +14,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood public MapGridComponent Grid; private bool _needToTransform = false; - private Matrix3 _matrix = Matrix3.Identity; + private Matrix3x2 _matrix = Matrix3x2.Identity; private Vector2 _offset; // Tiles which neighbor an exploding tile, but have not yet had the explosion spread to them due to an @@ -44,7 +44,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood int typeIndex, Dictionary edgeTiles, EntityUid? referenceGrid, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, Angle spaceAngle) { Grid = grid; @@ -72,9 +72,10 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood var transform = IoCManager.Resolve().GetComponent(Grid.Owner); var size = (float) Grid.TileSize; - _matrix.R0C2 = size / 2; - _matrix.R1C2 = size / 2; - _matrix *= transform.WorldMatrix * Matrix3.Invert(spaceMatrix); + _matrix.M31 = size / 2; + _matrix.M32 = size / 2; + Matrix3x2.Invert(spaceMatrix, out var invSpace); + _matrix *= transform.WorldMatrix * invSpace; var relativeAngle = transform.WorldRotation - spaceAngle; _offset = relativeAngle.RotateVec(new Vector2(size / 4, size / 4)); } @@ -228,7 +229,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood return; } - var center = _matrix.Transform(tile); + var center = Vector2.Transform(tile, _matrix); SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.X), (int) MathF.Floor(center.Y + _offset.Y))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.Y), (int) MathF.Floor(center.Y + _offset.X))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.X), (int) MathF.Floor(center.Y - _offset.Y))); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs index 719a2eca79..556fe7c141 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs @@ -60,7 +60,7 @@ public sealed partial class ExplosionSystem : EntitySystem { Dictionary transformedEdges = new(); - var targetMatrix = Matrix3.Identity; + var targetMatrix = Matrix3x2.Identity; Angle targetAngle = new(); var tileSize = DefaultTileSize; var maxDistanceSq = (int) (maxDistance * maxDistance); @@ -75,9 +75,9 @@ public sealed partial class ExplosionSystem : EntitySystem tileSize = targetGrid.TileSize; } - var offsetMatrix = Matrix3.Identity; - offsetMatrix.R0C2 = tileSize / 2f; - offsetMatrix.R1C2 = tileSize / 2f; + var offsetMatrix = Matrix3x2.Identity; + offsetMatrix.M31 = tileSize / 2f; + offsetMatrix.M32 = tileSize / 2f; // Here we can end up with a triple nested for loop: // foreach other grid @@ -106,7 +106,7 @@ public sealed partial class ExplosionSystem : EntitySystem var xform = xforms.GetComponent(gridToTransform); var (_, gridWorldRotation, gridWorldMatrix, invGridWorldMatrid) = xform.GetWorldPositionRotationMatrixWithInv(xforms); - var localEpicentre = (Vector2i) invGridWorldMatrid.Transform(epicentre.Position); + var localEpicentre = (Vector2i) Vector2.Transform(epicentre.Position, invGridWorldMatrid); var matrix = offsetMatrix * gridWorldMatrix * targetMatrix; var angle = gridWorldRotation - targetAngle; @@ -119,7 +119,7 @@ public sealed partial class ExplosionSystem : EntitySystem if (delta.X * delta.X + delta.Y * delta.Y > maxDistanceSq) // no Vector2.Length??? continue; - var center = matrix.Transform(tile); + var center = Vector2.Transform(tile, matrix); if ((dir & NeighborFlag.Cardinal) == 0) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index bce3dc21c2..c30a9ce300 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -298,8 +298,8 @@ public sealed partial class ExplosionSystem /// Same as , but for SPAAAAAAACE. /// internal void ExplodeSpace(BroadphaseComponent lookup, - Matrix3 spaceMatrix, - Matrix3 invSpaceMatrix, + Matrix3x2 spaceMatrix, + Matrix3x2 invSpaceMatrix, Vector2i tile, float throwForce, DamageSpecifier damage, @@ -341,7 +341,7 @@ public sealed partial class ExplosionSystem } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in EntityUid uid) { if (state.Processed.Contains(uid)) @@ -352,7 +352,7 @@ public sealed partial class ExplosionSystem if (xform.ParentUid == state.LookupOwner) { // parented directly to the map, use local position - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(xform.LocalPosition))) + if (state.GridBox.Contains(Vector2.Transform(xform.LocalPosition, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; @@ -360,14 +360,14 @@ public sealed partial class ExplosionSystem // finally check if it intersects our tile var wpos = state.System.GetWorldPosition(xform); - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(wpos))) + if (state.GridBox.Contains(Vector2.Transform(wpos, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in FixtureProxy proxy) { var uid = proxy.Entity; @@ -585,12 +585,12 @@ sealed class Explosion /// /// The matrix that defines the reference frame for the explosion in space. /// - private readonly Matrix3 _spaceMatrix; + private readonly Matrix3x2 _spaceMatrix; /// /// Inverse of /// - private readonly Matrix3 _invSpaceMatrix; + private readonly Matrix3x2 _invSpaceMatrix; /// /// Have all the tiles on all the grids been processed? @@ -656,7 +656,7 @@ sealed class Explosion List gridData, List tileSetIntensity, MapCoordinates epicenter, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, int area, float tileBreakScale, int maxTileBreak, @@ -695,7 +695,7 @@ sealed class Explosion }); _spaceMatrix = spaceMatrix; - _invSpaceMatrix = Matrix3.Invert(spaceMatrix); + Matrix3x2.Invert(spaceMatrix, out _invSpaceMatrix); } foreach (var grid in gridData) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs index a42dd11083..8b4c0e14c5 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs @@ -26,7 +26,7 @@ public sealed partial class ExplosionSystem : EntitySystem /// The maximum intensity that the explosion can have at any given tile. This /// effectively caps the damage that this explosion can do. /// A list of tile-sets and a list of intensity values which describe the explosion. - private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3)? GetExplosionTiles( + private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3x2)? GetExplosionTiles( MapCoordinates epicenter, string typeID, float totalIntensity, @@ -84,7 +84,7 @@ public sealed partial class ExplosionSystem : EntitySystem Dictionary>? previousGridJump; // variables for transforming between grid and space-coordinates - var spaceMatrix = Matrix3.Identity; + var spaceMatrix = Matrix3x2.Identity; var spaceAngle = Angle.Zero; if (referenceGrid != null) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index d332531502..5db8b55bf9 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Explosion; using Content.Shared.Explosion.Components; using Robust.Server.GameObjects; @@ -35,7 +36,7 @@ public sealed partial class ExplosionSystem : EntitySystem /// /// Constructor for the shared using the server-exclusive explosion classes. /// - private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) + private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) { var explosionEntity = Spawn(null, MapCoordinates.Nullspace); var comp = AddComp(explosionEntity); diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index a3717fd94c..52afdcf8b4 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Popups; using Content.Shared.Chemistry.Components; @@ -317,7 +318,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem var userXform = Transform(user); var targetPos = _transform.GetWorldPosition(target); - var localPos = _transform.GetInvWorldMatrix(userXform).Transform(targetPos); + var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform)); localPos = userXform.LocalRotation.RotateVec(localPos); _melee.DoLunge(user, used, Angle.Zero, localPos, null, false); diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 22d368e57b..5e2ef0c02c 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -417,7 +417,7 @@ namespace Content.Server.GameTicking { var gridXform = Transform(gridUid); - return new EntityCoordinates(gridUid, gridXform.InvWorldMatrix.Transform(toMap.Position)); + return new EntityCoordinates(gridUid, Vector2.Transform(toMap.Position, gridXform.InvWorldMatrix)); } return spawn; diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index 38e88f6e1e..1acbf292f0 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Access.Systems; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; @@ -366,8 +367,8 @@ public sealed class SuitSensorSystem : EntitySystem if (transform.GridUid != null) { coordinates = new EntityCoordinates(transform.GridUid.Value, - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery) - .Transform(_transform.GetWorldPosition(transform, xformQuery))); + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); } else if (transform.MapUid != null) { diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs index 5daf38c420..b986ed5259 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs @@ -36,7 +36,7 @@ public sealed partial class PathfindingSystem return Vector2.Zero; } - endPos = startXform.InvWorldMatrix.Transform(endXform.WorldMatrix.Transform(endPos)); + endPos = Vector2.Transform(Vector2.Transform(endPos, endXform.WorldMatrix), startXform.InvWorldMatrix); } // TODO: Numerics when we changeover. diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index 52f7db77ed..f4af65c617 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -395,7 +395,7 @@ public sealed partial class PathfindingSystem private Vector2i GetOrigin(EntityCoordinates coordinates, EntityUid gridUid) { - var localPos = _transform.GetInvWorldMatrix(gridUid).Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), _transform.GetInvWorldMatrix(gridUid)); return new Vector2i((int) Math.Floor(localPos.X / ChunkSize), (int) Math.Floor(localPos.Y / ChunkSize)); } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs index 3672ad047b..b2958f0ccb 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -405,7 +405,7 @@ namespace Content.Server.NPC.Pathfinding return null; } - var localPos = xform.InvWorldMatrix.Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), xform.InvWorldMatrix); var origin = GetOrigin(localPos); if (!TryGetChunk(origin, comp, out var chunk)) diff --git a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs index 1783a56790..a19f7e4701 100644 --- a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs +++ b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs @@ -19,7 +19,7 @@ public sealed partial class DungeonJob var gen = _prototype.Index(preset); var dungeonRotation = _dungeon.GetDungeonRotation(seed); - var dungeonTransform = Matrix3.CreateTransform(_position, dungeonRotation); + var dungeonTransform = Matrix3Helpers.CreateTransform(_position, dungeonRotation); var roomPackProtos = new Dictionary>(); foreach (var pack in _prototype.EnumeratePrototypes()) @@ -69,7 +69,7 @@ public sealed partial class DungeonJob var dungeon = new Dungeon(); var availablePacks = new List(); var chosenPacks = new DungeonRoomPackPrototype?[gen.RoomPacks.Count]; - var packTransforms = new Matrix3[gen.RoomPacks.Count]; + var packTransforms = new Matrix3x2[gen.RoomPacks.Count]; var packRotations = new Angle[gen.RoomPacks.Count]; // Actually pick the room packs and rooms @@ -97,7 +97,7 @@ public sealed partial class DungeonJob // Iterate every pack random.Shuffle(availablePacks); - Matrix3 packTransform = default!; + Matrix3x2 packTransform = default!; var found = false; DungeonRoomPackPrototype pack = default!; @@ -128,7 +128,7 @@ public sealed partial class DungeonJob var aRotation = dir.AsDir().ToAngle(); // Use this pack - packTransform = Matrix3.CreateTransform(bounds.Center, aRotation); + packTransform = Matrix3Helpers.CreateTransform(bounds.Center, aRotation); packRotations[i] = aRotation; pack = aPack; break; @@ -168,7 +168,7 @@ public sealed partial class DungeonJob { var roomDimensions = new Vector2i(roomSize.Width, roomSize.Height); Angle roomRotation = Angle.Zero; - Matrix3 matty; + Matrix3x2 matty; if (!roomProtos.TryGetValue(roomDimensions, out var roomProto)) { @@ -176,13 +176,13 @@ public sealed partial class DungeonJob if (!roomProtos.TryGetValue(roomDimensions, out roomProto)) { - Matrix3.Multiply(packTransform, dungeonTransform, out matty); + matty = Matrix3x2.Multiply(packTransform, dungeonTransform); for (var x = roomSize.Left; x < roomSize.Right; x++) { for (var y = roomSize.Bottom; y < roomSize.Top; y++) { - var index = matty.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter).Floored(); + var index = Vector2.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter, matty).Floored(); tiles.Add((index, new Tile(_tileDefManager["FloorPlanetGrass"].TileId))); } } @@ -209,10 +209,10 @@ public sealed partial class DungeonJob roomRotation += Math.PI; } - var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation); + var roomTransform = Matrix3Helpers.CreateTransform(roomSize.Center - packCenter, roomRotation); - Matrix3.Multiply(roomTransform, packTransform, out matty); - Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty); + matty = Matrix3x2.Multiply(roomTransform, packTransform); + var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. _dungeon.SpawnRoom(gridUid, grid, dungeonMatty, room); @@ -232,7 +232,7 @@ public sealed partial class DungeonJob continue; } - var tilePos = dungeonMatty.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset); + var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty); exterior.Add(tilePos.Floored()); } } @@ -244,7 +244,7 @@ public sealed partial class DungeonJob for (var y = 0; y < room.Size.Y; y++) { var roomTile = new Vector2i(x + room.Offset.X, y + room.Offset.Y); - var tilePos = dungeonMatty.Transform(roomTile + tileOffset); + var tilePos = Vector2.Transform(roomTile + tileOffset, dungeonMatty); var tileIndex = tilePos.Floored(); roomTiles.Add(tileIndex); diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index 03bcc2b4b1..5b4de34906 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -67,7 +67,7 @@ public sealed partial class DungeonSystem bool clearExisting = false, bool rotation = false) { - var originTransform = Matrix3.CreateTranslation(origin); + var originTransform = Matrix3Helpers.CreateTranslation(origin.X, origin.Y); var roomRotation = Angle.Zero; if (rotation) @@ -75,8 +75,8 @@ public sealed partial class DungeonSystem roomRotation = GetRoomRotation(room, random); } - var roomTransform = Matrix3.CreateTransform((Vector2) room.Size / 2f, roomRotation); - Matrix3.Multiply(roomTransform, originTransform, out var finalTransform); + var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation); + var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform); SpawnRoom(gridUid, grid, finalTransform, room, clearExisting); } @@ -101,7 +101,7 @@ public sealed partial class DungeonSystem public void SpawnRoom( EntityUid gridUid, MapGridComponent grid, - Matrix3 roomTransform, + Matrix3x2 roomTransform, DungeonRoomPrototype room, bool clearExisting = false) { @@ -116,7 +116,7 @@ public sealed partial class DungeonSystem // go BRRNNTTT on existing stuff if (clearExisting) { - var gridBounds = new Box2(roomTransform.Transform(Vector2.Zero), roomTransform.Transform(room.Size)); + var gridBounds = new Box2(Vector2.Transform(Vector2.Zero, roomTransform), Vector2.Transform(room.Size, roomTransform)); _entitySet.Clear(); // Polygon skin moment gridBounds = gridBounds.Enlarged(-0.05f); @@ -148,7 +148,7 @@ public sealed partial class DungeonSystem var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y); var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices); - var tilePos = roomTransform.Transform(indices + tileOffset); + var tilePos = Vector2.Transform(indices + tileOffset, roomTransform); var rounded = tilePos.Floored(); _tiles.Add((rounded, tileRef.Tile)); } @@ -164,7 +164,7 @@ public sealed partial class DungeonSystem foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained)) { var templateXform = _xformQuery.GetComponent(templateEnt); - var childPos = roomTransform.Transform(templateXform.LocalPosition - roomCenter); + var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform); var childRot = templateXform.LocalRotation + finalRoomRotation; var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID; @@ -192,7 +192,7 @@ public sealed partial class DungeonSystem // Offset by 0.5 because decals are offset from bot-left corner // So we convert it to center of tile then convert it back again after transform. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles. - var position = roomTransform.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter); + var position = Vector2.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter, roomTransform); position -= Vector2Helpers.Half; // Umm uhh I love decals so uhhhh idk what to do about this diff --git a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs index b8193c4d2f..ccee7cf227 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs @@ -195,11 +195,11 @@ public partial class RadiationSystem Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner ? sourceTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Source); + : Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix); Vector2 dstLocal = destTrs.ParentUid == grid.Owner ? destTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Destination); + : Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix); Vector2i sourceGrid = new( (int) Math.Floor(srcLocal.X / grid.Comp.TileSize), diff --git a/Content.Server/Salvage/FultonSystem.cs b/Content.Server/Salvage/FultonSystem.cs index ad998e5359..e2c0a6b4c5 100644 --- a/Content.Server/Salvage/FultonSystem.cs +++ b/Content.Server/Salvage/FultonSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Salvage.Fulton; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -61,8 +62,9 @@ public sealed class FultonSystem : SharedFultonSystem var metadata = MetaData(uid); var oldCoords = xform.Coordinates; var offset = _random.NextVector2(1.5f); - var localPos = TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid) - .Transform(TransformSystem.GetWorldPosition(beaconXform)) + offset; + var localPos = Vector2.Transform( + TransformSystem.GetWorldPosition(beaconXform), + TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid)) + offset; TransformSystem.SetCoordinates(uid, new EntityCoordinates(beaconXform.ParentUid, localPos)); diff --git a/Content.Server/Shuttles/Events/FTLStartedEvent.cs b/Content.Server/Shuttles/Events/FTLStartedEvent.cs index 965da7f0c5..eafbeddd5c 100644 --- a/Content.Server/Shuttles/Events/FTLStartedEvent.cs +++ b/Content.Server/Shuttles/Events/FTLStartedEvent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.Map; namespace Content.Server.Shuttles.Events; @@ -6,4 +7,4 @@ namespace Content.Server.Shuttles.Events; /// Raised when a shuttle has moved to FTL space. /// [ByRefEvent] -public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3 FTLFrom, Angle FromRotation); +public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3x2 FTLFrom, Angle FromRotation); diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index e87e781e62..eb1f9796b2 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Numerics; using Content.Server.Administration; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; @@ -277,7 +278,7 @@ public sealed class ArrivalsSystem : EntitySystem foreach (var (ent, xform) in toDump) { var rotation = xform.LocalRotation; - _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, args.FTLFrom.Transform(xform.LocalPosition))); + _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, Vector2.Transform(xform.LocalPosition, args.FTLFrom))); _transform.SetWorldRotation(ent, args.FromRotation + rotation); } } diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs index e46a7c715f..1a95ef9cb2 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -46,13 +46,13 @@ public sealed partial class DockingSystem FixturesComponent shuttleFixtures, MapGridComponent grid, bool isMap, - out Matrix3 matty, + out Matrix3x2 matty, out Box2 shuttleDockedAABB, out Angle gridRotation) { shuttleDockedAABB = Box2.UnitCentered; gridRotation = Angle.Zero; - matty = Matrix3.Identity; + matty = Matrix3x2.Identity; if (shuttleDock.Docked || gridDock.Docked || @@ -71,9 +71,9 @@ public sealed partial class DockingSystem var gridDockAngle = gridDockXform.LocalRotation.Opposite(); var offsetAngle = gridDockAngle - shuttleDockAngle; - var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle); - var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); - Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty); + var stationDockMatrix = Matrix3Helpers.CreateInverseTransform(stationDockPos, shuttleDockAngle); + var gridXformMatrix = Matrix3Helpers.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); + matty = Matrix3x2.Multiply(stationDockMatrix, gridXformMatrix); if (!ValidSpawn(grid, matty, offsetAngle, shuttleFixtures, isMap)) return false; @@ -193,7 +193,7 @@ public sealed partial class DockingSystem } // Can't just use the AABB as we want to get bounds as tight as possible. - var gridPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero)); + var gridPosition = new EntityCoordinates(targetGrid, Vector2.Transform(Vector2.Zero, matty)); var spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, gridPosition.ToMapPos(EntityManager, _transform)); // TODO: use tight bounds @@ -303,9 +303,9 @@ public sealed partial class DockingSystem /// /// Checks whether the shuttle can warp to the specified position. /// - private bool ValidSpawn(MapGridComponent grid, Matrix3 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) + private bool ValidSpawn(MapGridComponent grid, Matrix3x2 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) { - var transform = new Transform(matty.Transform(Vector2.Zero), angle); + var transform = new Transform(Vector2.Transform(Vector2.Zero, matty), angle); // Because some docking bounds are tight af need to check each chunk individually foreach (var fix in shuttleFixturesComp.Fixtures.Values) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index f346398cda..8a8d2d883d 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Shuttles.Components; using Content.Shared.Audio; using Robust.Shared.Audio; @@ -38,8 +39,8 @@ public sealed partial class ShuttleSystem var otherXform = Transform(args.OtherEntity); - var ourPoint = ourXform.InvWorldMatrix.Transform(args.WorldPoint); - var otherPoint = otherXform.InvWorldMatrix.Transform(args.WorldPoint); + var ourPoint = Vector2.Transform(args.WorldPoint, ourXform.InvWorldMatrix); + var otherPoint = Vector2.Transform(args.WorldPoint, otherXform.InvWorldMatrix); var ourVelocity = _physics.GetLinearVelocity(uid, ourPoint, ourBody, ourXform); var otherVelocity = _physics.GetLinearVelocity(args.OtherEntity, otherPoint, otherBody, otherXform); diff --git a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs index f1d0af6f90..779b2f5971 100644 --- a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs +++ b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Atmos.Components; using Content.Server.Singularity.Components; using Content.Shared.Ghost; @@ -126,7 +127,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. /// (optional) The transform of the entity at the epicenter of the gravitational pulse. - public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV, TransformComponent? xform = null) + public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV, TransformComponent? xform = null) { if (Resolve(uid, ref xform)) GravPulse(xform.Coordinates, maxRange, minRange, in baseMatrixDeltaV); @@ -154,7 +155,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) => GravPulse(entityPos.ToMap(EntityManager, _transform), maxRange, minRange, in baseMatrixDeltaV); /// @@ -175,7 +176,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) { if (mapPos == MapCoordinates.Nullspace) return; // No gravpulses in nullspace please. @@ -205,7 +206,7 @@ public sealed class GravityWellSystem : SharedGravityWellSystem continue; var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants. - _physics.ApplyLinearImpulse(entity, (displacement * baseMatrixDeltaV) * scaling, body: physics); + _physics.ApplyLinearImpulse(entity, Vector2.Transform(displacement, baseMatrixDeltaV) * scaling, body: physics); } } @@ -218,10 +219,9 @@ public sealed class GravityWellSystem : SharedGravityWellSystem /// The base amount of velocity that will be added to entities in range towards the epicenter of the pulse. /// The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) - => GravPulse(mapPos, maxRange, minRange, new Matrix3( - baseRadialDeltaV, +baseTangentialDeltaV, 0.0f, - -baseTangentialDeltaV, baseRadialDeltaV, 0.0f, - 0.0f, 0.0f, 1.0f + => GravPulse(mapPos, maxRange, minRange, new Matrix3x2( + baseRadialDeltaV, -baseTangentialDeltaV, 0.0f, + +baseTangentialDeltaV, baseRadialDeltaV, 0.0f )); #endregion GravPulse diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 7247109e37..cb893299a9 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -396,7 +396,7 @@ public sealed partial class GunSystem : SharedGunSystem var (_, gridRot, gridInvMatrix) = TransformSystem.GetWorldPositionRotationInvMatrix(gridXform, xformQuery); fromCoordinates = new EntityCoordinates(gridUid.Value, - gridInvMatrix.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem))); + Vector2.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem), gridInvMatrix)); // Use the fallback angle I guess? angle -= gridRot; diff --git a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs index 7477b6c26e..88e81c93c5 100644 --- a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs +++ b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -15,7 +16,7 @@ public sealed partial class ExplosionVisualsComponent : Component public Dictionary>> Tiles = new(); public List Intensity = new(); public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; } @@ -27,7 +28,7 @@ public sealed class ExplosionVisualsState : ComponentState public Dictionary>> Tiles; public List Intensity; public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; public ExplosionVisualsState( @@ -36,7 +37,7 @@ public sealed class ExplosionVisualsState : ComponentState List intensity, Dictionary>? spaceTiles, Dictionary>> tiles, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, ushort spaceTileSize) { Epicenter = epicenter; diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 29e82f8ade..5eaa25f484 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -192,7 +192,7 @@ public abstract class SharedItemSystem : EntitySystem var shapes = GetItemShape(entity); var boundingShape = shapes.GetBoundingBox(); var boundingCenter = ((Box2) boundingShape).Center; - var matty = Matrix3.CreateTransform(boundingCenter, rotation); + var matty = Matrix3Helpers.CreateTransform(boundingCenter, rotation); var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft; var adjustedShapes = new List(); diff --git a/Content.Shared/Maps/TurfSystem.cs b/Content.Shared/Maps/TurfSystem.cs index ad8b3ddea8..8a4bbf68be 100644 --- a/Content.Shared/Maps/TurfSystem.cs +++ b/Content.Shared/Maps/TurfSystem.cs @@ -44,7 +44,7 @@ public sealed class TurfSystem : EntitySystem var size = grid.TileSize; var localPos = new Vector2(indices.X * size + (size / 2f), indices.Y * size + (size / 2f)); - var worldPos = matrix.Transform(localPos); + var worldPos = Vector2.Transform(localPos, matrix); // This is scaled to 95 % so it doesn't encompass walls on other tiles. var tileAabb = Box2.UnitCentered.Scale(0.95f * size); diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 2d9993096b..b67acdea4f 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -751,7 +751,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return; var invMatrix = TransformSystem.GetInvWorldMatrix(userXform); - var localPos = invMatrix.Transform(coordinates.Position); + var localPos = Vector2.Transform(coordinates.Position, invMatrix); if (localPos.LengthSquared() <= 0f) return; From 6235fa1ac69f72d0ed9ef4b089245f04e7ca01f9 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:08:15 +1200 Subject: [PATCH 77/84] Content changes for entity categories PR (#27232) * Content changes for entity categories PR * Poke tests * Why are tests suddenly working? --- Content.IntegrationTests/Tests/EntityTest.cs | 11 +++-------- .../Rules/Components/NukeOperativeSpawnerComponent.cs | 5 +++-- .../Roles/Components/GhostRoleMobSpawnerComponent.cs | 2 +- .../Components/RandomHumanoidSpawnerComponent.cs | 2 +- .../Components/ConditionalSpawnerComponent.cs | 2 +- .../Spawners/Components/RandomSpawnerComponent.cs | 2 +- .../Spawners/Components/TimedSpawnerComponent.cs | 2 +- Content.Shared/Actions/BaseActionComponent.cs | 4 +++- .../Body/Prototypes/BodyPrototypeSerializer.cs | 2 +- Content.Shared/Dragon/SharedDragonRiftComponent.cs | 3 ++- Resources/Locale/en-US/entity-categories.ftl | 1 + Resources/Prototypes/Alerts/alerts.yml | 2 +- Resources/Prototypes/Alerts/revenant.yml | 2 +- .../Prototypes/Entities/Objects/Devices/flatpack.yml | 2 +- .../Entities/Structures/Machines/flatpacker.yml | 2 +- Resources/Prototypes/Entities/categories.yml | 4 ++++ 16 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 Resources/Locale/en-US/entity-categories.ftl create mode 100644 Resources/Prototypes/Entities/categories.yml diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 54af64122b..926374cf05 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -19,6 +19,8 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(EntityUid))] public sealed class EntityTest { + private static readonly ProtoId SpawnerCategory = "Spawner"; + [Test] public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps() { @@ -234,14 +236,6 @@ namespace Content.IntegrationTests.Tests "StationEvent", "TimedDespawn", - // Spawner entities - "DragonRift", - "RandomHumanoidSpawner", - "RandomSpawner", - "ConditionalSpawner", - "GhostRoleMobSpawner", - "NukeOperativeSpawner", - "TimedSpawner", // makes an announcement on mapInit. "AnnounceOnSpawn", }; @@ -253,6 +247,7 @@ namespace Content.IntegrationTests.Tests .Where(p => !p.Abstract) .Where(p => !pair.IsTestPrototype(p)) .Where(p => !excluded.Any(p.Components.ContainsKey)) + .Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) .Select(p => p.ID) .ToList(); diff --git a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs index bb1b7c8746..54eaa6e32e 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs @@ -1,3 +1,5 @@ +using Robust.Shared.Prototypes; + namespace Content.Server.GameTicking.Rules.Components; /// @@ -5,6 +7,5 @@ namespace Content.Server.GameTicking.Rules.Components; /// and providing loadout + name for the operative on spawn. /// TODO: Remove once systems can request spawns from the ghost role system directly. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class NukeOperativeSpawnerComponent : Component; - diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 6c2a6986fc..6116173f90 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -5,7 +5,7 @@ namespace Content.Server.Ghost.Roles.Components /// /// Allows a ghost to take this role, spawning a new entity. /// - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Access(typeof(GhostRoleSystem))] public sealed partial class GhostRoleMobSpawnerComponent : Component { diff --git a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs index b56664fe19..bb38e94e04 100644 --- a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs +++ b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.Humanoid.Components; /// This is added to a marker entity in order to spawn a randomized /// humanoid ingame. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomHumanoidSpawnerComponent : Component { [DataField("settings", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs index 5b98989bb3..1b06367b0d 100644 --- a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Virtual] public partial class ConditionalSpawnerComponent : Component { diff --git a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs index 9bf4d6d253..e6a597f365 100644 --- a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs @@ -2,7 +2,7 @@ using Robust.Shared.Prototypes; namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomSpawnerComponent : ConditionalSpawnerComponent { [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs index b60afbc88b..828e541717 100644 --- a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs @@ -9,7 +9,7 @@ namespace Content.Server.Spawners.Components; /// Can configure the set of entities, spawn timing, spawn chance, /// and min/max number of entities to spawn. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class TimedSpawnerComponent : Component, ISerializationHooks { /// diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index 57c145a0ec..9156f747f5 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -1,14 +1,16 @@ using Robust.Shared.Audio; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Actions; -// TODO ACTIONS make this a seprate component and remove the inheritance stuff. +// TODO ACTIONS make this a separate component and remove the inheritance stuff. // TODO ACTIONS convert to auto comp state? // TODO add access attribute. Need to figure out what to do with decal & mapping actions. // [Access(typeof(SharedActionsSystem))] +[EntityCategory("Actions")] public abstract partial class BaseActionComponent : Component { public abstract BaseActionEvent? BaseEvent { get; } diff --git a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs index e2b54bf951..ae09976704 100644 --- a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs +++ b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs @@ -182,7 +182,7 @@ public sealed class BodyPrototypeSerializer : ITypeReader(), organs ?? new Dictionary()); + var slot = new BodyPrototypeSlot(part, connections ?? new HashSet(), organs ?? new Dictionary()); slots.Add(slotId, slot); } diff --git a/Content.Shared/Dragon/SharedDragonRiftComponent.cs b/Content.Shared/Dragon/SharedDragonRiftComponent.cs index 0d2bf44018..8377dbfee7 100644 --- a/Content.Shared/Dragon/SharedDragonRiftComponent.cs +++ b/Content.Shared/Dragon/SharedDragonRiftComponent.cs @@ -1,9 +1,10 @@ using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Dragon; -[NetworkedComponent] +[NetworkedComponent, EntityCategory("Spawner")] public abstract partial class SharedDragonRiftComponent : Component { [DataField("state")] diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl new file mode 100644 index 0000000000..190fe5713a --- /dev/null +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -0,0 +1 @@ +entity-category-name-actions = Actions diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 7881cddd4a..72412fde7c 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -28,7 +28,7 @@ - type: entity id: AlertSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite layers: diff --git a/Resources/Prototypes/Alerts/revenant.yml b/Resources/Prototypes/Alerts/revenant.yml index 7f3f98949e..38933df4fe 100644 --- a/Resources/Prototypes/Alerts/revenant.yml +++ b/Resources/Prototypes/Alerts/revenant.yml @@ -16,7 +16,7 @@ - type: entity id: AlertEssenceSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite sprite: /Textures/Interface/Alerts/essence_counter.rsi diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index e3e77d5c88..b9c2b752db 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -4,7 +4,7 @@ name: base flatpack description: A flatpack used for constructing something. categories: - - hideSpawnMenu + - HideSpawnMenu components: - type: Item size: Large diff --git a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml index b4f05cf68a..78f1504003 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml @@ -84,7 +84,7 @@ - type: entity id: FlatpackerNoBoardEffect categories: - - hideSpawnMenu + - HideSpawnMenu components: - type: Sprite sprite: Structures/Machines/autolathe.rsi diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml new file mode 100644 index 0000000000..2fb56818f9 --- /dev/null +++ b/Resources/Prototypes/Entities/categories.yml @@ -0,0 +1,4 @@ +- type: entityCategory + id: Actions + name: entity-category-name-actions + hideSpawnMenu: true From 4c667b2d8f898abc4f7b9fcf9d8753f9cbee9afd Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:13:12 -0500 Subject: [PATCH 78/84] Fix Lasers Being Blocked by External Airlocks and Shuttle Airlocks (#28065) --- .../Structures/Doors/Airlocks/external.yml | 11 +++++ .../Structures/Doors/Airlocks/shuttle.yml | 47 +++---------------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml index 293aaac273..3197ba417f 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/external.yml @@ -33,3 +33,14 @@ sprite: Structures/Doors/Airlocks/Glass/external.rsi - type: PaintableAirlock group: ExternalGlass + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close + density: 100 + mask: + - FullTileMask + layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks + - GlassAirlockLayer diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml index 43d1228a40..5d27cec181 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml @@ -82,29 +82,17 @@ components: - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle.rsi - snapCardinals: false - layers: - - state: closed - map: ["enum.DoorVisualLayers.Base"] - - state: closed_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseUnlit"] - - state: welded - map: ["enum.WeldableLayers.BaseWelded"] - - state: bolted_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseBolted"] - - state: emergency_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - - state: panel_open - map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: Occluder enabled: false - type: PaintableAirlock group: ShuttleGlass - type: Door occludes: false + - type: Fixtures + fixtures: + fix1: + layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks + - GlassAirlockLayer - type: entity id: AirlockShuttleAssembly @@ -126,36 +114,13 @@ - type: entity id: AirlockGlassShuttleSyndicate - parent: AirlockShuttle + parent: AirlockGlassShuttle name: external airlock suffix: Glass, Docking description: Necessary for connecting two space craft together. components: - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle_syndicate.rsi - snapCardinals: false - layers: - - state: closed - map: ["enum.DoorVisualLayers.Base"] - - state: closed_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseUnlit"] - - state: welded - map: ["enum.WeldableLayers.BaseWelded"] - - state: bolted_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseBolted"] - - state: emergency_unlit - shader: unshaded - map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - - state: panel_open - map: ["enum.WiresVisualLayers.MaintenancePanel"] - - type: Occluder - enabled: false - - type: PaintableAirlock - group: ShuttleGlass - - type: Door - occludes: false - type: entity parent: AirlockShuttle From f5bc921bdd7e0c2c0c8a4ea7620b6022aa4e503e Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 04:14:18 +0000 Subject: [PATCH 79/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 0c671f49ee..de9e112c59 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Plykiya - changes: - - message: Ion storms now have a chance of happening from the start of shift. - type: Tweak - id: 6163 - time: '2024-03-16T03:47:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26165 - author: Krunk changes: - message: Zombies and animals can be stripped once again. @@ -3853,3 +3846,10 @@ id: 6662 time: '2024-06-02T01:41:06.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28496 +- author: Cojoke-dot + changes: + - message: Lasers now pass through Glass External Airlocks and Glass Shuttle Airlocks + type: Fix + id: 6663 + time: '2024-06-02T04:13:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28065 From 12839893464ef4af1bf3279e9f76f92e82b0afe1 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Sun, 2 Jun 2024 06:17:53 +0200 Subject: [PATCH 80/84] Flash overlay rework and bugfixes (#27369) --- Content.Client/Entry/EntryPoint.cs | 1 - Content.Client/Flash/FlashOverlay.cs | 78 +++++++++------ Content.Client/Flash/FlashSystem.cs | 99 ++++++++++--------- .../Components/DamagedByFlashingComponent.cs | 3 +- .../Components/FlashImmunityComponent.cs | 19 ++-- Content.Server/Flash/FlashSystem.cs | 84 ++++++++-------- .../Flash/Components/FlashComponent.cs | 11 ++- .../Flash/Components/FlashedComponent.cs | 9 ++ Content.Shared/Flash/FlashableComponent.cs | 40 -------- Content.Shared/Flash/SharedFlashSystem.cs | 15 +-- .../Mobs/Cyborgs/base_borg_chassis.yml | 2 +- .../Entities/Mobs/NPCs/argocyte.yml | 1 - .../Prototypes/Entities/Mobs/NPCs/pets.yml | 2 - .../Entities/Mobs/NPCs/simplemob.yml | 2 + .../Prototypes/Entities/Mobs/Species/base.yml | 21 +--- .../Entities/Objects/base_shadow.yml | 3 - Resources/Prototypes/status_effects.yml | 3 + .../Textures/Shaders/flashed_effect.swsl | 2 +- 18 files changed, 187 insertions(+), 208 deletions(-) create mode 100644 Content.Shared/Flash/Components/FlashedComponent.cs delete mode 100644 Content.Shared/Flash/FlashableComponent.cs diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index b28c6a11fb..51210a8a93 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -152,7 +152,6 @@ namespace Content.Client.Entry _parallaxManager.LoadDefaultParallax(); _overlayManager.AddOverlay(new SingularityOverlay()); - _overlayManager.AddOverlay(new FlashOverlay()); _overlayManager.AddOverlay(new RadiationPulseOverlay()); _chatManager.Initialize(); _clientPreferencesManager.Initialize(); diff --git a/Content.Client/Flash/FlashOverlay.cs b/Content.Client/Flash/FlashOverlay.cs index fe9c888227..9ea00275e8 100644 --- a/Content.Client/Flash/FlashOverlay.cs +++ b/Content.Client/Flash/FlashOverlay.cs @@ -1,12 +1,11 @@ -using System.Numerics; +using Content.Shared.Flash; +using Content.Shared.Flash.Components; +using Content.Shared.StatusEffect; using Content.Client.Viewport; using Robust.Client.Graphics; using Robust.Client.State; using Robust.Client.Player; using Robust.Shared.Enums; -using Robust.Shared.Graphics; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using SixLabors.ImageSharp.PixelFormats; @@ -17,66 +16,87 @@ namespace Content.Client.Flash { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IClyde _displayManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private readonly StatusEffectsSystem _statusSys; public override OverlaySpace Space => OverlaySpace.WorldSpace; private readonly ShaderInstance _shader; - private double _startTime = -1; - private double _lastsFor = 1; - private Texture? _screenshotTexture; + public float PercentComplete = 0.0f; + public Texture? ScreenshotTexture; public FlashOverlay() { IoCManager.InjectDependencies(this); - _shader = _prototypeManager.Index("FlashedEffect").Instance().Duplicate(); + _shader = _prototypeManager.Index("FlashedEffect").InstanceUnique(); + _statusSys = _entityManager.System(); } - public void ReceiveFlash(double duration) + protected override void FrameUpdate(FrameEventArgs args) + { + var playerEntity = _playerManager.LocalEntity; + + if (playerEntity == null) + return; + + if (!_entityManager.HasComponent(playerEntity) + || !_entityManager.TryGetComponent(playerEntity, out var status)) + return; + + if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status)) + return; + + var curTime = _timing.CurTime; + var lastsFor = (float) (time.Value.Item2 - time.Value.Item1).TotalSeconds; + var timeDone = (float) (curTime - time.Value.Item1).TotalSeconds; + + PercentComplete = timeDone / lastsFor; + } + + public void ReceiveFlash() { if (_stateManager.CurrentState is IMainViewportState state) { + // take a screenshot + // note that the callback takes a while and ScreenshotTexture will be null the first few Draws state.Viewport.Viewport.Screenshot(image => { var rgba32Image = image.CloneAs(SixLabors.ImageSharp.Configuration.Default); - _screenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); + ScreenshotTexture = _displayManager.LoadTextureFromImage(rgba32Image); }); } + } - _startTime = _gameTiming.CurTime.TotalSeconds; - _lastsFor = duration; + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + return PercentComplete < 1.0f; } protected override void Draw(in OverlayDrawArgs args) { - if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) - return; - - if (args.Viewport.Eye != eyeComp.Eye) - return; - - var percentComplete = (float) ((_gameTiming.CurTime.TotalSeconds - _startTime) / _lastsFor); - if (percentComplete >= 1.0f) + if (ScreenshotTexture == null) return; var worldHandle = args.WorldHandle; + _shader.SetParameter("percentComplete", PercentComplete); worldHandle.UseShader(_shader); - _shader.SetParameter("percentComplete", percentComplete); - - if (_screenshotTexture != null) - { - worldHandle.DrawTextureRectRegion(_screenshotTexture, args.WorldBounds); - } - + worldHandle.DrawTextureRectRegion(ScreenshotTexture, args.WorldBounds); worldHandle.UseShader(null); } protected override void DisposeBehavior() { base.DisposeBehavior(); - _screenshotTexture = null; + ScreenshotTexture = null; + PercentComplete = 1.0f; } } } diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs index ad8f8b0b82..9a0579f6aa 100644 --- a/Content.Client/Flash/FlashSystem.cs +++ b/Content.Client/Flash/FlashSystem.cs @@ -1,62 +1,67 @@ using Content.Shared.Flash; +using Content.Shared.Flash.Components; +using Content.Shared.StatusEffect; using Robust.Client.Graphics; using Robust.Client.Player; -using Robust.Shared.GameStates; -using Robust.Shared.Timing; +using Robust.Shared.Player; -namespace Content.Client.Flash +namespace Content.Client.Flash; + +public sealed class FlashSystem : SharedFlashSystem { - public sealed class FlashSystem : SharedFlashSystem + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private FlashOverlay _overlay = default!; + + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IOverlayManager _overlayManager = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnStatusAdded); + + _overlay = new(); + } + + private void OnPlayerAttached(EntityUid uid, FlashedComponent component, LocalPlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, FlashedComponent component, LocalPlayerDetachedEvent args) + { + _overlay.PercentComplete = 1.0f; + _overlay.ScreenshotTexture = null; + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnInit(EntityUid uid, FlashedComponent component, ComponentInit args) + { + if (_player.LocalEntity == uid) { - base.Initialize(); - - SubscribeLocalEvent(OnFlashableHandleState); + _overlayMan.AddOverlay(_overlay); } + } - private void OnFlashableHandleState(EntityUid uid, FlashableComponent component, ref ComponentHandleState args) + private void OnShutdown(EntityUid uid, FlashedComponent component, ComponentShutdown args) + { + if (_player.LocalEntity == uid) { - if (args.Current is not FlashableComponentState state) - return; + _overlay.PercentComplete = 1.0f; + _overlay.ScreenshotTexture = null; + _overlayMan.RemoveOverlay(_overlay); + } + } - // Yes, this code is awful. I'm just porting it to an entity system so don't blame me. - if (_playerManager.LocalEntity != uid) - { - return; - } - - if (state.Time == default) - { - return; - } - - // Few things here: - // 1. If a shorter duration flash is applied then don't do anything - // 2. If the client-side time is later than when the flash should've ended don't do anything - var currentTime = _gameTiming.CurTime.TotalSeconds; - var newEndTime = state.Time.TotalSeconds + state.Duration; - var currentEndTime = component.LastFlash.TotalSeconds + component.Duration; - - if (currentEndTime > newEndTime) - { - return; - } - - if (currentTime > newEndTime) - { - return; - } - - component.LastFlash = state.Time; - component.Duration = state.Duration; - - var overlay = _overlayManager.GetOverlay(); - overlay.ReceiveFlash(component.Duration); + private void OnStatusAdded(EntityUid uid, FlashedComponent component, StatusEffectAddedEvent args) + { + if (_player.LocalEntity == uid && args.Key == FlashedKey) + { + _overlay.ReceiveFlash(); } } } diff --git a/Content.Server/Flash/Components/DamagedByFlashingComponent.cs b/Content.Server/Flash/Components/DamagedByFlashingComponent.cs index 2a9024607d..ef33454295 100644 --- a/Content.Server/Flash/Components/DamagedByFlashingComponent.cs +++ b/Content.Server/Flash/Components/DamagedByFlashingComponent.cs @@ -3,7 +3,6 @@ using Robust.Shared.Prototypes; namespace Content.Server.Flash.Components; -// Also needed FlashableComponent on entity to work [RegisterComponent, Access(typeof(DamagedByFlashingSystem))] public sealed partial class DamagedByFlashingComponent : Component { @@ -11,5 +10,5 @@ public sealed partial class DamagedByFlashingComponent : Component /// damage from flashing /// [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier FlashDamage = new (); + public DamageSpecifier FlashDamage = new(); } diff --git a/Content.Server/Flash/Components/FlashImmunityComponent.cs b/Content.Server/Flash/Components/FlashImmunityComponent.cs index 80bbdd1282..a982a9059f 100644 --- a/Content.Server/Flash/Components/FlashImmunityComponent.cs +++ b/Content.Server/Flash/Components/FlashImmunityComponent.cs @@ -1,10 +1,13 @@ -namespace Content.Server.Flash.Components +namespace Content.Server.Flash.Components; + +/// +/// Makes the entity immune to being flashed. +/// When given to clothes in the "head", "eyes" or "mask" slot it protects the wearer. +/// +[RegisterComponent, Access(typeof(FlashSystem))] +public sealed partial class FlashImmunityComponent : Component { - [RegisterComponent, Access(typeof(FlashSystem))] - public sealed partial class FlashImmunityComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("enabled")] - public bool Enabled { get; set; } = true; - } + [ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] + public bool Enabled { get; set; } = true; } diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index dd8cdab426..ccb58e94f8 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -9,18 +9,17 @@ using Content.Shared.Charges.Systems; using Content.Shared.Eye.Blinding.Components; using Content.Shared.Flash; using Content.Shared.IdentityManagement; -using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory; -using Content.Shared.Physics; using Content.Shared.Tag; using Content.Shared.Traits.Assorted; using Content.Shared.Weapons.Melee.Events; +using Content.Shared.StatusEffect; +using Content.Shared.Examine; using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Random; -using Robust.Shared.Timing; using InventoryComponent = Content.Shared.Inventory.InventoryComponent; namespace Content.Server.Flash @@ -31,14 +30,14 @@ namespace Content.Server.Flash [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedChargesSystem _charges = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; public override void Initialize() { @@ -46,7 +45,7 @@ namespace Content.Server.Flash SubscribeLocalEvent(OnFlashMeleeHit); // ran before toggling light for extra-bright lantern - SubscribeLocalEvent(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) }); + SubscribeLocalEvent(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) }); SubscribeLocalEvent(OnInventoryFlashAttempt); SubscribeLocalEvent(OnFlashImmunityFlashAttempt); SubscribeLocalEvent(OnPermanentBlindnessFlashAttempt); @@ -114,19 +113,35 @@ namespace Content.Server.Flash float flashDuration, float slowTo, bool displayPopup = true, - FlashableComponent? flashable = null, bool melee = false, TimeSpan? stunDuration = null) { - if (!Resolve(target, ref flashable, false)) - return; - var attempt = new FlashAttemptEvent(target, user, used); RaiseLocalEvent(target, attempt, true); if (attempt.Cancelled) return; + // don't paralyze, slowdown or convert to rev if the target is immune to flashes + if (!_statusEffectsSystem.TryAddStatusEffect(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true)) + return; + + if (stunDuration != null) + { + _stun.TryParalyze(target, stunDuration.Value, true); + } + else + { + _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration / 1000f), true, + slowTo, slowTo); + } + + if (displayPopup && user != null && target != user && Exists(user.Value)) + { + _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you", + ("user", Identity.Entity(user.Value, EntityManager))), target, target); + } + if (melee) { var ev = new AfterFlashedEvent(target, user, used); @@ -135,48 +150,31 @@ namespace Content.Server.Flash if (used != null) RaiseLocalEvent(used.Value, ref ev); } - - flashable.LastFlash = _timing.CurTime; - flashable.Duration = flashDuration / 1000f; // TODO: Make this sane... - Dirty(target, flashable); - - if (stunDuration != null) - { - _stun.TryParalyze(target, stunDuration.Value, true); - } - else - { - _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true, - slowTo, slowTo); - } - - if (displayPopup && user != null && target != user && Exists(user.Value)) - { - _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you", - ("user", Identity.Entity(user.Value, EntityManager))), target, target); - } } public void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { var transform = Transform(source); var mapPosition = _transform.GetMapCoordinates(transform); - var flashableQuery = GetEntityQuery(); + var statusEffectsQuery = GetEntityQuery(); + var damagedByFlashingQuery = GetEntityQuery(); foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range)) { if (!_random.Prob(probability)) continue; - if (!flashableQuery.TryGetComponent(entity, out var flashable)) + // Is the entity affected by the flash either through status effects or by taking damage? + if (!statusEffectsQuery.HasComponent(entity) && !damagedByFlashingQuery.HasComponent(entity)) continue; - // Check for unobstructed entities while ignoring the mobs with flashable components. - if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, predicate: (e) => flashableQuery.HasComponent(e) || e == source.Owner)) + // Check for entites in view + // put damagedByFlashingComponent in the predicate because shadow anomalies block vision. + if (!_examine.InRangeUnOccluded(entity, mapPosition, range, predicate: (e) => damagedByFlashingQuery.HasComponent(e))) continue; // They shouldn't have flash removed in between right? - Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity)); + Flash(entity, user, source, duration, slowTo, displayPopup); } _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)); @@ -195,13 +193,15 @@ namespace Content.Server.Flash private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args) { - if(component.Enabled) + if (component.Enabled) args.Cancel(); } private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args) { - args.Cancel(); + // check for total blindness + if (component.Blindness == 0) + args.Cancel(); } private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args) @@ -210,6 +210,10 @@ namespace Content.Server.Flash } } + /// + /// Called before a flash is used to check if the attempt is cancelled by blindness, items or FlashImmunityComponent. + /// Raised on the target hit by the flash, the user of the flash and the flash used. + /// public sealed class FlashAttemptEvent : CancellableEntityEventArgs { public readonly EntityUid Target; @@ -224,8 +228,8 @@ namespace Content.Server.Flash } } /// - /// Called after a flash is used via melee on another person to check for rev conversion. - /// Raised on the user of the flash, the target hit by the flash, and the flash used. + /// Called after a flash is used via melee on another person to check for rev conversion. + /// Raised on the target hit by the flash, the user of the flash and the flash used. /// [ByRefEvent] public readonly struct AfterFlashedEvent @@ -241,6 +245,4 @@ namespace Content.Server.Flash Used = used; } } - - } diff --git a/Content.Shared/Flash/Components/FlashComponent.cs b/Content.Shared/Flash/Components/FlashComponent.cs index a9098bc85a..29f92eb94f 100644 --- a/Content.Shared/Flash/Components/FlashComponent.cs +++ b/Content.Shared/Flash/Components/FlashComponent.cs @@ -1,6 +1,6 @@ -using Content.Shared.Flash; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Flash.Components { @@ -43,4 +43,13 @@ namespace Content.Shared.Flash.Components [DataField] public float Probability = 1f; } + + [Serializable, NetSerializable] + public enum FlashVisuals : byte + { + BaseLayer, + LightLayer, + Burnt, + Flashing, + } } diff --git a/Content.Shared/Flash/Components/FlashedComponent.cs b/Content.Shared/Flash/Components/FlashedComponent.cs new file mode 100644 index 0000000000..75bbb12304 --- /dev/null +++ b/Content.Shared/Flash/Components/FlashedComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Flash.Components; + +/// +/// Exists for use as a status effect. Adds a shader to the client that obstructs vision. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class FlashedComponent : Component { } diff --git a/Content.Shared/Flash/FlashableComponent.cs b/Content.Shared/Flash/FlashableComponent.cs deleted file mode 100644 index c4f8074cea..0000000000 --- a/Content.Shared/Flash/FlashableComponent.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Shared.Physics; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Flash -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class FlashableComponent : Component - { - public float Duration; - public TimeSpan LastFlash; - - [DataField] - public CollisionGroup CollisionGroup = CollisionGroup.Opaque; - - public override bool SendOnlyToOwner => true; - } - - [Serializable, NetSerializable] - public sealed class FlashableComponentState : ComponentState - { - public float Duration { get; } - public TimeSpan Time { get; } - - public FlashableComponentState(float duration, TimeSpan time) - { - Duration = duration; - Time = time; - } - } - - [Serializable, NetSerializable] - public enum FlashVisuals : byte - { - BaseLayer, - LightLayer, - Burnt, - Flashing, - } -} diff --git a/Content.Shared/Flash/SharedFlashSystem.cs b/Content.Shared/Flash/SharedFlashSystem.cs index 16fdbfc2f3..f83f02a310 100644 --- a/Content.Shared/Flash/SharedFlashSystem.cs +++ b/Content.Shared/Flash/SharedFlashSystem.cs @@ -1,19 +1,10 @@ -using Robust.Shared.GameStates; +using Content.Shared.StatusEffect; namespace Content.Shared.Flash { public abstract class SharedFlashSystem : EntitySystem { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnFlashableGetState); - } - - private static void OnFlashableGetState(EntityUid uid, FlashableComponent component, ref ComponentGetState args) - { - args.State = new FlashableComponentState(component.Duration, component.LastFlash); - } + [ValidatePrototypeId] + public const string FlashedKey = "Flashed"; } } diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 8bf55e48bd..baea453cb8 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -126,6 +126,7 @@ - Stun - KnockedDown - SlowedDown + - Flashed - type: TypingIndicator proto: robot - type: Speech @@ -147,7 +148,6 @@ locked: true - type: ActivatableUIRequiresLock - type: LockedWiresPanel - - type: Flashable - type: Damageable damageContainer: Silicon - type: Destructible diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml index 39e68b63a7..3b6c4e8ed9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/argocyte.yml @@ -48,7 +48,6 @@ sprite: Mobs/Effects/onfire.rsi normalState: Generic_mob_burning - type: Climbing - - type: Flashable - type: NameIdentifier group: GenericNumber diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 23eb462db1..c1cfa40836 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -781,7 +781,6 @@ amount: 3 - id: DrinkTequilaBottleFull amount: 1 - - type: Flashable - type: Tag tags: - CannotSuicide @@ -808,7 +807,6 @@ # name: ghost-role-information-tropico-name # description: ghost-role-information-tropico-description # - type: GhostTakeoverAvailable -# - type: Flashable - type: Tag tags: - VimPilot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 12ea40b1e3..4936d883e5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -25,6 +25,7 @@ - ForcedSleep - TemporaryBlindness - Pacified + - Flashed - type: Buckle - type: StandingState - type: Tag @@ -99,6 +100,7 @@ - TemporaryBlindness - Pacified - StaminaModifier + - Flashed - type: Bloodstream bloodMaxVolume: 150 - type: MobPrice diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index a49f8ff34c..eeabecef8b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -140,6 +140,7 @@ - TemporaryBlindness - Pacified - StaminaModifier + - Flashed - type: Reflect enabled: false reflectProb: 0 @@ -234,7 +235,6 @@ id: BaseMobSpeciesOrganic abstract: true components: - - type: Flashable - type: Barotrauma damage: types: @@ -249,24 +249,7 @@ Heat: -0.07 groups: Brute: -0.07 - # Organs - - type: StatusEffects - allowed: - - Stun - - KnockedDown - - SlowedDown - - Stutter - - SeeingRainbows - - Electrocution - - ForcedSleep - - TemporaryBlindness - - Drunk - - SlurredSpeech - - RatvarianLanguage - - PressureImmunity - - Muted - - Pacified - - StaminaModifier + - type: Blindable # Other - type: Temperature diff --git a/Resources/Prototypes/Entities/Objects/base_shadow.yml b/Resources/Prototypes/Entities/Objects/base_shadow.yml index 2375855bf9..1cefef9e56 100644 --- a/Resources/Prototypes/Entities/Objects/base_shadow.yml +++ b/Resources/Prototypes/Entities/Objects/base_shadow.yml @@ -15,9 +15,6 @@ path: /Audio/Items/hiss.ogg params: variation: 0.08 - - type: Flashable - collisionGroup: - - None - type: DamagedByFlashing flashDamage: types: diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml index a991bf4035..27609b1bb5 100644 --- a/Resources/Prototypes/status_effects.yml +++ b/Resources/Prototypes/status_effects.yml @@ -59,3 +59,6 @@ - type: statusEffect id: StaminaModifier + +- type: statusEffect + id: Flashed diff --git a/Resources/Textures/Shaders/flashed_effect.swsl b/Resources/Textures/Shaders/flashed_effect.swsl index ec486ab531..519a9fdd99 100644 --- a/Resources/Textures/Shaders/flashed_effect.swsl +++ b/Resources/Textures/Shaders/flashed_effect.swsl @@ -11,7 +11,7 @@ void fragment() { highp vec4 textureMix = mix(tex1, tex2, 0.5); - // Gradually mixes between the texture mix and a full-white texture, causing the "blinding" effect + // Gradually mixes between the texture mix and a full-black texture, causing the "blinding" effect highp vec4 mixed = mix(vec4(0.0, 0.0, 0.0, 1.0), textureMix, percentComplete); COLOR = vec4(mixed.rgb, remaining); From 827211369cd18c9377a9994ec97fd242a74b87fd Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 2 Jun 2024 04:18:59 +0000 Subject: [PATCH 81/84] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index de9e112c59..a6e7cbbc71 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: Krunk - changes: - - message: Zombies and animals can be stripped once again. - type: Fix - - message: Dead or critical mobs can no longer be stripped instantly. - type: Fix - id: 6164 - time: '2024-03-16T03:50:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26166 - author: metalgearsloth changes: - message: Fix store refunds. @@ -3853,3 +3844,12 @@ id: 6663 time: '2024-06-02T04:13:12.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28065 +- author: slarticodefast + changes: + - message: Fixed the flash effect getting darker with each appearence. + type: Fix + - message: Fixed short-sightedness making you immune to flashes. + type: Fix + id: 6664 + time: '2024-06-02T04:17:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/27369 From 452b53988c336ba6c45c16e9082f5bc91108bce1 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:26:42 +1200 Subject: [PATCH 82/84] Add debug asserts to ensure that network groups are up to date (#28495) --- .../Power/Pow3r/BatteryRampPegSolver.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 0afd86679b..12118968b7 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Robust.Shared.Utility; using System.Linq; using Robust.Shared.Threading; @@ -37,6 +38,7 @@ namespace Content.Server.Power.Pow3r DebugTools.Assert(state.GroupedNets.Select(x => x.Count).Sum() == state.Networks.Count); _networkJob.State = state; _networkJob.FrameTime = frameTime; + ValidateNetworkGroups(state, state.GroupedNets); // Each network height layer can be run in parallel without issues. foreach (var group in state.GroupedNets) @@ -321,9 +323,69 @@ namespace Content.Server.Power.Pow3r RecursivelyEstimateNetworkDepth(state, network, groupedNetworks); } + ValidateNetworkGroups(state, groupedNetworks); return groupedNetworks; } + /// + /// Validate that network grouping is up to date. I.e., that it is safe to solve each networking in a given + /// group in parallel. This assumes that batteries are the only device that connects to multiple networks, and + /// is thus the only obstacle to solving everything in parallel. + /// + [Conditional("DEBUG")] + private void ValidateNetworkGroups(PowerState state, List> groupedNetworks) + { + HashSet nets = new(); + HashSet netIds = new(); + foreach (var layer in groupedNetworks) + { + nets.Clear(); + netIds.Clear(); + + foreach (var net in layer) + { + foreach (var batteryId in net.BatteryLoads) + { + var battery = state.Batteries[batteryId]; + if (battery.LinkedNetworkDischarging == default) + continue; + + var subNet = state.Networks[battery.LinkedNetworkDischarging]; + if (battery.LinkedNetworkDischarging == net.Id) + { + DebugTools.Assert(subNet == net); + continue; + } + + DebugTools.Assert(!nets.Contains(subNet)); + DebugTools.Assert(!netIds.Contains(subNet.Id)); + DebugTools.Assert(subNet.Height < net.Height); + } + + foreach (var batteryId in net.BatterySupplies) + { + var battery = state.Batteries[batteryId]; + if (battery.LinkedNetworkCharging == default) + continue; + + var parentNet = state.Networks[battery.LinkedNetworkCharging]; + if (battery.LinkedNetworkCharging == net.Id) + { + DebugTools.Assert(parentNet == net); + continue; + } + + DebugTools.Assert(!nets.Contains(parentNet)); + DebugTools.Assert(!netIds.Contains(parentNet.Id)); + DebugTools.Assert(parentNet.Height > net.Height); + } + + DebugTools.Assert(nets.Add(net)); + DebugTools.Assert(netIds.Add(net.Id)); + } + } + } + private static void RecursivelyEstimateNetworkDepth(PowerState state, Network network, List> groupedNetworks) { network.Height = -2; From 79e841056ba2b4eb7f32c6c46c3c0c7d547c0f53 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:27:48 +1200 Subject: [PATCH 83/84] Update engine to v224.0.0 (#28502) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index c89c529ba4..ff4548f108 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit c89c529ba4fa7516e2265f3c47549da35ab6c3d8 +Subproject commit ff4548f108a5b11e1a81cb7c01f7462400e4af2b From ba04f94e480f9aed5f72eec0953396945f5d5d88 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sat, 1 Jun 2024 23:21:07 -0700 Subject: [PATCH 84/84] Emergency Fix for Whitelist logic (#28510) fix issue Co-authored-by: plykiya --- Content.Shared/Materials/SharedMaterialStorageSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs index af815bdb2b..a27e0fb9cf 100644 --- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs +++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs @@ -123,7 +123,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem if (!CanTakeVolume(uid, volume, component)) return false; - if (component.MaterialWhiteList == null ? false : component.MaterialWhiteList.Contains(materialId)) + if (component.MaterialWhiteList == null ? false : !component.MaterialWhiteList.Contains(materialId)) return false; var amount = component.Storage.GetValueOrDefault(materialId);