Tutorial: Last Man Standing With Observer Mode

Discuss programming in the QuakeC language.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Tutorial: Last Man Standing With Observer Mode

Post by Baker »

Earlier in the year, Bam wrote up a high quality example Last Man Standing and really need to tutorialize it. Bam is the author of the Vote-Map Tutorial and has written several multiplayer mods including worked on Clan Arena X with R00k, made XCTF Capture The Flag.

What has changed?

Level of difficulty on a scale of 1 to 5: 3

Number of source files changed: 14
Number of source files added: 2

However, many of the changes are simple.

Foreword

Bam used a modified Quake 1.06 source as the base called POQ Modern 0.5 (POQ stands for Plain Old Quake) -- which addresses multiplayer bugs. This tutorial probably mostly works on vanilla progs 1.06 and probably mostly works on Clean QC source. It might entirely work. But I can't guarantee that.

POQ Modern 0.5 source: Download

Rounds With Observer Mode: Download
Trying It Yourself

In this mod, deathmatch 3 specifies "Rounds Mode". You will need at least 2 clients, 3 is really necessary, to see it in action and how it works.

Here is how you do this ... extract the progs.dat from the Rounds Version into quake\rounds folder.

Client #1 (in listen server mode)

1. Start a Quake like: "c:\quake\glquake.exe" -window -game rounds +maxplayers 4 +deathmatch 3 +fraglimit 5 +timelimit 0 +map dm6
2. Type status in the console, it will say your ip address like 192.168.1.105
3. Type color 4 to be red.

Client #2

4. Start ANOTHER Quake client like: "c:\quake\glquake.exe" -window
5. Type "connect 192.168.1.05" in the console

Client #3

6. Start ANOTHER Quake client like: "c:\quake\glquake.exe" -window
7. Type "connect 192.168.1.05" in the console
8. Type "color 13" to be blue

9. Switch to client #2 and type "color 12" to be yellow

The match begins. If you die, all you can do is watch and wonder around the map until the round is over. If you will, you get +1 points. fraglimit on the server (client #1) determines how many rounds until there is a winner. timelimit 0 means no time limit, but if you did timelimit 20, the game ends in 20 minutes.
Coming next ... the tutorial itself ...

[Edit = minor correction. Only 14 source files changed, not 15. Changed to reflect that.]
Last edited by Baker on Tue Jul 20, 2010 10:49 pm, edited 1 time in total.
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

Post by Baker »

This assumes you the POQ Modern 0.5 source open and these changes will add "deathmatch 3" as "Last Man Standing Mode" or Rounds.

1. progs.src
Add this after client.qc, these are the 2 new files.

utilities.qc //BAM MATCH ROUNDS
match.qc //BAM MATCH ROUNDS
2. Add match.qc (download) and utilities.qc (download) to your source folder.

3. buttons.qc

Add the yellow. You don't want observing dead players -- who are invisible to all -- being able to press buttons do you?

4. weapons.qc

No shooting before round countdown begins, no observing dead players shooting either ... obviously.
void() W_Attack =
{
local float r;

if (!W_CheckNoAmmo ())
return;

//MATCH ROUNDS: No attacking before round starts, and while observer
if ((mode == MATCH_STARTING && counter <= 8 ) || self.status & STS_OBSERVER)
return;


makevectors (self.v_angle); // calculate forward angle for velocity
self.show_hostile = time + 1; // wake monsters up

if (self.weapon == IT_AXE)
{
sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM);
r = random();
if (r < 0.25)
player_axe1 ();
else if (r<0.5)
player_axeb1 ();
else if (r<0.75)
player_axec1 ();
else
player_axed1 ();
self.attack_finished = time + 0.5;
}
else if (self.weapon == IT_SHOTGUN)
{
player_shot1 ();
W_FireShotgun ();
self.attack_finished = time + 0.5;
}
else if (self.weapon == IT_SUPER_SHOTGUN)
{
player_shot1 ();
W_FireSuperShotgun ();
self.attack_finished = time + 0.7;
}
else if (self.weapon == IT_NAILGUN)
{
player_nail1 ();
}
else if (self.weapon == IT_SUPER_NAILGUN)
{
player_nail1 ();
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
player_rocket1();
W_FireGrenade();
self.attack_finished = time + 0.6;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
player_rocket1();
W_FireRocket();
self.attack_finished = time + 0.8;
}
else if (self.weapon == IT_LIGHTNING)
{
player_light1();
self.attack_finished = time + 0.1;
sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM);
}
};
5. player.qc

Force player to become observer when dead instead of allowing them to respawn.
void() PlayerDie =
{
local float i;

//BAM v0.5: Remove powerups and prevent glowing dead bodies
self.items = self.items - (self.items & (IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD));
self.effects = 0;

self.invisible_finished = 0; // don't die as eyes
self.invincible_finished = 0;
self.super_damage_finished = 0;
self.radsuit_finished = 0;
self.modelindex = modelindex_player; // don't use eyes

if (deathmatch || coop)
DropBackpack();
///////////////////////
// MATCH ROUNDS
//BAM: Wait, then force observer
if (mode == MATCH_PLAYING)
observer_init (2.5);
// MATCH ROUNDS
///////////////////////

self.weaponmodel="";
self.view_ofs = '0 0 -8';
self.deadflag = DEAD_DYING;
self.solid = SOLID_NOT;
self.flags = self.flags - (self.flags & FL_ONGROUND);
self.movetype = MOVETYPE_TOSS;
if (self.velocity_z < 10)
self.velocity_z = self.velocity_z + random()*300;

if (self.health < -40)
{
GibPlayer ();
return;
}

DeathSound();

self.angles_x = 0;
self.angles_z = 0;

if (self.weapon == IT_AXE)
{
player_die_ax1 ();
return;
}

i = cvar("temp1");
if (!i)
i = 1 + floor(random()*6);

if (i == 1)
player_diea1();
else if (i == 2)
player_dieb1();
else if (i == 3)
player_diec1();
else if (i == 4)
player_died1();
else
player_diee1();

};
6. misc.qc

Observing players aren't affected by fire and don't take damage from it.
void() fire_touch =
{
//BAM
if (other.status & STS_OBSERVER)
return;


T_Damage (other, self, self, 20);
remove(self);
};
7. func.qc --- add this to the end ... these are function prototypes for the rest of the qc to know they exist

Code: Select all

///////////////////////
// MATCH ROUNDS
float (float pteam) team_allowed;
void() round_think;
void (float n, string s) centerprint_to;
void () match_end;
void (entity ent, float clientshirt, float clientpants) setcolor;
void (float DELAY) observer_init;
void () observer;
void (entity e, float chan, string samp, float vol, float atten) sound;
// MATCH ROUNDS
///////////////////////
8. plats.qc

Dead observing players can't start platforms.
void() plat_center_touch =
{
if (other.classname != "player")
return;

if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;


self = self.enemy;
if (self.state == STATE_BOTTOM)
plat_go_up ();
else if (self.state == STATE_TOP)
self.nextthink = self.ltime + 1; // delay going down
};

void() plat_outside_touch =
{
if (other.classname != "player")
return;

if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;


//dprint ("plat_outside_touch\n");
self = self.enemy;
if (self.state == STATE_TOP)
plat_go_down ();
};
9. initiate.qc

I am rather certain this isn't a stock progs 1.06 QuakeC source file name.

Prevents a newly connected client from barging in and playing a round in progress. Nope ... they get to watch instead of crash the party.
/*
============
PutClientInServer_Mod ()

Called everytime (minus intermission) PutClientInServer () is called. (this also means after
every map change) This function is a place for future mods to keep a player's non-parm
settings across but not limited to map changes.

IE: In the case of an observer mode, PutClientInServer () will revert a
player's non-parm settings after a map change, but PutClientInServer_Mod ()
should be the place to identify this and keep these observer settings.
============
*/

void ()
PutClientInServer_Mod =
{
local float isobserver;

if (self.status & STS_OBSERVER)
{
isobserver = TRUE;
self.status = self.status - STS_OBSERVER;
}


// for new players connecting
if (!self.status & STS_BINDINGS)
{
// The player is not yet ready to recieve commands stuffed
// to the client so we enforce a delay before any are sent.
send_commands ();
}

// after every map change
if (!self.status & STS_CONNECTED)
{
// reset player state, but keep observer
if (mode)
{
self.team2 = 0;
self.team = 1;
self.player_flag = PLAYER_WAITING;
setcolor (self, 0, 0); // Immediately sets client colors

// Keep player's observer status across map changes
if (isobserver)
observer ();
else if (mode != MATCH_WAITING)
observer ();
}


self.status = self.status | STS_CONNECTED;
}
};
10. Add this to the end of defs.qc, it is support for the rounds and observer mode

Code: Select all

/////////////////////////////////////
// BAM: Match defs
/////////////////////////////////////

float STS_OBSERVER			= 32; // The player is currently an observer
float STS_QUEUE 			= 64; // The player is currently in queue to join the match

// Identifies and controls the status of the match.
float mode;
float MATCH_PLAYING 	= 1;
float MATCH_STARTING 	= 2;
float MATCH_WAITING 	= 3;
float MATCH_POSTROUND	= 4;

// Because players become observers after they die mid-round we don't
// want their match status to change. They may be observer at the time,
// but they are still playing in the match.
.float player_flag;
float PLAYER_PLAYING 	= 1; // In match, whether or not this player is currently observer
float PLAYER_WAITING 	= 2; // Not in match, whether or not this player is currently observer

entity first_place, second_place, third_place, fourth_place; // For simplicity, global (top 4) entities are used

float gamecheck, counter, round, matchvar, match_ready, match_overtime, players_ready; // Used for round rules/status
.float team2, teamcheck; // team2: checks for team validation, teamcheck: delay color check

.string refresh; // Items refreshed between rounds are given a universal classname to identify them
Just 5 more files to go ... to be continued ...
Last edited by Baker on Tue Jul 20, 2010 11:09 pm, edited 1 time in total.
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

Post by Baker »

11. world.qc

11a. Check if deathmatch 3 is set at map load time, if so you are waiting for players to color up for the match to start. You can go around and shoot people while you wait.
void() worldspawn =
{
lastspawn = world;
InitBodyQue ();

// custom map attributes
if (self.model == "maps/e1m8.bsp")
cvar_set ("sv_gravity", "100");
else
cvar_set ("sv_gravity", "800");
///////////////////////
// MATCH ROUNDS
//Initiate match mode
if (deathmatch == 3)
mode = MATCH_WAITING;
// MATCH ROUNDS
///////////////////////

// the area based ambient sounds MUST be the first precache_sounds

// player precaches
11b. Precache some extra sounds that the match mode uses ...
precache_sound ("weapons/lhit.wav"); //lightning
precache_sound ("weapons/lstart.wav"); //lightning start
precache_sound ("items/damage3.wav");
precache_sound ("doors/drclos4.wav"); // vote sound
///////////////////////
// MATCH ROUNDS
// Added sound precaches
precache_sound ("buttons/switch04.wav");
precache_sound ("shambler/sdeath.wav");
precache_sound ("misc/runekey.wav");
precache_sound ("items/health1.wav");
// MATCH ROUNDS
///////////////////////

precache_sound ("misc/power.wav"); //lightning for boss

// player gib sounds
11c. Give round_think an opportunity to run once per second.
void() StartFrame =
{
teamplay = cvar("teamplay");
skill = cvar("skill");
///////////////////////
// MATCH ROUNDS
// Give update every second to handle match rounds
if (mode && gamecheck < time)
{
round_think ();

/* //BAM: Optional mode identifier, not efficient just simple
if (!matchvar || mode == MATCH_PLAYING)
{
if (mode != MATCH_STARTING)
{
if (teamplay == 0)
centerprint_to (-1, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nŽFFAŽ");
else
centerprint_to (-1, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nŽTEAMPLAYŽ");
}
else
{
if (teamplay == 0)
centerprint_to (PLAYER_WAITING, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nŽFFAŽ");
else
centerprint_to (PLAYER_WAITING, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nŽTEAMPLAYŽ");
}
}
*/

gamecheck = time + 1;
}
// MATCH ROUNDS
///////////////////////

framecount = framecount + 1;
};
12. doors.qc

All three changes are to prevent observing dead players from using doors, obviously ...

12a.
void() door_trigger_touch =
{
if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;


if (time < self.attack_finished)
12b.
void() door_touch =
{
if (other.classname != "player")
return;
if (self.owner.attack_finished > time)
return;
//BAM
if (other.status & STS_OBSERVER)
return;


self.owner.attack_finished = time + 2;
12c. Obviously observing players can't trigger "secrets"
*/
void() secret_touch =
{
if (other.classname != "player")
return;
if (self.attack_finished > time)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
13. triggers.qc

Observing dead players also don't get to trigger ... well anything ... you know like in a map "trigger_multiple", etc.

13a.
void() multi_touch =
{
if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;
13b.

Observing dead players can't be telefragged
void() tdeath_touch =
{
if (other == self.owner)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
13c.

Can't trigger a skill level change, like in the beginning of the start map in Quake.
void() trigger_skill_touch =
{
if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;
13d.

Can't trigger the "you need registered Quake" on the start map either ...
void() trigger_onlyregistered_touch =
{
if (other.classname != "player")
return;
if (self.attack_finished > time)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
Just 2 files remain ... to be continued ...
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

Post by Baker »

14. items.qc

Dead observing players cannot pick up items, runes, weapons, powerups, health, etc.

14a. Assists with marking items. They ALL respawn inbetween rounds.
/*
============
PlaceItem

plants the object on the floor
============
*/
void() PlaceItem =
{
local float oldz;

self.mdl = self.model; // so it can be restored on respawn
self.flags = FL_ITEM; // make extra wide
self.solid = SOLID_TRIGGER;
self.movetype = MOVETYPE_TOSS;
self.velocity = '0 0 0';
self.origin_z = self.origin_z + 6;
oldz = self.origin_z;
self.refresh = "item_to_refresh"; //MATCH ROUNDS: Identifies items to refresh before each round
14b. Observers can't get healthboxes
void() health_touch =
{
local float amount;
local string s;

if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;

14c. Observers can't get armor
void() armor_touch =
{
local float type, value, bit;

if (other.health <= 0)
return;
if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14d. Observers can't get weapons
void() weapon_touch =
{
local float hadammo, best, new, old;
local entity stemp;
local float leave;

if (!(other.flags & FL_CLIENT))
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14e. Observers can't get ammo
void() ammo_touch =
{
local entity stemp;
local float best;

if (other.classname != "player")
return;
if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14f. Observers can't pick up gold or silver keys
void() key_touch =
{
local entity stemp;
local float best;

if (other.classname != "player")
return;
if (other.health <= 0)
return;
if (other.items & self.items)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14g. Observers can't grab the rune and make Chthons angry
void() sigil_touch =
{
local entity stemp;
local float best;

if (other.classname != "player")
return;
if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14h. Observers can't get Quad, Pent, Ring, Suit
powerup_touch wrote:void() powerup_touch =
{
local entity stemp;
local float best;

if (other.classname != "player")
return;
if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14j. Observers can't get backpacks
BackpackTouch wrote:void() BackpackTouch =
{
local string s;
local float best, old, new;
local entity stemp;
local float acount;

if (other.classname != "player")
return;
if (other.health <= 0)
return;
//BAM
if (other.status & STS_OBSERVER)
return;
14k. Mark backpacks so they can be removed when round restarts.
DropBackpack wrote: item.movetype = MOVETYPE_TOSS;
item.classname = "backpack"; //MATCH ROUNDS: Identify backpacks so we can remove them before each round
item.refresh = "item_to_refresh"; //MATCH ROUNDS

setmodel (item, "progs/backpack.mdl");

1 file remains ... to be continued ...
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

Post by Baker »

15. client.qc

15a.

Observers can't exit the level going into an exit teleporter.
void ()
changelevel_touch =
{
local entity pos;

if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;
15b.

Observers can't kill themselves.
/*
============
ClientKill
Player entered the suicide command
============
*/
void ()
ClientKill =
{
if (self.deadflag || intermission_running)
return;

///////////////////////
// MATCH ROUNDS
// There's a time and place... otherwise, denied!
if ((mode == MATCH_PLAYING && self.player_flag == PLAYER_PLAYING) || mode == MATCH_WAITING || !mode)
{

//BAM v0.5: Fixed, works like a normal death
bprint (self.netname);
bprint (" suicides\n");
set_suicide_frame ();
self.modelindex = modelindex_player;
self.health = 0;
self.takedamage = DAMAGE_NO;
self.waterlevel = 0;
self.flags = self.flags - (self.flags & FL_INWATER);
if (self.frags > -99) self.frags = self.frags - 1;
self.touch = SUB_Null; // just in case later it is used
monster_death_use ();
self.th_die ();
}
else
{
sprint (self, "No\n");
return;
}
// MATCH ROUNDS
///////////////////////

};
15c. PutClientInServer ... at the bottom

Don't do teleport splashes for observers connecting to the server ... I think
PutClientInServer_Mod (); //BAM v0.5: See initiate.qc

//MATCH ROUNDS: Make sure the player isn't observer
if (!self.status & STS_OBSERVER)
{

//BAM v0.5: Don't do telefrag or spawn fog for intermission
if (deathmatch || coop)
{
makevectors(self.angles);
spawn_tfog (self.origin + v_forward*20);
}

spawn_tdeath (self.origin, self);
}
}
};
15d. PlayerDeathThink

At bottom ...

Do not respawn a dead player IF we are running match mode and a match is in progress. It's ok for them to respawn while waiting for a match to start.
self.button0 = 0;
self.button1 = 0;
self.button2 = 0;
//MATCH ROUNDS: In a match, we control when he respawns
if (mode == MATCH_WAITING || !mode)
respawn();

};
15e.

Match logic and forcing the player to color 0 (white) upon connecting. They have to select some other color to play.
void ()
PlayerPreThink =
{
local string steam;

if (intermission_running)
{
IntermissionThink (); // otherwise a button could be missed between
return; // the think tics
}

//BAM v0.5: Our player isn't ready yet
if (!self.status & STS_CONNECTED)
return;

if (self.view_ofs == '0 0 0')
return; // intermission or finale

makevectors (self.v_angle); // is this still used

///////////////////////
// MATCH ROUNDS
// Check for (match mode && change in color && correct mode && post match delay is done)
if (mode && self.team2 != self.team - 1 && mode != MATCH_STARTING && match_ready < time)
{
// Players join match by changing their color
if (self.team > 1 && self.team < 15)
{
// Check for another person on this color if FFA mode
if (team_allowed (self.team - 1) == TRUE)
{
// This player can't already be playing
if (self.player_flag != PLAYER_PLAYING && !self.status & STS_QUEUE)
{
// No match in progress, let them join
if (mode == MATCH_WAITING)
{
self.team2 = self.team - 1;
self.player_flag = PLAYER_PLAYING;
self.frags = 0;
self.teamcheck = time + 1;

if (players_ready)
{
sound (world, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
bprint ("Match timer \breset\n");
}
matchvar = 0; // reset match timer
players_ready = 0; // reset match timer

if (self.status & STS_OBSERVER)
{
SetParms ();
PutClientInServer ();
}
}
else
{
// If a match is in progress, put them in queue
self.status = self.status | STS_QUEUE;
sprint (self, "\bQueued\b to join match next round\n");
centerprint (self, "\bQueued\b to join match next round\n");
self.team2 = self.team - 1;
self.teamcheck = time + 1;
}
}
}
}
else
{
// This is for a player wanting out
if (self.player_flag == PLAYER_PLAYING || self.status & STS_QUEUE)
{
if (self.status & STS_QUEUE)
{
self.status = self.status - (self.status & STS_QUEUE);
sprint (self, "\bRemoved\b from queue\n");
}
else
{
if (players_ready && mode == MATCH_WAITING)
{
sound (world, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE);
bprint ("Match timer \breset\n");
}
matchvar = 0; // reset match timer
players_ready = 0; // reset match timer
}

self.player_flag = PLAYER_WAITING;
self.team2 = self.team - 1;
self.teamcheck = time + 1;
stuffcmd (self, "color 0\n");
self.frags = 0;

if (mode != MATCH_WAITING)
observer ();
}
}
}

// Force colors
if (mode && self.teamcheck < time)
{
steam = ftos (self.team2);
stuffcmd (self, "color ");
if (self.status & STS_QUEUE) stuffcmd (self, "0 ");
stuffcmd (self, steam);
stuffcmd (self, "\n");
self.teamcheck = time + 0.5;
}
// MATCH ROUNDS
///////////////////////


CheckRules ();
WaterMove ();
15f.

Dead players normally can't use impulses. But must be modified to allow observers, who are dead players, to use impulses -- for stats and observer commands and so forth.
/*
================
PlayerPostThink

Called every frame after physics are run
================
*/
void ()
PlayerPostThink =
{
if (intermission_running)
return; // intermission or finale
//BAM: Let observers use impulses no matter what
if (self.deadflag && !self.status & STS_OBSERVER)
return;


//BAM v0.5: Our player isn't ready yet
if (!self.status & STS_CONNECTED)
return;

// BAM v0.5:
// Moved impulse handling from W_WeaponFrame (), this solves the old
// weapon changing issue. The impulse to change weapons wouldn't register
// until after the player was able to shoot again.

if (self.impulse)
{
//BAM v0.12: Primary/Secondary impulse handling

if (!self.status & STS_IDENTIFY_PRIMARY)
ImpulseCommands (); // normal impulse search + handling
else
{
// BAM v0.12:
// If a player uses the primary impulse independently we get an
// endless loop because it is in search mode for the secondary impulse.

// Automatically cancel the search after a short delay.
if (self.primary_impulse_hack < time)
{
self.status = self.status - (self.status & STS_IDENTIFY_PRIMARY);
self.impulse = 0;
}
else
ImpulseCommands_Secondary (); // secondary impulse search + handling
}
}

//BAM: Nothing beyond here is used by observer
if (self.status & STS_OBSERVER)
return;


// do weapon stuff
W_WeaponFrame ();[/b][/color]
15g. ClientDisconnect

Reset some extra fields when a client disconnects.
/*
===========
ClientDisconnect
called when a player disconnects from a server
============
*/
void ()
ClientDisconnect =
{
local string s;

if (gameover)
return;
// if the level end trigger has been activated, just return
// since they aren't *really* leaving

// let everyone else know
s = ftos (self.frags);
bprint (self.netname);
bprint (" left the game with ");
bprint (s);
//BAM v0.5: Correct frag sentence
if (self.frags == 1 || self.frags == -1)
bprint (" frag\n");
else
bprint (" frags\n");

sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE);
set_suicide_frame ();

//BAM
setmodel (self, string_null);
self.invisible_finished = 0;
self.invincible_finished = 0;
self.super_damage_finished = 0;
self.radsuit_finished = 0;
self.items = self.items - (self.items & (IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD));
self.status = 0;
self.effects = 0;
self.vcount = 0;
self.status = 0;
self.classname = "disconnected";
self.health = 0;
self.solid = SOLID_NOT;
self.frags = 0;
///////////////////////
// MATCH ROUNDS
self.player_flag = 0;
self.team = 1;
self.team2 = 0;
// MATCH ROUNDS
///////////////////////

};
The End

Bam did a great job writing up the code for this. Hopefully this tutorial will prove useful to projects wanting a Last Man Standing Mode.
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 ..
Stealth Kill
Posts: 83
Joined: Fri Dec 29, 2006 12:34 pm

Post by Stealth Kill »

Thx!! :D I´m working on a new psp game with Last Man Standing mode.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Stealth Kill wrote:Thx!! :D I´m working on a new psp game with Last Man Standing mode.
I know about 10 projects would like to have "Last Man Standing Mode".

It sure isn't as easy as it seems. Sounds simple enough, but the details are quite challenging.

Bam really outdid himself this time :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 ..
Stealth Kill
Posts: 83
Joined: Fri Dec 29, 2006 12:34 pm

Post by Stealth Kill »

I got errors.

Where is all that stuff like PLAYER_PLAYING,STS_OBSERVER, status etc
defined?


Code: Select all

utilities.qc:13: error: sound redeclared, prev instance is in defs.qc
defs.qc:573:    sound  is defined here
utilities.qc:29: error: Type mismatch on redeclaration of setcolor. void (entity, float, float), should be void (entity, float)
in function centerprint_to2 (line 66),
utilities.qc:74: error: Unknown value "centerprint2".
in function centerprint_to3 (line 80),
utilities.qc:88: error: Unknown value "centerprint3".
in function centerprint_to4 (line 94),
utilities.qc:102: error: Unknown value "centerprint4".
in function centerprint_to5 (line 108),
utilities.qc:116: error: Unknown value "centerprint5".
in function number_players (line 174),
utilities.qc:182: error: Unknown value "status".
utilities.qc:182: error: "." - not a name

************ ERROR ************
Errors have occured

Error in utilities.qc on line 420
Is there a file missing?
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

defs.qc line #669 is where centerprint5 and such is ...

The tutorial is a modification of the POQ Modern 0.5 codebase.

Note: as long as you've been modding, you should have something like TextPad 5 which can just search a folder for a word in any text file. In TextPad 5 you click "Search -> Find In Files" and it returns ALL matches in the folder specified.

Image

Image

Something like TextPad5 makes searches extremely simple ...
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 ..
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Post by leileilol »

This is more of a glorified diff patch than an actual tutorial involving a natural vanilla code base.
i should not be here
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

leileilol wrote:This is more of a glorified diff patch than an actual tutorial involving a natural vanilla code base.
It's FAR better than the previous tutorial on doing this. :D :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 ..
Stealth Kill
Posts: 83
Joined: Fri Dec 29, 2006 12:34 pm

Post by Stealth Kill »

OK i got it working.
I´m an observer, how does the round start?
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Stealth Kill wrote:OK i got it working.
I´m an observer, how does the round start?
There must be at least 2 players for a round to start.

deathmatch 3 must be set BEFORE loading the map because it checks the deathmatch setting on map load in the qc.

Each player has to pick a non-"color 0" color. The QC forces you to white (color 0) on connection to indicate a "not ready" status.

So you need 2 clients to make this mode work and you do something like "color 12" for the first client and then "color 4" for the second one.

At that point, the QuakeC will notice players ready for the match round countdown will begin.
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 ..
Supa
Posts: 164
Joined: Tue Oct 26, 2004 8:10 am

Post by Supa »

Speaking from personal experience, one thing to keep in mind is that you really should have some way of detecting and handling ghost players who have dropped for one reason or another but haven't made it through ClientDisconnect yet. The server *does* eventually disconnect them but it can take a while to do so and it's a real pain when a ghost holds up the round. :|

So what I'd suggest is to add a server-to-client heartbeat that is sent by your round monitor or in StartFrame, which stuffcmd's an impulse to every remaining player. So long as you get that impulse back in the next few server frames you can assume that their client is still lucid and you can leave them alone. But if a client skips a heartbeat you should make at least a few more attempts to get a response out of that client before you kick them out of the round - otherwise you may inadvertently kick players for having packet loss at the wrong moment.

Keep in mind that ghosts won't run through PlayerPreThink/PlayerPostThink, so any method you use can't depend on the ghost going through the regular PreThink/physics/PostThink cycle. You can use this to your advantage if you want to try another method at ghost detection, though. Like the StartFrame heartbeat method, you can have each remaining player trip a still-living flag in PreThink or PostThink for the next server frame, and in StartFrame you'll need to cycle through the remaining players and pay attention to any without the still-living flag - the rest will have their flag cleared in preperation for the next check. From there you could keep checking on that player for the next x server frames and maybe boot them out of the game or you could try sending an impulse heartbeat as before. Just remember to make sure you don't have a false positive. :)
Post Reply