MuckyFoot-UrbanChaos/fallen/Source/bat.cpp
2017-05-20 11:14:17 +10:00

2715 lines
50 KiB
C++

//
// Bats/gargoles that circle an area and swoop to attack the player.
//
//
// Across a triangle...
//
//
// dx x1*v2 - x2*v1
// ---- = ---------------
// du u1*v2 - u2*v1
//
#include "game.h"
#include "bat.h"
#include "animate.h"
#include "statedef.h"
#include "sound.h"
#include "eway.h"
#include "psystem.h"
#include "poly.h"
#include "mav.h"
#include "wand.h"
#include "spark.h"
#include "pcom.h"
#include "memory.h"
#include "night.h"
//
// The timer used by the bats
//
#define BAT_TICKS_PER_SECOND 160
//
// The flags.
//
#define BAT_FLAG_ATTACKED (1 << 0)
#define BAT_FLAG_SYNC_FX (1 << 1)
#define BAT_FLAG_SYNC_FX2 (1 << 2)
//
// The different states a bat can be in.
//
#define BAT_STATE_IDLE 0
#define BAT_STATE_GOTO 1
#define BAT_STATE_CIRCLE 2
#define BAT_STATE_ATTACK 3
#define BAT_STATE_DYING 4
#define BAT_STATE_DEAD 5
#define BAT_STATE_GROUND 6
#define BAT_STATE_RECOIL 7
#define BAT_STATE_BALROG_WANDER 8
#define BAT_STATE_BALROG_ROAR 9
#define BAT_STATE_BALROG_FOLLOW 10
#define BAT_STATE_BALROG_CHARGE 11
#define BAT_STATE_BALROG_SWIPE 12
#define BAT_STATE_BALROG_STOMP 13
#define BAT_STATE_BALROG_FIREBALL 14
#define BAT_STATE_BALROG_IDLE 15
#define BAT_STATE_BANE_IDLE 16
#define BAT_STATE_BANE_ATTACK 17
#define BAT_STATE_BANE_START 18
#define BAT_STATE_NUMBER 19
#ifndef PSX
CBYTE *BAT_state_name[BAT_STATE_NUMBER] =
{
"Idle",
"Goto",
"Circle",
"Attack",
"Dying",
"Dead",
"Ground",
"Recoil",
"Balrog wander",
"Balrog roar",
"Balrog follow",
"Balrog charge",
"Balrog swipe",
"Balrog stomp",
"Balrog fireball",
"Balrog idle",
"Bane idle",
"Bane attack",
"Bane start"
};
#endif
#define BAT_SUBSTATE_NONE 0
#define BAT_SUBSTATE_CIRCLE_HOME 1
#define BAT_SUBSTATE_CIRCLE_TARGET 2
#define BAT_SUBSTATE_CIRCLE_WANT 3
#define BAT_SUBSTATE_GROUND_WAIT 4
#define BAT_SUBSTATE_GROUND_WAKE_UP 5
#define BAT_SUBSTATE_GROUND_FLY_UP 6
#define BAT_SUBSTATE_DEAD_INITIAL 7
#define BAT_SUBSTATE_DEAD_LOOP 8
#define BAT_SUBSTATE_DEAD_FINAL 9
#define BAT_SUBSTATE_YOMP_START 10
#define BAT_SUBSTATE_YOMP_MIDDLE 11
#define BAT_SUBSTATE_YOMP_END 12
#define BAT_SUBSTATE_FIREBALL_TURN 13
#define BAT_SUBSTATE_FIREBALL_FIRE 14
//
// The people bane is summoning.
//
#define BAT_SUMMON_NUM_BODIES 4
UWORD BAT_summon[BAT_SUMMON_NUM_BODIES];
void BAT_find_summon_people(Thing *p_thing)
{
SLONG i;
SLONG num;
SLONG bodies;
//
// Look around for the bodies...
//
num = THING_find_sphere(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
0x800,
THING_array,
THING_ARRAY_SIZE,
1 << CLASS_PERSON);
//
// Clear out the body array.
//
memset(BAT_summon, 0, sizeof(BAT_summon));
bodies = 0;
for (i = 0; i < num; i++)
{
Thing *p_found = TO_THING(THING_array[i]);
ASSERT(p_found->Class == CLASS_PERSON);
if (p_found->Genus.Person->pcom_ai == PCOM_AI_NONE ||
p_found->Genus.Person->pcom_ai == PCOM_AI_SUICIDE)
{
if (p_found->Genus.Person->PlayerID == 0)
{
//
// This person will do!
//
ASSERT(WITHIN(bodies, 0, BAT_SUMMON_NUM_BODIES - 1));
BAT_summon[bodies] = THING_array[i];
bodies += 1;
if (bodies == BAT_SUMMON_NUM_BODIES)
{
//
// Found all our bodies!
//
break;
}
}
}
}
}
//
// Creates sparks from the given bat to the summoning people.
//
void BAT_process_bane_sparks(Thing *p_thing)
{
SLONG i;
static UWORD last = 0;
last += 16 * TICK_RATIO >> TICK_SHIFT;
if (last > 16 * 20 * 5)
{
last = 0;
for (i = 0; i < BAT_SUMMON_NUM_BODIES; i++)
{
if (BAT_summon[i])
{
Thing *p_summon = TO_THING(BAT_summon[i]);
SPARK_Pinfo p1;
SPARK_Pinfo p2;
p1.type = SPARK_TYPE_LIMB;
p1.flag = 0;
p1.person = THING_NUMBER(p_thing);
p1.limb = 0;
p2.type = SPARK_TYPE_LIMB;
p2.flag = 0;
p2.person = THING_NUMBER(p_summon);
p2.limb = SUB_OBJECT_PELVIS;
SPARK_create(
&p1,
&p2,
255);
}
}
}
}
#ifndef PSX
//
// Initialise the bats.
//
void BAT_init()
{
memset(the_game.Bats, 0, sizeof(Bat) * BAT_MAX_BATS);
BAT_COUNT = 0;
}
#endif
//
// Sets a BAT anim...
//
#define BAT_ANIM_BAT_FLY 1
#define BAT_ANIM_BAT_DIE 2
#define BAT_ANIM_GARGOYLE_WAIT 1
#define BAT_ANIM_GARGOYLE_WAKE_UP 15
#define BAT_ANIM_GARGOYLE_FLY_UP 3
#define BAT_ANIM_GARGOYLE_FLY 2
#define BAT_ANIM_GARGOYLE_FLY_FORWARDS 12
#define BAT_ANIM_GARGOYLE_ATTACK 4
#define BAT_ANIM_GARGOYLE_TAKE_HIT 14
#define BAT_ANIM_GARGOYLE_START_FALL 16
#define BAT_ANIM_GARGOYLE_FALL_LOOP 17
#define BAT_ANIM_GARGOYLE_HIT_GROUND 18
#define BAT_ANIM_BALROG_YOMP 2
#define BAT_ANIM_BALROG_IDLE 3
#define BAT_ANIM_BALROG_SWIPE 4
#define BAT_ANIM_BALROG_YOMP_START 5
#define BAT_ANIM_BALROG_YOMP_END 6
#define BAT_ANIM_BALROG_TURN 8
#define BAT_ANIM_BALROG_ROAR 9
#define BAT_ANIM_BALROG_STOMP 10
#define BAT_ANIM_BALROG_TAKE_HIT 11
#define BAT_ANIM_BALROG_DIE 12
#define BAT_ANIM_BANE_IDLE 2
#define BAT_ANIM_BANE_ATTACK 3
#define BAT_ANIM_GENERIC_FLY (-1) // An anim that changes depending on the type of bat!
#define BAT_ANIM_GENERIC_TAKE_HIT (-2)
void BAT_set_anim(Thing *p_thing, SLONG anim)
{
if (anim < 0)
{
static UBYTE generic_bat_anim[2][2] =
{
{BAT_ANIM_BAT_FLY, BAT_ANIM_BAT_FLY},
{BAT_ANIM_GARGOYLE_FLY, BAT_ANIM_GARGOYLE_TAKE_HIT}
};
ASSERT(WITHIN(p_thing->Genus.Bat->type - 1, 0, 1));
ASSERT(WITHIN(-anim - 1, 0, 1));
anim = generic_bat_anim[p_thing->Genus.Bat->type - 1][-anim - 1];
}
DrawTween *dt = p_thing->Draw.Tweened;
if (dt->CurrentAnim == anim)
{
return;
}
dt->AnimTween = 0;
dt->QueuedFrame = NULL;
dt->TheChunk = &anim_chunk[p_thing->Genus.Bat->type];
dt->CurrentFrame = anim_chunk[p_thing->Genus.Bat->type].AnimList[anim];
dt->NextFrame = dt->CurrentFrame->NextFrame;
dt->CurrentAnim = anim;
dt->FrameIndex = 0;
if (dt->NextFrame == NULL)
{
dt->NextFrame = dt->CurrentFrame->NextFrame;
}
p_thing->Genus.Bat->flag&=~(BAT_FLAG_SYNC_FX|BAT_FLAG_SYNC_FX2);
}
//
// Animates the bat. Returns TRUE if the current anim is over.
//
SLONG BAT_animate(Thing *p_thing)
{
SLONG ret = FALSE;
SLONG tween_step;
DrawTween *dt = p_thing->Draw.Tweened;
tween_step = dt->CurrentFrame->TweenStep << 1;
tween_step = tween_step * TICK_RATIO >> TICK_SHIFT;
if (tween_step <= 0)
{
tween_step = 1;
}
dt->AnimTween += tween_step;
while (dt->AnimTween >= 256)
{
dt->AnimTween -= 256;
if (dt->NextFrame)
{
dt->AnimTween = (dt->AnimTween * dt->NextFrame->TweenStep) / dt->CurrentFrame->TweenStep;
}
dt->FrameIndex++;
SLONG advance_keyframe(DrawTween *draw_info);
ret |= advance_keyframe(dt);
}
return ret;
}
//
// Makes a bat turn towards his target. Returns the angle difference.
//
SLONG BAT_turn_to_target(Thing *p_thing)
{
SLONG dx;
SLONG dz;
SLONG wangle;
SLONG dangle;
SLONG turn;
Thing *p_target;
Bat *p_bat;
ASSERT(p_thing->Class == CLASS_BAT);
p_bat = p_thing->Genus.Bat;
p_target = TO_THING(p_bat->target);
ASSERT(p_bat->target);
/*
AENG_world_line(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
32,
0xff0000,
p_target->WorldPos.X >> 8,
p_target->WorldPos.Y >> 8,
p_target->WorldPos.Z >> 8,
16,
0x00ff00,
TRUE);
*/
dx = p_target->WorldPos.X - p_thing->WorldPos.X >> 8;
dz = p_target->WorldPos.Z - p_thing->WorldPos.Z >> 8;
wangle = calc_angle(dx,dz);
if (p_bat->type == BAT_TYPE_BALROG)
{
//
// Oh dear! He's back-to-front!
//
wangle += 1024;
}
dangle = wangle - p_thing->Draw.Tweened->Angle;
if (dangle > +1024) {dangle -= 2048;}
if (dangle < -1024) {dangle += 2048;}
turn = dangle >> 4;
SATURATE(turn, -20, +20);
p_thing->Draw.Tweened->Angle += turn;
p_thing->Draw.Tweened->Angle &= 2047;
return dangle;
}
//
// Makes the bat turn towards a given place. Returns the relative angle.
//
SLONG BAT_turn_to_place(Thing *p_thing, SLONG world_x, SLONG world_z)
{
SLONG dx;
SLONG dz;
SLONG wangle;
SLONG dangle;
SLONG turn;
Bat *p_bat;
ASSERT(p_thing->Class == CLASS_BAT);
p_bat = p_thing->Genus.Bat;
/*
AENG_world_line(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
32,
0xff0000,
world_x,
PAP_calc_map_height_at(world_x,world_z),
world_z,
16,
0xffffff,
TRUE);
*/
dx = world_x - (p_thing->WorldPos.X >> 8);
dz = world_z - (p_thing->WorldPos.Z >> 8);
wangle = calc_angle(dx,dz);
if (p_bat->type == BAT_TYPE_BALROG)
{
//
// Oh dear! He's back-to-front!
//
wangle += 1024;
}
dangle = wangle - p_thing->Draw.Tweened->Angle;
if (dangle > +1024) {dangle -= 2048;}
if (dangle < -1024) {dangle += 2048;}
turn = dangle >> 4;
SATURATE(turn, -20, +20);
p_thing->Draw.Tweened->Angle += turn;
p_thing->Draw.Tweened->Angle &= 2047;
return dangle;
}
//
// Makes the bat emit a fireball.
//
void BAT_emit_fireball(Thing *p_thing)
{
ASSERT(p_thing->Class == CLASS_BAT);
Bat *p_bat = p_thing->Genus.Bat;
ASSERT(p_bat->target);
Thing *p_target = TO_THING(p_bat->target);
SLONG dx;
SLONG dy;
SLONG dz;
dx = p_target->WorldPos.X - p_thing->WorldPos.X;
dy = p_target->WorldPos.Y + 0x3000 - p_thing->WorldPos.Y;
dz = p_target->WorldPos.Z - p_thing->WorldPos.Z;
SLONG dist = (QDIST3(abs(dx),abs(dy),abs(dz)) >> 8) + 1;
dx = 0x40 * dx / dist;
dy = 0x40 * dy / dist;
dz = 0x40 * dz / dist;
dy += Random() & 0x3ff;
//
// Fire a fireball!
//
PARTICLE_Add(
p_thing->WorldPos.X,
p_thing->WorldPos.Y + 0x5000,
p_thing->WorldPos.Z,
dx,
dy,
dz,
POLY_PAGE_METEOR,
2 + ((Random() & 0x3) << 2),
0xffffffff,
PFLAG_SPRITEANI | PFLAG_SPRITELOOP | PFLAG_EXPLODE_ON_IMPACT | PFLAG_GRAVITY | PFLAG_LEAVE_TRAIL,
100,
160,
1,
1,
1);
MFX_play_thing(THING_NUMBER(p_thing),S_BALROG_FIREBALL,0,p_thing);
}
//
// Chooses a new state for the bat to be in.
//
void BAT_change_state(Thing *p_thing)
{
ASSERT(p_thing->Class == CLASS_BAT);
SLONG want_x;
SLONG want_y;
SLONG want_z;
SLONG new_state;
SLONG old_target;
Bat *p_bat = p_thing->Genus.Bat;
Thing *darci;
old_target = p_bat->target;
SLONG dangle = 0;
if (p_bat->type == BAT_TYPE_BALROG)
{
if (p_bat->target)
{
Thing *p_target = TO_THING(p_bat->target);
extern SLONG is_person_dead(Thing *p_person);
if (p_target->Class == CLASS_PERSON && is_person_dead(p_target))
{
//
// Assume the Balrog killed this person.
//
extern UBYTE *EWAY_counter;
if (EWAY_counter[8] < 255)
{
EWAY_counter[8] += 1;
}
}
}
//
// Look for a target.
//
SLONG i;
SLONG dx;
SLONG dy;
SLONG dz;
SLONG dist;
SLONG score;
SLONG best_score = INFINITY;
SLONG best_dist = 0;
SLONG best_index = NULL;
Thing *p_found;
//
// Find all the people near us.
//
SLONG found_num = THING_find_sphere(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
0xa00,
THING_array,
THING_ARRAY_SIZE,
(1 << CLASS_PERSON) | (1 << CLASS_VEHICLE));
//
// Who is the best person to bully?
//
for (i = 0; i < found_num; i++)
{
p_found = TO_THING(THING_array[i]);
if (p_found == p_thing)
{
//
// Ignore yourself!
//
continue;
}
if (p_found->State == STATE_DEAD ||
p_found->State == STATE_DYING)
{
//
// Ignore dead people / vehicles.
//
continue;
}
if (p_found->Class == CLASS_PERSON)
{
extern SLONG is_person_ko(Thing *p_person);
if (is_person_ko(p_found))
{
//
// Knocked out people aren't fair game!
//
continue;
}
}
//
// Make sure we can get to the person.
//
MAV_do(
p_thing->WorldPos.X >> 16,
p_thing->WorldPos.Z >> 16,
p_found->WorldPos.X >> 16,
p_found->WorldPos.Z >> 16,
MAV_CAPS_GOTO);
if (MAV_do_found_dest)
{
//
// Score this person/vehicle. Lower scores are better.
//
dx = abs(p_thing->WorldPos.X - p_found->WorldPos.X);
dy = abs(p_thing->WorldPos.Y - p_found->WorldPos.Y);
dz = abs(p_thing->WorldPos.Z - p_found->WorldPos.Z);
dist = dx + dy + dy + dz >> 8; // Difference in y is more important...
score = dist;
if (p_bat->target == THING_array[i])
{
//
// Likes to keep the same target.
//
score >>= 1;
}
if (p_found->Genus.Person->PlayerID)
{
//
// More likely to attack players.
//
score >>= 1;
}
if (best_score > score)
{
best_score = score;
best_index = THING_array[i];
best_dist = dist;
}
}
}
p_bat->target = best_index;
//
// What state should the balrog be in now?
//
if (p_bat->target)
{
dangle = abs(BAT_turn_to_target(p_thing));
if (p_bat->target != old_target)
{
//
// Roar when you find a new target!
//
new_state = BAT_STATE_BALROG_ROAR;
}
else
{
if (best_dist < 0x200 && dangle < 256)
{
new_state = BAT_STATE_BALROG_SWIPE;
}
else
if (best_dist < 0x400)
{
if (Random() & 0x40)
{
new_state = BAT_STATE_BALROG_STOMP;
}
else
{
new_state = BAT_STATE_BALROG_FOLLOW;
}
}
else
{
if (Random() & 0x20)
{
if ((Random() & 0x3) == 0)
{
new_state = BAT_STATE_BALROG_CHARGE;
}
else
{
new_state = BAT_STATE_BALROG_FIREBALL;
}
}
else
{
new_state = BAT_STATE_BALROG_FOLLOW;
}
}
}
}
else
{
if (p_bat->state == BAT_STATE_BALROG_FIREBALL ||
p_bat->state == BAT_STATE_BALROG_CHARGE ||
p_bat->state == BAT_STATE_BALROG_SWIPE ||
p_bat->state == BAT_STATE_BALROG_STOMP)
{
//
// Roar after a successful attack!
//
new_state = BAT_STATE_BALROG_ROAR;
}
else
if ((Random() & 0x3) == 0)
{
if (Random() & 0x2)
{
new_state = BAT_STATE_BALROG_ROAR;
}
else
{
new_state = BAT_STATE_BALROG_IDLE;
}
}
else
{
//
// Wander the city looking for targets.
//
new_state = BAT_STATE_BALROG_WANDER;
}
}
}
else
if (p_bat->type == BAT_TYPE_BANE)
{
p_bat->target = NULL;
if (BAT_summon[0] == NULL)
{
new_state = BAT_STATE_BANE_START;
}
else
{
if ((Random() & 0x3) == 0)
{
new_state = BAT_STATE_BANE_ATTACK;
}
else
{
new_state = BAT_STATE_BANE_IDLE;
}
}
}
#ifndef PSX
else
{
//
// Is the player close by?
//
darci = NET_PERSON(0);
if (THING_dist_between(darci, p_thing) < 0x600)
{
p_bat->target = THING_NUMBER(darci);
}
else
{
p_bat->target = NULL;
}
//
// What state should be in now?
//
SLONG dx = p_thing->WorldPos.X - ((p_bat->home_x << 16) + 0x8000);
SLONG dz = p_thing->WorldPos.Z - ((p_bat->home_z << 16) + 0x8000);
if (abs(dx) > 0x40000 ||
abs(dz) > 0x40000)
{
//
// Too far from home...
//
new_state = BAT_STATE_GOTO;
}
else
{
new_state = Random() % 3;
if (p_bat->type == BAT_TYPE_GARGOYLE &&
new_state == BAT_STATE_CIRCLE &&
p_bat->target != NULL)
{
new_state = BAT_STATE_ATTACK;
}
}
}
#endif
//
// Initialise the new state.
//
p_bat->state = new_state;
p_bat->flag &= ~BAT_FLAG_ATTACKED;
switch(new_state)
{
#ifndef PSX
case BAT_STATE_IDLE:
p_bat->timer = BAT_TICKS_PER_SECOND * (1 + (Random() & 0x1));
BAT_set_anim(p_thing, BAT_ANIM_GENERIC_FLY);
break;
case BAT_STATE_GOTO:
want_x = p_bat->home_x << 8;
want_z = p_bat->home_z << 8;
want_x += Random() & 0x3ff;
want_z += Random() & 0x3ff;
want_x -= 0x1ff;
want_z -= 0x1ff;
want_y = PAP_calc_map_height_at(want_x, want_z) + 0x80 + (Random() & 0x7f);
p_bat->want_x = want_x;
p_bat->want_y = want_y;
p_bat->want_z = want_z;
p_bat->timer = BAT_TICKS_PER_SECOND * (2 + (Random() & 0x1));
BAT_set_anim(p_thing, BAT_ANIM_GENERIC_FLY);
break;
case BAT_STATE_CIRCLE:
if (p_bat->target)
{
p_bat->substate = BAT_SUBSTATE_CIRCLE_TARGET;
}
else
{
p_bat->substate = BAT_SUBSTATE_CIRCLE_HOME;
}
p_bat->timer = BAT_TICKS_PER_SECOND * (3 + (Random() & 0x3));
BAT_set_anim(p_thing, BAT_ANIM_GENERIC_FLY);
break;
case BAT_STATE_ATTACK:
ASSERT(p_bat->type == BAT_TYPE_GARGOYLE);
BAT_set_anim(p_thing, BAT_ANIM_GARGOYLE_ATTACK);
break;
#endif
case BAT_STATE_BALROG_WANDER:
{
SLONG want_x;
SLONG want_z;
MAV_Action ma;
WAND_get_next_place(
p_thing,
&want_x,
&want_z);
ma = MAV_do(
p_thing->WorldPos.X >> 16,
p_thing->WorldPos.Z >> 16,
want_x >> 8,
want_z >> 8,
MAV_CAPS_GOTO);
p_bat->want_x = (ma.dest_x << 8) + 0x80;
p_bat->want_z = (ma.dest_z << 8) + 0x80;
p_bat->timer = BAT_TICKS_PER_SECOND * (2 + (Random() & 0x1));
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP_START);
p_bat->substate = BAT_SUBSTATE_YOMP_START;
}
break;
case BAT_STATE_BALROG_ROAR:
//
// We don't have a roar anim! Do a swipe instead...
//
BAT_set_anim(p_thing, BAT_ANIM_BALROG_ROAR);
MFX_play_thing(THING_NUMBER(p_thing),S_BALROG_ROAR,0,p_thing);
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BALROG_CHARGE:
case BAT_STATE_BALROG_FOLLOW:
ASSERT(p_bat->target);
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP_START);
p_bat->timer = BAT_TICKS_PER_SECOND * (2 + (Random() & 0x1));
p_bat->substate = BAT_SUBSTATE_YOMP_START;
break;
case BAT_STATE_BALROG_SWIPE:
BAT_set_anim(p_thing, BAT_ANIM_BALROG_SWIPE);
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_GROWL_START,S_BALROG_GROWL_END),0,p_thing);
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BALROG_STOMP:
BAT_set_anim(p_thing, BAT_ANIM_BALROG_STOMP);
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_GROWL_START,S_BALROG_GROWL_END),0,p_thing);
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BALROG_FIREBALL:
if (dangle < 256)
{
BAT_set_anim(p_thing, BAT_ANIM_BALROG_SWIPE);
p_bat->substate = BAT_SUBSTATE_FIREBALL_FIRE;
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_GROWL_START,S_BALROG_GROWL_END),0,p_thing);
}
else
{
BAT_set_anim(p_thing, BAT_ANIM_BALROG_TURN);
p_bat->substate = BAT_SUBSTATE_FIREBALL_TURN;
}
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BALROG_IDLE:
BAT_set_anim(p_thing, BAT_ANIM_BALROG_IDLE);
p_bat->timer = BAT_TICKS_PER_SECOND * (2 + (Random() & 0x3));
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BANE_IDLE:
BAT_set_anim(p_thing, BAT_ANIM_BANE_IDLE);
p_bat->timer = BAT_TICKS_PER_SECOND * (3 + (Random() & 0x3));
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BANE_ATTACK:
BAT_set_anim(p_thing, BAT_ANIM_BANE_ATTACK);
p_bat->dx = 0;
p_bat->dz = 0;
break;
case BAT_STATE_BANE_START:
//
// Look for the people to summon.
//
BAT_find_summon_people(p_thing);
//
// Make them start floating.
//
{
SLONG i;
for (i = 0; i < BAT_SUMMON_NUM_BODIES; i++)
{
if (BAT_summon[i])
{
Thing *p_summon = TO_THING(BAT_summon[i]);
set_person_float_up(p_summon);
}
}
}
BAT_set_anim(p_thing, BAT_ANIM_BANE_IDLE);
// make some funky noises
MFX_play_thing(THING_NUMBER(p_thing),S_RECKONING_LOOP,MFX_LOOPED,p_thing);
break;
default:
ASSERT(0);
break;
}
}
//
// Slides a Balrog around buildings.
//
#define BAT_BALROG_WIDTH (0x3000)
void BAT_balrog_slide_along(
SLONG x1, SLONG z1,
SLONG *x2, SLONG *z2)
{
SLONG dx;
SLONG dz;
SLONG dist;
ULONG collide;
SLONG mx = *x2 >> 16;
SLONG mz = *z2 >> 16;
ASSERT(WITHIN(mx, 1, PAP_SIZE_HI - 2));
ASSERT(WITHIN(mz, 1, PAP_SIZE_HI - 2));
//
// Where do we collide?
//
#define BAT_COLLIDE_XS (1 << 0)
#define BAT_COLLIDE_XL (1 << 1)
#define BAT_COLLIDE_ZS (1 << 2)
#define BAT_COLLIDE_ZL (1 << 3)
#define BAT_COLLIDE_SS (1 << 4)
#define BAT_COLLIDE_LS (1 << 5)
#define BAT_COLLIDE_SL (1 << 6)
#define BAT_COLLIDE_LL (1 << 7)
MAV_Opt mo = MAV_opt[MAV_NAV(mx,mz)];
//
// The mapsquare edges.
//
collide = 0;
if (!(mo.opt[MAV_DIR_XS] & (MAV_CAPS_GOTO))) {collide |= BAT_COLLIDE_XS;}
if (!(mo.opt[MAV_DIR_XL] & (MAV_CAPS_GOTO))) {collide |= BAT_COLLIDE_XL;}
if (!(mo.opt[MAV_DIR_ZS] & (MAV_CAPS_GOTO))) {collide |= BAT_COLLIDE_ZS;}
if (!(mo.opt[MAV_DIR_ZL] & (MAV_CAPS_GOTO))) {collide |= BAT_COLLIDE_ZL;}
if (PAP_2HI(mx,mz).Flags & PAP_FLAG_HIDDEN)
{
//
// The Balrog is on a building! :( Turn off MAPSQUARE collision.
//
}
else
{
if (PAP_2HI(mx + 1, mz + 0).Flags & PAP_FLAG_HIDDEN) {collide |= BAT_COLLIDE_XL;}
if (PAP_2HI(mx - 1, mz + 0).Flags & PAP_FLAG_HIDDEN) {collide |= BAT_COLLIDE_XS;}
if (PAP_2HI(mx + 0, mz + 1).Flags & PAP_FLAG_HIDDEN) {collide |= BAT_COLLIDE_ZL;}
if (PAP_2HI(mx + 0, mz - 1).Flags & PAP_FLAG_HIDDEN) {collide |= BAT_COLLIDE_ZS;}
}
/*
if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx - 1, mz)) > 1) {collide |= BAT_COLLIDE_XS;}
if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx + 1, mz)) > 1) {collide |= BAT_COLLIDE_XL;}
if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx, mz - 1)) > 1) {collide |= BAT_COLLIDE_ZS;}
if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx, mz + 1)) > 1) {collide |= BAT_COLLIDE_ZL;}
if (!(collide & (BAT_COLLIDE_XS | BAT_COLLIDE_ZS))) {if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx - 1, mz - 1)) > 1) {collide |= BAT_COLLIDE_SS;}}
if (!(collide & (BAT_COLLIDE_XL | BAT_COLLIDE_ZS))) {if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx + 1, mz - 1)) > 1) {collide |= BAT_COLLIDE_LS;}}
if (!(collide & (BAT_COLLIDE_XS | BAT_COLLIDE_ZL))) {if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx - 1, mz + 1)) > 1) {collide |= BAT_COLLIDE_SL;}}
if (!(collide & (BAT_COLLIDE_XL | BAT_COLLIDE_ZL))) {if (abs(MAVHEIGHT(mx,mz) - MAVHEIGHT(mx + 1, mz + 1)) > 1) {collide |= BAT_COLLIDE_LL;}}
*/
//
// Edges...
//
if (collide & BAT_COLLIDE_XS)
{
//
// Keep out of the (xsmall) of this square.
//
if ((*x2 & 0xffff) < BAT_BALROG_WIDTH)
{
*x2 &= ~0xffff;
*x2 |= BAT_BALROG_WIDTH;
}
}
if (collide & BAT_COLLIDE_XL)
{
//
// Keep out of the (xlarge) of this square.
//
if ((*x2 & 0xffff) > 0x10000 - BAT_BALROG_WIDTH)
{
*x2 &= ~0xffff;
*x2 |= 0x10000 - BAT_BALROG_WIDTH;
}
}
if (collide & BAT_COLLIDE_ZS)
{
//
// Keep out of the (zsmall) of this square.
//
if ((*z2 & 0xffff) < BAT_BALROG_WIDTH)
{
*z2 &= ~0xffff;
*z2 |= BAT_BALROG_WIDTH;
}
}
if (collide & BAT_COLLIDE_ZL)
{
//
// Keep out of the (zlarge) of this square.
//
if ((*z2 & 0xffff) > 0x10000 - BAT_BALROG_WIDTH)
{
*z2 &= ~0xffff;
*z2 |= 0x10000 - BAT_BALROG_WIDTH;
}
}
//
// Corners...
//
if (collide & BAT_COLLIDE_SS)
{
dx = *x2 & 0xffff;
dz = *z2 & 0xffff;
dist = QDIST2(dx,dz) + 1;
if (dist < BAT_BALROG_WIDTH)
{
dx = dx * BAT_BALROG_WIDTH / dist;
dz = dz * BAT_BALROG_WIDTH / dist;
}
*x2 &= ~0xffff;
*z2 &= ~0xffff;
*x2 |= dx;
*z2 |= dz;
}
if (collide & BAT_COLLIDE_LS)
{
dx = *x2 & 0xffff;
dz = *z2 & 0xffff;
dx = 0x10000 - dx;
dist = QDIST2(dx,dz) + 1;
if (dist < BAT_BALROG_WIDTH)
{
dx = dx * BAT_BALROG_WIDTH / dist;
dz = dz * BAT_BALROG_WIDTH / dist;
}
dx = 0x10000 - dx;
*x2 &= ~0xffff;
*z2 &= ~0xffff;
*x2 |= dx;
*z2 |= dz;
}
if (collide & BAT_COLLIDE_SL)
{
dx = *x2 & 0xffff;
dz = *z2 & 0xffff;
dz = 0x10000 - dz;
dist = QDIST2(dx,dz) + 1;
if (dist < BAT_BALROG_WIDTH)
{
dx = dx * BAT_BALROG_WIDTH / dist;
dz = dz * BAT_BALROG_WIDTH / dist;
}
dz = 0x10000 - dz;
*x2 &= ~0xffff;
*z2 &= ~0xffff;
*x2 |= dx;
*z2 |= dz;
}
if (collide & BAT_COLLIDE_LS)
{
dx = *x2 & 0xffff;
dz = *z2 & 0xffff;
dx = 0x10000 - dx;
dz = 0x10000 - dz;
dist = QDIST2(dx,dz) + 1;
if (dist < BAT_BALROG_WIDTH)
{
dx = dx * BAT_BALROG_WIDTH / dist;
dz = dz * BAT_BALROG_WIDTH / dist;
}
dz = 0x10000 - dz;
*x2 &= ~0xffff;
*z2 &= ~0xffff;
*x2 |= dx;
*z2 |= dz;
}
}
//
// Processes a bat thing.
//
void BAT_normal(Thing *p_thing)
{
SLONG dx;
SLONG dy;
SLONG dz;
SLONG dist;
SLONG ddx;
SLONG ddz;
SLONG want_x;
SLONG want_y;
SLONG want_z;
SLONG want_dx;
SLONG want_dy;
SLONG want_dz;
SLONG wangle;
SLONG dangle;
SLONG goto_x;
SLONG goto_y;
SLONG goto_z;
SLONG ground;
SLONG end;
;; ;; ;;
;; ;; ;;
;; ;; ;;
;;;;;;; ;;;;; ;; ;; ;; ; ;;;; ;;
;; ;; ;; ;; ;;;;; ;;;; ;;; ;; ;;;
;; ;; ;;;;;; ;; ;; ;;; ;; ;;; ;; ;;;; ;;;
;; ;; ;; ; ;; ;;;;; ;; ;; ;; ;;
;; ;;;;; ;;;;; ;; ; ;; ;;;;; ;
; ; ; ; ;;; ;
; ;;
; ;;;;;;;;;;;;;;;; ;;
; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;;;;;;;;; ;;
; ; ;;;;;; ; ;; ;; ;
GameCoord newpos; ;;;;;;; ;;; ;
;
SLONG ticks = (BAT_TICKS_PER_SECOND / 20) * TICK_RATIO >> TICK_SHIFT;
ASSERT(p_thing->Class == CLASS_BAT); ;;;;; ;; ;;
;;; ;;;;
Bat *p_bat = p_thing->Genus.Bat; ;
;; ;;
Thing *p_target; ;;
#ifndef PSX
// make some batty sounds. if we're not a gargoyle. or a balrog. or bane.
if (p_bat->type==BAT_TYPE_BAT)
{
if (!(Random()&0xff)) MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BAT_SQUEEK_START,S_BAT_SQUEEK_END),MFX_NEVER_OVERLAP,p_thing);
}
#endif
switch(p_bat->state)
{
#ifndef PSX
case BAT_STATE_IDLE:
p_bat->dx -= p_bat->dx / 16;
p_bat->dy -= p_bat->dy / 16;
p_bat->dz -= p_bat->dz / 16;
if (p_bat->target)
{
BAT_turn_to_target(p_thing);
}
end = BAT_animate(p_thing);
break;
case BAT_STATE_GOTO:
want_dx = p_bat->want_x - (p_thing->WorldPos.X >> 8) << 5;
want_dy = p_bat->want_y - (p_thing->WorldPos.Y >> 8) << 5;
want_dz = p_bat->want_z - (p_thing->WorldPos.Z >> 8) << 5;
SATURATE(want_dx, -0x2000, +0x2000);
SATURATE(want_dy, -0x0800, +0x0800);
SATURATE(want_dz, -0x2000, +0x2000);
p_bat->dx += want_dx - p_bat->dx >> 4;
p_bat->dy += want_dy - p_bat->dy >> 4;
p_bat->dz += want_dz - p_bat->dz >> 4;
p_bat->dx -= p_bat->dx / 16;
p_bat->dy -= p_bat->dy / 16;
p_bat->dz -= p_bat->dz / 16;
if (p_bat->target)
{
BAT_turn_to_target(p_thing);
}
end = BAT_animate(p_thing);
break;
case BAT_STATE_CIRCLE:
//
// Where do we want to go?
//
switch(p_bat->substate)
{
case BAT_SUBSTATE_CIRCLE_HOME:
want_x = (p_bat->home_x << 8) + 0x80;
want_z = (p_bat->home_z << 8) + 0x80;
want_y = PAP_calc_map_height_at(want_x,want_z) + 0x100;
break;
case BAT_SUBSTATE_CIRCLE_WANT:
want_x = p_bat->want_x;
want_y = p_bat->want_y;
want_z = p_bat->want_z;
break;
case BAT_SUBSTATE_CIRCLE_TARGET:
ASSERT(p_bat->target);
p_target = TO_THING(p_bat->target);
switch(p_target->Class)
{
case CLASS_PERSON:
calc_sub_objects_position(
p_target,
0,
SUB_OBJECT_HEAD,
&want_x,
&want_y,
&want_z);
want_x += p_target->WorldPos.X >> 8;
want_y += p_target->WorldPos.Y >> 8;
want_z += p_target->WorldPos.Z >> 8;
break;
case CLASS_BAT:
want_x = p_target->WorldPos.X >> 8;
want_y = p_target->WorldPos.Y >> 8;
want_z = p_target->WorldPos.Z >> 8;
break;
default:
ASSERT(0);
break;
}
break;
}
//
// Circle around (want_x,want_y,want_z). Turn towards there slowly.
//
dx = want_x - (p_thing->WorldPos.X >> 8);
dz = want_z - (p_thing->WorldPos.Z >> 8);
wangle = calc_angle(dx,dz);
dangle = wangle - p_thing->Draw.Tweened->Angle;
if (dangle > +1024) {dangle -= 2048;}
if (dangle < -1024) {dangle += 2048;}
if (p_bat->substate == BAT_SUBSTATE_CIRCLE_TARGET)
{
//
// If we are flying towards our target... then swoop down to attack!
//
if (abs(dangle) < 300)
{
//
// The swoop down... make sure you don't stop.
//
p_bat->timer += ticks;
dy = want_y - (p_thing->WorldPos.Y >> 8);
if (QDIST3(abs(dx),abs(dy),abs(dz)) < 0x40)
{
if(p_target->Class==CLASS_PERSON && p_target->Genus.Person->PlayerID && EWAY_stop_player_moving())
{
//
// cut scene so don't hurt player
//
}
else
if (!(p_bat->flag & BAT_FLAG_ATTACKED))
{
//
// Hurt our target!
//
PainSound(p_target); // scream yer lungs out!
p_target->Genus.Person->Health -= 60;
if (p_target->Genus.Person->Health <= 0)
{
set_person_dead(
p_target,
NULL,
PERSON_DEATH_TYPE_OTHER,
FALSE,
FALSE);
}
p_bat->flag |= BAT_FLAG_ATTACKED; // Don't do this until the next swoop.
}
}
else
{
p_bat->flag &= ~BAT_FLAG_ATTACKED; // Allowed to attack again.
}
}
else
{
want_y = p_bat->want_y;
}
}
dangle >>= 2;
dist = QDIST2(abs(dx), abs(dz)) >> 4;
if (dist < 10) {dist = 10;}
SATURATE(dangle, -dist, +dist);
p_thing->Draw.Tweened->Angle += dangle;
p_thing->Draw.Tweened->Angle &= 2047;
//
// Fly in the direction you are facing.
//
want_dx = SIN(p_thing->Draw.Tweened->Angle) >> 2;
want_dz = COS(p_thing->Draw.Tweened->Angle) >> 2;
//want_dx += want_dx >> 1;
//want_dz += want_dz >> 1;
ddx = want_dx - p_bat->dx >> 4;
ddz = want_dz - p_bat->dz >> 4;
SATURATE(ddx, -0x800, +0x800);
SATURATE(ddz, -0x800, +0x800);
p_bat->dx += ddx;
p_bat->dz += ddz;
p_bat->dx -= p_bat->dx / 32;
p_bat->dy -= p_bat->dy / 4;
p_bat->dz -= p_bat->dz / 32;
//
// Do 'dy' differently!
//
dy = want_y - (p_thing->WorldPos.Y >> 8);
p_bat->dy += dy << 2;
/*
AENG_world_line(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
16,
0xffffff,
want_x,
want_y,
want_z,
0,
0xff0000,
FALSE);
*/
end = BAT_animate(p_thing);
break;
case BAT_STATE_ATTACK:
if (p_bat->target && !(p_bat->flag & BAT_FLAG_ATTACKED) && p_thing->Draw.Tweened->FrameIndex > 6)
{
BAT_emit_fireball(p_thing);
p_bat->flag |= BAT_FLAG_ATTACKED;
}
if (p_bat->target)
{
BAT_turn_to_target(p_thing);
}
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
#endif
case BAT_STATE_DYING:
switch(p_bat->substate)
{
#ifndef PSX
case BAT_SUBSTATE_DEAD_INITIAL:
//
// Only gargoyles have this state for now...
//
// ASSERT(p_bat->type == BAT_TYPE_GARGOYLE); not anymore
if (BAT_animate(p_thing))
{
p_bat->substate = BAT_SUBSTATE_DEAD_LOOP;
BAT_set_anim(p_thing, BAT_ANIM_GARGOYLE_FALL_LOOP);
}
break;
case BAT_SUBSTATE_DEAD_LOOP:
BAT_animate(p_thing);
//
// Fall out of the sky!
//
p_bat->dy -= 0x180;
p_bat->dx -= p_bat->dx / 32;
p_bat->dz -= p_bat->dz / 32;
ground = PAP_calc_map_height_at(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Z >> 8) << 8;
if (p_thing->WorldPos.Y <= ground + 0x4000)
{
//
// Hit the ground.
//
if (p_bat->type == BAT_TYPE_BAT && abs(p_bat->dy) > 0x1000)
{
//
// Bounce!
//
p_bat->dy = abs(p_bat->dy) >> 2;
}
else
{
//
// Dead!
//
p_bat->dx = 0;
p_bat->dy = 0;
p_bat->dz = 0;
if (p_bat->type == BAT_TYPE_BAT)
{
p_bat->state = BAT_STATE_DEAD;
}
else
{
p_bat->substate = BAT_SUBSTATE_DEAD_FINAL;
BAT_set_anim(p_thing, BAT_ANIM_GARGOYLE_HIT_GROUND);
}
}
}
break;
#endif
case BAT_SUBSTATE_DEAD_FINAL:
//
// Only gargoyles and balrogshave this state for now...
//
// ASSERT(p_bat->type == BAT_TYPE_GARGOYLE); balrogs too
if (BAT_animate(p_thing))
{
p_bat->state = BAT_STATE_DEAD;
p_thing->State = STATE_DEAD;
}
break;
default:
ASSERT(0);
break;
}
//
// Don't change state!
//
end = FALSE;
break;
case BAT_STATE_DEAD:
//
// NO MORE PROCESSING!
//
return;
//#ifndef PSX
case BAT_STATE_GROUND:
switch(p_bat->substate)
{
case BAT_SUBSTATE_GROUND_WAIT:
{
Thing *darci = NET_PERSON(0);
//
// Wake up when the player gets too near.
//
SLONG dx = abs(darci->WorldPos.X - p_thing->WorldPos.X);
SLONG dz = abs(darci->WorldPos.Z - p_thing->WorldPos.Z);
SLONG dist = QDIST2(dx,dz);
if (dist < 0xa0000)
{
BAT_set_anim(p_thing, BAT_ANIM_GARGOYLE_WAKE_UP);
p_bat->substate = BAT_SUBSTATE_GROUND_WAKE_UP;
}
}
break;
case BAT_SUBSTATE_GROUND_WAKE_UP:
if (BAT_animate(p_thing))
{
BAT_set_anim(p_thing, BAT_ANIM_GARGOYLE_FLY_UP);
p_bat->substate = BAT_SUBSTATE_GROUND_FLY_UP;
}
break;
case BAT_SUBSTATE_GROUND_FLY_UP:
if (BAT_animate(p_thing))
{
BAT_change_state(p_thing);
}
break;
default:
ASSERT(0);
break;
}
end = FALSE;
p_bat->timer = 0;
break;
#ifndef PSX
case BAT_STATE_RECOIL:
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
#endif
case BAT_STATE_BALROG_WANDER:
{
SLONG dangle;
//
// Turn towards where you are going.
//
dangle = BAT_turn_to_place(
p_thing,
p_bat->want_x,
p_bat->want_z);
if (abs(dangle) > 256 || p_bat->substate == BAT_SUBSTATE_YOMP_END)
{
//
// Don't move!
//
p_bat->dx -= p_bat->dx >> 2;
p_bat->dz -= p_bat->dz >> 2;
}
else
{
//
// Accelerate to moving speed.
//
want_dx = -SIN(p_thing->Draw.Tweened->Angle) >> 4;
want_dz = -COS(p_thing->Draw.Tweened->Angle) >> 4;
want_dx += want_dx >> 1;
want_dz += want_dz >> 1;
want_dx -= want_dx >> 5;
want_dz -= want_dz >> 5;
p_bat->dx += (want_dx - p_bat->dx) >> 3;
p_bat->dz += (want_dz - p_bat->dz) >> 3;
}
end = BAT_animate(p_thing);
if (end)
{
if (p_bat->substate == BAT_SUBSTATE_YOMP_START)
{
end = FALSE;
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP);
p_bat->substate = BAT_SUBSTATE_YOMP_MIDDLE;
}
else
if (p_bat->substate == BAT_SUBSTATE_YOMP_MIDDLE)
{
end = FALSE;
if (p_bat->timer == 0)
{
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP_END);
p_bat->substate = BAT_SUBSTATE_YOMP_END;
}
}
}
}
break;
case BAT_STATE_BALROG_ROAR:
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
case BAT_STATE_BALROG_FOLLOW:
case BAT_STATE_BALROG_CHARGE:
{
if (!p_bat->target)
{
//
// Emergency! How has this happened? Quick fix for now!
//
end = TRUE;
p_bat->timer = 0;
}
else
{
ASSERT(p_bat->target); //triggered
Thing *p_target = TO_THING(p_bat->target);
SLONG dangle;
//
// Turn towards where you are going.
//
extern SLONG there_is_a_los_mav(
SLONG x1, SLONG y1, SLONG z1,
SLONG x2, SLONG y2, SLONG z2);
if (!there_is_a_los_mav(
p_thing ->WorldPos.X >> 8,
p_thing ->WorldPos.Y + 0x4000 >> 8,
p_thing ->WorldPos.Z >> 8,
p_target->WorldPos.X >> 8,
p_target->WorldPos.Y + 0x4000 >> 8,
p_target->WorldPos.Z >> 8))
{
p_bat->want_x = p_target->WorldPos.X >> 8;
p_bat->want_z = p_target->WorldPos.Z >> 8;
}
else
{
MAV_Action ma = MAV_do(
p_thing ->WorldPos.X >> 16,
p_thing ->WorldPos.Z >> 16,
p_target->WorldPos.X >> 16,
p_target->WorldPos.Z >> 16,
MAV_CAPS_GOTO);
p_bat->want_x = (ma.dest_x << 8) + 0x80;
p_bat->want_z = (ma.dest_z << 8) + 0x80;
}
dangle = BAT_turn_to_place(
p_thing,
p_bat->want_x,
p_bat->want_z);
if (abs(dangle) > 256 || p_bat->substate == BAT_SUBSTATE_YOMP_END)
{
//
// Don't move!
//
p_bat->dx -= p_bat->dx >> 3;
p_bat->dz -= p_bat->dz >> 3;
}
else
{
//
// Accelerate to moving speed.
//
want_dx = -SIN(p_thing->Draw.Tweened->Angle) >> 4;
want_dz = -COS(p_thing->Draw.Tweened->Angle) >> 4;
want_dx += want_dx >> 1;
want_dz += want_dz >> 1;
want_dx -= want_dx >> 5;
want_dz -= want_dz >> 5;
p_bat->dx += (want_dx - p_bat->dx) >> 3;
p_bat->dz += (want_dz - p_bat->dz) >> 3;
}
end = BAT_animate(p_thing);
if (end)
{
if (p_bat->substate == BAT_SUBSTATE_YOMP_START)
{
end = FALSE;
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP);
p_bat->substate = BAT_SUBSTATE_YOMP_MIDDLE;
}
else
if (p_bat->substate == BAT_SUBSTATE_YOMP_MIDDLE)
{
end = FALSE;
if (p_bat->timer == 0)
{
BAT_set_anim(p_thing, BAT_ANIM_BALROG_YOMP_END);
p_bat->substate = BAT_SUBSTATE_YOMP_END;
}
}
}
}
}
break;
case BAT_STATE_BALROG_SWIPE:
if (p_bat->target && !(p_bat->flag & BAT_FLAG_ATTACKED) && p_thing->Draw.Tweened->FrameIndex > 4)
{
create_shockwave(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
0x200,
100,
p_thing);
p_bat->flag |= BAT_FLAG_ATTACKED;
}
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
case BAT_STATE_BALROG_STOMP:
if (p_bat->target && !(p_bat->flag & BAT_FLAG_ATTACKED) && p_thing->Draw.Tweened->FrameIndex > 12)
{
create_shockwave(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
0x300,
150,
p_thing);
p_bat->flag |= BAT_FLAG_ATTACKED;
PYRO_create(p_thing->WorldPos,PYRO_DUSTWAVE);
}
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
case BAT_STATE_BALROG_FIREBALL:
switch(p_bat->substate)
{
case BAT_SUBSTATE_FIREBALL_TURN:
end = BAT_animate(p_thing);
if (abs(BAT_turn_to_target(p_thing)) < 128)
{
if (end)
{
p_bat->substate = BAT_SUBSTATE_FIREBALL_FIRE;
BAT_set_anim(p_thing, BAT_ANIM_BALROG_SWIPE);
}
}
break;
case BAT_SUBSTATE_FIREBALL_FIRE:
if (p_bat->target && !(p_bat->flag & BAT_FLAG_ATTACKED) && p_thing->Draw.Tweened->FrameIndex > 4)
{
BAT_emit_fireball(p_thing);
p_bat->flag |= BAT_FLAG_ATTACKED;
}
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
default:
ASSERT(0);
break;
}
break;
case BAT_STATE_BALROG_IDLE:
end = BAT_animate(p_thing);
break;
case BAT_STATE_BANE_IDLE:
end = BAT_animate(p_thing);
//
// Make sparks connect to the summoning people.
//
BAT_process_bane_sparks(p_thing);
p_bat->glow += 64 * TICK_RATIO >> TICK_SHIFT;
if (p_bat->glow > 0xff00)
{
p_bat->glow = 0xff00;
}
break;
case BAT_STATE_BANE_ATTACK:
if (!(p_bat->flag & BAT_FLAG_ATTACKED) && p_thing->Draw.Tweened->FrameIndex > 12)
{
create_shockwave(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y >> 8,
p_thing->WorldPos.Z >> 8,
0x300,
150,
p_thing);
p_bat->flag |= BAT_FLAG_ATTACKED;
PYRO_create(p_thing->WorldPos,PYRO_DUSTWAVE);
{
Thing *darci = NET_PERSON(0);
extern SLONG is_person_dead(Thing *p_person);
if (!is_person_dead(darci))
{
if (THING_dist_between(p_thing, NET_PERSON(0)) < 0x600)
{
if (there_is_a_los(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Y + 0xc000 >> 8,
p_thing->WorldPos.Z >> 8,
darci->WorldPos.X >> 8,
darci->WorldPos.Y + 0x6000 >> 8,
darci->WorldPos.Z >> 8,
0))
{
SPARK_Pinfo p1;
SPARK_Pinfo p2;
p1.type = SPARK_TYPE_LIMB;
p1.flag = 0;
p1.person = THING_NUMBER(p_thing);
p1.limb = 0;
p2.type = SPARK_TYPE_LIMB;
p2.flag = 0;
p2.person = THING_NUMBER(darci);
p2.limb = SUB_OBJECT_HEAD;
SPARK_create(
&p1,
&p2,
50);
if (darci->State != STATE_DANGLING &&
darci->State != STATE_JUMPING)
{
set_face_thing(
darci,
p_thing);
}
darci->Genus.Person->Health -= 50;
if (darci->Genus.Person->Health <= 0)
{
set_person_dead(
darci,
NULL,
PERSON_DEATH_TYPE_OTHER,
FALSE,
0);
}
else
{
set_person_recoil(
darci,
ANIM_HIT_FRONT_MID,
0);
}
}
}
}
}
}
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
case BAT_STATE_BANE_START:
end = BAT_animate(p_thing);
p_bat->timer = 0;
break;
default:
ASSERT(0);
break;
}
newpos.X = p_thing->WorldPos.X + (p_bat->dx * TICK_RATIO >> TICK_SHIFT);
newpos.Y = p_thing->WorldPos.Y + (p_bat->dy * TICK_RATIO >> TICK_SHIFT);
newpos.Z = p_thing->WorldPos.Z + (p_bat->dz * TICK_RATIO >> TICK_SHIFT);
if (p_bat->type == BAT_TYPE_BALROG)
{
//
// The balrog collides with the MAVHEIGHT array.
//
BAT_balrog_slide_along(
p_thing->WorldPos.X,
p_thing->WorldPos.Z,
&newpos.X,
&newpos.Z);
//
// The balrog is always on the ground...
//
newpos.Y = PAP_calc_height_at(
p_thing->WorldPos.X >> 8,
p_thing->WorldPos.Z >> 8) + 0x40 << 8;
NIGHT_dlight_move(p_bat->glow, newpos.X>>8, (newpos.Y>>8)+64, newpos.Z>>8);
//
// The balrog has Big Fucking Feet
//
// TRACE("frame %d flags %d anim %d\n",p_thing->Draw.Tweened->FrameIndex,p_bat->flag,p_thing->Draw.Tweened->CurrentAnim);
/*
#define BAT_ANIM_BALROG_YOMP 2
#define BAT_ANIM_BALROG_YOMP_START 5
#define BAT_ANIM_BALROG_YOMP_END 6
*/
switch(p_thing->Draw.Tweened->CurrentAnim)
{
case BAT_ANIM_BALROG_YOMP:
if (
(((p_thing->Draw.Tweened->FrameIndex>=1)&&(p_thing->Draw.Tweened->FrameIndex<6))
||((p_thing->Draw.Tweened->FrameIndex>=11)&&(p_thing->Draw.Tweened->FrameIndex<16)))
&&!(p_bat->flag&BAT_FLAG_SYNC_FX))
{
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_STEP_START,S_BALROG_STEP_END),0,p_thing);
p_bat->flag&=~BAT_FLAG_SYNC_FX2;
p_bat->flag|=BAT_FLAG_SYNC_FX;
}
if ((p_thing->Draw.Tweened->FrameIndex>=6)&&(p_thing->Draw.Tweened->FrameIndex<=11)&&!(p_bat->flag&BAT_FLAG_SYNC_FX2))
{
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_STEP_START,S_BALROG_STEP_END),0,p_thing);
p_bat->flag&=~BAT_FLAG_SYNC_FX;
p_bat->flag|=BAT_FLAG_SYNC_FX2;
}
break;
case BAT_ANIM_BALROG_YOMP_END:
if (
(((p_thing->Draw.Tweened->FrameIndex>=1)&&(p_thing->Draw.Tweened->FrameIndex<5))
||((p_thing->Draw.Tweened->FrameIndex>=11)&&(p_thing->Draw.Tweened->FrameIndex<16)))
&&!(p_bat->flag&BAT_FLAG_SYNC_FX))
{
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_STEP_START,S_BALROG_STEP_END),0,p_thing);
p_bat->flag&=~BAT_FLAG_SYNC_FX2;
p_bat->flag|=BAT_FLAG_SYNC_FX;
}
if ((p_thing->Draw.Tweened->FrameIndex>=5)&&(p_thing->Draw.Tweened->FrameIndex<=11)&&!(p_bat->flag&BAT_FLAG_SYNC_FX2))
{
MFX_play_thing(THING_NUMBER(p_thing),SOUND_Range(S_BALROG_STEP_START,S_BALROG_STEP_END),0,p_thing);
p_bat->flag&=~BAT_FLAG_SYNC_FX;
p_bat->flag|=BAT_FLAG_SYNC_FX2;
}
break;
}
}
move_thing_on_map(p_thing, &newpos);
//
// Too far from home?
//
dx = p_thing->WorldPos.X - ((p_bat->home_x << 16) + 0x8000);
dz = p_thing->WorldPos.Z - ((p_bat->home_z << 16) + 0x8000);
if (abs(dx) > 0x50000 ||
abs(dz) > 0x50000)
{
//
// Too far from home...
//
p_bat->timer >>= 1;
}
if (p_bat->timer <= ticks)
{
p_bat->timer = 0;
if (end)
{
BAT_change_state(p_thing);
}
}
else
{
p_bat->timer -= ticks;
}
}
//
// Create a new bat.
//
THING_INDEX BAT_create(
SLONG type,
SLONG x,
SLONG z,
UWORD yaw)
{
SLONG i;
Thing *p_thing;
Bat *p_bat;
DrawTween *dt;
//
// Get a thing structure.
//
p_thing = alloc_thing(CLASS_BAT);
if (p_thing == NULL)
{
//
// No more things left!
//
return NULL;
}
//
// Get a bat structure.
//
for (i = 0; i < BAT_MAX_BATS; i++)
{
p_bat = TO_BAT(i);
if (p_bat->type == BAT_TYPE_UNUSED)
{
goto found_unused_bat;
}
}
//
// No more bat structures.
//
return NULL;
found_unused_bat:;
//
// Allocate a drawtween structure.
//
dt = alloc_draw_tween(DT_ROT_MULTI);
if (dt == NULL)
{
return NULL;
}
#ifndef PSX
#ifndef TARGET_DC
//
// Initialise the thing.
//
if(anim_chunk[type].MultiObject[0]==0)
{
extern SLONG load_anim_prim_object(SLONG prim);
load_anim_prim_object(type);
// ASSERT(0);
}
#else
ASSERT(anim_chunk[type].MultiObject[0]!=0);
#endif
#endif
p_thing->WorldPos.X = x << 8;
p_thing->WorldPos.Z = z << 8;
p_thing->WorldPos.Y = PAP_calc_map_height_at(x,z) << 8;
p_thing->DrawType = DT_ANIM_PRIM;
p_thing->Draw.Tweened = dt;
p_thing->Genus.Bat = p_bat;
p_thing->State = STATE_NORMAL;
p_thing->SubState = NULL;
p_thing->StateFn = BAT_normal;
p_thing->Index = type;
add_thing_to_map(p_thing);
//
// Initialise the draw tween (anim prim <type>, animation 1)
//
dt->Angle = yaw;
dt->Roll = 0;
dt->Tilt = 0;
dt->AnimTween = 0;
dt->TweenStage = 0;
dt->NextFrame = NULL;
dt->QueuedFrame = NULL;
dt->TheChunk = &anim_chunk[type];
dt->CurrentFrame = anim_chunk[type].AnimList[1];
dt->NextFrame = dt->CurrentFrame->NextFrame;
dt->CurrentAnim = 1;
dt->FrameIndex = 0;
dt->Flags = 0;
//
// Initialise the bat.
//
p_bat->type = type;
p_bat->dx = 0;
p_bat->dy = 0;
p_bat->dz = 0;
p_bat->health = 100;
p_bat->home_x = x >> 8;
p_bat->home_z = z >> 8;
p_bat->target = NULL;
p_bat->timer = 0;
p_bat->flag = 0;
switch(type)
{
#ifndef PSX
case BAT_TYPE_BAT:
p_bat->state = BAT_STATE_IDLE;
p_bat->substate = BAT_SUBSTATE_NONE;
p_thing->WorldPos.Y += 0x100 << 8;
break;
case BAT_TYPE_GARGOYLE:
p_bat->state = BAT_STATE_GROUND;
p_bat->substate = BAT_SUBSTATE_GROUND_WAIT;
break;
#endif
case BAT_TYPE_BALROG:
p_bat->state = BAT_STATE_BALROG_ROAR;
p_bat->substate = BAT_SUBSTATE_NONE;
p_bat->health = 255;
BAT_set_anim(p_thing, BAT_ANIM_BALROG_ROAR);
// let's set this mutha alight
{
Thing *pyro;
pyro=PYRO_create(p_thing->WorldPos,PYRO_IMMOLATE);
if (pyro)
{
pyro->Genus.Pyro->victim=p_thing;
pyro->Genus.Pyro->Flags=PYRO_FLAGS_FLICKER;
}
//darci->Genus.Person->BurnIndex=PYRO_NUMBER(pyro->Genus.Pyro)+1;
}
// and cast some light nearby...
p_bat->glow=NIGHT_dlight_create( p_thing->WorldPos.X>>8, p_thing->WorldPos.Y>>8, p_thing->WorldPos.Z>>8, 200, 32, 28, 10);
break;
case BAT_TYPE_BANE:
p_bat->state = BAT_STATE_BANE_IDLE;
p_bat->substate = BAT_SUBSTATE_NONE;
p_bat->glow = 0x7f00;
p_thing->WorldPos.Y += 0x60 << 8;
BAT_set_anim(p_thing, BAT_ANIM_BANE_IDLE);
break;
default:
ASSERT(0);
break;
}
return THING_NUMBER(p_thing);
}
void BAT_apply_hit(
Thing *p_me,
Thing *p_aggressor,
SLONG damage)
{
SLONG dx;
SLONG dy;
SLONG dz;
SLONG dist;
ASSERT(p_me->Class == CLASS_BAT);
if (p_me->Genus.Bat->type == BAT_TYPE_BANE)
{
return;
}
ASSERT(damage>=0);
if (p_aggressor)
{
SLONG blood_x;
SLONG blood_y;
SLONG blood_z;
SLONG towards;
blood_x = p_me->WorldPos.X;
blood_y = p_me->WorldPos.Y;
blood_z = p_me->WorldPos.Z;
switch(p_me->Genus.Bat->type)
{
case BAT_TYPE_BAT: blood_y += 0x2000; towards = 0x40; break;
case BAT_TYPE_GARGOYLE: blood_y += 0x5000; towards = 0x60; break;
case BAT_TYPE_BALROG: blood_y += 0x8000; towards = 0x90; break;
default:
ASSERT(0);
break;
}
dx = p_aggressor->WorldPos.X - p_me->WorldPos.X;
dz = p_aggressor->WorldPos.Z - p_me->WorldPos.Z;
dx >>= 8;
dz >>= 8;
SLONG length;
length = QDIST2(abs(dx),abs(dz)) + 1;
dx = dx * towards / length;
dz = dz * towards / length;
blood_x += dx << 8;
blood_z += dz << 8;
//#ifndef VERSION_GERMAN
if(VIOLENCE)
PARTICLE_Add(
blood_x,
blood_y,
blood_z,
0,0,0,
POLY_PAGE_SMOKECLOUD2,2+((Random()&3)<<2),0x7FFF0000,
PFLAG_SPRITEANI|PFLAG_SPRITELOOP|PFLAG_FADE,10,75,1,20,5);
//#endif
if (p_me->Genus.Bat->type != BAT_TYPE_BALROG)
{
dx = p_aggressor->WorldPos.X - p_me->WorldPos.X;
dy = p_aggressor->WorldPos.Y - p_me->WorldPos.Y;
dz = p_aggressor->WorldPos.Z - p_me->WorldPos.Z;
dist = QDIST3(abs(dx),abs(dy),abs(dz)) + 1;
dx = (dx << 8) / dist;
dy = (dy << 8) / dist;
dz = (dz << 8) / dist;
//
// Knocked back by the hit.
//
p_me->Genus.Bat->dx -= dx << 4;
p_me->Genus.Bat->dy -= dy << 4;
p_me->Genus.Bat->dz -= dz << 4;
}
}
//
// Bats are nails!
//
damage >>= 1;
if (p_me->Genus.Bat->type == BAT_TYPE_BALROG)
{
damage >>= 3;
}
damage += 1;
if (p_me->Genus.Bat->health <= damage)
{
{
//
// Kill the bat.
//
p_me->Genus.Bat->health = 0;
p_me->Genus.Bat->state = BAT_STATE_DYING;
if (p_me->Genus.Bat->type == BAT_TYPE_GARGOYLE)
{
BAT_set_anim(p_me, BAT_ANIM_GARGOYLE_START_FALL);
p_me->Genus.Bat->substate = BAT_SUBSTATE_DEAD_INITIAL;
p_me->State = STATE_DEAD;
}
else
if (p_me->Genus.Bat->type == BAT_TYPE_BALROG)
{
p_me->Genus.Bat->substate = BAT_SUBSTATE_DEAD_FINAL;
p_me->Genus.Bat->dx = 0;
p_me->Genus.Bat->dy = 0;
p_me->Genus.Bat->dz = 0;
BAT_set_anim(p_me, BAT_ANIM_BALROG_DIE);
MFX_play_thing(THING_NUMBER(p_me),S_BALROG_DEATH,0,p_me);
}
else
{
p_me->Genus.Bat->substate = BAT_SUBSTATE_DEAD_LOOP;
BAT_set_anim(p_me, BAT_ANIM_BAT_DIE);
p_me->State = STATE_DEAD;
}
}
}
else
{
p_me->Genus.Bat->health -= damage;
if (p_me->Genus.Bat->type == BAT_TYPE_BALROG)
{
//
// The balrog makes you his target!
//
if (p_aggressor && p_aggressor->Class == CLASS_PERSON)
{
p_me->Genus.Bat->target = THING_NUMBER(p_aggressor);
//
// Change state to attack!
//
if (p_me->Genus.Bat->state == BAT_STATE_BALROG_IDLE ||
p_me->Genus.Bat->state == BAT_STATE_BALROG_WANDER)
{
p_me->Genus.Bat->timer = 0;
}
}
}
else
{
//
// React to being shot!
//
BAT_set_anim(p_me, BAT_ANIM_GENERIC_TAKE_HIT);
p_me->Genus.Bat->state = BAT_STATE_RECOIL;
}
}
}