Once characters take enough
damage, they die, and when that happens, the following
function is called:
void cCharController::death(sCharacter* attacker, sCharacter* victim)
{
// if a PC or NPC dies, then do not remove from list.
if(victim->type == CHAR_PC || victim->type == CHAR_NPC)
{
victim->update_enable = false;
if(victim->type == CHAR_PC)
pc_death(victim);
else
npc_death(victim);
}
else // victim->type = CHAR_MONSTER
{
// give attacker the victim's experience
if(attacker && gain_exp(attacker, victim->char_def.exp))
{
char text[128];
sprintf(text, "+%lu exp.", victim->char_def.exp);
set_char_msg(attacker, text, 500, COLOR_WHITE);
}
}
if(m_mil && victim->char_def.money)
drop_money(victim->pos_x, victim->pos_y, victim->pos_z, victim->char_def.money);
if(rand()%100 < victim->char_def.drop_chance)
drop_item(victim->pos_x, victim->pos_y, victim->pos_z, victim->char_def.drop_item);
sMeshAnim& mesh_anim = m_mesh_anim[victim->char_def.mesh_index];
if(--mesh_anim.count == 0)
{
mesh_anim.mesh.free();
mesh_anim.anim.free();
}
// remove the dead character from list
if(victim->prev)
victim->prev->next = victim->next;
else
m_root_char = victim->next;
if(victim->next)
victim->next->prev = victim->prev;
victim->prev = victim->next = NULL;
delete victim;
}
virtual void pc_death(sCharacter* character)
{
}
virtual void npc_death(sCharacter* character)
{
}
Taking the pointer to the victim,
the controller is able to handle its death appropriately.
If the victim is a monster, you use the attacking character pointer to apply the
experience points. Also, if a monster dies, the death function determines how
much
gold the monster drops and what item (if any) is dropped and calls the
appropriate
controller function to handle such dropped items.
Leading into the next function,
whenever a PC kills a monster, that PC gains the
experience stored in the monster’s MCL definition. To apply the experience, use
the following function:
virtual bool gain_exp(sCharacter* character, long amount)
{
character->char_def.exp += amount;
return true;
}
Notice that the gain_exp function
can be overridden. This can occur when you’re
using a separate battle sequence engine; you don’t want experience added to the
PC until the battle is over. Consequently, you use your own function to keep
track
of how much experience to apply when the battle is over.
The overridden function can also
occur when the character needs to go up in
experience levels once he gains a certain number of experience points. The
gan_exp function is the place to determine just when a character goes up an
experience level and to take the appropriate actions to increase their
abilities.
One note about the gain_exp
function: The character controller normally displays
the number of experience points that a PC gains when killing a monster. To stop
the controller from displaying this number (as in the case of the separate
battle
sequences), return a value of false from the Experience function.
The next couple of functions
are the ones responsible for processing attacks and
spells. Both functions take pointers to the attacking characters (if any) as
well as
their intended victims. For spells, a sSpellTracker structure is required to
tell the
controller which spell to process, as well as the sSpell structure that contains
the
information about the spell effects to use:
bool cCharController::attack(sCharacter* attacker, sCharacter* victim)
{
if(attacker == NULL || victim == NULL)
return false;
// do not attack dead or hurt character
if(victim->action == CHAR_DIE || victim->action == CHAR_HURT)
return false;
victim->attacker = attacker;
attacker->victim = victim;
// return if hit missed
if(rand()%1000 > get_to_hit(attacker))
{
set_char_msg(victim, "Missed!", 500, COLOR_WHITE);
return false;
}
// return if hit dodged
if(rand()%1000 <= get_agility(victim))
{
set_char_msg(victim, "Dodged!", 500, COLOR_WHITE);
return false;
}
// if character is asleep, randomly wake them up (50% chance).
if((victim->ailments & AILMENT_SLEEP) && rand()%100 < 50)
victim->ailments &= ~AILMENT_SLEEP;
// attack landed, apply damage.
damage(victim, true, get_attack(attacker), -1, -1);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
bool cCharController::spell(sCharacter* caster, const sSpellTracker* spell_tracker, const sSpell* spells)
{
if(caster == NULL || spell_tracker == NULL || spells == NULL)
return false;
const sSpell* spell_ptr = &spells[spell_tracker->spell_index];
// reduce magic
caster->mana_points -= spell_ptr->cost;
if(caster->mana_points < 0)
caster->mana_points = 0;
// can not cast if silenced
if(caster->ailments & AILMENT_SILENCED)
{
set_char_msg(caster, "Silenced!", 500, COLOR_WHITE);
return false;
}
// handle self-targeting spells instantly
if(spell_ptr->target == TARGET_SELF)
{
spell_effect(caster, caster, spell_ptr);
return true;
}
float spell_dist = spell_ptr->range * spell_ptr->range;
sCharacter* closest_char = NULL;
float closest = 0.0f;
// scan through all characters and look for hits
for(sCharacter* char_ptr = m_root_char; char_ptr != NULL; char_ptr = char_ptr->next)
{
// only bother with characters of allowed types.
// also, allow a RAISE_DEAD PC spell to affect any character.
bool allow = false;
if(char_ptr != caster && spell_tracker->affect_type == char_ptr->type)
allow = true;
if(char_ptr->type == CHAR_PC && spell_ptr->effect == RAISE_DEAD)
allow = true;
if(!allow)
continue;
// get distance from target to character
float x_diff = fabs(char_ptr->pos_x - spell_tracker->target_x);
float y_diff = fabs(char_ptr->pos_y - spell_tracker->target_y);
float z_diff = fabs(char_ptr->pos_z - spell_tracker->target_z);
// get x/z and y distances
float xz_dist = (x_diff * x_diff + z_diff * z_diff) - spell_dist;
float y_dist = (y_diff * y_diff) - spell_dist;
// get target x/z and y radius
float min_x, min_y, min_z, max_x, max_y, max_z;
char_ptr->object.get_bounds(&min_x, &min_y, &min_z, &max_x, &max_y, &max_z, NULL);
float xz_radius = max(max_x - min_x, max_z - min_z) * 0.5f;
float y_radius = (max_y - min_y) * 0.5f;
// check if character in range
if(xz_dist > (xz_radius * xz_radius) || y_dist > (y_radius * y_radius))
continue;
// determine what to do if in range
if(spell_ptr->target == TARGET_SINGLE)
{
// record closest character in range
float dist = x_diff * x_diff + y_diff * y_diff + z_diff * z_diff;
if(closest_char == NULL || dist < closest)
{
closest_char = char_ptr;
closest = dist;
}
}
else // spell hit area targets
spell_effect(caster, char_ptr, spell_ptr);
}
// process spell on closest character if needed
if(spell_ptr->target == TARGET_SINGLE && closest_char)
spell_effect(caster, closest_char, spell_ptr);
return true;
}
Each of the preceding functions
takes into account the attacking and defending
characters’ abilities and adjust their values accordingly. When an attack
connects,
damage is dealt. When a spell is found to have affected the target (remember,
there’s a chance it might fail), the next function is called to process the
effects:
bool cCharController::spell_effect(sCharacter* caster, sCharacter* target, const sSpell* spell)
{
if(target == NULL || spell == NULL)
return false;
long chance;
// calculate chance of hitting
if(caster)
{
// a spell always lands if target == caster
if(caster == target)
chance = 100;
else
chance = (get_mental(caster)/100.0f + 1.0f) * spell->chance;
}
else
chance = spell->chance;
// alter chance by target's registance
if(caster != target)
chance *= (1.0f - get_resistance(target)/100.0f);
// see if spell failed
if(rand()%100 > chance)
{
set_char_msg(target, "Failed!", 500, COLOR_WHITE);
return false;
}
bool can_hit = true; // flag character to allow effect
if(target->action == CHAR_HURT || target->action == CHAR_DIE)
can_hit = false;
// store attacker and victim
target->attacker = caster;
if(caster)
caster->victim = target;
char text[64];
// process spell effect
switch(spell->effect)
{
case ALTER_HEALTH:
if(can_hit)
{
if(spell->value[0] < 0.0f) // apply damage
damage(target, false, -spell->value[0], spell->damage_class, spell->cure_class);
else if(spell->value[0] > 0.0f) // cure damage
{
target->health_points += spell->value[0];
if(target->health_points > target->char_def.health_points)
target->health_points = target->char_def.health_points;
// display amount healed
sprintf(text, "+%lu HP", spell->value[0]);
set_char_msg(target, text, 500, D3DCOLOR_RGBA(0, 64, 255, 255));
}
}
break;
case ALTER_MANA:
if(can_hit)
{
target->mana_points += spell->value[0];
if(target->mana_points < 0)
target->mana_points = 0;
else if(target->mana_points > target->char_def.mana_points)
target->mana_points = target->char_def.mana_points;
if(spell->value[0] < 0.0f)
sprintf(text, "%ld MP", spell->value[0]);
else if(spell->value[0] > 0.0f)
sprintf(text, "+%ld MP", spell->value[0]);
set_char_msg(target, text, 500, D3DCOLOR_RGBA(0, 128, 64, 255));
}
break;
case CURE_AILMENT:
if(can_hit)
{
// cure ailment and display message
target->ailments &= ~(long)spell->value[0];
set_char_msg(target, "Cure", 500, COLOR_WHITE);
}
break;
case CAUSE_AILMENT:
if(can_hit)
{
// cause ailment and display message
target->ailments |= (long)spell->value[0];
set_char_msg(target, "Ailment", 500, COLOR_WHITE);
}
break;
case RAISE_DEAD:
if(target->action == CHAR_DIE)
{
target->health_points = 1;
target->mana_points = 0;
target->action = CHAR_DIE;
target->is_lock = false;
target->action_timer = 0;
target->ailments = 0;
target->update_enable = true;
}
break;
case INSTANT_KILL:
if(can_hit)
set_char_action(target, CHAR_DIE, 0);
break;
case DISPEL_MAGIC:
if(can_hit)
target->ailments = 0;
break;
case TELEPORT: // teleport PC/NPC/MONSTER
if(can_hit)
{
if(target->type == CHAR_PC)
pc_teleport(caster, spell);
else
{
target->pos_x = spell->value[0];
target->pos_y = spell->value[1];
target->pos_z = spell->value[2];
}
}
break;
}
return true;
}