/**************************************************************************
* 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;
}