Index | FAQ | Search | Memberlist | Usergroups | Profile | Log in to check your private messages | Log in | Register

Tutorial: Last Man Standing With Observer Mode

| Inside3d Forums Forum Index -> QuakeC Programming

View previous topic :: View next topic
Author | Message
--- | ---
Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 9:16 pm Post subject: Tutorial: Last Man Standing With Observer Mode |

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

Quote:
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.]
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Last edited by Baker on Tue Jul 20, 2010 10:49 pm; edited 1 time in total

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 9:40 pm Post subject: |

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

Quote:
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.

Quote:
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.

Quote:
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.

Quote:
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:
/////////////////////// // 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.

Quote:
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.

Quote:
/* ============ 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:
///////////////////////////////////// // 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 ...
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Last edited by Baker on Tue Jul 20, 2010 11:09 pm; edited 1 time in total

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 10:04 pm Post subject: |

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.

Quote:
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 ...

Quote:
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.

Quote:
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.

Quote:
void() door_trigger_touch = { if (other.health <= 0) return; //BAM
if (other.status & STS_OBSERVER)
return;
if (time < self.attack_finished)

12b.

Quote:
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"

Quote:
*/ 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.

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

13b.
Observing dead players can't be telefragged

Quote:
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.

Quote:
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 ...

Quote:
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 ...
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 10:15 pm Post subject: |

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.

Quote:
/* ============ 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

Quote:
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

Quote:
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

Quote:
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

Quote:
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

Quote:
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

Quote:
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 ...
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 10:32 pm Post subject: |

15. client.qc 15a. Observers can't exit the level going into an exit teleporter.

Quote:
void () changelevel_touch = { local entity pos; if (other.classname != "player")
return;
//BAM
if (other.status & STS_OBSERVER)
return;

15b.
Observers can't kill themselves.

Quote:
/* ============ 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

Quote:
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.

Quote:
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.

Quote:
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.

Quote:
/* ================ 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.

Quote:
/* =========== 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.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Stealth Kill Joined: 29 Dec 2006 Posts: 83 |

Posted: Tue Jul 20, 2010 10:42 pm Post subject: |

Thx!! I´m working on a new psp game with Last Man Standing mode.

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 10:47 pm Post subject: |

Stealth Kill wrote:
Thx!! 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
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Stealth Kill Joined: 29 Dec 2006 Posts: 83 |

Posted: Tue Jul 20, 2010 11:45 pm Post subject: |

I got errors. Where is all that stuff like PLAYER_PLAYING,STS_OBSERVER, status etc defined?

Code:
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?

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Tue Jul 20, 2010 11:54 pm Post subject: |

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.
Something like TextPad5 makes searches extremely simple ...
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

leileilol Joined: 15 Oct 2004 Posts: 1321 |

Posted: Wed Jul 21, 2010 12:09 am Post subject: |

This is more of a glorified diff patch than an actual tutorial involving a natural vanilla code base. _________________

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Wed Jul 21, 2010 12:38 am Post subject: |

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.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Stealth Kill Joined: 29 Dec 2006 Posts: 83 |

Posted: Wed Jul 21, 2010 12:48 am Post subject: |

OK i got it working. I´m an observer, how does the round start?

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Wed Jul 21, 2010 12:53 am Post subject: |

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.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Supa Joined: 26 Oct 2004 Posts: 122 |

Posted: Wed Jul 21, 2010 7:26 am Post subject: |

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. :)

Back to top |

Display posts from previous:

| Inside3d Forums Forum Index -> QuakeC Programming | All times are GMT
Page 1 of 1

Jump to:

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2004 phpBB Group