/************************************************************** * FFTacticsMUD : battle.cpp * ************************************************************** * (c) 2002 Damien Dailidenas (Trenton). All rights reserved. * **************************************************************/ #include "main.h" #include <strstream> #include <cstdio> BATTLE::BATTLE() { area = NULL; next = battle_list; battle_list = this; return; } BATTLE::~BATTLE() { short prize = 0; if(!winner) return; for(CH *ch = people, *ch_next; ch; ch = ch_next) { ch_next = ch->next_in_battle; if(ch->npc) { ch->leave_battle(); continue; } if(winner != ch && (!ch->group || !winner->group || ch->group != winner->group)) { ch->printf("You Lose! The battle is over.\n\r"); ch->die(); continue; } if(!prize) prize = (gil / ch->get_group_cnt()); ch->printf("Congratulations! The battle is complete.\n\r"); ch->money += prize; ch->printf("You receive %d Gil!\n\r", prize); ch->leave_battle(); if(ch->group && ch->group->leader != ch) ch->to(ch->group->leader->room); } if(this == battle_list) battle_list = next; else { for(BATTLE *prev = battle_list; prev; prev = prev->next) { if(prev->next == this) { prev->next = next; break; } } } return; } void CH::leave_battle() { if(!battle) return; if(battle->turn == this) battle->turn = false; if(this == battle->people) battle->people = next_in_battle; else { for(CH *prev = battle->people; prev; prev = prev->next_in_battle) { if(prev->next_in_battle == this) { prev->next_in_battle = next_in_battle; break; } } } if(npc) { CH *ch_this = this; zap(ch_this); return; } battle = NULL; action.action = NULL; action.target = NULL; action.ctr = 0; moved = acted = false; temp_action.action = NULL; CT = 0; commands = ""; clear_status(false); restore(); return; } bool twoswords_attack(CH *ch, CH *victim) { bool found = false; if(ch->physical_attack(victim, ch->calculate_dam(0, LOC_RHAND))) found = true; if(ch->physical_attack(victim, ch->calculate_dam(0, LOC_LHAND))) found = true; return found; } short CH::get_weap_loc() { return (eq[LOC_RHAND] && obj_table[eq[LOC_RHAND]->id].obj_type == OBJ_WEAPON ? LOC_RHAND : LOC_LHAND); } bool do_attack(CH *ch, const bool check) { CH *victim; if(!(victim = ch->action.target->people)) return false; if(ch->has_skill("Two Swords")) return twoswords_attack(ch, victim); return ch->physical_attack(victim, ch->calculate_dam(0, ch->get_weap_loc())); } bool CH::check_evade() { if(status(status_charging)) return false; if(num_percent() > get_hitpercent()) return true; return false; } bool CH::physical_attack(CH *victim, short dam) { bool crit = false; if(!victim) return false; if(victim->check_reaction(this, TRIG_ATTACK)) return false; if(victim->check_evade()) { battle->echo(victim->name + " evades " + name + "'s attack!\n\r"); return false; } if(num_percent() <= 5) { crit = true; dam += num_range(1, dam); } if(victim->status(status_charging)) dam = (short)(dam * 1.5); damage(victim, dam); if(!victim || !victim->battle) return true; if(crit && num_percent() < 50) if(victim->move(rev_dir[dir(victim)])) battle->echo(victim->name + " is pushed back!\n\r"); return true; } short CH::calculate_dam(const short K, const short loc) { short dam = 0; OBJ *weapon = eq[loc]; if(!weapon) dam = (short)((float)(PA + K) * ((float)Br / 100) * (float)PA); else { switch(obj_table[weapon->id].type) { case TYPE_KNIGHT_SWORD: dam = Br / 100 * (PA + K) * obj_table[weapon->id].ATp; break; case TYPE_KNIFE: case TYPE_NINJA_SWORD: case TYPE_BOW: dam = (PA + K + Sp + K) / 2 * obj_table[weapon->id].ATp; break; case TYPE_KATANA: dam = Br / 100 * (PA + K) * obj_table[weapon->id].ATp; break; case TYPE_STAFF: case TYPE_STICK: dam = (MA + K) * obj_table[weapon->id].ATp; break; case TYPE_SWORD: case TYPE_ROD: case TYPE_CROSSBOW: case TYPE_SPEAR: dam = (PA + K) * obj_table[weapon->id].ATp; break; case TYPE_GUN: dam = (obj_table[weapon->id].ATp + K) * obj_table[weapon->id].ATp; break; case TYPE_FLAIL: case TYPE_AXE: case TYPE_BAG: dam = ((PA + K) / 2 + 1) * obj_table[weapon->id].ATp; break; case TYPE_INSTRUMENT: case TYPE_DICTIONARY: case TYPE_CLOTH: dam = (PA + K + MA + K) / 2 * obj_table[weapon->id].ATp; break; } } return dam; } bool CH::check_reaction(CH *victim, const short trigger) { BATTLE_ROOM *room; DO_ACT *action; short id; if(!job[cjob].reaction || !skill_table[job[cjob].reaction].trigger || skill_table[job[cjob].reaction].trigger != trigger || num_percent() < 50) return false; // switch(trigger) { // case TRIG_HPDAMAGE: room = this->action.target; action = this->action.action; id = this->action.id; this->action.target = victim->battle_room; this->action.id = job[cjob].reaction; this->action.action = skill_table[this->action.id].fun; // resolve_action(ch); this->action.action(this); if(battle) { this->action.target = room; this->action.action = action; this->action.id = id; } return true; // case TRIG_ATTACK: // room = // } } void CH::damage(CH *victim, const short dam) { ostrstream ost; ost << "{!" << name << " hits " << victim->name << "! " << dam << "{0\n\r" << ends; battle->echo(ost.str()); victim->HP[0] -= dam; if(victim->HP[0] < 1) { if(victim->npc) { victim->battle->gil += 100; victim->die(this); } else { victim->die(this); if(!victim->get_group_cnt()) { victim->battle->winner = this; zap(victim->battle); } } return; } victim->check_reaction(this, TRIG_HPDAMAGE); return; } void CH::die(CH *killer=NULL) { if(npc) battle->echo(name + " has been killed!\n\r"); else { if(!status(status_dead)) { battle->echo(name + " has been killed!\n\r"); add(status_dead, 0, 0, false); return; } if(get_group_cnt()) battle->echo(name + "'s spirit turned into crystal...\n\r"); clear_status(); if(!admin) { exp -= 50; job[cjob].JP -= 50; job[cjob].exp -= 50; } to(area->room[ROOM_OUTSIDE]); if(group) do_leave(this); } leave_battle(); if(killer && !killer->get_nearest_target(false)) { killer->battle->winner = killer; zap(killer->battle); } return; } short CH::get_hitpercent() { float hitpercent = 100; hitpercent *= 1 - (float)Ev / 100; for(short x = 2; x < 5; ++x) { if(eq[x]) { // if(x <= 1 && !has_skill(ch, "Weapon Guard", TRUE)) // continue; hitpercent *= 1 - ((float)obj_table[eq[x]->id].pEv / 100); } } if(status(status_defending)) hitpercent /= 2; return (int)hitpercent; } bool CH::check_ok(const bool act=false) { if((orig_room && battle_room->distance(orig_room) > temp_action.range) || battle_room->occupied(this)) return false; if(act && !skill_table[action.id].self && battle_room == orig_room) return false; return true; } void do_ok(CH *ch, string argument="") { if(ch->temp_action.action == do_move) { if(ch->check_ok()) { ch->printf("ok: ERROR: Select within moveable range.\n\r"); return; } ch->temp_action.action = NULL; ch->orig_room = NULL; do_look(ch); ch->moved = true; ch->battle->echo("{B" + ch->name + " moved.{0\n\r"); ch->cancel(status_charging, false); if(ch && !ch->acted) { ch->printf("{^ACT or WAIT?{0\n\r"); ch->commands = "act wait"; } } else if(ch->temp_action.action == do_act) { if(ch->check_ok(true)) { ch->printf("ok: ERROR: Select within firing range.\n\r"); return; } ch->temp_action.action = NULL; if(skill_table[ch->action.id].range) { if(!ch->npc) { ch->action.target = ch->battle_room; ch->to(ch->orig_room); ch->orig_room = NULL; do_look(ch); } ch->battle->echo(ch->name + " selected an action and target.\n\r"); } ch->acted = true; if(skill_table[ch->action.id].job == JOB_ARCHER && skill_table[ch->action.id].type == SK_ACTION) ch->add(status_charging, 0, 0, 0); else if(ch->action.action == do_defend) ch->add(status_defending, 0, 0, 0); if(!ch->action.ctr) ch->resolve_action(); if(!ch) return; if(!ch->moved && ch->battle) { ch->printf("{^MOVE or WAIT?{0\n\r"); ch->commands = "move wait"; } } if(ch->moved && ch->acted) ch->end_turn(); return; } void do_wait(CH *ch, string argument="") { ch->battle->echo(ch->name + " waits.\n\r"); ch->end_turn(); return; } void do_cancel(CH *ch, string argument="") { ch->printf((string)(ch->temp_action.action == do_move ? "MOVE" : "ACT") + " cancelled.\n\r"); if(ch->temp_action.action == do_move) { if(!ch->acted) { ch->printf("{^MOVE, ACT or WAIT?{0\n\r"); ch->commands = "move act wait"; } else { ch->printf("{^MOVE or WAIT?{0\n\r"); ch->commands = "act wait"; } } else { ch->action.action = NULL; if(!ch->moved) { ch->printf("{^MOVE, ACT or WAIT?{0\n\r"); ch->commands = "move act wait"; } else { ch->printf("{^ACT or WAIT?{0\n\r"); ch->commands = "act wait"; } } ch->temp_action.action = NULL; if(ch->orig_room) { ch->to(ch->orig_room); ch->orig_room = NULL; } return; } short get_skill_id_by_function(DO_ACT *fun) { for(short x = 1; skill_table[x].name; ++x) if(skill_table[x].fun == fun) return x; return 0; } void CH::npc_turn() { short id; for(short x = 0; job_table[cjob].skill[x]; ++x) { id = get_skill_id_by_function(job_table[cjob].skill[x]); temp_action.range = skill_table[id].range; if(job_table[cjob].skill[x](this, true)) { do_act(this); action.action = job_table[cjob].skill[x]; action.id = id; action.ctr = skill_table[id].ctr; if(!skill_table[id].range) { resolve_action(); if(!this) return; acted = true; } if(!moved) { do_ok(this); if(!this) return; do_move(this); retreat(); } do_ok(this); return; } } do_move(this); advance(); do_ok(this); do_wait(this); return; } void BATTLE::echo(string str) { for(CH *pers = people; pers; pers = pers->next_in_battle) if(pers->desc) pers->printf(str); return; } void BATTLE::echo(CH *ch, char *txt, ...) { char string[MSL]; va_list args; va_start(args, txt); vsprintf(string, txt, args); va_end(args); for(CH *pers = ch->battle->people; pers; pers = pers->next_in_battle) if(pers->desc) ch->printf(string); return; } void CH::resolve_action() { short x; announce_action(); if(!skill_table[action.id].range && !skill_table[action.id].self) { bool found = false; for(x = 1; battle->area->room[x]; ++x) { if(battle->area->room[x] != battle_room && battle_room->distance(battle->area->room[x]) <= skill_table[action.id].effect - 1) { action.target = battle->area->room[x]; if(action.action(this, false)) found = true; } } if(found) { gain_exp(); gain_jp(); } action.action = NULL; action.target = NULL; return; } if(action.action(this, false)) { gain_exp(); gain_jp(); } if(!this || !battle) return; if(skill_table[action.id].effect > 1) { BATTLE_ROOM *orig_target = action.target; for(x = 1; battle->area->room[x]; ++x) { if(battle->area->room[x] != orig_target && orig_target->distance(battle->area->room[x]) <= skill_table[action.id].effect - 1) { action.target = battle->area->room[x]; action.action(this, false); } } } action.action = NULL; action.target = NULL; return; } void CH::end_turn() { battle->echo(name + "'s turn has ended.\n\r"); acted = moved = false; battle->turn = NULL; CT = (moved || acted ? MAX(CT - 80, 60) : MAX(CT - 60, 60)); status_update(); commands = ""; return; } void CH::add_queue(const string str) { for(short x = 0; x < MAX_QUEUES; ++x) { if(queue[x].empty()) { queue[x] = str; return; } } return; } /************** * BATTLE_FUN * **************/ bool battle_orbonne_monastery(CH *ch, const short phase=1) { if(phase == 1 && ch->battles[BATTLE_ORBONNE_MONASTERY]) return false; switch(phase) { case 1: ch->add_queue(dialogue_box("Female Knight", "Princess Ovelia, let's go.", true)); ch->add_queue(dialogue_box("Princess Ovelia", "Just a moment, Agrias...", true)); ch->add_queue(dialogue_box("Agrias", "The guards have already arrived.", true)); ch->add_queue(dialogue_box("Priest", "Princess, don't give Agrias trouble. Please hurry...", true)); ch->add_queue("A knight and two squires come in from outside.\n\r" + dialogue_box("Black Knight", "What's going on? It's been nearly an hour!", true)); ch->add_queue(dialogue_box("Agrias", "Don't be rude to the Princess, Gafgarion.", true)); ch->add_queue("The two squires behind the Black Knight kneel down while the Black Knight bows his head.\n\r" + dialogue_box("Gafgarion", "Is this going to be alright, Agrias? This is an urgent issue for us.", true)); ch->add_queue(dialogue_box("Agrias", "So there are rude knaves even among the Hokuten?", true)); ch->add_queue(dialogue_box("Gafgarion", "I'm being more than kind to the guard captains here. Besides, we're mercenaries hired by the Hokuten. I'm not obliged to show respect to you.", true)); ch->add_queue(dialogue_box("Agrias", "What? How dare you!", true)); ch->add_queue("The princess stands up.\n\r" + dialogue_box("Ovelia", "Enough. Let's go.", true)); ch->add_queue("The two squires get up on their feet. The princess walks over to the priest.\n\r" + dialogue_box("Priest", "Go with God.", true)); ch->add_queue(dialogue_box("Ovelia", "You too, Simon.", true)); ch->add_queue("A female knight walks in with a nasty wound. The priest helps the knight.\n\r" + dialogue_box("Female Knight", "Lady Agrias!... The enemy!", true)); ch->add_queue(dialogue_box("Simon", "Prince Goltana's men!?", true)); ch->add_queue("Agrias quickly rushes outside!\n\r" + dialogue_box("Gafgarion", "What one must do to make money. What, " + ch->name + "? You have a problem, too?", true)); ch->add_queue(dialogue_box(ch->name, "...I'm no longer a Knight. Just a mercenary like you.", true)); ch->add_queue(dialogue_box("Gafgarion", "...That's right. Well then. Let's go!", true)); ch->add_queue("The Knight and the two squires go outside.\n\r" + dialogue_box("Ovelia", "Oh God...", false)); ch->add_queue("phase 2"); break; case 2: ch->to(get_battle_area(AREA_ORBONNE_MONASTERY)->room[57]); do_look(ch); break; } return true; }