diff --git a/Content.Server/_CorvaxNext/Speech/Components/ParrotSpeechComponent.cs b/Content.Server/_CorvaxNext/Speech/Components/ParrotSpeechComponent.cs
new file mode 100644
index 00000000000..1ad09fe7067
--- /dev/null
+++ b/Content.Server/_CorvaxNext/Speech/Components/ParrotSpeechComponent.cs
@@ -0,0 +1,40 @@
+using Content.Server.Speech.EntitySystems;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Speech.Components;
+
+[RegisterComponent]
+[Access(typeof(ParrotSpeechSystem))]
+public sealed partial class ParrotSpeechComponent : Component
+{
+ ///
+ /// The maximum number of words the parrot can learn per phrase.
+ /// Phrases are 1 to MaxPhraseLength words in length.
+ ///
+ [DataField]
+ public int MaximumPhraseLength = 7;
+
+ [DataField]
+ public int MaximumPhraseCount = 20;
+
+ [DataField]
+ public int MinimumWait = 60; // 1 minutes
+
+ [DataField]
+ public int MaximumWait = 120; // 2 minutes
+
+ ///
+ /// The probability that a parrot will learn from something an overheard phrase.
+ ///
+ [DataField]
+ public float LearnChance = 0.2f;
+
+ [DataField]
+ public EntityWhitelist Blacklist { get; private set; } = new();
+
+ [DataField]
+ public TimeSpan? NextUtterance;
+
+ [DataField(readOnly: true)]
+ public List LearnedPhrases = new();
+}
diff --git a/Content.Server/_CorvaxNext/Speech/EntitySystems/ParrotSpeechSystem.cs b/Content.Server/_CorvaxNext/Speech/EntitySystems/ParrotSpeechSystem.cs
new file mode 100644
index 00000000000..ddfdfe5cee1
--- /dev/null
+++ b/Content.Server/_CorvaxNext/Speech/EntitySystems/ParrotSpeechSystem.cs
@@ -0,0 +1,85 @@
+using System.Linq;
+using Content.Server.Chat.Systems;
+using Content.Server.Speech.Components;
+using Content.Shared.Mind.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Speech.EntitySystems;
+
+public sealed class ParrotSpeechSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnListen);
+ SubscribeLocalEvent(CanListen);
+ }
+
+ public override void Update(float frameTime)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var component))
+ {
+ if (component.LearnedPhrases.Count == 0)
+ // This parrot has not learned any phrases, so can't say anything interesting.
+ continue;
+ if (TryComp(uid, out var mind) && mind.HasMind)
+ // Pause parrot speech when someone is controlling the parrot.
+ continue;
+ if (_timing.CurTime < component.NextUtterance)
+ continue;
+
+ if (component.NextUtterance != null)
+ {
+ _chat.TrySendInGameICMessage(
+ uid,
+ _random.Pick(component.LearnedPhrases),
+ InGameICChatType.Speak,
+ hideChat: true, // Don't spam the chat with randomly generated messages
+ hideLog: true, // TODO: Don't spam admin logs either.
+ // If a parrot learns something inappropriate, admins can search for
+ // the player that said the inappropriate thing.
+ checkRadioPrefix: false);
+ }
+
+ component.NextUtterance = _timing.CurTime + TimeSpan.FromSeconds(_random.Next(component.MinimumWait, component.MaximumWait));
+ }
+ }
+
+ private void OnListen(EntityUid uid, ParrotSpeechComponent component, ref ListenEvent args)
+ {
+ if (_random.Prob(component.LearnChance))
+ {
+ // Very approximate word splitting. But that's okay: parrots aren't smart enough to
+ // split words correctly.
+ var words = args.Message.Split(" ", StringSplitOptions.RemoveEmptyEntries);
+ // Prefer longer phrases
+ var phraseLength = 1 + (int) (Math.Sqrt(_random.NextDouble()) * component.MaximumPhraseLength);
+
+ var startIndex = _random.Next(0, Math.Max(0, words.Length - phraseLength + 1));
+
+ var phrase = string.Join(" ", words.Skip(startIndex).Take(phraseLength)).ToLower();
+
+ while (component.LearnedPhrases.Count >= component.MaximumPhraseCount)
+ {
+ _random.PickAndTake(component.LearnedPhrases);
+ }
+
+ component.LearnedPhrases.Add(phrase);
+ }
+ }
+
+ private void CanListen(EntityUid uid, ParrotSpeechComponent component, ref ListenAttemptEvent args)
+ {
+ if (_whitelistSystem.IsBlacklistPass(component.Blacklist, args.Source))
+ args.Cancel();
+ }
+}
diff --git a/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/mobs/npcs/animals.ftl b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/mobs/npcs/animals.ftl
new file mode 100644
index 00000000000..119ace125c5
--- /dev/null
+++ b/Resources/Locale/ru-RU/ss14-ru/prototypes/_corvaxnext/entities/mobs/npcs/animals.ftl
@@ -0,0 +1,3 @@
+ent-MobParrotSmart = { ent-MobParrot }
+ .desc = Проникает в ваши владения, шпионит за вами и при этом остаётся классным питомцем. Умеет говорить.
+ .suffix = Умный
diff --git a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml
index 10c715bb99e..dd9fd1d16e0 100644
--- a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml
+++ b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml
@@ -181,7 +181,7 @@
components:
- type: StorageFill
contents:
- - id: MobParrot
+ - id: MobParrotSmart # Corvax-Next-Parrot: replaced MobParrot
amount: 3
- type: entity
diff --git a/Resources/Prototypes/Corvax/Entities/Markers/Spawners/Random/animals.yml b/Resources/Prototypes/Corvax/Entities/Markers/Spawners/Random/animals.yml
index 23d44481582..4d18914fa1a 100644
--- a/Resources/Prototypes/Corvax/Entities/Markers/Spawners/Random/animals.yml
+++ b/Resources/Prototypes/Corvax/Entities/Markers/Spawners/Random/animals.yml
@@ -32,7 +32,7 @@
- MobLizard
- MobSlug
- MobFrog
- - MobParrot
+ - MobParrotSmart # Corvax-Next-Parrot: replaced MobParrot
- MobPenguin
- MobSnake
- MobPossum
diff --git a/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml b/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
index 723cd1148b1..2122bca7b33 100644
--- a/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
+++ b/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
@@ -721,7 +721,7 @@
sprite: Mobs/Animals/parrot.rsi
- type: ConditionalSpawner
prototypes:
- - MobParrot
+ - MobParrotSmart # Corvax-Next-Parrot: replaced MobParrot
- type: entity
name: Butterfly Spawner
diff --git a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml
index f2723e5369e..46603de45f5 100644
--- a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml
+++ b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml
@@ -392,7 +392,7 @@
prob: 0.03
- id: MobMouse
orGroup: fauna
- - id: MobParrot
+ - id: MobParrotSmart # Corvax-Next-Parrot: replaced MobParrot
orGroup: fauna
maxAmount: 1
- id: MobPenguin
diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/_CorvaxNext/Entities/Mobs/NPCs/animals.yml
new file mode 100644
index 00000000000..9ab8d6a0420
--- /dev/null
+++ b/Resources/Prototypes/_CorvaxNext/Entities/Mobs/NPCs/animals.yml
@@ -0,0 +1,88 @@
+- type: entity
+ name: parrot
+ parent: [ SimpleMobBase, FlyingMobBase ]
+ id: MobParrotSmart
+ description: Infiltrates your domain, spies on you, and somehow still a cool pet. Actually can talk.
+ suffix: Smart
+ components:
+ - type: MovementSpeedModifier
+ baseWalkSpeed : 2.5
+ baseSprintSpeed : 6
+ - type: Sprite
+ drawdepth: Mobs
+ layers:
+ - map: ["enum.DamageStateVisualLayers.Base", "movement"]
+ state: parrot-moving # until upstream
+ sprite: _CorvaxNext/Mobs/Animals/parrot.rsi
+ - type: SpriteMovement
+ movementLayers:
+ movement:
+ state: parrot-moving
+ noMovementLayers:
+ movement:
+ state: parrot-moving # until upstream
+ - type: Item
+ sprite: _CorvaxNext/Mobs/Animals/parrot.rsi
+ size: Normal
+ inhandVisuals:
+ left:
+ - state: inhand-left
+ right:
+ - state: inhand-right
+ - type: Clothing
+ quickEquip: false
+ sprite: _CorvaxNext/Mobs/Animals/parrot.rsi
+ slots:
+ - HEAD
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeCircle
+ radius: 0.25
+ density: 10
+ mask:
+ - FlyingMobMask
+ layer:
+ - FlyingMobLayer
+ - type: Appearance
+ - type: DamageStateVisuals
+ states:
+ Alive:
+ Base: parrot
+ Critical:
+ Base: dead
+ Dead:
+ Base: dead
+ - type: Butcherable
+ spawned:
+ - id: FoodMeat
+ amount: 1
+ - type: Speech
+ speechSounds: Parrot
+ speechVerb: Parrot
+ - type: Vocal
+ sounds:
+ Unsexed: Parrot
+ - type: CanEscapeInventory
+ - type: ParrotAccent
+ - type: Strippable
+ - type: InteractionPopup
+ successChance: 0.6
+ interactSuccessString: petting-success-bird
+ interactFailureString: petting-failure-generic
+ interactSuccessSpawn: EffectHearts
+ interactSuccessSound:
+ path: /Audio/Animals/parrot_raught.ogg
+ - type: Bloodstream
+ bloodMaxVolume: 50
+ - type: ActiveListener
+ range: 5
+ - type: ParrotSpeech
+ blacklist:
+ components:
+ - ParrotSpeech # Stop parrots repeating their own speech
+ - SurveillanceCamera
+ - SurveillanceCameraMonitor
+ - RadioSpeaker
+ - VendingMachine
\ No newline at end of file
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/dead.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/dead.png
new file mode 100644
index 00000000000..8a87b42a3be
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/dead.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/equipped-HELMET.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/equipped-HELMET.png
new file mode 100644
index 00000000000..f44994cc2f1
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/equipped-HELMET.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/icon.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/icon.png
new file mode 100644
index 00000000000..10459e3c3ce
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/icon.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-left.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-left.png
new file mode 100644
index 00000000000..1203ff93458
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-left.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-right.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-right.png
new file mode 100644
index 00000000000..5385fd47db0
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/inhand-right.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/meta.json b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/meta.json
new file mode 100644
index 00000000000..e0d80415b48
--- /dev/null
+++ b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/meta.json
@@ -0,0 +1,43 @@
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b",
+ "states": [
+ {
+ "name": "parrot-moving",
+ "directions": 4,
+ "delays": [
+ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
+ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
+ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
+ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
+ ]
+ },
+ {
+ "name": "parrot",
+ "directions": 4
+ },
+ {
+ "name": "dead"
+ },
+ {
+ "name": "icon"
+ },
+ {
+ "name": "equipped-HELMET",
+ "directions": 4
+ },
+ {
+ "name": "inhand-left",
+ "directions": 4
+ },
+ {
+ "name": "inhand-right",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot-moving.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot-moving.png
new file mode 100644
index 00000000000..fd9079fd668
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot-moving.png differ
diff --git a/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot.png b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot.png
new file mode 100644
index 00000000000..fd78a533ee9
Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Mobs/Animals/parrot.rsi/parrot.png differ