Objectives (#2459)

* temp commit to save progress

* adds objectives

* refactors mind.addobjective a bit

* better names for my testobjectives which i'll remove later on anyways

* nullable errors

* some misc fixes

* no sorted or set, what was i thinking here?

* removes unused imports

* added commands

* fully implements stealcondition

* started uiwork

* moved prototypeicon to engine

* removes objective class & uiwork

* refactors ui to only update when opened
adds progresstexturerect

* adds some margin

* removes some testing code

* ignores objectiveprototypes on clientside

* fixes

* removes using statements for exp

* gets the job

* always show issuer

* locs & _

* giving commands some love

* Update Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterBar.cs

Co-authored-by: Exp <theexp111@gmail.com>

* makes commands use new thingy

* string interpolation

* good catch exp

* loc'd

* linq gone

* runtime

* moves function from engine

* oopsie

* Update Content.Server/Objectives/Conditions/StealCondition.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* makes messages directed

* base call & validation

* shuffle once

* No? Money down!

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Exp <theexp111@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
Paul Ritter
2020-11-22 08:38:07 +01:00
committed by GitHub
parent 392454b4cb
commit 6602c8c972
24 changed files with 724 additions and 27 deletions

View File

@@ -79,6 +79,7 @@ namespace Content.Client
prototypes.RegisterIgnore("gasReaction"); prototypes.RegisterIgnore("gasReaction");
prototypes.RegisterIgnore("seed"); // Seeds prototypes are server-only. prototypes.RegisterIgnore("seed"); // Seeds prototypes are server-only.
prototypes.RegisterIgnore("barSign"); prototypes.RegisterIgnore("barSign");
prototypes.RegisterIgnore("objective");
ClientContentIoC.Register(); ClientContentIoC.Register();

View File

@@ -1,26 +1,30 @@
#nullable enable
using System.Drawing;
using Content.Client.GameObjects.Components.Mobs; using Content.Client.GameObjects.Components.Mobs;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Shared.GameObjects.Components.Actor;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Players;
namespace Content.Client.GameObjects.Components.Actor namespace Content.Client.GameObjects.Components.Actor
{ {
[RegisterComponent] [RegisterComponent]
public sealed class CharacterInfoComponent : Component, ICharacterUI public sealed class CharacterInfoComponent : SharedCharacterInfoComponent, ICharacterUI
{ {
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
private CharacterInfoControl _control; private CharacterInfoControl _control = default!;
public override string Name => "CharacterInfo"; public Control Scene { get; private set; } = default!;
public Control Scene { get; private set; }
public UIPriority Priority => UIPriority.Info; public UIPriority Priority => UIPriority.Info;
public override void OnAdd() public override void OnAdd()
@@ -30,18 +34,29 @@ namespace Content.Client.GameObjects.Components.Actor
Scene = _control = new CharacterInfoControl(_resourceCache); Scene = _control = new CharacterInfoControl(_resourceCache);
} }
public override void Initialize() public void Opened()
{ {
base.Initialize(); SendNetworkMessage(new RequestCharacterInfoMessage());
}
if (Owner.TryGetComponent(out ISpriteComponent spriteComponent)) public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
if(session?.AttachedEntity != Owner) return;
switch (message)
{ {
_control.SpriteView.Sprite = spriteComponent; case CharacterInfoMessage characterInfoMessage:
} _control.UpdateUI(characterInfoMessage);
if (Owner.TryGetComponent(out ISpriteComponent? spriteComponent))
{
_control.SpriteView.Sprite = spriteComponent;
}
_control.NameLabel.Text = Owner.Name; _control.NameLabel.Text = Owner.Name;
// ReSharper disable once StringLiteralTypo break;
_control.SubText.Text = Loc.GetString("Professional Greyshirt"); }
} }
private sealed class CharacterInfoControl : VBoxContainer private sealed class CharacterInfoControl : VBoxContainer
@@ -50,8 +65,12 @@ namespace Content.Client.GameObjects.Components.Actor
public Label NameLabel { get; } public Label NameLabel { get; }
public Label SubText { get; } public Label SubText { get; }
public VBoxContainer ObjectivesContainer { get; }
public CharacterInfoControl(IResourceCache resourceCache) public CharacterInfoControl(IResourceCache resourceCache)
{ {
IoCManager.InjectDependencies(this);
AddChild(new HBoxContainer AddChild(new HBoxContainer
{ {
Children = Children =
@@ -66,7 +85,8 @@ namespace Content.Client.GameObjects.Components.Actor
(SubText = new Label (SubText = new Label
{ {
SizeFlagsVertical = SizeFlags.None, SizeFlagsVertical = SizeFlags.None,
StyleClasses = {StyleNano.StyleClassLabelSubText} StyleClasses = {StyleNano.StyleClassLabelSubText},
}) })
} }
} }
@@ -78,16 +98,65 @@ namespace Content.Client.GameObjects.Components.Actor
PlaceholderText = Loc.GetString("Health & status effects") PlaceholderText = Loc.GetString("Health & status effects")
}); });
AddChild(new Placeholder(resourceCache) AddChild(new Label
{ {
PlaceholderText = Loc.GetString("Objectives") Text = Loc.GetString("Objectives"),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
}); });
ObjectivesContainer = new VBoxContainer();
AddChild(ObjectivesContainer);
AddChild(new Placeholder(resourceCache) AddChild(new Placeholder(resourceCache)
{ {
PlaceholderText = Loc.GetString("Antagonist Roles") PlaceholderText = Loc.GetString("Antagonist Roles")
}); });
} }
public void UpdateUI(CharacterInfoMessage characterInfoMessage)
{
SubText.Text = characterInfoMessage.JobTitle;
ObjectivesContainer.RemoveAllChildren();
foreach (var (groupId, objectiveConditions) in characterInfoMessage.Objectives)
{
var vbox = new VBoxContainer
{
Modulate = Color.Gray
};
vbox.AddChild(new Label
{
Text = groupId,
Modulate = Color.LightSkyBlue
});
foreach (var objectiveCondition in objectiveConditions)
{
var hbox = new HBoxContainer();
hbox.AddChild(new ProgressTextureRect
{
Texture = objectiveCondition.SpriteSpecifier.Frame0(),
Progress = objectiveCondition.Progress,
SizeFlagsVertical = SizeFlags.ShrinkCenter
});
hbox.AddChild(new Control
{
CustomMinimumSize = (10,0)
});
hbox.AddChild(new VBoxContainer
{
Children =
{
new Label{Text = objectiveCondition.Title},
new Label{Text = objectiveCondition.Description}
}
}
);
vbox.AddChild(hbox);
}
ObjectivesContainer.AddChild(vbox);
}
}
} }
} }
} }

View File

@@ -116,6 +116,7 @@ namespace Content.Client.GameObjects.Components.Actor
public class CharacterWindow : SS14Window public class CharacterWindow : SS14Window
{ {
private readonly VBoxContainer _contentsVBox; private readonly VBoxContainer _contentsVBox;
private readonly List<ICharacterUI> _windowComponents;
public CharacterWindow(List<ICharacterUI> windowComponents) public CharacterWindow(List<ICharacterUI> windowComponents)
{ {
@@ -129,6 +130,17 @@ namespace Content.Client.GameObjects.Components.Actor
{ {
_contentsVBox.AddChild(element.Scene); _contentsVBox.AddChild(element.Scene);
} }
_windowComponents = windowComponents;
}
protected override void Opened()
{
base.Opened();
foreach (var windowComponent in _windowComponents)
{
windowComponent.Opened();
}
} }
} }
} }

View File

@@ -0,0 +1,22 @@
using System;
using Content.Client.GameObjects.EntitySystems.DoAfter;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Actor
{
public class ProgressTextureRect : TextureRect
{
public float Progress;
protected override void Draw(DrawingHandleScreen handle)
{
var dims = Texture != null ? GetDrawDimensions(Texture) : UIBox2.FromDimensions(Vector2.Zero, PixelSize);
dims.Top = Math.Max(dims.Bottom - dims.Bottom * Progress,0);
handle.DrawRect(dims, DoAfterHelpers.GetProgressColor(Progress));
base.Draw(handle);
}
}
}

View File

@@ -17,5 +17,10 @@ namespace Content.Client.GameObjects.Components.Mobs
/// The order it will appear in the character UI, higher is lower /// The order it will appear in the character UI, higher is lower
/// </summary> /// </summary>
UIPriority Priority { get; } UIPriority Priority { get; }
/// <summary>
/// Called when the CharacterUi was opened
/// </summary>
void Opened(){}
} }
} }

View File

@@ -1,4 +1,4 @@
#nullable enable #nullable enable
using System; using System;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Shaders; using Robust.Client.Graphics.Shaders;
@@ -105,16 +105,10 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
} }
color = new Color(1.0f, 0.0f, 0.0f, _flash ? 1.0f : 0.0f); color = new Color(1.0f, 0.0f, 0.0f, _flash ? 1.0f : 0.0f);
} }
else if (Ratio >= 1.0f)
{
color = new Color(0f, 1f, 0f);
}
else else
{ {
// lerp color = DoAfterHelpers.GetProgressColor(Ratio);
var hue = (5f / 18f) * Ratio;
color = Color.FromHsv((hue, 1f, 0.75f, 1f));
} }
handle.UseShader(_shader); handle.UseShader(_shader);
@@ -128,4 +122,18 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
handle.DrawRect(box, color); handle.DrawRect(box, color);
} }
} }
public static class DoAfterHelpers
{
public static Color GetProgressColor(float progress)
{
if (progress >= 1.0f)
{
return new Color(0f, 1f, 0f);
}
// lerp
var hue = (5f / 18f) * progress;
return Color.FromHsv((hue, 1f, 0.75f, 1f));
}
}
} }

View File

@@ -0,0 +1,58 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Mobs.Roles;
using Content.Shared.GameObjects.Components.Actor;
using Content.Shared.Objectives;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Players;
namespace Content.Server.GameObjects.Components.Actor
{
[RegisterComponent]
public class CharacterInfoComponent : SharedCharacterInfoComponent
{
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
switch (message)
{
case RequestCharacterInfoMessage _:
var conditions = new Dictionary<string, List<ConditionInfo>>();
var jobTitle = "No Profession";
if (Owner.TryGetComponent(out MindComponent? mindComponent))
{
var mind = mindComponent.Mind;
if (mind != null)
{
// getting conditions
foreach (var objective in mind.AllObjectives)
{
if (!conditions.ContainsKey(objective.Issuer))
conditions[objective.Issuer] = new List<ConditionInfo>();
foreach (var condition in objective.Conditions)
{
conditions[objective.Issuer].Add(new ConditionInfo(condition.GetTitle(),
condition.GetDescription(), condition.GetIcon(), condition.GetProgress(mindComponent.Mind)));
}
}
// getting jobtitle
foreach (var role in mind.AllRoles)
{
if (role.GetType() == typeof(Job))
{
jobTitle = role.Name;
break;
}
}
}
}
SendNetworkMessage(new CharacterInfoMessage(jobTitle, conditions));
break;
}
}
}
}

View File

@@ -0,0 +1,23 @@
using Robust.Server.GameObjects.Components.Container;
namespace Content.Server.GameObjects.Components.ContainerExt
{
public static class ContainerExt
{
public static int CountPrototypeOccurencesRecursive(this ContainerManagerComponent mgr, string prototypeId)
{
int total = 0;
foreach (var container in mgr.GetAllContainers())
{
foreach (var entity in container.ContainedEntities)
{
if (entity.Prototype?.ID == prototypeId) total++;
if(!entity.TryGetComponent<ContainerManagerComponent>(out var component)) continue;
total += component.CountPrototypeOccurencesRecursive(prototypeId);
}
}
return total;
}
}
}

View File

@@ -16,7 +16,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;

View File

@@ -10,7 +10,6 @@
"SubFloorHide", "SubFloorHide",
"LowWall", "LowWall",
"ReinforcedWall", "ReinforcedWall",
"CharacterInfo",
"InteractionOutline", "InteractionOutline",
"MeleeWeaponArcAnimation", "MeleeWeaponArcAnimation",
"AnimationsTest", "AnimationsTest",

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Mobs.Roles; using Content.Server.Mobs.Roles;
using Content.Server.Objectives;
using Content.Server.Players; using Content.Server.Players;
using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
@@ -27,6 +28,8 @@ namespace Content.Server.Mobs
{ {
private readonly ISet<Role> _roles = new HashSet<Role>(); private readonly ISet<Role> _roles = new HashSet<Role>();
private readonly List<ObjectivePrototype> _objectives = new List<ObjectivePrototype>();
/// <summary> /// <summary>
/// Creates the new mind attached to a specific player session. /// Creates the new mind attached to a specific player session.
/// </summary> /// </summary>
@@ -74,6 +77,12 @@ namespace Content.Server.Mobs
[ViewVariables] [ViewVariables]
public IEnumerable<Role> AllRoles => _roles; public IEnumerable<Role> AllRoles => _roles;
/// <summary>
/// An enumerable over all the objectives this mind has.
/// </summary>
[ViewVariables]
public IEnumerable<ObjectivePrototype> AllObjectives => _objectives;
/// <summary> /// <summary>
/// The session of the player owning this mind. /// The session of the player owning this mind.
/// Can be null, in which case the player is currently not logged in. /// Can be null, in which case the player is currently not logged in.
@@ -144,6 +153,32 @@ namespace Content.Server.Mobs
return _roles.Any(role => role.GetType() == t); return _roles.Any(role => role.GetType() == t);
} }
/// <summary>
/// Adds an objective to this mind.
/// </summary>
public bool TryAddObjective(ObjectivePrototype objective)
{
if (!objective.CanBeAssigned(this))
return false;
_objectives.Add(objective);
return true;
}
/// <summary>
/// Removes an objective to this mind.
/// </summary>
/// <returns>Returns true if the removal succeeded.</returns>
public bool TryRemoveObjective(int index)
{
if (_objectives.Count >= index) return false;
var objective = _objectives[index];
_objectives.Remove(objective);
return true;
}
/// <summary> /// <summary>
/// Transfer this mind's control over to a new entity. /// Transfer this mind's control over to a new entity.
/// </summary> /// </summary>

View File

@@ -0,0 +1,139 @@
#nullable enable
using System.Linq;
using Content.Server.Administration;
using Content.Server.Players;
using Content.Shared.Administration;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.Objectives
{
[AdminCommand(AdminFlags.Admin)]
public class AddObjectiveCommand : IClientCommand
{
public string Command => "addobjective";
public string Description => "Adds an objective to the player's mind.";
public string Help => "addobjective <username> <objectiveID>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (args.Length != 2)
{
shell.SendText(player, "Expected exactly 2 arguments.");
return;
}
var mgr = IoCManager.Resolve<IPlayerManager>();
if (!mgr.TryGetPlayerDataByUsername(args[0], out var data))
{
shell.SendText(player, "Can't find the playerdata.");
return;
}
var mind = data.ContentData()?.Mind;
if (mind == null)
{
shell.SendText(player, "Can't find the mind.");
return;
}
if (!IoCManager.Resolve<IPrototypeManager>()
.TryIndex<ObjectivePrototype>(args[1], out var objectivePrototype))
{
shell.SendText(player, $"Can't find matching ObjectivePrototype {objectivePrototype}");
return;
}
if (!mind.TryAddObjective(objectivePrototype))
{
shell.SendText(player, "Objective requirements dont allow that objective to be added.");
}
}
}
[AdminCommand(AdminFlags.Admin)]
public class ListObjectivesCommand : IClientCommand
{
public string Command => "lsobjectives";
public string Description => "Lists all objectives in a players mind.";
public string Help => "lsobjectives [<username>]";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
IPlayerData? data;
if (args.Length == 0 && player != null)
{
data = player.Data;
}
else if (player == null || !IoCManager.Resolve<IPlayerManager>().TryGetPlayerDataByUsername(args[0], out data))
{
shell.SendText(player, "Can't find the playerdata.");
return;
}
var mind = data.ContentData()?.Mind;
if (mind == null)
{
shell.SendText(player, "Can't find the mind.");
return;
}
shell.SendText(player, $"Objectives for player {data.UserId}:");
var objectives = mind.AllObjectives.ToList();
if (objectives.Count == 0)
{
shell.SendText(player, "None.");
}
for (var i = 0; i < objectives.Count; i++)
{
shell.SendText(player, $"- [{i}] {objectives[i]}");
}
}
}
[AdminCommand(AdminFlags.Admin)]
public class RemoveObjectiveCommand : IClientCommand
{
public string Command => "rmobjective";
public string Description => "Removes an objective from the player's mind.";
public string Help => "rmobjective <username> <index>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (args.Length != 2)
{
shell.SendText(player, "Expected exactly 2 arguments.");
return;
}
var mgr = IoCManager.Resolve<IPlayerManager>();
if (mgr.TryGetPlayerDataByUsername(args[0], out var data))
{
var mind = data.ContentData()?.Mind;
if (mind == null)
{
shell.SendText(player, "Can't find the mind.");
return;
}
if (int.TryParse(args[1], out var i))
{
shell.SendText(player,
mind.TryRemoveObjective(i)
? "Objective successfully removed!"
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
}
else
{
shell.SendText(player, $"Invalid index {args[1]}!");
}
}
else
{
shell.SendText(player, "Can't find the playerdata.");
}
}
}
}

View File

@@ -0,0 +1,57 @@
#nullable enable
using Content.Server.GameObjects.Components.ContainerExt;
using Content.Server.Mobs;
using Content.Server.Objectives.Interfaces;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Conditions
{
public class StealCondition : IObjectiveCondition
{
public string PrototypeId { get; private set; } = default!;
public int Amount { get; private set; }
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.PrototypeId, "prototype", "");
serializer.DataField(this, x => x.Amount, "amount", 1);
if (Amount < 1)
{
Logger.Error("StealCondition has an amount less than 1 ({0})", Amount);
}
}
private string PrototypeName =>
IoCManager.Resolve<IPrototypeManager>().TryIndex<EntityPrototype>(PrototypeId, out var prototype)
? prototype.Name
: "[CANNOT FIND NAME]";
public string GetTitle() => Loc.GetString("Steal {0} {1}", Amount > 1 ? $"{Amount}x" : "", Loc.GetString(PrototypeName));
public string GetDescription() => Loc.GetString("We need you to steal {0}. Don't get caught.", Loc.GetString(PrototypeName));
public SpriteSpecifier GetIcon()
{
return new SpriteSpecifier.EntityPrototype(PrototypeId);
}
public float GetProgress(Mind? mind)
{
if (mind?.OwnedEntity == null) return 0f;
if (!mind.OwnedEntity.TryGetComponent<ContainerManagerComponent>(out var containerManagerComponent)) return 0f;
float count = containerManagerComponent.CountPrototypeOccurencesRecursive(PrototypeId);
return count/Amount;
}
public float GetDifficulty() => 1f;
}
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Mobs;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Utility;
namespace Content.Server.Objectives.Interfaces
{
public interface IObjectiveCondition : IExposeData
{
/// <summary>
/// Returns the title of the condition.
/// </summary>
string GetTitle();
/// <summary>
/// Returns the description of the condition.
/// </summary>
string GetDescription();
/// <summary>
/// Returns a SpriteSpecifier to be used as an icon for the condition.
/// </summary>
SpriteSpecifier GetIcon();
/// <summary>
/// Returns the current progress of the condition in %.
/// </summary>
/// <returns>Current progress in %.</returns>
float GetProgress(Mind mind);
/// <summary>
/// Returns a difficulty of the condition.
/// </summary>
float GetDifficulty();
}
}

View File

@@ -0,0 +1,15 @@
using Content.Server.Mobs;
using Robust.Shared.Interfaces.Serialization;
namespace Content.Server.Objectives.Interfaces
{
public interface IObjectiveRequirement : IExposeData
{
/// <summary>
/// Checks whether or not the entity & its surroundings are valid to be given the objective.
/// </summary>
/// <returns>Returns true if objective can be given.</returns>
bool CanBeAssigned(Mind mind);
}
}

View File

@@ -0,0 +1,17 @@
using Content.Server.Mobs;
namespace Content.Server.Objectives.Interfaces
{
public interface IObjectivesManager
{
/// <summary>
/// Returns all objectives the provided mind is valid for.
/// </summary>
ObjectivePrototype[] GetAllPossibleObjectives(Mind mind);
/// <summary>
/// Returns a randomly picked (no pop) collection of objectives the provided mind is valid for.
/// </summary>
ObjectivePrototype[] GetRandomObjectives(Mind mind, float maxDifficulty = 3f);
}
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Mobs;
using Content.Server.Objectives.Interfaces;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Content.Server.Objectives
{
[Prototype("objective")]
public class ObjectivePrototype : IPrototype, IIndexedPrototype
{
[ViewVariables]
public string ID { get; private set; }
[ViewVariables(VVAccess.ReadWrite)]
public string Issuer { get; private set; }
[ViewVariables]
public float Probability { get; private set; }
[ViewVariables]
public IReadOnlyList<IObjectiveCondition> Conditions => _conditions;
[ViewVariables]
public IReadOnlyList<IObjectiveRequirement> Requirements => _requirements;
[ViewVariables]
public float Difficulty => _difficultyOverride ?? _conditions.Sum(c => c.GetDifficulty());
private List<IObjectiveCondition> _conditions = new List<IObjectiveCondition>();
private List<IObjectiveRequirement> _requirements = new List<IObjectiveRequirement>();
[ViewVariables(VVAccess.ReadWrite)]
private float? _difficultyOverride = null;
public bool CanBeAssigned(Mind mind)
{
foreach (var requirement in _requirements)
{
if (!requirement.CanBeAssigned(mind)) return false;
}
return true;
}
public void LoadFrom(YamlMappingNode mapping)
{
var ser = YamlObjectSerializer.NewReader(mapping);
ser.DataField(this, x => x.ID, "id", string.Empty);
ser.DataField(this, x => x.Issuer, "issuer", "Unknown");
ser.DataField(this, x => x.Probability, "prob", 0.3f);
ser.DataField(this, x => x._conditions, "conditions", new List<IObjectiveCondition>());
ser.DataField(this, x => x._requirements, "requirements", new List<IObjectiveRequirement>());
ser.DataField(this, x => x._difficultyOverride, "difficultyOverride", null);
}
}
}

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Mobs;
using Content.Server.Objectives.Interfaces;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Objectives
{
public class ObjectivesManager : IObjectivesManager
{
[Dependency] private IPrototypeManager _prototypeManager = default!;
[Dependency] private IRobustRandom _random = default!;
public ObjectivePrototype[] GetAllPossibleObjectives(Mind mind)
{
return _prototypeManager.EnumeratePrototypes<ObjectivePrototype>().Where(objectivePrototype => objectivePrototype.CanBeAssigned(mind)).ToArray();
}
public ObjectivePrototype[] GetRandomObjectives(Mind mind, float maxDifficulty = 3)
{
var objectives = GetAllPossibleObjectives(mind);
//to prevent endless loops
if(objectives.Length == 0 || objectives.Sum(o => o.Difficulty) == 0f) return objectives;
var result = new List<ObjectivePrototype>();
var currentDifficulty = 0f;
_random.Shuffle(objectives);
while (currentDifficulty < maxDifficulty)
{
foreach (var objective in objectives)
{
if (!_random.Prob(objective.Probability)) continue;
result.Add(objective);
currentDifficulty += objective.Difficulty;
if (currentDifficulty >= maxDifficulty) break;
}
}
if (currentDifficulty > maxDifficulty) //will almost always happen
{
result.Pop();
}
return result.ToArray();
}
}
}

View File

@@ -0,0 +1,17 @@
using Content.Server.Mobs;
using Content.Server.Mobs.Roles.Suspicion;
using Content.Server.Objectives.Interfaces;
using Robust.Shared.Serialization;
namespace Content.Server.Objectives.Requirements
{
public class SuspicionTraitorRequirement : IObjectiveRequirement
{
public void ExposeData(ObjectSerializer serializer){}
public bool CanBeAssigned(Mind mind)
{
return mind.HasRole<SuspicionTraitorRole>();
}
}
}

View File

@@ -14,6 +14,8 @@ using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking; using Content.Server.Interfaces.GameTicking;
using Content.Server.Interfaces.PDA; using Content.Server.Interfaces.PDA;
using Content.Server.Objectives;
using Content.Server.Objectives.Interfaces;
using Content.Server.PDA; using Content.Server.PDA;
using Content.Server.Preferences; using Content.Server.Preferences;
using Content.Server.Sandbox; using Content.Server.Sandbox;
@@ -49,6 +51,7 @@ namespace Content.Server
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>(); IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();
IoCManager.Register<IAccentManager, AccentManager>(); IoCManager.Register<IAccentManager, AccentManager>();
IoCManager.Register<IConnectionManager, ConnectionManager>(); IoCManager.Register<IConnectionManager, ConnectionManager>();
IoCManager.Register<IObjectivesManager, ObjectivesManager>();
IoCManager.Register<IAdminManager, AdminManager>(); IoCManager.Register<IAdminManager, AdminManager>();
IoCManager.Register<IDeviceNetwork, DeviceNetwork>(); IoCManager.Register<IDeviceNetwork, DeviceNetwork>();
IoCManager.Register<EuiManager, EuiManager>(); IoCManager.Register<EuiManager, EuiManager>();

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using Content.Shared.Objectives;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Actor
{
public class SharedCharacterInfoComponent : Component
{
public override string Name => "CharacterInfo";
public override uint? NetID => ContentNetIDs.CHARACTERINFO;
[Serializable, NetSerializable]
protected class RequestCharacterInfoMessage : ComponentMessage
{
public RequestCharacterInfoMessage()
{
Directed = true;
}
}
[Serializable, NetSerializable]
protected class CharacterInfoMessage : ComponentMessage
{
public readonly Dictionary<string, List<ConditionInfo>> Objectives;
public readonly string JobTitle;
public CharacterInfoMessage(string jobTitle, Dictionary<string, List<ConditionInfo>> objectives)
{
Directed = true;
JobTitle = jobTitle;
Objectives = objectives;
}
}
}
}

View File

@@ -84,6 +84,7 @@
public const uint PULLABLE = 1078; public const uint PULLABLE = 1078;
public const uint GAS_TANK = 1079; public const uint GAS_TANK = 1079;
public const uint SINGULARITY = 1080; public const uint SINGULARITY = 1080;
public const uint CHARACTERINFO = 1081;
// Net IDs for integration tests. // Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001; public const uint PREDICTION_TEST = 10001;

View File

@@ -0,0 +1,23 @@
using System;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Objectives
{
[Serializable, NetSerializable]
public class ConditionInfo
{
public string Title { get; }
public string Description { get; }
public SpriteSpecifier SpriteSpecifier { get; }
public float Progress { get; }
public ConditionInfo(string title, string description, SpriteSpecifier spriteSpecifier, float progress)
{
Title = title;
Description = description;
SpriteSpecifier = spriteSpecifier;
Progress = progress;
}
}
}

View File

@@ -0,0 +1,9 @@
- type: objective
id: SoapDeluxeStealObjective
prob: 0.3
issuer: The Syndicate
requirements:
- !type:SuspicionTraitorRequirement {}
conditions:
- !type:StealCondition
prototype: SoapDeluxe