Skip to content

Commit

Permalink
Foreigner Traits (Simple-Station#525)
Browse files Browse the repository at this point in the history
# Description
Adds two negative traits, foreigner and its light version. Both of them
make you unable to speak galactic common and give you a translator that
lets you speak/understand GC, and also requires you to know one of the
non-GC languages your entity normally speaks in order to work. The
translator starts with a high-capacity power cell to compensate for the
usual scarcity of publicly accessible chargers on most stations.

The light version of the trait only makes you unable to speak GC. The
heavy version also makes you unable to understand others when they speak
it.

This PR also makes you able to wear translator in the neck slot for
convenience and transparency, and rewamps the examination menu of
translators to provide more useful info.

One little known issue (as seen in the screenshots below) is that, since
the system chooses the first language in the list of languages your
entity can speak, it can sometimes pick wrong if your species speaks
more than two language, but it won't prevent you from using the
translator normally.

Also, the name of the light version of the trait is subject to change. I
just couldn't think of anything more creative for it.

# Why

Although supposedly trystan or others have a language menu in mind, it's
still not even being worked on yet. At the same time, I've already seen
some people roleplay as though their characters do not speak galactic
common.

<details><summary><h1>Media</h1></summary>
<p>


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69920617/42984284-3a70-40bb-ad48-b11218cd5c5b)


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69920617/f3d26cef-a908-49e7-84e0-cb50d5d98c0d)


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69920617/6f44b3cc-5906-402b-ae5c-a3f0ad743bc6)


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69920617/4edfe7ce-1633-4e6a-94ca-5db0dff88eb0)


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69920617/ec5b3da0-b400-41f3-90c1-e5dc6b5af7c5)

</p>
</details>

---

# Changelog

:cl:
- add: Added two new foreigner traits that make your character unable to
speak Galactic Common and give you a translator instead.
- tweak: Translators can now be equipped in the neck slot and display
useful info when examined.
  • Loading branch information
Mnemotechnician authored Jul 12, 2024
1 parent e6779b6 commit 6084740
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 12 deletions.
3 changes: 0 additions & 3 deletions Content.Server/Language/TranslatorSystem.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using System.Linq;
using Content.Server.Language.Events;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Language;
using Content.Shared.Language.Events;
using Content.Shared.Language.Systems;
using Content.Shared.PowerCell;
using Content.Shared.Language.Components.Translators;
using Robust.Shared.Utility;

namespace Content.Server.Language;

Expand Down
36 changes: 36 additions & 0 deletions Content.Server/Traits/Assorted/ForeignerTraitComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Content.Shared.Language;
using Content.Shared.Language.Systems;
using Robust.Shared.Prototypes;

namespace Content.Server.Traits.Assorted;

/// <summary>
/// When applied to a not-yet-spawned player entity, removes <see cref="BaseLanguage"/> from the lists of their languages
/// and gives them a translator instead.
/// </summary>
[RegisterComponent]
public sealed partial class ForeignerTraitComponent : Component
{
/// <summary>
/// The "base" language that is to be removed and substituted with a translator.
/// By default, equals to the fallback language, which is GalacticCommon.
/// </summary>
[DataField]
public ProtoId<LanguagePrototype> BaseLanguage = SharedLanguageSystem.FallbackLanguagePrototype;

/// <summary>
/// Whether this trait prevents the entity from understanding the base language.
/// </summary>
public bool CantUnderstand = true;

/// <summary>
/// Whether this trait prevents the entity from speaking the base language.
/// </summary>
public bool CantSpeak = true;

/// <summary>
/// The base translator prototype to use when creating a translator for the entity.
/// </summary>
[DataField(required: true)]
public ProtoId<EntityPrototype> BaseTranslator = default!;
}
105 changes: 105 additions & 0 deletions Content.Server/Traits/Assorted/ForeignerTraitSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Linq;
using Content.Server.Hands.Systems;
using Content.Server.Language;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory;
using Content.Shared.Language;
using Content.Shared.Language.Components;
using Content.Shared.Language.Components.Translators;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;

namespace Content.Server.Traits.Assorted;


public sealed partial class ForeignerTraitSystem : EntitySystem
{
[Dependency] private readonly EntityManager _entMan = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly LanguageSystem _languages = default!;
[Dependency] private readonly StorageSystem _storage = default!;

public override void Initialize()
{
SubscribeLocalEvent<ForeignerTraitComponent, ComponentInit>(OnSpawn); // TraitSystem adds it after PlayerSpawnCompleteEvent so it's fine
}

private void OnSpawn(Entity<ForeignerTraitComponent> entity, ref ComponentInit args)
{
if (entity.Comp.CantUnderstand && !entity.Comp.CantSpeak)
Log.Warning($"Allowing entity {entity.Owner} to speak a language but not understand it leads to undefined behavior.");

if (!TryComp<LanguageKnowledgeComponent>(entity, out var knowledge))
{
Log.Warning($"Entity {entity.Owner} does not have a LanguageKnowledge but has a ForeignerTrait!");
return;
}

var alternateLanguage = knowledge.SpokenLanguages.Find(it => it != entity.Comp.BaseLanguage);
if (alternateLanguage == null)
{
Log.Warning($"Entity {entity.Owner} does not have an alternative language to choose from (must have at least one non-GC for ForeignerTrait)!");
return;
}

if (TryGiveTranslator(entity.Owner, entity.Comp.BaseTranslator, entity.Comp.BaseLanguage, alternateLanguage, out var translator))
{
_languages.RemoveLanguage(entity, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand, knowledge);
}
}

/// <summary>
/// Tries to create and give the entity a translator to translator that translates speech between the two specified languages.
/// </summary>
public bool TryGiveTranslator(
EntityUid uid,
string baseTranslatorPrototype,
ProtoId<LanguagePrototype> translatorLanguage,
ProtoId<LanguagePrototype> entityLanguage,
out EntityUid result)
{
result = EntityUid.Invalid;
if (translatorLanguage == entityLanguage)
return false;

var translator = _entMan.SpawnNextToOrDrop(baseTranslatorPrototype, uid);
result = translator;

if (!TryComp<HandheldTranslatorComponent>(translator, out var handheld))
{
handheld = AddComp<HandheldTranslatorComponent>(translator);
handheld.ToggleOnInteract = true;
handheld.SetLanguageOnInteract = true;
}

// Allows to speak the specified language and requires entities language.
handheld.SpokenLanguages = [translatorLanguage];
handheld.UnderstoodLanguages = [translatorLanguage];
handheld.RequiredLanguages = [entityLanguage];

// Try to put it in entities hand
if (_hands.TryPickupAnyHand(uid, translator, false, false, false))
return true;

// Try to find a valid clothing slot on the entity and equip the translator there
if (TryComp<ClothingComponent>(translator, out var clothing)
&& clothing.Slots != SlotFlags.NONE
&& _inventory.TryGetSlots(uid, out var slots)
&& slots.Any(it => _inventory.TryEquip(uid, translator, it.Name, true, false)))
return true;

// Try to put the translator into entities bag, if it has one
if (_inventory.TryGetSlotEntity(uid, "back", out var bag)
&& TryComp<StorageComponent>(bag, out var storage)
&& _storage.Insert(bag.Value, translator, out _, null, storage, false, false))
return true;

// If all of the above has failed, just drop it at the same location as the entity
// This should ideally never happen, but who knows.
Transform(translator).Coordinates = Transform(uid).Coordinates;

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Content.Shared.Language.Components;

// TODO: move to server side, it's never synchronized!

/// <summary>
/// Stores data about entities' intrinsic language knowledge.
/// </summary>
Expand Down
18 changes: 14 additions & 4 deletions Content.Shared/Language/Systems/SharedTranslatorSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Content.Shared.Examine;
using Content.Shared.Toggleable;
using Content.Shared.Language.Components.Translators;
Expand All @@ -17,11 +18,20 @@ public override void Initialize()

private void OnExamined(EntityUid uid, HandheldTranslatorComponent component, ExaminedEvent args)
{
var state = Loc.GetString(component.Enabled
? "translator-enabled"
: "translator-disabled");
var understoodLanguageNames = component.UnderstoodLanguages
.Select(it => Loc.GetString($"language-{it}-name"));
var spokenLanguageNames = component.SpokenLanguages
.Select(it => Loc.GetString($"language-{it}-name"));
var requiredLanguageNames = component.RequiredLanguages
.Select(it => Loc.GetString($"language-{it}-name"));

args.PushMarkup(state);
args.PushMarkup(Loc.GetString("translator-examined-langs-understood", ("languages", string.Join(", ", understoodLanguageNames))));
args.PushMarkup(Loc.GetString("translator-examined-langs-spoken", ("languages", string.Join(", ", spokenLanguageNames))));

args.PushMarkup(Loc.GetString(component.RequiresAllLanguages ? "translator-examined-requires-all" : "translator-examined-requires-any",
("languages", string.Join(", ", requiredLanguageNames))));

args.PushMarkup(Loc.GetString(component.Enabled ? "translator-examined-enabled" : "translator-examined-disabled"));
}

protected void OnAppearanceChange(EntityUid translator, HandheldTranslatorComponent? comp = null)
Expand Down
9 changes: 7 additions & 2 deletions Resources/Locale/en-US/language/translator.ftl
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
translator-component-shutoff = The {$translator} shuts off.
translator-component-turnon = The {$translator} turns on.
translator-enabled = It appears to be active.
translator-disabled = It appears to be disabled.
translator-implanter-refuse = The {$implanter} has no effect on {$target}.
translator-implanter-success = The {$implanter} successfully injected {$target}.
translator-implanter-ready = This implanter appears to be ready to use.
translator-implanter-used = This implanter seems empty.
translator-examined-langs-understood = It can translate from: [color=green]{$languages}[/color].
translator-examined-langs-spoken = It can translate to: [color=green]{$languages}[/color].
translator-examined-requires-any = It requires you to know at least one of these languages: [color=yellow]{$languages}[/color].
translator-examined-requires-all = It requires you to know all of these languages: [color=yellow]{$languages}[/color].
translator-examined-enabled = It appears to be [color=green]active[/color].
translator-examined-disabled = It appears to be [color=red]turned off[/color].
10 changes: 10 additions & 0 deletions Resources/Locale/en-US/traits/traits.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ trait-name-Thieving = Thieving
trait-description-Thieving =
You are deft with your hands, and talented at convincing people of their belongings.
You can identify pocketed items, steal them quieter, and steal ~33% faster.
trait-name-ForeignerLight = Foreigner (light)
trait-description-ForeignerLight =
You struggle to learn this station's primary language, and as such, cannot speak it. You can, however, comprehend what others say in that language.
To help you overcome this obstacle, you are equipped with a translator that helps you speak in this station's primary language.
trait-name-Foreigner = Foreigner
trait-description-Foreigner =
For one reason or another you do not speak this station's primary language.
Instead, you have a translator issued to you that only you can use.
18 changes: 15 additions & 3 deletions Resources/Prototypes/Entities/Objects/Devices/translators.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- type: entity
abstract: true
noSpawn: true
id: TranslatorUnpowered
parent: BaseItem
name: translator
Expand All @@ -23,9 +23,14 @@
False: { visible: false }
- type: HandheldTranslator
enabled: false
- type: Clothing # To allow equipping translators on the neck slot
slots: [neck, pocket]
equipDelay: 0.3
unequipDelay: 0.3
quickEquip: false # Would conflict

- type: entity
abstract: true
noSpawn: true
id: Translator
parent: [ TranslatorUnpowered, PowerCellSlotMediumItem ]
suffix: Powered
Expand All @@ -34,7 +39,7 @@
drawRate: 1

- type: entity
abstract: true
noSpawn: true
id: TranslatorEmpty
parent: Translator
suffix: Empty
Expand All @@ -44,6 +49,13 @@
cell_slot:
name: power-cell-slot-component-slot-name-default

- type: entity
noSpawn: true
id: TranslatorForeigner
parent: [ Translator, PowerCellSlotHighItem ]
name: foreigner's translator
description: A special-issue translator that helps foreigner's speak and understand this station's primary language.


- type: entity
id: CanilunztTranslator
Expand Down
23 changes: 23 additions & 0 deletions Resources/Prototypes/Traits/inconveniences.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,26 @@
fourRandomProb: 0
threeRandomProb: 0
cutRandomProb: 0

- type: trait
id: ForeignerLight
category: Mental
points: 1
requirements:
- !type:TraitGroupExclusionRequirement
prototypes: [ Foreigner ]
components:
- type: ForeignerTrait
cantUnderstand: false # Allows to understand
baseTranslator: TranslatorForeigner

- type: trait
id: Foreigner
category: Mental
points: 2
requirements: # TODO: Add a requirement to know at least 1 non-gc language
- !type:TraitGroupExclusionRequirement
prototypes: [ ForeignerLight ]
components:
- type: ForeignerTrait
baseTranslator: TranslatorForeigner

0 comments on commit 6084740

Please sign in to comment.