/* * XEvil(TM) Copyright (C) 1994,2000 Steve Hardt and Michael Judge * http://www.xevil.com * satan@xevil.com * * 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, the file "gpl.txt"; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA, or visit http://www.gnu.org. */ // "physical.cpp" #include "stdafx.h" #if X11 #ifndef NO_PRAGMAS #pragma implementation "physical.h" #endif #endif // Include Files #include "xdata.h" #include "physical.h" #include using namespace std; #if X11 #include #endif #if WIN32 #include #endif #include "utils.h" #include "coord.h" #include "area.h" #include "world.h" #include "id.h" #include "intel.h" #include "locator.h" #include "physical.h" #include "actual.h" #include "bitmaps/transform/transform.bitmaps" // Defines #define VEL_SMALL 1 #define MOVING_ANIM_TIME 2 #define MOVING_EXTRA_VEL_MAX 9 #define FALLING_VEL_MAX 12 #define HEAVY_DAMAGE_VEL 3 #define ITEM_CAN_TAKE_TIME 15 #define ITEM_DROP_OFFSET_WIDTH 20 #define ITEM_DROP_OFFSET_HEIGHT 20 #define ITEM_CLASS_MAX 2 // How items of a given class can be held. #define LIFTABLE_EXCLUDED_TIME ITEM_CAN_TAKE_TIME #define EXTRA_VEL_DAMP 1 #define DROP_SPEED 2 #define CREATURE_ACCELERATION_MAX 40 #define CREATURE_AIR_SPEED_MAX 40 #define CREATURE_CENTER_SPEED_MAX 40 #define CREATURE_CLIMB_SPEED_MAX 40 #define CREATURE_CRAWL_SPEED_MAX 40 #define CREATURE_JUMP_MAX 40 #define CREATURE_HIGH_VIBRATE 3 #define CREATURE_HEALTH_MAX_MAX 2000 #define CREATURE_DROPLETS_MAX 10 // Max droplets of blood per hit. // Greater than 25 damage to get second droplet #define CREATURE_DROPLET_FACTOR .04 #define FIGHTER_DAMAGE_MAX 150 #define FIGHTER_SLIDE_TIME 4 #define SHOT_OFFSET 19 #define HEAT_MAX 25 #define FLYING_GRAV_VEL_CUTOFF 1 #define CORPSE_TIME 140 #define FLASH_TIME 5 #define TOUCHABLE_RADIUS 20 //11 //10 #define HUGGER_TIME 90 #define USER_CYCLE_TIME 50 #define SPLATTER_SPEED 8 #if WIN32 #define M_PI 3.14159265 #endif //// Functions Physical::Physical(const PhysicalContext &p_c,WorldP w,LocatorP l) { world = w; locator = l; command = IT_NO_COMMAND; pc = &p_c; health = healthNext = pc->health; mass = massNext = pc->mass; // dontCollide initializes itself. intel = NULL; if (! staticValid) { init_static(); } deleteMe = False; mapped = mappedNext = True; noDeathDelete = False; dieCalled = False; heat = heatNext = 0; previousHeatWasSecondary = False; dontCollideComposite = COMPOSITE_ID_NONE; // Add 1 to FLASH_TIME to draw for proper number of turns. Timer t(FLASH_TIME + 1); flashTimer = t; quietDeath = False; netData = NULL; creationOk = True; } Physical::Physical() { assert(0); } Physical::Physical(InStreamP in,const PhysicalContext &p_c, WorldP w,LocatorP l) { creationOk = True; world = w; locator = l; command = IT_NO_COMMAND; pc = &p_c; mass = massNext = pc->mass; // dontCollide initializes itself. intel = NULL; if (! staticValid) { init_static(); } deleteMe = False; noDeathDelete = False; previousHeatWasSecondary = False; dontCollideComposite = COMPOSITE_ID_NONE; quietDeath = False; netData = NULL; _update_from_stream(in,False); } void Physical::update_from_stream(InStreamP in) { // Things should be ok to start with, child classes haven't had a chance // to call creation_failed() yet. assert(creationOk); _update_from_stream(in,True); } void Physical::_update_from_stream(InStreamP in,Boolean alreadyExists) { if (alreadyExists) { // If updating from stream, not creating from stream, // the id read in should be the same as the existing one. Id streamId(in); if (!(id == streamId)) { creationOk = False; } } else { // create_from_stream(), old value of id should not be set yet. id.read(in); } u_short h2 = in->read_short(); signed short h1 = (signed short)h2; health = (Health)h1; healthNext = health; heat = in->read_short(); heatNext = heat; // mapped, dieCalled, and flash u_char flags = in->read_char(); mapped = (flags & 0x1); mappedNext = mapped; // Not really needed, just to check an assertion. dieCalled = (flags & 0x2); Boolean flash = (flags & 0x4); if (flash) { // Will never be clocked anyway. flashTimer.set(1); } else { flashTimer.set(0); } // May stil be invalidated by child classes. creationOk = creationOk && in->alive(); LocatorP locator = get_locator(); IntelId iId; iId.read(in); intel = locator->lookup(iId); // may be NULL. } int Physical::get_write_length() { return id.get_write_length() + // id sizeof(short) + // health // intel->get_write_length() + // intel sizeof(short) + // heat sizeof(char)+ // mapped, dieCalled, flash Identifier::get_write_length(); // intelId } void Physical::write(OutStreamP out) { id.write(out); signed short h1 = (signed short)health; u_short h2 = (u_short)h1; out->write_short(h2); // intel assert(heat < USHRT_MAX); out->write_short((u_short)heat); u_char flags = 0x0; flags |= mapped; flags |= (dieCalled << 1); flags |= (!flashTimer.ready() << 2); out->write_char(flags); IntelId iId; if (intel) { iId = intel->get_intel_id(); } iId.write(out); } Physical::~Physical() { delete netData; } Health Physical::get_health_max() { return pc->health; } Dir Physical::get_dir() { return CO_air; } int Physical::get_drawing_level() { return 1; } Vel Physical::get_vel() // Returns (0,0) { Vel vel; return vel; } Boolean Physical::is_moving() { return False; } Boolean Physical::is_shot() { return False; } Boolean Physical::is_item() { return False; } Boolean Physical::is_shield() { return False; } Boolean Physical::is_bomb() { return False; } Boolean Physical::is_weapon() { return False; } Boolean Physical::is_cutter() { return False; } Boolean Physical::is_gun() { return False; } Boolean Physical::is_creature() { return False; } AbilityP Physical::get_ability(AbilityId) { return NULL; } HolderP Physical::get_holder() { return NULL; } LocomotionP Physical::get_locomotion() { return NULL; } CompositeP Physical::get_composite() { return NULL; } Boolean Physical::is_auto_use() { return False; } Boolean Physical::is_drugs() { return False; } Boolean Physical::is_liftable() { return False; } Boolean Physical::collidable() { return True; } PHsig Physical::get_id(Id &outside_id) { // used to have check for idValid outside_id = id; return PH_NO_SIG; } void Physical::get_followers(PtrList &) { // Default is no followers. } void Physical::follow(const Area &,Dir,Boolean) { assert(0); } void Physical::set_id(const Id &new_id) { id = new_id; // used to have check for idValid } NetDataP Physical::get_net_data() { if (!netData) { netData = new NetData(); } return netData; } Boolean Physical::command_repeatable(ITcommand c) { switch (c) { case IT_WEAPON_CHANGE: case IT_WEAPON_DROP: case IT_ITEM_USE: case IT_ITEM_CHANGE: case IT_ITEM_DROP: return False; // Allow repeat for all movement keys and for WEAPON_USE default: return True; } } void Physical::heal() { set_health_next(get_health_max()); } void Physical::set_intel(IntelP i) { intel = i; if (i) { i->set_id(id); } } void Physical::set_mapped_next(Boolean val) { mappedNext = val; } void Physical::flash() { flashTimer.set(); } Boolean Physical::corporeal_attack(PhysicalP,int damage) { // Ignore "superficial" damage. if (damage > 0 && // alive_next() checks if something else has already killed this. alive() && alive_next()) { healthNext = health - damage; } // For now dead objects return True. I.e. corpses will bleed. return True; } void Physical::heat_attack(PhysicalP,int h,Boolean secondary) { // Secondary means the attack came from colliding with something that was // on fire, as opposed to directly being hit by fire, e.g. a flame thrower. assert(h >= 0); // Don't propagate fires. if (! (secondary && previousHeatWasSecondary)) { heatNext = heat + h; if (heatNext > HEAT_MAX) { heatNext = HEAT_MAX; } if (heat == 0 && heatNext > 0) { LocatorP l = get_locator(); ostrstream str; str << get_class_name() << " is on fire." << ends; locator->message_enq(str.str()); } } previousHeatWasSecondary = secondary; } Boolean Physical::swap_protect() { return False; } Boolean Physical::frog_protect() { return False; } void Physical::avoid(PhysicalP) { } void Physical::collide(PhysicalP) { } void Physical::set_quiet_death() { quietDeath = True; } void Physical::act() { const Area &area = get_area(); /* The knocked out of world check is now done inside the locator. */ if (heatNext > 0) { Pos pos; Size size; area.get_rect(pos,size); pos.x += Utils::choose(size.width); // Start fire in upper-half. if (size.height > 1) { pos.y += Utils::choose((int)(size.height * .5)); } LocatorP locator = get_locator(); PhysicalP fire = new Fire(get_world(),locator,pos); fire->set_dont_collide(this->get_id()); locator->add(fire); heatNext--; // alive_next() checks if something else has already killed this. if (alive() && alive_next()) { healthNext -= Fire::get_damage(); } } // Call non-virtual version, to handle following. // Parent update_area_next()'s will already have been called as // necessary. _update_area_next(False); } void Physical::update() { mass = massNext; command = IT_NO_COMMAND; // Fire damage is now in Physical::act health = healthNext; mapped = mappedNext; heat = heatNext; flashTimer.clock(); } void Physical::die() { if (dieCalled) { cerr << "Physical::die called twice" << endl; } if (healthNext >= 0) { cerr << "Physical::die, healthNext >= 0, value is " << healthNext << endl; healthNext = -1; } if (health < 0) { cerr << "Physical::die, health already less than 0, value is " << health << endl; } dieCalled = True; if (intel) { intel->die(); } intel = NULL; if (!noDeathDelete) { set_delete_me(); } } void Physical::dr_clock() { // Make followers follow, setting current and next variables. _update_area_next(True); } ClassId Physical::get_weapon_string(const char *&str) { // static const char noneStr[] = "none"; str = "none"; return A_None; } void Physical::drop_all() { } Dir Physical::get_dir_next() { return CO_air; } ITcommand Physical::get_command() { return command; } void Physical::init_static() { // CO_air_* is not really a unit. unitAccs[CO_center_R].ddx = 1.0f; unitAccs[CO_center_L].ddx = -1.0f; unitAccs[CO_air_R].ddx = .4f; unitAccs[CO_air_L].ddx = -.4f; unitAccs[CO_air_R].ddy = unitAccs[CO_air_L].ddy = -1.0f; // -0.95 unitAccs[CO_air_UP].ddy = -1.0f; unitAccs[CO_air_DN].ddy = 1.0f; unitAccs[CO_center_R].ddy = unitAccs[CO_center_L].ddy = unitAccs[CO_air].ddy = unitAccs[CO_air].ddx = unitAccs[CO_center].ddx = unitAccs[CO_center].ddy = unitAccs[CO_air_UP].ddx = unitAccs[CO_air_DN].ddx = 0.0f; unitAccs[CO_r].ddx = unitAccs[CO_r].ddy = unitAccs[CO_dn].ddx = unitAccs[CO_dn].ddy = unitAccs[CO_l].ddx = unitAccs[CO_l].ddy = unitAccs[CO_up].ddx = unitAccs[CO_up].ddy = 0.0f; unitAccs[CO_r_DN].ddx = unitAccs[CO_r_UP].ddx = unitAccs[CO_dn_R].ddy = unitAccs[CO_dn_L].ddy = unitAccs[CO_l_DN].ddx = unitAccs[CO_l_UP].ddx = unitAccs[CO_up_R].ddy = unitAccs[CO_up_L].ddy = unitAccs[CO_climb_DN].ddx = unitAccs[CO_climb_UP].ddx = 0.0f; unitAccs[CO_r_DN].ddy = unitAccs[CO_l_DN].ddy = unitAccs[CO_climb_DN].ddy = 0.5f; unitAccs[CO_r_UP].ddy = unitAccs[CO_l_UP].ddy = unitAccs[CO_climb_UP].ddy = -0.5f; unitAccs[CO_dn_R].ddx = unitAccs[CO_up_R].ddx = 0.5f; unitAccs[CO_dn_L].ddx = unitAccs[CO_up_L].ddx = -0.5f; int n; for (n = 0; n < 16; n++) { float theta = (float)M_PI * n / 8.0f; unitAccs[CO_R + n].ddx = (float)cos(theta); unitAccs[CO_R + n].ddy = (float)sin(theta); } for (n = 0; n < CO_DIR_MAX; n++) { unitVels[n] = unitAccs[n]; } staticValid = True; } void Physical::update_area_next(Boolean currentAndNext) { _update_area_next(currentAndNext); } void Physical::_update_area_next(Boolean currentAndNext) { // Whether currentAndNext is true or not, get_area_next() and // get_dir_next() should give the correct results here, because // Physical::_update_area_next() is called last. const Area &areaNext = get_area_next(); if (currentAndNext) { // sanity check const Area &area = get_area(); if (!(area == areaNext)) { cerr << "Physical::_update_area_next() area does not equal " << "areaNext when expected." << endl; } } Dir dirNext = get_dir_next(); // Make all followers follow. PtrList followers; get_followers(followers); for (int n = 0; n < followers.length(); n++) { PhysicalP p = (PhysicalP)followers.get(n); p->follow(areaNext,dirNext,currentAndNext); } } Boolean Physical::staticValid = False; Acc Physical::unitAccs[CO_DIR_MAX]; Vel Physical::unitVels[CO_DIR_MAX]; Protection::Protection(const ProtectionContext &pr_c,ProtectionXdata &x_data, WorldP w,LocatorP l,const Area &ar) : Physical(pr_c.physicalContext,w,l) { area = areaNext = areaBaseNext = ar; delta = 0; pXdata = &x_data; prc = &pr_c; } Protection::Protection(InStreamP in, const ProtectionContext &pr_c, ProtectionXdata &x_data, WorldP w, LocatorP l) : Physical(in,pr_c.physicalContext,w,l) { pXdata = &x_data; prc = &pr_c; // Leave areaBaseNext as the default. _update_from_stream(in); } void Protection::update_from_stream(InStreamP in) { Physical::update_from_stream(in); _update_from_stream(in); } void Protection::_update_from_stream(InStreamP in) { delta = in->read_signed_char(); area.read(in); areaNext = area; } int Protection::get_write_length() { return Physical::get_write_length() + sizeof(char) + // delta Area::get_write_length(); // area } void Protection::write(OutStreamP out) { Physical::write(out); if (!(area == areaNext)) { cerr << "Protection::write(), area != areaNext." << endl; } out->write_signed_char(delta); area.write(out); } Boolean Protection::collidable() { return False; } const Area &Protection::get_area() { return area; } const Area &Protection::get_area_next() { return areaNext; } void Protection::follow(const Area &followeeArea,Dir, Boolean currentAndNext) { areaBaseNext = followeeArea; Pos basePos; Size baseSize; areaBaseNext.get_rect(basePos,baseSize); Pos nPos(basePos.x - delta,basePos.y - delta); Size nSize; nSize.width = baseSize.width + 2 * delta; nSize.height = baseSize.height + 2 * delta; if (nSize.width < 0) { nSize.width = 0; } if (nSize.height < 0) { nSize.height = 0; } // Don't get bigger than a grid cell. if (nSize.width > OL_GRID_WIDTH) { nSize.width = OL_GRID_WIDTH; } if (nSize.height > OL_GRID_HEIGHT) { nSize.height = OL_GRID_HEIGHT; } Area nArea(AR_RECT,nPos,nSize); areaNext = nArea; update_area_next(currentAndNext); } void Protection::act() { _act(); Physical::act(); } void Protection::update() { area = areaNext; Physical::update(); } void Protection::dr_clock() { // We are not really doing all the right things here. // should do animation purely on the Client side like Moving does. // areaNext should be computed in _act(), not in follow(). // We don't know if dr_clock() will be called before, or after // follow(). Of course, we already have the same problem with StandAlone. _act(); Physical::dr_clock(); } void Protection::update_area_next(Boolean currentAndNext) { // Also set current state. if (currentAndNext) { area = areaNext; } Physical::update_area_next(currentAndNext); } void Protection::_act() { delta++; if (delta == DELTA_MAX) { delta = DELTA_MIN; } } Moving::Moving(const MovingContext &m_c, MovingXdata &x_data, WorldP w, LocatorP l, const Pos &raw_pos, Dir dirInitial) : Physical(m_c.physicalContext,w,l) { movingXdata = &x_data; mc = &m_c; rawPos = rawPosNext = raw_pos; rawPosChanged = False; dir = dirNext = dirInitial; /* Don't change. See Shell. */ if (m_c.animMax[dir]) { movingAnimNum = Utils::choose(m_c.animMax[dir]); } else { movingAnimNum = 0; } assert(movingAnimNum < PH_ANIM_MAX); // offsets[CO_air] and sizes[CO_air] are guaranteed to be set. // Be careful to set movingXdata and mc before calling get_offsets(). Size *offsets = get_offsets(); Area nArea(AR_RECT,raw_pos + offsets[dir],mc->sizes[dir]); area = areaNext = nArea; WorldP world = get_world(); hitWall = hitWallNext = !world->open(area); moverVel.set_zero(); extraVelNextSet = False; assert(context_valid()); // invisible initializes itself. invisibleNext = 0; attackHook = NULL; // NOTE: protection initializes itself. }; Moving::Moving() { assert(0); } Moving::Moving(InStreamP in, const MovingContext &m_c, MovingXdata &x_data, WorldP w, LocatorP l) : Physical(in,m_c.physicalContext,w,l) { movingXdata = &x_data; mc = &m_c; rawPosChanged = False; hitWall = hitWallNext = False; moverVel.set_zero(); extraVelNextSet = False; assert(context_valid()); // invisible initializes itself. invisibleNext = 0; attackHook = NULL; movingAnimNum = 0; // Don't choose randomly, we don't know the dir. _update_from_stream(in); } DEFINE_UPDATE_FROM_STREAM(Moving,Physical) void Moving::_update_from_stream(InStreamP in) { // invisible and movingAnimNum u_char flags = in->read_char(); Boolean isInvisible = (flags & 0x80) >> 7; if (isInvisible) { // Don't really have to worry about this time running out, // object will get nuked if CLIENT_OLD_OBJECT_KILL turns pass without // another update. invisible.set(PH_INVISIBLE_TIME); } else { // Set not invisible. invisible.set(0); } // All but high bit, 128 frames maximum // movingAnimNum = flags & 0x7F; rawPos.read(in); rawPosNext = rawPos; area.read(in); areaNext = area; Dir dirRead = (Dir)in->read_char(); // Changing direction, so reset animation. if (dir != dirRead) { movingAnimNum = 0; } dir = dirRead; dirNext = dir; NetDataP netData = get_net_data(); // CHANGED: Ignore animation we read in, just increment whatever we have. // // Make sure we have a valid animation frame. // X has less frames than Windows sometimes. if (mc->animMax[dir]) { // Only animate if this is the first update_from_stream() coming in this // turn. if (!netData->clocked_this_turn()) { // new_anim_num() uses the timer. movingAnimNum = new_anim_num(movingAnimNum,mc->animMax[dir]); //movingAnimNum = movingAnimNum % mc->animMax[dir]; } } else { // For funny things like Fireball. movingAnimNum = 0; } protection.read(in); vel.read(in); velNext = vel; assert(movingAnimNum < PH_ANIM_MAX); // Do not call update_area_next(), or do anything to affect followers. } int Moving::get_write_length() { return Physical::get_write_length() + sizeof(char) + // movingAnimNum, invisible Pos::get_write_length() + // rawPos Area::get_write_length() + // area sizeof(char) + // dir protection.get_write_length() + // protection Vel::get_write_length(); // vel // Not sending extraVel, only used for vibrating right now. } void Moving::write(OutStreamP out) { Physical::write(out); // Put invisible on the front of movingAnimNum. assert(movingAnimNum < 128); u_char flags = (u_char)movingAnimNum; Boolean isInvisible = is_invisible(); flags |= (isInvisible & 0x1) << 7; out->write_char(flags); // assert(rawPos == rawPosNext); if (!(rawPos == rawPosNext)) { cerr << "ERROR: rawPos and rawPosNext don't match for some " << get_class_name() << endl; } rawPos.write(out); // assert(area == areaNext); if (!(area == areaNext)) { cerr << "ERROR: area and areaNext don't match for some " << get_class_name() << endl; } area.write(out); if (dir != dirNext) { cerr << "ERROR: dir and dirNext don't match for some " << get_class_name() << endl; } // assert(dir == dirNext); out->write_char((char)dir); protection.write(out); if (!(vel == velNext)) { cerr << get_class_name() << " vel does not equal velNext" << endl; } vel.write(out); } Boolean Moving::is_moving() { return True; } const Area &Moving::get_area() { return area; } const Area &Moving::get_area_next() { return areaNext; } Vel Moving::get_vel() { return vel; } Boolean Moving::ignore_walls() { return False; } Dir Moving::get_dir() { return dir; } Id Moving::get_protection() { return protection; } void Moving::get_followers(PtrList &list) { // The protection. LocatorP locator = get_locator(); PhysicalP p = locator->lookup(protection); if (p) { list.add(p); } Physical::get_followers(list); } void Moving::follow(const Area &followeeArea,Dir, Boolean currentAndNext) { Pos pos = followeeArea.get_middle(); // Don't use set_middle_next(), because that won't handle currentAndNext // properly. _set_middle_next() exists for this purpose. _set_middle_next(pos); update_area_next(currentAndNext); } // This is the version other objects call on this one during the act() // phase. void Moving::set_middle_next(const Pos &middleNext) { _set_middle_next(middleNext); update_area_next(False); } // Used by children who may have to call update_area_next(). void Moving::_set_middle_next(const Pos &middleNext) { Pos pos; Size size; area.get_rect(pos,size); // BUG: This won't work right if a child overrides get_size_offset_next(). Size *offsets = get_offsets(); Pos rPosNext = middleNext - offsets[dir] - 0.5f * size; /* Important to set rawPosChanged if called before act. */ set_raw_pos_next(rPosNext); } void Moving::relocate(const Pos &rPos) { assert(area == areaNext); assert(dir == dirNext); assert(vel == velNext); assert(rawPos == rawPosNext); // assert(extraVel == extraVelNext); assert(hitWall == hitWallNext); Pos pos; Size size; area.get_rect(pos,size); Size delta = pos - rawPos; rawPos = rawPosNext = rPos; Area nArea(AR_RECT,rawPos + delta,size); area = areaNext = nArea; hitWall = hitWallNext = False; } void Moving::set_mapped_next(Boolean val) { LocatorP locator = get_locator(); PhysicalP p = locator->lookup(protection); if (p) { p->set_mapped_next(val); } Physical::set_mapped_next(val); } void Moving::set_protection(const Id &id) { protection = id; } void Moving::set_invisible_next(int turns) { assert(mc->invisibility); if (turns == -1) { // Means turn off invisibility. invisibleNext = -1; } else { // Don't want to reduce the value if already set. invisibleNext = Utils::maximum(invisibleNext,turns); // Works even if invisibleNext was formerly -1. } } Boolean Moving::corporeal_attack(PhysicalP killer,int damage) { // Protection from corporeal attacks. LocatorP l = get_locator(); ProtectionP p = (ProtectionP)l->lookup(protection); if (p && p->corporeal_protect(damage)) { return False; } return Physical::corporeal_attack(killer,damage); } void Moving::heat_attack(PhysicalP killer,int h,Boolean secondary) { // Protection from heat. LocatorP l = get_locator(); ProtectionP p = (ProtectionP)l->lookup(protection); if (p && p->heat_protect(h,secondary)) { return; } Physical::heat_attack(killer,h,secondary); } void Moving::act() { // Just set next variables. _act(); // Don't run into walls. _update_area_next(False); Physical::act(); } // Used by act() and dr_clock(). void Moving::_act() { // Note: no restrictions on vel. (There are in Creature.) extraVel.limit((float)MOVING_EXTRA_VEL_MAX); Vel sumVel = velNext + extraVel + moverVel; moverVel.set_zero(); extraVel.damp((float)EXTRA_VEL_DAMP); if (!rawPosChanged) { rawPosNext = rawPos + sumVel; } } int Moving::new_anim_num(int animPrev,int animMax) { assert(animMax > 0 && animPrev >= 0); int ret = animPrev; // Timer so that one frame may be shown over multiple turns. if (animTimer.ready()) { ret = (ret + 1) % animMax; animTimer.set(get_anim_time()); } animTimer.clock(); assert(ret < PH_ANIM_MAX); return ret; } void Moving::update() { _update(); Physical::update(); } void Moving::_update() { rawPos = rawPosNext; rawPosChanged = False; area = areaNext; if(mc->animMax[dir]) { movingAnimNum = new_anim_num(movingAnimNum,mc->animMax[dir]); } // If direction changed, reset animation counter. if (dir != dirNext) { movingAnimNum = 0; } dir = dirNext; hitWall = hitWallNext; vel = velNext; if (extraVelNextSet) { extraVel = extraVelNext; extraVelNextSet = False; } // Clock first, so will be invisible for, e.g. 1 turn if // invisibleNext is set to 1. invisible.clock(); if (invisibleNext > 0) { // Never reduce the amount of time invisible. invisible.set(Utils::maximum(invisible.get_remaining(),invisibleNext)); } // Turn off invisibility. if (invisibleNext == -1) { invisible.set(0); } // invisibleNext doesn't act like the other next variables, it is just // non-zero for 1 turn. invisibleNext = 0; } void Moving::avoid(PhysicalP other) { const Area &otherArea = other->get_area(); Size offset = area.avoid_no_up(otherArea); set_raw_pos_next(rawPos + offset); } void Moving::collide(PhysicalP other) { Mass m1 = this->get_mass(); Mass m2 = other->get_mass(); // Don`t have collisions with objects of zero mass. if (m1 > 0 && m2 > 0) { Vel v1 = this->get_vel(); Vel v2 = other->get_vel(); Area a1 = this->get_area(); Area a2 = other->get_area(); Pos p1 = a1.get_middle(); Pos p2 = a2.get_middle(); Size delta = p2 - p1; // Fix the bug with objects being dragged along. if (delta.dot(v1) > 0 || // p1 running into p2 delta.dot(v2) < 0) { // p2 running into p1 Vel newVel(compute_collision(m1,v1.dx,m2,v2.dx), compute_collision(m1,v1.dy,m2,v2.dy)); set_vel_next(0); extraVel = newVel; } } } void Moving::die() { LocatorP locator = get_locator(); PhysicalP p = locator->lookup(protection); // Must be careful about killing p because we are in the die() phase. if (p && !p->die_called()) { p->kill_self(); p->die(); } Physical::die(); } void Moving::dr_clock() { _act(); // Keep from going through walls. _update_area_next(False); // We use False instead of True here, because _update() will copy the // next variables into the current ones. _update(); Physical::dr_clock(); } Touching Moving::offset_generator(Dir dir) { return Coord::dir_to_touching(dir); } Dir Moving::get_dir_next() { return dirNext; } int Moving::get_anim_time() { return MOVING_ANIM_TIME; } Size *Moving::get_offsets() { check_generate_offsets(*mc,movingXdata); return movingXdata->offsets; } Boolean Moving::draw_outline(ColorNum &colorNum) { if (is_invisible()) { return False; } // Draw outline around object if controlled by a Human or if a slave to // a Human master. IntelP intel = get_intel(); IntelP master = NULL; if (intel) { if (intel->is_human()) { colorNum = ((HumanP)intel)->get_color_num(); return True; } else { LocatorP locator = get_locator(); master = locator->lookup(((MachineP)intel)->get_master_intel_id()); if (master && master->is_human()) { colorNum = ((HumanP)master)->get_color_num(); return True; } } } return False; } Boolean Moving::generate_masks() { return False; } void Moving::update_area_next(Boolean currentAndNext) { _update_area_next(currentAndNext); Physical::update_area_next(currentAndNext); } void Moving::get_size_offset_next(Size &size,Size &offset,Dir dir) { Size *offsets = get_offsets(); size = mc->sizes[dir]; offset = offsets[dir]; } void Moving::set_size_offset_current_and_next(const Size &size, const Size &offset) { Area nArea(AR_RECT,rawPos + offset,size); area = areaNext = nArea; } void Moving::patch_size_and_size_next() { // Make rawPos{Next} and area{Next} fit with the local implemenation's // (virtual) size and offset. Do this by centering on the area read in // from the remote machine. // local size and offset Size lSize,lOffset; assert(dir == dirNext); get_size_offset_next(lSize,lOffset,dir); // remote pos and size Pos rPos; Size rSize; area.get_rect(rPos,rSize); // Do nothing if they already match if (!(lSize == rSize)) { // A hack to have Creature-specific knowledge here, but fuck it. // This whole method is a hack. Touching touching = CO_dn; // for objects on the ground. if (is_creature()) { touching = Coord::dir_to_touching(dir); } // Create newPos so that touching is preserved over the change in size. Pos newPos; // Get X coordinate if (touching == CO_l) { newPos.x = rPos.x; } else if (touching == CO_r) { newPos.x = rPos.x + rSize.width - lSize.width; } else { newPos.x = rPos.x + (int)(0.5 * (rSize.width - lSize.width)); } // Get Y coordinate if (touching == CO_up) { newPos.y = rPos.y; } else if (touching == CO_dn) { newPos.y = rPos.y + rSize.height - lSize.height; } else { newPos.y = rPos.y + (int)(0.5 * (rSize.height - lSize.height)); } Area newArea(AR_RECT,newPos,lSize); area = newArea; areaNext = newArea; rawPos = newPos - lOffset; rawPosNext = rawPos; } } void Moving::_update_area_next(Boolean currentAndNext) { // Compute areaNext and hitWallNext. May modify rawPosNext or dirNext. Size size,off_set; get_size_offset_next(size,off_set,dirNext); Area first_guess(AR_RECT,rawPosNext + off_set,size); if (ignore_walls()) { areaNext = first_guess; hitWallNext = False; } else { WorldP world = get_world(); Size offset; // Use velNext to avoid ChopperBoy up down through wall bug. switch (world->open_offset(offset,first_guess,velNext)) { case W_NO_SIG: areaNext = first_guess; hitWallNext = False; break; case W_CLOSE: rawPosNext = rawPosNext + offset; areaNext = first_guess; areaNext.shift(offset); hitWallNext = True; /* Doesn't set velocity to 0. */ break; case W_CLOSE_BAD: rawPosNext = rawPosNext + offset; areaNext = first_guess; areaNext.shift(offset); hitWallNext = True; set_vel_next(0); // ???? break; case W_FAILURE: /* Abort all changes to area, rawPos, or dir. */ areaNext = area; rawPosNext = rawPos; dirNext = dir; hitWallNext = True; set_vel_next(0); // ??? Will help? 8/18/94 break; } } // Also apply changes to current value. if (currentAndNext) { rawPos = rawPosNext; area = areaNext; dir = dirNext; hitWall = hitWallNext; } } // If you get an assertion here, you probably messed up something in one of the // .bitmaps files. Boolean Moving::context_valid() { assert(PH_AUTO_GEN != 0 && PH_AUTO_GEN != (CMN_BITS_ID)1); /* if (!(mc->sizes[CO_climb] == mc->sizes[CO_climb_UP]) || !(mc->sizes[CO_climb] == mc->sizes[CO_climb_DN])) return False; */ /* frog.bitmaps */ // See Moving::corner #if 0 if ((mc->animMax[CO_r] && mc->animMax[CO_r_UP]) && !(mc->sizes[CO_r] == mc->sizes[CO_r_UP])) { assert(0); return False; } if ((mc->animMax[CO_r] && mc->animMax[CO_r_DN]) && !(mc->sizes[CO_r] == mc->sizes[CO_r_DN])) { assert(0); return False; } #endif if ((mc->animMax[CO_dn_R] && mc->animMax[CO_dn_L]) && !(mc->sizes[CO_dn_R] == mc->sizes[CO_dn_L])) { assert(0); return False; } #if 0 if ((mc->animMax[CO_l] && mc->animMax[CO_l_UP]) && !(mc->sizes[CO_l] == mc->sizes[CO_l_UP])) { assert(0); return False; } if ((mc->animMax[CO_l] && mc->animMax[CO_l_DN]) && !(mc->sizes[CO_l] == mc->sizes[CO_l_DN])) { assert(0); return False; } #endif if ((mc->animMax[CO_up_R] && mc->animMax[CO_up_L]) && !(mc->sizes[CO_up_R] == mc->sizes[CO_up_L])) { assert(0); return False; } // No longer true that CO_dn or CO_up have to be same size as CO_dn_[RL] and // CO_up_[RL], respectively. Hugger. // // Removed even more constraints for dog on the ceiling and walls. // No longer assert that all objects have CO_air. for (int dir = 0; dir < CO_DIR_MAX; dir++) { if (mc->animMax[dir] < 0) { assert(0); return False; } int m; for (m = 0; m < mc->animMax[dir]; m++) { if (mc->pixmapBits[dir][m] == 0) { // Auto-generated pixmaps will have 0 for several of the // frames. if (mc->pixmapBits[dir][0] != PH_AUTO_GEN) { assert(0); return False; } } #if X11 if (mc->maskBits[dir][m] == 0) { // Auto-generated frames will also have 0 for the // masks. // Also, masks are null for XPM specified pixmaps. if (mc->pixmapBits[dir][0] != PH_AUTO_GEN && !mc->useXPM) { assert(0); return False; } } #endif } // for m } // for dir return True; } float Moving::compute_collision(Mass m1,float v1,Mass m2,float v2) { assert(m1 > 0 && m2 > 0); float A = m1 * v1 + m2 * v2; float B = 0.5f * m1 * v1 * v1 + 0.5f * m2 * v2 * v2; float a = (float)(m1*m2 + m1*m1); float b = -2 * m1 * A; float c = A * A - 2 * m2 * B; float disc = b * b - 4 * a * c; /* Hack. If the discriminant is negative for some reason, returning 0 shouldn't do too much damage. */ if (disc < 0) { return 0.0f; } float v1_1 = (float)((-b + sqrt(disc))/ (2 * a)); float v1_2 = (float)((-b - sqrt(disc))/ (2 * a)); return fabs(v1_1 - v1) > fabs(v1_2 - v1) ? v1_1 : v1_2; } void Moving::check_generate_offsets(const MovingContext &mc,MovingXdata *xdata) { if (xdata->offsetsValid) { return; } Dir dir; // Double check sizeMax Size sizeMax; sizeMax.set(0,0); for (dir = 0; dir < CO_DIR_MAX; dir++) { sizeMax.width = Utils::maximum(mc.sizes[dir].width,sizeMax.width); sizeMax.height = Utils::maximum(mc.sizes[dir].height,sizeMax.height); } if (!(sizeMax == mc.physicalContext.sizeMax)) { cerr << mc.physicalContext.className << ": computed sizeMax (" << sizeMax.width << "," << sizeMax.height << "), doesn't equal that in context (" << mc.physicalContext.sizeMax.width << "," << mc.physicalContext.sizeMax.height << ")" << endl; assert(0); } // Some classes specify their own special code for generating offsets. if (mc.offsetGenerator) { Coord::generate_offsets(xdata->offsets,mc.sizes,sizeMax, mc.offsetGenerator); } else { Coord::generate_offsets(xdata->offsets,mc.sizes,sizeMax, Moving::offset_generator); } xdata->offsetsValid = True; } // Put the CMN_BITS_ID in bits 0-15, transform in 16-19, // isMask in bit 23, 24-31 are zero. void* Moving::compute_key(Dir dir,int animNum,Boolean isMask, const MovingContext* mc,MovingXdata*) { // We know unsigned int is 32 bits from XETP::check_sizes(). // We also know that void* is at least 32 bits. assert(mc->animMax[dir] > 0); // If auto-generated bitmap, use the transformations as part of the // SurfaceManager key. int tNum = 0; const TransformType* transforms = NULL; Dir baseDir = dir; if (mc->pixmapBits[dir][0] == PH_AUTO_GEN) { transforms = Transform2D::get_transforms(tNum,dir,mc->transformOverride); baseDir = Transform2D::get_base(dir,mc->transformOverride); } // Use the pixmap resource id as the base. u_int cmnBitsId = (unsigned intptr_t)mc->pixmapBits[baseDir][animNum]; // It is ok to call OneTransform::compute_key() even if there is no // transformation, will just return 0x0. u_int transKey = OneTransform::compute_key(transforms,tNum); // transKey must fit in 4 bits. assert((transKey & 0xfffffff0) == 0x0); // cmnBitsId must fit in 16 bits. assert((cmnBitsId & 0xffff0000) == 0x0); u_int ret = (transKey << 16) | cmnBitsId; // In bit 23. if (isMask) { ret |= 0x1 << 23; } // Top-most byte must be empty, see comments about unique keys in s_man.h. assert((ret & 0xff000000) == 0x0); return (void*)ret; } void Moving::check_auto_generated(const MovingContext& mc) { int n; int m; for (n = 0; n < CO_DIR_MAX; n++) { if (mc.animMax[n] > 0 && mc.pixmapBits[n][0] == PH_AUTO_GEN) { assert(!Transform2D::is_base(n,mc.transformOverride)); Dir base = Transform2D::get_base(n,mc.transformOverride); // Must be same number of frames. assert(mc.animMax[n] == mc.animMax[base]); // Size must be same as the transformed size of the base. int tNum; const TransformType* transforms = Transform2D::get_transforms(tNum,n,mc.transformOverride); Size trSize = mc.sizes[base]; for (m = 0; m < tNum; m++) { trSize = Transform2D::apply(transforms[m],trSize); } assert(mc.sizes[n] == trSize); } } } Shot::Shot(const ShotContext &s_c,ShotXdata &xdata,WorldP w,LocatorP l, const Pos &p,const Id &shoot_er, Dir shotDir,Dir movingDir) : Moving(s_c.movingContext,xdata,w,l,p,movingDir) { context = &s_c; shooter = shoot_er; const Vel *unitVels = get_unit_vels(); Vel initVel = (float)context->speed * unitVels[shotDir]; set_vel(initVel); set_vel_next(initVel); // This is a little funny, changing next values in the constructor. // Won't be clocked until next turn. if (hit_wall()) { kill_self(); } firstTurn = True; } CONSTRUCTOR_INTERNAL_NODE_IO(Shot,Moving,moving) { context = &cx; firstTurn = False; // shooter initializes itself. } Boolean Shot::is_shot() { return True; } void Shot::avoid(PhysicalP) {} void Shot::collide(PhysicalP other) { if (other->is_shot()) { return; } // If we hit out in a wall, don't hurt anyone. if (hit_wall() && firstTurn) { return; } // Will kill_self() in ::act(). LocatorP locator = get_locator(); PhysicalP p = locator->lookup(shooter); // p may be NULL. other->corporeal_attack(p,context->damage); kill_self(); } void Shot::act() { if (hit_wall()) { // Used to be hit_wall_next() but lances died too soon. kill_self(); } Moving::act(); } void Shot::update() { firstTurn = False; Moving::update(); } Droplet::Droplet(const DropletContext &cx,DropletXdata &x_data, WorldP w,LocatorP l,const Pos &raw_pos,const Vel &vel, Dir dirInitial) : Moving(cx.movingContext,x_data,w,l,raw_pos, (dirInitial == CO_air ? vel.get_dir() : dirInitial)) { // If True, the dir never changes for the droplet. staticDir = (dirInitial != CO_air); inAir = True; set_vel(vel); set_vel_next(vel); context = &cx; } CONSTRUCTOR_INTERNAL_NODE_IO(Droplet,Moving,moving) { inAir = False; // Do we need to stream this out? context = &cx; } Boolean Droplet::collidable() { return False; } void Droplet::act() { Dir dir = get_dir(); if (inAir) { Touching touching; Hanging hanging; const Area &area = get_area(); WorldP world = get_world(); MoverP dummy; world->compute_touching_hanging(touching,hanging,dummy,area); // If in air and hit wall, stick to wall. if (touching != CO_air) { if (!staticDir) { set_dir_next((Dir)touching); } set_vel_next(0); timer.set(context->dissolveTime); inAir = False; } // Account for gravity. else { Vel velNext = get_vel_next(); velNext.dy += PH_GRAVITY; // Reuse FALLING_VEL_MAX. velNext.limit((float)FALLING_VEL_MAX); set_vel_next(velNext); if (!staticDir) { set_dir_next(velNext.get_dir()); } } } else { if (timer.ready()) { kill_self(); } } // Note: doesn't check if world has changed. timer.clock(); Moving::act(); } Size Droplet::get_size_max(DropletType dType) { // Of course the whole point of OO programming is to avoid switch statements // like these. Can't do that with static fntns though. switch (dType) { case DROPLET_BLOOD: return Blood::get_size_max(); case DROPLET_GREEN_BLOOD: return GreenBlood::get_size_max(); case DROPLET_OIL: return OilDroplet::get_size_max(); // case DROPLET_SPARK: // return Spark::get_size_max(); default: assert (0); } // Make compiler happy. Size dummy; dummy.set(0,0); return dummy; } PhysicalP Droplet::create_and_add_if(DropletType dType,WorldP w, LocatorP l,const Pos &p,const Vel &v) { switch (dType) { case DROPLET_BLOOD: return Blood::create_and_add_if(w,l,p,v); case DROPLET_GREEN_BLOOD: return GreenBlood::create_and_add_if(w,l,p,v); case DROPLET_OIL: return OilDroplet::create_and_add_if(w,l,p,v); // case DROPLET_SPARK: // return Spark::create_and_add_if(w,l,p,v); default: assert (0); } return NULL; } int Droplet::get_available_entry(LocatorP locator) { // Initialize new slot. Never uninitialized, but doesn't matter. // No memory leak. if (dropletMax < DROPLET_MAX) { return dropletMax++; } // Else try to reuse existing slots. for (int n = 0; n < DROPLET_MAX; n++) { // Important! Check for recentAdded objects in this lookup. if (!locator->lookup(dropletList[n],True)) return n; } // Nothing free. Tough shit. return -1; } void Droplet::set_entry(int index,const Id &id) { assert(index >=0 && index < dropletMax); dropletList[index] = id; } Id Droplet::dropletList[DROPLET_MAX]; int Droplet::dropletMax = 0; Falling::Falling(const FallingContext &f_c,FallingXdata &x_data, WorldP w,LocatorP l,const Pos &raw_pos,Dir dirInitial) : Moving(f_c.movingContext,x_data,w,l,raw_pos,dirInitial) {} CONSTRUCTOR_INTERNAL_NODE_IO(Falling,Moving,moving) { } void Falling::act() { // Account for gravity. if (gravity_on()) { Touching touching; Hanging hanging; const Area &area = get_area(); WorldP world = get_world(); MoverP dummy; world->compute_touching_hanging(touching,hanging,dummy,area); Vel velNext = get_vel_next(); if (touching != CO_dn) { velNext.dy += PH_GRAVITY; } else { velNext.dy = 0.0f; } if (touching == CO_r || touching == CO_l) { velNext.dx = 0.0f; } velNext.limit((float)FALLING_VEL_MAX); set_vel_next(velNext); } Moving::act(); } Boolean Falling::gravity_on() { return True; } Touchable::Touchable(const TouchableContext &t_c,TouchableXdata &x_data, WorldP w,LocatorP l,const Pos &raw_pos) : Falling(t_c.fallingContext,x_data,w,l,raw_pos) { touched = False; context = &t_c; } CONSTRUCTOR_INTERNAL_NODE_IO(Touchable,Falling,falling) { touched = False; context = &cx; } int Touchable::get_drawing_level() { return 0; } Boolean Touchable::collidable() { return False; } void Touchable::act() { if (!touched) { PhysicalP nearby[OL_NEARBY_MAX]; int nItems; LocatorP l = get_locator(); l->get_nearby(nearby,nItems,this,TOUCHABLE_RADIUS); const Area &area = get_area(); for (int n = 0; n < nItems; n++) { IntelP intel = nearby[n]->get_intel(); if (intel && intel->is_human()) { const Area &otherArea = nearby[n]->get_area(); if (area.overlap(otherArea)) touched = True; } } } Falling::act(); } Heavy::Heavy(const HeavyContext &h_c,HeavyXdata &x_data, WorldP w,LocatorP l,const Pos &raw_pos) : Falling(h_c.fallingContext,x_data,w,l,raw_pos) { context = &h_c; // pusher starts out invalid } CONSTRUCTOR_INTERNAL_NODE_IO(Heavy,Falling,falling) { context = &cx; // pusher starts out invalid } void Heavy::collide(PhysicalP other) { const Vel &vel = get_vel(); if (vel.dy >= HEAVY_DAMAGE_VEL) { const Area &area = get_area(); const Area &otherArea = other->get_area(); const Pos &otherMiddle = otherArea.get_middle(); // If target is below or inside the Heavy. Dir dirTo = area.dir_to(otherMiddle); if ((dirTo == CO_DN_R) || (dirTo == CO_DN) || (dirTo == CO_DN_L) || (dirTo == CO_air)) { // Attack is done by the pusher. LocatorP l = get_locator(); PhysicalP p = l->lookup(pusher); other->corporeal_attack(p,context->damage); } } else { // Don't change pusher if bumped by another stationary object. if (other->get_intel()) { pusher = other->get_id(); } } Falling::collide(other); } Liftable::Liftable(const LiftableContext &cx,LiftableXdata &x_data, WorldP w,LocatorP l,const Pos &raw_pos) : Heavy(cx.heavyContext,x_data,w,l,raw_pos) { // lifter starts out invalid excludedTimer.set_max(LIFTABLE_EXCLUDED_TIME); excludedTime = False; } CONSTRUCTOR_INTERNAL_NODE_IO(Liftable,Heavy,heavy) { // lifter starts out invalid excludedTime = False; } Boolean Liftable::is_liftable() { return True; } int Liftable::get_drawing_level() { return 2; } Boolean Liftable::being_lifted() { if (excludedTime) { return False; } LocatorP l = get_locator(); PhysicalP p = l->lookup(lifter); return (p != NULL); } void Liftable::update() { excludedTimer.clock(); if (excludedTime && excludedTimer.ready()) { excludedTime = False; lifter.invalidate(); // Disable set_dont_collide(). set_dont_collide(lifter); } Heavy::update(); } void Liftable::collide(PhysicalP other) { LocatorP l = get_locator(); PhysicalP p = l->lookup(lifter); if (p && !excludedTime) { // Being lifted, so don't hurt anything. Falling::collide(other); } else { // Just like any other Heavy object. Heavy::collide(other); } } void Liftable::lift(PhysicalP l) { lifter = l->get_id(); set_dont_collide(lifter); excludedTime = False; // Want lifter to get credit for any kills. set_pusher(lifter); } void Liftable::release(PhysicalP) { // Don't invalidate lifter or set_dont_collide() until timer expires. excludedTime = True; excludedTimer.set(); } Item::Item(const ItemContext &c_x, ItemXdata &x_data, WorldP w, LocatorP l, const Pos &pos,Dir dirInitial) : Falling(c_x.fallingContext,x_data,w,l,pos,dirInitial) { //canTake = ntimer; excludedTimer.set_max(ITEM_CAN_TAKE_TIME); dieMessage = DESTROYED; held = False; context = &c_x; cantTake = False; } CONSTRUCTOR_INTERNAL_NODE_IO(Item,Falling,falling) { dieMessage = DESTROYED; context = &cx; cantTake = False; _update_from_stream(in); } DEFINE_UPDATE_FROM_STREAM(Item,Falling) void Item::_update_from_stream(InStreamP in) { holderId.read(in); // Set value of held based on holderId. // We should really just get rid of "held" all together and // just use whether holderId is valid or not. LocatorP locator = get_locator(); if (locator->lookup(holderId)) { held = True; } else { held = False; } } int Item::get_write_length() { return Falling::get_write_length() + Identifier::get_write_length(); // holderId or dummy } void Item::write(OutStreamP out) { Falling::write(out); if (held) { holderId.write(out); } else { // Don't use holderId because it might be invalid. Id dummy; dummy.write(out); } } Boolean Item::can_take(PhysicalP taker) { return (taker->get_id() != excluded || excludedTimer.ready()) && !held && !cantTake; } int Item::get_drawing_level() { return 2; } Id Item::get_holder_id() { if (held) { return holderId; } else { Id invalid; return invalid; } } int Item::get_coolness() { return context->coolness; } void Item::set_quiet_death() { dieMessage = NONE; Falling::set_quiet_death(); } void Item::taken(PhysicalP p) { assert(!held); assert(!cantTake); held = True; holderId = p->get_id(); set_mapped_next(False); } void Item::dropped(PhysicalP dropper) { // Can happen in update phase (from User::die). LocatorP locator = get_locator(); assert(held); assert(locator->lookup(holderId) == dropper); held = False; const Area area = dropper->get_area(); Pos middleNext = area.get_middle(); // Decide whether to drop to side or below. if (dropper->is_creature()) { Touching touching = ((CreatureP)dropper)->get_touching_area(); if (touching == CO_dn) { switch(dropper->get_dir()) { case CO_center_R: case CO_dn_R: middleNext.x -= ITEM_DROP_OFFSET_WIDTH; break; case CO_center_L: case CO_dn_L: middleNext.x += ITEM_DROP_OFFSET_WIDTH; break; default: middleNext.x += (Utils::coin_flip() ? ITEM_DROP_OFFSET_WIDTH : - ITEM_DROP_OFFSET_WIDTH); } } else { middleNext.y += ITEM_DROP_OFFSET_HEIGHT; } } else { middleNext.x += (Utils::coin_flip() ? ITEM_DROP_OFFSET_WIDTH : - ITEM_DROP_OFFSET_WIDTH); } _set_middle_next(middleNext); excludedTimer.set(); excluded = dropper->get_id(); set_mapped_next(True); update_area_next(False); } void Item::use(PhysicalP) { dieMessage = USED; SoundRequest req(context->useSound,get_area()); LocatorP locator = get_locator(); locator->submitSoundRequest(req); } void Item::act() { excludedTimer.clock(); Falling::act(); } void Item::die() { LocatorP locator = get_locator(); ostrstream msg; switch (dieMessage) { case NONE: break; case USED: msg << get_class_name() << " has been used." << ends; locator->message_enq(msg.str()); break; case DESTROYED: msg << get_class_name() << " is destroyed." << ends; locator->message_enq(msg.str()); break; }; Falling::die(); } Boolean Item::gravity_on() { return !is_held(); } AutoUse::AutoUse(const AutoUseContext &c, AutoUseXdata &x_data, WorldP w, LocatorP l, const Pos &pos) : Item(c.itemContext,x_data,w,l,pos) { context = &c; } CONSTRUCTOR_INTERNAL_NODE_IO(AutoUse,Item,item) { context = &cx; } Boolean AutoUse::is_auto_use() { return True; } void AutoUse::collide(PhysicalP other) { IntelP otherIntel = other->get_intel(); // First, ways in which AutoUse doesn't automatically use() itself. if (!other->is_creature() || ((CreatureP)other)->get_holder() || !otherIntel || (!otherIntel->is_human() && context->humansOnly)) { Item::collide(other); } else { use(other); } } Drugs::Drugs(const DrugsContext &c, DrugsXdata &x_data, WorldP w, LocatorP l, const Pos &pos) : AutoUse(c.autoUseContext,x_data,w,l,pos) { context = &c; } CONSTRUCTOR_INTERNAL_NODE_IO(Drugs,AutoUse,autoUse) { context = &cx; } Boolean Drugs::is_drugs() { return True; } void Drugs::taken(PhysicalP p) { // Prevent non-biologicals from picking up drugs. if (p->is_creature() && !((CreatureP)p)->biological()) { ostrstream msg; msg << "Non-Biological Creatures Cannot Use Drugs" << ends; LocatorP locator = get_locator(); locator->arena_message_enq(msg.str(),p); kill_self(); } else { AutoUse::taken(p); } } void Drugs::use(PhysicalP p) { assert(p->alive() && p->is_creature()); LocatorP locator = get_locator(); if (((CreatureP)p)->biological()) { // check for chance of overdose if context->odChance is not 0. assert(context->odChance >= 0); if (context->odChance && Utils::choose(context->odChance) == 0) { p->kill_self(); ostrstream msg; msg << p->get_class_name() << " dies from crack overdose." << ends; locator->message_enq(msg.str()); ostrstream arenaMsg; arenaMsg << "You Died From Drug Overdose" << ends; locator->arena_message_enq(arenaMsg.str(),p); set_quiet_death(); // so doesn't report "has been used" message. } else { /*** Add to Creature's modifier list ***/ ModifierP modifiers = ((CreatureP)p)->get_modifiers(); assert(modifiers); ModifierP m = create_modifier(); modifiers->append_unique(m); } } else { // Shouldn't get here, should kill self when picking it up. ostrstream msg; msg << "Non-Biological Creatures Cannot Use Drugs" << ends; locator->arena_message_enq(msg.str(),p); } kill_self(); AutoUse::use(p); } Shield::Shield(const ShieldContext &s_c, ShieldXdata &x_data, WorldP w, LocatorP l, const Pos &pos) : AutoUse(s_c.autoUseContext,x_data,w,l,pos) { } CONSTRUCTOR_INTERNAL_NODE_IO(Shield,AutoUse,autoUse) { } Boolean Shield::is_shield() { return True; } void Shield::use(PhysicalP p) { assert(p->is_moving()); LocatorP l = get_locator(); Id id = ((MovingP)p)->get_protection(); ProtectionP oldProtection = (ProtectionP) l->lookup(id); if (oldProtection) { oldProtection->kill_self(); } ProtectionP protection = create_protection(p->get_area()); l->add(protection); ((MovingP)p)->set_protection(protection->get_id()); kill_self(); AutoUse::use(p); } Animated::Animated(const AnimatedContext &a_c, AnimatedXdata &x_data, WorldP w, LocatorP l, const Pos &pos,Frame frameInitial) : Item(a_c.itemContext,x_data,w,l,pos,frameInitial + FRAME_BASE) {} CONSTRUCTOR_INTERNAL_NODE_IO(Animated,Item,item) { } Weapon::Weapon(const WeaponContext &c_x, WeaponXdata &x_data, WorldP w, LocatorP l, const Pos &pos,Dir dirInitial) : Item(c_x.itemContext,x_data,w,l,pos,dirInitial) { enteredScope = False; wc = &c_x; } CONSTRUCTOR_INTERNAL_NODE_IO(Weapon,Item,item) { enteredScope = False; wc = &cx; } void Weapon::fire(const Id &,ITcommand) { //not best way to do this //put noise in shot so it can decide if repeating. LocatorP locator; locator = get_locator(); SoundRequest req(wc->soundid,get_area()); locator->submitSoundRequest(req); } Boolean Weapon::useful_no_ammo() { return False; } Boolean Weapon::fire_repeatedly() { return True; } void Weapon::use(PhysicalP) { // Doesn't call Item::use(). } void Weapon::enter_scope_next(PhysicalP) { /* #ifdef PRINT_ERRORS if (enteredScope) cerr << "Warning:: " << get_class_name() << " already entered scope." << endl; #endif */ enteredScope = True; } void Weapon::leave_scope_next(PhysicalP) { /* #ifdef PRINT_ERRORS if (!enteredScope) cerr << "Warning:: " << get_class_name() << " not in scope." << endl; #endif */ enteredScope = False; } void Weapon::take_ammo_from(WeaponP other) { int thisAmmo = get_ammo(); int otherAmmo = other->get_ammo(); if ((thisAmmo != PH_AMMO_UNLIMITED) && (otherAmmo != PH_AMMO_UNLIMITED)) { int maxTake = get_ammo_max() - thisAmmo; int trans = Utils::minimum(maxTake,otherAmmo); set_ammo(thisAmmo + trans); other->set_ammo(otherAmmo - trans); } } Dir Weapon::compute_weapon_dir(ITcommand command) { return Intel::command_weapon_to_dir_8(command); } Cutter::Cutter(const CutterContext &c_x,CutterXdata &x_data, WorldP w,LocatorP l,const Pos &pos) : Weapon(c_x.weaponContext,x_data,w,l,pos,UNHELD_DIR) { // Now just uses UNHELD_DIR for direction when unheld. // When dropping Cutter, seems to drop too far to right?? inScope = inScopeNext = False; context = &c_x; } CONSTRUCTOR_INTERNAL_NODE_IO(Cutter,Weapon,weapon) { inScope = inScopeNext = False; context = &cx; _update_from_stream(in); } DEFINE_UPDATE_FROM_STREAM(Cutter,Weapon) void Cutter::_update_from_stream(InStreamP in) { inScope = (Boolean)in->read_char(); inScopeNext = inScope; } int Cutter::get_write_length() { return Weapon::get_write_length() + sizeof(char); // inScope } void Cutter::write(OutStreamP out) { Weapon::write(out); // Don't know why this happens. Still need to check out this warning. #if 0 if (inScope != inScopeNext) { cerr << "WARNING: Cutter::write() inScope != inScopeNext" << endl; } #endif out->write_char((u_char)inScope); } Boolean Cutter::is_cutter() { return True; } Boolean Cutter::ready() { return False; } int Cutter::get_ammo() { return PH_AMMO_UNLIMITED; } int Cutter::get_ammo_max() { return PH_AMMO_UNLIMITED; } Boolean Cutter::ignore_walls() { return is_held(); } void Cutter::set_ammo(int) { assert(0); } void Cutter::follow(const Area &followeeArea,Dir followeeDir, Boolean currentAndNext) { Pos middle = followeeArea.get_middle(); if (inScope) { set_dir_next(followeeDir); _set_middle_next(middle + context->offsets[followeeDir]); } else { _set_middle_next(middle); } // Don't use set_middle_next(), that won't handle currentAndNext correctly. update_area_next(currentAndNext); } void Cutter::enter_scope_next(PhysicalP user) { if(!entered_scope()) { set_dont_collide(user->get_id()); set_mapped_next(True); killerId = user->get_id(); set_dir_next(CO_center); inScopeNext = True; // moving will now get the correct results from // Cutter::get_size_offset_next() update_area_next(False); Weapon::enter_scope_next(user); } #ifdef PRINT_ERRORS else { cerr << "Warning:: " << get_class_name() << " entered scope twice." << endl; } #endif } void Cutter::leave_scope_next(PhysicalP user) { if (entered_scope()) { // Probably should remember what previous value of dontCollide was. Id invalid; set_dont_collide(invalid); set_mapped_next(False); set_dir_next(UNHELD_DIR); inScopeNext = False; // moving will now get the correct results from // Cutter::get_size_offset_next() update_area_next(False); Weapon::leave_scope_next(user); } #ifdef PRINT_ERRORS else cerr << "Warning:: " << get_class_name() << " left scope twice." << endl; #endif } void Cutter::collide(PhysicalP other) { if (inScope) { PhysicalP p; LocatorP locator = get_locator(); if (p = locator->lookup(killerId)) { assert(p->is_moving()); other->corporeal_attack(p,context->damage); ((MovingP)p)->attack_hook(); } else { other->corporeal_attack(NULL,context->damage); } SoundRequest req(context->cuttingsound,get_area()); locator->submitSoundRequest(req); } else { Weapon::collide(other); } } void Cutter::update() { inScope = inScopeNext; Weapon::update(); } void Cutter::take_ammo_from(WeaponP other) { // Switch healths. if (get_health() >= 0 && other->get_health() > get_health()) { // Don't need temp variable because setting next variables. other->set_health_next(get_health()); set_health_next(other->get_health()); } } Gun::Gun(const GunContext &g_c, GunXdata &x_data, WorldP w, LocatorP l, const Pos &p) : Weapon(g_c.weaponContext,x_data,w,l,p) { assert(g_c.ammoInitial <= g_c.ammoMax); gc = &g_c; Timer ntimer(g_c.shotTime); timer = ntimer; ammo = g_c.ammoInitial; } CONSTRUCTOR_INTERNAL_NODE_IO(Gun,Weapon,weapon) { gc = &cx; _update_from_stream(in); } DEFINE_UPDATE_FROM_STREAM(Gun,Weapon) void Gun::_update_from_stream(InStreamP in) { u_short val = in->read_short(); if (val == 0xffff) { ammo = PH_AMMO_UNLIMITED; } else { ammo = val; } } int Gun::get_write_length() { return Weapon::get_write_length() + sizeof(short); // ammo } void Gun::write(OutStreamP out) { Weapon::write(out); if (ammo == PH_AMMO_UNLIMITED) { out->write_short(0xffff); } else { assert(ammo < USHRT_MAX); // strictly less than to avoid 0xffff out->write_short((u_short)ammo); } } Boolean Gun::is_gun() { return True; } Boolean Gun::ready() { return timer.ready(); } int Gun::get_ammo() { return ammo; } int Gun::get_ammo_max() { return gc->ammoMax; } void Gun::update() { timer.clock(); Weapon::update(); } void Gun::set_ammo(int val) { assert(val <= gc->ammoMax && val >= 0); ammo = val; } Size Gun::get_shot_size(Dir) { return Shell::get_size(); } void Gun::fire(const Id &id,ITcommand command) { Dir dir = compute_weapon_dir(command); _fire(id,dir); } void Gun::_fire(const Id &id,Dir dir,Boolean setTimer,Boolean costsAmmo) /* NOTE: PH_AMMO_UNLIMITED is not currently tested. */ { assert((ammo == PH_AMMO_UNLIMITED) || ((ammo <= gc->ammoMax) && (ammo >= 0))); // Person who fired weapon may not exist anymore. (See FThrower) LocatorP locator = get_locator(); PhysicalP p; if (p = locator->lookup(id)) { if ((dir != CO_air) && ready() && ((ammo == PH_AMMO_UNLIMITED) || (ammo > 0))) { const Area &area = p->get_area(); // Pos newPos = area.adjacent_rect(get_shot_size(dir),dir); Pos newPos = Coord::shot_initial_pos(area, p->is_creature() ? ((CreatureP)p)->get_touching_area() : CO_air, get_shot_size(dir),dir); WorldP world = get_world(); LocatorP locator = get_locator(); PhysicalP shot = create_shot(p,world,locator,newPos,dir); shot->set_dont_collide(p->get_id()); // Shot should not collide with any members of the shooter's composite object. CompositeP comp = p->get_composite(); if (comp) { shot->set_dont_collide_composite(comp->get_composite_id()); } locator->add(shot); if (setTimer) timer.set(); if (costsAmmo && ammo != PH_AMMO_UNLIMITED) ammo--; Weapon::fire(id, dir); } } } PhysicalP Gun::create_shot(PhysicalP shooter,WorldP world,LocatorP locator, const Pos &pos,Dir dir) { PhysicalP shot = new Shell(world,locator,pos,shooter->get_id(), dir); assert (shot); return shot; } SingleGun::SingleGun(const SingleGunContext &c, SingleGunXdata &x_data, WorldP w,LocatorP l,const Pos &p) : Gun(c.gunContext,x_data,w,l,p) {} CONSTRUCTOR_INTERNAL_NODE_IO(SingleGun,Gun,gun) { } Boolean SingleGun::ready() { // Can only fire if previous shot has been destroyed. LocatorP locator = get_locator(); return (locator->lookup(shotId) == NULL) && Gun::ready(); } void SingleGun::fire(const Id &id,ITcommand command) { assert(get_ammo() == PH_AMMO_UNLIMITED); LocatorP locator = get_locator(); PhysicalP p; if (p = locator->lookup(id)) { Dir dir = compute_weapon_dir(command); if ((dir != CO_air) && ready()) { const Area &area = p->get_area(); // Pos newPos = area.adjacent_rect(get_shot_size(dir),dir); Pos newPos = Coord::shot_initial_pos(area, p->is_creature() ? ((CreatureP)p)->get_touching_area() : CO_air, get_shot_size(dir),dir); WorldP world = get_world(); LocatorP locator = get_locator(); PhysicalP shot = create_shot(p,world,locator,newPos,dir); shot->set_dont_collide(p->get_id()); // Shot should not collide with any members of the shooter's composite object. CompositeP comp = p->get_composite(); if (comp) { shot->set_dont_collide_composite(comp->get_composite_id()); } locator->add(shot); shotId = shot->get_id(); Weapon::fire(id, dir); } } } Whistle::Whistle(const WhistleContext &cx, WhistleXdata &xdata, WorldP w, LocatorP l, const Pos &raw_pos) : Weapon(cx.weaponContext,xdata,w,l,raw_pos) { context = &cx; ammo = cx.ammo; petNum = 0; } CONSTRUCTOR_INTERNAL_NODE_IO(Whistle,Weapon,weapon) { context = &cx; petNum = 0; _update_from_stream(in); } DEFINE_UPDATE_FROM_STREAM(Whistle,Weapon) void Whistle::_update_from_stream(InStreamP in) { u_short val = in->read_short(); if (val == 0xffff) { ammo = PH_AMMO_UNLIMITED; } else { ammo = val; } } int Whistle::get_write_length() { return Weapon::get_write_length() + sizeof(short); // ammo } void Whistle::write(OutStreamP out) { Weapon::write(out); if (ammo == PH_AMMO_UNLIMITED) { out->write_short(0xffff); } else { assert(ammo < USHRT_MAX); // strictly less than to avoid 0xffff out->write_short((u_short)ammo); } } Boolean Whistle::ready() { return True; } int Whistle::get_ammo() { return ammo; } int Whistle::get_ammo_max() { return PET_MAX; } int Whistle::get_coolness() { if (ammo <= 0) { return context->coolnessNoAmmo; } return Weapon::get_coolness(); } Boolean Whistle::useful_no_ammo() { return True; } Boolean Whistle::fire_repeatedly() { return False; } void Whistle::fire(const Id &id,ITcommand command) { LocatorP locator = get_locator(); PhysicalP shooter = locator->lookup(id); IntelP shooterIntel = NULL; if (shooter) { shooterIntel = shooter->get_intel(); } // Make sure the guy who fired the whistle exists and has an intel, // else just give up and return. if (!shooter || !shooterIntel) { return; } if (ammo > 0 && petNum < PET_MAX) { // There is a possibility of an alternate pet only if alternateChance is // non-zero. Boolean alternatePet = (context->alternateChance ? (Utils::choose(context->alternateChance) == 0) : False); // Create all pets at once. int n; for (n = 0; n < ammo && petNum < PET_MAX; n++) { PetP pet = create_pet(shooterIntel,alternatePet,n); pets[petNum] = pet->get_intel_id(); petNum++; } set_ammo(0); // First whistle blow after creating any pets says go get em. } // Tell all pets to goGetEm or to return to master. // This happens if no ammo to make new pets, or we already have // the max number of pets. else { LocatorP locator = get_locator(); int n; for (n = 0; n < petNum; n++) { PetP pet = (PetP)locator->lookup(pets[n]); if (pet) { // Give command if pet should go get em, or IT_WEAPON_CENTER to // return to master. pet->go_get_em(shooter,command); } } } } void Whistle::act() { // Make sure Pet intelligences still exist. Delete any that are gone. LocatorP locator = get_locator(); int n; for (n = 0; n < petNum; ) { PetP pet = (PetP)locator->lookup(pets[n]); if (pet) { // Go to next. n++; } else { pets[n] = pets[petNum - 1]; // copy from last element. petNum--; // Do not increment n. } } // Kill self if out of ammo and all pets are dead. if (ammo == 0 && petNum == 0) { kill_self(); } Weapon::act(); } void Whistle::set_ammo(int val) { ammo = val; } Modifier::Modifier(int timedVal,Boolean head) { if (timedVal) { timed = True; timer.set(timedVal); } else { timed = False; } next = NULL; prev = NULL; isHead = head; } Modifier::~Modifier() { // Can be NULL. delete next; } ModifierId Modifier::get_modifier_id() { return M_None; } #if 0 int Modifier::apply(Attribute,int startVal) { // Do nothing. return startVal; } #endif void Modifier::combine(ModifierP m) { assert(get_modifier_id() == m->get_modifier_id()); if (timed) { timer.set(timer.get_remaining() + m->timer.get_remaining()); } delete m; } #if 0 void Modifier::prepend(Modifier *m) { assert(isHead && !m->next && !m->prev && !prev); m->next = next; next->prev = m; m->prev = this; next = m; } #endif void Modifier::append_unique(Modifier *m) { assert(isHead && !m->next && !m->prev && !prev); ModifierId mId = m->get_modifier_id(); Modifier *p = this; Modifier *beforeP; do { // If find Modifier of same type. if (p->get_modifier_id() == mId) { p->combine(m); return; } beforeP = p; p = p->next; } while (p); // Else append to list. beforeP->next = m; m->prev = beforeP; } void Modifier::clear() { assert(isHead); // Clear out everything after this. delete next; next = NULL; } void Modifier::remove() { assert(!isHead); if (next) { next->prev = prev; next = NULL; } if (prev) { prev->next = next; prev = NULL; } } #if 0 void Modifier::remove(Modifier *m) { assert(isHead && m != this); Modifier *p = this; while (p->next != m && p->next) { p = p->next; } p->next = m->next; m->next = NULL; } #endif #if 0 int Modifier::apply_list(Attribute attr,int startVal) { // Feels like 6.001 all over again. int nextVal = apply(attr,startVal); if (next) return next->apply_list(attr,nextVal); else return nextVal; } #endif int Modifier::apply(Attribute attr,int val) { if (next) return next->apply(attr,val); else return val; } void Modifier::clock(PhysicalP p) { Boolean deleteThis = False; if (timed && timer.ready()) { deleteThis = True; remove(); // fixup surrounding list } timer.clock(); // Call down chain. if (next) next->clock(p); // Only call at end of function. if (deleteThis) { preDie(p); delete this; } } void Modifier::preDie(PhysicalP) { } Doubler::Doubler(int timed) :Modifier(timed) { multiplier = 2.0f; increment = 1.0f; } void Doubler::combine(ModifierP other) { assert(other->get_modifier_id() == get_modifier_id()); multiplier += increment; delete other; // Should probably also call Modifier::combine to add the timers. } Creature::Creature(const CreatureContext &c_c, CreatureXdata &x_data, PtrList *caps, WorldP w, LocatorP l, const Pos &raw_pos) : Moving(c_c.movingContext,x_data,w,l,raw_pos) { touching = CO_air; canClimb = False; stance = stanceNext = CO_air; grav = gravNext = 0; cc = &c_c; Timer nTimer(CORPSE_TIME); corpseTimer = nTimer; assert(context_valid()); birthTime = time(NULL); stunNext = 0; wantClimb = False; // Empty modifier list, mark as the head. modifiers = new Modifier(0,True); assert(modifiers); initialize_abilities(caps); check_abilities(); } Creature::Creature() { assert(0); } Creature::Creature(InStreamP in,const CreatureContext &cx, CreatureXdata &x_data, WorldP w,LocatorP l) : Moving(in,cx.movingContext,x_data,w,l) { touching = CO_air; canClimb = False; stance = stanceNext = CO_air; grav = gravNext = 0; cc = &cx; assert(context_valid()); birthTime = 0; stunNext = 0; wantClimb = False; // Empty modifier list, mark as the head. modifiers = new Modifier(0,True); assert(modifiers); // Will set all abilities to NULL initialize_abilities(NULL); // Will read abilities from the stream. _update_from_stream(in); if (!in->alive()) { creation_failed(); return; } check_abilities(); } void Creature::update_from_stream(InStreamP in) { Moving::update_from_stream(in); _update_from_stream(in); } void Creature::_update_from_stream(InStreamP in) { ABBitField bitIn = in->read_int(); ABBitField bitCurrent = compute_abilities_bit_field(); // Update the existing abilities from the stream. if (bitIn == bitCurrent) { // Abilities will have been written in order. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->update_from_stream(in); } } } // Abilities have changed, recreate all abilities. else { // Delete all existing ones. int n; for (n = 0; n < AB_MAX; n++) { delete abilities[n]; abilities[n] = NULL; } // Read new ones from stream. for (n = 0; n < AB_MAX; n++) { // If stream provides ability n. if (bitIn & (0x1 << n)) { abilities[n] = Ability::create_from_stream(in,n,this); } } } } int Creature::get_write_length() { // bit field of all the abilities. int bitFieldLen = sizeof(ABBitField); // size of actual abilities int abLen = 0; for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abLen += abilities[n]->get_write_length(); } } return Moving::get_write_length() + bitFieldLen + abLen; } void Creature::write(OutStreamP out) { Moving::write(out); int n; ABBitField bitField = compute_abilities_bit_field(); out->write_int(bitField); for (n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->write(out); } } } Creature::~Creature() { delete modifiers; for (int n = 0; n < AB_MAX; n++) { // may be NULL delete abilities[n]; } } Boolean Creature::is_creature() { return True; } ITcommand Creature::get_command() { ITcommand mCommand = Moving::get_command(); if (stunned()) { // The only commands allowed when stunned are // IT_WEAPON_CHANGE, IT_WEAPON_DROP, IT_ITEM_USE, IT_ITEM_CHANGE, // and IT_ITEM_DROP. Can still whip out your chainsaw to cut up huggers. if (mCommand == IT_WEAPON_CHANGE || mCommand == IT_WEAPON_DROP || mCommand == IT_ITEM_USE || mCommand == IT_ITEM_CHANGE || mCommand == IT_ITEM_DROP) { return mCommand; } else { return IT_NO_COMMAND; } } else { return mCommand; } } Boolean Creature::on_door() { const Area &area = get_area(); Loc dest; WorldP world = get_world(); return world->check_door(area.middle_wsquare(),dest); } void Creature::get_followers(PtrList &list) { for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->get_followers(list); } } Moving::get_followers(list); } Boolean Creature::command_repeatable(ITcommand c) { // Don't repeat going through doors, or getting up from a crouch. if (c == IT_UP && (on_door() || get_stance() == CO_dn)) { return False; } // See if any ability disallows the command repeat. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (!abilities[n]->command_repeatable(c)) { return False; } } } return Moving::command_repeatable(c); } Boolean Creature::standing_squarely() { Boolean ret = ((stance == CO_dn || stance == CO_center) && hanging.corner != CO_dn_R && hanging.corner != CO_dn_L); return ret; } ClassId Creature::get_weapon_string(const char *&str) { // Check all the abilities. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { ClassId cId = abilities[n]->get_weapon_string(str); if (cId != A_None) { return cId; } } } return Moving::get_weapon_string(str); } int Creature::get_drawing_level() { // Check all the abilities. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { int val = abilities[n]->get_drawing_level(); if (val != -1) { return val; } } } return Moving::get_drawing_level(); } int Creature::get_anim_time() { // Check all the abilities. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { int val = abilities[n]->get_anim_time(); if (val != -1) { return val; } } } return Moving::get_anim_time(); } void Creature::follow(const Area &followeeArea,Dir followeeDir, Boolean currentAndNext) { // Check all the abilities. for (int n = 0; n < AB_MAX; n++) { // Go until one returns True. if (abilities[n]) { if (abilities[n]->follow(followeeArea,followeeDir,currentAndNext)) { return; } } } Moving::follow(followeeArea,followeeDir,currentAndNext); } Boolean Creature::swap_protect() { // Go until one of the abilities returns True. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->swap_protect()) { return True; } } } return Moving::swap_protect(); } Boolean Creature::frog_protect() { // Go until one of the abilities returns True. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->frog_protect()) { return True; } } } return Moving::frog_protect(); } Boolean Creature::stick_touching(Touching touching) { // Does any ability allow touching in the given direction for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->stick_touching(touching)) { return True; } } } return (touching == CO_dn); } int Creature::compute_droplets_num(int damage) { if (damage == 0) { return 0; } // "superficial damage" if (damage < 0) { damage = -damage; } return Utils::minimum(CREATURE_DROPLETS_MAX, (int)ceil(damage * CREATURE_DROPLET_FACTOR)); } void Creature::stun_next(int time) { stunNext = time; #if 0 stunTimer.set(time); // Kind of a hack, mucking with set_vel(0); set_vel_next(0); set_extra_vel_next(0); set_stance_next(CO_air); #endif } void Creature::set_intel(IntelP intel) { for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->set_intel(intel); } } Moving::set_intel(intel); } void Creature::set_quiet_death() { // Get rid of corpses. if (!alive()) { set_delete_me(); } Moving::set_quiet_death(); } void Creature::heal() { for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->heal(); } } Moving::heal(); } void Creature::set_mapped_next(Boolean val) { for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->set_mapped_next(val); } } Moving::set_mapped_next(val); } void Creature::drop_all() { for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->drop_all(); } } Moving::drop_all(); } Boolean Creature::corporeal_attack(PhysicalP killer,int damage) { // Does not take the possible existence of a Protection into account for // figuring out killer. IntelP kIntel; IntelId kIntelId; // Starts out invalid. if (killer && (kIntel = killer->get_intel())) { kIntelId = kIntel->get_intel_id(); } IntelP intel = get_intel(); if (intel) { intel->set_killer_intel_id(kIntelId); } // Protection from corporeal attacks. damage = apply_modifiers(Modifier::CORPOREAL_ATTACK,damage); // Put in a hook for Ability::corporeal_attack() when we need it. if (Moving::corporeal_attack(killer,damage)) { // Don't create blood if attack is blocked. if (cc->dropletType != DROPLET_NONE) { // Create Blood drop. LocatorP locator = get_locator(); const Area &area = get_area(); Pos middle = area.get_middle(); Size bloodSize = Droplet::get_size_max(cc->dropletType); Pos rawPos(middle.x - bloodSize.width / 2, middle.y - bloodSize.height / 2); for (int n = 0; n < compute_droplets_num(damage); n++) { // splatter up, right/left choosen randomly. Vel bloodVel((float)(Utils::choose(2 * SPLATTER_SPEED + 1) - SPLATTER_SPEED), (float)-(Utils::choose(SPLATTER_SPEED)+1)); // Try to create blood drop, don't care about return value. Droplet::create_and_add_if(cc->dropletType,get_world(), locator,rawPos,bloodVel); } } return True; } return False; } void Creature::heat_attack(PhysicalP killer,int heat,Boolean secondary) { // Does not take the possible existence of a Protection into account for // figuring out killer. IntelP kIntel; IntelId kIntelId; // Starts out invalid. if (killer && (kIntel = killer->get_intel())) { kIntelId = kIntel->get_intel_id(); } IntelP intel = get_intel(); if (intel) { intel->set_killer_intel_id(kIntelId); } // Protection from heat attacks. // // Not sure whether modifiers should go before or after abilities. heat = apply_modifiers(Modifier::HEAT_ATTACK,heat); // See if one of the Abilities wants to handle it. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->heat_attack(killer,heat,secondary)) { return; } } } _heat_attack(killer,heat,secondary); } void Creature::_heat_attack(PhysicalP killer,int heat,Boolean secondary) { Moving::heat_attack(killer,heat,secondary); } void Creature::act() { // Make all abilities act. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->act(); } } WorldP world = get_world(); Vel velNext = get_vel_next(); // Account for gravity. velNext.dy += gravNext; // Limit velocities. switch (stanceNext) { case CO_air: velNext.limit((float)apply_modifiers(Modifier::AIR_SPEED,cc->airSpeed, CREATURE_AIR_SPEED_MAX)); break; case CO_center: velNext.limit((float)apply_modifiers(Modifier::CENTER_SPEED,cc->centerSpeed, CREATURE_CENTER_SPEED_MAX)); break; case CO_climb: velNext.limit((float)apply_modifiers(Modifier::CLIMB_SPEED,cc->climbSpeed, CREATURE_CLIMB_SPEED_MAX)); break; case CO_r: case CO_l: case CO_up: { // Hack, shouldn't put knowledge about Sticky here. // Should make a Creature::get_wall_crawl_speed() that defers to the // abilities. Then Sticky::get_wall_crawl_speed() would return non-zero. Sticky* sticky = (Sticky*)get_ability(AB_Sticky); // We shouldn't be on the walls if we aren't Sticky. // This may change sometime, requiring us to undo this hack. assert(sticky); Speed wallCrawlSpeed = sticky->get_wall_crawl_speed(); velNext.limit((float)apply_modifiers(Modifier::CLIMB_SPEED,wallCrawlSpeed, CREATURE_CLIMB_SPEED_MAX)); } break; case CO_dn: velNext.limit((float)apply_modifiers(Modifier::CRAWL_SPEED,cc->crawlSpeed, CREATURE_CRAWL_SPEED_MAX)); break; default: assert(0); } set_vel_next(velNext); set_dir_next(compute_dir(stanceNext,velNext)); // See if we are high. if (alive()) { int high = apply_modifiers(Modifier::HIGH,0); if (high) { // vibrate parallel to the wall we are touching, or up/down for ladder. const Vel *unitVels = get_unit_vels(); Dir dir = Coord::parallel_dir(stanceNext); if (dir == CO_air) { // vibrate in any of sixteen directions. dir = Utils::choose(CO_DIR_PURE) + CO_R; } set_extra_vel_next((float)(CREATURE_HIGH_VIBRATE * (Utils::coin_flip() ? 1 : -1)) * // direction or its opposite unitVels[dir]); } } // Move along with Movers. MoverP mover = world->lookup(touchingMoverId); // Only move along with mover if you stick in the right direction. if (mover) { if (stick_touching(touching)) { set_mover_vel(mover->get_vel()); } } // Check for doors. ITcommand command = get_command(); Loc dest; const Area &area = get_area(); Loc loc = area.middle_wsquare(); const Pos &rawPos = get_raw_pos(); // If on a door and command is IT_UP, go through the door. if (command == IT_UP && world->check_door(loc,dest)) { Pos p; p.x = rawPos.x - loc.c * WSQUARE_WIDTH + dest.c * WSQUARE_WIDTH; p.y = rawPos.y - loc.r * WSQUARE_HEIGHT + dest.r * WSQUARE_HEIGHT; set_raw_pos_next(p); } // If marked for wanting to climb and we can do so, then climb. if (canClimb && wantClimb) { set_stance_next(CO_climb); set_vel_next(0); set_grav_next(0); center_wsquare_x_next(area.middle_wsquare()); set_want_climb(False); } if (! alive()) { if (get_intel()) { cerr << "Warning: Creature::act(): Corpse with non-NULL intelligence." << endl; } corpseTimer.clock(); } Moving::act(); } void Creature::update() { // update all abilities for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->update(); } } // Clock Creature's modifiers, then Intel's modifiers. // Maybe Intel's modifiers should be clocked in Intel::clock, but for // now I'll keep them in the same place. modifiers->clock(this); IntelP intel = get_intel(); if (intel) { ModifierP iModifiers = intel->get_modifiers(); iModifiers->clock(this); } stance = stanceNext; grav = gravNext; const Area area = get_area_next(); WorldP world = get_world(); MoverP touchingMover; world->compute_touching_hanging(touching,hanging,touchingMover,area); if (touchingMover) { touchingMoverId = touchingMover->get_mover_id(); } else { touchingMoverId.invalidate(); } canClimb = world->can_climb(area.middle_wsquare()); // Remove corpse after time limit or if damaged enough. if (!get_quiet_death() && !alive() && (corpseTimer.ready() || (get_health() < - cc->corpseHealth))) { LocatorP locator = get_locator(); ostrstream msg; if (corpseTimer.ready()) { msg << get_class_name() << " corpse has decomposed." << ends; locator->message_enq(msg.str()); } else { msg << get_class_name() << " corpse has been destroyed." << ends; locator->message_enq(msg.str()); } set_delete_me(); } if (stunNext) { stunTimer.set(stunNext); // All before Moving::update. set_vel_next(0); set_extra_vel_next(0); set_stance_next(CO_air); stunNext = 0; } stunTimer.clock(); alienImmune.clock(); Moving::update(); } void Creature::die() { // die for all abilities for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { abilities[n]->die(); } } LocatorP locator = get_locator(); if (!get_quiet_death()) { if (cc->deadSoundId) { SoundRequest req(cc->deadSoundId,get_area()); locator->submitSoundRequest(req); } // Increment Creature stats stats.add_death(birthTime); // Increment stats for the leaf node. const PhysicalContext &pc = cc->movingContext.physicalContext; if (pc.get_stats) { Stats &leafStats = pc.get_stats(pc.arg); leafStats.add_death(birthTime); } } // Prepare for being a corpse. if (!get_quiet_death()) { corpseTimer.set(); // Set current and next values because called in update phase. Vel velNew(0.0f,(float)DROP_SPEED); set_vel_next(velNew); set_stance_next(CO_air); ostrstream msg; msg << get_class_name() << " has died." << ends; locator->message_enq(msg.str()); set_no_death_delete(); } // Deal with awarding kills. IntelP intel = get_intel(); IntelId killerIntelId; if (intel) { killerIntelId = intel->get_killer_intel_id(); } IntelP killerIntel; if (killerIntel = locator->lookup(killerIntelId)) { // Compute soups. I.e. A human killed by something other than a // different human. if (intel && intel->is_human() && (!killerIntel || !killerIntel->is_human() || killerIntel == intel)) { intel->add_soup(); } // Award killer with a kill of the appropriate type. Don't get kills // for taking yourself out. if (intel && killerIntel && intel != killerIntel) { if (intel->is_human()) { killerIntel->add_human_kill(); } else if (((MachineP)intel)->is_enemy()) { killerIntel->add_enemy_kill(); } } } else if (intel && intel->is_human()) { intel->add_soup(); } // We may be changing the next variables because the creature will be a // corpse next turn. update_area_next(False); Moving::die(); } void Creature::dr_clock() { CompositeP comp = get_composite(); if (comp && !comp->is_leader()) { // The leader will take care of calling follow() to make all the // others follow. return; } Moving::dr_clock(); } void Creature::collide(PhysicalP other) { // Go until one of the abilities returns True. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->collide(other)) { return; } } } Moving::collide(other); } AbilityP Creature::get_ability(AbilityId cId) { // Take advantage of abilities being indexed by AbilityId. if (abilities[cId]) { assert(abilities[cId]->get_ability_id() == cId); return abilities[cId]; } return NULL; } HolderP Creature::get_holder() { for (int n = 0; n < AB_MAX; n++) { if (abilities[n] && abilities[n]->is_holder()) { return (HolderP)abilities[n]; } } return NULL; } LocomotionP Creature::get_locomotion() { for (int n = 0; n < AB_MAX; n++) { if (abilities[n] && abilities[n]->is_locomotion()) { return (LocomotionP)abilities[n]; } } return NULL; } CompositeP Creature::get_composite() { for (int n = 0; n < AB_MAX; n++) { if (abilities[n] && abilities[n]->is_composite()) { return (CompositeP)abilities[n]; } } return NULL; } void Creature::add_ability(AbilityP a) { AbilityId aId = a->get_ability_id(); assert(!abilities[aId]); // Would be better to have this in the constructor, but we // have the chicken-and-egg problem when constructing Creatures. a->initialize(this); abilities[aId] = a; // For debugging. check_abilities(); } void Creature::remove_ability(AbilityId aId) { assert(abilities[aId]); // To make User, Carrier, drop stuff, etc. abilities[aId]->die(); // May be weird for Morphed, will cause Corpse to be dropped. delete(abilities[aId]); abilities[aId] = NULL; } Health Creature::get_health_max() { return apply_modifiers(Modifier::HEALTH_MAX, cc->movingContext.physicalContext.health, CREATURE_HEALTH_MAX_MAX); } Boolean Creature::generate_masks() { // Could check abilities, e.g. Alien doesn't really need masks, // but difficult to keep in-sync if abilities change. return True; } Speed Creature::get_acceleration() { return apply_modifiers(Modifier::ACCELERATION, cc->acceleration, CREATURE_ACCELERATION_MAX); } Speed Creature::get_jump() { return apply_modifiers(Modifier::JUMP, cc->jump, CREATURE_JUMP_MAX); } Touching Creature::get_touching_stance() { if (stance == CO_center) { return CO_dn; } else if (stance == CO_climb) { return CO_air; } else { return (Touching)stance; } } void Creature::center_wsquare_x_next(const Loc &loc) { const Area area = get_area(); const MovingContext *mc = get_moving_context(); const Pos rawPos = get_raw_pos(); assert (stanceNext == CO_climb); assert (area.overlap(loc)); Size *offsets = get_offsets(); Pos rpos(loc.c * WSQUARE_WIDTH + (WSQUARE_WIDTH - mc->sizes[CO_climb].width) / 2 - offsets[CO_climb].width, rawPos.y); set_raw_pos_next(rpos); } void Creature::corner(const Hanging &hanging) { Pos rpos; const MovingContext *mc = get_moving_context(); WorldP world = get_world(); Size *offsets = get_offsets(); ////// Corner around a Loc. if (hanging.type == Hanging::LOC) { const Loc &loc = hanging.loc; switch (hanging.corner) { case CO_up_L: // Final is CO_r_DN. assert(stanceNext == CO_r); rpos.x = loc.c * WSQUARE_WIDTH - mc->sizes[CO_r].width - offsets[CO_r].width; rpos.y = (loc.r + 1) * WSQUARE_HEIGHT - (int)ceil(0.5 * mc->sizes[CO_r].height) - offsets[CO_r].height; break; case CO_dn_L: // Final is CO_r_UP. assert(stanceNext == CO_r); rpos.x = loc.c * WSQUARE_WIDTH - mc->sizes[CO_r].width - offsets[CO_r].width; rpos.y = loc.r * WSQUARE_HEIGHT - (int)floor(0.5 * mc->sizes[CO_r].height) - offsets[CO_r].height; break; case CO_l_UP: // Final is CO_dn_R. assert(stanceNext == CO_dn); rpos.x = (loc.c + 1) * WSQUARE_WIDTH - (int)ceil(0.5 * mc->sizes[CO_dn].width) - offsets[CO_dn].width; rpos.y = loc.r * WSQUARE_HEIGHT - mc->sizes[CO_dn].height - offsets[CO_dn].height; break; case CO_r_UP: // Final is CO_dn_L. assert(stanceNext == CO_dn); rpos.x = loc.c * WSQUARE_WIDTH - (int)floor(0.5 * mc->sizes[CO_dn].width) - offsets[CO_dn].width; rpos.y = loc.r * WSQUARE_HEIGHT - mc->sizes[CO_dn].height - offsets[CO_dn].height; break; case CO_up_R: // Final is CO_l_DN. assert(stanceNext == CO_l); rpos.x = (loc.c + 1) * WSQUARE_WIDTH - offsets[CO_l].width; rpos.y = (loc.r + 1) * WSQUARE_HEIGHT - (int)ceil(0.5 * mc->sizes[CO_l].height) - offsets[CO_l].height; break; case CO_dn_R: // Final is CO_l_UP. assert(stanceNext == CO_l); rpos.x = (loc.c + 1) * WSQUARE_WIDTH - offsets[CO_l].width; rpos.y = loc.r * WSQUARE_HEIGHT - (int)floor(0.5 * mc->sizes[CO_l].height) - offsets[CO_l].height; break; case CO_l_DN: // Final is CO_up_R. assert(stanceNext == CO_up); rpos.x = (loc.c + 1) * WSQUARE_WIDTH - (int)ceil(0.5 * mc->sizes[CO_up].width) - offsets[CO_up].width; rpos.y = (loc.r + 1) * WSQUARE_HEIGHT - offsets[CO_up].height; break; case CO_r_DN: // Final is CO_up_L. assert(stanceNext == CO_up); rpos.x = loc.c * WSQUARE_WIDTH - (int)floor(0.5 * mc->sizes[CO_up].width) - offsets[CO_up].width; rpos.y = (loc.r + 1) * WSQUARE_HEIGHT - offsets[CO_up].height; break; default: assert(0); } } ////// Corner around a Mover. else if (hanging.type == Hanging::MOVER) { MoverP mover = world->lookup(hanging.moverId); if (!mover) { // Mover doesn't exist anymore. return; } const Area &area = mover->get_area(); Pos pos; Size size; area.get_rect(pos,size); switch (hanging.corner) { case CO_up_L: // Final is CO_r_DN. assert(stanceNext == CO_r); rpos.x = pos.x - mc->sizes[CO_r].width - offsets[CO_r].width; rpos.y = (pos.y + size.height) - (int)ceil(0.5 * mc->sizes[CO_r].height) - offsets[CO_r].height; break; case CO_dn_L: // Final is CO_r_UP. assert(stanceNext == CO_r); rpos.x = pos.x - mc->sizes[CO_r].width - offsets[CO_r].width; rpos.y = pos.y - (int)floor(0.5 * mc->sizes[CO_r].height) - offsets[CO_r].height; break; case CO_l_UP: // Final is CO_dn_R. assert(stanceNext == CO_dn); rpos.x = (pos.x + size.width) - (int)ceil(0.5 * mc->sizes[CO_dn].width) - offsets[CO_dn].width; rpos.y = pos.y - mc->sizes[CO_dn].height - offsets[CO_dn].height; break; case CO_r_UP: // Final is CO_dn_L. assert(stanceNext == CO_dn); rpos.x = pos.x - (int)floor(0.5 * mc->sizes[CO_dn].width) - offsets[CO_dn].width; rpos.y = pos.y - mc->sizes[CO_dn].height - offsets[CO_dn].height; break; case CO_up_R: // Final is CO_l_DN. assert(stanceNext == CO_l); rpos.x = (pos.x + size.width) - offsets[CO_l].width; rpos.y = (pos.y + size.height) - (int)ceil(0.5 * mc->sizes[CO_l].height) - offsets[CO_l].height; break; case CO_dn_R: // Final is CO_l_UP. assert(stanceNext == CO_l); rpos.x = (pos.x + size.width) - offsets[CO_l].width; rpos.y = pos.y - (int)floor(0.5 * mc->sizes[CO_l].height) - offsets[CO_l].height; break; case CO_l_DN: // Final is CO_up_R. assert(stanceNext == CO_up); rpos.x = (pos.x + size.width) - (int)ceil(0.5 * mc->sizes[CO_up].width) - offsets[CO_up].width; rpos.y = (pos.y + size.height) - offsets[CO_up].height; break; case CO_r_DN: // Final is CO_up_L. assert(stanceNext == CO_up); rpos.x = pos.x - (int)floor(0.5 * mc->sizes[CO_up].width) - offsets[CO_up].width; rpos.y = (pos.y + size.height) - offsets[CO_up].height; break; default: assert(0); } } ////// invalid Hanging::type else { assert(0); } set_raw_pos_next(rpos); } int Creature::apply_modifiers(Attribute attr,int initial,int maximum) { // First apply Creature's modifiers int ret = modifiers->apply(attr,initial); // Then, apply Intel's modifiers. IntelP intel = get_intel(); if (intel) { ModifierP iModifiers = intel->get_modifiers(); ret = iModifiers->apply(attr,ret); } // limit to maximum if supplied. if (maximum != -1) { ret = Utils::minimum(ret,maximum); } return ret; } Dir Creature::get_dir_next() { return Moving::get_dir_next(); } void Creature::update_area_next(Boolean currentAndNext) { Moving::update_area_next(currentAndNext); } void Creature::get_size_offset_next(Size &size,Size &offset,Dir dir) { // Give it to the first ability that returns True. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->get_size_offset_next(size,offset,dir)) { return; } } } _get_size_offset_next(size,offset,dir); } void Creature::_get_size_offset_next(Size &size,Size &offset,Dir dir) { // get_quiet_death() check taken care of in Creature::die. if (!alive_next() && !get_quiet_death()) { Moving::get_size_offset_next(size,offset,DEAD_DIR); } else { Moving::get_size_offset_next(size,offset,dir); } } void Creature::get_pixmap_mask(Xvars &xvars,int dpyNum,CMN_IMAGEDATA &pixmap, Dir dir,int animNum) { // Give it to the first ability that returns True. for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->get_pixmap_mask(xvars,dpyNum,pixmap,dir,animNum)) { return; } } } _get_pixmap_mask(xvars,dpyNum,pixmap,dir,animNum); } void Creature::_get_pixmap_mask(Xvars &xvars,int dpyNum,CMN_IMAGEDATA &pixmap, Dir dir,int animNum) { if (!alive()) { Moving::get_pixmap_mask(xvars,dpyNum,pixmap,DEAD_DIR,0); } else { Moving::get_pixmap_mask(xvars,dpyNum,pixmap,dir,animNum); } } void Creature::set_stance_next(const Stance &stance) { assert((stance == CO_r) || (stance == CO_dn) || (stance == CO_l) || (stance == CO_up) || (stance == CO_air) || (stance == CO_center) || (stance == CO_climb)); stanceNext = stance; } Dir Creature::compute_dir(const Stance &st,const Vel &v) { Dir ret = CO_center; switch (st) { case CO_r: if (fabs(v.dy) < VEL_SMALL) ret = CO_r; else ret = v.dy > 0 ? CO_r_DN : CO_r_UP; break; case CO_dn: if (fabs(v.dx) < VEL_SMALL) ret = CO_dn; else ret = v.dx > 0 ? CO_dn_R : CO_dn_L; break; case CO_l: if (fabs(v.dy) < VEL_SMALL) ret = CO_l; else ret = v.dy > 0 ? CO_l_DN : CO_l_UP; break; case CO_up: if (fabs(v.dx) < VEL_SMALL) ret = CO_up; else ret = v.dx > 0 ? CO_up_R : CO_up_L; break; case CO_center: if (fabs(v.dx) < VEL_SMALL) ret = CO_center; else ret = v.dx > 0 ? CO_center_R : CO_center_L; break; // If diagonal, will give horizontal direction. case CO_air: if (fabs(v.dx) < VEL_SMALL) { if (fabs(v.dy) < VEL_SMALL) { ret = CO_air; } else { // Changed ret = v.dy > 0 ? CO_air_DN : CO_air_UP; } } else { ret = v.dx > 0 ? CO_air_R : CO_air_L; } break; // If diagonal, will give horizontal direction. case CO_climb: if (fabs(v.dx) < VEL_SMALL) { if (fabs(v.dy) < VEL_SMALL) { ret = CO_climb; } else { // Changed ret = v.dy > 0 ? CO_climb_DN : CO_climb_UP; } } else { ret = v.dx > 0 ? CO_climb_R : CO_climb_L; } break; default: assert (0); break; } return ret; } Boolean Creature::context_valid() { if ((cc->crawlSpeed < 0) || (cc->centerSpeed < 0) || (cc->airSpeed < 0) || (cc->climbSpeed < 0) || (cc->jump < 0) || (cc->acceleration < 0)) { return False; } return True; } void Creature::initialize_abilities(PtrList *caps) { // Initialize abilities list int n; for (n = 0; n < AB_MAX; n++) { abilities[n] = NULL; } // caps may be NULL if there are no abilities. if (caps) { // WARNING: Make sure to call Ability::initialize(). for (n = 0; n < caps->length(); n++) { AbilityP c = (AbilityP)caps->get(n); AbilityId id = c->get_ability_id(); assert (id >= 0 && id < AB_MAX); // Check for duplicate abilities assert(abilities[id] == NULL); abilities[id] = c; abilities[id]->initialize(this); } delete caps; } } void Creature::check_abilities() { // Check for some incompatabilities. assert(!(abilities[AB_Fighter] && abilities[AB_BuiltIn])); // should be one locomotion and <= one holer. int locomotions = 0; int holders = 0; int n; for (n = 0; n < AB_MAX; n++) { if (abilities[n]) { if (abilities[n]->is_locomotion()) { locomotions++; } if (abilities[n]->is_holder()) { holders++; } } } assert(locomotions == 1); assert(holders <= 1); } ABBitField Creature::compute_abilities_bit_field() { ABBitField bitField = 0x0; for (int n = 0; n < AB_MAX; n++) { if (abilities[n]) { bitField |= (0x1 << n); } } return bitField; } Stats Creature::stats; Ability::Ability() { cre = NULL; } Ability::~Ability(){ } Ability::Ability(InStreamP,CreatureP creat) { cre = creat; } void Ability::initialize(CreatureP creat) { assert(cre == NULL); // Guard against double initialization. cre = creat; } void Ability::update_from_stream(InStreamP) { } int Ability::get_write_length() { return 0; } void Ability::write(OutStreamP) { } Boolean Ability::is_holder() { return False; } Boolean Ability::is_locomotion() { return False; } Boolean Ability::is_composite() { return False; } Boolean Ability::get_pixmap_mask(Xvars &,int,CMN_IMAGEDATA &,Dir,int) { return False; } Boolean Ability::get_size_offset_next(Size &,Size &,Dir) { return False; } ClassId Ability::get_weapon_string(const char *&) { return A_None; } void Ability::drop_all() { } void Ability::get_followers(PtrList &) { } Boolean Ability::follow(const Area &,Dir,Boolean) { return False; } Boolean Ability::command_repeatable(ITcommand) { return True; } int Ability::get_drawing_level() { // means use default. return -1; } int Ability::get_anim_time() { // means use default. return -1; } Boolean Ability::heat_attack(PhysicalP,int,Boolean) { return False; } Boolean Ability::swap_protect() { return False; } Boolean Ability::frog_protect() { return False; } void Ability::set_intel(IntelP) { } void Ability::heal() { } void Ability::set_mapped_next(Boolean) { } void Ability::act() { } void Ability::update() { } void Ability::die() { } Boolean Ability::collide(PhysicalP) { return False; } Boolean Ability::stick_touching(Touching) { return False; } void Ability::register_abilities() { create_function[AB_Grounded] = Grounded::create_from_stream; create_function[AB_Suicide] = Suicide::create_from_stream; create_function[AB_Hopping] = Hopping::create_from_stream; create_function[AB_User] = User::create_from_stream; create_function[AB_Carrier] = Carrier::create_from_stream; create_function[AB_Fighter] = Fighter::create_from_stream; create_function[AB_Walking] = Walking::create_from_stream; create_function[AB_Sticky] = Sticky::create_from_stream; create_function[AB_Flying] = Flying::create_from_stream; create_function[AB_BuiltIn] = BuiltIn::create_from_stream; create_function[AB_Hugger] = Hugger::create_from_stream; create_function[AB_Prickly] = Prickly::create_from_stream; create_function[AB_Healing] = Healing::create_from_stream; create_function[AB_Lifter] = Lifter::create_from_stream; create_function[AB_Morphed] = Morphed::create_from_stream; create_function[AB_AnimTime] = AnimTime::create_from_stream; create_function[AB_OnFire] = OnFire::create_from_stream; create_function[AB_SwapProtect] = SwapProtect::create_from_stream; create_function[AB_Segmented] = Segmented::create_from_stream; create_function[AB_Sensitive] = Sensitive::create_from_stream; // Make sure we got 'em all. for (int n = 0; n < AB_MAX; n++) { assert(create_function[n]); } } AbilityP Ability::create_from_stream(InStreamP in,AbilityId aId, CreatureP creat) { // Just call the apropriate create function. assert(create_function[aId]); AbilityP ret = create_function[aId](in,creat); assert(ret); return ret; } AbilityCreateFunctionType Ability::create_function[AB_MAX]; Locomotion::Locomotion() : Ability() { } Locomotion::Locomotion(InStreamP in,CreatureP c) : Ability(in,c) { } Boolean Locomotion::is_locomotion() { return True; } Grounded::Grounded(const GroundedContext &,GroundedXdata &) :Locomotion() { } Grounded::Grounded(InStreamP in,CreatureP c, const GroundedContext &, GroundedXdata &) : Locomotion(in,c) { } DEFINE_CREATE_ABILITY_FROM_STREAM(Grounded) GroundedContext Grounded::defaultContext; GroundedXdata Grounded::defaultXdata; AbilityId Grounded::get_ability_id() { return AB_Grounded; } #if 1 #define DO_INCREASE(x) do {if (x < 0) x = 0.0f;} while (0) #define DO_DECREASE(x) do {if (x > 0) x = 0.0f;} while (0) #else #define DO_INCREASE(x) #define DO_DECREASE(x) #endif void Grounded::act() { // Methods of Moving or Creature. Speed acceleration = cre->get_acceleration(); const Stance stance = cre->get_stance(); const Touching touchingArea = cre->get_touching_area(); const Touching touchingStance = cre->get_touching_stance(); Vel vel = cre->get_vel(); const Acc *unitAccs = cre->get_unit_accs(); const ITcommand command = cre->get_command(); if (touchingArea != touchingStance) { // Hit the ground. if (touchingArea == CO_dn) { cre->set_stance_next(CO_center); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } // Hit side walls. else if ((touchingArea == CO_r) || (touchingArea == CO_l)) { Vel velNew(0.0f,vel.dy); cre->set_vel_next(velNew); } // In the air. else if (touchingArea == CO_air) { cre->set_stance_next(CO_air); } } else if (touchingArea == CO_dn) { Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } // Set gravity. cre->set_grav_next((touchingArea == CO_dn) ? 0 : PH_GRAVITY); /* Interpret commands from controlling intelligence. May override some of above settings. */ switch (command) { case IT_CENTER: if (stance == CO_center) { cre->set_vel_next(0); } break; case IT_R: if (stance == CO_center) { DO_INCREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_R]); } break; case IT_L: if (stance == CO_center) { DO_DECREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_L]); } break; } } Suicide::Suicide(const SuicideContext &,SuicideXdata &) :Ability() { } DEFINE_CREATE_ABILITY_FROM_STREAM(Suicide) SuicideContext Suicide::defaultContext; SuicideXdata Suicide::defaultXdata; Suicide::Suicide(InStreamP in,CreatureP c, const SuicideContext &, SuicideXdata &) : Ability(in,c) { } AbilityId Suicide::get_ability_id() { return AB_Suicide; } void Suicide::act() { if (cre->get_command() == IT_ITEM_USE) { cre->kill_self(); } } Hopping::Hopping(const HoppingContext &,HoppingXdata &) :Locomotion() { } DEFINE_CREATE_ABILITY_FROM_STREAM(Hopping) HoppingContext Hopping::defaultContext; HoppingXdata Hopping::defaultXdata; Hopping::Hopping(InStreamP in,CreatureP c, const HoppingContext &, HoppingXdata &) : Locomotion(in,c) { } AbilityId Hopping::get_ability_id() { return AB_Hopping; } void Hopping::act() { // Methods of Moving or Creature. Speed acceleration = cre->get_acceleration(); Speed jump = cre->get_jump(); const Stance stance = cre->get_stance(); const Touching touchingArea = cre->get_touching_area(); const Touching touchingStance = cre->get_touching_stance(); const Vel vel = cre->get_vel(); const Vel *unitVels = cre->get_unit_vels(); const Boolean canClimb = cre->can_climb(); const Area area = cre->get_area(); const ITcommand command = cre->get_command(); Speed halfJump = (int)(jump * 0.5); // Default set gravity. cre->set_grav_next((touchingArea == CO_dn) || (canClimb && (stance == CO_climb) && cre->alive()) ? 0 : PH_GRAVITY); // Change stance if necessary. if (stance != CO_climb) { if (touchingArea != touchingStance) { cre->set_want_climb(False); // Hit the ground. if (touchingArea == CO_dn) { cre->set_stance_next(CO_center); cre->set_vel_next(0); } // Side walls. else if (touchingArea == CO_r) { Vel velNew(0.0f,vel.dy); cre->set_vel_next(velNew); } else if (touchingArea == CO_l) { Vel velNew(0.0f,vel.dy); cre->set_vel_next(velNew); } else if (touchingArea == CO_air) { cre->set_stance_next(CO_air); } else if (touchingArea != CO_up) { assert(0); } } else if (touchingArea == CO_dn) { Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } } else {// stance == CO_climb // Stop climbing if not in climbing square. if (!canClimb) { cre->set_stance_next(CO_air); } else if (! vel.is_zero()) { cre->set_vel_next(0); } } /* Interpret commands from controlling intelligence. May override some of above settings. */ switch (command) { case IT_CENTER: if (stance == CO_air) { cre->set_want_climb(True); } if ((stance != CO_climb) && canClimb) { cre->set_stance_next(CO_climb); cre->set_grav_next(0); cre->set_vel_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } break; case IT_R: if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)jump * unitVels[CO_UP_R_R]); cre->set_stance_next(CO_air); } break; case IT_DN_R: if (stance == CO_climb) { cre->set_vel_next((float)jump * unitVels[CO_DN_R]); cre->set_stance_next(CO_air); } else if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)halfJump * unitVels[CO_UP_R_R]); cre->set_stance_next(CO_air); } break; case IT_DN: if (stance == CO_air) { cre->set_want_climb(True); } if ((stance != CO_climb) && canClimb) { cre->set_stance_next(CO_climb); cre->set_grav_next(0); cre->set_vel_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } else if (stance == CO_climb) { cre->set_vel_next((float)jump * unitVels[CO_climb_DN]); } break; case IT_DN_L: if (stance == CO_climb) { cre->set_vel_next((float)jump * unitVels[CO_DN_L]); cre->set_stance_next(CO_air); } else if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)halfJump * unitVels[CO_UP_L_L]); cre->set_stance_next(CO_air); } break; case IT_L: if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)jump * unitVels[CO_UP_L_L]); cre->set_stance_next(CO_air); } break; case IT_UP_L: if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)jump * unitVels[CO_air_L]); cre->set_stance_next(CO_air); } break; case IT_UP: // Doors are handled by Creature. if (cre->on_door()) break; if (stance == CO_air) { cre->set_want_climb(True); } if ((stance != CO_climb) && canClimb) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } else if (stance == CO_center) {// Jump up. cre->set_vel_next((float)jump * unitVels[CO_air_UP]); cre->set_stance_next(CO_air); } else if (stance == CO_climb) { cre->set_vel_next((float)jump * unitVels[CO_climb_UP]); } break; case IT_UP_R: // Jump right. if ((stance == CO_center) || (stance == CO_climb)) { cre->set_vel_next((float)jump * unitVels[CO_air_R]); cre->set_stance_next(CO_air); } break; }; } Holder::Holder() : Ability() { } Holder::Holder(InStreamP in,CreatureP c) : Ability(in,c) { } Boolean Holder::is_holder() { return True; } Boolean Holder::ok_to_hold(PhysicalP other) { if (!(cre->alive() && other->is_item() && ((ItemP)other)->can_take(cre))) { return False; } // Must be a better way than to put explicit knowledge about Lifter here. LifterP lifter = (LifterP)cre->get_ability(AB_Lifter); if (lifter && lifter->is_lifting()) { return False; } return True; } User::User(const UserContext &cx,UserXdata &) :Holder() { weaponsNum = weaponCurrent = itemsNum = itemCurrent = 0; context = &cx; Timer dummy(USER_CYCLE_TIME); weaponCycleTimer = dummy; itemCycleTimer = dummy; itemCount = 0; } DEFINE_CREATE_ABILITY_FROM_STREAM(User) UserContext User::defaultContext; UserXdata User::defaultXdata; User::User(InStreamP in,CreatureP c, const UserContext &cx, UserXdata &) : Holder(in,c) { weaponsNum = weaponCurrent = itemsNum = itemCurrent = 0; context = &cx; itemCount = 0; update_from_stream(in); } void User::update_from_stream(InStreamP in) { Id id(in); LocatorP locator = cre->get_locator(); PhysicalP p = locator->lookup(id); if (p) { // Set user to have p as the only weapon, and make it the current weapon. if (p->is_weapon()) { weaponsNum = 1; weapons[0] = id; weaponCurrent = 0; return; } cerr << "ERROR: User::update_from_stream(), " << "should have received a weapon." << endl; // Drop through and clear weapons. } // Clear all weapons. weaponsNum = 0; weaponCurrent = 0; } int User::get_write_length() { // The current weapon. return Identifier::get_write_length(); } void User::write(OutStreamP out) { // Write the identifier of the current weapon. if (weaponCurrent != weaponsNum) { weapons[weaponCurrent].write(out); } else { Id invalid; invalid.write(out); } } AbilityId User::get_ability_id() { return AB_User; } int User::get_weapons_num() { return weaponsNum; } int User::get_items_num() { return itemsNum; } PhysicalP User::get_weapon(int n) { assert(n >= 0); if (n < weaponsNum) { LocatorP locator = cre->get_locator(); PhysicalP ret; if (ret = locator->lookup(weapons[n])) { return ret; } } return NULL; } PhysicalP User::get_item(int n) { assert(n >= 0); if (n < itemsNum) { LocatorP locator = cre->get_locator(); PhysicalP ret; if (ret = locator->lookup(items[n])) { return ret; } } return NULL; } PhysicalP User::get_weapon_current() { assert (weaponCurrent <= weaponsNum); if (weaponCurrent != weaponsNum) { LocatorP locator = cre->get_locator(); PhysicalP ret; if (ret = locator->lookup(weapons[weaponCurrent])) { return ret; } } return NULL; } ClassId User::get_weapon_string(const char *&str) { PhysicalP p = get_weapon_current(); if (p) { str = p->get_class_name(); return p->get_class_id(); } return A_None; } PhysicalP User::get_item_current() { assert (itemCurrent <= itemsNum); if (itemCurrent != itemsNum) { PhysicalP ret; LocatorP locator = cre->get_locator(); if (ret = locator->lookup(items[itemCurrent])) { return ret; } } return NULL; } int User::get_item_count() { return itemCount; } void User::get_followers(PtrList &list) { LocatorP locator = cre->get_locator(); int n; // weapons for (n = 0; n < weaponsNum; n++) { PhysicalP p = locator->lookup(weapons[n]); if (p) { list.add(p); } } // items for (n = 0; n < itemsNum; n++) { PhysicalP p = locator->lookup(items[n]); if (p) { list.add(p); } } } Boolean User::command_repeatable(ITcommand command) { // Don't repeat firing a weapon if // 1) it is just becoming empty // 2) it doesn't allow repeated firing. if (Intel::is_command_weapon(command)) { if (weaponCurrent != weaponsNum) { LocatorP locator = cre->get_locator(); WeaponP p; if (p = (WeaponP)locator->lookup(weapons[weaponCurrent])) { int ammo = p->get_ammo(); // Is about to become empty, PH_AMMO_UNLIMITED is not affected by this. if (ammo == 1 && p->ready()) { return False; } if (!p->fire_repeatedly()) { return False; } } } } return True; } void User::set_mapped_next(Boolean val) { if (cre->get_mapped_next() != val) { LocatorP locator = cre->get_locator(); PhysicalP weapon; if ((weaponCurrent != weaponsNum) && (weapon = locator->lookup(weapons[weaponCurrent]))) { if (val) { ((WeaponP)weapon)->enter_scope_next(cre); } else { ((WeaponP)weapon)->leave_scope_next(cre); } } } } // NOTE: This does not check if the Item persists. E.g. Bombs will // be dropped. void User::drop_all() { // Damn, this is easy. while (weaponsNum > 0) { weapon_drop(); } while (itemsNum > 0) { item_drop(); } } void User::act() { assert(weaponCurrent <= weaponsNum); assert(itemCurrent <= itemsNum); // Make sure that don't have any weapons or items if can't use them. assert((context->usesWeapons || weaponsNum == 0) && (context->usesItems || itemsNum == 0)); LocatorP locator = cre->get_locator(); ITcommand command = cre->get_command(); if (Intel::is_command_weapon(command)) { // Fire weapon. weapon_use(command); } else { switch (command) { case IT_WEAPON_CHANGE: weapon_change(); break; case IT_WEAPON_DROP: weapon_drop(); break; case IT_ITEM_USE: item_use(); break; case IT_ITEM_CHANGE: item_change(); break; case IT_ITEM_DROP: item_drop(); break; } } assert(weaponCurrent <= weaponsNum); assert(itemCurrent <= itemsNum); /* Go through list of weapons and items, moving them along with the object or deleting them if they no longer exist or have no ammo. */ const Area &areaNext = cre->get_area_next(); Dir dirNext = cre->get_dir_next(); // Traverse list of weapons. int n; for (n = 0; n < weaponsNum;) { assert(weaponCurrent <= weaponsNum); PhysicalP p; if (p = locator->lookup(weapons[n])) { if (((WeaponP)p)->get_ammo() > 0 || ((WeaponP)p)->get_ammo() == PH_AMMO_UNLIMITED || ((WeaponP)p)->useful_no_ammo()) { // ((WeaponP)p)->follow(areaNext,dirNext,False); } else { p->kill_self(); } n++; } // p is NULL, clean up the weapon. else { weapons[n] = weapons[weaponsNum - 1]; weaponsNum--; if (weaponCurrent == weaponsNum && n != weaponsNum) { weaponCurrent = n; // Keep same current weapon. } else if (weaponCurrent == weaponsNum + 1) { weaponCurrent = weaponsNum; // So still no weapon. } else if (weaponCurrent == n) { weaponCurrent = coolest_weapon(); // Still some weapon active. } PhysicalP weapon; if ((weaponCurrent != weaponsNum) && (weapon = locator->lookup(weapons[weaponCurrent]))) { ((WeaponP)weapon)->enter_scope_next(cre); } } } // Traverse list of items. // Count number of current item that User has. Set itemCountNext. // NOTE: This might not be completely accurate, as itemCurrent can change // during this traversal. But, who cares. It'll be updated next turn. itemCountNext = 0; ClassId itemCurrentClassId = A_None; PhysicalP item; if (itemCurrent != itemsNum && (item = locator->lookup(items[itemCurrent]))) { itemCurrentClassId = item->get_class_id(); } for (n = 0; n < itemsNum;) { assert(itemCurrent <= itemsNum); PhysicalP p; if (p = locator->lookup(items[n])) { // Increment itemCountNext. if (p->get_class_id() == itemCurrentClassId) { itemCountNext++; } // Make item follow user. // ((ItemP)p)->follow(areaNext,dirNext,False); n++; } // p is NULL else { items[n] = items[itemsNum - 1]; itemsNum--; if (itemCurrent == itemsNum && n != itemsNum) { itemCurrent = n; // Keep same current item. } else if (itemCurrent == itemsNum + 1) { itemCurrent = itemsNum; // So still no item. } else if (itemCurrent == n) { itemCurrent = coolest_item(); // Some item or itemsNum. } } } assert(weaponCurrent <= weaponsNum); assert(itemCurrent <= itemsNum); weaponCycleTimer.clock(); itemCycleTimer.clock(); } void User::update() { itemCount = itemCountNext; } Boolean User::collide(PhysicalP other) { if (!ok_to_hold(other)) { return False; } // Used below. int itemClassCount = 0; // Pick up weapon. if (other->is_weapon()) { WeaponP weapon; Boolean destroyOther = False; // If this User can't use weapons. if (!context->usesWeapons) { destroyOther = True; } // If User already has this weapon. else if (has_weapon(&weapon,other->get_class_id())) { // Take ammo from other and kill other. // Cutter heals itself to other's health. weapon->take_ammo_from((WeaponP)other); destroyOther = True; } // If User is already holding the max number of weapons. else if (weaponsNum >= PH_WEAPONS_MAX) { ostrstream msg; msg << "Can only hold " << PH_WEAPONS_MAX << " weapons" << ends; LocatorP locator = cre->get_locator(); locator->arena_message_enq(msg.str(),cre); destroyOther = True; } // Actually pick it up. else { ((ItemP)other)->taken(cre); weapons[weaponsNum] = other->get_id(); // Increment weaponCurrent along with weaponsNum, so if no weapon was // selected, still no weapon selected. if (weaponCurrent == weaponsNum) { weaponCurrent++; } weaponsNum++; if (weaponsNum == 1) { // Used to be 0. // Will set it to new weapon or builtIn weapon. weaponCurrent = coolest_weapon(); // If new weapon is selected. if (weaponCurrent == 0) { ((WeaponP)other)->enter_scope_next(cre); } } // else don't change weaponCurrent. } // We are destroying the weapon for some reason. if (destroyOther) { other->set_quiet_death(); other->kill_self(); } } // is weapon // Pick up item. else if (context->usesItems && itemsNum < PH_ITEMS_MAX && ((itemClassCount = item_class_count(other->get_class_id())) < ITEM_CLASS_MAX)) { assert(!other->is_weapon()); ((ItemP)other)->taken(cre); items[itemsNum] = other->get_id(); itemsNum++; // itemCurrent is 0 if no previous item. } else { LocatorP locator = cre->get_locator(); if (itemsNum == PH_ITEMS_MAX) { ostrstream msg; msg << "Can only hold " << PH_ITEMS_MAX << " items" << ends; locator->arena_message_enq(msg.str(),cre); } else if (itemClassCount >= ITEM_CLASS_MAX) { ostrstream msg; msg << "Can only hold " << ITEM_CLASS_MAX << " of any one item" << ends; locator->arena_message_enq(msg.str(),cre); } other->set_quiet_death(); other->kill_self(); } // Did something with other. return True; } void User::die() { LocatorP locator = cre->get_locator(); // Drop all weapons. int n; for (n = 0; n < weaponsNum; n++) { PhysicalP weapon = locator->lookup(weapons[n]); if (weapon) { if (n == weaponCurrent) { ((WeaponP)weapon)->leave_scope_next(cre); } // FIXED: Don't drop persistent items if doing a quiet death. if (((ItemP)weapon)->persists() && !cre->get_quiet_death()) { ((ItemP)weapon)->dropped(cre); } else if (!weapon->die_called()) { weapon->set_quiet_death(); weapon->kill_self(); weapon->die(); } } } weaponCurrent = 0; weaponsNum = 0; // Drop all items. itemCurrent = 0; for (n = 0; n < itemsNum; n++) { PhysicalP item = locator->lookup(items[n]); if (item) { // FIXED: Don't drop persistent items if doing a quiet death. if (((ItemP)item)->persists() && !cre->get_quiet_death()) { ((ItemP)item)->dropped(cre); } else if (!item->die_called()) { item->set_quiet_death(); item->kill_self(); item->die(); } } } itemsNum = 0; } Boolean User::has_weapon(Weapon **weaponOut,ClassId classId) { for (int n = 0; n < weaponsNum; n++) { PhysicalP weapon; LocatorP locator = cre->get_locator(); // Clean up deleted weapons elsewhere. if ((weapon = locator->lookup(weapons[n])) && (weapon->get_class_id() == classId)) { if (weaponOut) { *weaponOut = (WeaponP)weapon; } return True; } } return False; } int User::coolest_weapon() { LocatorP locator = cre->get_locator(); int index = weaponsNum; int coolnessMax = 0; BuiltInP builtIn = (BuiltInP)cre->get_ability(AB_BuiltIn); if (builtIn) { coolnessMax = builtIn->get_weapon_coolness(); } // Your builtIn weapon must be defaultable. assert(coolnessMax >= 0); int n; for (n = 0; n < weaponsNum; n++) { WeaponP weapon = (WeaponP)locator->lookup(weapons[n]); if (weapon && weapon->get_coolness() > coolnessMax && // Only weapons of nonnegative coolness. weapon->get_coolness() >= 0) { coolnessMax = weapon->get_coolness(); index = n; } } return index; } /* Mostly copied from User::coolest_weapon. */ int User::coolest_item() { LocatorP locator = cre->get_locator(); int index = itemsNum; int coolnessMax = 0; // Your builtIn item must be defaultable. assert(coolnessMax >= 0); int n; for (n = 0; n < itemsNum; n++) { ItemP item = (ItemP)locator->lookup(items[n]); if (item && item->get_coolness() > coolnessMax && // Only items of nonnegative coolness. item->get_coolness() >= 0) { coolnessMax = item->get_coolness(); index = n; } } return index; } int User::next_coolest_weapon() { LocatorP locator = cre->get_locator(); // Find the coolness of having weapon set to "none". BuiltInP builtIn = (BuiltInP)cre->get_ability(AB_BuiltIn); int noneCoolness = 0; if (builtIn) { noneCoolness = builtIn->get_weapon_coolness(); } // Find coolness of current weapon or of builtIn weapon. int currentCoolness = 0; if (weaponCurrent != weaponsNum) { WeaponP weapon = (WeaponP)locator->lookup(weapons[weaponCurrent]); if (weapon) { currentCoolness = weapon->get_coolness(); } else { // Somehow, the current weapon got destroyed, just return coolest // of all weapons. return coolest_weapon(); } } // Current weapon is none. else { currentCoolness = noneCoolness; } // Look for weapon of maximum coolness less than currentCoolness. Boolean found = False; int maxx; int index; int n; for (n = 0; n < weaponsNum; n++) { WeaponP weapon = (WeaponP)locator->lookup(weapons[n]); if (weapon && (!found || weapon->get_coolness() > maxx) && weapon->get_coolness() < currentCoolness) { maxx = weapon->get_coolness(); index = n; found = True; } } // See if setting weapon to none is the best fit. if ((noneCoolness < currentCoolness) && (!found || noneCoolness > maxx)) { maxx = noneCoolness; // unnecessary. index = weaponsNum; found = True; } if (!found) { // Cycle around. return coolest_weapon(); } else { return index; } } /* Mostly copied from User::next_coolest_weapon. */ int User::next_coolest_item() { LocatorP locator = cre->get_locator(); // Find coolness of current item. int currentCoolness; if (itemCurrent != itemsNum) { ItemP item = (ItemP)locator->lookup(items[itemCurrent]); if (item) { currentCoolness = item->get_coolness(); } else { // Somehow, the current item got destroyed, just return coolest // of all items. return coolest_item(); } } else { currentCoolness = 0; } // Look for item of maximum coolness less than currentCoolness. Boolean found = False; int maxx; int index; int n; for (n = 0; n < itemsNum; n++) { ItemP item = (ItemP)locator->lookup(items[n]); if (item && (!found || item->get_coolness() > maxx) && (item->get_coolness() < currentCoolness)) { maxx = item->get_coolness(); index = n; found = True; } } if (!found) { // Cycle around. return coolest_item(); } else { return index; } } void User::weapon_use(ITcommand command) { if (weaponCurrent != weaponsNum) { LocatorP locator = cre->get_locator(); PhysicalP p; if (p = locator->lookup(weapons[weaponCurrent])) { ((WeaponP)p)->fire(cre->get_id(),command); // Inform Creature that an attack occurred. cre->attack_hook(); } } } void User::weapon_change() { LocatorP locator = cre->get_locator(); PhysicalP weapon = NULL; if ((weaponCurrent != weaponsNum) && (weapon = locator->lookup(weapons[weaponCurrent]))) { ((WeaponP)weapon)->leave_scope_next(cre); } int coolest = coolest_weapon(); // If its been awhile since the last weapon change AND // we're not already on the coolest weapon, if (weaponCycleTimer.ready() && weaponCurrent != coolest) { weaponCurrent = coolest; } else { // If we changed weapons recently, cycle to the next coolest weapon. weaponCurrent = next_coolest_weapon(); } // weapon was just changed weaponCycleTimer.set(); if ((weaponCurrent != weaponsNum) && (weapon = locator->lookup(weapons[weaponCurrent]))) { ((WeaponP)weapon)->enter_scope_next(cre); weapon->flash(); } } void User::weapon_drop() { LocatorP locator = cre->get_locator(); if (weaponCurrent != weaponsNum) { // Some weapon is current. // Clean up dead weapons elsewhere. PhysicalP weapon; if (weapon = locator->lookup(weapons[weaponCurrent])) { ((WeaponP)weapon)->leave_scope_next(cre); ((ItemP)weapon)->dropped(cre); weaponsNum--; weapons[weaponCurrent] = weapons[weaponsNum]; // Some weapon or weaponCurrent == weaponsNum. weaponCurrent = coolest_weapon(); PhysicalP weapon; if ((weaponCurrent != weaponsNum) && (weapon = locator->lookup(weapons[weaponCurrent]))) { ((WeaponP)weapon)->enter_scope_next(cre); } } // BUG, can get into an infinite loop here from User::drop_all } else { // No current weapon. /* Cycle to next available weapon, so can keep dropping. */ weapon_change(); } } void User::item_use() { if (itemCurrent != itemsNum) { LocatorP locator = cre->get_locator(); PhysicalP p; if (p = locator->lookup(items[itemCurrent])) { ((ItemP)p)->use(cre); } } } void User::item_change() { int coolest = coolest_item(); // If its been awhile since the last item change AND // we're not already on the coolest item, if (itemCycleTimer.ready() && itemCurrent != coolest) { itemCurrent = coolest; } else { // If we changed items recently, cycle to the next coolest item. itemCurrent = next_coolest_item(); } // item was just changed itemCycleTimer.set(); PhysicalP p; LocatorP locator = cre->get_locator(); if ((itemCurrent != itemsNum) && (p = locator->lookup(items[itemCurrent]))) { p->flash(); } } void User::item_drop() { LocatorP locator = cre->get_locator(); if (itemCurrent != itemsNum) { // Some item is current. // Clean up dead items elsewhere. PhysicalP item; if (item = locator->lookup(items[itemCurrent])) { ((ItemP)item)->dropped(cre); itemsNum--; items[itemCurrent] = items[itemsNum]; // Some item current. itemCurrent = coolest_item(); } } else { // No current item. /* Cycle to next available item, so can keep dropping. */ item_change(); } } int User::item_class_count(ClassId cId) { LocatorP l = cre->get_locator(); int ret = 0; for (int n = 0; n < itemsNum; n++) { PhysicalP p = l->lookup(items[n]); if (p && p->get_class_id() == cId) { ret++; } } return ret; } Carrier::Carrier(const CarrierContext &,CarrierXdata &) :Holder() { hasItem = False; } DEFINE_CREATE_ABILITY_FROM_STREAM(Carrier) CarrierContext Carrier::defaultContext; CarrierXdata Carrier::defaultXdata; Carrier::Carrier(InStreamP in,CreatureP c, const CarrierContext &, CarrierXdata &) : Holder(in,c) { hasItem = False; } AbilityId Carrier::get_ability_id() { return AB_Carrier; } int Carrier::get_weapons_num() { return 0; } int Carrier::get_items_num() { return hasItem ? 1 : 0; } PhysicalP Carrier::get_weapon(int) { return NULL; } PhysicalP Carrier::get_weapon_current() { return NULL; } PhysicalP Carrier::get_item(int n) { assert(n >= 0); if (n == 0 && hasItem) { LocatorP locator = cre->get_locator(); PhysicalP ret = locator->lookup(item); return ret; } return NULL; } PhysicalP Carrier::get_item_current() { // Returns NULL if !has_item(), so does the right thing. return get_item(0); } int Carrier::get_item_count() { return hasItem ? 1 : 0; } void Carrier::get_followers(PtrList &list) { if (hasItem) { LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(item); if (p) { list.add(p); } } } void Carrier::drop_all() { if (hasItem) { LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(item); if (p) { ((ItemP)p)->dropped(cre); } hasItem = False; } } void Carrier::act() { LocatorP locator = cre->get_locator(); ITcommand command = cre->get_command(); PhysicalP p = NULL; if ( hasItem ) { p = locator->lookup(item); if (p) { assert(p->is_item()); } } switch (command) { case IT_ITEM_USE: // Can only use AutoUse items. if (p) { if (p->is_auto_use()) { ((ItemP)p)->use(cre); } else { ostrstream str; str << "Cannot use " << p->get_class_name() << ends; locator->arena_message_enq(str.str(),cre); } } break; case IT_ITEM_CHANGE: if (p) { p->flash(); } break; case IT_ITEM_DROP: if (p) { ((ItemP)p)->dropped(cre); hasItem = False; } } if (p) { #if 0 const Area &areaNext = get_area_next(); Dir dirNext = get_dir_next(); // Make item follow user. ((ItemP)p)->follow(areaNext,dirNext,False); #endif } else { // If item has been destroyed. hasItem = False; } } Boolean Carrier::collide(PhysicalP other) { if (!ok_to_hold(other)) { return False; } if (hasItem) { return False; } // Both weapons and items are put in the item slot. ((ItemP)other)->taken(cre); item = other->get_id(); hasItem = True; return True; } void Carrier::die() { if (hasItem) { LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(item); if (p) { if (((ItemP)p)->persists()) { ((ItemP)p)->dropped(cre); } else if (!p->die_called()) { p->set_quiet_death(); p->kill_self(); p->die(); } } } } Fighter::Fighter(const FighterContext &f_c,FighterXdata &) :Ability() { Timer nTimer(FIGHTER_SLIDE_TIME); stuckTimer = nTimer; fc = &f_c; attack = attackNext = attackNone; } Fighter::Fighter(InStreamP in,CreatureP c, const FighterContext &cx, FighterXdata &) : Ability(in,c) { fc = &cx; update_from_stream(in); } void Fighter::update_from_stream(InStreamP in) { char attk = in->read_char(); attack = (Attack)attk; attackNext = attack; } int Fighter::get_write_length() { return sizeof(char); } void Fighter::write(OutStreamP out) { assert(attack == attackNext); out->write_char((char)attack); } // Right now, this is the only Ability that needs special code, // the rest use DEFINE_CREATE_ABILITY_FROM_STREAM. (also AnimTime) AbilityP Fighter::create_from_stream(InStreamP in,CreatureP creat) { ClassId cId = creat->get_class_id(); AbilityP ret = new Fighter(in,creat,*lookup_context(cId),defaultXdata); assert(ret); return ret; } void Fighter::initialize(CreatureP c) { Ability::initialize(c); // Just so we will assert if we forgot to register the Fighter. lookup_context(cre->get_class_id()); // If you assert here, check your ".bitmaps" file. // MovingContext::offsetGenerator should be set to Fighter::offset_generator // for all Fighters. See "ninja.bitmaps". assert(cre->get_offset_generator() == Fighter::offset_generator); // Similar to above. Should be Fighter::transformOverride for all Fighters. assert(cre->get_transform_override() == &Fighter::transformOverride || cre->get_transform_override() == &Fighter::uprightTransformOverride); } AbilityId Fighter::get_ability_id() { return AB_Fighter; } Boolean Fighter::get_pixmap_mask(Xvars &xvars,int dpyNum,CMN_IMAGEDATA &pixmap, Dir dir,int) { // Alive check is somewhat of a hack. if (cre->alive() && attack && dir_to_attack(dir)) { // Just map the dir to the attackDir used to store the // fighter pixmaps. Dir attackDir = dir_to_attack_dir(dir); cre->get_pixmap_mask(xvars,dpyNum,pixmap,attackDir,0); return True; } return False; } Boolean Fighter::get_size_offset_next(Size &size,Size &offset, Dir dirNext) { // Alive check is somewhat of a hack. if (cre->alive_next() && attackNext && dir_to_attack(dirNext)) { // Just map the dir to the attackDir used to store the // fighter pixmaps. Dir attackDirNext = dir_to_attack_dir(dirNext); cre->get_size_offset_next(size,offset,attackDirNext); return True; } return False; } ClassId Fighter::get_weapon_string(const char *&str) { // If we are a Holder with a current weapon, let Holder take // care of it. HolderP holder = cre->get_holder(); if (holder && holder->get_weapon_current()) { return A_None; } // If a Lifter lifting something, let lifter take care of it. LifterP lifter = (LifterP)cre->get_ability(AB_Lifter); if (lifter && lifter->is_lifting()) { return A_None; } // Set to hand-to-hand str = "hand-to-hand"; return A_HandToHand; } void Fighter::act() { ITcommand command = cre->get_command(); const Dir dir = cre->get_dir(); // No longer attacking, e.g. hit the ground after a flying attack. if (attack && (attack != dir_to_attack(dir))) { attackNext = attackNone; // cre->set_vel_next(0); } // Sliding along the ground to attack. if (attack == attackStuck) { if (stuckTimer.ready()) { attackNext = attackNone; cre->set_vel_next(0); } else { stuckTimer.clock(); } } // If we have a current weapon, don't attack. HolderP holder = cre->get_holder(); if (holder->get_weapon_current()) { return; } // If a Lifter lifting something, don't attack. LifterP lifter = (LifterP)cre->get_ability(AB_Lifter); if (lifter && lifter->is_lifting()) { return; } // Start new attack. if (!attack && Intel::is_command_weapon(command)) { Boolean didAttack = True; // Choose attack depending on stance. switch (cre->get_stance()) { case CO_center: switch (command) { case IT_WEAPON_R: attack_stuck(CO_center_R,CO_center); break; case IT_WEAPON_DN_R: attack_stuck(CO_dn_R,CO_dn); break; case IT_WEAPON_DN_L: attack_stuck(CO_dn_L,CO_dn); break; case IT_WEAPON_L: attack_stuck(CO_center_L,CO_center); break; case IT_WEAPON_UP_L: attack_free_horizontal(CO_UP_L); break; case IT_WEAPON_UP: attack_free_vertical(CO_air_UP); break; case IT_WEAPON_UP_R: attack_free_horizontal(CO_UP_R); break; default: didAttack = False; } break; case CO_air: if (command == IT_WEAPON_DN) { attack_free_vertical(CO_air_DN); } else { didAttack = False; } break; case CO_r: switch (command) { case IT_WEAPON_L: case IT_WEAPON_DN_L: attack_free_horizontal(CO_center_L); break; case IT_WEAPON_UP_L: attack_free_horizontal(CO_UP_L); break; case IT_WEAPON_UP: case IT_WEAPON_UP_R: attack_stuck(CO_r_UP,CO_r); break; case IT_WEAPON_DN: case IT_WEAPON_DN_R: attack_stuck(CO_r_DN,CO_r); break; default: didAttack = False; } break; case CO_dn: switch (command) { case IT_WEAPON_UP_R: attack_free_horizontal(CO_UP_R); break; case IT_WEAPON_UP_L: attack_free_horizontal(CO_UP_L); break; case IT_WEAPON_UP: attack_free_vertical(CO_air_UP); break; case IT_WEAPON_R: case IT_WEAPON_DN_R: attack_stuck(CO_dn_R,CO_dn); break; case IT_WEAPON_L: case IT_WEAPON_DN_L: attack_stuck(CO_dn_L,CO_dn); break; default: didAttack = False; } break; case CO_l: switch (command) { case IT_WEAPON_R: case IT_WEAPON_DN_R: attack_free_horizontal(CO_center_R); break; case IT_WEAPON_UP_R: attack_free_horizontal(CO_UP_R); break; case IT_WEAPON_UP: case IT_WEAPON_UP_L: attack_stuck(CO_l_UP,CO_l); break; case IT_WEAPON_DN: case IT_WEAPON_DN_L: attack_stuck(CO_l_DN,CO_l); break; default: didAttack = False; } break; case CO_up: switch (command) { case IT_WEAPON_DN: attack_free_vertical(CO_air_DN); break; case IT_WEAPON_R: case IT_WEAPON_DN_R: case IT_WEAPON_UP_R: attack_stuck(CO_up_R,CO_up); break; case IT_WEAPON_L: case IT_WEAPON_DN_L: case IT_WEAPON_UP_L: attack_stuck(CO_up_L,CO_up); break; default: didAttack = False; } break; case CO_climb: switch (command) { case IT_WEAPON_R: case IT_WEAPON_DN_R: attack_free_horizontal(CO_center_R); break; case IT_WEAPON_DN: attack_free_vertical(CO_air_DN); break; case IT_WEAPON_L: case IT_WEAPON_DN_L: attack_free_horizontal(CO_center_L); break; case IT_WEAPON_UP_L: attack_free_horizontal(CO_UP_L); break; case IT_WEAPON_UP: attack_free_vertical(CO_air_UP); break; case IT_WEAPON_UP_R: attack_free_horizontal(CO_UP_R); break; default: didAttack = False; } break; default: didAttack = False; break; } // Inform Creature that an attack occurred. if (didAttack) { SoundRequest req(fc->attackSound,cre->get_area()); LocatorP locator=cre->get_locator(); locator->submitSoundRequest(req); cre->attack_hook(); } } // if starting a new attack. // Careful about adding logic here, there is a return up above. } void Fighter::update() { attack = attackNext; } Boolean Fighter::collide(PhysicalP other) { if (attack) { // Special case so Pets with same master don't hurt each other. //// This is unnecessary with the new Teams code. IntelP intel = cre->get_intel(); IntelP oIntel = other->get_intel(); if (intel && oIntel && !intel->is_human() && !oIntel->is_human() && ((MachineP)intel)->get_master_intel_id() == ((MachineP)oIntel)->get_master_intel_id()) { return False; } Dir dir = cre->get_dir(); const Area &area = cre->get_area(); Pos pos; Size size; area.get_rect(pos,size); const Area &otherArea = other->get_area(); // Compute the Attack from the current dir, don't just use current value // of this->attack. Attack attackFromDir = dir_to_attack(dir); if (attackFromDir) { // If there is no hotSpot specified for the current direction, generate // one by // transforming from the hotSpot of the base for the direction. // // This is not as clean as I'd like: // 1) We are directly looking at MovingContext // 2) This scheme will mess up if any creature legitimately has a // hotSpot of (0,0). // // Hot Spots are indexed in FighterContext by the non-attacking dir. Size hotSpot = fc->hotSpots[dir]; if (hotSpot.width == 0 && hotSpot.height == 0) { // Get the base direction for dir. const MovingContext *mc = cre->get_moving_context(); Dir base = Transform2D::get_base(dir,mc->transformOverride); // Convert from Size to Pos so we can use Transform2D's methods. Pos newHotSpot(fc->hotSpots[base].width,fc->hotSpots[base].height); // The base must have a valid hot spot. assert(!(newHotSpot.x == 0 && newHotSpot.y == 0)); // Get the size of the base. Dir attackBase = dir_to_attack_dir(base); const Size &baseSize = mc->sizes[attackBase]; // Transform the hot spot of the base. newHotSpot = Transform2D::apply_all(dir,newHotSpot, baseSize,mc->transformOverride); // Convert back from Pos to Size. hotSpot.width = newHotSpot.x; hotSpot.height = newHotSpot.y; } // If the hotSpot is inside other's area, then do some damage. if (otherArea.overlap(pos + hotSpot)){ // Move just enough to get out of the way. Size offset = area.avoid(otherArea); const Pos &rawPos = cre->get_raw_pos(); cre->set_raw_pos_next(rawPos + offset); int damage = (attackFromDir == attackStuck) ? fc->damageStuck : fc->damageFree; damage = cre->apply_modifiers(Modifier::DAMAGE,damage, FIGHTER_DAMAGE_MAX); other->corporeal_attack(cre,damage); attackNext = attackNone; return True; } } // attackFromDir } return False; } Attack Fighter::dir_to_attack(Dir dir) { switch (dir) { case CO_center_R: case CO_center_L: case CO_r_DN: case CO_r_UP: case CO_dn_R: case CO_dn_L: case CO_l_DN: case CO_l_UP: case CO_up_R: case CO_up_L: return attackStuck; case CO_air_R: case CO_air_DN: case CO_air_L: case CO_air_UP: return attackFree; } return attackNone; } Dir Fighter::dir_to_attack_dir(Dir dir) { switch (dir) { case CO_center_R: return CO_DN_R_R; case CO_center_L: return CO_DN_R; case CO_air_R: return CO_DN_DN_R; case CO_air_L: return CO_DN; case CO_r_DN: return CO_DN_DN_L; case CO_r_UP: return CO_DN_L; case CO_dn_R: return CO_DN_L_L; case CO_dn_L: return CO_L; case CO_l_DN: return CO_UP_L_L; case CO_l_UP: return CO_UP_L; case CO_up_R: return CO_UP_UP_L; case CO_up_L: return CO_UP; case CO_air_UP: return CO_UP_UP_R; case CO_air_DN: return CO_UP_R; default: assert(0); return CO_air; } } Boolean Fighter::is_attack_dir(Dir dir) { return dir >= CO_DN_R_R && dir <= CO_UP_R; } Dir Fighter::attack_dir_to_dir(Dir dir) { switch (dir) { case CO_DN_R_R: return CO_center_R; case CO_DN_R: return CO_center_L; case CO_DN_DN_R: return CO_air_R; case CO_DN: return CO_air_L; case CO_DN_DN_L: return CO_r_DN; case CO_DN_L: return CO_r_UP; case CO_DN_L_L: return CO_dn_R; case CO_L: return CO_dn_L; case CO_UP_L_L: return CO_l_DN; case CO_UP_L: return CO_l_UP; case CO_UP_UP_L: return CO_up_R; case CO_UP: return CO_up_L; case CO_UP_UP_R: return CO_air_UP; case CO_UP_R: return CO_air_DN; default: assert(0); return CO_air; } } void Fighter::attack_stuck(Dir dir,Stance stance) { const Vel *unitVels = cre->get_unit_vels(); cre->set_vel_next((float)fc->slide * unitVels[dir]); cre->set_stance_next(stance); stuckTimer.set(); attackNext = attackStuck; } void Fighter::attack_free_horizontal(Dir dir) { const Acc *unitAccs = cre->get_unit_accs(); cre->set_vel_next(cre->get_vel() + fc->jumpHorizontal * unitAccs[dir]); cre->set_stance_next(CO_air); attackNext = attackFree; } void Fighter::attack_free_vertical(Dir dir) { const Acc *unitAccs = cre->get_unit_accs(); Vel vel = cre->get_vel(); Vel velNext(0.0f,vel.dy + fc->jumpVertical * unitAccs[dir].ddy); cre->set_vel_next(velNext); cre->set_stance_next(CO_air); attackNext = attackFree; } Touching Fighter::offset_generator(Dir dir) { if (is_attack_dir(dir)) { Dir nonAttack = attack_dir_to_dir(dir); return Creature::offset_generator(nonAttack); } return Creature::offset_generator(dir); } const FighterContext *Fighter::lookup_context(ClassId cId) { // Not very extendable. This sucks, Beavis. switch (cId) { case A_Ninja: return &Ninja::fighterContext; case A_Hero: return &Hero::fighterContext; case A_Dog: return &Dog::fighterContext; case A_Mutt: return &Mutt::fighterContext; default: assert(0); } return NULL; } FighterXdata Fighter::defaultXdata; Walking::Walking(const WalkingContext &,WalkingXdata &) :Locomotion() { } DEFINE_CREATE_ABILITY_FROM_STREAM(Walking) WalkingContext Walking::defaultContext; WalkingXdata Walking::defaultXdata; Walking::Walking(InStreamP in,CreatureP c, const WalkingContext &, WalkingXdata &) : Locomotion(in,c) { } AbilityId Walking::get_ability_id() { return AB_Walking; } void Walking::act() { Vel vel = cre->get_vel(); const Pos &rawPos = cre->get_raw_pos(); // Avoid getting stuck. /* if (cre->alive() && velPrev.dy > 0 && rawPosPrev == rawPos) { Vel velNext(0,vel.dy); cre->set_vel_next(velNext); #ifdef PRINT_ERRORS cerr << "Walking::_act() trying to get unstuck." << endl; #endif return; } velPrev = vel; rawPosPrev = rawPos; */ // Methods of Moving or Creature. Speed acceleration = cre->get_acceleration(); #if WIN32 acceleration = (int)(acceleration * 1.5); #endif Speed jump = cre->get_jump(); const Stance stance = cre->get_stance(); const Touching touchingArea = cre->get_touching_area(); const Touching touchingStance = cre->get_touching_stance(); const Hanging &hanging = cre->get_hanging(); const Acc *unitAccs = cre->get_unit_accs(); const Boolean canClimb = cre->can_climb(); const Area area = cre->get_area(); const ITcommand command = cre->get_command(); // Default set gravity. cre->set_grav_next((touchingArea == CO_dn) || (canClimb && (stance == CO_climb) && cre->alive()) ? 0 : PH_GRAVITY); // Change stance if necessary. if (stance != CO_climb) { if (touchingArea != touchingStance) { cre->set_want_climb(False); // Hit the ground. if (touchingArea == CO_dn) { cre->set_stance_next(CO_center); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } // Side walls. else if (touchingArea == CO_r) { // Keep the +-1 in so that when Heros jump against small // walls/bumps he still goes over. Vel velNew(1.0f,vel.dy); cre->set_vel_next(velNew); } else if (touchingArea == CO_l) { Vel velNew(-1.0f,vel.dy); cre->set_vel_next(velNew); } // In the air. else if (touchingArea == CO_air) { cre->set_stance_next(CO_air); } else if (touchingArea != CO_up) { assert(0); } } else if (touchingArea == CO_dn) { if (vel.dy != 0) { Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } } } // Stop climbing if not in climbing square. else if (!canClimb) { // stance == CO_climb cre->set_stance_next(CO_air); } /* Interpret commands from controlling intelligence. May override some of above settings. */ switch (command) { case IT_CENTER: if (stance == CO_air) { cre->set_want_climb(True); } if ((stance != CO_climb) && canClimb && (stance != CO_dn) && (stance != CO_center)) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); cre->set_want_climb(False); } else if (stance != CO_air) { cre->set_vel_next(0); } break; case IT_R: if ((stance == CO_dn) || (stance == CO_center) || // Regular walking. (stance == CO_climb)) { DO_INCREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_R]); } break; case IT_DN_R: if (stance == CO_dn) { DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_R]); } else if (stance == CO_center) { // Crawling. DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_R]); cre->set_stance_next(CO_dn); } else if (stance == CO_climb) { DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_R]); } break; case IT_DN: if (stance == CO_air) { cre->set_want_climb(True); } if ((stance != CO_climb) && canClimb && !cre->standing_squarely()) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); cre->set_want_climb(False); } // slow down. else if (stance == CO_dn) { cre->set_vel_next(0); } else if (stance == CO_center) { cre->set_stance_next(CO_dn); } else if (stance == CO_climb) { DO_INCREASE(vel.dy); if (touchingArea == CO_dn) { cre->set_stance_next(CO_dn); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } else { cre->set_vel_next(vel + acceleration * unitAccs[CO_climb_DN]); } } break; case IT_DN_L: if (stance == CO_dn) { // Crawling. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_L]); } else if (stance == CO_center) { // Crawling. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_L]); cre->set_stance_next(CO_dn); } else if (stance == CO_climb) { DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_L]); } break; case IT_L: if ((stance == CO_dn) || (stance == CO_center) || // Regular walking. (stance == CO_climb)) { DO_DECREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_L]); } break; case IT_UP_L: if ((stance == CO_dn) || (stance == CO_center) || (stance == CO_climb)) { // Jump left. DO_DECREASE(vel.dx); cre->set_vel_next(vel + jump * unitAccs[CO_air_L]); cre->set_stance_next(CO_air); } break; case IT_UP: if (stance == CO_air) { cre->set_want_climb(True); } // Doors are handled by Creature. if (cre->on_door()) break; if ((stance != CO_climb) && canClimb) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); cre->set_want_climb(False); } else if (stance == CO_dn) { cre->set_stance_next(CO_center); } else if (stance == CO_center) { // Jump up. cre->set_vel_next(vel + jump * unitAccs[CO_air_UP]); cre->set_stance_next(CO_air); } else if (stance == CO_climb) { DO_DECREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_climb_UP]); } break; case IT_UP_R: // Jump right. if ((stance == CO_dn) || (stance == CO_center) || (stance == CO_climb)) { DO_INCREASE(vel.dx); cre->set_vel_next(vel + jump * unitAccs[CO_air_R]); cre->set_stance_next(CO_air); } break; } } Sticky::Sticky(const StickyContext &cx,StickyXdata &) : Locomotion() { wantCorner1 = wantCorner2 = CO_air; set_dont_attach(False); context = &cx; } DEFINE_CREATE_ABILITY_FROM_STREAM(Sticky) StickyContext Sticky::defaultContext; StickyXdata Sticky::defaultXdata; Sticky::Sticky(InStreamP in,CreatureP c, const StickyContext &, StickyXdata &) : Locomotion(in,c) { wantCorner1 = wantCorner2 = CO_air; set_dont_attach(False); } void Sticky::initialize(CreatureP c) { Locomotion::initialize(c); assert(has_required_frames(cre->get_moving_context())); } AbilityId Sticky::get_ability_id() { return AB_Sticky; } void Sticky::act() { // Methods of Moving or Creature. Speed acceleration = cre->get_acceleration(); #if WIN32 acceleration = (int)(acceleration * 1.5); #endif Speed jump = cre->get_jump(); const Stance stance = cre->get_stance(); const Touching touchingArea = cre->get_touching_area(); const Touching touchingStance = cre->get_touching_stance(); const Hanging &hanging = cre->get_hanging(); Vel vel = cre->get_vel(); const Acc *unitAccs = cre->get_unit_accs(); const Boolean canClimb = cre->can_climb(); const Area area = cre->get_area(); const ITcommand command = cre->get_command(); // Disable the dontAttach flag if we aren't in the air anymore. if (stance != CO_air && get_dont_attach()) { set_dont_attach(False); } // Have to do this check here because we need the flag set // before checking whether we should attach to walls. if ((command == IT_UP_L || command == IT_UP_R) && stance == CO_air) { set_dont_attach(True); } /* Set gravity. Don't lock on walls if dead. */ cre->set_grav_next( // Dead and not on the ground (!cre->alive() && (touchingArea != CO_dn)) || // Touching wall or ceiling, but dontAttach flag is set. (get_dont_attach() && (touchingArea == CO_r || touchingArea == CO_l || touchingArea == CO_up)) || // In the air and not on a ladder. ((touchingArea == CO_air) && !(canClimb && (stance == CO_climb))) ? PH_GRAVITY : 0); /* Make stance correspond to touching. I.e. attach / detach to wall. Do not attach to wall if climbing. */ if ((stance != CO_climb) || !canClimb) { if ((touchingArea != touchingStance) || ((stance == CO_climb) && !canClimb)) { cre->set_want_climb(False); if (touchingArea == CO_dn) { cre->set_stance_next(CO_center); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } else if (touchingArea == CO_r) { if (get_dont_attach()) { // Keep the +-1 in so that when jump against small // walls/bumps he still goes over. Vel velNew(1.0f,vel.dy); cre->set_vel_next(velNew); } else { // attach cre->set_stance_next(CO_r); cre->set_vel_next(0); // Vel velNew(0.0f,vel.dy); // cre->set_vel_next(velNew); } } else if (touchingArea == CO_l) { if (get_dont_attach()) { Vel velNew(-1.0f,vel.dy); cre->set_vel_next(velNew); } else { // attach cre->set_stance_next(CO_l); cre->set_vel_next(0); // Vel velNew(0.0f,vel.dy); // cre->set_vel_next(velNew); } } else if (touchingArea == CO_up) { if (!get_dont_attach()) { cre->set_stance_next(CO_up); // cre->set_vel_next(0); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } } else if (touchingArea == CO_air) { cre->set_stance_next(CO_air); } else { assert(0); } } else if ((touchingArea == CO_dn) || (touchingArea == CO_up)) { // Maybe we should get on/off ladder here, right now is done // below. if (vel.dy != 0) { Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } } else if ((touchingArea == CO_r) || (touchingArea == CO_l)) { if (vel.dx != 0) { Vel velNew(0.0f,vel.dy); cre->set_vel_next(velNew); } } } // Stop climbing if not in climbing square. // else if (! canClimb) // stance == CO_climb // cre->set_stance_next(CO_air); /* Interpret commands from controlling intelligence. May override some of above settings. */ switch (command) { case IT_CENTER: if (stance == CO_air) { cre->set_want_climb(True); } set_want_corner(CO_air); if ((stance != CO_climb) && canClimb && (stance != CO_dn) && (stance != CO_center)) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } else if (stance != CO_air) { cre->set_vel_next(0); } break; case IT_R: set_want_corner(CO_air); if (stance == CO_r) { // On right wall. cre->set_vel_next(0); set_want_corner(CO_r_UP,CO_r_DN); } else if ((stance == CO_dn) || (stance == CO_center) || // Regular walking. (stance == CO_climb)) { DO_INCREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_R]); } else if (stance == CO_l) { // On left wall. DO_INCREASE(vel.dx); cre->set_vel_next(vel + PH_PUSH_OFF * unitAccs[CO_R]); } else if (stance == CO_up) {// On ceiling. DO_INCREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_up_R]); } break; case IT_DN_R: set_want_corner(CO_air); if (stance == CO_r) {// On right wall. DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); set_want_corner(CO_r_DN); cre->set_vel_next(vel + acceleration * unitAccs[CO_r_DN]); } else if (stance == CO_dn) { DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); set_want_corner(CO_dn_R); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_R]); } else if ((stance == CO_l) || (stance == CO_up)) { DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + PH_PUSH_OFF * unitAccs[CO_DN_R]); } else if (stance == CO_center) { // regular walking DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_R]); cre->set_stance_next(CO_dn); } else if (stance == CO_climb) { DO_INCREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_R]); } break; case IT_DN: if (stance == CO_air) { cre->set_want_climb(True); } set_want_corner(CO_air); if ((stance != CO_climb) && canClimb && !cre->standing_squarely()) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } else if (stance == CO_dn) { cre->set_vel_next(0); set_want_corner(CO_dn_R,CO_dn_L); } else if (stance == CO_center) { // duck cre->set_stance_next(CO_dn); } else if (stance == CO_up) { DO_INCREASE(vel.dy); cre->set_vel_next(vel + PH_PUSH_OFF * unitAccs[CO_DN]); } else if (stance == CO_r) { DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_r_DN]); } else if (stance == CO_l) { DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_l_DN]); } else if (stance == CO_climb) { if (touchingArea == CO_dn) { cre->set_stance_next(CO_dn); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } else { DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_climb_DN]); } } break; case IT_DN_L: set_want_corner(CO_air); if ((stance == CO_r) || (stance == CO_up)) {// On other walls. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + PH_PUSH_OFF * unitAccs[CO_DN_L]); } else if (stance == CO_dn) {// Crawling. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); set_want_corner(CO_dn_L); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_L]); } else if (stance == CO_l) {// On left wall. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); set_want_corner(CO_l_DN); cre->set_vel_next(vel + acceleration * unitAccs[CO_l_DN]); } else if (stance == CO_center) {// Crawling. DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_dn_L]); cre->set_stance_next(CO_dn); } else if (stance == CO_climb) { DO_DECREASE(vel.dx); DO_INCREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_L]); } break; case IT_L: set_want_corner(CO_air); if (stance == CO_l) {// On left wall. set_want_corner(CO_l_DN,CO_l_UP); cre->set_vel_next(0); } else if ((stance == CO_dn) || (stance == CO_center) || // Regular walking. (stance == CO_climb)) { DO_DECREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_center_L]); } else if (stance == CO_r) {// On right wall. DO_DECREASE(vel.dx); cre->set_vel_next(vel + PH_PUSH_OFF * unitAccs[CO_L]); } else if (stance == CO_up) {// On ceiling. DO_DECREASE(vel.dx); cre->set_vel_next(vel + acceleration * unitAccs[CO_up_L]); } break; case IT_UP_L: set_want_corner(CO_air); if ((stance == CO_r) || (stance == CO_dn) || (stance == CO_center) || (stance == CO_climb)) { // Jump left. DO_DECREASE(vel.dx); DO_DECREASE(vel.dy); cre->set_vel_next(vel + jump * unitAccs[CO_air_L]); cre->set_stance_next(CO_air); } else if (stance == CO_l) {// On left wall. DO_DECREASE(vel.dx); DO_DECREASE(vel.dy); set_want_corner(CO_l_UP); cre->set_vel_next(vel + acceleration * unitAccs[CO_l_UP]); } else if (stance == CO_up) {// On ceiling. DO_DECREASE(vel.dx); DO_DECREASE(vel.dy); set_want_corner(CO_up_L); cre->set_vel_next(vel + acceleration * unitAccs[CO_up_L]); } break; case IT_UP: // Doors are handled by Creature. if (cre->on_door()) { break; } if (stance == CO_air) { cre->set_want_climb(True); } set_want_corner(CO_air); if ((stance != CO_climb) && canClimb && stance != CO_up) { cre->set_stance_next(CO_climb); cre->set_vel_next(0); cre->set_grav_next(0); cre->center_wsquare_x_next(area.middle_wsquare()); } else if (stance == CO_up) {// On ceiling. set_want_corner(CO_up_R,CO_up_L); cre->set_vel_next(0); } else if (stance == CO_dn) { cre->set_stance_next(CO_center); } else if (stance == CO_center) {// Jump up. DO_DECREASE(vel.dy); cre->set_vel_next(vel + jump * unitAccs[CO_air_UP]); cre->set_stance_next(CO_air); } else if (stance == CO_r) {// On right wall. DO_DECREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_r_UP]); } else if (stance == CO_l) {// On left wall. DO_DECREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_l_UP]); } else if (stance == CO_climb) { if (touchingArea == CO_up) { cre->set_stance_next(CO_up); Vel velNew(vel.dx,0.0f); cre->set_vel_next(velNew); } else { DO_DECREASE(vel.dy); cre->set_vel_next(vel + acceleration * unitAccs[CO_climb_UP]); } } break; case IT_UP_R: set_want_corner(CO_air); if (stance == CO_r) {// On right wall. DO_INCREASE(vel.dx); DO_DECREASE(vel.dy); set_want_corner(CO_r_UP); cre->set_vel_next(vel + acceleration * unitAccs[CO_r_UP]); } else if ((stance == CO_dn) || (stance == CO_l) || (stance == CO_center) || (stance == CO_climb)) { /* Jump right. */ DO_INCREASE(vel.dx); DO_DECREASE(vel.dy); cre->set_vel_next(vel + jump * unitAccs[CO_air_R]); cre->set_stance_next(CO_air); } else if (stance == CO_up) // On ceiling. { DO_INCREASE(vel.dx); DO_DECREASE(vel.dy); set_want_corner(CO_up_R); cre->set_vel_next(vel + acceleration * unitAccs[CO_up_R]); } break; } // Go around outside corners. if (want_corner(hanging.corner)) { cre->set_stance_next(cornered_stance(hanging)); // Transfer verto to horiz velocity if going from vertical to horiz stance. if (hanging.corner == CO_r_DN ||hanging.corner == CO_l_UP) { // Positive Y becomes positive X. Vel velNew(vel.dy,0.0f); cre->set_vel_next(velNew); } else if ( hanging.corner == CO_l_DN || hanging.corner == CO_r_UP) { // Positive Y becomes negative X. Vel velNew(-vel.dy,0.0f); cre->set_vel_next(velNew); } else { cre->set_vel_next(0); } cre->corner(hanging); set_want_corner(CO_air); } } Boolean Sticky::has_required_frames(const MovingContext *mc) { assert(mc); // Needs fucking everything. for (Dir dir = CO_center_R; dir < CO_R; dir++) { if (mc->animMax[dir] < 1) { return False; } } return True; } Boolean Sticky::want_corner(const Corner &corner) { return (corner != CO_air) && ((corner == wantCorner1) || (corner == wantCorner2)); } Boolean Sticky::stick_touching(Touching touching) { switch (touching) { case CO_r: // case CO_dn: Already granted by Creature. case CO_l: case CO_up: return True; default: return False; } } Stance Sticky::cornered_stance(const Hanging &hanging) { switch (hanging.corner) { case CO_r_DN: return CO_up; case CO_r_UP: return CO_dn; case CO_dn_R: return CO_l; case CO_dn_L: return CO_r; case CO_l_DN: return CO_up; case CO_l_UP: return CO_dn; case CO_up_R: return CO_l; case CO_up_L: return CO_r; default: assert(0); return CO_air; } } Flying::Flying(const FlyingContext &cx,FlyingXdata &) :Locomotion() { context = &cx; Timer nTimer(cx.gravTime); gravTimer = nTimer; } DEFINE_CREATE_ABILITY_FROM_STREAM(Flying) FlyingContext Flying::defaultContext; FlyingXdata Flying::defaultXdata; Flying::Flying(InStreamP in,CreatureP c, const FlyingContext &cx, FlyingXdata &) : Locomotion(in,c) { context = &cx; } AbilityId Flying::get_ability_id() { return AB_Flying; } void Flying::act() { // Methods of Moving or Creature. Speed acceleration = cre->get_acceleration(); #if WIN32 acceleration = (int)(acceleration * 1.5); #endif Speed jump = cre->get_jump(); const Stance stance = cre->get_stance(); const Touching touchingArea = cre->get_touching_area(); const Touching touchingStance = cre->get_touching_stance(); const Vel vel = cre->get_vel(); const Acc *unitAccs = cre->get_unit_accs(); const ITcommand command = cre->get_command(); // Used to be an assertion. But, now with dynamic abilities, is possible // for a creature to be in some other stance when given the Flying ability. // Drawing code should be ok. If the Creature used to have a Locomotion // that put him in the current stance, then it must have the frames to // draw in the stance. if (stance != CO_air && stance != CO_center) { cre->set_stance_next(CO_air); } /* Deal with gravity here instead of in Creature because falling less than one pixel per clock. */ cre->set_grav_next(0); // Don't use Creature's notion of gravity. // Limits on velNext imposed by Creature. Vel velNext = vel; if (touchingArea != CO_dn && gravTimer.ready() && vel.dy <= FLYING_GRAV_VEL_CUTOFF) { velNext.dy += 1; gravTimer.set(); } if (touchingArea != touchingStance) { if (touchingArea == CO_dn) { cre->set_stance_next(CO_center); velNext.dy = 0.0f; } else if (touchingArea == CO_air) { cre->set_stance_next(CO_air); } } /* Interpret commands from controlling intelligence. May override some of above settings. */ switch (command) { case IT_CENTER: velNext.set_zero(); break; case IT_R: DO_INCREASE(velNext.dx); velNext = velNext + acceleration * unitAccs[CO_R]; break; case IT_DN_R: DO_INCREASE(velNext.dx); DO_INCREASE(velNext.dy); if (stance == CO_air) { velNext = velNext + acceleration * unitAccs[CO_DN_R]; } else { velNext = velNext + acceleration * unitAccs[CO_center_R]; } break; case IT_DN: if (stance == CO_air) { DO_INCREASE(velNext.dy); velNext = velNext + acceleration * unitAccs[CO_DN]; } else { velNext.set_zero(); } break; case IT_DN_L: DO_DECREASE(velNext.dx); DO_INCREASE(velNext.dy); if (stance == CO_air) { velNext = velNext + acceleration * unitAccs[CO_DN_L]; } else { velNext = velNext + acceleration * unitAccs[CO_center_L]; } break; case IT_L: DO_DECREASE(velNext.dx); velNext = velNext + acceleration * unitAccs[CO_L]; break; case IT_UP_L: if (stance == CO_air) { DO_DECREASE(velNext.dx); DO_DECREASE(velNext.dy); velNext = velNext + acceleration * unitAccs[CO_air_L]; } else { DO_DECREASE(velNext.dx); velNext = velNext + jump * unitAccs[CO_air_L]; cre->set_stance_next(CO_air); } break; case IT_UP: // Doors are handled by Creature. if (cre->on_door()) break; if (stance == CO_air) { DO_DECREASE(velNext.dy); velNext = velNext + acceleration * unitAccs[CO_air_UP]; } else { velNext = velNext + jump * unitAccs[CO_air_UP]; cre->set_stance_next(CO_air); } break; case IT_UP_R: if (stance == CO_air) { DO_INCREASE(velNext.dx); DO_DECREASE(velNext.dy); velNext = velNext + acceleration * unitAccs[CO_air_R]; } else { DO_INCREASE(velNext.dx); velNext = velNext + jump * unitAccs[CO_air_R]; cre->set_stance_next(CO_air); } break; }; cre->set_vel_next(velNext); gravTimer.clock(); } BuiltIn::BuiltIn(const BuiltInContext &cx,BuiltInXdata &) :Ability() { // AltarOfSin requires that BuiltInXdata not be used, passes in dummy. context = &cx; Timer nTimer(cx.shotTime); shotTimer = nTimer; } DEFINE_CREATE_ABILITY_FROM_STREAM(BuiltIn) BuiltInContext BuiltIn::defaultContext; BuiltInXdata BuiltIn::defaultXdata; BuiltIn::BuiltIn(InStreamP in,CreatureP c, const BuiltInContext &cx, BuiltInXdata &) : Ability(in,c) { context = &cx; } AbilityId BuiltIn::get_ability_id() { return AB_BuiltIn; } ClassId BuiltIn::get_weapon_string(const char *&str) { // If we are a Holder with a current weapon, let Holder take // care of it. HolderP holder = cre->get_holder(); if (holder && holder->get_weapon_current()) { return A_None; } // Set to the BuiltIn weapon. str = context->weaponStr; return context->weaponClassId; } Boolean BuiltIn::ready() { return shotTimer.ready(); } void BuiltIn::act() { Dir shotDir; // If the context specifies a particular compute_weapon_dir function, // use it. Else, default. if (context->compute_weapon_dir) { shotDir = context->compute_weapon_dir(cre->get_command()); } else { shotDir = Intel::command_weapon_to_dir_8(cre->get_command()); } assert(context->create_shot && context->get_shot_size); HolderP holder = cre->get_holder(); if (shotTimer.ready() && shotDir != CO_air && // Don't fire if Creature is a holder with a current weapon. !(holder && holder->get_weapon_current())) { const Area &area = cre->get_area(); LocatorP locator = cre->get_locator(); Pos shotPos = Coord::shot_initial_pos(area,cre->get_touching_area(), context->get_shot_size(shotDir),shotDir); PhysicalP shot = context->create_shot(cre->get_world(),locator,shotPos, cre->get_id(),shotDir); shot->set_dont_collide(cre->get_id()); // Shot should not collide with any members of the shooter's composite object. CompositeP comp = cre->get_composite(); if (comp) { shot->set_dont_collide_composite(comp->get_composite_id()); } locator->add(shot); shotTimer.set(); // Make sound. if (context->attackSound) { SoundRequest req(context->attackSound,cre->get_area()); locator->submitSoundRequest(req); } // Signal that we attacked something. cre->attack_hook(); } shotTimer.clock(); } Hugger::Hugger(const HuggerContext &cx,HuggerXdata &) :Ability() { context = &cx; } DEFINE_CREATE_ABILITY_FROM_STREAM(Hugger) HuggerContext Hugger::defaultContext; HuggerXdata Hugger::defaultXdata; Hugger::Hugger(InStreamP in,CreatureP c, const HuggerContext &cx, HuggerXdata &) : Ability(in,c) { context = &cx; } AbilityId Hugger::get_ability_id() { return AB_Hugger; } int Hugger::get_drawing_level() { return 2; } void Hugger::act() { LocatorP locator = cre->get_locator(); PhysicalP huggee = locator->lookup(huggeeId); if (cre->alive() && huggee) { // Someone else killed the huggee while being hugged. if (!huggee->alive()) { Id invalid; huggeeId = invalid; } // Create new alien. else if (!cre->stunned() && !cre->stunned_next()) { const Area &area = huggee->get_area(); Pos middle = area.get_middle(); Size alienSize = Alien::get_size_max(); Pos alienPos(middle.x - alienSize.width / 2, middle.y - alienSize.height / 2); PhysicalP alien = new Alien(cre->get_world(),locator,alienPos); assert(alien); locator->add(alien); // Set the intelligence of the new Alien. if (context->useHuggeeIntel) { // Use intelligence of huggee. alien->set_intel(huggee->get_intel()); huggee->set_intel(NULL); // No kill is awarded for this death. } else { // Do one point of damage to huggee, so hugger will get credit for the // kill. Do this before swapping the intels. huggee->corporeal_attack(cre,1); // Use own intelligence. Huggee's intel dies with the huggee's // physical body. alien->set_intel(cre->get_intel()); cre->set_intel(NULL); } // kill huggee huggee->kill_self(); // kill hugger cre->kill_self(); } // huggee is alive() but hugger is stunned. else { // Stay locked onto hugee's face. const Area &area = huggee->get_area(); Pos p; Size s; area.get_rect(p,s); // Guess where hugee's face is, upper-left + (width/2,height*.3). Pos pNext(p.x + (int) (0.5 * s.width), p.y + (int) (0.3 * s.height)); cre->_set_middle_next(pNext); // Don't need to call update_area_next(), because Moving::act() is // yet to happen. } } // if (alive() && huggee) } Boolean Hugger::collide(PhysicalP other) { LocatorP l = cre->get_locator(); if ( // Dead Huggers don't hug. cre->alive() && // Make sure not already hugging something. !l->lookup(huggeeId) && // Only hug living biological creatures. other->alive() && other->is_creature() && ((CreatureP)other)->biological() && ///// Don't need the following two checks now that we have Teams. // Don't hug aliens or other huggers. other->get_class_id() != A_Alien && !((CreatureP)other)->get_ability(AB_Hugger) && // Don't hug alien immune creatures. !((CreatureP)other)->get_alien_immune() ) { // Kind of a hack, stun self as well as huggee. cre->stun_next(HUGGER_TIME); // Stun other for one turn longer so that we have one turn to // turn other into an alien. ((CreatureP)other)->stun_next(HUGGER_TIME + 1); // Don't want other to get killed or hugged when already being hugged. // Need two extra turns, one to turn other into Alien, and one for // synchronization as alienImmune is immediate and stun is not. ((CreatureP)other)->set_alien_immune(HUGGER_TIME + 2); huggeeId = other->get_id(); return True; } return False; } Prickly::Prickly(const PricklyContext &cx,PricklyXdata &) :Ability() { context = &cx; } DEFINE_CREATE_ABILITY_FROM_STREAM(Prickly) PricklyContext Prickly::defaultContext; PricklyXdata Prickly::defaultXdata; Prickly::Prickly(InStreamP in,CreatureP c, const PricklyContext &cx, PricklyXdata &) : Ability(in,c) { context = &cx; } AbilityId Prickly::get_ability_id() { return AB_Prickly; } ClassId Prickly::get_weapon_string(const char *&str) { // Code copied from Fighter::get_weapon_string(). // Sucks to have Abilities having to depend on peer Abilities. // If we are a Holder with a current weapon, let Holder take // care of it. HolderP holder = cre->get_holder(); if (holder && holder->get_weapon_current()) { return A_None; } // If a Lifter lifting something, let lifter take care of it. LifterP lifter = (LifterP)cre->get_ability(AB_Lifter); if (lifter && lifter->is_lifting()) { return A_None; } // Set to hand-to-hand str = "prickly"; return A_Prickly; } Boolean Prickly::collide(PhysicalP other) { if ( // Dead don't hurt. cre->alive() && // Use an AutoUse, don't destroy it. Would be better to put this check // somewhere else. !other->is_auto_use() && other->get_class_id() != A_AltarOfSin) { int damage = cre->apply_modifiers(Modifier::DAMAGE,context->damage); other->corporeal_attack(cre,damage); cre->attack_hook(); return True; } return False; } Healing::Healing(const HealingContext &cx,HealingXdata &) :Ability() { // don't use Xdata, AltarOfSin passes in dummy value. context = &cx; Timer t(context->time); healTimer = t; healTimer.set(); // dont set healthMin until Ability::cre is valid, in initialize(). } DEFINE_CREATE_ABILITY_FROM_STREAM(Healing) HealingContext Healing::defaultContext; HealingXdata Healing::defaultXdata; Healing::Healing(InStreamP in,CreatureP c, const HealingContext &cx, HealingXdata &) : Ability(in,c) { context = &cx; healthMin = cre->get_health(); } void Healing::initialize(CreatureP c) { Ability::initialize(c); // Must be after Ability::initialize(). healthMin = cre->get_health(); } AbilityId Healing::get_ability_id() { return AB_Healing; } void Healing::heal() { healthMin = cre->get_health_max(); } void Healing::act() { // alive_next() checks if something else has already killed this. if (cre->alive() && cre->alive_next()) { Health health = cre->get_health(); // BUG FIX: If we were just healed via Healing::heal(), don't heal // incrementallythis turn. Wait until next turn when // health has the new value. Otherwise we set healthMin incorrectly. // Would probably be a cleaner fix to have a "healthMinNext" like Moving does. if (cre->get_health_next() <= health) { if (health < healthMin) { healthMin = health; } // Only heal every few turns. if (healTimer.ready()) { // Heal up to multiplier * min health in units of unit. Health healthBest = Utils::minimum(cre->get_health_max(),((int)(healthMin * context->multiplier))); Health healthNext = health + context->unit; if (healthNext > healthBest) { healthNext = healthBest; } cre->set_health_next(healthNext); healTimer.set(); } } healTimer.clock(); } } LifterSpeedModifier::LifterSpeedModifier() : Modifier() { lifting = False; } ModifierId LifterSpeedModifier::get_modifier_id() { return M_LifterSpeed; } int LifterSpeedModifier::apply(Attribute attr,int val) { // Cut speed in half if lifting something. if (lifting) { switch (attr) { // case ACCELERATION: case AIR_SPEED: case CENTER_SPEED: case CLIMB_SPEED: case CRAWL_SPEED: // Using *= gives warnings on some compilers. val = (int)(val * 0.75); break; } } if (next) { return next->apply(attr,val); } else { return val; } } Lifter::Lifter(const LifterContext &cx,LifterXdata &) :Ability() { context = &cx; } DEFINE_CREATE_ABILITY_FROM_STREAM(Lifter) LifterContext Lifter::defaultContext; LifterXdata Lifter::defaultXdata; Lifter::Lifter(InStreamP in,CreatureP c, const LifterContext &cx, LifterXdata &) : Ability(in,c) { context = &cx; // Don't bother adding LifterSpeedModifier } void Lifter::initialize(CreatureP c) { // Call first, to set cre. Ability::initialize(c); ModifierP modifiers = cre->get_modifiers(); assert(modifiers); speedModifier = new LifterSpeedModifier(); assert(speedModifier); modifiers->append_unique(speedModifier); } AbilityId Lifter::get_ability_id() { return AB_Lifter; } ClassId Lifter::get_weapon_string(const char *&str) { // Don't have to worry about Holder since Lifter will only // lift if Holder has no items. LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(liftable); if (p) { if (context->throwSpeed) { str = "throw"; } else { str = "drop"; } return A_DropLiftable; } return A_None; } void Lifter::act() { LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(liftable); if (p) { assert(p->is_liftable()); // Make the Liftable follow the Lifter Area area = cre->get_area(); Pos pos; Size size; area.get_rect(pos,size); Pos middle = area.get_middle(); Area otherArea = p->get_area(); Size otherSize = otherArea.get_size(); Stance stance = cre->get_stance(); // Liftable drags on the floor or is carried. if (stance == CO_center || stance == CO_dn || // If can carry, always do so. context->throwSpeed) { // Start with position on floor. int yMiddle = pos.y + size.height - otherSize.height + (otherSize.height >> 1); int xMiddle = middle.x; // Carry head height if (context->throwSpeed) { // Center liftable about at head-height, unless that is lower // down than the floor y value. int headYMiddle = pos.y + (int)(0.3 * size.height); yMiddle = Utils::minimum(yMiddle,headYMiddle); // Don't lag when carrying. Vel vel = cre->get_vel(); xMiddle = (int)(middle.x + vel.dx); } // else drag on floor Pos middleNext(xMiddle,yMiddle); // Can cast to moving because it is a Liftable. ((MovingP)p)->set_middle_next(middleNext); } // Liftable hangs beneath the Lifter. else { Pos middleNext(middle.x, pos.y + size.height + (otherSize.height >> 1)); // Can cast to moving because it is a Liftable. ((MovingP)p)->set_middle_next(middleNext); } // Put all this shit in follow(). // We picked up a weapon, so drop the Liftable. HolderP holder = cre->get_holder(); if (holder && holder->get_weapons_num() > 0) { drop_liftable(p); return; } // Drop/throw the Liftable if any type of WEAPON command is issued. ITcommand command = cre->get_command(); if (command == IT_WEAPON_CHANGE || command == IT_WEAPON_DROP || command == IT_WEAPON_CENTER) { drop_liftable(p); return; } // Throw if we can. else if (Intel::is_command_weapon(command)) { if (context->throwSpeed) { Dir dir = Intel::command_weapon_to_dir_8(command); // Throw. throw_liftable(p,dir); } else { // Can only drop. drop_liftable(p); } return; } } // Tell speedModifier whether or not to slow us down. ((LifterSpeedModifier *)speedModifier)->set_lifting(p != NULL); } Boolean Lifter::collide(PhysicalP other) { // Don't do anything if already lifting. LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(liftable); if (p) { // Disable collisions with weapons while we are lifting something. if (other->is_weapon()) { return True; } else { // Normal collisions. return False; } } // Can only lift Liftables. if (!other->is_liftable()) { return False; } // Already being lifted by someone else. if (((LiftableP)other)->being_lifted()) { return False; } // Not allowed to pick anything up if we have weapons. HolderP holder = cre->get_holder(); if (holder && holder->get_weapons_num() > 0) { return False; } // Similar to the way Heavy works, but more restrictive. const Area &area = cre->get_area(); const Area &otherArea = other->get_area(); const Pos &otherMiddle = otherArea.get_middle(); Dir dirTo = area.dir_to(otherMiddle); // Make it easier to pick up stuff, wider range of acceptable dirs. if ((dirTo == CO_DN || dirTo == CO_DN_R || dirTo == CO_DN_L) || // If we can throw, we can pick up from any direction. context->throwSpeed > 0) { liftable = other->get_id(); ((LiftableP)other)->lift(cre); // Don't need to make the Liftable follow here, because _act() will take // care of it. collide() happens before act(). } return True; } Boolean Lifter::is_lifting() { LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(liftable); return (p != NULL); } void Lifter::drop_liftable(PhysicalP p) { if (p) { ((LiftableP)p)->release(cre); liftable.invalidate(); // Dropping is considered an attack. cre->attack_hook(); } } void Lifter::throw_liftable(PhysicalP p,Dir dir) { if (p && p->is_moving()) { ((LiftableP)p)->release(cre); liftable.invalidate(); const Vel *unitVels = cre->get_unit_vels(); ((MovingP)p)->set_extra_vel_next((float)context->throwSpeed * unitVels[dir]); // Throwing is considered an attack. cre->attack_hook(); } } Morphed::Morphed(const MorphedContext &cx,MorphedXdata &, PhysicalP p) :Ability() { if (p) { assert(p->get_intel() == NULL); unmapped = p->get_id(); timer.set(cx.revertTime); unmappedSet = True; } else { unmappedSet = False; } } DEFINE_CREATE_ABILITY_FROM_STREAM(Morphed) MorphedContext Morphed::defaultContext; MorphedXdata Morphed::defaultXdata; Morphed::Morphed(InStreamP in,CreatureP c, const MorphedContext &,MorphedXdata &) : Ability(in,c) { unmappedSet = False; } AbilityId Morphed::get_ability_id() { return AB_Morphed; } void Morphed::get_followers(PtrList &list) { // Included the unmapped object, e.g. so you don't get stuck as a frog for // the new level. if (unmappedSet) { LocatorP locator = cre->get_locator(); PhysicalP p = locator->lookup(unmapped); if (p) { list.add(p); } } } void Morphed::drop_all() { // Drop everything the unmapped object is carrying. if (unmappedSet) { LocatorP locator = cre->get_locator(); PhysicalP p = locator->lookup(unmapped); if (p) { p->drop_all(); } } } void Morphed::act() { // alive() may not be necessary. if (cre->alive() && unmappedSet) { LocatorP locator = cre->get_locator(); PhysicalP p = locator->lookup(unmapped); if (p && p->alive()) { #if 0 // Should be taken care of by the follower logic. assert(p->is_moving()); const Area &area = cre->get_area(); ((MovingP)p)->set_middle_next(area.get_middle()); #endif // Morph cre back into its original form. if (timer.ready()) { p->set_intel(cre->get_intel()); p->set_mapped_next(True); cre->set_intel(NULL); cre->set_quiet_death(); cre->kill_self(); unmappedSet = False; } } } if (unmappedSet) { timer.clock(); } } void Morphed::die() { if (unmappedSet) { LocatorP locator = cre->get_locator(); PhysicalP p; if ((p = locator->lookup(unmapped)) && p->alive()) { assert(p->get_intel() == NULL); p->set_intel(cre->get_intel()); cre->set_intel(NULL); // propagate the quiet death flag, or map so that the corpse is visible. if (cre->get_quiet_death()) { p->set_quiet_death(); } else { p->set_mapped_next(True); // Is this necessary with the follower logic? const Area &area = cre->get_area(); assert(p->is_moving()); ((MovingP)p)->set_middle_next(area.get_middle()); } // Kill off the unmapped object if (!p->die_called()) { /* Partial hack to prevent corpse from getting knocked out of the world. */ const Area &area = p->get_area_next(); WorldP world = cre->get_world(); if (!world->inside(area.middle_wsquare())) { p->set_quiet_death(); } p->kill_self(); p->die(); } // Want the corpse of the unmapped, not the Morphed Creature. cre->set_quiet_death(); } } } AnimTime::AnimTime(const AnimTimeContext &cx,AnimTimeXdata &) :Ability() { context = &cx; assert(context->animTime > 0); } AbilityP AnimTime::create_from_stream(InStreamP in,CreatureP creat) { ClassId cId = creat->get_class_id(); AbilityP ret = new AnimTime(in,creat,*lookup_context(cId),defaultXdata); assert(ret); return ret; } AnimTime::AnimTime(InStreamP in,CreatureP c, const AnimTimeContext &cx,AnimTimeXdata &) : Ability(in,c) { context = &cx; } void AnimTime::initialize(CreatureP c) { Ability::initialize(c); // Just so will assert if a class wasn't registered. lookup_context(cre->get_class_id()); } AbilityId AnimTime::get_ability_id() { return AB_AnimTime; } int AnimTime::get_anim_time() { return context->animTime; } const AnimTimeContext *AnimTime::lookup_context(ClassId cId) { switch (cId) { case A_Seal: return &Seal::animTimeContext; case A_Walker: return &Walker::animTimeContext; case A_Yeti: return &Yeti::animTimeContext; default: assert(0); } return NULL; } AnimTimeXdata AnimTime::defaultXdata; OnFire::OnFire(const OnFireContext &cx,OnFireXdata &) :Ability() { context = &cx; // don't use OnFireXdata, AltarOfSin passes in dummy value. } DEFINE_CREATE_ABILITY_FROM_STREAM(OnFire) OnFireContext OnFire::defaultContext; OnFireXdata OnFire::defaultXdata; OnFire::OnFire(InStreamP in,CreatureP c, const OnFireContext &cx,OnFireXdata &) :Ability(in,c) { context = &cx; } AbilityId OnFire::get_ability_id() { return AB_OnFire; } Boolean OnFire::heat_attack(PhysicalP,int,Boolean) { // We took care of it, do nothing. return True; } void OnFire::act() { // For composite objects, only the leader is OnFire. // May need something more general later. CompositeP comp = cre->get_composite(); if (comp) { if (!comp->is_leader()) { return; } } // Dead object only burn if context->deadBurns is set. if (!(cre->alive() || context->deadBurns)) { return; } const Area &area = cre->get_area(); Pos pos; Size size; area.get_rect(pos,size); LocatorP locator = cre->get_locator(); // Generate fire. for (int n = 0; n < context->fires; n++) { Pos firePos(pos.x + Utils::choose(size.width), pos.y + Utils::choose((int)(.8 * size.height))); PhysicalP fire = new Fire(cre->get_world(),locator,firePos,False); locator->add(fire); } } Boolean OnFire::collide(PhysicalP other) { // Don't burn things that we want to pick up. HolderP holder = cre->get_holder(); if (holder && holder->ok_to_hold(other)) { return False; } if (cre->alive() && !other->is_shot()) { other->heat_attack(cre,context->heat); cre->attack_hook(); return True; } return False; } SwapProtect::SwapProtect(const SwapProtectContext &cx,SwapProtectXdata &) :Ability() { swapResistance = cx.swapResistance; } DEFINE_CREATE_ABILITY_FROM_STREAM(SwapProtect) SwapProtectContext SwapProtect::defaultContext; SwapProtectXdata SwapProtect::defaultXdata; SwapProtect::SwapProtect(InStreamP in,CreatureP c, const SwapProtectContext &,SwapProtectXdata &) :Ability(in,c) { swapResistance = 0; } AbilityId SwapProtect::get_ability_id() { return AB_SwapProtect; } Boolean SwapProtect::swap_protect() { if (swapResistance > 0) { swapResistance--; return True; } return False; } Boolean SwapProtect::frog_protect() { return True; } Composite::Composite() : Ability() { } Composite::Composite(InStreamP in,CreatureP cre) : Ability(in,cre) { } Boolean Composite::is_composite() { return True; } PhysicalP Composite::get_draw_before_me() { return NULL; } CompositeId Composite::idGenerator = 0; Segmented::Segmented(const SegmentedContext &cx,SegmentedXdata &) :Composite() { context = &cx; isLeader = False; leaderAnimNum = 0; leaderAnimTimer.set_max(LEADER_ANIM_TIME); followDistance = context->followDistance; } DEFINE_CREATE_ABILITY_FROM_STREAM(Segmented) Segmented::Segmented(InStreamP in,CreatureP c, const SegmentedContext &cx,SegmentedXdata &) :Composite(in,c) { context = &cx; isLeader = False; update_from_stream(in); // don't bother with leaderAnimTimer // followDistance will come from update_from_stream(). } void Segmented::update_from_stream(InStreamP in) { isLeader = (Boolean)in->read_char(); next.read(in); leaderAnimNum = (int)in->read_char(); followDistance = (int)in->read_short(); } int Segmented::get_write_length() { return sizeof(char) + // isLeader Identifier::get_write_length() + // next, needed for get_draw_before_me() sizeof(char) + // leaderAnimNum sizeof(u_short); // followDistance } void Segmented::write(OutStreamP out) { out->write_char(isLeader); next.write(out); out->write_char((u_char)leaderAnimNum); assert(followDistance < USHRT_MAX); out->write_short((u_short)followDistance); } AbilityId Segmented::get_ability_id() { return AB_Segmented; } void Segmented::get_followers(PtrList &list) { if (cre->alive()) { LocatorP l = cre->get_locator(); PhysicalP nextP = l->lookup(next); if (nextP) { if (nextP->alive() && nextP->is_creature() && nextP->get_ability(AB_Segmented)) { list.add(nextP); } } } } Boolean Segmented::follow(const Area &followeeArea,Dir, Boolean currentAndNext) { // Make cre follow followeeArea const Area &area = cre->get_area(); Pos fPos = followeeArea.get_middle(); Pos pos = area.get_middle(); int dist_2 = pos.distance_2(fPos); // Don't get too close. if (dist_2 > followDistance * followDistance) { // Kinda expensive, a sqrt() and a divide. float dist = (float)sqrt((double)dist_2); float invert_dist = 1.0f / dist; // delta points from followee to cre Vel delta((float)(pos.x - fPos.x),(float)(pos.y - fPos.y)); // Move along vector from nPos to pos. Pos newPos = fPos + (followDistance * invert_dist) * delta; // The "approved" way of setting the next pos for ::follow(). // Don't use set_middle_next(), because that won't handle currentAndNext // properly. _set_middle_next() exists for this purpose. cre->_set_middle_next(newPos); cre->update_area_next(currentAndNext); } // else if already too close, don't worry about it. // True even if we are too close. In either case, we handled it. return True; } Boolean Segmented::get_pixmap_mask(Xvars &xvars,int dpyNum,CMN_IMAGEDATA &pixmap, Dir dir,int animNum) { Dir mappedDir; int mappedAnimNum; map_dir_anim_num(mappedDir,mappedAnimNum,dir,animNum); // Call non-virtual Creature::_get_pixmap_mask, to avoid recursion. cre->_get_pixmap_mask(xvars,dpyNum,pixmap,mappedDir,mappedAnimNum); return True; } void Segmented::set_intel(IntelP intel) { LocatorP l = cre->get_locator(); // Is this the head of a chain of null intels. // That is the only thing where a Human is allowed to control // more than just one segment. Boolean headOfNullIntels = True; if (l->lookup(prev)) { headOfNullIntels = False; } else { PhysicalP p = cre; while (p) { if (p->get_intel()) { headOfNullIntels = False; break; } SegmentedP s = (SegmentedP)p->get_ability(AB_Segmented); if (s) { p = l->lookup(s->next); // Success as soon as p is NULL here. } // This shouldn't happen. else { headOfNullIntels = False; break; } } } // Another hack here, this doesn't work properly if the locator hasn't // been clocked yet, but doesn't break anything. When called from // Game::create_human_and_physical(). // kind of a hack. // If a human takes over, change the compositeId. if (!headOfNullIntels && intel && intel->is_human()) { CompositeId newCId = Composite::generate_id(); set_composite_id(newCId); } // TODO: If a human soul-swaps into a dragon, do // something smart like turn all the // following segments into doppelgangers, or make them all // harmless, or remove their intel. Careful not to fuck up the // locator's registry of intels. } void Segmented::act() { LocatorP l = cre->get_locator(); // Disconnect prev segment if not valid. PhysicalP p = l->lookup(prev); if (p && !compatible_prev(p)) { prev.invalidate(); SegmentedP segPrev = (SegmentedP)p->get_ability(AB_Segmented); if (segPrev) { segPrev->next.invalidate(); } } // Become leader if prev has died or has become incompatible. // Must be after checking for disconnecting prev segment. if (!isLeader) { if (!l->lookup(prev)) { assume_leadership(); } } // Disconnect next segment if not valid. PhysicalP n = l->lookup(next); if (n && !compatible_next(n)) { next.invalidate(); SegmentedP segNext = (SegmentedP)n->get_ability(AB_Segmented); if (segNext) { segNext->prev.invalidate(); } } leaderAnimTimer.clock(); } void Segmented::update() { if (leaderAnimTimer.ready()) { const MovingContext* mc = cre->get_moving_context(); // Choose the leader animNum randomly. // We will probably want to make this behavior more general if we use // Segmented for more than just Dragon. leaderAnimNum = Utils::choose(mc->animMax[LEADER_DIR]); leaderAnimTimer.set(); } } Boolean Segmented::compatible_prev(PhysicalP p) { assert(p); // Only a null intel can follow another null intel. IntelP intel = cre->get_intel(); IntelP prevIntel = p->get_intel(); if (intel && !prevIntel) { return False; } if (!p->alive() || !cre->alive()) { return False; } SegmentedP seg = (SegmentedP)p->get_ability(AB_Segmented); if (!seg) { return False; } // Disconnect if composite ids don't match. if (seg->get_composite_id() != get_composite_id()) { return False; } // A human cannot follow anything except null. if (intel && intel->is_human() && prevIntel) { return False; } // Ok for a non-human to follow. // Machine::clock() will cause non-humans to do nothing if not the // leader. return True; } Boolean Segmented::compatible_next(PhysicalP n) { assert(n); // Only a null intel can follow another null intel. IntelP intel = cre->get_intel(); IntelP nextIntel = n->get_intel(); if (!intel && nextIntel) { return False; } if (!n->alive() || !cre->alive()) { return False; } SegmentedP seg = (SegmentedP)n->get_ability(AB_Segmented); if (!seg) { return False; } // Disconnect if composite ids don't match. if (seg->get_composite_id() != get_composite_id()) { return False; } // human can only be followed by null intel. if (intel && intel->is_human() && nextIntel) { return False; } // Ok for a non-human to follow a human, // Machine::clock() will cause non-humans to do nothing if not the // leader. return True; } Boolean Segmented::is_leader() { return isLeader; } PhysicalP Segmented::get_draw_before_me() { // Always draw the Segmented object from tail to head. LocatorP l = cre->get_locator(); PhysicalP p = l->lookup(next); // May be NULL. return p; } void Segmented::create_and_add_composite(PtrList &ret,WorldP world, LocatorP locator, int segmentsNum,const Pos &pos, PhysicalP (*create)(void *arg,WorldP w,LocatorP l,const Pos &pos), void *arg) { assert(segmentsNum > 0 && ret.length() == 0); CompositeId cId = generate_id(); ret.fill(segmentsNum); // Create chain from back to front. PhysicalP nextSegment = NULL; // The segment just created. for (int n = 0; n < segmentsNum; n++) { // Create them all in the same place. No reason not to. PhysicalP obj = create(arg,world,locator,pos); assert(obj && obj->is_creature()); // Mark all objects in the chain as being part of the same composite // Creature. SegmentedP seg = (SegmentedP)obj->get_ability(AB_Segmented); // The create() function better create something with the Segmented abiltiy. assert(seg); seg->set_composite_id(cId); // Must add before seg->insert_before() so has valid Id. locator->add(obj); if (nextSegment) { SegmentedP seg = (SegmentedP)obj->get_ability(AB_Segmented); assert(seg); seg->insert_before((CreatureP)nextSegment); } nextSegment = obj; // Insert in reverse order so head is at element 0. ret.set(segmentsNum - 1 - n,obj); } // nextSegment ends up pointing at the beginning of the chain. assert(nextSegment); } void Segmented::insert_before(CreatureP nextC) { LocatorP l = cre->get_locator(); SegmentedP nextS = (SegmentedP)nextC->get_ability(AB_Segmented); assert(nextS); Id id = cre->get_id(); // assert(l->lookup(id)); Id nextId = nextC->get_id(); // assert(l->lookup(nextId)); // Generic linked list insert. prev = nextS->prev; PhysicalP prevP = l->lookup(prev); if (prevP) { SegmentedP prevS = (SegmentedP)prevP->get_ability(AB_Segmented); assert(prevS); assert(prevS->next == nextId); // Or chain was broken. prevS->next = id; } nextS->prev = id; next = nextId; // Each segment agrees not to collide with the next one. cre->set_dont_collide(nextC->get_id()); } void Segmented::assume_leadership() { // Doesn't work when you shoot the head. #if 0 // kind of a hack. // If a human takes over, change the compositeId. IntelP intel = cre->get_intel(); if (intel && intel->is_human()) { LocatorP l = cre->get_locator(); CompositeId newCId = Composite::generate_id(); // Don't recurse, human will only control one segment. PhysicalP p = cre; // while (p) { SegmentedP seg = (SegmentedP)p->get_ability(AB_Segmented); assert(seg); seg->set_composite_id(newCId); // p = l->lookup(seg->get_next()); //} } #endif isLeader = True; } void Segmented::map_dir_anim_num(Dir& mappedDir,int& mappedAnimNum,Dir dir,int animNum) { mappedDir = dir; mappedAnimNum = animNum; // Map direction to get leader pixmaps. Don't remap dead pixmaps. if (dir != Creature::DEAD_DIR && is_leader()) { mappedAnimNum = leaderAnimNum; mappedDir = LEADER_DIR; } } SegmentedContext Segmented::defaultContext; SegmentedXdata Segmented::defaultXdata; Sensitive::Sensitive(const SensitiveContext &cx,SensitiveXdata &) :Ability() { context = &cx; } Sensitive::Sensitive(InStreamP in,CreatureP c, const SensitiveContext &cx,SensitiveXdata &) :Ability(in,c) { context = &cx; // Not yet implemented. assert(context->corporealMultiplier == 0); } DEFINE_CREATE_ABILITY_FROM_STREAM(Sensitive) AbilityId Sensitive::get_ability_id() { return AB_Sensitive; } Boolean Sensitive::heat_attack(PhysicalP killer,int heat,Boolean secondary) { if (context->heatMultiplier == 0) { return False; } // Multiply the heat by heatMultiplier. cre->_heat_attack(killer,context->heatMultiplier * heat,secondary); // Ignoring the value returned by Creature::_heat_attack(). return True; } SensitiveContext Sensitive::defaultContext; SensitiveXdata Sensitive::defaultXdata;