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

@@ -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>();
}
}
}