/* ================================================================ GOLIATH ================================================================ Copyright (C) 1998 by 2015, Inc. All rights reserved. This source is may not be distributed and/or modified without expressly written permission by 2015, Inc. */ #include "goliath.h" #include "object.h" #include "player.h" #define GOLIATH_MAX_OBJECTS 20 //=============================================================== // GOLIATH //=============================================================== Event EV_Goliath_InitThrowTime("throwtime"); Event EV_Goliath_MeleeForce( "meleeforce" ); Event EV_Goliath_SetLeftHand("setlefthand"); Event EV_Goliath_SetRightHand("setrighthand"); Event EV_Goliath_SetBothHands("setbothhands"); Event EV_Goliath_SetAttackStage("attackstage"); Event EV_Goliath_IfThrowHigh("ifthrowhigh"); Event EV_Goliath_IfThrowLow("ifthrowlow"); CLASS_DECLARATION(Actor, Goliath, "monster_goliath"); ResponseDef Goliath::Responses[] = { {&EV_Goliath_InitThrowTime, (Response)Goliath::InitThrowTime}, {&EV_Goliath_MeleeForce, (Response)Goliath::SetMeleeForce}, {&EV_Goliath_SetLeftHand, (Response)Goliath::SetLeftHand}, {&EV_Goliath_SetRightHand, (Response)Goliath::SetRightHand}, {&EV_Goliath_SetBothHands, (Response)Goliath::SetBothHands}, {&EV_Goliath_SetAttackStage, (Response)Goliath::SetAttackStage}, {&EV_Goliath_IfThrowHigh, (Response)Goliath::IfThrowHighEvent}, {&EV_Goliath_IfThrowLow, (Response)Goliath::IfThrowLowEvent}, {NULL, NULL} }; Goliath::Goliath() { randomthrowtime = 0; rubbletime = 0; throwhigh = false; leftdamage = 0; leftforce = 0; rightdamage = 0; rightforce = 0; hitsentient = false; if(attackstage < 1 || attackstage > 2) { attackstage = 1; } if ( actorthread ) { actorthread->Vars()->SetVariable( "attackstage", attackstage ); } flags |= FL_POSTTHINK; } void Goliath::Prethink(void) { trace_t trace; Vector tmpvec; qboolean nowthrowing = false; Entity *ent; if(attackstage == 2) { if(randomthrowtime && (randomthrowtime < level.time)) { // we want to throw something... Entity *bestent; float bestdist; float dist; float distance; Vector delta; distance = 300; bestent = NULL; bestdist = distance * distance; ent = NULL; while(ent = findradius(ent, worldorigin.vec3(), distance)) { if(ent->isSubclassOf(ThrowObject)) { delta = centroid - ent->centroid; dist = delta * delta; if(dist <= bestdist) { bestent = ent; bestdist = dist; } } } if(bestent) { bestent = CheckObjectsAbove(bestent); SetVariable("other", bestent); if(DoAction("throwthing", false)) { randomthrowtime = 0; nowthrowing = true; // easy his rumbling tendancies a bit if(rubbletime) rubbletime += 4 + G_Random(2) - skill->value; } } } // rumble timmer for when he goes to long wanting to // throw something without anything around if(randomthrowtime && !nowthrowing) { if(!rubbletime) { rubbletime = level.time + 5 + G_Random(2) - (skill->value * 1.5); } else if(rubbletime < level.time) { ScriptVariable *var; int numobjects; // check max objects at one time limit var = levelVars.GetVariable("goliathobject_count"); if(var) numobjects = var->intValue(); else numobjects = 0; if(numobjects < GOLIATH_MAX_OBJECTS) { // time to make more stuff to throw >) if(DoAction("rumbleattack", false)) { randomthrowtime = 0; nowthrowing = true; // ah, feel better now rubbletime = 0; } } else { // ease off his rubble making tendacies for a bit rubbletime = level.time + 1 + G_Random(2); } } } } // check for having a hissy hit and going into stage two else { if(health < (max_health*0.5)) { if(DoAction("pissedoff", false)) { // yup, he's pissed attackstage = 2; nowthrowing = true; // make sure rubble attack timmer is inited rubbletime = 0; } } // stage one throwing occures less often then stage two throwing if(!nowthrowing && randomthrowtime && ((randomthrowtime + 1) < level.time)) { // we want to throw something... Entity *bestent; float bestdist; float dist; float distance; Vector delta; distance = 320; bestent = NULL; bestdist = distance * distance; ent = NULL; while(ent = findradius(ent, worldorigin.vec3(), distance)) { if(ent->isSubclassOf(ThrowObject)) { delta = centroid - ent->centroid; dist = delta * delta; if(dist <= bestdist) { bestent = ent; bestdist = dist; } } } if(bestent) { bestent = CheckObjectsAbove(bestent); if(bestent) { SetVariable("other", bestent); if(DoAction("throwthing", false)) { randomthrowtime = 0; nowthrowing = true; } } } } } if(!nowthrowing && randomthrowtime) { // if there's ever a throwable thing in our // way, then get it out of our way. tmpvec = worldorigin + move; trace = G_Trace(worldorigin, mins, maxs, tmpvec, this, edict->clipmask, "Goliath::Prethink"); if((trace.fraction != 1) && (trace.ent)) { // check if we ran into something we can throw ent = trace.ent->entity; if(ent->isSubclassOf(ThrowObject)) { ent = CheckObjectsAbove(ent); if(ent) { SetVariable("other", ent); // if in attackstage 2, there's a chance we'll just smash it if((attackstage == 2) && (G_Random() < 0.6)) { DoAction("destroyobstruction", false); } else { DoAction("throwobstruction", false); } } } } } // // call our superclass // Actor::Prethink(); } #define HAND_SIZE 40 // here's where the hand collision detection is done void Goliath::Postthink(void) { vec3_t trans[3]; vec3_t orient; int groupindex; int tri_num; Vector pos, forward; Vector offset; Vector tmpvec; Vector handmins, handmaxs; Vector min, max; float handdiff; int num; edict_t *touch[MAX_EDICTS]; handmins = Vector(-HAND_SIZE, -HAND_SIZE, -HAND_SIZE); handmaxs = Vector(HAND_SIZE, HAND_SIZE, HAND_SIZE); if(rightdamage || rightforce) { if(!gi.GetBoneInfo(edict->s.modelindex, "gun", &groupindex, &tri_num, orient)) { // couldn't get proper bone info gi.dprintf("Couldn't find Goliath's gun bone\n"); return; } AngleVectors(orient, forward.vec3(), NULL, NULL); // get hand position offset = vec_zero; gi.GetBoneTransform(edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame, edict->s.scale, trans, offset.vec3()); MatrixTransformVector(offset.vec3(), orientation, pos.vec3()); pos += worldorigin; // check at an interpolated position if hand moved enough from last frame if(lastrightpos != vec_zero) { tmpvec = lastrightpos - pos; handdiff = tmpvec.length(); if(handdiff > (HAND_SIZE*3)) { tmpvec = (lastrightpos + pos)*0.5; min = tmpvec + handmins; max = tmpvec + handmaxs; num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS); if(num) DoHandHits(tmpvec, forward, rightdamage, rightforce, touch, num); num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID); if(num) DoHandHits(tmpvec, forward, rightdamage, rightforce, touch, num); } } // check current hand position min = pos + handmins; max = pos + handmaxs; num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS); if(num) DoHandHits(pos, forward, rightdamage, rightforce, touch, num); num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID); if(num) DoHandHits(pos, forward, rightdamage, rightforce, touch, num); // save current hand position lastrightpos = pos; } else { // clear out last hand position lastrightpos = vec_zero; } if(leftdamage || leftforce) { if(!gi.GetBoneInfo(edict->s.modelindex, "leftgun", &groupindex, &tri_num, orient)) { // couldn't get proper bone info gi.dprintf("Couldn't find Goliath's leftgun bone\n"); return; } AngleVectors(orient, forward.vec3(), NULL, NULL); // get hand position offset = vec_zero; gi.GetBoneTransform(edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame, edict->s.scale, trans, offset.vec3()); MatrixTransformVector(offset.vec3(), orientation, pos.vec3()); pos += worldorigin; // check at an interpolated position if hand moved enough from last frame if(lastleftpos != vec_zero) { tmpvec = lastleftpos - pos; handdiff = tmpvec.length(); if(handdiff > (HAND_SIZE*3)) { tmpvec = (lastleftpos + pos)*0.5; min = tmpvec + handmins; max = tmpvec + handmaxs; num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS); if(num) DoHandHits(tmpvec, forward, leftdamage, leftforce, touch, num); num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID); if(num) DoHandHits(tmpvec, forward, leftdamage, leftforce, touch, num); } } // check current hand position min = pos + handmins; max = pos + handmaxs; num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_TRIGGERS); if(num) DoHandHits(pos, forward, leftdamage, leftforce, touch, num); num = gi.BoxEdicts(min.vec3(), max.vec3(), touch, MAX_EDICTS, AREA_SOLID); if(num) DoHandHits(pos, forward, leftdamage, leftforce, touch, num); // save current hand position lastleftpos = pos; } else { // clear out last hand position lastleftpos = vec_zero; } } void Goliath::InitThrowTime(Event *ev) { float mintime, maxtime, timediff; // if fewer than 2 numbers are given, clear timmer if(ev->NumArgs() < 2) { randomthrowtime = 0; return; } mintime = ev->GetFloat(1); maxtime = ev->GetFloat(2); timediff = maxtime - mintime; randomthrowtime = level.time + mintime + G_Random(timediff); randomthrowtime = level.time + G_Random(timediff); } Entity *Goliath::CheckObjectsAbove(Entity *ent) { Vector tmpvec, tmpvec2; trace_t trace; int mask, i; Entity *newent; float tmpflt; // make sure we're passed a good thing if(!ent) return NULL; if((ent->health <= 0) || (!ent->isSubclassOf(ThrowObject))) return NULL; // check for a better thing on the thing mask = MASK_PROJECTILE; // first try a full size trace tmpvec = ent->worldorigin + Vector(0, 0, 16); trace = G_Trace(ent->worldorigin, ent->mins, ent->maxs, tmpvec, ent, mask, "Goliath::CheckObjectsAbove"); // check for something good if(trace.ent) { newent = trace.ent->entity; // check for a stackable object that's above us if((newent->spawnflags & 8) && (newent->worldorigin.z > ent->worldorigin.z)) { throwhigh = true; return newent; } } // try several different spots for(i = 0; i < 13; i++) { tmpvec = ent->worldorigin; switch(i) { // corners case 1: tmpvec.x += ent->mins.x + 8; tmpvec.y += ent->mins.y + 8; break; case 2: tmpvec.x += ent->mins.x + 8; tmpvec.y += ent->maxs.y - 8; break; case 3: tmpvec.x += ent->maxs.x - 8; tmpvec.y += ent->mins.y + 8; break; case 4: tmpvec.x += ent->maxs.x - 8; tmpvec.y += ent->maxs.y - 8; break; // edge middles case 5: tmpvec.x += ent->mins.x + 8; break; case 6: tmpvec.x += ent->maxs.x - 8; break; case 7: tmpvec.y += ent->mins.y + 8; break; case 8: tmpvec.y += ent->maxs.y - 8; break; } tmpvec2 = tmpvec + Vector(0, 0, 16); trace = G_Trace(tmpvec, Vector(-4, -4, 0), Vector(4, 4, ent->maxs.z - 1), tmpvec2, ent, mask, "Goliath::CheckObjectsAbove"); // check for something good if(trace.ent) { newent = trace.ent->entity; // check for a stackable object that's above us if(newent->worldorigin.z > ent->worldorigin.z) { throwhigh = true; return newent; } } } // couldn't find anything, so stick with that we've got // check our object's pichup height tmpflt = ent->worldorigin.z - worldorigin.z; if(tmpflt > 32) throwhigh = true; else throwhigh = false; return ent; } //=============================================================== // melee attack stuff void Goliath::SetMeleeForce (Event *ev) { melee_force = ev->GetFloat(1); melee_force *= edict->s.scale; } void Goliath::SetLeftHand(Event *ev) { leftdamage = ev->GetFloat(1); leftforce = ev->GetFloat(2); } void Goliath::SetRightHand(Event *ev) { rightdamage = ev->GetFloat(1); rightforce = ev->GetFloat(2); } void Goliath::SetBothHands(Event *ev) { rightdamage = ev->GetFloat(1); rightforce = ev->GetFloat(2); leftdamage = rightdamage; leftforce = rightforce; } void Goliath::DoHandHits(Vector pos, Vector dir, float handdamage, float handforce, edict_t *touch[MAX_EDICTS], int num) { edict_t *hit; Entity *ent; int i; float damage; // horizontalize the direction dir.z = 0; dir.normalize(); for(i = 0; i < num; i++) { hit = touch[i]; if(!hit->inuse) continue; assert(hit->entity); ent = hit->entity; if(ent == this) continue; if(ent->getSolidType() == SOLID_BSP) continue; // so goliaths don't hurt each other if(ent->isSubclassOf(Goliath)) continue; if(ent->isSubclassOf(Sentient) && hitsentient) continue; if(ent->takedamage) { if(ent->isSubclassOf(Sentient)) { damage = melee_damage*handdamage; hitsentient = true; } else { damage = ent->max_health * 10; // make a hit sound ent->RandomGlobalSound("impact_smallexplosion", 0.5, CHAN_BODY); } ent->Damage( this, this, (int)damage, pos, vec_zero, vec_zero, 0, DAMAGE_NO_KNOCKBACK, MOD_MUTANTHANDS, -1, -1, 1.0f ); ent->velocity -= dir * (melee_force * handforce); if(ent->velocity.z < 50) ent->velocity.z = 50; ent->groundentity = NULL; } } } //=============================================================== void Goliath::SetAttackStage(Event *ev) { attackstage = ev->GetInteger(1); if(attackstage < 1) attackstage = 1; else if(attackstage > 2) attackstage = 2; if ( actorthread ) { actorthread->Vars()->SetVariable( "attackstage", attackstage ); } } void Goliath::IfThrowHighEvent(Event *ev) { ScriptThread *thread; thread = ev->GetThread(); assert(thread); if(!thread) { return; } if(throwhigh) { thread->ProcessCommandFromEvent(ev, 1); } } void Goliath::IfThrowLowEvent(Event *ev) { ScriptThread *thread; thread = ev->GetThread(); assert(thread); if(!thread) { return; } if(!throwhigh) { thread->ProcessCommandFromEvent(ev, 1); } } //=============================================================== /**************************************************************************** RumbleAttack Class Definition Goliath does an attack that causes the player to get jittered around and also causes rubble to fall from the ceiling ****************************************************************************/ CLASS_DECLARATION( Behavior, RumbleAttack, NULL ); Event EV_RumbleAttack_DoRumble("dorumble"); ResponseDef RumbleAttack::Responses[] = { {&EV_Behavior_Args, (Response)RumbleAttack::SetArgs}, {&EV_Behavior_AnimDone, (Response)RumbleAttack::AnimDone}, {&EV_RumbleAttack_DoRumble,(Response)RumbleAttack::DoRumble}, {NULL, NULL} }; void RumbleAttack::ShowInfo (Actor &self) { Behavior::ShowInfo(self); gi.printf("\nanim: %s\n", anim.c_str()); gi.printf("animdone: %d\n", animdone); gi.printf("\nrubbletarget: %s\n", rubbletarget.c_str()); gi.printf("rubblenums: %d\n", rubblenums); } void RumbleAttack::Begin (Actor &self) { if(!anim.length()) { anim = "rumble"; } animdone = false; // start off the rumbling animation if(self.HasAnim(anim.c_str())) { self.SetAnim(anim.c_str(), EV_Actor_NotifyBehavior); } else { // no animation, so don't do it self.SetAnim("idle"); animdone = true; } } void RumbleAttack::SetArgs (Event *ev) { anim = ev->GetString(2); rubbletarget = ev->GetString(3); } void RumbleAttack::AnimDone (Event *ev) { animdone = true; } qboolean RumbleAttack::Evaluate (Actor &self) { // when the animation is finished, the behaivior is finished if(animdone) return false; return true; } void RumbleAttack::End (Actor &self) { self.SetAnim("idle"); } void RumbleAttack::DoRumble(Event *ev) { str targ; int num, i; Entity *ent; Player *player; Event *event; targ = rubbletarget; if(targ.length()) { num = 0; do { num = G_FindTarget(num, targ.c_str()); if(!num) { break; } ent = G_GetEntity(num); event = new Event(EV_Activate); event->AddEntity(ev->GetEntity(1)); ent->PostEvent(event, 0); } while(1); } // jitter the player around for(i = 1; i <= maxclients->value; i++) { if(!g_edicts[i].inuse || !g_edicts[i].entity) { continue; } player = (Player *)(g_edicts[i].entity); player->SetAngleJitter(12, 5, 1); player->SetOffsetJitter(10, 2.5, 1); } } /**************************************************************************** GoliathMelee Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, GoliathMelee, NULL ); ResponseDef GoliathMelee::Responses[] = { {&EV_Behavior_AnimDone, (Response)GoliathMelee::AnimDone}, {NULL, NULL} }; GoliathMelee::GoliathMelee() { } void GoliathMelee::ShowInfo (Actor &self) { Behavior::ShowInfo( self ); gi.printf("\naim:\n"); turnto.ShowInfo(self); gi.printf( "animdone: %d\n", animdone ); } void GoliathMelee::Begin (Actor &self) { mode = 0; animdone = false; // clear Goliaths hitsentient value ((Goliath *)&self)->hitsentient = false; } void GoliathMelee::SetArgs (Event *ev) { } void GoliathMelee::AnimDone (Event *ev) { animdone = true; } qboolean GoliathMelee::Evaluate (Actor &self) { if(animdone) { return false; } if(!self.has_melee || !self.currentEnemy) { return false; } if(mode == 0) { float r; Vector delta; delta = self.currentEnemy->centroid - self.centroid; r = delta.length(); if(r > self.melee_range) { return false; } r = delta.toYaw(); turnto.SetDirection(r); turnto.SetTolerance(10); if(turnto.Evaluate(self)) { return true; } // melee self.SetAnim("melee", EV_Actor_NotifyBehavior); self.Chatter("snd_attacktaunt", 5); mode = 1; } return true; } void GoliathMelee::End (Actor &self) { turnto.End(self); self.SetAnim("idle"); } /**************************************************************************** Goliath Pickup Behavior Class Definition ****************************************************************************/ CLASS_DECLARATION( Behavior, GoliathPickupAndThrow, NULL ); extern Event EV_PickupAndThrow_Pickup; extern Event EV_PickupAndThrow_Throw; ResponseDef GoliathPickupAndThrow::Responses[] = { {&EV_Behavior_Args, (Response)GoliathPickupAndThrow::SetArgs}, {&EV_Behavior_AnimDone, (Response)GoliathPickupAndThrow::AnimDone}, {&EV_PickupAndThrow_Pickup, (Response)GoliathPickupAndThrow::Pickup}, {&EV_PickupAndThrow_Throw, (Response)GoliathPickupAndThrow::Throw}, {NULL, NULL} }; GoliathPickupAndThrow::GoliathPickupAndThrow() { } void GoliathPickupAndThrow::ShowInfo (Actor &self) { Behavior::ShowInfo( self ); gi.printf( "\naim:\n" ); aim.ShowInfo( self ); gi.printf( "\nmode: %d\n", mode ); gi.printf( "\nanim: %s\n", anim.c_str() ); gi.printf( "animdone: %d\n", animdone ); if ( pickup_target ) { gi.printf( "\npickup_target: #%d '%s'\n", pickup_target->entnum, pickup_target->targetname.c_str() ); } else { gi.printf( "\npickup_target: NULL\n" ); } } void GoliathPickupAndThrow::Begin (Actor &self) { mode = 0; animdone = false; if(!anim.length()) { anim = "pickup"; } // turn towards the object if(pickup_target) { turnto.SetTarget(pickup_target); turnto.SetTolerance(20); turnto.Begin(self); } } void GoliathPickupAndThrow::SetArgs (Event *ev) { pickup_target = ev->GetEntity( 2 ); if(ev->NumArgs() > 2) { anim = ev->GetString(3); } } void GoliathPickupAndThrow::AnimDone (Event *ev) { animdone = true; } void GoliathPickupAndThrow::Pickup (Event *ev) { Entity * ent; Event * e; ent = ev->GetEntity( 1 ); if ( pickup_target ) { e = new Event( EV_ThrowObject_Pickup ); e->AddEntity( ent ); e->AddString( ev->GetString( 2 ) ); pickup_target->ProcessEvent( e ); turnto.End(*((Actor *)ent)); } } void GoliathPickupAndThrow::Throw (Event *ev) { Actor * act; Event * e; act = (Actor *)ev->GetEntity( 1 ); if ( pickup_target ) { if ( !act->currentEnemy ) return; e = new Event( EV_ThrowObject_Throw ); e->AddEntity( act ); e->AddFloat( 800 ); e->AddEntity( act->currentEnemy ); e->AddFloat( 1 ); pickup_target->ProcessEvent( e ); } } qboolean GoliathPickupAndThrow::Evaluate (Actor &self) { Event * ev; if ( !self.currentEnemy || !pickup_target ) return false; switch( mode ) { case 0 : if ( self.HasAnim( anim.c_str() ) ) { animdone = false; self.SetAnim( anim.c_str(), EV_Actor_NotifyBehavior ); mode = 1; } else { // skip the pickup animation ev = new Event( EV_PickupAndThrow_Pickup ); ev->AddEntity( &self ); ev->AddString( "gun" ); ProcessEvent( ev ); animdone = true; } case 1 : if ( !animdone ) break; aim.Begin( self ); mode = 2; if ( self.HasAnim( "throw_aim" ) ) { self.SetAnim( "throw_aim", EV_Actor_NotifyBehavior ); } else { self.SetAnim( "idle" ); } case 2 : // aim towards our target aim.SetTarget( self.currentEnemy ); if ( aim.Evaluate( self ) ) { break; } mode = 3; case 3 : // Throwing mode = 4; if ( self.HasAnim( "throw" ) ) { animdone = false; self.SetAnim( "throw", EV_Actor_NotifyBehavior ); mode = 4; } else { // skip the pickup animation ev = new Event( EV_PickupAndThrow_Throw ); ev->AddEntity( &self ); ProcessEvent( ev ); animdone = true; } break; case 4 : if ( !animdone ) break; return false; break; } return true; } void GoliathPickupAndThrow::End (Actor &self) { aim.End( self ); turnto.End(self); self.SetAnim( "idle" ); }