Welcome to the Builder Academy

Question Mob Switching

More
09 Mar 2026 01:48 #10992 by soth
Mob Switching was created by soth
Hello,
Not sure if this has been implemented yet by someone or not, search didn't really find anything when I tried it. I had thought about the T/H/D (tank, healer, dps) and then thought about how I could make mobs switch targets to start with based on an aggro table or for instance if the DPS was doing crazy amount of damage compared to the tank. Possibly implementing a threat table of some sort and having it check each round or that might be too intensive? What are some thoughts on this and any ideas on how to implement it?

If this was implemented I could see giving the tank a taunt, provoke or other skill to try and hold aggro.

TY!

Please Log in or Create an account to join the conversation.

More
10 Mar 2026 13:52 #10993 by WhiskyTest
Replied by WhiskyTest on topic Mob Switching
Sounds like a neat feature! 

I would take a look at the existing aggressive mob code block on line 92 in mobact.c

You can plagiarize most of it to create a function that looks for the player with the highest 'threat' in the room.
So instead of all those checks for mob flags, just look at the value of GET_THREAT(vict) and remember who has the highest.
Call it when you want to evaluate target switching. Eg: from mobile_activity it's every 10 seconds, or perhaps you want it every combat round.


I imagine 'threat' would be a number that goes higher when you do amazing things, and wears off after some time. Adding another 'points' stat could work, eg:

struct char_point_data
{
+  sh_int threat;     /**< Current Threat level  */

   sh_int mana;     /**< Current mana level  */
etc..


Make a macro
+ #define GET_THREAT(ch)    ((ch->points.threat))

Have threat clear at the end of the fight, or each tick, or mud events etc.
The following user(s) said Thank You: thomas

Please Log in or Create an account to join the conversation.

More
11 Mar 2026 11:49 - 11 Mar 2026 11:57 #10996 by WhiskyTest
Replied by WhiskyTest on topic Mob Switching
I changed my mind. Having threat attached to the player would mean all mobs would treat them the same way, but that's not necessarily an accurate reflection..
What if said player hadn't even attacked a mob - why would that particular mob consider them a threat?

Perhaps having each mob use their own threat assessment works better. We do a similar thing with mob memory..

If we created a new struct that holds a player pointer and their threat level per mob:
(Warning - Semi-tested code follows! :) )
Code:
/** Special data used by NPCs, not PCs */ struct mob_special_data {   memory_rec *memory; /**< List of PCs to remember */   byte attack_type;   /**< The primary attack type (bite, sting, hit, etc.) */   byte default_pos;   /**< Default position (standing, sleeping, etc.) */   byte damnodice;     /**< The number of dice to roll for damage */   byte damsizedice;   /**< The size of each die rolled for damage. */   struct threat_data *threat_list; }; struct threat_data {   struct char_data *ch;   int threat;   struct threat_data *next; };

When we damage a mob we add threat:
Code:
void add_threat(struct char_data *mob, struct char_data *ch, int amount) {   struct threat_data *t;   if (!IS_NPC(mob) || !ch)     return;   for (t = mob->mob_specials.threat_list; t; t = t->next) {     if (t->ch == ch) {       t->threat += amount;       return;     }   }   CREATE(t, struct threat_data, 1);   t->ch = ch;   t->threat = amount;   t->next = mob->mob_specials.threat_list;   mob->mob_specials.threat_list = t; }

And then during combat you can evaluate which character in the threat_list has the highest value:
Code:
struct char_data *get_top_threat(struct char_data *mob) {   struct threat_data *t;   struct char_data *target = NULL;   int highest = 0;   for (t = mob->mob_specials.threat_list; t; t = t->next) {     if (t->ch && t->threat > highest && IN_ROOM(t->ch) == IN_ROOM(mob)) {       highest = t->threat;       target = t->ch;     }   }   return target; }

Then in the fight sequence target the highest threat player, somewhere in perform_violence?
Code:
      target = get_top_threat(ch);       if (target && FIGHTING(ch) != target) {         send_to_room(IN_ROOM(ch), "%s switches target to %s.\r\n", GET_NAME(ch), GET_NAME(target));         set_fighting(ch, target);       }

Ps: you'd need to clear memory to prevent leaks when the mob dies, or combat stops. 

This approach is a bit more involved for sure, but I feel it gives more flexibility to expand.
You can add functions that reduce specific threat , like a skill that reduces threat by 20%, taunt could double your threat, stitch up a friend by increasing their threat instead of your own...
Last edit: 11 Mar 2026 11:57 by WhiskyTest.

Please Log in or Create an account to join the conversation.

More
14 Mar 2026 02:42 - 14 Mar 2026 02:52 #10998 by soth
Replied by soth on topic Mob Switching
Hey Whiskey, I too, your suggestions along with some other ideas, some I was already working on before I got to look at your code, but I will try and post what I have done. Right now I have given each class a threat multiplier. You can tweak this as one sees fit and from testing.

A group of two, war and a mage go into a fight. After a few rounds the mage cast a high level spell and suddenly takes the lead in the hate table. The warrior will probably have to do a "taunt" or "shout" of some type to regain the hate or possibly a 'rescue" will work by elevating the warriors number on the aggro table of the mob.  The hard part I had was when a mage did an AOE spell with 4 mobs in the room. I finally got the warrior to get on each mobs aggro table and leap above the mag in terms of hate.

Here is some of the code.
<fight.c>
Code:
/** * @author  Soth * @date    2026.03.10 * @brief   Modify threat for group members * @details Updates the aggro table for the mob. * @param   victim: the mob being hit or seeing a heal * @param   ch: the player doing the action * @param   amount: the raw damage or healing value   */ void modify_threat(struct char_data *victim, struct char_data *ch, int amount) {     struct aggro_node *node;     float multiplier = 1.0;     /* Safety checks: Only NPCs have aggro tables, and only players go on them. */     if (!victim || !ch || !IS_NPC(victim) || IS_NPC(ch))         return;     /* Class Multipliers: Help define the 'Roles' in your group */     switch (GET_CLASS(ch)) {         case CLASS_WARRIOR:             multiplier = 1.5;  /* Warriors generate slightly more hate to act as tanks */             if (AFF_FLAGGED(ch, AFF_VANGUARD)) {                 multiplier = 2.1; // 50% increase in threat generation while in Vanguard             }             break;         case CLASS_CLERIC:             multiplier = 0.9;  /* Healers generate slightly less hate for balance */             break;         case CLASS_MAGIC_USER:             multiplier = 0.8;  /* Standard threat for dps */             break;         case CLASS_NECROMANCER:             multiplier = 1.0;  /* Standard threat for dps */             break;         case CLASS_THIEF:             multiplier = 1.0;  /* Standard threat for dps */             break;         case CLASS_MONK:             multiplier = 1.0;  /* Standard threat for dps */             break;         default:             multiplier = 1.0;  /* Standard threat for dps */             break;     }     int total_threat = (int)(amount * multiplier);     /* Search for the player on the mob's table */     for (node = victim->mob_specials.aggro_table; node; node = node->next) {         if (node->ch == ch) {             node->amount += total_threat;             return;         }     }     /* If they aren't on the table yet, add a new node at the head */     CREATE(node, struct aggro_node, 1);     node->ch = ch;     node->amount = total_threat;     node->next = victim->mob_specials.aggro_table;     victim->mob_specials.aggro_table = node; } /** * @author * @date * @brief */ int damage(struct char_data *ch, struct char_data *victim, int dam, int attacktype) {     //log("DEBUG: damage() called. Attacker: %s, Victim: %s, Skill: %d", ch ? GET_NAME(ch) : "NONE", GET_NAME(victim), attacktype);     if (!validate_damage(ch, victim, &dam))         return 0;     /* NEW LOGIC: If the attacker is NULL OR the attacker is the victim (self-damage) */     /* and they are currently in a fight, give credit to the opponent. */     if ((ch == NULL || ch == victim) && FIGHTING(victim) != NULL) {         ch = FIGHTING(victim);     }     setup_combat(ch, victim);     //log("DEBUG: setup_combat complete. Victim fighting: %s", FIGHTING(victim) ? GET_NAME(FIGHTING(victim)) : "NONE");     dam = apply_attacker_modifiers(ch, victim, dam);     check_death_blow(ch, victim);     dam = apply_damage_mitigation(ch, victim, dam);     /* Vanguard stance for a warrior */     if (AFF_FLAGGED(ch, AFF_VANGUARD)) {         dam = (int)(dam * 0.80); // Reduce damage by 20%     }     /* Apply the damage to HP */     GET_HIT(victim) -= dam;     /* Inside damage() function in fight.c */     if (victim != ch) {         int threat_amt = dam;         if (attacktype == 170) { // HARDCODE 170 to be sure             threat_amt = 15000;             //log("SHOUT_LOG: SUCCESS! Applied 15,000 threat to %s", GET_NAME(victim));         } else {             // Normal combat threat             if (AFF_FLAGGED(ch, AFF_VANGUARD)) {                 threat_amt = dam * 10;             }         }         if (threat_amt > 0 || attacktype == 170) {             modify_threat(victim, ch, threat_amt);         }     }     /* Only gain swing-by-swing XP if we have a valid attacker */     if (ch && ch != victim)         gain_exp(ch, dam / 2, NULL, TRUE);     update_pos(victim);     send_damage_output(ch, victim, dam, attacktype);     handle_victim_state(ch, victim);     if (GET_POS(victim) == POS_DEAD) {         //log("DEBUG: damage() detected POS_DEAD. ch is: %s", ch ? GET_NAME(ch) : "NULL");         return handle_death(ch, victim);     }     return dam; } /** * @author  Soth * @date    2026.03.09 * @brief   Gets threat from players */ long get_threat(struct char_data *mob, struct char_data *target) {     struct aggro_node *node;     if (!mob || !target || !IS_NPC(mob)) return 0;     for (node = mob->mob_specials.aggro_table; node; node = node->next) {         if (node->ch == target)             return node->amount;     }     return 0; } /* Control the fight * This is called every 2 seconds from <comm.c>*/ /** * @date    2026.03.06 * @brief Performs the violence */ #define VALID_FIGHTER(ch) (FIGHTING(ch) && IN_ROOM(ch) == IN_ROOM(FIGHTING(ch))) void perform_violence(void) {     struct char_data *ch, *tch, *next_combat_list;     struct aggro_node *node, *top_node = NULL;     long max_threat = -1;     float switch_threshold = 1.1;     for (ch = combat_list; ch; ch = next_combat_list) {         next_combat_list = ch->next_fighting;         max_threat = -1;         top_node = NULL;         /* only NPCs use the aggro table to switch targets */         if (IS_NPC(ch) && FIGHTING(ch)) {             for (node = ch->mob_specials.aggro_table; node; node = node->next) {                 if (node->ch && IN_ROOM(node->ch) == IN_ROOM(ch) && GET_POS(node->ch) > POS_DEAD) {                     if (node->amount > max_threat) {                         max_threat = node->amount;                         top_node = node;                     }                 }             }             if (top_node && FIGHTING(ch) && top_node->ch != FIGHTING(ch)) {                     long current_target_threat = get_threat(ch, FIGHTING(ch));                 switch_threshold = 1.1;                                 if (AFF_FLAGGED(FIGHTING(ch), AFF_VANGUARD)) {                     switch_threshold = 1.5;                 }                 if (max_threat > current_target_threat && max_threat <= (current_target_threat * switch_threshold)) {                     if (rand_number(0, 2) == 0) {                         act("\tO$n\tn glares menancingly at \tO$N\tn, looking like $e might switch targets!", FALSE, ch, 0, top_node->ch, TO_NOTVICT);                         act("\tO$n\tn glares menancingly at YOU, looking like $e might switch targets!", FALSE, ch, 0, top_node->ch, TO_VICT);                     }                 }                 if (max_threat > (current_target_threat * switch_threshold)) {                     act("\tO$n \tRscreams in a blind rage and turns toward \tO$N!\tn", FALSE, ch, 0, top_node->ch, TO_NOTVICT);                     act("\tO$n\tR snarls and turns their eyes on YOU!\tn", FALSE, ch, 0, top_node->ch, TO_VICT);                                                    stop_fighting(ch);                     set_fighting(ch, top_node->ch);                 }             }         }         /* Skip if not fighting or not in same room */         if (!FIGHTING(ch) || IN_ROOM(ch) != IN_ROOM(FIGHTING(ch))) {                         /* --- NEW VANGUARD INTERCEPT LOGIC --- */             /* If we aren't fighting, but we are a Vanguard in a group, look for trouble */             if (!IS_NPC(ch) && AFF_FLAGGED(ch, AFF_VANGUARD) && GROUP(ch)) {                 struct char_data *vict;                 for (vict = world[IN_ROOM(ch)].people; vict; vict = vict->next_in_room) {                     if (vict != ch && GROUP(vict) == GROUP(ch) && FIGHTING(vict) && IS_NPC(FIGHTING(vict))) {                         act("@WYou see $N under attack and leap to intercept!@n", FALSE, ch, 0, vict, TO_CHAR);                         act("@W$n leaps to intercept the attack on $N!@n", FALSE, ch, 0, vict, TO_NOTVICT);                         set_fighting(ch, FIGHTING(vict));                         break;                     }                 }             }             /* ------------------------------------ */                         if (!FIGHTING(ch)) {                 stop_fighting(ch);                 continue;             }         }         /* Mob wait handling */         if (IS_NPC(ch) && GET_MOB_WAIT(ch) > 0) {             GET_MOB_WAIT(ch) -= PULSE_VIOLENCE;             continue;         }         if (IS_NPC(ch))             GET_MOB_WAIT(ch) = 0;         /* Ensure fighting position */         if (GET_POS(ch) < POS_FIGHTING) {             if (IS_NPC(ch)) {                 GET_POS(ch) = POS_FIGHTING;                 act("$n scrambles to $s feet!", TRUE, ch, 0, 0, TO_ROOM);             } else {                 send_to_char(ch, "You can't fight while sitting!!\r\n");                 continue;             }         }         /* Group auto-assist (standard) */         if (GROUP(ch) && GROUP(ch)->members && GROUP(ch)->members->iSize) {             struct iterator_data Iterator;             tch = (struct char_data *)merge_iterator(&Iterator, GROUP(ch)->members);             for (; tch; tch = next_in_list(&Iterator)) {                 if (tch == ch || (!IS_NPC(tch) && !PRF_FLAGGED(tch, PRF_AUTOASSIST)))                     continue;                 if (IN_ROOM(ch) != IN_ROOM(tch) || FIGHTING(tch) || GET_POS(tch) != POS_STANDING)                     continue;                 if (!CAN_SEE(tch, ch))                     continue;                 do_assist(tch, GET_NAME(ch), 0, 0);             }         }         /* MAIN HIT */         if (FIGHTING(ch))             hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);         if (!FIGHTING(ch))             continue;         /* Contagion, multi-attacks, and spec-procs follow... */         /* Contagion effect */         if (IS_NPC(ch) && AFF_FLAGGED(ch, AFF_CONTAGION)) {             if (rand_number(1, 100) > 50) {                 damage(ch, ch, rand_number(1, GET_LEVEL(ch) ), SPELL_POISON);                 if (!FIGHTING(ch))                     continue;             }         }         /* Mob extra attacks */         if (IS_NPC(ch) && GET_LEVEL(ch) > 5) {             for (int i = 0; i < 2; i++) {                 if (!FIGHTING(ch) || IN_ROOM(ch) != IN_ROOM(FIGHTING(ch))) {                     stop_fighting(ch);                     break;                 }                 if (MOB_FLAGGED(ch, MOB_BERSERKER)) {                     if (rand_number(1, GET_LEVEL(ch)) < (GET_LEVEL(ch) / 2)) {                         hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                         if (!FIGHTING(ch))                             break;                         hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                         if (!FIGHTING(ch))                             break;                     }                 } else {                     if (rand_number(1, GET_LEVEL(ch)) < (GET_LEVEL(ch) / 3)) {                         hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                         if (!FIGHTING(ch))                             break;                     }                 }             }         }         /* Player multi-attacks */         if (!IS_NPC(ch) && FIGHTING(ch)) {             /* Off-hand attack */             if (GET_SKILL(ch, SKILL_DUAL_WIELD) > 0) {                 if (FIGHTING(ch) && rand_number(1, 100) <= GET_SKILL(ch, SKILL_DUAL_WIELD) / 3) {                     const char *attacker_msgs[] = {                         "You sneak in a quick slash with your off-hand!",                         "Your off-hand weapon arcs in a deadly strike!",                         "With a deft flick, your off-hand attacks!"                     };                     const char *victim_msgs[] = {                         "$n sneaks in a quick slash with $s off-hand weapon!",                         "$n's off-hand weapon arcs in a deadly strike toward you!",                         "With a deft flick, $n attacks you with $s off-hand weapon!"                     };                     const char *room_msgs[] = {                         "$n sneaks in a quick slash with $s off-hand weapon!",                         "$n's off-hand weapon arcs in a deadly strike!",                         "With a deft flick, $n attacks with $s off-hand weapon!"                     };                     int idx = rand_number(0, 2);                     send_to_char(ch, "\tY%s\tn\r\n", attacker_msgs[idx]);                     act(victim_msgs[idx], FALSE, ch, NULL, FIGHTING(ch), TO_VICT);                     act(room_msgs[idx], TRUE, ch, NULL, FIGHTING(ch), TO_NOTVICT);                     hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                                         if (!FIGHTING(ch))                         continue;                 }             }             /* Second & third attack */             if (FIGHTING(ch) && rand_number(1, 100) < (GET_SKILL(ch, SKILL_ATTACK2) / 1.5)) {                 hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                 if (!FIGHTING(ch))                     continue;             }                         if (FIGHTING(ch) && rand_number(1, 100) < (GET_SKILL(ch, SKILL_ATTACK3) / 2)) {                 hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.5);                 if (!FIGHTING(ch))                     continue;             }             /* Player rage effect */             if (AFF_FLAGGED(ch, AFF_RAGE) && FIGHTING(ch)) {                 if (GET_MOVE(ch) < 25) {                     send_to_char(ch, "\tRYou feel too exhausted right now.\tn\r\n");                 } else {                     if (FIGHTING(ch)) {                         send_to_char(ch, "\tRYou start to \tWfoam at the mouth\tR!!!!!\tn\r\n");                         for (int i = 0; i < GET_LEVEL(ch) / 6; i++) {                             if (!FIGHTING(ch))                                 break;                             hit(ch, FIGHTING(ch), TYPE_UNDEFINED, 0.25);                             GET_MOVE(ch) -= (GET_LEVEL(ch) / 4);                         }                     }                 }             }         }                 /* Mob spec proc */         if (MOB_FLAGGED(ch, MOB_SPEC) && GET_MOB_SPEC(ch) && !MOB_FLAGGED(ch, MOB_NOTDEADYET)) {             char actbuf[MAX_INPUT_LENGTH] = "";             (GET_MOB_SPEC(ch))(ch, ch, 0, actbuf);         }     } }



in structs.h
Code:
#define AFF_VANGUARD        41      /**< vanguard stance for warrior */ #define NUM_AFF_FLAGS       42      /**< needs to be 1 number higher than previous AFF */ /* define max cooldowns */ #define CD_FADE                 0 #define CD_HURRICANE_KICK       1 #define CD_CHALLENGING_SHOUT    2 #define NUM_COOLDOWNS           10


In act.offensive.c
Code:
/** * @author  Soth * @date    2026.03.12 * @brief   Challenging Shout */ ACMD(do_challenging_shout) {     struct char_data *vict, *next_vict;     struct aggro_node *node, *tank_node;     long current_max;     act("You let out a blood-curdling Challenging Shout!", FALSE, ch, 0, 0, TO_CHAR);     act("$n lets out a blood-curdling Challenging Shout!", FALSE, ch, 0, 0, TO_ROOM);     for (vict = world[IN_ROOM(ch)].people; vict; vict = next_vict) {         next_vict = vict->next_in_room;         if (IS_NPC(vict) && vict != ch) {             current_max = 0;             tank_node = NULL;             /* 1. Find current max AND check if the Tank (ch) is already on the table */             for (node = vict->mob_specials.aggro_table; node; node = node->next) {                 if (node->amount > current_max)                     current_max = node->amount;                                 if (node->ch == ch)                     tank_node = node;             }             /* 2. Calculate the target value (1.6x to clear the 1.1x/1.5x threshold) */             long taunt_value = (long)(current_max * 1.6) + 1000;             /* 3. Manually update or create the node */             if (tank_node) {                 tank_node->amount = taunt_value;             }             else {                 /* Tank isn't on the table yet, we must create a new node */                 CREATE(node, struct aggro_node, 1);                 node->ch = ch;                 node->amount = taunt_value;                 node->next = vict->mob_specials.aggro_table;                 vict->mob_specials.aggro_table = node;             }         }     }         if (found > 0) {         set_cooldown(ch, CD_CHALLENGING_SHOUT, 8);         WAIT_STATE(ch, PULSE_VIOLENCE);     }     else {         send_to_char(ch, "No enemies were close enough to hear your challenge.\r\n");     } } /** * @author  Soth * @date    2026.03.09 * @brief   Taunts the mob */ ACMD(do_taunt) {     struct char_data *victim;     struct aggro_node *node;     char arg[MAX_INPUT_LENGTH]; /* FIX: Declare the missing 'arg' */     long max_threat = 0;     bool found = FALSE;     one_argument(argument, arg);     if (!(victim = get_char_vis(ch, arg, NULL, FIND_CHAR_ROOM))) {         if (FIGHTING(ch)) {             victim = FIGHTING(ch);         } else {             send_to_char(ch, "Taunt who?\r\n");             return;         }     }     if (victim == ch || !IS_NPC(victim)) {         send_to_char(ch, "You can't really taunt that.\r\n");         return;     }     if (GET_MOVE(ch) < 15) {         send_to_char(ch, "You are too winded to shout a challenge!\r\n");         return;     }         GET_MOVE(ch) -= 15;     /* Find the highest threat currently on the mob's table */     for (node = victim->mob_specials.aggro_table; node; node = node->next) {         if (node->amount > max_threat)             max_threat = node->amount;     }     /* Ensure the floor is at least 100 so the taunt does something on a fresh pull */     if (max_threat < 100) max_threat = 100;         /* FIX: Instead of set_threat, we find the Warrior and set their amount */     for (node = victim->mob_specials.aggro_table; node; node = node->next) {         if (node->ch == ch) {             node->amount = (long)(max_threat * 1.15);             found = TRUE;             break;         }     }     /* If the Warrior wasn't on the table yet, add them now */     if (!found) {         modify_threat(victim, ch, (int)(max_threat * 1.15));     }     act("You shout insults at $N, grabbing their attention!", FALSE, ch, 0, victim, TO_CHAR);     act("$n shouts insults at $N!", FALSE, ch, 0, victim, TO_NOTVICT);     act("$n shouts foul insults at YOU!", FALSE, ch, 0, victim, TO_VICT);         if (FIGHTING(victim) != ch) {         stop_fighting(victim);         set_fighting(victim, ch);     }         WAIT_STATE(ch, PULSE_VIOLENCE * 2); } /** * @author  Soth * @date    2026.03.09 * @brief   Vanguard Stance for Warrior */ ACMD(do_vanguard) {     if (IS_NPC(ch)) return;     /* 1. Check if they even know the skill */     if (IS_NPC(ch) || !GET_SKILL(ch, SKILL_VANGUARD)) {         send_to_char(ch, "You have no idea how to enter such a protective stance.\r\n");         return;     }     /* 2. Optional: Add a failure chance if their practice is low */     if (rand_number(1, 101) > GET_SKILL(ch, SKILL_VANGUARD)) {         send_to_char(ch, "You try to enter a vanguard stance but lose your footing!\r\n");         WAIT_STATE(ch, PULSE_VIOLENCE);         return;     }     /* make sure class is a warrior */     if (!IS_WARRIOR(ch)) {         send_to_char(ch, "You don't have the training to lead a Vanguard.\r\n");         return;     }     if (AFF_FLAGGED(ch, AFF_VANGUARD)) {         REMOVE_BIT_AR(AFF_FLAGS(ch), AFF_VANGUARD);         /* Remove the AC bonus - assuming 20 points for tbaMUD/Circle scale */         GET_AC(ch) += 20;         send_to_char(ch, "You \tRdrop\tn your Vanguard stance.\r\n");         act("$n relaxes $s guard, looking less imposing.", FALSE, ch, 0, 0, TO_ROOM);     } else {         SET_BIT_AR(AFF_FLAGS(ch), AFF_VANGUARD);         /* Apply the AC bonus (Lower is better in traditional Circle/tba) */         GET_AC(ch) -= 20;         send_to_char(ch, "You enter the \tYVanguard\tn stance!\r\n");         act("$n plants $s feet, eyes fixed on every enemy movement.", FALSE, ch, 0, 0, TO_ROOM);     } }

I think that will get most of it. There are some other files that need VANGUARD declared in like all the other skills, I just didn't include them here.  I have refactored my damage() function as well.  Here is the output from my imp looking at the fight with Haides and Tinkerfrost.  Haides starts the fight as usual. Tinkerfrost cast meteor which is an AOE and she gets on the 2nd and 3rd mobs aggro table.  Haides is not on theirs yet, but when he does "challenging shout" he gets on it and jumps ahead of Tinkerfrost.

My thought is to make the tank be an active player to some degree and not just have him sitting there spamming skills. He needs to watch to make sure someone else does not steal aggro off him, but yet not have him continuously having to spam taunt or challenging shout every other round too.

The fight log.

The meteor Tinkerfrost summoned upon Talon's head seems to have disintegrated.  Oops!
Talon practices shadow-boxing while Tinkerfrost takes a break.
Code:
The meteor Tinkerfrost summoned upon Talon's head seems to have disintegrated.  Oops! Talon practices shadow-boxing while Tinkerfrost takes a break. Aggro Table for: Talon ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Tinkerfrost          |  516 Haides               |  2553 ------------------------------------------ <1600hp 100ma 204mv > Aggro Table for: Hassan ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Tinkerfrost          |  532 ------------------------------------------ <1600hp 100ma 204mv > Aggro Table for: Fury stands here ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Tinkerfrost          |  544 ------------------------------------------ <1600hp 100ma 204mv > Hassan hits Tinkerfrost. Hassan hits Tinkerfrost hard. Fury stands here hits Tinkerfrost. Fury stands here hits Tinkerfrost hard. Fury stands here hits Tinkerfrost. Talon barely avoids a blow from Tinkerfrost! Talon hits Haides hard. Talon hits Haides. Haides slashes Talon. Haides slashes Talon hard. Haides slashes Talon. <1600hp 100ma 204mv > Hassan hits Tinkerfrost hard. Hassan hits Tinkerfrost. Fury stands here hits Tinkerfrost. Fury stands here hits Tinkerfrost hard. Tinkerfrost tickles Talon as he hits it. Talon hits Haides hard. Talon hits Haides. Haides slashes Talon. <1600hp 100ma 204mv > Haides kicks Talon in face! <1600hp 100ma 204mv > Hassan hits Tinkerfrost hard. Fury stands here hits Tinkerfrost hard. Fury stands here hits Tinkerfrost hard. Tinkerfrost tries to hit Talon who easily avoids the blow. Talon hits Haides hard. Haides slashes Talon. Haides's off-hand weapon arcs in a deadly strike! Haides slashes Talon. <1600hp 100ma 204mv > Hassan hits Tinkerfrost hard. Hassan hits Tinkerfrost hard. Fury stands here hits Tinkerfrost hard. Fury stands here hits Tinkerfrost hard. Tinkerfrost tickles Talon as he hits it. Talon hits Haides hard. Talon hits Haides. Haides slashes Talon. Haides sneaks in a quick slash with his off-hand weapon! Haides slashes Talon. Haides's slash misses Talon who laughs in sheer delight, HA! Haides slashes Talon. <1600hp 100ma 204mv > Haides lets out a blood-curdling Challenging Shout! <1600hp 100ma 204mv > aggro talon aggro hassan aggro fury Tinkerfrost stares at Talon and utters the words, 'wohoaf'. Tinkerfrost has summoned a flaming meteor from the sky upon the doomed head of Talon! Tinkerfrost has summoned a flaming meteor from the sky upon the doomed head of Fury stands here! Tinkerfrost has summoned a flaming meteor from the sky upon the doomed head of Hassan! Aggro Table for: Talon ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Tinkerfrost          |  674 Haides               |  6988 ------------------------------------------ <1600hp 100ma 204mv > Aggro Table for: Hassan ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Haides               |  1851 Tinkerfrost          |  645 ------------------------------------------ <1600hp 100ma 204mv > Aggro Table for: Fury stands here ------------------------------------------ Player Name          |  Threat Level ---------------------+-------------------- Haides               |  1870 Tinkerfrost          |  710 ------------------------------------------ <1600hp 100ma 204mv > Hassan screams in a blind rage and turns toward Haides! Haides parries Hassan's attack! Hassan hits Haides hard. Fury stands here screams in a blind rage and turns toward Haides! Haides parries Fury stands here's attack! Tinkerfrost tickles Talon as he hits it. Haides parries Talon's attack! Haides slashes Talon hard. Haides slashes Talon. Haides slashes Talon. <1600hp 100ma 204mv > peace Everything is quite peaceful now. :::: Here is the prompt Haides sees :::: <468hp 100ma 371mv TNL:121016 Mob: [++++    ] : Haides >
A player will see the mob condition and who "that" mob is currently focused on.  Not the greatest, but will give some idea at least for now. I'm sure there is a cleaner and more efficient way of implementing this.  Let me know if I missed anything or you have more suggestions for me.

Thanks Whiskey!!!
Kevin
Last edit: 14 Mar 2026 02:52 by soth. Reason: Code did not come out correctly.

Please Log in or Create an account to join the conversation.

More
14 Mar 2026 02:56 #10999 by soth
Replied by soth on topic Mob Switching
Oh, as you can see I had to hard code a value of 170 in there for now and do some debug logging to see why I was never getting on the aggro table of the other mobs. Also I have a command called "aggro" that displays that table I was executing from Soth.

Kevin

Please Log in or Create an account to join the conversation.

More
14 Mar 2026 17:17 #11000 by soth
Replied by soth on topic Mob Switching
Here is the code for aggro_mob in act.wizard.c
Code:
ACMD(do_aggro) {     struct char_data *victim;     struct aggro_node *node;     char buf[MAX_STRING_LENGTH];     char arg[MAX_INPUT_LENGTH];         one_argument(argument, arg);     if (!(victim = get_char_vis(ch, arg, NULL, FIND_CHAR_ROOM))) {         send_to_char(ch, "Show aggro for which mobile?\r\n");         return;     }     if (!IS_NPC(victim)) {         send_to_char(ch, "Players don't have aggro tables.\r\n");         return;     }     if (!victim->mob_specials.aggro_table) {         send_to_char(ch, "That mobile has no active threats.\r\n");         return;     }     send_to_char(ch, "\r\n\tgAggro Table for: \tO%s\tn\r\n", GET_NAME(victim));     send_to_char(ch, "\tD------------------------------------------\r\n");     send_to_char(ch, "\tGPlayer Name\tn          |  \tGThreat Level\tn\r\n");     send_to_char(ch, "\tD---------------------\tW+\tD--------------------\tn\r\n");     for (node = victim->mob_specials.aggro_table; node; node = node->next) {         if (node->ch) {             snprintf(buf, sizeof(buf), "%-20s |  %ld\r\n", GET_NAME(node->ch), node->amount);             send_to_char(ch, "%s", buf);         }     }     send_to_char(ch, "\tD------------------------------------------\tn\r\n"); }

If you can see anything in these functions that I am missing or need to change/improve let me know :) I've not tested it as much as needed either.

ty!

Please Log in or Create an account to join the conversation.

Time to create page: 0.202 seconds