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:
@@ -79,6 +79,7 @@ namespace Content.Client
|
||||
prototypes.RegisterIgnore("gasReaction");
|
||||
prototypes.RegisterIgnore("seed"); // Seeds prototypes are server-only.
|
||||
prototypes.RegisterIgnore("barSign");
|
||||
prototypes.RegisterIgnore("objective");
|
||||
|
||||
ClientContentIoC.Register();
|
||||
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
#nullable enable
|
||||
using System.Drawing;
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Shared.GameObjects.Components.Actor;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Actor
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class CharacterInfoComponent : Component, ICharacterUI
|
||||
public sealed class CharacterInfoComponent : SharedCharacterInfoComponent, ICharacterUI
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private CharacterInfoControl _control;
|
||||
private CharacterInfoControl _control = default!;
|
||||
|
||||
public override string Name => "CharacterInfo";
|
||||
|
||||
public Control Scene { get; private set; }
|
||||
public Control Scene { get; private set; } = default!;
|
||||
public UIPriority Priority => UIPriority.Info;
|
||||
|
||||
public override void OnAdd()
|
||||
@@ -30,18 +34,29 @@ namespace Content.Client.GameObjects.Components.Actor
|
||||
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;
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
_control.SubText.Text = Loc.GetString("Professional Greyshirt");
|
||||
_control.NameLabel.Text = Owner.Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CharacterInfoControl : VBoxContainer
|
||||
@@ -50,8 +65,12 @@ namespace Content.Client.GameObjects.Components.Actor
|
||||
public Label NameLabel { get; }
|
||||
public Label SubText { get; }
|
||||
|
||||
public VBoxContainer ObjectivesContainer { get; }
|
||||
|
||||
public CharacterInfoControl(IResourceCache resourceCache)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
@@ -66,7 +85,8 @@ namespace Content.Client.GameObjects.Components.Actor
|
||||
(SubText = new Label
|
||||
{
|
||||
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")
|
||||
});
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ namespace Content.Client.GameObjects.Components.Actor
|
||||
public class CharacterWindow : SS14Window
|
||||
{
|
||||
private readonly VBoxContainer _contentsVBox;
|
||||
private readonly List<ICharacterUI> _windowComponents;
|
||||
|
||||
public CharacterWindow(List<ICharacterUI> windowComponents)
|
||||
{
|
||||
@@ -129,6 +130,17 @@ namespace Content.Client.GameObjects.Components.Actor
|
||||
{
|
||||
_contentsVBox.AddChild(element.Scene);
|
||||
}
|
||||
|
||||
_windowComponents = windowComponents;
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
{
|
||||
base.Opened();
|
||||
foreach (var windowComponent in _windowComponents)
|
||||
{
|
||||
windowComponent.Opened();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,10 @@ namespace Content.Client.GameObjects.Components.Mobs
|
||||
/// The order it will appear in the character UI, higher is lower
|
||||
/// </summary>
|
||||
UIPriority Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the CharacterUi was opened
|
||||
/// </summary>
|
||||
void Opened(){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
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);
|
||||
}
|
||||
else if (Ratio >= 1.0f)
|
||||
{
|
||||
color = new Color(0f, 1f, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// lerp
|
||||
var hue = (5f / 18f) * Ratio;
|
||||
color = Color.FromHsv((hue, 1f, 0.75f, 1f));
|
||||
color = DoAfterHelpers.GetProgressColor(Ratio);
|
||||
}
|
||||
|
||||
handle.UseShader(_shader);
|
||||
@@ -128,4 +122,18 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timers;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"SubFloorHide",
|
||||
"LowWall",
|
||||
"ReinforcedWall",
|
||||
"CharacterInfo",
|
||||
"InteractionOutline",
|
||||
"MeleeWeaponArcAnimation",
|
||||
"AnimationsTest",
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.Mobs.Roles;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Players;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
@@ -27,6 +28,8 @@ namespace Content.Server.Mobs
|
||||
{
|
||||
private readonly ISet<Role> _roles = new HashSet<Role>();
|
||||
|
||||
private readonly List<ObjectivePrototype> _objectives = new List<ObjectivePrototype>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the new mind attached to a specific player session.
|
||||
/// </summary>
|
||||
@@ -74,6 +77,12 @@ namespace Content.Server.Mobs
|
||||
[ViewVariables]
|
||||
public IEnumerable<Role> AllRoles => _roles;
|
||||
|
||||
/// <summary>
|
||||
/// An enumerable over all the objectives this mind has.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IEnumerable<ObjectivePrototype> AllObjectives => _objectives;
|
||||
|
||||
/// <summary>
|
||||
/// The session of the player owning this mind.
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Transfer this mind's control over to a new entity.
|
||||
/// </summary>
|
||||
|
||||
139
Content.Server/Objectives/Commands.cs
Normal file
139
Content.Server/Objectives/Commands.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Content.Server/Objectives/Conditions/StealCondition.cs
Normal file
57
Content.Server/Objectives/Conditions/StealCondition.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
35
Content.Server/Objectives/Interfaces/IObjectiveCondition.cs
Normal file
35
Content.Server/Objectives/Interfaces/IObjectiveCondition.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
17
Content.Server/Objectives/Interfaces/IObjectivesManager.cs
Normal file
17
Content.Server/Objectives/Interfaces/IObjectivesManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
60
Content.Server/Objectives/ObjectivePrototype.cs
Normal file
60
Content.Server/Objectives/ObjectivePrototype.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Content.Server/Objectives/ObjectivesManager.cs
Normal file
53
Content.Server/Objectives/ObjectivesManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.Chat;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Server.Interfaces.PDA;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.Preferences;
|
||||
using Content.Server.Sandbox;
|
||||
@@ -49,6 +51,7 @@ namespace Content.Server
|
||||
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();
|
||||
IoCManager.Register<IAccentManager, AccentManager>();
|
||||
IoCManager.Register<IConnectionManager, ConnectionManager>();
|
||||
IoCManager.Register<IObjectivesManager, ObjectivesManager>();
|
||||
IoCManager.Register<IAdminManager, AdminManager>();
|
||||
IoCManager.Register<IDeviceNetwork, DeviceNetwork>();
|
||||
IoCManager.Register<EuiManager, EuiManager>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@
|
||||
public const uint PULLABLE = 1078;
|
||||
public const uint GAS_TANK = 1079;
|
||||
public const uint SINGULARITY = 1080;
|
||||
public const uint CHARACTERINFO = 1081;
|
||||
|
||||
// Net IDs for integration tests.
|
||||
public const uint PREDICTION_TEST = 10001;
|
||||
|
||||
23
Content.Shared/Objectives/ConditionInfo.cs
Normal file
23
Content.Shared/Objectives/ConditionInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Resources/Prototypes/Objectives/traitorObjectives.yml
Normal file
9
Resources/Prototypes/Objectives/traitorObjectives.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
- type: objective
|
||||
id: SoapDeluxeStealObjective
|
||||
prob: 0.3
|
||||
issuer: The Syndicate
|
||||
requirements:
|
||||
- !type:SuspicionTraitorRequirement {}
|
||||
conditions:
|
||||
- !type:StealCondition
|
||||
prototype: SoapDeluxe
|
||||
Reference in New Issue
Block a user