Site hosted by Angelfire.com: Build your free website today!

Go to main page.
Go to C3 Mod page.
Go to Sol Badguy for OpenBoR page.

This is a listing of changes I've made to the OpenBoR and OpenDarkBoR source codes. Each .rar file contains an executable for the PC which contains the new features, and a .c file called openbor.c which is the modified source. Any changes I've made of any kind will be surrounded by a pair of '//Fugue///'s. So if you're looking for what's different, just do a search for //Fugue///.

None of these are in the latest OpenBoR executable. They are in the .rars I have here for download. If anyone wants to try out the features, please download the executable and do so- any comments, questions, or bug reports would be very helpful.


Executable with 4 new/re-improved featues:

BoR Source/EXE Edits

  This version contains the following features:
1: Counterattack frames (if an attack hits an entity's bbox in a certain frame, the attacked will switch to a different animation)
2: Additional Pain/fall animations
3: Additional types of stage 'rock' animations.
4: Followup animations (If an attack hits, the attacker will instantly switch to a different animation)

These were added to the PC version 2.0041 and everything should work for both players and enemies (except the stage rocking, of course). I didn't notice any massive slowdown or anything like that, but I didn't get to test them in a wide variety of situations. I've been unable to reproduce the random crashing with counterattacks, so I think I figured out the problem.

Follow-up attacks:
To use follow-up attacks, there are now 4 new animations (FOLLOW1, FOLLOW2, FOLLOW3, and FOLLOW4) and two new settings defined in individual animations called 'followanim' and 'followcond'. followanim takes one argument, the number of the follow-up animation to switch to on a hit. It is also used to control the animation for counterattacks, which is why I seperated it from followcond. The other argument, followcond, takes one argument which determines what conditions must be true for the switch to take place:

Here are the arguments for 'followcond':
0 or leaving the command out of an animation means it's never performed.
1 means the followup is always performed on any hit, even if the attack is blocked, kills the target, and/or is hitting a projectile or obstacle.
2 means as long as the attack hits a player or enemy.
3 means as long as the attack is performed on a player or enemy who is not blocking or dead.
4 means as long as the attack is not performed on a blocking or dead player or enemy, and that player or enemy can be grabbed (i.e. cantgrab is set to off).
For example, if an attack with the commands
<>
followanim 3
followcond  4
<>
<>in it hits a grabbable player or an enemy, and that player or enemy does not block the attack or die, the attacker will instantly switch to their FOLLOW3 animation.


Counterattacks:

These also use the FOLLOW# animations (and thus need the 'followanim' command as well), and adds a once-per-animation command called 'counterframe'. It takes three arguments.
First argument is the number of the frame in which the counterattack is possible. In order to counter, the entity has to be hit while showing that frame.
<>Second argument determines the requirements necessary for a successful counterattack:
0 or blank means no swap.
1 means if the counterattacker is hit in the right frame, it always switches.
2 means if the attack comes from a player or enemy, it'll switch.
3 means as long as the attack is not unblockable, doesn't freeze, hits from the front, and is performed by a player/enemy, it'll switch.
Third argument is a flag for damage. If it's set to 0 or left out, the counterattacker won't take damage (if they counter). If set to 1, the counterattacker will take the attack's damage before countering, and if they are killed by the attack they'll skip the counter entirely and just die instead.

<>For example, if a character with the commands
followanim   2
counterframe  8   3   0
is hit by a blockable attack which does not freeze, comes from the front, is performed by a player or enemy, and hits them in the 8th frame of their animation, the character who was hit would take no damage and instead change to their FOLLOW2 animation.


Additional pain/fall animations:

Added 6 new animations (PAIN2, FALL2, PAIN3, FALL3, PAIN4, and FALL4) and 3 new attack types (attack2, attack3, and attack4). They work exactly the same as attack, burn, and shock, except for the fact that the new FALL animations don't change to the last frame of the normal FALL animation when they complete. If anyone needs more than 6 different pain/fall animation combinations, I figure they'd be better off making their own changes to the source code (It's very, very easy to do- just copy/paste and compile).


New rocking animations:

The new 'rock' types are pretty easy, too. All you need to do to use them is put the already-existing 'rock' command in a stage, but you can now also pass a 2 or a 3 as well 0 or 1.
0 or lack of definition still means nothing happens.
1 still moves the stage in a smooth, slow, sine wave pattern
2 will cause the stage to remain steady for a second or so, then quickly shake twice. Should resemble the steady rocking on a train ride.

3 will cause the stage to shake with a constant, steady rumbling, with occasional 'hiccups'. This one looks like what you might feel if you were riding in a moving van or on top of a moving eighteen wheeler.

In the ATK_... defines, for the new pain/falls:

<>#define ATK_NORMAL2 6 // Additional attack types for the new pain anims
#define ATK_NORMAL3 7
#define ATK_NORMAL4 8


In the ANI_... defines, for followups, counters, and new pain animations:
#define ANI_FOLLOW1 59 // Multi-purpose animations
#define ANI_FOLLOW2 60
#define ANI_FOLLOW3 61
#define ANI_FOLLOW4 62
#define ANI_PAIN2 63 // Additional pain/fall animations
#define ANI_FALL2 64
#define ANI_PAIN3 65
#define ANI_FALL3 66
#define ANI_PAIN4 67
#define ANI_FALL4 68
#define MAX_ANIS 69 // max_anis increased for new ANIs

In the definition for struct s_anim, for followups and counters:
    int         followanim;  // use which FOLLOW anim?
    int         followcond; // conditions under which to use a followup
    int         counterframe; // frame in which a counter is possible
    int         countercond; // conditions to switch to a FOLLOW anim
    int         counterdamage;  // You can specify if counterattackers take damage

in void load_cached_model(char * name), for followups, counters, and new pain animations:
else if(stricmp(value, "follow1")==0){ // 4 followup animations for follow-ups and counters
newchar->animation[ANI_FOLLOW1] = newanim;
}
else if(stricmp(value, "follow2")==0){
newchar->animation[ANI_FOLLOW2] = newanim;
}
else if(stricmp(value, "follow3")==0){
newchar->animation[ANI_FOLLOW3] = newanim;
}
else if(stricmp(value, "follow4")==0){
newchar->animation[ANI_FOLLOW4] = newanim;
}
else if(stricmp(value, "pain2")==0){ // 3 new pairs of pain/fall animations
newchar->animation[ANI_PAIN2] = newanim;
}
else if(stricmp(value, "fall2")==0){
newchar->animation[ANI_FALL2] = newanim;
}
else if(stricmp(value, "pain3")==0){
newchar->animation[ANI_PAIN3] = newanim;
}
else if(stricmp(value, "fall3")==0){
newchar->animation[ANI_FALL3] = newanim;
}
else if(stricmp(value, "pain4")==0){
newchar->animation[ANI_PAIN4] = newanim;
}
else if(stricmp(value, "fall4")==0){
newchar->animation[ANI_FALL4] = newanim;
}

In the reset vars section of the same function, for followups and counters:
        newanim->followanim = -1; // Default disabled
        newanim->followcond = 0;
        newanim->counterframe = -1;
        newanim->countercond = 0;
        newanim->counterdamage = 0; // Default no damage on counter

Even later on in the same function, while reading in animation data, for followups and counters:
            else if(stricmp(command, "followanim")==0){ // entities can now use followup attacks
              newanim->followanim = atoi(findarg(buf+pos, 1));
              if(newanim->followanim > 4) newanim->followanim = 4; // only 4 followups
          if(newanim->followanim < 1) newanim->followanim = 1;
            }
        else if(stricmp(command, "followcond")==0){
          newanim->followcond = atoi(findarg(buf+pos, 1));
        }
            else if(stricmp(command, "counterframe")==0){ // entities can now counterattack
              newanim->counterframe = atoi(findarg(buf+pos, 1));
              newanim->countercond = atoi(findarg(buf+pos, 2));
              newanim->counterdamage = atoi(findarg(buf+pos, 3));
              if(newanim->counterframe > 4) newanim->counterframe = 4;
        }

Still the same function, now while loading the attack data, for additional pain anims:
else if(stricmp(command, "attack2")==0){ // Even more attack types.
attack[0] = atoi(findarg(buf+pos, 1));
attack[1] = atoi(findarg(buf+pos, 2));
attack[2] = atoi(findarg(buf+pos, 3));
attack[3] = atoi(findarg(buf+pos, 4));
attackforce = atoi(findarg(buf+pos, 5));
attackdrop = atoi(findarg(buf+pos, 6));
noblock = atoi(findarg(buf+pos, 7));
noflash = atoi(findarg(buf+pos, 8));
pauseadd = atoi(findarg(buf+pos, 9));
attacktype = ATK_NORMAL2;
}
else if(stricmp(command, "attack3")==0){ // Even more attack types.
attack[0] = atoi(findarg(buf+pos, 1));
attack[1] = atoi(findarg(buf+pos, 2));
attack[2] = atoi(findarg(buf+pos, 3));
attack[3] = atoi(findarg(buf+pos, 4));
attackforce = atoi(findarg(buf+pos, 5));
attackdrop = atoi(findarg(buf+pos, 6));
noblock = atoi(findarg(buf+pos, 7));
noflash = atoi(findarg(buf+pos, 8));
pauseadd = atoi(findarg(buf+pos, 9));
attacktype = ATK_NORMAL3;
}
else if(stricmp(command, "attack4")==0){ // Even more attack types.
attack[0] = atoi(findarg(buf+pos, 1));
attack[1] = atoi(findarg(buf+pos, 2));
attack[2] = atoi(findarg(buf+pos, 3));
attack[3] = atoi(findarg(buf+pos, 4));
attackforce = atoi(findarg(buf+pos, 5));
attackdrop = atoi(findarg(buf+pos, 6));
noblock = atoi(findarg(buf+pos, 7));
noflash = atoi(findarg(buf+pos, 8));
pauseadd = atoi(findarg(buf+pos, 9));
attacktype = ATK_NORMAL4;
}

In the declarations for void do_attack(entity *e), for followups and counters:
    int them2; // A stricter definition of "them" for followups

 Same function, this should replace the current code used to define 'them':
    if(e->projectile > 0) them2 = them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_PLAYER && !savedata.mode){
      them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
      them2 = TYPE_PLAYER | TYPE_ENEMY;
    }
    else if(e->type == TYPE_SHOT && !savedata.mode){
      them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
      them2 = TYPE_PLAYER | TYPE_ENEMY;
    }
    else if(e->type == TYPE_ENEMY && e->model->subtype == SUBTYPE_ARROW){
      them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;       // Added so arrows can damage enemies, obstacles and players
      them2 = TYPE_PLAYER;
    }
    else if(e->model->hitenemy) them2 = them = TYPE_PLAYER | TYPE_ENEMY;        // Enemy projectiles can now hit enemies
    else if(e->type == TYPE_ENEMY) them2 = them = TYPE_PLAYER;
    else if(e->type == TYPE_PLAYER && savedata.mode){
      them = TYPE_ENEMY | TYPE_OBSTACLE;
      them2 = TYPE_ENEMY;
    }
    else if(e->type == TYPE_SHOT && savedata.mode){
      them = TYPE_ENEMY | TYPE_OBSTACLE;
      them2 = TYPE_ENEMY;
    }
    else if(e->type == TYPE_TRAP) them2 = them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;
    else if(e->type == TYPE_OBSTACLE) them2 = them = TYPE_PLAYER | TYPE_ENEMY | TYPE_OBSTACLE;  // Added so obstacles can explode and damage enemies/players/obstacles
    else return;

Same function, after spawning a blockflash but before else{ self->takedamage(...), for counters:
                                // New counterattack code
                                else if((self->animation->countercond) && // Counterattack?
                    (self->animation->counterframe == self->animpos) && // is this the right frame?
                    (!(self->frozen || didblock)) && // check for frozen or blocked attacks 
                    ((self->animation->countercond < 2) || (ent_list[i]->type & them2)) && // Does type matter?
                    ((self->animation->countercond < 3) || !(e->animation->no_block[e->animpos] || // Can you counter unblockables?
                                         (self->direction == e->direction) || // How about back attacks?
                                         (e->animation->attack_type[e->animpos] == ATK_FREEZE))) && // And freeze attacks?
                    (!self->animation->counterdamage || (self->health > force))){ // Does damage matter?
                  if(self->animation->counterdamage) self->health -= force; // Take damage?
                  ent_reset_anim(self, ANI_FOLLOW1 + self->animation->followanim - 1);
                }
                               
                else{   // Didn't block or counter so go ahead and take the hit 

Same function, between temp2->type = TYPE_FLASH; and self = temp;, for followups:
                                // New followup code
                                if((e->animation->followcond) && // follow up?
                   ((e->animation->followcond < 2) || (ent_list[i]->type & them2)) && // Does type matter?
                   ((e->animation->followcond < 3) || ((self->health > 0) &&
                                       !didblock)) && // check if health or blocking matters
                   ((e->animation->followcond < 4) || !self->model->cantgrab)) // check if nograb matters
                 
                                  ent_set_anim(e, ANI_FOLLOW1 + e->animation->followanim - 1); // Then go to it!


In void player_takedamage(entity *other, int force, int drop, int type), for new fall animations:
else if(type == ATK_NORMAL2 && self->model->animation[ANI_FALL2]) ent_reset_anim(self, ANI_FALL2);
else if(type == ATK_NORMAL3 && self->model->animation[ANI_FALL3]) ent_reset_anim(self, ANI_FALL3);
else if(type == ATK_NORMAL4 && self->model->animation[ANI_FALL4]) ent_reset_anim(self, ANI_FALL4);

Same function, but this time for the pain animations:
else if(type == ATK_NORMAL2 && self->model->animation[ANI_PAIN2]) ent_reset_anim(self, ANI_PAIN2);
else if(type == ATK_NORMAL3 && self->model->animation[ANI_PAIN3]) ent_reset_anim(self, ANI_PAIN3);
else if(type == ATK_NORMAL4 && self->model->animation[ANI_PAIN4]) ent_reset_anim(self, ANI_PAIN4);

In int enemy_trymove(float xdir, float zdir), checking if a throw can be performed, for additional fall animations:
self->animation != self->model->animation[ANI_FALL2] &&
self->animation != self->model->animation[ANI_FALL3] &&
self->animation != self->model->animation[ANI_FALL4] &&
(This code should also be copied into the check for grabbing further down.)

In void enemy_takedamage(entity *other, int force, int drop, int type), for the new fall animations:
else if(type == ATK_NORMAL2 && self->model->animation[ANI_FALL2]) ent_reset_anim(self, ANI_FALL2);
else if(type == ATK_NORMAL3 && self->model->animation[ANI_FALL3]) ent_reset_anim(self, ANI_FALL3);
else if(type == ATK_NORMAL4 && self->model->animation[ANI_FALL4]) ent_reset_anim(self, ANI_FALL4);

Same function, but this time for the pain animations:
else if(type == ATK_NORMAL2 && self->model->animation[ANI_FALL2]) ent_reset_anim(self, ANI_FALL2);
else if(type == ATK_NORMAL3 && self->model->animation[ANI_FALL3]) ent_reset_anim(self, ANI_FALL3);
else if(type == ATK_NORMAL4 && self->model->animation[ANI_FALL4]) ent_reset_anim(self, ANI_FALL4);

Now in void biker_takedamage(entity *other, int force, int drop, int type), for new fall animations:
else if(type == ATK_NORMAL2 && self->model->animation[ANI_FALL2]) ent_reset_anim(self, ANI_FALL2);
else if(type == ATK_NORMAL3 && self->model->animation[ANI_FALL3]) ent_reset_anim(self, ANI_FALL3);
else if(type == ATK_NORMAL4 && self->model->animation[ANI_FALL4]) ent_reset_anim(self, ANI_FALL4);


Now in void draw_scrolled_bg(), for the new rock animations:
static int rockoffssine[32] = {
2, 2, 3, 4, 5, 6, 7, 7,
8, 8, 9, 9, 9, 9, 8, 8,
7, 7, 6, 5, 4, 3, 2, 2,
1, 1, 0, 0, 0, 0, 1, 1
}; // normal, smooth rock
static int rockoffsshake[32] = {
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 0, 4, 2, 0, 4, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 0, 4, 2, 0, 4, 2,
}; // slow, constant jarring rock, like on a train
static int rockoffsrumble[32] = {
2, 2, 3, 3, 2, 2, 3, 3,
2, 2, 3, 3, 2, 3, 2, 3,
2, 2, 3, 3, 2, 2, 3, 3,
2, 2, 3, 3, 2, 3, 2, 3
}; // fast, constant rumbling, like in/on a van or trailer

Same function, after the line rockpos = (time/(GAME_SPEED/8)) & 31;, for the new rocking features:
if(level->rocking == 1)
gfx_y_offset = quake - 4 - rockoffssine[rockpos];
else if(level->rocking == 2)
gfx_y_offset = quake - 4 - rockoffsshake[rockpos];
else if(level->rocking == 3)
gfx_y_offset = quake - 4 - rockoffsrumble[rockpos];

These changes SHOULD all work safely without crashing or breaking any other features, but any comments/bug reports anyone can give would be very helpful.


Aggression source and EXE here:
BoR Source/EXE Edits

Again, any comments are appreciated.

In this version, I've only added one new variable, called 'aggression'. It's for enemies only, and can be defined in their .txt file, or in their spawn definitions in levels. It influences how much time they will spend standing around like an idiot before they'll start their own attacks. In other words, the higher this number is, the quicker this enemy will attack, and the lower this number is, the more time this enemy will stand still deciding to attack.
It also currently changes how much time an enemy will take to rise up after being knocked down, and how long they pause after/before moving around. Those changes could be removed easily, though.
The behavior still has a somewhat random element, but this would give some more control to the modder over enemy intelligence.
Negative numbers make the enemy slower, positive make them faster. Using 0 will not change their A.I. at all.

Some examples:
aggression -50
would make an enemy who will attack much more slowly.

aggression 10
would make an enemy who will attack slightly quicker.

aggression 50
would be a very cruel enemy who attacked pretty much as soon as a player got in range of any attacks. Overusing high-aggression enemies saps the fun out of the game pretty fast, but when used in moderation I think it adds a lot.

And now, the code:

in the definition for structs 's_model', 'entity', and 's_spawn_entry':
int aggression; // 01/18/05 New 'control' for enemy A.I.: alters delay before thinking


in void load_cached_model(...), initializing variables:
newchar->aggression = 0; // 01/18/05 New 'control's for enemy A.I.: alters delay before attacking


same function, when reading variables:
else if(stricmp(command, "aggression")==0){
value = findarg(buf+pos, 1);
newchar->aggression = atoi(value);
}


in void load_level(..), reading variables:
else if(stricmp(command, "aggression")==0){ // Aggression can be set per spawn.
next.aggression = atoi(findarg(buf+pos, 1));
}

in entity * spawn(...):
e->aggression = model->aggression; // 01/18/05 New 'control's for enemy A.I.: alters delay before attacking

in void enemy_think(...), after 'self->think = enemy_prepare':
self->stalltime = time + (GAME_SPEED/4) + (rand32()%(GAME_SPEED/10) - self->aggression);

same function, but after deciding a speed and direction for walking (Remove this part to not affect enemy movement)
self->stalltime = time + GAME_SPEED - self->aggression;

in void enemy_attack(...), at the very end:
self->stalltime = time + GAME_SPEED - self->aggression;

in void enemy_fall(...), also at the very end (Remove this part to not affect enemy recovery from a knockdown):
self->stalltime = time + GAME_SPEED - self->aggression;

in entity * smartspawn(...), while checking if variables have been defined:
if(props->aggression != 0)e->aggression = props->aggression; // Aggression can be changed with spawn points now



Source/Executable with Tracker shots:

BoR Source/EXE Edits

 

Again, any comments are appreciated.

This is one I'm pretty happy with, if only because of the fact that it makes using Tail's alpha transparency so much easier. Since it was designed with that in mind, it's naturally based on the code for OpenDarkBoR.
These changes will add two new projectiles, one for players and one for enemies. The player projectile is called 'playshottr' and the enemy version is simply called 'track'. What these projectiles do is automatically follow the position of the entity which shot them. So you could shoot one, then walk across the screen, and the projectile will still be 'attached' to you. This makes it MUCH easier to make characters who have attacks which are partially transparent, especially if the entity moves during the attack.
Once spawned, there are two ways to make the tracker projectile dissapear. The first is to set it not to loop, in which case it will die automatically once it's animation is complete. The other is to let an attack hit the entity which spawned the tracker. That way, the attack won't remain out if something knocks the shooter out of an attack.

These can be used just like any other projectile, except that you can't set the altitude at which it is fired. Which makes sense, since it'll always have the same altitude as the entity which fired it.

To define a player's tracker, use the line

playshottr   {name}

For enemy trackers, use the line

track   {name}
To give a player or enemy an attack-specific tracker shot, use
custpshottr   {name}
or
custtrack   {name}
respectively.
And to actually use the projectile,
pshotframetr   {frame}
or
trackframe   {frame}

And now, the code:

in the definition for struct 's_anim':
    int         trackframe;     // enemy offset-tracking projectile
    int         pshotframetr;   // offset-tracking projectile
    char        custpshottr[MAX_NAME_LEN+1]; // cust pshot for pshottr
    char        custtrack[MAX_NAME_LEN+1];

in the definition for struct 's_model':
    char                playshottr[MAX_NAME_LEN+1]; 
    char                track[MAX_NAME_LEN+1];

in function 'load_cached_model', while setting defaults:
    strncpy(newchar->track, "track", MAX_NAME_LEN);

Same function, while reading the file:
          else if(stricmp(command, "playshottr")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->playshottr, value, MAX_NAME_LEN);
            }
                else if(stricmp(command, "track")==0){
                value = findarg(buf+pos, 1);
                find_model(value);
                strncpy(newchar->track, value, MAX_NAME_LEN);
            }

Same function, now while setting the defaults for animations:
                newanim->pshotframetr = -1;
                newanim->trackframe = -1;
                strncpy(newanim->custtrack, newchar->track, MAX_NAME_LEN);
                strncpy(newanim->custpshottr, newchar->playshottr, MAX_NAME_LEN);
 
Still the same function, while reading in animations:
            else if(stricmp(command, "trackframe")==0){ // Frame to use tracking shot
                newanim->trackframe = atoi(findarg(buf+pos, 1));
                newanim->throwa = 0; // No point setting throwa: it'll be ignored!
            }
            else if(stricmp(command, "pshotframetr")==0){
                newanim->pshotframetr = atoi(findarg(buf+pos, 1));
                newanim->throwa = 0;
            }
            else if(stricmp(command, "custpshottr")==0){
                                value = findarg(buf+pos, 1);
                                find_model(value);
                                strncpy(newanim->custpshottr, value, MAX_NAME_LEN);
                        }
                        else if(stricmp(command, "custtrack")==0){
                                value = findarg(buf+pos, 1);
                                find_model(value);
                                strncpy(newanim->custtrack, value, MAX_NAME_LEN);
                        }
 
Right before the function 'ent_set_anim', to be used later:
            void pshot_spawntr(float x, float z, float a, int direction, int pi);
            void track_spawn(float x, float z, float a, int direction, entity* enem);
 
Now inside both 'ent_set_anim' and 'ent_reset_anim':
        // Spawns for new offset-tracking projectiles.
        else if(ent->type == TYPE_PLAYER && ent->animpos == ent->animation->pshotframetr){
                pshot_spawntr(ent->x, ent->z, ent->a, ent->direction, ent->playerindex);
        }
        else if(ent->type == TYPE_ENEMY && ent->animpos == ent->animation->trackframe && ent->think){
                track_spawn(ent->x, ent->z, ent->a, ent->direction, ent);
        }
       
In the 'update_ents' function, while spawning projectiles:
                                                    // New tracker projectile
                                                    if(self->type == TYPE_PLAYER && self->animpos == self->animation->pshotframetr){
                                                        pshot_spawntr(self->x, self->z, self->a, self->direction, self->playerindex);
                                                    }
                                                    // New Tracker projectile
                                                    else if(self->type == TYPE_ENEMY && self->animpos == self->animation->trackframe){
                                                        track_spawn(self->x, self->z, self->a, self->direction, self);
                                                }

in function 'player_weapon', while grabbing the info from the original entity:
    if(newmodel->playshottr) strncpy(player[self->playerindex].model->playshottr, newmodel->playshottr, MAX_NAME_LEN);
    else strncpy(player[self->playerindex].model->playshottr, NULL, MAX_NAME_LEN);


Two new functions which go with the other player projectile thinking/spawning functions:
// for tracking player projectiles
void range_thinktr(void){
        if(freezeall) return;   // Don't update animation if special is being executed

        if(!self->animating) kill(self); // autokill self when done animation
    if(self->owner->think == player_grabbed || self->owner->think == player_pain || self->owner->think == player_fall){
      kill(self);
      return; // Kill self if owner is attacked
    }

        if(self->model->speed != 0){
          self->model->speed = 0;
        }
        self->direction = self->owner->direction;
        self->x = self->owner->x;
        self->a = self->owner->a;
        self->z = self->owner->z;  // Track the owner!

    self->nextthink = time + THINK_SPEED / 2;
}

void pshot_spawntr(float x, float z, float a, int direction, int pi){
    entity *e;

        e = spawn(x, z, a, player[pi].ent->animation->custpshottr);
    if(e==NULL) return;

    e->attacking = 1;
    e->direction = direction;
    e->think = range_thinktr;
    e->remove_on_attack = e->model->remove;
    e->owner = player[pi].ent;       //Need to remember the owner to track them!
    e->playerindex = pi;
        e->base = a;
        e->screen = e->model->alpha;//transparents effect by tails
    if(e->model->speed) e->model->speed = 0;    // Don't want trackers moving on their own!
}

Two new functions which go with the other enemy projectile thinking/spawning functions:
void track_think(void){
        if(freezeall) return;   // Don't update animation if special is being executed

        if(!self->animating) kill(self); // Dissapear when animation is finished
    if(self->owner->think == enemy_grabbed || self->owner->think == enemy_takedamage || self->owner->think == biker_takedamage){
      kill(self);
      return; // Kill self if owner is attacked
    }

        if(self->model->speed != 0) self->model->speed = 0;
   
        self->direction = self->owner->direction;
        self->x = self->owner->x;
        self->a = self->owner->a;
        self->z = self->owner->z;  // Track the owner!

        self->nextthink = time + THINK_SPEED / 2;
}

void track_spawn(float x, float z, float a, int direction, entity* enem){
    entity *e;

    e = spawn(x, z, a, enem->animation->custtrack);
    if(e==NULL) return;

    e->takedamage = enemy_takedamage;
    e->owner = enem;    // Need to know the owner to track them!
    e->attacking = 1;
    if(e->model->speed != 0) e->model->speed = 0;
    e->model->subtype = SUBTYPE_EWEAPON;        // Flag so projectiles don't fall when players spawn
    e->nograb = 1;
    e->direction = direction;
    e->think = track_think;
    e->screen = e->model->alpha; // For Tail's Alpha transparency
    e->remove_on_attack = e->model->remove;
    e->base = a;
}


Demo screenshots:

.... I don't really have any screenshots at the moment. So I'll reuse this picture..