This tutorial was created by Hap of FreeForm Interactive(creators of Future vs. Fantasy). These works are all (c) 1998 Freeform Interactive.

This tutorial will modify the shotgun we created in the last tutorial to shoot pellets rather than big bullet holes and will also add a punch attack. This requires the modifications you did in the last tutorial. First we need to modify the weapons.c file.

First venture to the bottom of the file and edit the FireWeapon function to look like so:

geBoolean FireWeapon(GenVSI *VSI, void *PlayerData, float Time)
{
    GPlayer             *Player;

    assert(PlayerData);

    Player = (GPlayer*)PlayerData;

    if (GenVSI_GetTime(VSI) < Player->NextWeaponTime)
        return GE_TRUE;
   
    ValidateWeapon(VSI, PlayerData);

    switch (Player->CurrentWeapon)
    {
        case 0:
           FirePunch(VSI, Player, Time);
            break;
        case 1:
            FireGrenade(VSI, Player, Time);
            break;
        case 2:
            FireRocket(VSI, Player, Time);
            break;
        case 3:
            FireCombatShotgun(VSI, Player, Time);
            break;
}

    return GE_TRUE;
}

Now we must replace our old shotgun fire and control routines to look like the code below:

//=====================================================================================
//    CombatShotgun_Control
//=====================================================================================
geBoolean CombatShotgun_Control(GenVSI *VSI, void *PlayerData, float Time)
{
    GPlayer             *Player;
static int currentPellet; //number of effective shotgun pellet number

    Player = (GPlayer*)PlayerData;
currentPellet++;

if (currentPellet == 1)
{
// Play the sound
    //GenVSI_PlaySound(VSI, SOUND_INDEX_SHREDDER, &Player->XForm.Translation);
}
else if (currentPellet >= 6) // max number of shotgun pellets... should be a constant
{
Player->Owner->Weapon = NULL;
GenVSI_DestroyPlayer(VSI, PlayerData);
currentPellet = 0;
return GE_TRUE;
}

Player->XForm.Translation.X += rndSpread(10); //Shotgun spread
Player->XForm.Translation.Y += rndSpread(10);

    return GE_TRUE;
}

//=====================================================================================
//    FireCombatShotgun
//=====================================================================================
void FireCombatShotgun(GenVSI *VSI, void *PlayerData, float Time)
{
    GPlayer             *Weapon;
    GE_Collision    Collision;
    geVec3d             Front, Back, In;
    GPlayer             *Player;
    geWorld             *World;

    Player = (GPlayer*)PlayerData;

    if (Player->Inventory[ITEM_SHREDDER] <= 0 || !Player->InventoryHas[ITEM_SHREDDER])
    {
        SwitchToNextBestWeapon(VSI, Player);
        return;
    }

    if (!Player->Weapon)        // Check to see if allready firing
    {
        Weapon = GenVSI_SpawnPlayer(VSI, "Weapon_Shredder");

        if (!Weapon)
        {
            GenVSI_ConsolePrintf(VSI, "FireShredder: Failed to add player.\n");
            return;
        }
    }
    else
        return;

    if (Player->Inventory[ITEM_SHREDDER] < 0)
    {
        SwitchToNextBestWeapon(VSI, Player);
        Player->Inventory[ITEM_SHREDDER] = 0;
    }

   // Setup the callbacks for this weapon
    Weapon->Control = CombatShotgun_Control;
    Weapon->Blocked = NULL;
    Weapon->Trigger = NULL;
    Weapon->Owner = Player;
    Player->Weapon = Weapon;
    Weapon->Time = 0.0f;

    Weapon->ViewFlags = VIEW_TYPE_NONE;
    Weapon->ViewIndex = VIEW_INDEX_NONE;
    Weapon->FxFlags = FX_SCATTERGUN;   

Weapon->XForm = Player->XForm;

    geVec3d_Add(&Weapon->XForm.Translation, &Player->GunOffset, &Weapon->XForm.Translation);

    Front = Weapon->XForm.Translation;
    geXForm3d_GetIn(&Weapon->XForm, &In);
geVec3d_AddScaled(&Front, &In, 10000.0f, &Back);
   
    World = GenVSI_GetWorld(VSI);

    assert(World);

    if (geWorld_Collision(World, NULL, NULL, &Front, &Back, GE_COLLIDE_ACTORS, 0xffffffff, &Collision))
    {
        if (Collision.Actor)
        {
            GPlayer         *PlayerHit;

            PlayerHit = GenVSI_ActorToPlayer(VSI, Collision.Actor);

            if (PlayerHit)
                DammagePlayer(VSI, Weapon, PlayerHit, 25, 90.0f, Time);
        }

        if (Collision.Model)
        {
            GPlayer         *Target;
            #pragma message ("Use: GenVSI_ModelToPlayer...")

            Target = (GPlayer*)geWorld_ModelGetUserData(Collision.Model);
            if (Target && Target->Trigger && (Target->ViewFlags & VIEW_TYPE_PHYSOB))
            {
                geXForm3d_GetIn(&Player->XForm, &In);
                Player->Inertia = In;
                geVec3d_Scale(&In, 1000.f, &Player->Inertia);
                Target->Trigger(VSI, Target, Player, (void*)&Collision);
            }
        }
    }

    Player->VPos = Player->XForm.Translation;
Player->NextWeaponTime = GenVSI_GetTime(VSI) + 1.0f; //fire rate
}

Lastly we need to add in our new punch code. Add the following code somewhere into your weapons.c file:

//=====================================================================================
//    Punch_Control
//=====================================================================================
geBoolean Punch_Control(GenVSI *VSI, void *PlayerData, float Time)
{
    GPlayer             *Player;

    Player = (GPlayer*)PlayerData;

    Player->Time += Time;

    if (Player->Time > 0.1f)
{
Player->Owner->Weapon = NULL;
    GenVSI_DestroyPlayer(VSI, PlayerData);
    return GE_TRUE;
}

    Player->XForm = Player->Owner->XForm;

    geVec3d_Add(&Player->XForm.Translation, &Player->Owner->GunOffset, &Player->XForm.Translation);

    Player->VPos = Player->XForm.Translation;

    return GE_TRUE;
}

//=====================================================================================
//    FirePunch
//=====================================================================================
void FirePunch(GenVSI *VSI, void *PlayerData, float Time)
{
    GPlayer             *Weapon;
    GE_Collision    Collision;
    geVec3d             Front, Back, In;
    GPlayer             *Player;
    geWorld             *World;
float distance; //used for limiting range

    Player = (GPlayer*)PlayerData;
Weapon = GenVSI_SpawnPlayer(VSI, "Weapon_Shredder");

   // Setup the callbacks for this weapon
    Weapon->Control = Punch_Control;
    Weapon->Blocked = NULL;
    Weapon->Trigger = NULL;
    Weapon->Owner = Player;
    Player->Weapon = Weapon;
    Weapon->Time = 0.0f;

    Weapon->ViewFlags = VIEW_TYPE_NONE;
    Weapon->ViewIndex = VIEW_INDEX_NONE;
    Weapon->FxFlags = FX_PUNCH;
    Weapon->XForm = Player->XForm;

    geVec3d_Add(&Weapon->XForm.Translation, &Player->GunOffset, &Weapon->XForm.Translation);

    // Play the sound
    //GenVSI_PlaySound(VSI, SOUND_INDEX_SHREDDER, &Player->XForm.Translation);

    Front = Weapon->XForm.Translation;
geXForm3d_GetIn(&Weapon->XForm, &In);
geVec3d_AddScaled(&Front, &In, 150.0f, &Back);

World = GenVSI_GetWorld(VSI);

    assert(World);

if (geWorld_Collision(World, NULL, NULL, &Front, &Back, GE_COLLIDE_ACTORS, 0xffffffff, &Collision))
    {
if (Collision.Actor)
        {
            GPlayer         *PlayerHit;

            PlayerHit = GenVSI_ActorToPlayer(VSI, Collision.Actor);

            if (PlayerHit)
                DammagePlayer(VSI, Weapon, PlayerHit, 25, 90.0f, Time);
        }

        if (Collision.Model)
        {
            GPlayer         *Target;
            #pragma message ("Use: GenVSI_ModelToPlayer...")

Target = (GPlayer*)geWorld_ModelGetUserData(Collision.Model);
            if (Target && Target->Trigger && (Target->ViewFlags & VIEW_TYPE_PHYSOB))
            {
                geXForm3d_GetIn(&Player->XForm, &In);
                Player->Inertia = In;
                geVec3d_Scale(&In, 1000.f, &Player->Inertia);
                Target->Trigger(VSI, Target, Player, (void*)&Collision);
            }
        }
    }

    Player->VPos = Player->XForm.Translation;

    Player->NextWeaponTime = GenVSI_GetTime(VSI) + 0.5f;
}

Next we need to edit our fx.c file. Add the following functions to the bottom your fx.c file.

//=====================================================================================
//    ControlScattergunAnim
//=====================================================================================
static geBoolean ControlScattergunAnim(Fx_System *Fx, Fx_TempPlayer *Player, float Time)
{
    GE_LVertex        Vert;
    int32             Frame;

    Player->Time += Time;

    Vert.X = Player->Pos.X;
    Vert.Y = Player->Pos.Y;
    Vert.Z = Player->Pos.Z;
    Vert.r = Vert.g = Vert.b = Vert.a = 255.0f;
    Vert.u = Vert.v = 0.0f;

    Frame = (int32)(Player->Time*20.0f);
Frame *= 2; //twice as fast, don't want long pellet trails

    if (Frame >= NUM_SMOKE_TEXTURES)
    {
        // Make sure we draw the last frame...
        Frame = NUM_SMOKE_TEXTURES-1;
//smaller pellets
        geWorld_AddPolyOnce(Fx->World, &Vert, 1, Fx->SmokeTextures[Frame], GE_TEXTURED_POINT, GE_RENDER_DO_NOT_OCCLUDE_OTHERS, 0.1f);    
        Fx_SystemRemoveTempPlayer(Fx, Player);         // Smoke is done...
    }
    else
        geWorld_AddPolyOnce(Fx->World, &Vert, 1, Fx->SmokeTextures[Frame], GE_TEXTURED_POINT, GE_RENDER_DO_NOT_OCCLUDE_OTHERS, 0.1f);

    return GE_TRUE;
}

//=====================================================================================
//    ControlScattergunFx
//=====================================================================================
static geBoolean ControlScattergunFx(Fx_System *Fx, Fx_Player *Player, float Time)
{
    assert(Fx);
    assert(Player);

    Player->Time += Time;

    if (Player->Time >= 0.03f)
    {
        Fx_TempPlayer    *TempPlayer;
        GE_Collision    Collision;
        geVec3d             Front, Back, In;
        geVec3d             Mins = {-1.0f, -1.0f, -1.0f};
        geVec3d             Maxs = { 1.0f, 1.0f, 1.0f};

        TempPlayer = Fx_SystemAddTempPlayer(Fx);

        if (!TempPlayer)
            return GE_TRUE;        // Oh well...

        TempPlayer->Control = ControlScattergunAnim;
        TempPlayer->Time = 0.0f;

        Front = Player->XForm.Translation;

        geXForm3d_GetIn(&Player->XForm, &In);
        geVec3d_AddScaled(&Front, &In, 10000.0f, &Back);
       
        if (geWorld_Collision(Fx->World, NULL, NULL, &Front, &Back, GE_COLLIDE_ACTORS | GE_COLLIDE_MODELS, 0xffffffff, &Collision))
        {
            // Move it back a little from the wall
            geVec3d_AddScaled(&Collision.Impact, &In, -50.0f, &Collision.Impact);
           
            TempPlayer->Pos = Collision.Impact;
        }
        else
            TempPlayer->Pos = Player->XForm.Translation;

        Player->Time = 0.0f;
    }
    return GE_TRUE;
}

//=====================================================================================
//    ControlPunchAnim
//=====================================================================================
static geBoolean ControlPunchAnim(Fx_System *Fx, Fx_TempPlayer *Player, float Time)
{
    GE_LVertex        Vert;
    int32             Frame;

    Player->Time += Time;

    Vert.X = Player->Pos.X;
    Vert.Y = Player->Pos.Y;
    Vert.Z = Player->Pos.Z;
    Vert.r = Vert.g = Vert.b = Vert.a = 255.0f;
    Vert.u = Vert.v = 0.0f;

    Frame = (int32)(Player->Time*20.0f);

    if (Frame >= NUM_PARTICLE_TEXTURES)
    {
        // Make sure we draw the last frame...
        Frame = NUM_SMOKE_TEXTURES-1;
        geWorld_AddPolyOnce(Fx->World, &Vert, 1, Fx->ParticleTextures[Frame], GE_TEXTURED_POINT, GE_RENDER_DO_NOT_OCCLUDE_OTHERS, 0.1f);     // 2,6
        Fx_SystemRemoveTempPlayer(Fx, Player);         // Smoke is done...
    }
    else
        geWorld_AddPolyOnce(Fx->World, &Vert, 1, Fx->ParticleTextures[Frame], GE_TEXTURED_POINT, GE_RENDER_DO_NOT_OCCLUDE_OTHERS, 0.1f);
   
    return GE_TRUE;
}

//=====================================================================================
//    ControlPunchFx
//=====================================================================================
static geBoolean ControlPunchFx(Fx_System *Fx, Fx_Player *Player, float Time)
{
    assert(Fx);
    assert(Player);

    Player->Time += Time;

    if (Player->Time >= 0.03f)
    {
        Fx_TempPlayer    *TempPlayer;
        GE_Collision    Collision;
        geVec3d             Front, Back, In;
        geVec3d             Mins = {-1.0f, -1.0f, -1.0f};
        geVec3d             Maxs = { 1.0f, 1.0f, 1.0f};

        TempPlayer = Fx_SystemAddTempPlayer(Fx);

        if (!TempPlayer)
            return GE_TRUE;         // Oh well...

        TempPlayer->Control = ControlPunchAnim;
        TempPlayer->Time = 0.0f;

        Front = Player->XForm.Translation;

        geXForm3d_GetIn(&Player->XForm, &In);
        geVec3d_AddScaled(&Front, &In, 150.0f, &Back);
       
        if (geWorld_Collision(Fx->World, NULL, NULL, &Front, &Back, GE_COLLIDE_ACTORS | GE_COLLIDE_MODELS, 0xffffffff, &Collision))
        {
           // Move it back a little from the wall
            geVec3d_AddScaled(&Collision.Impact, &In, -50.0f, &Collision.Impact);
           
            TempPlayer->Pos = Collision.Impact;
        }
        else
            TempPlayer->Pos = Player->XForm.Translation;

        Player->Time = 0.0f;
    }
    return GE_TRUE;
}


Lastly we need to modify the fx.h file. Modify the definitions(towards the beginning of your file) to look like the code below:

// Flagged fx for players
#define FX_SMOKE_TRAIL                     (1<<0)
#define FX_PARTICLE_TRAIL                 (1<<1)
#define FX_SHREDDER                         (1<<2)
#define FX_PUNCH                              (1<<4)
#define FX_SCATTERGUN                     (1<<8)

// Spawnable fx
#define FX_EXPLODE1                         0
#define FX_EXPLODE2                         1

The end. Enjoy!