Exemple #1
0
        private static ActionChosen AICombatActionMeleeOnly(EcsRegistrar rgs, long globalId, long attackerId, List <long> battlefieldEntityIds)
        {
            var action = new ActionChosen {
                Action = Vals.CombatAction.DoNothing, AgentId = attackerId
            };

            var naturalWeaponsMap = rgs.GetPartSingle <Parts.NaturalWeaponsMap>(globalId);

            var targetIdsToWeights = battlefieldEntityIds
                                     .Select(id => new { Id = id, Agent = rgs.GetPartSingleOrDefault <Parts.Agent>(id) })
                                     .Where(a => a.Id != attackerId && a.Agent != null && !Vals.CombatStatusTag.CombatTerminalStatuses.Intersect(a.Agent.CombatStatusTags).Any())
                                     .ToDictionary(a => a.Id, a => 0);

            var attackerFaction     = rgs.GetPartSingle <Parts.Faction>(attackerId);
            var factionInteractions = rgs.GetPartSingle <Parts.FactionInteractionSet>(globalId);

            //eventually we have to figure out how to weight faction relative to everything else.
            var possibleTargetIds = targetIdsToWeights.Keys.ToArray();

            foreach (var possibleTargetId in possibleTargetIds)
            {
                var targetFaction = rgs.GetPartSingle <Parts.Faction>(possibleTargetId);
                targetIdsToWeights[possibleTargetId] = factionInteractions.Interactions
                                                       .SingleOrDefault(i => i.SourceFaction == attackerFaction.CurrentPublicFaction && i.TargetFaction == targetFaction.CurrentPublicFaction)
                                                       ?.Disposition ?? 0;
            }

            var minWeight = targetIdsToWeights.Values.Any() ? targetIdsToWeights.Values.Min() : 0;

            if (minWeight < 0)
            {
                var targetId = targetIdsToWeights.First(kvp => kvp.Value == minWeight).Key;

                var attackerAnatomy = rgs.GetPartSingle <Parts.Anatomy>(attackerId);

                //MWCTODO++: yaaaah this is way too human centric.
                var weaponId = attackerAnatomy.SlotsEquipped.GetValueOrDefault(Vals.BodySlots.WieldHandRight, 0);
                if (weaponId == 0)
                {
                    weaponId = attackerAnatomy.SlotsEquipped.GetValueOrDefault(Vals.BodySlots.WieldHandLeft, 0);
                }

                if (weaponId == 0)
                {
                    var nwmap = naturalWeaponsMap.NaturalWeaponSets.GetValueOrDefault(attackerAnatomy.NaturalWeaponsCategory, null);

                    if (nwmap != null && nwmap.Keys.Any())
                    {
                        //MWCTODO: and this isn't great.
                        weaponId = nwmap.First().Value;
                    }
                }

                action.Action         = Vals.CombatAction.AttackWeaponMelee;
                action.TargetEntityId = targetId;
                action.WeaponEntityId = weaponId;
            }

            return(action);
        }
Exemple #2
0
        internal static Output Unequip(EcsRegistrar rgs, long unequipperId, long gearId)
        {
            var output = new Output {
                Category = "Text"
            };

            var unequipperEntityName = rgs.GetPartSingle <Parts.EntityName>(unequipperId);
            var unequipperAnatomy    = rgs.GetPartSingle <Parts.Anatomy>(unequipperId);
            var gearEntityName       = rgs.GetPartSingle <Parts.EntityName>(gearId);

            bool unequipped = false;

            foreach (var slot in unequipperAnatomy.SlotsEquipped)
            {
                if (slot.Value == gearId)
                {
                    unequipperAnatomy.SlotsEquipped[slot.Key] = 0L;
                    unequipped = true;
                }
            }

            output.Data       = unequipped
                ? output.Data = $"{unequipperEntityName.ProperName} removes the {gearEntityName.GeneralName}."
                : output.Data = $"{unequipperEntityName.ProperName} doesn't have the {gearEntityName.GeneralName} equipped and can't remove it.";

            return(output);
        }
Exemple #3
0
 //internal static List<Messages.Combat> ProcessAgentAction(EcsRegistrar rgs, long agentId, long? targetId, string action)
 internal static List <Messages.Combat> ProcessAgentAction(EcsRegistrar rgs, long agentId, ActionChosen agentAction)
 {
     if (Stances.Contains(agentAction.Action))
     {
         return(ApplyStance(rgs, agentId, agentAction.Action));
     }
     else if (agentAction.Action == Vals.CombatAction.SwitchToAI)
     {
         return(SwitchToAI(rgs, agentId));
     }
     else if (agentAction.Action == Vals.CombatAction.AttackWeaponMelee)
     {
         return(ResolveSingleTargetMelee(rgs, agentId, agentAction.WeaponEntityId.Value, agentAction.TargetEntityId.Value));
     }
     else if (agentAction.Action == Vals.CombatAction.DoNothing)
     {
         var agentNames = rgs.GetPartSingle <Parts.EntityName>(agentId);
         return(new List <Messages.Combat> {
             new Messages.Combat {
                 Tick = rgs.NewId(), ActorId = agentId, ActorAction = $"{agentNames.ProperName} stands still and looks around in confusion. Probably some programmer failed to give him a good action in this scenario."
             }
         });
     }
     else
     {
         throw new ArgumentException($"Action '{agentAction.Action}' not supported.");
     }
 }
        internal static long Agent(EcsRegistrar rgs, string agentBlueprintName, string armorBlueprintName, string weaponBlueprintName)
        {
            long agentId  = Blueprinter.GetEntityFromBlueprint(rgs, agentBlueprintName);
            long?armorId  = string.IsNullOrEmpty(armorBlueprintName) ? (long?)null : Blueprinter.GetEntityFromBlueprint(rgs, armorBlueprintName);
            long?weaponId = string.IsNullOrEmpty(weaponBlueprintName) ? (long?)null :  Blueprinter.GetEntityFromBlueprint(rgs, weaponBlueprintName);

            var anatomy = rgs.GetPartSingle <Parts.Anatomy>(agentId);

            if (!anatomy.SlotsEquipped.Any())
            {
                var bodySlotsAsEnum = Vals.BodyPlan.BodyPlanToSlots[anatomy.BodyPlan];
                anatomy.SlotsEquipped = bodySlotsAsEnum.GetUniqueFlags().ToDictionary(s => (Vals.BodySlots)s, s => 0L);
            }

            if (armorId.HasValue)
            {
                var armorResults = Sys.Anatomy.Equip(rgs, agentId, armorId.Value);
            }

            if (weaponId.HasValue)
            {
                var wieldResults = Sys.Anatomy.Equip(rgs, agentId, weaponId.Value);
            }

            return(agentId);
        }
Exemple #5
0
 internal static Dictionary <string, int> GetAdjustedSkills(EcsRegistrar rgs, IEnumerable <long> entityIds)
 {
     return(entityIds
            .Select(id => GetAdjustedSkills(rgs, id))
            .SelectMany(d => d).ToLookup(dicts => dicts.Key, dicts => dicts.Value)
            .ToDictionary(l => l.Key, l => l.Sum()));
 }
        internal static List <Output> GetEndOfBattleOutputs(EcsRegistrar rgs, List <long> battlefieldEntityIds)
        {
            var outputs = new List <Output>();

            var nonCombatTerminals = battlefieldEntityIds
                                     .Select(id => new { Id = id, Agent = rgs.GetPartSingleOrDefault <Parts.Agent>(id) })
                                     .Where(a => a.Agent != null && !a.Agent.CombatStatusTags.Intersect(Vals.CombatStatusTag.CombatTerminalStatuses).Any());

            var distinctNctFactions = nonCombatTerminals
                                      .Select(a => rgs.GetPartSingleOrDefault <Parts.Faction>(a.Id)?.CurrentPublicFaction ?? Vals.FactionName.Unaffiliated)
                                      .Distinct()
                                      .ToList();

            //this needs to hook into the narrator.
            var output = new Output()
            {
                Category = OutputCategory.Text
            };

            if (!distinctNctFactions.Any())
            {
                output.Data = "Somehow everyone died. Oops!";
                outputs.Add(output);
            }
            else if (distinctNctFactions.Count == 1)
            {
                output.Data = $"Combat is over. {distinctNctFactions.First()} win.";
                outputs.Add(output);
            }

            return(outputs);
        }
        /// <summary>
        /// Write (or overwrite) a blueprint for an entity. Only for use during programming, not during play.
        /// </summary>
        internal static void WriteBlueprint(EcsRegistrar rgs, long entityId, string blueprintName)
        {
            string revisedPartsJson = GetBlueprintForEntity(rgs, entityId);

            //we do not want to write a blueprint to the executable folder, that's useless.

            File.WriteAllText(_sourceCodeFolder + blueprintName + _blueprintSuffix, revisedPartsJson);
        }
Exemple #8
0
        internal static ActionChosen GetAgentAICombatAction(EcsRegistrar rgs, long globalId, string attackerAI, long attackerId, List <long> battlefieldEntityIds)
        {
            if (attackerAI == Vals.AI.MeleeOnly)
            {
                return(AICombatActionMeleeOnly(rgs, globalId, attackerId, battlefieldEntityIds));
            }

            throw new ArgumentException($"Attacker AI {attackerAI} not supported.");
        }
Exemple #9
0
 internal static WeaponBundle GetWeaponBundle(EcsRegistrar rgs, long weaponId)
 {
     return(new WeaponBundle
     {
         EntityName = rgs.GetPartSingle <Parts.EntityName>(weaponId),
         PhysicalObject = rgs.GetPartSingle <Parts.PhysicalObject>(weaponId),
         Damagers = rgs.GetParts <Parts.Damager>(weaponId).ToList()
     });
 }
Exemple #10
0
 internal static AgentBundle GetAgentBundle(EcsRegistrar rgs, long agentId)
 {
     return(new AgentBundle
     {
         Agent = rgs.GetPartSingle <Parts.Agent>(agentId),
         Anatomy = rgs.GetPartSingle <Parts.Anatomy>(agentId),
         EntityName = rgs.GetPartSingle <Parts.EntityName>(agentId),
         PhysicalObject = rgs.GetPartSingle <Parts.PhysicalObject>(agentId)
     });
 }
Exemple #11
0
        internal static Dictionary <string, int> GetAdjustedSkills(EcsRegistrar rgs, long entityId)
        {
            var skillDicts    = rgs.GetParts <Parts.Skillset>(entityId).Select(p => p.Skills);
            var modifierDicts = rgs.GetParts <Parts.SkillsModifier>(entityId).Select(p => p.SkillDeltas);

            return(skillDicts.Union(modifierDicts)
                   .SelectMany(d => d)
                   .ToLookup(dicts => dicts.Key, dicts => dicts.Value)
                   .ToDictionary(l => l.Key, l => l.Sum()));
        }
Exemple #12
0
        /// <summary>
        /// Overwrite an entity name with the values from a new EntityName part. Null values in the new part are ignored.
        /// The original part is kept, and the new part is discarded after overwriting.
        /// </summary>
        internal static void Overwrite(EcsRegistrar rgs, long entityId, Parts.EntityName newNames)
        {
            var currentNames = rgs.GetParts <Parts.EntityName>(entityId).Single();

            currentNames.LongDescription  = newNames.LongDescription ?? currentNames.LongDescription;
            currentNames.ObscuredName     = newNames.ObscuredName ?? currentNames.ObscuredName;
            currentNames.Pronoun          = newNames.Pronoun ?? currentNames.Pronoun;
            currentNames.ProperName       = newNames.ProperName ?? currentNames.ProperName;
            currentNames.ShortDescription = newNames.ShortDescription ?? currentNames.ShortDescription;
        }
Exemple #13
0
        private static List <Messages.Combat> SwitchToAI(EcsRegistrar rgs, long agentId)
        {
            var ai = rgs.GetPartSingle <Parts.Agent>(agentId);

            ai.ActiveCombatAI = Vals.AI.MeleeOnly;
            return(new List <Messages.Combat> {
                new Messages.Combat {
                    Tick = rgs.NewId(), ActorId = agentId, ActorAction = "Switched to AI."
                }
            });
        }
        internal static IEnumerable <List <Output> > RunBattlefield(EcsRegistrar rgs, long globalId, long battlefieldId, Func <CombatChoicesAndTargets, ActionChosen> receivePlayerInput)
        {
            while (true)
            {
                var battlefieldContainer = rgs.GetPartSingle <Parts.Container>(battlefieldId);
                var battlefieldEntityIds = battlefieldContainer.EntityIds.ToList();

                foreach (var agentId in battlefieldEntityIds)
                {
                    var battlefieldAgent = rgs.GetPartSingleOrDefault <Parts.Agent>(agentId);

                    if (battlefieldAgent == null)
                    {
                        continue;
                    }
                    if (battlefieldAgent.CombatStatusTags.Intersect(Vals.CombatStatusTag.CombatTerminalStatuses).Any())
                    {
                        continue;
                    }

                    ActionChosen agentActionChosen;
                    if (battlefieldAgent.ActiveCombatAI == Vals.AI.PlayerChoice)
                    {
                        var possibleActions = Agent.GetPossibleActions(rgs, globalId, agentId, battlefieldEntityIds);
                        agentActionChosen = receivePlayerInput(possibleActions);
                    }
                    else
                    {
                        agentActionChosen = Agent.GetAgentAICombatAction(rgs, globalId, battlefieldAgent.ActiveCombatAI, agentId, battlefieldEntityIds);
                    }

                    var results = Combat.ProcessAgentAction(rgs, agentId, agentActionChosen);

                    var output = Narrator.OutputForCombatMessages(rgs, results);

                    yield return(output);
                }

                var endOfBattleOutputs = GetEndOfBattleOutputs(rgs, battlefieldEntityIds);
                if (endOfBattleOutputs.Any())
                {
                    yield return(endOfBattleOutputs);

                    yield break;
                }
            }
        }
Exemple #15
0
        internal static long CreateBattlefield(EcsRegistrar rgs, IEnumerable <long> battlefieldEntityIds)
        {
            var battlefieldId        = rgs.CreateEntity();
            var battlefieldContainer = new Parts.Container {
                Description = "battlefield", Tag = Vals.ContainerTag.Battlefield, ItemsAreVisible = true, Preposition = "on"
            };

            battlefieldEntityIds = battlefieldEntityIds ?? new long[] { };
            foreach (long entityId in battlefieldEntityIds)
            {
                battlefieldContainer.EntityIds.Add(entityId);
            }

            rgs.AddPart(battlefieldId, battlefieldContainer);

            return(battlefieldId);
        }
Exemple #16
0
        internal static List <Messages.Combat> ApplyStance(EcsRegistrar rgs, long agentId, string stance)
        {
            var result = new Messages.Combat {
                Tick = rgs.NewId(), ActorId = agentId
            };
            var stances = rgs.GetParts <Parts.SkillsModifier>(agentId).Where(p => Stances.Contains(p.Tag));

            rgs.RemoveParts(agentId, stances);

            result.ActorAction = stance;
            if (stance == Vals.CombatAction.StanceAggressive)
            {
                rgs.AddPart(agentId, new Parts.SkillsModifier {
                    Tag         = Vals.CombatAction.StanceAggressive,
                    SkillDeltas = new Dictionary <string, int> {
                        [Vals.EntitySkillPhysical.Melee] = 1,
                        [Vals.EntitySkillPhysical.Dodge] = -1
                    }
                });
            }
            else if (stance == Vals.CombatAction.StanceDefensive)
            {
                rgs.AddPart(agentId, new Parts.SkillsModifier
                {
                    Tag         = Vals.CombatAction.StanceDefensive,
                    SkillDeltas = new Dictionary <string, int>
                    {
                        [Vals.EntitySkillPhysical.Melee] = -1,
                        [Vals.EntitySkillPhysical.Dodge] = 1
                    }
                });
            }
            else if (stance == Vals.CombatAction.StanceStandGround)
            {
                //default stance, so the removal of previous ones means we're done.
            }
            else
            {
                throw new ArgumentException($"Invalid stance {stance}.");
            }

            return(new List <Messages.Combat> {
                result
            });
        }
        //as Parts evolve, the blueprints won't keep up. different ways we could handle, one might be to cycle through
        // every blueprint in the folder and load/rewrite it, that will at least get new props in as nulls.

        /// <summary>
        /// Get a blueprint for an entity. Blueprint part Ids are all forced to 0 (to reduce possible
        /// confusion when reading by hand). This method has a limited exception to the no-references rule
        /// --an entity with Container or Anatomy parts will automatically have the entity related values emptied
        /// before writing (which makes them safe).
        /// </summary>
        internal static string GetBlueprintForEntity(EcsRegistrar rgs, long entityId)
        {
            //make a copy before changing anything, just in case the user wants to continue working with original.
            string originalPartsJson = rgs.SerializeEntityParts(entityId);
            var    revisedPartsList  = rgs.DeserializeParts(originalPartsJson);

            foreach (var part in revisedPartsList)
            {
                part.Id = 0;
                if (part is Parts.Container)
                {
                    ((Parts.Container)part).EntityIds.Clear();
                }
                if (part is Parts.Anatomy)
                {
                    var anatomy = (Parts.Anatomy)part;
                    //anatomy.SlotsEquipped = GetClearedAnatomySlotsEquipped(anatomy.SlotsEquipped);
                    anatomy.SlotsEquipped = anatomy.SlotsEquipped.ToDictionary(k => k.Key, v => 0L);
                }
            }

            return(rgs.SerializeEntityParts(revisedPartsList));
        }
Exemple #18
0
        //internal static HashSet<long> GetEntityIdsFromFirstTagged(EcsRegistrar rgs, long ownerId, string tag)
        //{
        //    var container = rgs.GetParts<Parts.Container>(ownerId).FirstOrDefault(c => c.Tag == tag);

        //    return container?.EntityIds ?? new HashSet<long>();
        //}

        internal static AlterContainerContentsResultsMessage AddOrRemoveInventory(Action action, EcsRegistrar rgs, long ownerId, long itemAddOrRemoveId)
        {
            var containerPart = rgs.GetParts <Parts.Container>(ownerId).Single(c => c.Tag == Vals.ContainerTag.Inventory);

            var ids = containerPart.EntityIds;

            if (action == Action.AddEntity && !ids.Contains(itemAddOrRemoveId))
            {
                ids.Add(itemAddOrRemoveId);
            }
            else if (action == Action.RemoveEntity && ids.Contains(itemAddOrRemoveId))
            {
                ids.Remove(itemAddOrRemoveId);
            }
            else
            {
                return(new AlterContainerContentsResultsMessage {
                    Succeeded = false, Output = "Can't add what already exists, or remove what does not exist."
                });
            }

            var ownerNamePart = rgs.GetParts <Parts.EntityName>(ownerId).Single();
            var itemNamePart  = rgs.GetParts <Parts.EntityName>(itemAddOrRemoveId).SingleOrDefault();

            string verbPhrase = (action == Action.AddEntity)
                ? $"adds {itemNamePart.GeneralName} to"
                : $"removes {itemNamePart.GeneralName} from";

            string actionPhrase = $"{ownerNamePart.ProperName} {verbPhrase} {ownerNamePart.Pronoun} {containerPart.Tag}.";

            return(new AlterContainerContentsResultsMessage {
                Succeeded = true, Output = actionPhrase
            });
        }
Exemple #19
0
 internal static AlterContainerContentsResultsMessage RemoveFromInventory(EcsRegistrar rgs, long ownerId, long toRemoveId, long?containerId = null)
 {
     return(AddOrRemoveInventory(Action.AddEntity, rgs, ownerId, toRemoveId));
 }
        internal static long GetEntityFromBlueprint(EcsRegistrar rgs, string blueprintName)
        {
            var blueprintJson = File.ReadAllText(_blueprintFolder + blueprintName + _blueprintSuffix);

            return(rgs.CreateEntity(blueprintJson));
        }
Exemple #21
0
        // do we want to pass in the random function for better testing? not sure, but let's try it.
        public static List <Messages.Combat> ResolveSingleTargetMelee(EcsRegistrar rgs, long attackerId, long weaponId, long targetId, Func <int> randoRange7)
        {
            var attacker = Bundle.GetAgentBundle(rgs, attackerId);

            if (weaponId == 0)
            {
                return(new List <Messages.Combat> {
                    new Messages.Combat {
                        Tick = rgs.NewId(), ActorId = attackerId, ActorAction = $"{attacker.EntityName.ProperName} starts to attack, but merely spins around in place, because its weaponId is 0. SHOULD NOT EVER HAPPEN."
                    }
                });
            }

            var weapon = Bundle.GetWeaponBundle(rgs, weaponId);

            //this is only temporary, later we'll have a clock/scheduler.
            var msg = new Messages.Combat
            {
                Tick         = rgs.NewId(),
                ActorId      = attackerId,
                TargetId     = targetId,
                ActorAction  = Vals.CombatAction.AttackWeaponMelee,
                TargetAction = Vals.CombatAction.Dodge,
                AttackVerb   = weapon.PhysicalObject.MeleeVerb
            };

            var attackerAdjustedSkills = Skills.GetAdjustedSkills(rgs, attackerId);
            var targetAdjustedSkills   = Skills.GetAdjustedSkills(rgs, targetId);

            int     attackRoll           = randoRange7();
            decimal attackCritMultiplier = GetDamageMultiplierFromRange7(attackRoll);
            int     attackerMeleeSkill   = attackerAdjustedSkills[Vals.EntitySkillPhysical.Melee];
            int     attackerAdjustedRoll = attackRoll + attackerMeleeSkill;

            msg.ActorCritical      = attackCritMultiplier.ToString();
            msg.ActorAdjustedSkill = attackerMeleeSkill;
            msg.ActorAdjustedRoll  = attackerAdjustedRoll;

            int targetDodgeRoll  = randoRange7();
            int targetDodgeSkill = targetAdjustedSkills[Vals.EntitySkillPhysical.Dodge];
            //dodge is a difficult skill and always reduced by 1.
            int targetAdjustedDodgeRoll = Math.Max(0, targetDodgeRoll + targetDodgeSkill - 1);

            msg.TargetAdjustedSkill = targetDodgeSkill;
            msg.TargetAdjustedRoll  = targetAdjustedDodgeRoll;

            int netAttack = Math.Max(0, attackerAdjustedRoll - targetAdjustedDodgeRoll);

            msg.NetActorRoll = netAttack;

            //a good attack gets whatever the attack crit damage multiplier is, a barely-attack gets a .5, and less gets a 0.
            decimal finalAttackMultiplier = (netAttack > 1) ? attackCritMultiplier
                : (netAttack == 1) ? 0.5m
                : 0m;

            var target = Bundle.GetAgentBundle(rgs, targetId);
            //heh, that distinct is important, since the same armor can now occupy multiple slots.
            var targetEquipmentIds = target.Anatomy.SlotsEquipped.Where(s => s.Value != 0).Select(s => s.Value).Distinct().ToList();

            // ReSharper disable once ConvertClosureToMethodGroup
            var targetDamagePreventers = targetEquipmentIds.SelectMany(eid => rgs.GetParts <Parts.DamagePreventer>(eid)).ToList();

            var damageAttempted = weapon.Damagers.Sum(d => d.DamageAmount) * finalAttackMultiplier;
            var damagePrevented = target.PhysicalObject.DefaultDamageThreshold + targetDamagePreventers.Sum(p => p.DamageThreshold);

            msg.AttemptedDamage = damageAttempted;

            //so we apply crit/attack multipliers first, then we subtract damage prevention, then we apply default damage multiplier and armor multipliers. whew!
            var damageDealt = Math.Max(0,
                                       (damageAttempted - damagePrevented)
                                       * target.PhysicalObject.DefaultDamageMultiplier
                                       * (targetDamagePreventers.Select(p => p.DamageMultiplier).Aggregate(1m, (p1, p2) => p1 * p2)));

            msg.NetDamage = damageDealt;

            target.PhysicalObject.Condition = target.PhysicalObject.Condition - (int)damageDealt;
            msg.TargetCondition             = target.PhysicalObject.Condition;

            if (target.PhysicalObject.Condition <= 0)
            {
                if (!target.Agent.CombatStatusTags.Contains(Vals.CombatStatusTag.Dead))
                {
                    target.Agent.CombatStatusTags.Add(Vals.CombatStatusTag.Dead);
                    msg.NewTargetCombatTags.Add(Vals.CombatStatusTag.Dead);
                }
            }

            return(new List <Messages.Combat> {
                msg
            });
        }
Exemple #22
0
 public static List <Messages.Combat> ResolveSingleTargetMelee(EcsRegistrar rgs, long attackerId, long weaponId, long targetId)
 {
     return(ResolveSingleTargetMelee(rgs, attackerId, weaponId, targetId, Rando.GetRange7));
 }
Exemple #23
0
        public static CombatChoicesAndTargets GetPossibleActions(EcsRegistrar rgs, long globalId, long agentId, List <long> battlefieldEntityIds)
        {
            var possibleCombatActions = new CombatChoicesAndTargets();

            var naturalWeaponsMap = rgs.GetPartSingle <Parts.NaturalWeaponsMap>(globalId);

            var agentAnatomy = rgs.GetPartSingle <Parts.Anatomy>(agentId);

            //var agentWieldingDisabled = agentAnatomyModifers
            //    .Where(m => m.EquipmentSlotDisabled == Vals.BodyEquipmentSlots.WieldObjectAppendage)
            //    .Select(m => m.EquipmentSlotDisabled)
            //    .ToList();

            var agentWieldedIds = agentAnatomy.SlotsEquipped
                                  .Where(s => s.Key == Vals.BodySlots.WieldHandLeft || s.Key == Vals.BodySlots.WieldHandRight || s.Key == Vals.BodySlots.WieldTwoHanded)
                                  .Select(s => s.Value)
                                  .ToList();
            var agentWieldedIdToWeaponEntityName = agentWieldedIds
                                                   .Where(id => id != 0)
                                                   .Distinct()
                                                   .Select(id => new { Id = id, Name = rgs.GetPartSingle <Parts.EntityName>(id) })
                                                   .ToDictionary(n => n.Id, n => n.Name);

            var agentNaturalWeaponSlots = naturalWeaponsMap.NaturalWeaponSets[agentAnatomy.NaturalWeaponsCategory];
            //MWCTODO: some magic happens here and we find out a wristblade or a laser eye has been equipped, and add that to the slots.
            // also any quickslots that might have attack items in them. we want quickslots. maybe a wristblade or a laser eye automatically adds a new, fixed quickslot for itself? i like this idea upon first think!
            var recordedIds = new HashSet <long>();

            foreach (var slot in agentNaturalWeaponSlots.Keys)
            {
                var weaponId = agentAnatomy.SlotsEquipped[slot];
                if (weaponId == 0L)
                {
                    weaponId = agentNaturalWeaponSlots[slot];
                }

                if (recordedIds.Contains(weaponId))
                {
                    continue;
                }

                var weapon = Bundle.GetWeaponBundle(rgs, weaponId);
                var choice = new AgentActionChoice
                {
                    AgentId        = agentId,
                    Category       = Vals.CombatCategory.TopLevelAction,
                    Description    = $"Melee {weapon.EntityName.GeneralName}",
                    Action         = Vals.CombatAction.AttackWeaponMelee,
                    WeaponBodySlot = slot,
                    WeaponEntityId = weaponId,
                    NextCategory   = Vals.CombatCategory.MeleeTarget
                };

                possibleCombatActions.Choices.Add(choice);
                recordedIds.Add(weaponId);
            }

            foreach (long id in battlefieldEntityIds)
            {
                if (id == agentId)
                {
                    continue;
                }

                var entityName  = rgs.GetPartSingleOrDefault <Parts.EntityName>(id);
                var entityAgent = rgs.GetPartSingleOrDefault <Parts.Agent>(id);

                if (entityName == null || entityAgent == null)
                {
                    continue;
                }

                if (entityAgent.CombatStatusTags.Intersect(Vals.CombatStatusTag.CombatTerminalStatuses).Any())
                {
                    continue;
                }

                possibleCombatActions.MeleeTargets.Add(id, entityName.ProperName);
            }

            //add stances
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId     = agentId,
                Category    = Vals.CombatCategory.AllStances,
                Description = "Stance (Defensive)",
                Action      = Vals.CombatAction.StanceDefensive
            });
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId     = agentId,
                Category    = Vals.CombatCategory.AllStances,
                Description = "Stance (Stand Ground)",
                Action      = Vals.CombatAction.StanceStandGround
            });
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId     = agentId,
                Category    = Vals.CombatCategory.AllStances,
                Description = "Stance (Aggressive)",
                Action      = Vals.CombatAction.StanceAggressive
            });

            //add top-level choices that we know are present: note that we already did NextCategory = primary-melee because the description there is context dependent.
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId      = agentId,
                Category     = Vals.CombatCategory.TopLevelAction,
                Description  = "All Melee Options",
                NextCategory = Vals.CombatCategory.AllMelee
            });
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId      = agentId,
                Category     = Vals.CombatCategory.TopLevelAction,
                Description  = "Change Stance",
                NextCategory = Vals.CombatCategory.AllStances
            });
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId     = agentId,
                Category    = Vals.CombatCategory.TopLevelAction,
                Description = "Run Away",
                Action      = Vals.CombatAction.RunAway
            });
            possibleCombatActions.Choices.Add(new AgentActionChoice
            {
                AgentId     = agentId,
                Category    = Vals.CombatCategory.TopLevelAction,
                Description = "Do Nothing",
                Action      = Vals.CombatAction.DoNothing
            });


            return(possibleCombatActions);
        }
Exemple #24
0
        //for now, hardcoding this is fine. once we get a dozen, reconsider (or maybe not).
        internal static Dictionary <string, Dictionary <Vals.BodySlots, long> > InitializeNaturalWeapons(EcsRegistrar rgs)
        {
            var humanPunchId    = Blueprinter.GetEntityFromBlueprint(rgs, Vals.NaturalWeaponNames.HumanPunch);
            var humanNatWeapons = new Dictionary <Vals.BodySlots, long>
            {
                [Vals.BodySlots.WieldHandLeft]  = humanPunchId,
                [Vals.BodySlots.WieldHandRight] = humanPunchId
            };

            var wolfBiteId     = Blueprinter.GetEntityFromBlueprint(rgs, Vals.NaturalWeaponNames.WolfBite);
            var wolfNatWeapons = new Dictionary <Vals.BodySlots, long>
            {
                [Vals.BodySlots.VertebrateMouth] = wolfBiteId
            };

            return(new Dictionary <string, Dictionary <Vals.BodySlots, long> >
            {
                [Vals.NaturalWeaponSet.Human] = humanNatWeapons,
                [Vals.NaturalWeaponSet.Wolf] = wolfNatWeapons
            });
        }
Exemple #25
0
        internal static List <Output> OutputForCombatMessages(EcsRegistrar rgs, List <Messages.Combat> messages)
        {
            var results = new List <Output>();

            foreach (var msg in messages)
            {
                //MWCTODO: add a debug switch for this info
                results.Add(new Output {
                    Category = OutputCategory.Text, Data = $"(DEBUG: attempted damage {msg.AttemptedDamage} net damage {msg.NetDamage} critical {msg.ActorCritical} target condition {msg.TargetCondition})"
                });

                var result = new Output {
                    Category = OutputCategory.Text
                };

                var sb = new StringBuilder();

                var actorNames = rgs.GetPartSingle <Parts.EntityName>(msg.ActorId);

                if (Vals.CombatAction.AllStances.Contains(msg.ActorAction))
                {
                    result.Data = $"{actorNames.ProperName} assumes a {msg.ActorAction} stance.";
                    results.Add(result);
                }
                else if (msg.ActorAction == Vals.CombatAction.DoNothing)
                {
                    result.Data = $"{actorNames.ProperName} stands still and looks around dumbly. What do?";
                    results.Add(result);
                }
                else if (msg.ActorAction == Vals.CombatAction.RunAway)
                {
                    result.Data = $"{actorNames.ProperName} tries to run away, and {actorNames.Pronoun} feet spin on the floor in a cartoon whirlwind, but nothing actually happens. Uh oh.";
                    results.Add(result);
                }
                else if (msg.TargetId == 0)
                {
                    result.Data = $"MWCTODO: we don't know what {msg.ActorAction} is.";
                    results.Add(result);
                }

                //if the result has data, we're done with this iteration.
                if (!string.IsNullOrEmpty(result.Data))
                {
                    continue;
                }

                var targetNames = rgs.GetPartSingle <Parts.EntityName>(msg.TargetId);

                sb.Append($"{actorNames.ProperName} {PresentTenseVerb(msg.AttackVerb)} with a {msg.ActorAdjustedRoll} attack.");

                if (msg.ActorAdjustedRoll <= 0)
                {
                    sb.Append($", presenting no real threat to {targetNames.ProperName}, who steps back from it casually.");
                    result.Data = sb.ToString();
                    AppendNoDamageButNearlyDead(sb, targetNames.ProperName, targetNames.Pronoun, msg.TargetCondition);
                    results.Add(result);
                    continue;
                }

                if (msg.AttemptedDamage <= 0)
                {
                    sb.Append($", but {targetNames.ProperName} sees it coming and ducks out of the way, avoiding it entirely.");
                    result.Data = sb.ToString();
                    AppendNoDamageButNearlyDead(sb, targetNames.ProperName, targetNames.Pronoun, msg.TargetCondition);
                    results.Add(result);
                    continue;
                }

                if (msg.NetDamage <= 0)
                {
                    sb.Append($" and connects! Unfortunately for {actorNames.ProperName}, the attack isn't solid, and {targetNames.ProperName}'s armor protects {targetNames.Pronoun} entirely.");
                    result.Data = sb.ToString();
                    AppendNoDamageButNearlyDead(sb, targetNames.ProperName, targetNames.Pronoun, msg.TargetCondition);
                    results.Add(result);
                    continue;
                }

                var targetConditionString = GetLivingThingConditionDesc(msg.TargetCondition);

                //any fatality or any crit ought to get a special fatality/crit message.
                if (msg.NetDamage > 7000)
                {
                    sb.Append($".  The attack is nearly perfect, and it is devastating. {targetNames.ProperName} is {targetConditionString}.");
                }
                else if (msg.NetDamage > 4000)
                {
                    sb.Append($".  It's a fine strike, penetrating the armor and inflicting a nasty wound. {targetNames.ProperName} is {targetConditionString}.  ");
                }
                else if (msg.NetDamage > 1900)
                {
                    sb.Append($".  A hit . . . hard enough to notice. {targetNames.ProperName} is {targetConditionString}.  ");
                }
                else if (msg.NetDamage > 0)
                {
                    if (msg.TargetCondition > 2000)
                    {
                        sb.Append($".  It doesn't amount to much, though. Barely a scratch. It will take a lot of these to wear down {targetNames.ProperName}.");
                    }
                    else if (msg.TargetCondition > 1)
                    {
                        sb.Append($".  Earlier, {targetNames.ProperName} would have laughed at a wound like this. It's not much more than a scratch. {targetNames.ProperName} isn't laughing now. When you're already gravely wounded, any attack that lands might be your last, and {targetNames.ProperName} is already looking death in the eyes.");
                    }
                    else
                    {
                        sb.Append($".  It's not much of an attack, but it didn't need to be. {targetNames.ProperName} watches in horror as the point slides home. {targetNames.Pronoun} lets go of {targetNames.Pronoun} weapon and reaches up for a moment before falling to the ground, dead.");
                    }
                }

                result.Data = sb.ToString();

                results.Add(result);
            }

            return(results);
        }
Exemple #26
0
        internal static Output Equip(EcsRegistrar rgs, long equipperId, long gearId)
        {
            var output = new Output {
                Category = "Text"
            };

            //MWCTODO: bundleize this

            var equipperAnatomy        = rgs.GetPartSingle <Parts.Anatomy>(equipperId);
            var equipperPhysicalObject = rgs.GetPartSingle <Parts.PhysicalObject>(equipperId);
            var equipperEntityName     = rgs.GetPartSingle <Parts.EntityName>(equipperId);
            var gearPhysicalObject     = rgs.GetPartSingle <Parts.PhysicalObject>(gearId);
            var gearEntityName         = rgs.GetPartSingle <Parts.EntityName>(gearId);

            //someday we'll support gear that occupies multiple slots or using complex slot logic.
            if (gearPhysicalObject.EquipmentSlots == Vals.BodySlots.NoSlot || gearPhysicalObject.EquipmentSlots == Vals.BodySlots.Special)
            {
                throw new NotImplementedException();
            }

            //MWCTODO++: rework this to use new DisabledSlot entity concept.
            //var equipperDisabledSlots = equipperAnatomyModifications.Select(m => m.NewEquipmentSlotsDisabled).ToList();
            var equipperSlots = equipperAnatomy
                                .SlotsEquipped
                                //.Where(kvp => !equipperDisabledSlots.Contains(kvp.Key))
                                .ToDictionary(d => d.Key, d => d.Value);

            var matchingEquipperSlots = equipperSlots.Where(slot => (slot.Key & gearPhysicalObject.EquipmentSlots) == slot.Key).ToList();

            if (!matchingEquipperSlots.Any())
            {
                output.Data = $"{equipperEntityName.ProperName} doesn't have the right body parts to equip the {gearEntityName.GeneralName}.";
                return(output);
            }

            if (matchingEquipperSlots.All(s => s.Value != 0))
            {
                output.Data = $"There's no open place to equip the {gearEntityName.GeneralName}. Unequip something else first.";
                return(output);
            }

            //we will special case 2h weapons here, though. and body armor!
            bool gearIs2Handed        = (gearPhysicalObject.EquipmentSlots & Vals.BodySlots.WieldTwoHanded) == Vals.BodySlots.WieldTwoHanded;
            bool gearIsHumanBodyArmor = (gearPhysicalObject.EquipmentSlots & Vals.BodySlots.HumanBodyArmor) == Vals.BodySlots.HumanBodyArmor;

            if (gearIs2Handed && Vals.BodyPlan.BodySlotsEncompassedByOtherSlot[Vals.BodySlots.WieldTwoHanded].Any(s => equipperAnatomy.SlotsEquipped[s] != 0))
            {
                output.Data = $"Two free hands are needed for the {gearEntityName.GeneralName}. Unequip something else first.";
                return(output);
            }

            if (gearIsHumanBodyArmor && Vals.BodyPlan.BodySlotsEncompassedByOtherSlot[Vals.BodySlots.HumanBodyArmor].Any(s => equipperAnatomy.SlotsEquipped[s] != 0))
            {
                output.Data = $"Other equipment is in the way of equipping the {gearEntityName.GeneralName}. Unequip other gear first.";
                return(output);
            }

            //by equipping 2h-weapons and body armor in ALL the related slots, it makes checking equipment/weapons easy elsewhere. it will complicate unequip, though.

            var firstOpenSlot = matchingEquipperSlots.First(s => s.Value == 0);

            equipperAnatomy.SlotsEquipped[firstOpenSlot.Key] = gearId;
            if (gearIs2Handed)
            {
                foreach (var slot in Vals.BodyPlan.BodySlotsEncompassedByOtherSlot[Vals.BodySlots.WieldTwoHanded])
                {
                    equipperAnatomy.SlotsEquipped[slot] = gearId;
                }
            }

            if (gearIsHumanBodyArmor)
            {
                foreach (var slot in Vals.BodyPlan.BodySlotsEncompassedByOtherSlot[Vals.BodySlots.HumanBodyArmor])
                {
                    equipperAnatomy.SlotsEquipped[slot] = gearId;
                }
            }

            output.Data = $"{equipperEntityName.ProperName} equips the {gearEntityName.GeneralName}.";
            return(output);
        }