Okay, think about ALL of the code stored in all of the executables and dlls of counter strike, half life, and all of the 'half life' modifications...the stuff that is the same for each one is the engine. The engine is what does all of the house work. All an 'engine' knows how to do is load data from a file, perform collision detections, render stuff, take care of windows and gather input, stuff like that. It is technically incorrect to say 'counter strike is a half life mod', because like you mentioned, half life uses the glquake game engine. It's probably totally modified anyway though, so you can get away with saying half life is its own engine. An 'engine' doesn't think about good guys or bad guys or monsters and stuff like that, it is just concerned with gathering data and setting stuff up so that a game can be played. The actual monsters you create goes into a different program that uses the game engine. That's the game itself, and it is the game code that thinks about good guys and bad guy sand monsters and stuff like that.
EDIT:
I included a screenshot of how my engine is setup. You will distinctly notice that in the same workspace I have a section called 'engine', which expands into many other folders, then I have a section called 'game' which contains the folders and code for the game project I am finally coding. In reality I should be compiling these in two different workspaces, with the engine crap compiled into dlls, but this is easier. you will also notice a folder called 'export'. This is essentially the interface to the game engine itself. Every single object in the world is a 'worldobject' (except for lights, which are thought about slightly differently by the engine), and every single worldobject can access the main game engine whenever it wants to do something.
EDIT1:
In order for a 'worldobject' to be interfaces with the engine, it overrides some virtual functions that supply its functionality (so that interactive stuff happens). The 3 basic components that I found I needed were:
a TouchFunction
an Update function
a BeginPhysics function
and an ActonPhysics function
The TouchFunction determines how the object behaves when another object bumps into it. Doors Open, and Elevators raise upwards.
Update just gathers input and calculates an ideal velocity
Ideally BeginPhysics tries to move the object, and ideally ActOnPhysics respondes to the collision by modifying the velocity and entering the collision response loop, but im finding I do everything in BeginPhysics.
Here's the complete file for Bert. Update just determines where the player is, and calculates the pitch and yaw so that bert faces the player. The begin physics tries to move towards the player. Everytime you see something like "Export.something" bert is accessing the main game engine.
Code:
#include "Bert.h"
#include "GAMEEXPORT.H"
#include "Render.h"
#include "GLext.h"
#include "Matrix.h"
#include <windows.h>
#include "UNITS.h"
extern GameExport Export;
extern HWND hWnd;
extern PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
extern PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB;
extern PFNGLGENOCCLUSIONQUERIESNVPROC glGenOcclusionQueriesNV ;
extern PFNGLDELETEOCCLUSIONQUERIESNVPROC glDeleteOcclusionQueriesNV;
extern PFNGLISOCCLUSIONQUERYNVPROC glIsOcclusionQueryNV;
extern PFNGLBEGINOCCLUSIONQUERYNVPROC glBeginOcclusionQueryNV ;
extern PFNGLENDOCCLUSIONQUERYNVPROC glEndOcclusionQueryNV ;
extern PFNGLGETOCCLUSIONQUERYIVNVPROC glGetOcclusionQueryivNV ;
extern PFNGLGETOCCLUSIONQUERYUIVNVPROC glGetOcclusionQueryuivNV;
Bert::~Bert()
{
}
void Bert::Attack()
{
}
/*
*/
void Bert::VWOTouchFunc()
{
}
void Bert::VWOUpdate()
{
Vector3 to = Export.mpPlayer->mPosition - mPosition;
to.y = 0;
to.Normalize();
Vector3 toplayer(Export.mpPlayer->mPosition.x-mPosition.x,0,Export.mpPlayer->mPosition.z-mPosition.z);
mYaw = VecToYaw(&toplayer,&Export.mpPlayer->mpCam->mZAxis);
if(Export.mpPlayer->mPosition.x < mPosition.x)
mYaw = -mYaw;
Vector3 dirdir = Vector3(mVelocity.Dir.x,0,mVelocity.Dir.z);
dirdir.Normalize();
if(DotProduct(&dirdir,&to)<0)
{
mVelocity.Dir *= 1-(2*Export.mFPS.TimeFrac); //apply break
}
if(GetKeyState('Q')&0x80)
{
mVelocity.Dir.x += to.x * FOOT(15)*Export.mFPS.TimeFrac;
mVelocity.Dir.z += to.z * FOOT(15)*Export.mFPS.TimeFrac;
}
else
{
mVelocity.Dir.x += to.x * FOOT(5)*Export.mFPS.TimeFrac;
mVelocity.Dir.z += to.z * FOOT(5)*Export.mFPS.TimeFrac;
}
}
void Bert::VWORenderModel()
{
RenderRedBall(mPosition+mMins);
mpModel->Draw(&mPosition,mPitch,mYaw,0);
}
int Bert::VWOFrustumTest()
{
return Export.mFrustum.BBInFrustum(mPosition+mMins,mPosition+mMaxs);
}
void Bert::VWOBeginPhysics()
{
mVelocity.Dir.y -= FOOT(32) * Export.mFPS.TimeFrac;
}
void Bert::VWOActOnPhysics()
{
Velocity to;
int tries = 5;
Vector3 original = to.Dir;
while(tries--)
{
to.Dir = mVelocity.Dir;
float IncomingMVELLength = to.Dir.GetLength();
to.Mag = IncomingMVELLength*Export.mFPS.TimeFrac; //base it on time
if(IncomingMVELLength > .0001)
{
to.Dir *= (1/(IncomingMVELLength)); //Normalize
}
else
{
WOReLink();
WOMakeBrush();
return; //Not enough to even bother tracing
}
Export.mpBSP->TraceRay(this,&mPosition, &to,&mMins,&mMaxs,HITWORLD|HITENTS|RUNTOUCH);
if(Export.mpBSP->mBSPColInfo.closestFraction == 1.0f)
{
mPosition = Export.mpBSP->mBSPColInfo.mIntersect;
WOReLink();
WOMakeBrush();
return;
}
else
{
if(Export.mpBSP->mBSPColInfo.mpClosestEnt)
{
Export.mPhysics.ApplyImpulse(this,Export.mpBSP->mBSPColInfo.mpClosestEnt);
}
else
{
WOClipVelocity(&Export.mpBSP->mBSPColInfo.hitNormal,0);
}
}
}
}