Here is a nice piece of code that will add nadetracking to your killcam - see http://social.xfire.com/video/62f7a3
Edit: This code works WITHOUT libcod, on any vanilla cod2 install (although only tested on 1.3)
Modify your _killcam.gsc until it looks like this:
(all changes are highlighted with //izno before/after themPHP Code:
init()
{
precacheString(&"MP_KILLCAM");
precacheString(&"PLATFORM_PRESS_TO_SKIP");
precacheString(&"PLATFORM_PRESS_TO_RESPAWN");
precacheShader("black");
//izno: added following 2 lines as precache stuff
precachemodel("xmodel/weapon_potato");
precachemodel("xmodel/prop_barrel_benzin");
killcam = getCvar("scr_killcam");
if(killcam == "") // Kill cam
killcam = "1";
setCvar("scr_killcam", killcam, true);
level.killcam = getCvarInt("scr_killcam");
if(level.killcam > 0)
setarchive(true);
thread maps\mp\gametypes\_nadekillcam::init(); //izno: initialize the nadetracker
for(;;)
{
updateKillcamSettings();
wait 1;
}
}
updateKillcamSettings()
{
killcam = getCvarInt("scr_killcam");
if(level.killcam != killcam)
{
level.killcam = getCvarInt("scr_killcam");
if((level.killcam > 0) || (getCvarInt("g_antilag") > 0))
setarchive(true);
else
setarchive(false);
}
}
killcam(attackerNum, delay, offsetTime, respawn, nade) //izno: modified function definition by adding nade as an input variable
{
self endon("spawned");
// killcam
if(attackerNum < 0)
return;
self.sessionstate = "spectator";
self.spectatorclient = attackerNum;
self.archivetime = delay + 7;
self.psoffsettime = offsetTime;
// ignore spectate permissions
self allowSpectateTeam("allies", true);
self allowSpectateTeam("axis", true);
self allowSpectateTeam("freelook", true);
self allowSpectateTeam("none", true);
// wait till the next server frame to allow code a chance to update archivetime if it needs trimming
wait 0.05;
if(self.archivetime <= delay)
{
self.sessionstate = "dead";
self.spectatorclient = -1;
self.archivetime = 0;
self.psoffsettime = 0;
return;
}
self.killcam = true;
//izno: added the following 2 lines for nade-killcam purposes
if(isdefined(nade))
self thread donadefollow(nade);
if(!level.splitscreen && !isdefined(self.kc_topbar))
{
self.kc_topbar = newClientHudElem(self);
self.kc_topbar.archived = false;
self.kc_topbar.x = 0;
self.kc_topbar.y = 0;
self.kc_topbar.horzAlign = "fullscreen";
self.kc_topbar.vertAlign = "fullscreen";
self.kc_topbar.alpha = 0.5;
self.kc_topbar setShader("black", 640, 112);
}
if(!level.splitscreen && !isdefined(self.kc_bottombar))
{
self.kc_bottombar = newClientHudElem(self);
self.kc_bottombar.archived = false;
self.kc_bottombar.x = 0;
self.kc_bottombar.y = 368;
self.kc_bottombar.horzAlign = "fullscreen";
self.kc_bottombar.vertAlign = "fullscreen";
self.kc_bottombar.alpha = 0.5;
self.kc_bottombar setShader("black", 640, 112);
}
if(!isdefined(self.kc_title))
{
self.kc_title = newClientHudElem(self);
self.kc_title.archived = false;
self.kc_title.x = 0;
self.kc_title.alignX = "center";
self.kc_title.alignY = "middle";
self.kc_title.horzAlign = "center_safearea";
self.kc_title.vertAlign = "top";
self.kc_title.sort = 1; // force to draw after the bars
self.kc_title.fontScale = 3.5;
if(level.splitscreen)
self.kc_title.y = 22;
else
self.kc_title.y = 30;
}
self.kc_title setText(&"MP_KILLCAM");
if(!isdefined(self.kc_skiptext))
{
self.kc_skiptext = newClientHudElem(self);
self.kc_skiptext.archived = false;
self.kc_skiptext.x = 0;
self.kc_skiptext.alignX = "center";
self.kc_skiptext.alignY = "middle";
self.kc_skiptext.horzAlign = "center_safearea";
self.kc_skiptext.vertAlign = "top";
self.kc_skiptext.sort = 1; // force to draw after the bars
self.kc_skiptext.font = "default";
if(level.splitscreen)
{
self.kc_skiptext.y = 52;
self.kc_skiptext.fontscale = 1.6;
}
else
{
self.kc_skiptext.y = 70;
self.kc_skiptext.fontscale = 2;
}
}
if(isdefined(respawn))
self.kc_skiptext setText(&"PLATFORM_PRESS_TO_RESPAWN");
else
self.kc_skiptext setText(&"PLATFORM_PRESS_TO_SKIP");
if(!level.splitscreen)
{
if(!isdefined(self.kc_timer))
{
self.kc_timer = newClientHudElem(self);
self.kc_timer.archived = false;
self.kc_timer.x = 0;
self.kc_timer.y = -32;
self.kc_timer.alignX = "center";
self.kc_timer.alignY = "middle";
self.kc_timer.horzAlign = "center_safearea";
self.kc_timer.vertAlign = "bottom";
self.kc_timer.fontScale = 3.5;
self.kc_timer.sort = 1;
}
self.kc_timer setTenthsTimer(self.archivetime - delay);
}
self thread spawnedKillcamCleanup();
self thread waitSkipKillcamButton();
self thread waitKillcamTime();
self waittill("end_killcam");
//izno: added the following 2 lines to clean up the freezecontrols/disableweapon caused by the nadekillcam
self freezecontrols(false);
self enableweapon();
self removeKillcamElements();
self.sessionstate = "dead";
self.spectatorclient = -1;
self.archivetime = 0;
self.psoffsettime = 0;
self.killcam = undefined;
self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
}
//izno: added the following function for nade-killcam purposes
donadefollow(nade)
{
self endon("disconnect");
self endon("end_killcam");
counter = gettime() - self.archivetime * 1000;
while(counter < nade.create_time)
{
counter += 50;
wait 0.05;
}
self freezecontrols(true);
self disableweapon();
frames_back = int((gettime() - nade.create_time) / 50);
userorgs = [];
user_alive = [];
for(i = 0; i < nade.prev_org.size; i++)
{
if(self.prev_org.size - frames_back + i < 0)
{
userorgs[i] = (0, 0, 0);
user_alive[i] = false;
}
else
{
userorgs[i] = self.prev_org[self.prev_org.size - frames_back + i];
user_alive[i] = self.was_alive[self.was_alive.size - frames_back + i];
}
}
self allowSpectateTeam("allies", false);
self allowSpectateTeam("axis", false);
self allowSpectateTeam("freelook", true);
self allowSpectateTeam("none", true);
a = spawn("script_model", nade.prev_org[0]);
b = spawn("script_model", userorgs[0]);
a setmodel("xmodel/weapon_potato");
b setmodel("xmodel/prop_barrel_benzin");
a hide();
b hide();
a showtoplayer(self);
if(user_alive[0])
b showtoplayer(self);
a thread deleteonevent("disconnect", self);
b thread deleteonevent("disconnect", self);
a thread deleteonevent("end_killcam", self);
b thread deleteonevent("end_killcam", self);
for(i = 0; i < nade.prev_org.size; i++)
{
if(user_alive[i])
b showtoplayer(self);
else
b hide();
self setplayerangles((90, 0, 0));
//uncomment the next 2 lines to actually follow the grenade instead of looking at it from above - it's very laggy though
//self setplayerangles(vectortoangles(userorgs[i] - (nade.prev_org[i]+ (0, 0, 50))));
//self setorigin(nade.prev_org[i] + (0, 0, 50) - maps\mp\_utility::vectorscale(vectornormalize(make2d(userorgs[i] - nade.prev_org[i])), 50));
self setorigin(nade.prev_org[i] + (0, 0, 150));
a.origin = nade.prev_org[i];
b.origin = userorgs[i];
if(i < nade.prev_org.size - 1)
{
a moveto(nade.prev_org[i + 1], 0.05);
b moveto(userorgs[i + 1], 0.05);
}
wait 0.05;
}
a notify("done");
b notify("done");
a delete();
b delete();
}
//izno: helper function
make2d(vec)
{
return (vec[0], vec[1], 0);
}
//izno: helper function
deleteonevent(ev, us)
{
if(ev == "disconnect")
us endon("end_killcam");
else
us endon("disconnect");
self endon("done");
us waittill(ev);
if(isdefined(self))
self delete();
}
waitKillcamTime()
{
self endon("disconnect");
self endon("end_killcam");
wait(self.archivetime - 0.05);
self notify("end_killcam");
}
waitSkipKillcamButton()
{
self endon("disconnect");
self endon("end_killcam");
while(self useButtonPressed())
wait .05;
while(!(self useButtonPressed()))
wait .05;
self notify("end_killcam");
}
removeKillcamElements()
{
if(isDefined(self.kc_topbar))
self.kc_topbar destroy();
if(isDefined(self.kc_bottombar))
self.kc_bottombar destroy();
if(isDefined(self.kc_title))
self.kc_title destroy();
if(isDefined(self.kc_skiptext))
self.kc_skiptext destroy();
if(isDefined(self.kc_timer))
self.kc_timer destroy();
}
spawnedKillcamCleanup()
{
self endon("end_killcam");
self waittill("spawned");
self removeKillcamElements();
}
Create maps\mp\gametypes\_nadekillcam.gsc with this in it:
and finally, replace this code in your gametype.gsc:PHP Code:
init()
{
thread waitforconnect();
}
findnade(type)
{
nades = getentarray("grenade", "classname");
for(i = 0; i < nades.size; i++)
{
if(!isdefined(nades[i].monitoring) && distancesquared(self.origin, nades[i].origin) < 100 * 100)
{
nades[i].monitoring = true;
nades[i] thread domonitor(self, type);
}
}
}
waitforconnect()
{
while(true)
{
level waittill("connecting", player);
player thread waitforspawn();
}
}
waitforspawn()
{
self endon("disconnect");
self thread storeorigins();
while(true)
{
self waittill("spawned_player");
self thread monitorplayer();
}
}
storeorigins()
{
self endon("disconnect");
self.prev_org = [];
self.was_alive = [];
while(true)
{
if(self.prev_org.size > 200)
{
for(i = 1; i < self.prev_org.size; i++)
self.prev_org[i - 1] = self.prev_org[i];
self.prev_org[self.prev_org.size - 1] = self.origin;
}
else
self.prev_org[self.prev_org.size] = self.origin;
if(self.was_alive.size > 200)
{
for(i = 1; i < self.was_alive.size; i++)
self.was_alive[i - 1] = self.was_alive[i];
self.was_alive[self.was_alive.size - 1] = (isdefined(self.sessionstate) && self.sessionstate == "playing");
}
else
self.was_alive[self.was_alive.size] = (isdefined(self.sessionstate) && self.sessionstate == "playing");
wait 0.05;
}
}
monitorplayer()
{
self endon("disconnect");
self endon("spawned_player");
old_nadecount = 0;
old_smokecount = 0;
while(isdefined(self.sessionstate) && self.sessionstate == "playing")
{
nadecount = self getammocount("frag_grenade_american_mp") + self getammocount("frag_grenade_russian_mp") + self getammocount("frag_grenade_british_mp") + self getammocount("frag_grenade_german_mp");
smokecount = self getammocount("smoke_grenade_american_mp") + self getammocount("smoke_grenade_russian_mp") + self getammocount("smoke_grenade_british_mp") + self getammocount("smoke_grenade_german_mp");
if(!isdefined(self.sessionstate) || self.sessionstate != "playing")
return;
if(nadecount < old_nadecount)
self findnade("frag");
else if(smokecount < old_smokecount)
self findnade("smoke");
wait 0.05;
old_nadecount = nadecount;
old_smokecount = smokecount;
}
}
cleanup_array(arr)
{
if(!isdefined(arr))
return [];
new = [];
for(i = 0; i < arr.size; i++)
{
if(isdefined(arr[i]))
new[new.size] = arr[i];
}
return new;
}
domonitor(owner, type)
{
clone = spawn("script_origin", (0,0,0));
clone.prev_org = [];
clone.prev_org[clone.prev_org.size] = self.origin;
clone.create_time = gettime();
clone.exploded = undefined;
clone.type = type;
owner.nades = cleanup_array(owner.nades);
owner.nades[owner.nades.size] = clone;
t = 0;
while(t < 5 && isdefined(self))
{
clone.prev_org[clone.prev_org.size] = self.origin;
t += 0.05;
wait 0.05;
}
if(isdefined(self))
{
//was a smoke grenade...
clone.exploded = true;
wait 30;
if(isdefined(self))
{
self delete(); //clean up activision mess that didnt delete smokegrenades and would allow an entity overflow
clone delete();
}
}
else
{
clone.exploded = true;
clone deleteaftertime(20);
}
}
deleteaftertime(t)
{
wait t;
if(isdefined(self))
self delete();
}
with this:Code:delay = 2; // Delay the player becoming a spectator till after he's done dying
wait delay; // ?? Also required for Callback_PlayerKilled to complete before respawn/killcam can execute
if(doKillcam && level.killcam)
self maps\mp\gametypes\_killcam::killcam(attackerNum, delay, psOffsetTime, true);
self thread respawn();
Thats all.PHP Code:
//izno: the next few lines of code (until delay = 2) is added to find if the player was killed by a nade and, if so, which one
grenade = undefined;
if(isdefined(sWeapon) && (sWeapon == "frag_grenade_german_mp" || sWeapon == "frag_grenade_american_mp" || sWeapon == "frag_grenade_british_mp" || sWeapon == "frag_grenade_russian_mp"))
{
if(isdefined(attacker) && isdefined(attacker.nades))
{
for(i = 0; i < attacker.nades.size; i++)
{
if(!isdefined(attacker.nades[i]))
continue;
if(!isdefined(attacker.nades[i].exploded) && attacker.nades[i].type == "frag")
{
grenade = attacker.nades[i];
break;
}
}
}
}
delay = 2; // Delay the player becoming a spectator till after he's done dying
wait delay; // ?? Also required for Callback_PlayerKilled to complete before respawn/killcam can execute
if(doKillcam && level.killcam)
self maps\mp\gametypes\_killcam::killcam(attackerNum, delay, psOffsetTime, true, grenade); //izno: added grenade as input for the killcam function
self thread respawn();
Buglist:
- In free spec mode (used to track the nade) during killcam, all players are invisible, hence the barrel model instead of the playermodel
- In free spec mode during killcam, all items are invisible, hence the potato model instead of the grenade model
- In free spec mode during killcam, no fx is being played (and the nade-explosion hole is already in place by then), thus, no big kaboom.
These are all bugs due to engine limitations. Dont expect miracles from the engine of a 9 year old game (oktober 2005)