Engine hack to make coop per player scores

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Engine hack to make coop per player scores

Post by Baker »

Here is the story.

I'm strongly considering enabling TAB during coop to display the full scoreboard because otherwise in coop it is incredibly difficult:
(1) to see who has connected
(2) to see what there colors are, etc. (to tell them to change color so they don't take friendly fire with "teamplay 1").

However, it is annoying seeing ZERO as everyone's score. I'd prefer per player monster kills (yeah, I know coop isn't competitive).

Since there are HUNDREDS of single player releases with their own QuakeC, I'm thinking of jacking into ClientObituary or Killed (Killed seems more logical) ...
(1) Only do this if deathmatch is 0 and coop is non-zero. Something like DMSP won't be affected, DMSP uses deathmatch scoreboard.
(2) Seeing if the attacker score increased -- if it did, could be some sort of mod.
(3) If it didn't increase, make it increase.

combat.qc:Killed

Code: Select all

void(entity targ, entity attacker) Killed =
{
	local entity oself;

	oself = self;
	self = targ;
	
...
// bump the monster counter
	if (self.flags & FL_MONSTER)
	{
		killed_monsters = killed_monsters + 1;
		WriteByte (MSG_ALL, SVC_KILLEDMONSTER);
	}

	ClientObituary(self, attacker); // <----------- called here
	
	self.takedamage = DAMAGE_NO;
...
};
client.qc:ClientObituary

Code: Select all

void(entity targ, entity attacker) ClientObituary =
{
	local	float rnum;
	local	string deathstring, deathstring2;
	rnum = random();

	if (targ.classname == "player")
	{
		if (attacker.classname == "teledeath")
		{
			bprint (targ.netname);
			bprint (" was telefragged by ");
			bprint (attacker.owner.netname);
			bprint ("\n");

			attacker.owner.frags = attacker.owner.frags + 1;
			return;
		}

		if (attacker.classname == "teledeath2")
		{
			bprint ("Satan's power deflects ");
			bprint (targ.netname);
			bprint ("'s telefrag\n");

			targ.frags = targ.frags - 1;
			return;
		}

		if (attacker.classname == "player")
		{
			if (targ == attacker)
			{
				// killed self
				attacker.frags = attacker.frags - 1;
				bprint (targ.netname);
				
				if (targ.weapon == 64 && targ.waterlevel > 1)
				{
					bprint (" discharges into the water.\n");
					return;
				}
				if (targ.weapon == IT_GRENADE_LAUNCHER)
					bprint (" tries to put the pin back in\n");
				else
					bprint (" becomes bored with life\n");
				return;
			}
			else if ( (teamplay == 2) && (targ.team > 0)&&(targ.team == attacker.team) )
			{
				if (rnum < 0.25)
					deathstring = " mows down a teammate\n";
				else if (rnum < 0.50)
					deathstring = " checks his glasses\n";
				else if (rnum < 0.75)
					deathstring = " gets a frag for the other team\n";
				else
					deathstring = " loses another friend\n";
				bprint (attacker.netname);
				bprint (deathstring);
				attacker.frags = attacker.frags - 1;
				return;
			}
			else
			{
				attacker.frags = attacker.frags + 1;

				rnum = attacker.weapon;
				if (rnum == IT_AXE)
				{
					deathstring = " was ax-murdered by ";
					deathstring2 = "\n";
				}
				if (rnum == IT_SHOTGUN)
				{
					deathstring = " chewed on ";
					deathstring2 = "'s boomstick\n";
				}
				if (rnum == IT_SUPER_SHOTGUN)
				{
					deathstring = " ate 2 loads of ";
					deathstring2 = "'s buckshot\n";
				}
				if (rnum == IT_NAILGUN)
				{
					deathstring = " was nailed by ";
					deathstring2 = "\n";
				}
				if (rnum == IT_SUPER_NAILGUN)
				{
					deathstring = " was punctured by ";
					deathstring2 = "\n";
				}
				if (rnum == IT_GRENADE_LAUNCHER)
				{
					deathstring = " eats ";
					deathstring2 = "'s pineapple\n";
					if (targ.health < -40)
					{
						deathstring = " was gibbed by ";
						deathstring2 = "'s grenade\n";
					}
				}
				if (rnum == IT_ROCKET_LAUNCHER)
				{
					deathstring = " rides ";
					deathstring2 = "'s rocket\n";
					if (targ.health < -40)
					{
						deathstring = " was gibbed by ";
						deathstring2 = "'s rocket\n" ;
					}
				}
				if (rnum == IT_LIGHTNING)
				{
					deathstring = " accepts ";
					if (attacker.waterlevel > 1)
						deathstring2 = "'s discharge\n";
					else
						deathstring2 = "'s shaft\n";
				}
				bprint (targ.netname);
				bprint (deathstring);
				bprint (attacker.netname);
				bprint (deathstring2);
			}
			return;
		}
		else
		{
			targ.frags = targ.frags - 1;
			bprint (targ.netname);

			// killed by a montser?
			if (attacker.flags & FL_MONSTER)
			{
				if (attacker.classname == "monster_army")
					bprint (" was shot by a Grunt\n");
				if (attacker.classname == "monster_demon1")
					bprint (" was eviscerated by a Fiend\n");
				if (attacker.classname == "monster_dog")
					bprint (" was mauled by a Rottweiler\n");
				if (attacker.classname == "monster_dragon")
					bprint (" was fried by a Dragon\n");
				if (attacker.classname == "monster_enforcer")
					bprint (" was blasted by an Enforcer\n");
				if (attacker.classname == "monster_fish")
					bprint (" was fed to the Rotfish\n");
				if (attacker.classname == "monster_hell_knight")
					bprint (" was slain by a Death Knight\n");
				if (attacker.classname == "monster_knight")
					bprint (" was slashed by a Knight\n");
				if (attacker.classname == "monster_ogre")
					bprint (" was destroyed by an Ogre\n");
				if (attacker.classname == "monster_oldone")
					bprint (" became one with Shub-Niggurath\n");
				if (attacker.classname == "monster_shalrath")
					bprint (" was exploded by a Vore\n");
				if (attacker.classname == "monster_shambler")
					bprint (" was smashed by a Shambler\n");
				if (attacker.classname == "monster_tarbaby")
					bprint (" was slimed by a Spawn\n");
				if (attacker.classname == "monster_vomit")
					bprint (" was vomited on by a Vomitus\n");
				if (attacker.classname == "monster_wizard")
					bprint (" was scragged by a Scrag\n");
				if (attacker.classname == "monster_zombie")
					bprint (" joins the Zombies\n");

				return;
			}

			// tricks and traps
			if (attacker.classname == "explo_box")
			{
				bprint (" blew up\n");
				return;
			}
			if (attacker.solid == SOLID_BSP && attacker != world)
			{	
				bprint (" was squished\n");
				return;
			}
			if (attacker.classname == "trap_shooter" || attacker.classname == "trap_spikeshooter")
			{
				bprint (" was spiked\n");
				return;
			}
			if (attacker.classname == "fireball")
			{
				bprint (" ate a lavaball\n");
				return;
			}
			if (attacker.classname == "trigger_changelevel")
			{
				bprint (" tried to leave\n");
				return;
			}

			// in-water deaths
			rnum = targ.watertype;
			if (rnum == -3)
			{
				if (random() < 0.5)
					bprint (" sleeps with the fishes\n");
				else
					bprint (" sucks it down\n");
				return;
			}
			else if (rnum == -4)
			{
				if (random() < 0.5)
					bprint (" gulped a load of slime\n");
				else
					bprint (" can't exist on slime alone\n");
				return;
			}
			else if (rnum == -5)
			{
				if (targ.health < -15)
				{
					bprint (" burst into flames\n");
					return;
				}
				if (random() < 0.5)
					bprint (" turned into hot slag\n");
				else
					bprint (" visits the Volcano God\n");
				return;
			}

			// fell to their death?
			if (targ.deathtype == "falling")
			{
				targ.deathtype = "";
				bprint (" fell to his death\n");
				return;
			}

			// hell if I know; he's just dead!!!
			bprint (" died\n");
		}
	}
};
Caveats/Consideration:
(1) Some monsters aren't killed by players.
(2) A Chthon or Shub? I wonder how that even works for the QuakeC "attacker".

Idea for a rainy day ...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Re: Engine hack to make coop per player scores

Post by frag.machine »

Coop scores have very mod-oriented criterias.
I'd suggest instead you simply don't show the individual player points column if coop == 1 (but you may assume that if coop > 1 then it's OK to show the individual scores).
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Engine hack to make coop per player scores

Post by Spike »

meh, just start killing other players, then it won't be 0.
rewarding players only for killing blows still isn't in the spirit of coop.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Engine hack to make coop per player scores

Post by Baker »

Spike wrote:meh, just start killing other players, then it won't be 0.
Hadn't thought of that. Also -1 frag per suicide/ lava death / managed to squish self.

Yeah, the killing blow thing but then that is how DM frags work :D
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Engine hack to make coop per player scores

Post by Baker »

And why not?

Image

Goes something like this ...

Code: Select all

#ifdef SUPPORTS_COOP_ENHANCEMENTS
extern dfunction_t *pr_handles_killed;
extern int pr_in_killed;
void PR_In_Killed_Check (dfunction_t *f, int depth);
void PR_In_Killed_Finish (void);
#endif // SUPPORTS_COOP_ENHANCEMENTS

#ifdef SUPPORTS_COOP_ENHANCEMENTS
	if (pr_handles_killed && f == pr_handles_killed && !pr_in_killed)
		PR_In_Killed_Check (f, pr_depth);
#endif // SUPPORTS_COOP_ENHANCEMENTS


#ifdef SUPPORTS_COOP_ENHANCEMENTS
	if (pr_in_killed && pr_depth == pr_in_killed)
		PR_In_Killed_Finish ();
#endif // SUPPORTS_COOP_ENHANCEMENTS

#ifdef SUPPORTS_COOP_ENHANCEMENTS
dfunction_t *pr_handles_killed;		// Baker: coop fake scoreboard

int 	pr_in_killed; // Reset this on progs load in case of pr error
int 	pr_in_killed_start_frags;
eval_t	*pr_in_killed_murderer_frags_ev;

void PR_In_Killed_Check (dfunction_t *f, int depth)
{
	// Baker: Find out who attacker is and if we need to increment
	// Save attackers score and increment it upon leaving function.

	// parm 1 is target
	// parm 2 is killer
	int the_dead_addy = ((int *)pr_globals)[f->parm_start];
	int the_murderer_addy = ((int *)pr_globals)[f->parm_start + 1];

	// Determine the entity number
	int the_dead_entnum = the_dead_addy / pr_edict_size;
	int the_murderer_entnum = the_murderer_addy / pr_edict_size;
	edict_t *dead_edict = EDICT_NUM(the_dead_entnum);
	edict_t *murderer_edict = EDICT_NUM(the_murderer_entnum);
	eval_t	*dead_flags_ev = GETEDICTFIELDVALUE(dead_edict, eval_flags);
	eval_t	*murderer_frag_ev = GETEDICTFIELDVALUE(murderer_edict, eval_frags);
	qboolean dead_is_monster = dead_flags_ev ? (int)dead_flags_ev->_float & FL_MONSTER : false;
	int murderer_start_frags = murderer_frag_ev ? (int)murderer_frag_ev->_float : 99999;

	// Make sure dead entity is a monster
	if (!dead_is_monster)
		return; 

	// Make sure the killer is a player
	if (the_murderer_entnum < 1 || svs.maxclients < the_murderer_entnum)
		return;

	// Make sure isn't a suicide or self-kill
	if (the_murderer_entnum == the_dead_entnum)
		return;

	pr_in_killed = depth;
	pr_in_killed_murderer_frags_ev = murderer_frag_ev;
	pr_in_killed_start_frags = murderer_frag_ev->_float;
	Con_SafePrintf ("Entered killed\n");
}

void PR_In_Killed_Finish (void)
{
	pr_in_killed = 0;
	Con_SafePrintf ("Exit killed\n");

	if (!pr_in_killed_murderer_frags_ev)
		return; // Error?
	
	// Frags didn't change, so we will do it.
	if (pr_in_killed_murderer_frags_ev->_float == pr_in_killed_start_frags)
		pr_in_killed_murderer_frags_ev->_float ++;
}

dfunction_t* PR_Check_Coop_Kills (void)
{
	// Reset this on progs load in case of pr error
	pr_in_killed = 0;
	if (sv_coop_enhancements.value && coop.value /*pr_global_struct->coop hasn't been set yet */)
	{
		dfunction_t* testfunc = PR_FindFunction ("Killed", PRFF_NOBUILTINS);
		if (testfunc && testfunc->numparms == 2 && testfunc->parm_size[0] == 1 && testfunc->parm_size[1])
		{
			Con_SafePrintf ("Per player coop scores initialized\n");
			return testfunc; // Looks good!
		}
	}
	return NULL;
}
#endif // SUPPORTS_COOP_ENHANCEMENTS

#ifdef SUPPORTS_COOP_ENHANCEMENTS
	pr_handles_killed = PR_Check_Coop_Kills ();
#endif // SUPPORTS_COOP_ENHANCEMENTS

#ifdef SUPPORTS_COOP_ENHANCEMENTS
	if (sv_coop_enhancements.value &&  cmd_source != src_command && cl.gametype != GAME_DEATHMATCH && coop.value && teamplay.value && sv.active)
	{
		if (isDedicated)
			bottom = 12; // Dedicated, you get yellow I guess.
		else bottom = (int)cl_color.value & 15;
	}
#endif // SUPPORTS_COOP_ENHANCEMENTS
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply