/* Copyright (C) 1995-2002 FSGames. Ported by Sean Ford and Yan Shosh * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // // Living; derived class of walker // #include "graph.h" #include "smooth.h" // From picker extern long difficulty_level[DIFFICULTY_SETTINGS]; extern long current_difficulty; living::living(unsigned char *data, screen *myscreen) : walker(data, myscreen) { current_special = 1; lifetime = 0; } living::~living() { // Zardus: PORT: this tries to delte its parent class (I think), and g++ doesn't // seem to like chicken and egg problems: walker::~walker(); } short living::act() { long temp; if (bonus_rounds>0 && !dead) // we get extra rounds to act this cycle { bonus_rounds--; act(); } if (dead) return 0; // Make sure everyone we're pointing to is valid if (foe && (foe->dead || (random(foe->invisibility_left/20) > 0) ) ) foe = NULL; if (is_friendly(foe)) foe = NULL; if (leader && leader->dead) leader = NULL; if (owner && owner->dead) { //owner = NULL; // A living who had an owner who is now dead, dies as well dead = 1; death(); return 0; } if (lifetime) { if (!owner || owner->dead) // our owner gone? { dead = 1; death(); return 0; } if (lifetime-- < 1) { dead = 1; return death(); } // Do other things based on our type .. switch (family) { case FAMILY_FIREELEMENTAL: // we take a toll from our mage .. if (stats->hitpoints < stats->max_hitpoints) // we're hurt { // Take a 'toll' of one health and 3 mp of mage, if there temp = 0; if (owner->stats->hitpoints >= (owner->stats->max_hitpoints/3) ) { temp = 1; owner->stats->hitpoints--; } if (temp && (owner->stats->magicpoints >= 3) ) { temp += 1; owner->stats->magicpoints -= 3; } if (temp == 2) // had both MP and HP, so heal 1 unit stats->hitpoints++; else // else go down one more unit of lifetime lifetime--; } // end of hurt elemental break; // end of elemental drain default: break; } // end of special stuff for summoned guys } // end of summoned monster stuff collide_ob = NULL; // always start with no collison.. /* if (ignore) { printf("ignoring living\n"); return 0; } */ // Regenerate magic if (stats->magicpoints < stats->max_magicpoints && !(screenp->enemy_freeze || bonus_rounds) ) { stats->magicpoints += stats->magic_per_round; stats->current_magic_delay++; if (stats->current_magic_delay >= stats->max_magic_delay) { stats->magicpoints++; stats->current_magic_delay = 0; } if (stats->magicpoints > stats->max_magicpoints) stats->magicpoints = stats->max_magicpoints; } // end magic increment section //Regenerate hitpoints if (stats->hitpoints < stats->max_hitpoints && !(screenp->enemy_freeze || bonus_rounds) ) { stats->hitpoints += stats->heal_per_round; stats->current_heal_delay++; if (stats->current_heal_delay >= stats->max_heal_delay) { stats->hitpoints++; stats->current_heal_delay = 0; } if (stats->hitpoints > stats->max_hitpoints) stats->hitpoints = stats->max_hitpoints; } // end hitpoint increment section // Special-viewing if (view_all > 0) view_all--; // Invulnerability if (invulnerable_left > 0) invulnerable_left--; // Invisibility if (invisibility_left > 0) invisibility_left--; else outline = 0; // Flight if (flight_left > 0) flight_left--; if (!screenp->query_grid_passable(xpos, ypos, this) && !flight_left) { flight_left++; stats->hitpoints--; if (stats->hitpoints < 1) { dead = 1; death(); } } // Charmed-ness if (charm_left > 1) charm_left--; else { charm_left = 0; if (real_team_num != 255) { team_num = real_team_num; real_team_num = 255; } } if ( stats->query_bit_flags(BIT_FORESTWALK) && ( mysmoother->query_genre_x_y( xpos/GRID_SIZE, ypos/GRID_SIZE) == TYPE_TREES || mysmoother->query_genre_x_y( (xpos+sizex)/GRID_SIZE, ypos/GRID_SIZE) == TYPE_TREES || mysmoother->query_genre_x_y( (xpos+sizex)/GRID_SIZE, (ypos+sizey)/GRID_SIZE) == TYPE_TREES || mysmoother->query_genre_x_y( xpos/GRID_SIZE, (ypos+sizey)/GRID_SIZE) == TYPE_TREES ) ) { // charge us a point of magic .. if (stats->magicpoints) stats->magicpoints--; if (myguy) temp = (4 - myguy->dexterity/10); else temp = (4 - stats->level/2); if (temp < 0) temp = 0; stepsize -= temp; if (stepsize < 1) { // stepsize = random(9-temp); // if (stepsize) stepsize = 1; } } // end of forestwalk check else stepsize = normal_stepsize; // Speed bonus if (speed_bonus_left > 1) { speed_bonus_left--; stepsize += speed_bonus; } // Special things for various different living types switch (family) { case FAMILY_ARCHMAGE: // gets bonus viewing, at times if (stats->level >= 40) temp = 1; else temp = 40 - stats->level; if (!(drawcycle%temp)) // then we get to see.. view_all += 1; break; default: break; } // end of special family auto-powers // Complete previous animations (like firing) if (ani_type != ANI_WALK) return animate(); // Are we frozen? if (stats->frozen_delay) { stats->frozen_delay--; return 1; } if (busy > 0) busy--; // This allows busy to be our FIRING delay. // Find new action // Turn if you want to (...turn, around the world...) if (curdir != enddir && query_order() == ORDER_LIVING) return turn(enddir); // Are we performing some action? if (stats->commandlist) { temp = stats->do_command(); if (temp) return 1; } if (skip_exit > 0) skip_exit--; // Do we have a generic action-type set? if (action && (user == -1) ) { temp = do_action(); if (temp) return temp; } switch (act_type) { // We are the control character case ACT_CONTROL: { return 1; //break; } // We are a generator case ACT_GENERATE: { printf("LIVING Generator?\n"); // act_generate(); break; } // We are a weapon case ACT_FIRE: { printf("Living think's it's a weapon (act_fire)\n"); // act_fire(); return 1; //break; } case ACT_GUARD: { act_guard(); break; } case ACT_DIE: { this->dead = 1; return 1; } // We are randomly walking toward enemy case ACT_RANDOM: { if (!random(5) ) //1 in 5 to do our special { // Should we do our special? Are we full of magic? if (stats->magicpoints >= stats->special_cost[1]) { current_special = (char) (random((stats->level+2)/3) + 1); if ( (current_special > 4) || (!strcmp(screenp->special_name[family][current_special], "NONE")) ) current_special = 1; if (check_special() ) return special(); } else // do random walking .. { act_random(); return 1; } } else if (!random(5) ) //1 in 5 to do act_random() function act_random(); else // 4 of 5 times { if (!foe) { foe = screenp->find_near_foe(this); } if (foe) // && random(2) ) { curdir = enddir = (char) ((enddir/2) * 2); //stats->try_command(COMMAND_SEARCH, 40, 0, 0); stats->try_command(COMMAND_SEARCH, 300, 0, 0); } //else if (foe) // stats->try_command(COMMAND_RIGHT_WALK,40,0,0); else if (!random(2)) foe = screenp->find_far_foe(this); else stats->try_command(COMMAND_RANDOM_WALK,20); return 1; } } // END RANDOM break; default: { printf("No act type set.\n"); return 0; } } // END SWITCH return 0; } short living::shove(walker *target, short x, short y) { //return 0; //debug memory if (target && !target->dead && (query_order()==ORDER_LIVING) && //we are alive (is_friendly(target)) // we are allied ) // Make sure WE don't get shoved if (random(3) && target->query_act_type() != ACT_CONTROL) { // We have to prevent a build-up of shoves which is // caused by a blocked target. We do so for now by clearing // all commands target->stats->clear_command(); if (target->query_family()==FAMILY_CLERIC) { target->current_special = 1; // healing target->special(); } target->stats->set_command(COMMAND_WALK,4,x ,y ); return 1; } return 0; } short living::walk(short x, short y) { short dir; // short newdir, newcurdir; // short distance; // distance between current and desired facings // Repeat last walk. // lastx = x; // lasty = y; dir = facing(x, y); if (curdir == dir) // if continue direction { // check if off map if (x+xpos < 0 || x+xpos >= screenp->maxx*GRID_SIZE || y+ypos < 0 || y+ypos >= screenp->maxy*GRID_SIZE) { return 0; } // Here we check if the move is valid // Normally we would check if the object at this grid point // is passable (I cheated for now) if (screenp->query_passable((short) (xpos+x),(short) (ypos+y),this)) { // Control object does complete redraw anyway move(x,y); cycle++; //if (!ani || (curdir*cycle > sizeof(ani)) ) // printf("WALKER::WALK: Bad ani!\n"); if (ani[curdir][cycle] == -1) cycle = 0; set_frame(ani[curdir][cycle]); return 1; } else //Invalid move? { if (collide_ob && !collide_ob->dead) { if (collide_ob->query_order() == ORDER_LIVING && is_friendly(collide_ob) ) { shove(collide_ob, x, y); } } // end hit some object if (stats->query_bit_flags(BIT_ANIMATE) ) // animate regardless.. { cycle++; if (ani[curdir][cycle] == -1) cycle = 0; set_frame(ani[curdir][cycle]); } return 0; } } else // Just changing direction { enddir = (char) dir; // Technically, control gets and EXTRA call to TURN // because first we call WALK, then ACT, whereas // other walkers call ACT. This would cause control // to turn TWICE on the first call to walk, which is bad. // So we stop that behavior here. if (this->query_act_type() != ACT_CONTROL || stats->commandlist) turn(enddir); } return 1; } short living::collide(walker *ob) { collide_ob = ob; //return 1; // debug if ( ob && ob->query_order() == ORDER_LIVING && (is_friendly(ob) == 0) && !ob->dead && !dead) init_fire(); return 1; } walker * living::do_summon(char whatfamily, unsigned short lifetime) { walker *newob; newob = screenp->add_ob(ORDER_LIVING, whatfamily); newob->owner = this; newob->lifetime = lifetime; newob->transform_to(ORDER_LIVING, whatfamily); // printf("\n\nSummoned %d, life %d\n", whatfamily, lifetime); return newob; } // Returns true or false on whether it's good to do // the special or not .. short living::check_special() { unsigned long distance, myrange; oblink *enemylist, *templink; short howmany; enemylist = NULL; templink = NULL; shifter_down = random(2); // on or off, randomly .. // Make sure we have enough .. if (stats->magicpoints < stats->special_cost[current_special]) current_special = 1; // make us do default .. switch (family) { case FAMILY_SOLDIER: // Check for foe in range x if (foe) // already have a foe .. { distance = (unsigned long) distance_to_ob(foe);//long (deltax*deltax) + long (deltay*deltay); if (distance < 75 && distance > 20) // about 3 squares max, 1 square min return 1; return 0; } else // get a new foe .. { foe = screenp->find_near_foe(this); if (!foe) return 0; distance = (unsigned long) distance_to_ob(foe); // (deltax*deltax) + long (deltay*deltay); if (distance < 75 && distance > 20) // about 3 squares max, 1 min return 1; return 0; } //break; // end of fighter case case FAMILY_FIREELEMENTAL: // Check for foe in range x case FAMILY_ARCHER: case FAMILY_GHOST: case FAMILY_ORC: if (foe) // already have a foe .. { distance = (unsigned long) distance_to_ob(foe); //long (deltax*deltax) + long (deltay*deltay); if (distance < 130) // about 6 squares return 1; return 0; } else // get a new foe .. { foe = screenp->find_near_foe(this); if (!foe) return 0; distance = (unsigned long) distance_to_ob(foe); //long (deltax*deltax) + long (deltay*deltay); if (distance < 130) // about 6 squares return 1; return 0; } //break; // end of fighter case case FAMILY_THIEF: if (current_special == 1) // drop bomb { if (foe) // already have a foe .. { distance = (unsigned long) distance_to_ob(foe); // (deltax*deltax) + long (deltay*deltay); if (distance < 130 && distance > 35) // about 6 squares max, 2 min return 0; } else // get a new foe .. { enemylist = screenp->find_foes_in_range(screenp->oblist, 110, &howmany, this); // Zardus: TAG: this seems to just delete the list. lets use delete_list /*while(enemylist) { templink=enemylist; enemylist = enemylist->next; delete templink; templink = NULL; }*/ delete_list(enemylist); if (howmany < 3) return 0; return 1; } break; // end of thief case } // end of bomb else if (current_special == 3) { if (!shifter_down) // taunt myrange = 80 + 4*stats->level; else // charm myrange = 16 + 4*stats->level; enemylist = screenp->find_foes_in_range(screenp->oblist, myrange, &howmany, this); delete_list(enemylist); if (howmany < 1) return 0; else return 1; } else return 1; // default is go for it case FAMILY_MAGE: // TP if away from guys .. howmany = 0; enemylist = screenp->find_foes_in_range(screenp->oblist, 110, &howmany, this); // Zardus: TAG: lets use delete_list here too /*while(enemylist) { templink=enemylist; enemylist = enemylist->next; delete templink; templink = NULL; }*/ delete_list(enemylist); if (howmany < 1) // away from anybody .. return 1; if (howmany > 3) // too many enemies! return 1; return 0; //break; // end of fighter case case FAMILY_SLIME: if (screenp->numobs < MAXOBS) return 1; else return 0; case FAMILY_CLERIC: // any friends? if (current_special == 1) // healing { enemylist = screenp->find_friends_in_range(screenp->oblist, 60, &howmany, this); // Zardus: TAG: lets use delete_list here as well /*while(enemylist) { templink=enemylist; enemylist = enemylist->next; delete templink; templink = NULL; }*/ delete_list(enemylist); if (howmany > 1) // other than ourselves? { shifter_down = 0; // we're HEALING return 1; } else if (stats->magicpoints >= (stats->max_magicpoints/2) ) { // Do mace ... shifter_down = 1; return 1; } else return 0; } // end of healing/mace else return 1; //break; case FAMILY_SKELETON: // Tunnel if no foes near .. howmany = 0; enemylist = screenp->find_foes_in_range(screenp->oblist, 5*GRID_SIZE, &howmany, this); // Zardus: TAG: delete_list strikes again! /*while(enemylist) { templink=enemylist; enemylist = enemylist->next; delete templink; templink = NULL; }*/ delete_list(enemylist); if (howmany < 1) // away from anybody .. return 1; // so tunnel return 0; //break; // end of skeleton case default : return 1; } return 1; //unreachable? } void living::set_difficulty(unsigned long whatlevel) { // long calcdelay,calcrate; // apparently not used anymore unsigned long dif1 = difficulty_level[current_difficulty]; unsigned long levmult = (unsigned long) whatlevel*(unsigned long) whatlevel; switch (family) { case FAMILY_ARCHER: stats->max_hitpoints += 11*levmult; stats->max_magicpoints += 12*levmult; damage += 4*whatlevel; stats->armor += levmult; break; case FAMILY_MAGE: stats->max_hitpoints += 7*levmult; stats->max_magicpoints += 14*levmult; damage += 3*whatlevel; stats->armor += levmult/2; break; case FAMILY_CLERIC: case FAMILY_DRUID: stats->max_hitpoints += 9*levmult; stats->max_magicpoints += 12*levmult; damage += 4*whatlevel; stats->armor += levmult/2; break; case FAMILY_SOLDIER: // default as soldier stats->max_hitpoints += 13*levmult; stats->max_magicpoints += 8*levmult; weapons_left = (short) ((whatlevel+1) / 2); damage += 5*whatlevel; stats->armor += 2*levmult; break; case FAMILY_ORC: stats->max_hitpoints += 14*levmult; stats->max_magicpoints += 7*levmult; damage += 6*whatlevel; stats->armor += 3*levmult; break; case FAMILY_GOLEM: stats->max_hitpoints += 18*levmult; stats->max_magicpoints += 5*levmult; damage += 7*whatlevel; stats->armor += 4*levmult; break; default: stats->max_hitpoints += 11*levmult; stats->max_magicpoints += 11*levmult; damage += (short) 4*whatlevel; stats->armor += 2*levmult; break; } // Adjust for difficulty settings now... if (team_num != 0) // do all EXCEPT player characters { stats->max_hitpoints = (stats->max_hitpoints*dif1) / 100; stats->max_magicpoints = (stats->max_magicpoints*dif1) / 100; damage = (damage * dif1) / 100; } stats->hitpoints = stats->max_hitpoints; stats->magicpoints = stats->max_magicpoints; stats->max_heal_delay = REGEN; //defined in graph.h stats->current_heal_delay = (levmult * 4); //for purposes of calculation only while (stats->current_heal_delay > REGEN) { stats->current_heal_delay -= REGEN; stats->heal_per_round++; } // this takes care of the integer part, now calculate the fraction if (stats->current_heal_delay > 1) { stats->max_heal_delay /= (long) (stats->current_heal_delay + 1); } stats->current_heal_delay = 0; //start off without healing //make sure we have at least a 2 wait, otherwise we should have //calculated our heal_per_round as one higher, and the math must //have been screwed up some how if (stats->max_heal_delay < 2) stats->max_heal_delay = 2; // Set the magic delay .. stats->max_magic_delay = REGEN; stats->current_magic_delay = (long) (levmult*30);//for calculation only while (stats->current_magic_delay > REGEN) { stats->current_magic_delay -= REGEN; stats->magic_per_round++; } // this takes care of the integer part, now calculate the fraction if (stats->current_magic_delay > 1) { stats->max_magic_delay /= (long) (stats->current_magic_delay + 1); } stats->current_magic_delay = 0; //start off without magic regen //make sure we have at least a 2 wait, otherwise we should have //calculated our magic_per_round as one higher, and the math must //have been screwed up some how if (stats->max_magic_delay < 2) stats->max_magic_delay = 2; } short living::facing(short x, short y) { long bigy = (long) (y*1000); long slope; if (!x) if (y>0) return FACE_DOWN; else return FACE_UP; slope = bigy / x; if (x>0) { if (slope > 2414) return FACE_DOWN; if (slope > 414) return FACE_DOWN_RIGHT; if (slope > -414) return FACE_RIGHT; if (slope > -2414) return FACE_UP_RIGHT; return FACE_UP; } else { if (slope > 2414) return FACE_UP; if (slope > 414) return FACE_UP_LEFT; if (slope > -414) return FACE_LEFT; if (slope > -2414) return FACE_DOWN_LEFT; return FACE_DOWN; } } short living::act_random() { // short newx, newy; // apparently not used anymore short xdist, ydist; // Find our foe if (!random(80) || (!foe)) foe = screenp->find_near_foe(this); if (!foe) return stats->try_command(COMMAND_RANDOM_WALK,40); xdist = (short) (foe->xpos - xpos); ydist = (short) (foe->ypos - ypos); // If foe is in firing range, turn and fire if (abs(xdist) < lineofsight*GRID_SIZE && abs(ydist) < lineofsight*GRID_SIZE) { if (fire_check(xdist, ydist)) { init_fire(xdist, ydist); stats->set_command(COMMAND_FIRE, (short) random(24), xdist, ydist); return 1; } else // Nearest foe is blocked turn(facing(xdist,ydist)); } stats->try_command(COMMAND_SEARCH,200,0,0); //stats->try_command(COMMAND_RIGHT_WALK,50, 0, 0); return 1; } short living::do_action() { if (!action) return 0; switch (action) { case ACTION_FOLLOW: // follow our leader, attack his targets .. if (foe) return 0; // continue as normal leader = screenp->find_nearest_player(this); if (!leader) return 0; // continue as normal ... shouldn't happen if (leader->foe) { foe = leader->foe; return 0; // continue from this point .. } // Else follow our leader stats->force_command(COMMAND_FOLLOW, 5, 0, 0); return 1; default: return 0; } }