diff --git a/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurComponent.cs b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurComponent.cs new file mode 100644 index 00000000000..97fde3f70dd --- /dev/null +++ b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurComponent.cs @@ -0,0 +1,223 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.Roboisseur.Roboisseur +{ + [RegisterComponent] + public sealed class RoboisseurComponent : Component + { + [ViewVariables] + [DataField("accumulator")] + public float Accumulator = 0f; + + [ViewVariables(VVAccess.ReadOnly)] + [DataField("impatient")] + public Boolean Impatient { get; set; } = false; + + [ViewVariables] + [DataField("resetTime")] + public TimeSpan ResetTime = TimeSpan.FromMinutes(10); + + [DataField("barkAccumulator")] + public float BarkAccumulator = 0f; + + [DataField("barkTime")] + public TimeSpan BarkTime = TimeSpan.FromMinutes(1); + + [ViewVariables(VVAccess.ReadWrite)] + public EntityPrototype DesiredPrototype = default!; + + [DataField("demandMessages")] + public IReadOnlyList DemandMessages = new[] + { + "roboisseur-request-1", + "roboisseur-request-2", + "roboisseur-request-3", + "roboisseur-request-4", + "roboisseur-request-5", + "roboisseur-request-6" + }; + + [DataField("impatientMessages")] + public IReadOnlyList ImpatientMessages = new[] + { + "roboisseur-request-impatient-1", + "roboisseur-request-impatient-2", + "roboisseur-request-impatient-3", + }; + + [DataField("demandMessagesTier2")] + public IReadOnlyList DemandMessagesTier2 = new[] + { + "roboisseur-request-second-1", + "roboisseur-request-second-2", + "roboisseur-request-second-3" + }; + + [DataField("rewardMessages")] + public IReadOnlyList RewardMessages = new[] + { + "roboisseur-thanks-1", + "roboisseur-thanks-2", + "roboisseur-thanks-3", + "roboisseur-thanks-4", + "roboisseur-thanks-5" + }; + + [DataField("rewardMessagesTier2")] + public IReadOnlyList RewardMessagesTier2 = new[] + { + "roboisseur-thanks-second-1", + "roboisseur-thanks-second-2", + "roboisseur-thanks-second-3", + "roboisseur-thanks-second-4", + "roboisseur-thanks-second-5" + }; + + [DataField("rejectMessages")] + public IReadOnlyList RejectMessages = new[] + { + "roboisseur-deny-1", + "roboisseur-deny-2", + "roboisseur-deny-3" + }; + + [DataField("tier2Protos")] + public List Tier2Protos = new() + { + "FoodBurgerEmpowered", + "FoodSoupClown", + "FoodSoupChiliClown", + "FoodBurgerSuper", + "FoodNoodlesCopy", + "FoodMothMallow", + "FoodPizzaCorncob", + "FoodPizzDonkpocket", + "FoodSoupMonkey", + "FoodMothSeedSoup", + "FoodTartGrape", + "FoodMealCubancarp", + "FoodMealSashimi", + "FoodBurgerCarp", + "FoodMealTaco", + "FoodMothMacBalls", + "FoodSoupNettle", + "FoodBurgerDuck", + "FoodBurgerBaseball" + }; + + [DataField("tier3Protos")] + public List Tier3Protos = new() + { + "FoodBurgerGhost", + "FoodSaladWatermelonFruitBowl", + "FoodBakedCannabisBrownieBatch", + "FoodPizzaDank", + "FoodBurgerBear", + "FoodBurgerMime", + "FoodCakeSuppermatter", + "FoodSoupChiliCold", + "FoodSoupBisque", + "FoodCakeSlime", + "FoodBurgerCrazy" + }; + + [DataField("robossuierRewards")] + public IReadOnlyList RobossuierRewards = new[] + { + "DrinkIceCreamGlass", + "FoodFrozenPopsicleOrange", + "FoodFrozenPopsicleBerry", + "FoodFrozenPopsicleJumbo", + "FoodFrozenSnowconeBerry", + "FoodFrozenSnowconeFruit", + "FoodFrozenSnowconeClown", + "FoodFrozenSnowconeMime", + "FoodFrozenSnowconeRainbow", + "FoodFrozenCornuto", + "FoodFrozenSundae", + "FoodFrozenFreezy", + "FoodFrozenSandwichStrawberry", + "FoodFrozenSandwich", + }; + + [DataField("blacklistedProtos")] + public IReadOnlyList BlacklistedProtos = new[] + { + "FoodMothPesto", + "FoodBurgerSpell", + "FoodBreadBanana", + "FoodMothSqueakingFry", + "FoodMothFleetSalad", + "FoodBreadMeatSpider", + "FoodBurgerHuman", + "FoodNoodlesBoiled", + "FoodMothOatStew", + "FoodMeatLizardtailKebab", + "FoodSoupTomato", + "FoodDonkpocketGondolaWarm", + "FoodDonkpocketBerryWarm", + "LockboxDecloner", + "FoodBreadButteredToast", + "FoodMothCottonSoup", + "LeavesTobaccoDried", + "FoodSoupEyeball", + "FoodMothKachumbariSalad", + "FoodMeatHumanKebab", + "FoodMeatRatdoubleKebab", + "FoodBurgerCorgi", + "FoodBreadPlain", + "FoodMeatKebab", + "FoodBreadBun", + "FoodBurgerCat", + "FoodSoupTomatoBlood", + "FoodMothSaladBase", + "FoodPieXeno", + "FoodDonkpocketTeriyakiWarm", + "FoodMothBakedCheese", + "FoodMothTomatoSauce", + "FoodMothPizzaCotton", + "AloeCream", + "FoodSnackPopcorn", + "FoodBurgerSoy", + "FoodMothToastedSeeds", + "FoodMothCornmealPorridge", + "FoodMothBakedCorn", + "FoodBreadMoldySlice", + "FoodRiceBoiled", + "FoodMothEyeballSoup", + "FoodMeatRatKebab", + "FoodBreadCreamcheese", + "FoodSoupOnion", + "FoodBurgerAppendix", + "FoodBurgerRat", + "RegenerativeMesh", + "FoodCheeseCurds", + "FoodDonkpocketHonkWarm", + "FoodOatmeal", + "FoodBreadJellySlice", + "FoodMothCottonSalad", + "FoodBreadMoldy", + "FoodDonkpocketSpicyWarm", + "FoodCannabisButter", + "FoodNoodles", + "FoodBreadMeat", + "LeavesCannabisDried", + "FoodBurgerCheese", + "FoodDonkpocketDankWarm", + "FoodSpaceshroomCooked", + "FoodMealFries", + "MedicatedSuture", + "FoodDonkpocketWarm", + "FoodCakePlain", + "DisgustingSweptSoup", + "FoodBurgerPlain", + "FoodBreadGarlicSlice", + "FoodSoupMushroom", + "FoodSoupWingFangChu", + "FoodBreadMeatXeno", + "FoodCakeBrain", + "FoodBurgerBrain", + "FoodSaladCaesar" + }; + } +} diff --git a/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs new file mode 100644 index 00000000000..40d49fc4c05 --- /dev/null +++ b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs @@ -0,0 +1,186 @@ +using Content.Shared.Interaction; +using Content.Shared.Mobs.Components; +using Content.Server.Chat.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Content.Shared.Random.Helpers; +using Content.Shared.Kitchen; +using Robust.Server.GameObjects; +using Content.Server.Materials; + +namespace Content.Server.Roboisseur.Roboisseur +{ + public sealed partial class RoboisseurSystem : EntitySystem + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly MaterialStorageSystem _material = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; + + + public override void Initialize() + { + base.Initialize(); + + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnInteractUsing); + } + + + private void OnInit(EntityUid uid, RoboisseurComponent component, ComponentInit args) + { + NextItem(component); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + + foreach (var roboisseur in EntityQuery()) + { + roboisseur.Accumulator += frameTime; + roboisseur.BarkAccumulator += frameTime; + if (roboisseur.BarkAccumulator >= roboisseur.BarkTime.TotalSeconds) + { + roboisseur.BarkAccumulator = 0; + string message = Loc.GetString(_random.Pick(roboisseur.DemandMessages), ("item", roboisseur.DesiredPrototype.Name)); + if (roboisseur.ResetTime.TotalSeconds - roboisseur.Accumulator < 120) + { + roboisseur.Impatient = true; + message = Loc.GetString(_random.Pick(roboisseur.ImpatientMessages), ("item", roboisseur.DesiredPrototype.Name)); + } + else if (CheckTier(roboisseur.DesiredPrototype.ID, roboisseur) > 2) + message = Loc.GetString(_random.Pick(roboisseur.DemandMessagesTier2), ("item", roboisseur.DesiredPrototype.Name)); + _chat.TrySendInGameICMessage(roboisseur.Owner, message, InGameICChatType.Speak, false); + } + + if (roboisseur.Accumulator >= roboisseur.ResetTime.TotalSeconds) + { + roboisseur.Impatient = false; + NextItem(roboisseur); + } + } + } + + private void RewardServicer(EntityUid uid, RoboisseurComponent component, int tier) + { + var r = new Random(); + int rewardToDispense = r.Next(100, 350) + 250 * tier; + + _material.SpawnMultipleFromMaterial(rewardToDispense, "Credit", Transform(uid).Coordinates); + if(tier > 1) + { + while (tier != 0) + { + EntityManager.SpawnEntity(_random.Pick(component.RobossuierRewards), Transform(uid).Coordinates); + tier--; + } + } + return; + } + + private void OnInteractHand(EntityUid uid, RoboisseurComponent component, InteractHandEvent args) + { + if (!TryComp(args.User, out var actor)) + return; + + string message = Loc.GetString(_random.Pick(component.DemandMessages), ("item", component.DesiredPrototype.Name)); + if (CheckTier(component.DesiredPrototype.ID, component) > 1) + message = Loc.GetString(_random.Pick(component.DemandMessagesTier2), ("item", component.DesiredPrototype.Name)); + + _chat.TrySendInGameICMessage(component.Owner, message, InGameICChatType.Speak, false); + } + + private void OnInteractUsing(EntityUid uid, RoboisseurComponent component, InteractUsingEvent args) + { + if (HasComp(args.Used) || + MetaData(args.Used)?.EntityPrototype == null) + return; + + var validItem = CheckValidity(MetaData(args.Used).EntityName, component.DesiredPrototype); + var nextItem = true; + + if (!validItem) + { + _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(component.RejectMessages)), InGameICChatType.Speak, true); + return; + } + + component.Impatient = false; + EntityManager.QueueDeleteEntity(args.Used); + + int tier = CheckTier(component.DesiredPrototype.ID, component); + + string message = Loc.GetString(_random.Pick(component.RewardMessages)); + if (tier > 1) + message = Loc.GetString(_random.Pick(component.RewardMessagesTier2)); + _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Speak, true); + + RewardServicer(args.User, component, tier); + + if (nextItem) + NextItem(component); + } + + private bool CheckValidity(String name, EntityPrototype target) + { + // 1: directly compare Names + // name instead of ID because the oracle asks for them by name + // this could potentially lead to like, labeller exploits maybe but so far only mob names can be fully player-set. + if (name == target.Name) + return true; + + return false; + } + + private int CheckTier(String target, RoboisseurComponent component) + { + if (component.Tier2Protos.Contains(target)) + return 2; + if (component.Tier3Protos.Contains(target)) + return 3; + return 1; + } + + private void NextItem(RoboisseurComponent component) + { + component.Accumulator = 0; + component.BarkAccumulator = 0; + var protoString = GetDesiredItem(component); + + if (_prototypeManager.TryIndex(protoString, out var proto)) + component.DesiredPrototype = proto; + else + Log.Error("Roboisseur can't index prototype " + protoString); + } + + private string GetDesiredItem(RoboisseurComponent component) + { + return _random.Pick(GetAllProtos(component)); + } + + public List GetAllProtos(RoboisseurComponent component) + { + + var allRecipes = _prototypeManager.EnumeratePrototypes(); + var allProtos = new List(); + + foreach (var recipe in allRecipes) + allProtos.Add(recipe.Result); + + foreach (var proto in component.BlacklistedProtos) + allProtos.Remove(proto); + + return allProtos; + } + } + + public enum RobossieurVisualLayers : byte + { + Angry + } +} diff --git a/Resources/Locale/en-US/deltav/roboisseur/roboisseur.ftl b/Resources/Locale/en-US/deltav/roboisseur/roboisseur.ftl new file mode 100644 index 00000000000..c24c3dc2599 --- /dev/null +++ b/Resources/Locale/en-US/deltav/roboisseur/roboisseur.ftl @@ -0,0 +1,25 @@ +roboisseur-request-1 = I humbly request one {$item} for my wealthiest client. +roboisseur-request-2 = Oodles of dough await you, for a nice {$item}! +roboisseur-request-3 = There may be an exorbitantly affluent personage in this sector looking for {INDEFINITE($item)} {$item}. +roboisseur-request-4 = My more... Adventurous Clients require {INDEFINITE($item)} {$item}. +roboisseur-request-5 = {$item}. It's rare. It's valuable. You can make it, yes? +roboisseur-request-6 = Local changes in this quadrant's stock exchange decteted. Seeking {INDEFINITE($item)} {$item}. +roboisseur-request-impatient-1 = There is no time left! Bring me {THE($item)}, immediately! +roboisseur-request-impatient-2 = My market opportunity is dwindling! Quickly, you fools, {THE($item)}! +roboisseur-request-impatient-3 = Damn you all! Deliver me {INDEFINITE($item)} {$item} already! +roboisseur-request-second-1 = I humbly request one {$item} for one of my snowbound clients. +roboisseur-request-second-2 = The frozen planet in this sector requests {INDEFINITE($item)} {$item}! +roboisseur-request-second-3 = A local ice cream vendor is looking to trade for {INDEFINITE($item)} {$item}. +roboisseur-thanks-1 = Mmm, I'm sure my client will be most pleased. +roboisseur-thanks-2 = I cannot believe you were able to source it, but I suppose you are the best. +roboisseur-thanks-3 = When I first heard there were such skilled creatives on this station, I was shocked. My thanks. +roboisseur-thanks-4 = Unlike... most of your coworkers, my clients can afford these delicacies! +roboisseur-thanks-5 = You have my word, my nominal fees are... more than reasonable. +roboisseur-thanks-second-1 = You shared your hopsitality, now they share their frozen delights. +roboisseur-thanks-second-2 = A cold, sweet treat should be reward enough, but have some cold, hard cash too. +roboisseur-thanks-second-3 = While the iced delicacy alone is currency to them, they understand your needs. +roboisseur-thanks-second-4 = Summer on their planet lasts for only two weeks. You have their thanks. +roboisseur-thanks-second-5 = My client's pockets are as deep as their world is cold. +roboisseur-deny-1 = Kindly prepare the correct item. +roboisseur-deny-2 = This is not what we agreed. +roboisseur-deny-3 = Not what the client's looking for. diff --git a/Resources/Prototypes/DeltaV/NPC/roboisseur.yml b/Resources/Prototypes/DeltaV/NPC/roboisseur.yml new file mode 100644 index 00000000000..2c0b3aee8a0 --- /dev/null +++ b/Resources/Prototypes/DeltaV/NPC/roboisseur.yml @@ -0,0 +1,20 @@ +- type: entity + parent: BaseStructure + id: Roboisseur + name: Mr. Butlertron + description: It asks for food to deliver to exotic customers across the cosmos. Powered by the latest technology in bluespace food delivery. + components: + - type: Sprite + noRot: true + drawdepth: Mobs + sprite: DeltaV/Structures/Machines/roboisseur.rsi + layers: + - state: roboisseur-1 + - type: Roboisseur + - type: Speech + speechSounds: Pai + - type: Appearance + - type: Grammar + attributes: + gender: male + proper: true diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/meta.json b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/meta.json new file mode 100644 index 00000000000..97d0e5f4bac --- /dev/null +++ b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "JustAnOrange", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "roboisseur-1" + }, + { + "name": "roboisseur-2" + }, + { + "name": "roboisseur-3" + }, + { + "name": "roboisseur-4" + }, + { + "name": "roboisseur-impatient1" + }, + { + "name": "roboisseur-impatient2" + } + ] +} diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-1.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-1.png new file mode 100644 index 00000000000..454e6dd065a Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-1.png differ diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-2.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-2.png new file mode 100644 index 00000000000..d33fad6405d Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-2.png differ diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-3.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-3.png new file mode 100644 index 00000000000..fbcb190fba5 Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-3.png differ diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-4.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-4.png new file mode 100644 index 00000000000..d839fd636d9 Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-4.png differ diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient1.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient1.png new file mode 100644 index 00000000000..b1ef2cd81ee Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient1.png differ diff --git a/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient2.png b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient2.png new file mode 100644 index 00000000000..fc6e6ee2521 Binary files /dev/null and b/Resources/Textures/DeltaV/Structures/Machines/roboisseur.rsi/roboisseur-impatient2.png differ