My goal was to break up the game into a set of discrete steps that could be implemented quickly and independently.  I got six days in before realizing that wasn’t going to be universally possible :P.  I’ve added the first part of support for mobs, but it required me to partially fill in other subsystems and there are still a few steps to go.  Here’s what’s been added:

1) MonsterTypes and Xml-based MonsterType definitions:

<Monsters>
  <Monster Id="Mon4" Name="Rabid Dog" Family="Animal" Rarity="Common" HP="0" Tile="DogBrown" Level="1-5" Description="tbd: generic monster desc"/>
  <Monster Id="Mon72" Name="Hobgoblin" Family="Goblinoid" Rarity="Uncommon" HP="1" Tile="HalfOgreFighter" Level="1-5" Description="tbd: generic monster desc"/>
  <Monster Id="Mon9" Name="Grey Wolf" Family="Animal"  Rarity="Rare" HP="0" Tile="GrayWolf" Level="1-5" Description="tbd: generic monster desc"/>
  <Monster Id="Mon12" Name="Sewer Rat" Family="Animal" Rarity="Common" HP="-1" Tile="RatBrownDark" Level="1-5" Description="tbd: generic monster desc"/>
</Monsters>

The “HP” is “relative hitpoints” and ranges between -2 and 2. It’s intended to specify the amount of HP that that monster has relative to it’s family. There’s a (most complete) equation in there that calculates maximum hitpoints based on family, level, and relative HP. This allows (e.g.) a level 1 Dragon to have much higher hit points than a level 1 goblinoid. There’s still a few things to tweak here – I mostly wanted the basic system in place so that I could have killable monsters. (note to self: I should probably have just hacked in “100HP” rather than hacking in a half-complete system).

2) Monsters as Actors. This was easy to slot in, and mostly consisted of implementing basic AI for “DecideNextAction.”
3) Basic AI. The TurnMgr gives each Actor a chance to figure out what it wants to do; for the Player, that’s typically defined by the last command they specified. For Monsters, we need basic AI. One cool thing is that I was able to leverage the existing lighting/visibility determination code to calculate the cells that the monster can see each turn.  That in turn required things like Affinity (eventually you’ll be able to charm monsters and have pets, and this’ll allow things like mobs that hate other mob types).  Here’s the Monster.DecideNextAction() function; it determines who to attack by summing up a variety of factors to determine who it can see that it hates.  It’s functional in that it’ll find whoever it hates the most, but there’s more to slot in:


        override public void DecideNextAction()
        {
            if (this.IsDead)
                return;
            SetNextAction(null);

            // Look at all of the Actors around us; determine which one (if any) we hate the most, and go after it...
            Actor mostHatedTarget = null;
            int hatredMeasure = 0;
            foreach (Cell visibleCell in ThisTurnVisibleCells)
            {
                Actor actor = visibleCell.ContainsActor();
                if (actor != null && actor != this && !actor.IsDead)
                {
                    // There's an Actor in the cell - do we hate it more than the mostHatedTarget?

                    // Start by checking our Affinity to the actor in the cell.
                    // AffinityForActor() returns an Affinity value; we convert that to a -20 to 20 value
                    int newHatredMeasure = ConvertToHatredMeasurement(AffinityForActor(actor));

                    // Offset a bit by the 'weakness' of the actor.  If it's near dead, then swarm it.
                    // TBD: per-monster type IQ here.  Some monsters might attack the weakest; some might attack the most-closest-to-dead, etc.
                    if (actor.HitPoints / actor.MaximumHitPoints < .25f)
                        newHatredMeasure += 2;

                    // Next, offset it a bit by distance - we want to attack things that are closer (unless we REALLY hate something, then
                    // we're willing to go a bit farther to get to it.
                    // Subtract 1 affinity value per cell.
                    newHatredMeasure -= this.DistanceTo(actor);

                    // Next, offset it by a little bit so that we prefer to keep attacking the same mob; this keeps an Actor from switching
                    // targets arbitrarily; something needs to happen to make them hate the other target more.
                    // TBD: Track who's (a) doing damage to last turn, and (b) who's done the most damage.
                    // TBD: "healing" mobs I hate should increase my aggro towards the hearler
                    // Offset by two so that we'll chase creatures that run away (Rather than being distracted by others that are closer)
                    if (actor == AggroTarget)
                        newHatredMeasure += 2;

                    // Now that we know how we feel about the Actor in the cell - check if we hate them more than the previous target
                    if (newHatredMeasure > hatredMeasure)
                    {
                        // We have a new contestant! Go after them instead...
                        mostHatedTarget = actor;
                        hatredMeasure = newHatredMeasure;
                    }

                    // tbd: if we can't get to our mostHatedTarget, then attack other Hated Actors in between us and them.
                }
            }

            // If there's a new most hated actor, then set them as our Aggro target
            if (AggroTarget != mostHatedTarget && mostHatedTarget != null && mostHatedTarget > HatredTowardsCurrentAggroTarget)
            {
                AggroTarget = mostHatedTarget;
                HatredTowardsCurrentAggroTarget = mostHatedTarget;
            }

            if (AggroTarget == null)
            {
                // Nothing to attack within sight either (sniff).  Wander around a bit (potentially).
                // tbd-later: odds of wandering depend on the monstertype.
                if (Rand.Next(10) < 10)
                {
                    // pick a random (valid) neighboring cell to wander to.  This is not gauranteed to
                    // generate a valid option, but that's okay.
                    for (int i = 0; i < 10; i++)  // try 10 times
                    {
                        // Pick a random direction
                        Direction dir = (Direction)Rand.Next(8);
                        Cell dest = Location.Neighbor(dir);
                        if (dest != null && dest.CanWalkOn())
                        {
                            SetNextAction(Action_Move.Create(this, dir));
                            break;
                        }
                    }
                }
                return;
            }

            // If here, then we have a target to go after.
            Cell targetLoc = AggroTarget.Location;

            // If we're right next to the player, then next action is to do melee attack
            // TBD: This is mob-specific; e.g. a ranged monster might attack differently.  Also need
            // to take Flee, quaff, etc into account.
            if (targetLoc.Neighbors.Contains(Location))
                SetNextAction(Action_Attack.Create(this, AggroTarget));
            else
            {
                TryToMoveTo(targetLoc);
            }
        }

3) Attack_Action and Event_Attack. I didn’t want to implement these, but given the mob AI above, they quickly swarmed the player and since two Actors can’t occupy the same Cell, no one could move :).  So I added in the underpinnings of the attack flow.  It’s tied into the Event System, allowing modification or filtering of the AttemptStrike and DoDamage events.  However, since I don’t currently have Items implemented, it’s hard to fire the “EventType.TargetHit” event on the player’s weapon :P.  So for now when you attack a monster, it’s insta-kill (just to unblock the player from moving).  I followed the design pattern established by Action_Move and Event_Move (object pooling, event-system-integration, etc) – cool to see that it could work, but the sheer amount of code is concerning.  Given a few more Actions I’ll need to see if there’s a better design pattern here…

4) Lots of little spackling; e.g. implementing empty Item and ItemType classes so that I could give the Player object a WieldedWeapon upon which to fire Events.  Hmm; per the note to self above, I probably shouldn’t have done this (especially since the code that uses WieldedWeapon is commented out).  Ah well, Items are coming soon enough.

Things to do next:

  • Lots of holes to fill in given the above.  Either cull them or finish them; per above, I don’t like hacking in 25% complete subsystems :P.
  • Add persistence of monsters.  Didn’t have a chance to get to it, so I currently throw an exception to make sure I don’t forget ;-).
  • Tie Monsters into the LevelTheme and add MonsterClasses.
  • Add Monster attacking the player. Honestly, it may already be happening 🙂
  • Add code to stop the player from exploring if there’s something “of interest” e.g. a Monster.

I should be able to get to those tomorrow night.

Given all of the above, the game now has monsters with AI that target the Player, and the ability to attack monsters by tapping on them!  Here’s what a buck’ll buy you today:

Updated source: http://wanderlinggames.com/files/dungeon/dungeon-3-17-11.zip

Updated executable: http://wanderlinggames.com/files/dungeon/dungeonexe-3-17-11.zip

Advertisements