mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-18 14:07:53 +03:00
## Mirror of PR #26421: [Nerf ninja research stealing](https://github.com/space-wizards/space-station-14/pull/26421) from <img src="https://avatars.githubusercontent.com/u/10567778?v=4" alt="space-wizards" width="22"/> [space-wizards](https://github.com/space-wizards)/[space-station-14](https://github.com/space-wizards/space-station-14) ###### `3b9c5d43ec97a0693ced0118fcfcb8467aa5595e` PR opened by <img src="https://avatars.githubusercontent.com/u/98561806?v=4" width="16"/><a href="https://github.com/EmoGarbage404"> EmoGarbage404</a> at 2024-03-25 01:44:21 UTC - merged at 2024-03-26 00:52:27 UTC --- PR changed 4 files with 75 additions and 8 deletions. The PR had the following labels: - Status: Needs Review --- <details open="true"><summary><h1>Original Body</h1></summary> > <!-- Please read these guidelines before opening your PR: https://docs.spacestation14.io/en/getting-started/pr-guideline --> > <!-- The text between the arrows are comments - they will not be visible on your PR. --> > > ## About the PR > <!-- What did you change in this PR? --> > Changes ninja gloves to steal a flat rate of techs instead of wiping all of them. > Additionally adjusts the objective to require 1 to 2 steals to reach the target amount. > > ## Why / Balance > <!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. --> > Ninja's stealing research was previously an extremely trivial and annoying objective. For them, you can pretty easily break into the server room and wait out the 20 second doafter. It's not super hard and you're not exactly shooting off warning signs. At this very low cost, science loses literally an hour+ of progress. > > This nerf changes it to only remove 4-8 random technologies, or only about 20k to 40k points worth. This is still pretty crippling but not awful. On top of that, they need to do it twice to hit the target amounts of 9 to 12 techs stolen. This provides counterplay by actually giving science a chance to see that all their research has vanished before the ninja is already gone. It also makes it significantly harder for ninja to obliterate everything science has done. > > ## Media > <!-- > PRs which make ingame changes (adding clothing, items, new features, etc) are required to have media attached that showcase the changes. > Small fixes/refactors are exempt. > Any media may be used in SS14 progress reports, with clear credit given. > > If you're unsure whether your PR will require media, ask a maintainer. > > Check the box below to confirm that you have in fact seen this (put an X in the brackets, like [X]): > --> > > - [x] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase > > ## Breaking changes > <!-- > List any breaking changes, including namespace, public class/method/field changes, prototype renames; and provide instructions for fixing them. This will be pasted in #codebase-changes. > --> > > **Changelog** > <!-- > Make players aware of new features and changes that could affect how they play the game by adding a Changelog entry. Please read the Changelog guidelines located at: https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog > --> > > 🆑 > - tweak: Ninjas no longer wipe all technologies when using their gloves on a research server. > </details> Co-authored-by: SimpleStation14 <Unknown>
284 lines
10 KiB
C#
284 lines
10 KiB
C#
using System.Linq;
|
|
using Content.Shared.Research.Components;
|
|
using Content.Shared.Research.Prototypes;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Shared.Research.Systems;
|
|
|
|
public abstract class SharedResearchSystem : EntitySystem
|
|
{
|
|
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
|
[Dependency] private readonly IRobustRandom _random = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<TechnologyDatabaseComponent, MapInitEvent>(OnMapInit);
|
|
}
|
|
|
|
private void OnMapInit(EntityUid uid, TechnologyDatabaseComponent component, MapInitEvent args)
|
|
{
|
|
UpdateTechnologyCards(uid, component);
|
|
}
|
|
|
|
public void UpdateTechnologyCards(EntityUid uid, TechnologyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return;
|
|
|
|
var availableTechnology = GetAvailableTechnologies(uid, component);
|
|
_random.Shuffle(availableTechnology);
|
|
|
|
component.CurrentTechnologyCards.Clear();
|
|
foreach (var discipline in component.SupportedDisciplines)
|
|
{
|
|
var selected = availableTechnology.FirstOrDefault(p => p.Discipline == discipline);
|
|
if (selected == null)
|
|
continue;
|
|
|
|
component.CurrentTechnologyCards.Add(selected.ID);
|
|
}
|
|
Dirty(uid, component);
|
|
}
|
|
|
|
public List<TechnologyPrototype> GetAvailableTechnologies(EntityUid uid, TechnologyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component, false))
|
|
return new List<TechnologyPrototype>();
|
|
|
|
var availableTechnologies = new List<TechnologyPrototype>();
|
|
var disciplineTiers = GetDisciplineTiers(component);
|
|
foreach (var tech in PrototypeManager.EnumeratePrototypes<TechnologyPrototype>())
|
|
{
|
|
if (IsTechnologyAvailable(component, tech, disciplineTiers))
|
|
availableTechnologies.Add(tech);
|
|
}
|
|
|
|
return availableTechnologies;
|
|
}
|
|
|
|
public bool IsTechnologyAvailable(TechnologyDatabaseComponent component, TechnologyPrototype tech, Dictionary<string, int>? disciplineTiers = null)
|
|
{
|
|
disciplineTiers ??= GetDisciplineTiers(component);
|
|
|
|
if (tech.Hidden)
|
|
return false;
|
|
|
|
if (!component.SupportedDisciplines.Contains(tech.Discipline))
|
|
return false;
|
|
|
|
if (tech.Tier > disciplineTiers[tech.Discipline])
|
|
return false;
|
|
|
|
if (component.UnlockedTechnologies.Contains(tech.ID))
|
|
return false;
|
|
|
|
foreach (var prereq in tech.TechnologyPrerequisites)
|
|
{
|
|
if (!component.UnlockedTechnologies.Contains(prereq))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public Dictionary<string, int> GetDisciplineTiers(TechnologyDatabaseComponent component)
|
|
{
|
|
var tiers = new Dictionary<string, int>();
|
|
foreach (var discipline in component.SupportedDisciplines)
|
|
{
|
|
tiers.Add(discipline, GetHighestDisciplineTier(component, discipline));
|
|
}
|
|
|
|
return tiers;
|
|
}
|
|
|
|
public int GetHighestDisciplineTier(TechnologyDatabaseComponent component, string disciplineId)
|
|
{
|
|
return GetHighestDisciplineTier(component, PrototypeManager.Index<TechDisciplinePrototype>(disciplineId));
|
|
}
|
|
|
|
public int GetHighestDisciplineTier(TechnologyDatabaseComponent component, TechDisciplinePrototype techDiscipline)
|
|
{
|
|
var allTech = PrototypeManager.EnumeratePrototypes<TechnologyPrototype>()
|
|
.Where(p => p.Discipline == techDiscipline.ID && !p.Hidden).ToList();
|
|
var allUnlocked = new List<TechnologyPrototype>();
|
|
foreach (var recipe in component.UnlockedTechnologies)
|
|
{
|
|
var proto = PrototypeManager.Index<TechnologyPrototype>(recipe);
|
|
if (proto.Discipline != techDiscipline.ID)
|
|
continue;
|
|
allUnlocked.Add(proto);
|
|
}
|
|
|
|
var highestTier = techDiscipline.TierPrerequisites.Keys.Max();
|
|
var tier = 2; //tier 1 is always given
|
|
|
|
// todo this might break if you have hidden technologies. i'm not sure
|
|
|
|
while (tier <= highestTier)
|
|
{
|
|
// we need to get the tech for the tier 1 below because that's
|
|
// what the percentage in TierPrerequisites is referring to.
|
|
var unlockedTierTech = allUnlocked.Where(p => p.Tier == tier - 1).ToList();
|
|
var allTierTech = allTech.Where(p => p.Discipline == techDiscipline.ID && p.Tier == tier - 1).ToList();
|
|
|
|
if (allTierTech.Count == 0)
|
|
break;
|
|
|
|
var percent = (float) unlockedTierTech.Count / allTierTech.Count;
|
|
if (percent < techDiscipline.TierPrerequisites[tier])
|
|
break;
|
|
|
|
if (tier >= techDiscipline.LockoutTier &&
|
|
component.MainDiscipline != null &&
|
|
techDiscipline.ID != component.MainDiscipline)
|
|
break;
|
|
tier++;
|
|
}
|
|
|
|
return tier - 1;
|
|
}
|
|
|
|
public FormattedMessage GetTechnologyDescription(
|
|
TechnologyPrototype technology,
|
|
bool includeCost = true,
|
|
bool includeTier = true,
|
|
bool includePrereqs = false,
|
|
TechDisciplinePrototype? disciplinePrototype = null)
|
|
{
|
|
var description = new FormattedMessage();
|
|
if (includeTier)
|
|
{
|
|
disciplinePrototype ??= PrototypeManager.Index(technology.Discipline);
|
|
description.AddMarkup(Loc.GetString("research-console-tier-discipline-info",
|
|
("tier", technology.Tier), ("color", disciplinePrototype.Color), ("discipline", Loc.GetString(disciplinePrototype.Name))));
|
|
description.PushNewline();
|
|
}
|
|
|
|
if (includeCost)
|
|
{
|
|
description.AddMarkup(Loc.GetString("research-console-cost", ("amount", technology.Cost)));
|
|
description.PushNewline();
|
|
}
|
|
|
|
if (includePrereqs && technology.TechnologyPrerequisites.Any())
|
|
{
|
|
description.AddMarkup(Loc.GetString("research-console-prereqs-list-start"));
|
|
foreach (var recipe in technology.TechnologyPrerequisites)
|
|
{
|
|
var techProto = PrototypeManager.Index(recipe);
|
|
description.PushNewline();
|
|
description.AddMarkup(Loc.GetString("research-console-prereqs-list-entry",
|
|
("text", Loc.GetString(techProto.Name))));
|
|
}
|
|
description.PushNewline();
|
|
}
|
|
|
|
description.AddMarkup(Loc.GetString("research-console-unlocks-list-start"));
|
|
foreach (var recipe in technology.RecipeUnlocks)
|
|
{
|
|
var recipeProto = PrototypeManager.Index(recipe);
|
|
description.PushNewline();
|
|
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry",
|
|
("name",recipeProto.Name)));
|
|
}
|
|
foreach (var generic in technology.GenericUnlocks)
|
|
{
|
|
description.PushNewline();
|
|
description.AddMarkup(Loc.GetString("research-console-unlocks-list-entry-generic",
|
|
("text", Loc.GetString(generic.UnlockDescription))));
|
|
}
|
|
|
|
return description;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether a technology is unlocked on this database or not.
|
|
/// </summary>
|
|
/// <returns>Whether it is unlocked or not</returns>
|
|
public bool IsTechnologyUnlocked(EntityUid uid, TechnologyPrototype technology, TechnologyDatabaseComponent? component = null)
|
|
{
|
|
return Resolve(uid, ref component) && IsTechnologyUnlocked(uid, technology.ID, component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether a technology is unlocked on this database or not.
|
|
/// </summary>
|
|
/// <returns>Whether it is unlocked or not</returns>
|
|
public bool IsTechnologyUnlocked(EntityUid uid, string technologyId, TechnologyDatabaseComponent? component = null)
|
|
{
|
|
return Resolve(uid, ref component, false) && component.UnlockedTechnologies.Contains(technologyId);
|
|
}
|
|
|
|
public void TrySetMainDiscipline(TechnologyPrototype prototype, EntityUid uid, TechnologyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return;
|
|
|
|
var discipline = PrototypeManager.Index(prototype.Discipline);
|
|
if (prototype.Tier < discipline.LockoutTier)
|
|
return;
|
|
component.MainDiscipline = prototype.Discipline;
|
|
Dirty(uid, component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a technology and its recipes from a technology database.
|
|
/// </summary>
|
|
public bool TryRemoveTechnology(Entity<TechnologyDatabaseComponent> entity, ProtoId<TechnologyPrototype> tech)
|
|
{
|
|
return TryRemoveTechnology(entity, PrototypeManager.Index(tech));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a technology and its recipes from a technology database.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public bool TryRemoveTechnology(Entity<TechnologyDatabaseComponent> entity, TechnologyPrototype tech)
|
|
{
|
|
if (!entity.Comp.UnlockedTechnologies.Remove(tech.ID))
|
|
return false;
|
|
|
|
// check to make sure we didn't somehow get the recipe from another tech.
|
|
// unlikely, but whatever
|
|
var recipes = tech.RecipeUnlocks;
|
|
foreach (var recipe in recipes)
|
|
{
|
|
var hasTechElsewhere = false;
|
|
foreach (var unlockedTech in entity.Comp.UnlockedTechnologies)
|
|
{
|
|
var unlockedTechProto = PrototypeManager.Index<TechnologyPrototype>(unlockedTech);
|
|
|
|
if (!unlockedTechProto.RecipeUnlocks.Contains(recipe))
|
|
continue;
|
|
hasTechElsewhere = true;
|
|
break;
|
|
}
|
|
|
|
if (!hasTechElsewhere)
|
|
entity.Comp.UnlockedRecipes.Remove(recipe);
|
|
}
|
|
Dirty(entity, entity.Comp);
|
|
UpdateTechnologyCards(entity, entity);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all unlocked technologies from the database.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public void ClearTechs(EntityUid uid, TechnologyDatabaseComponent? comp = null)
|
|
{
|
|
if (!Resolve(uid, ref comp) || comp.UnlockedTechnologies.Count == 0)
|
|
return;
|
|
|
|
comp.UnlockedTechnologies.Clear();
|
|
Dirty(uid, comp);
|
|
}
|
|
}
|