/************************************************************************** * File: combat.c * * Author: Midboss * * Purpose: Core functionality for turn-based combat on ROM MUDs. * * License: * * Give credit where it is due; be it in the main combat helpfile * * or in your login sequence. Just don't claim this as your own * * original work, lest you become the next Vryce. * * * * This code is provided as-is, and was created on a stock QuickMUD * * server. The only guarantee I'll offer is that it works, when * * properly installed, on a ROM MUD (in particular, QuickMUD). * **************************************************************************/ #if defined(macintosh) #include <types.h> #else #include <sys/types.h> #endif #include <stdio.h> #include <stdlib.h> #include "merc.h" #include "interp.h" /* * Updater for what few autonomous functions used in this combat system. * Mostly just triggers mobile's turns and updates battlefield conditions. * Also controls the automatic turn skip when someone tries to stall. */ void combat_update (void) { COMBAT_DATA * battle, * battle_next; CHAR_DATA * ch; for (battle = battle_list; battle != NULL; battle = battle_next) { battle_next = battle->next; if (battle->turn_list->roundtime >= 2000000000) nuke_roundtime (battle); ch = battle->turn_list->unit; if (ch == NULL) { bug ("combat_update (): Turn with NULL character!", 0); clean_combat (battle); } else if (IS_NPC (ch)) mobile_turn (ch); else if (ch->desc == NULL) linkdead_turn (ch); else { battle->turn_list->timer++; if (battle->turn_list->timer == 70) turn_end (battle->turn_list, 300); } } } /* * Forces a mobile to take its turn. */ void mobile_turn (CHAR_DATA * ch) { CHAR_DATA * vch, * list; vch = ch; for (list = ch->in_battle->unit_list; list != NULL; list = list->next_in_battle) { if (!is_same_group (ch, list)) { if (vch == ch) vch = list; else if (vch != ch && number_percent () < 45) vch = list; else continue; } } /* * I'm leaving this at just attack for obvious reasons. * Be creative, will ya? */ if (vch == ch) clean_combat (ch->in_battle); else do_attack (ch, vch->name); } /* * PC version of mobile turn, for linkdead characters. */ void linkdead_turn (CHAR_DATA * ch) { CHAR_DATA * vch, * list; if (number_percent () < 40) { do_escape (ch, ""); return; } vch = ch; for (list = ch->in_battle->unit_list; list != NULL; list = list->next_in_battle) { if (!is_same_group (ch, list)) { if (vch == ch) vch = list; else if (vch != ch && number_percent () < 45) vch = list; else continue; } } if (vch == ch) clean_combat (ch->in_battle); else do_attack (ch, vch->name); } /* * This is a simple replacement to the original damage, here solely to make * the snippet run clean in stockier MUDs. */ bool damage_new (CHAR_DATA * ch, CHAR_DATA * vch, long dam, int sn, char * damverb, bool hide, bool fKill) { /* Make sure the damage is valid. */ if (ch == NULL || vch == NULL) { bug ("damage_new(): missing character", 0); return FALSE; } /* Apply damage caps. */ if (!fKill) dam = UMIN (dam, vch->hit - 1); dam = UMIN (dam, 1200); /* If you add damtype(s), this'd be where to check them. */ /* Dish out the damage. */ vch->hit = UMAX (0, vch->hit - dam); /* Grab an sn-based damverb if one isn't present. */ if (damverb[0] == '\0') { if (sn >= 0 && sn < MAX_SKILL && skill_table[sn].noun_damage != NULL) damverb = skill_table[sn].noun_damage; else damverb = "attack"; } /* Dispatch messaging, if hide is false. */ if (!hide) { char buf[200]; if (dam > 0) { sprintf (buf, "{CYour $t hits $N for %ld damage!{x\n\r", dam); act (buf, ch, damverb, vch, TO_CHAR); sprintf (buf, "{R$n's $t hits you for %ld damage!{x\n\r", dam); act (buf, ch, damverb, vch, TO_VICT); sprintf (buf, "{D$n's $t hits $N for %ld damage!{x\n\r", dam); act (buf, ch, damverb, vch, TO_NOTVICT); } else { act ("{CYour $t misses $N!{x\n\r", ch, damverb, vch, TO_CHAR); act ("{R$n's $t misses you!{x\n\r", ch, damverb, vch, TO_VICT); act ("{D$n's $t misses $N!{x\n\r", ch, damverb, vch, TO_NOTVICT); } } /* Trigger death. */ if (vch->hit <= 0) kill_unit (vch); return TRUE; } /* * Dispatches the KO affect to a unit and removes its turn from the list. * Will not remove the unit from combat. */ void kill_unit (CHAR_DATA * ch) { if (!is_fighting (ch)) return; SET_BIT(ch->affected_by, AFF_KNOCKOUT); remove_turn (ch->turn); act ("{DYou feel the sweet embrace of sleep eternal as your body " "fades out of existence.{x\n\r", ch, NULL, NULL, TO_CHAR); act ("$n's body slowly fades into nothingness.\n\r", ch, NULL, NULL, TO_ROOM); check_victory (ch->in_battle); } /* * Removes the KO affect from a unit and adds its turn back to the list. * Will not remove the unit from combat. */ void revive_unit (CHAR_DATA * ch) { if (!is_fighting (ch)) return; REMOVE_BIT(ch->affected_by, AFF_KNOCKOUT); ch->turn->roundtime = (ch->in_battle->turn_list->roundtime + 500); insert_turn (ch->in_battle, ch->turn); } /* * This will end combat. You're on your own when tallying up the experience * gains and whatnot, I'm just giving you the core, after all. */ void check_victory (COMBAT_DATA * battle) { CHAR_DATA * leader; CHAR_DATA * unit; bool victory = TRUE; bool found = FALSE; //First look for PCs. Since mobs don't gain, if only mobs are left, we abort. for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) if (!IS_NPC (unit)) found = TRUE; if (!found) { clean_combat (battle); return; } //PCs exist! Grab a guy. for (leader = battle->unit_list; leader != NULL; leader = leader->next_in_battle) { if (IS_AFFECTED (leader, AFF_KNOCKOUT)) continue; //Once we find the first guy that isn't dead, we look for another guy that //isn't dead, and isn't on his team. If one is found, battle's over. for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) { if ((IS_AFFECTED (unit, AFF_KNOCKOUT) && !is_same_group (leader, unit)) || is_same_group (leader, unit) || leader == unit) continue; else { victory = FALSE; break; } } if (!victory) continue; else { for (unit = battle->unit_list; unit != NULL; unit = unit->next_in_battle) { if (is_same_group (leader, unit)) act ("Your party has won the battle!\n\r", unit, NULL, NULL, TO_CHAR); else act ("Your party has lost the battle...\n\r", unit, NULL, NULL, TO_CHAR); } clean_combat (battle); return; } } } /* * True if ch is part of any combat. */ bool is_fighting (CHAR_DATA * ch) { if (ch->in_battle != NULL) return TRUE; return FALSE; } /* * True if ch may take a turn right now. */ bool is_active (CHAR_DATA * ch) { if (!is_fighting (ch)) return FALSE; if (ch->turn == ch->in_battle->turn_list) return TRUE; return FALSE; } /* * Inserts a character into combat and adds their turn to the list. */ void char_to_combat (CHAR_DATA * ch, COMBAT_DATA * combat) { TURN_DATA * turn; if (combat->unit_list == NULL) combat->unit_list = ch; else { ch->next_in_battle = combat->unit_list; combat->unit_list = ch; } ch->in_battle = combat; turn = new_turn (); turn->unit = ch; turn->in_battle = combat; //So people don't end up getting a dozen turns by joining in. if (combat->turn_count > 0) turn->roundtime = (combat->turn_list->roundtime + 200) - GET_SPEED (ch); else turn->roundtime = 500 - GET_SPEED (ch); ch->turn = turn; insert_turn (combat, turn); } /* * Removes a ch and their turn entry from combat. */ void char_from_combat (CHAR_DATA * ch) { CHAR_DATA * prev; if (ch == NULL) { bug ("cfc () -- null ch!?", 0); return; } if (!is_fighting (ch)) return; if (ch->in_battle->unit_list == ch) { ch->in_battle->unit_list = ch->next_in_battle; ch->next_in_battle = NULL; } else { for (prev = ch->in_battle->unit_list; prev != NULL; prev = prev->next_in_battle) { if (prev->next_in_battle == ch) { prev->next_in_battle = ch->next_in_battle; ch->next_in_battle = NULL; break; } } if (prev == NULL) { bug ("cfc () -- ch not found.", 0); return; } } remove_turn (ch->turn); ch->turn = NULL; ch->in_battle = NULL; /* * Here I've chosen to revive characters as they leave combat, but * you'll want to enter PCs into your death handler at this point, * and extract mobiles. */ if (IS_AFFECTED (ch, AFF_KNOCKOUT)) { REMOVE_BIT(ch->affected_by, AFF_KNOCKOUT); ch->hit = UMAX(1, ch->hit); } } /* * Returns the data for ch's next/current turn. */ TURN_DATA * get_turn_char (CHAR_DATA * ch) { TURN_DATA * turn; if (!is_fighting (ch)) return NULL; for (turn = ch->in_battle->turn_list; turn != NULL; turn = turn->next) if (turn->unit == ch) return turn; return NULL; } /* * Inserts a turn into its proper position in the specified list. */ void insert_turn (COMBAT_DATA * combat, TURN_DATA * nTurn) { if (combat->turn_list == NULL) { combat->turn_list = nTurn; nTurn->in_battle = combat; } else { TURN_DATA * prev; if (combat->turn_list->roundtime > nTurn->roundtime) { nTurn->next = combat->turn_list; combat->turn_list = nTurn; nTurn->in_battle = combat; } else { for (prev = combat->turn_list; prev != NULL; prev = prev->next) { if (prev->next != NULL && prev->next->roundtime > nTurn->roundtime) { nTurn->next = prev->next; prev->next = nTurn; nTurn->in_battle = combat; break; } else if (prev->next == NULL) { prev->next = nTurn; nTurn->in_battle = combat; break; } } } } } /* * Removes a turn from the list without freeing it. * It isn't freed here because you have to use this every time you move a turn * to a new position in the list. */ void remove_turn (TURN_DATA * turn) { TURN_DATA * prev; if (turn == NULL) { bug ("remove_turn () -- null turn!?", 0); return; } if (turn == turn->in_battle->turn_list) { turn->in_battle->turn_list = turn->next; turn->next = NULL; } else { for (prev = turn->in_battle->turn_list; prev != NULL; prev = prev->next) { if (prev->next == turn) { prev->next = turn->next; turn->next = NULL; break; } } } } /* * Similar to roundtime (), but used specifically at the end of each action. */ void turn_end (TURN_DATA * turn, int roundtime) { turn->timer = 0; turn->roundtime += roundtime; /* * On my MUD, I have code here to update affects each turn. */ remove_turn (turn); insert_turn (turn->in_battle, turn); turn->in_battle->turn_count++; } /* * Moves the turn down the list a bit. */ void skip_turn (TURN_DATA * turn, bool fWilling) { turn->roundtime = turn->next->roundtime + number_range (45, 75); remove_turn (turn); insert_turn (turn->in_battle, turn); if (fWilling) turn->in_battle->turn_count++; } /* * Adds to the roundtime of a turn, then replaces it in the order. */ void roundtime (TURN_DATA * turn, int roundtime) { turn->roundtime += roundtime; remove_turn (turn); insert_turn (turn->in_battle, turn); } /* * Swaps the placement of two turns, by switching roundtimes and reordering. */ void swap_turn (TURN_DATA * aTurn, TURN_DATA * bTurn) { COMBAT_DATA * battle = aTurn->in_battle; int rt = aTurn->roundtime; aTurn->roundtime = bTurn->roundtime; bTurn->roundtime = rt; remove_turn (aTurn); remove_turn (bTurn); insert_turn (battle, aTurn); insert_turn (battle, bTurn); } /* * Just in case some newb with a trigger leaves a battle against a training * dummy running for weeks on end with trigs, or something. You can never * have too much protection, y'know. -- Midboss */ void nuke_roundtime (COMBAT_DATA * battle) { TURN_DATA * list; for (list = battle->turn_list; list != NULL; list = list->next) list->roundtime -= 2000000000; } /* * Creates a new battle between two parties. * Recycles the data if there somehow aren't enough people. */ void initiate_combat (CHAR_DATA * ch, CHAR_DATA * vch) { CHAR_DATA * och; TURN_DATA * turn; COMBAT_DATA * battle; int acount = 0, bcount = 0; battle = new_combat (); if (battle_list == NULL) battle_list = battle; else { battle->next = battle_list; battle_list = battle; } for (och = ch->in_room->people; och != NULL; och = och->next_in_room) { if (is_same_group (ch, och)) char_to_combat (och, battle); if (is_same_group (vch, och)) char_to_combat (och, battle); } for (turn = battle->turn_list; turn != NULL; turn = turn->next) { if (is_same_group (ch, turn->unit)) acount++; else bcount++; } if (acount < 1 || bcount < 1) clean_combat (battle); } /* * Frees combat data at the end of battle. */ void clean_combat (COMBAT_DATA * battle) { COMBAT_DATA * list; CHAR_DATA * unit, *unit_next; for (unit = battle->unit_list; unit != NULL; unit = unit_next) { unit_next = unit->next_in_battle; char_from_combat (unit); //Perhaps call raw_kill() here on KOd mobiles? } if (battle == battle_list) battle_list = battle->next; else for (list = battle_list; list != NULL; list = list->next) { if (list->next == battle) list->next = battle->next; } battle->next = NULL; free_combat (battle); } /* * Standard Merc recycling functions for turns and battles. */ TURN_DATA *turn_free; TURN_DATA *new_turn (void) { static TURN_DATA turn_zero; TURN_DATA *turn; if (turn_free == NULL) turn = alloc_perm (sizeof (*turn)); else { turn = turn_free; turn_free = turn_free->next; } *turn = turn_zero; VALIDATE (turn); return turn; } void free_turn (TURN_DATA * turn) { if (!IS_VALID (turn)) return; INVALIDATE (turn); turn->next = turn_free; turn_free = turn; } COMBAT_DATA *combat_free; COMBAT_DATA *new_combat (void) { static COMBAT_DATA combat_zero; COMBAT_DATA *combat; if (combat_free == NULL) combat = alloc_perm (sizeof (*combat)); else { combat = combat_free; combat_free = combat_free->next; } *combat = combat_zero; VALIDATE (combat); return combat; } void free_combat (COMBAT_DATA * combat) { if (!IS_VALID (combat)) return; INVALIDATE (combat); combat->next = combat_free; combat_free = combat; }