Compare commits

...

4 Commits

Author SHA1 Message Date
an 10d98c67c0 add automatic pronoun detection for FTE_STRINGS engines 2019-09-20 19:56:41 -04:00
an af7d5bd846 clean up 2019-09-20 19:54:53 -04:00
an bcac79a048 add impulse command for spectating 2019-09-20 19:52:15 -04:00
an d3372f846a fix spectating edge cases 2019-09-20 11:38:29 -04:00
7 changed files with 241 additions and 195 deletions

View File

@ -8,6 +8,8 @@ alias pronoun_xey "impulse 26"
alias pronoun_ze_hir "impulse 27"
alias pronoun_ze_zir "impulse 28"
alias spectate "impulse 13"
set sc_cheats 0
set sc_lives 0
set sc_dist_ammo 0

View File

@ -1,5 +1,35 @@
// client.qc: player-adjacent functions
string(float pro) pronoun_subject = {
switch(pro) {
case PRO_NONE: return "none";
case PRO_FAE: return "fae";
case PRO_HE: return "he";
case PRO_IT: return "it";
case PRO_SHE: return "she";
case PRO_THEY: return "they";
case PRO_XEY: return "xey";
case PRO_ZE_H: return "ze";
case PRO_ZE_Z: return "ze";
default: return "unknown";
}
};
string(float pro) pronoun_possessive = {
switch(pro) {
case PRO_NONE: return "none";
case PRO_FAE: return "faer";
case PRO_HE: return "his";
case PRO_IT: return "its";
case PRO_SHE: return "her";
case PRO_THEY: return "their";
case PRO_XEY: return "xyr";
case PRO_ZE_H: return "hir";
case PRO_ZE_Z: return "zir";
default: return "unknown";
}
};
/*
=============================================================================
@ -22,28 +52,21 @@ void() SetChangeParms = {
}
// remove items
self.items &= ~(IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD);
self.items &= ~(IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY |
IT_SUIT | IT_QUAD);
// cap super health
if(self.health > 100) {
self.health = 100;
}
if(self.health < 50) {
self.health = 50;
}
parm1 = self.items;
parm2 = self.health;
parm3 = self.armorvalue;
if(self.ammo_shells < 25) {
parm4 = 25;
} else {
parm4 = self.ammo_shells;
}
parm5 = self.ammo_nails;
parm6 = self.ammo_rockets;
parm7 = self.ammo_cells;
parm8 = self.weapon;
parm9 = self.armortype * 100;
self.health = minmax(self.health, 50, 100);
parm1 = self.items;
parm2 = self.health;
parm3 = self.armorvalue;
parm4 = (self.ammo_shells < 25 ? 25 : self.ammo_shells);
parm5 = self.ammo_nails;
parm6 = self.ammo_rockets;
parm7 = self.ammo_cells;
parm8 = self.weapon;
parm9 = self.armortype * 100;
parm10 = self.pronoun;
};
@ -60,22 +83,37 @@ void() SetNewParms = {
};
void() DecodeLevelParms = {
float ofs, ofs2, pro;
if(serverflags) {
if(world.model == "maps/start.bsp") {
SetNewParms(); // take away all stuff on starting new episode
}
}
self.items = parm1;
self.health = parm2;
self.armorvalue = parm3;
self.ammo_shells = parm4;
self.ammo_nails = parm5;
self.items = parm1;
self.health = parm2;
self.armorvalue = parm3;
self.ammo_shells = parm4;
self.ammo_nails = parm5;
self.ammo_rockets = parm6;
self.ammo_cells = parm7;
self.weapon = parm8;
self.armortype = parm9 * 0.01;
self.pronoun = parm10;
self.ammo_cells = parm7;
self.weapon = parm8;
self.armortype = parm9 * 0.01;
self.pronoun = parm10;
if(ext_strings) {
ofs = strstrofs(self.netname, "(", 0);
if(ofs != -1) {
for(pro = PRO_NONE; pro < PRO_MAX; pro++) {
ofs2 = strstrofs(self.netname, pronoun_possessive(pro), ofs);
if(ofs2 == ofs + 1 && strstrofs(self.netname, ")", ofs) != -1) {
self.pronoun = pro;
break;
}
}
}
}
};
/*
@ -231,14 +269,15 @@ void() execute_changelevel = {
other = find(world, classname, "player");
while(other != world) {
other.view_ofs = VEC_ORIGIN;
other.angles = other.v_angle = pos.mangle;
other.fixangle = TRUE; // turn this way immediately
other.nextthink = time + 0.5;
other.view_ofs = VEC_ORIGIN;
other.angles = other.v_angle = pos.mangle;
other.fixangle = TRUE; // turn this way immediately
other.nextthink = time + 0.5;
other.takedamage = DAMAGE_NO;
other.solid = SOLID_NOT;
other.movetype = MOVETYPE_NONE;
other.solid = SOLID_NOT;
other.movetype = MOVETYPE_NONE;
other.modelindex = 0;
other.spectating = SPECTATING_INTERMISSION;
setorigin(other, pos.origin);
other = find(other, classname, "player");
}
@ -295,7 +334,7 @@ void() trigger_changelevel = {
/*
=============================================================================
PLAYER GAME EDGE FUNCTIONS
PLAYER GAME EDGE FUNCTIONS
=============================================================================
*/
@ -304,6 +343,7 @@ void() become_spectator = {
float not_dead;
entity pl;
self.spectating = (self.lives ? SPECTATING_SPECTATING : SPECTATING_DEAD);
self.health = self.max_health;
self.armortype = 0;
self.armorvalue = 0;
@ -455,7 +495,7 @@ void() PutClientInServer = {
} else {
bprint(ftos(self.lives), " lives left\n");
}
} else {
} else if(self.lives == 0) {
self.lives = sf_lives;
dprint("lives reset to ", ftos(sf_lives), "\n");
}
@ -465,22 +505,23 @@ void() PutClientInServer = {
spot = SelectSpawnPoint();
self.classname = "player";
self.health = 100;
self.takedamage = DAMAGE_AIM;
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_WALK;
self.show_hostile = 0;
self.max_health = 100;
self.flags = FL_CLIENT;
self.air_finished = time + 12;
self.dmg = 2; // initial water damage
self.classname = "player";
self.health = 100;
self.takedamage = DAMAGE_AIM;
self.solid = SOLID_SLIDEBOX;
self.movetype = MOVETYPE_WALK;
self.show_hostile = 0;
self.max_health = 100;
self.flags = FL_CLIENT;
self.air_finished = time + 12;
self.dmg = 2; // initial water damage
self.super_damage_finished = 0;
self.radsuit_finished = 0;
self.invisible_finished = 0;
self.invincible_finished = 0;
self.effects = 0;
self.invincible_time = 0;
self.radsuit_finished = 0;
self.invisible_finished = 0;
self.invincible_finished = 0;
self.effects = 0;
self.invincible_time = 0;
self.spectating = SPECTATING_NOT;
DecodeLevelParms();
@ -491,13 +532,12 @@ void() PutClientInServer = {
self.th_die = PlayerDie;
self.deadflag = DEAD_NO;
// paustime is set by teleporters to keep the player from moving a while
// pausetime is set by teleporters to keep the player from moving for a bit
self.pausetime = 0;
// spot = SelectSpawnPoint();
self.origin = spot.origin + '0 0 1';
self.angles = spot.angles;
self.origin = spot.origin + '0 0 1';
self.angles = spot.angles;
self.fixangle = TRUE; // turn this way immediately
// oh, this is a hack!
@ -524,7 +564,7 @@ void() PutClientInServer = {
/*
=============================================================================
QUAKED FUNCTIONS
QUAKED FUNCTIONS
=============================================================================
*/
@ -620,8 +660,7 @@ Exit deathmatch games upon conditions
============
*/
void() CheckRules = {
float timelimit;
float fraglimit;
float timelimit, fraglimit;
if(gameover) { // someone else quit the game already
return;
@ -630,12 +669,8 @@ void() CheckRules = {
timelimit = cvar("timelimit") * 60;
fraglimit = cvar("fraglimit");
if(timelimit && time >= timelimit) {
NextLevel();
return;
}
if(fraglimit && self.frags >= fraglimit) {
if((timelimit && time >= timelimit) ||
(fraglimit && self.frags >= fraglimit)) {
NextLevel();
return;
}
@ -707,17 +742,12 @@ void() PlayerJump = {
return;
}
if(!(self.flags & FL_ONGROUND)) {
return;
}
if(!(self.flags & FL_JUMPRELEASED)) {
if(!(self.flags & FL_ONGROUND) || !(self.flags & FL_JUMPRELEASED)) {
return; // don't pogo stick
}
self.flags = self.flags - (self.flags & FL_JUMPRELEASED);
self.flags = self.flags - FL_ONGROUND; // don't stairwalk
self.flags -= (self.flags & FL_JUMPRELEASED);
self.flags -= FL_ONGROUND; // don't stairwalk
self.button2 = 0;
// player jumping sound
@ -732,11 +762,8 @@ WaterMove
============
*/
void() WaterMove = {
//dprint(ftos(self.waterlevel));
if(self.movetype == MOVETYPE_NOCLIP) {
return;
}
if(self.health < 0) {
string water_snd;
if(self.movetype == MOVETYPE_NOCLIP || self.health < 0) {
return;
}
@ -769,41 +796,39 @@ void() WaterMove = {
return;
}
if(self.watertype == CONTENT_LAVA) {
// do damage
if(self.dmgtime < time) {
if(self.radsuit_finished > time) {
self.dmgtime = time + 1;
} else {
self.dmgtime = time + 0.2;
switch(self.watertype) {
case CONTENT_LAVA:
if(self.dmgtime < time) {
if(self.radsuit_finished > time) {
self.dmgtime = time + 1;
} else {
self.dmgtime = time + 0.2;
}
T_Damage(self, world, world, 10 * self.waterlevel);
}
T_Damage(self, world, world, 10 * self.waterlevel);
}
} else if(self.watertype == CONTENT_SLIME) {
// do damage
if(self.dmgtime < time && self.radsuit_finished < time) {
self.dmgtime = time + 1;
T_Damage(self, world, world, 4 * self.waterlevel);
}
break;
case CONTENT_SLIME:
if(self.dmgtime < time && self.radsuit_finished < time) {
self.dmgtime = time + 1;
T_Damage(self, world, world, 4 * self.waterlevel);
}
break;
}
if(!(self.flags & FL_INWATER)) {
// player enter water sound
if(self.watertype == CONTENT_LAVA) {
sound(self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM);
}
if(self.watertype == CONTENT_WATER) {
sound(self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM);
}
if(self.watertype == CONTENT_SLIME) {
sound(self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM);
switch(self.watertype) {
case CONTENT_LAVA: water_snd = "player/inlava.wav"; break;
case CONTENT_WATER: water_snd = "player/inh2o.wav"; break;
case CONTENT_SLIME: water_snd = "player/slimbrn2.wav"; break;
}
sound(self, CHAN_BODY, water_snd, 1, ATTN_NORM);
self.flags = self.flags + FL_INWATER;
self.dmgtime = 0;
}
if(!(self.flags & FL_WATERJUMP)) {
self.velocity = self.velocity - 0.8 * self.waterlevel * frametime * self.velocity;
self.velocity -= 0.8 * self.waterlevel * frametime * self.velocity;
}
};
@ -843,17 +868,14 @@ Called every frame before physics are run
================
*/
void() PlayerPreThink = {
// otherwise a button could be missed between the think tics
if(intermission_running) {
IntermissionThink(); // otherwise a button could be missed between
return; // the think tics
IntermissionThink();
return;
} else if(self.spectating) {
return;
}
if(self.view_ofs == VEC_ORIGIN) {
return; // intermission, finale or spectating
}
makevectors(self.v_angle); // is this still used
CheckRules();
WaterMove();
@ -895,7 +917,7 @@ Check for turning off powerups
================
*/
void() CheckPowerups = {
if(self.health <= 0) {
if(self.health <= 0 || self.spectating) {
return;
}
@ -1020,16 +1042,18 @@ Called every frame after physics are run
================
*/
void() PlayerPostThink = {
if(self.view_ofs == VEC_ORIGIN) {
return; // intermission, finale or spectating
if(time >= self.attack_finished) {
ImpulseCommands();
}
if(self.deadflag) {
if(self.spectating || self.deadflag) {
return;
}
// do weapon stuff
W_WeaponFrame();
if(time >= self.attack_finished) {
W_WeaponFrame();
}
// check to see if player landed and play landing sound
if(self.jump_flag < -300 && self.flags & FL_ONGROUND && self.health > 0) {
@ -1076,20 +1100,23 @@ called when a player disconnects from a server
============
*/
void() ClientDisconnect = {
// if the level end trigger has been activated, just return
// since they aren't *really* leaving
if(gameover) {
return;
}
// if the level end trigger has been activated, just return
// since they aren't *really* leaving
// let everyone else know
bprint(self.netname, " left the game with ", ftos(self.frags), " frags\n");
sound(self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE);
set_suicide_frame();
if(!self.spectating) {
sound(self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE);
set_suicide_frame();
}
};
void() cheat = {
if((deathmatch || coop) && !sf_cheats) {
if(((deathmatch || coop) && !sf_cheats) || self.spectating) {
return;
}
@ -1114,43 +1141,13 @@ void() cheat = {
};
void() cheat_quad = {
if((deathmatch || coop) && !sf_cheats) {
if(((deathmatch || coop) && !sf_cheats) || self.spectating) {
return;
}
self.super_time = 1;
self.super_time = 1;
self.super_damage_finished = time + 30;
self.items = self.items | IT_QUAD;
};
string(float pro) pronoun_subject = {
switch(pro) {
case PRO_NONE: return "none";
case PRO_FAE: return "fae";
case PRO_HE: return "he";
case PRO_IT: return "it";
case PRO_SHE: return "she";
case PRO_THEY: return "they";
case PRO_XEY: return "xey";
case PRO_ZE_H: return "ze";
case PRO_ZE_Z: return "ze";
default: return "unknown";
}
};
string(float pro) pronoun_possessive = {
switch(pro) {
case PRO_NONE: return "none";
case PRO_FAE: return "faer";
case PRO_HE: return "his";
case PRO_IT: return "its";
case PRO_SHE: return "her";
case PRO_THEY: return "their";
case PRO_XEY: return "xyr";
case PRO_ZE_H: return "hir";
case PRO_ZE_Z: return "zir";
default: return "unknown";
}
self.items = self.items | IT_QUAD;
};
void(float pro) change_pronoun = {
@ -1162,6 +1159,34 @@ void(float pro) change_pronoun = {
pronoun_possessive(pro), "\n");
};
void() spectate = {
switch(self.spectating) {
case SPECTATING_NOT:
bprint(self.netname, " has become a spectator\n");
become_spectator();
break;
case SPECTATING_SPECTATING:
bprint(self.netname, " has returned from spectating\n");
setspawnparms(self);
PutClientInServer();
break;
case SPECTATING_DEAD:
centerprint(self,
"You have no life force left\n"
"and cannot return to\n"
"the mortal world yet");
break;
case SPECTATING_INTERMISSION:
case SPECTATING_FINALE:
centerprint(self,
"You are incorporeal as you wait\n"
"for the next cycle\n"
"and cannot return to\n"
"the mortal world yet");
break;
}
};
void() ImpulseCommands = {
if(self.impulse >= 1 && self.impulse <= 8) {
W_ChangeWeapon(self.impulse);
@ -1173,6 +1198,7 @@ void() ImpulseCommands = {
case 10: W_CycleWeapon(); break;
case 11: cheat_quad(); break;
case 12: W_CycleWeaponReverse(); break;
case 13: spectate(); break;
}
}
@ -1545,7 +1571,7 @@ Player entered the suicide command
============
*/
void() ClientKill = {
if(self.view_ofs != VEC_ORIGIN) {
if(!self.spectating) {
reset_death_vel();
self.frags--; // extra penalty
ClientObituary(self, self);

View File

@ -52,15 +52,15 @@ entity msg_entity; // destination of single entity writes
// required prog functions
void() main; // only for testing
void() StartFrame;
void() StartFrame; // called every frame
void() PlayerPreThink;
void() PlayerPostThink;
void() PlayerPreThink; // called each frame on each client before physics
void() PlayerPostThink; // called each frame on each client after physics
void() ClientKill;
void() ClientConnect;
void() ClientKill; // called when the "kill" cmd is entered
void() ClientConnect; // called upon client "connect"
void() PutClientInServer; // call after setting the parm1... parms
void() ClientDisconnect;
void() ClientDisconnect; // called upon client "disconnect"
/* called when a client first connects to a server. sets parms so they can be
* saved off for restarts
@ -271,6 +271,17 @@ void(entity e) setspawnparms = #78;
float(string s) checkextension = #99;
float(string str, string sub, float startpos) strstrofs = #221;
float(string str, float ofs) str2chr = #222;
string(float... c) chr2str = #223;
string(float ccase, float calpha, float cnum, string... s) strconv = #224;
string(float chars, string... s) strpad = #225;
string(string info, string key, string... value) infoadd = #226;
string(string info, string key) infoget = #227;
float(string s1, string s2, float len) strncmp = #228;
float(string s1, string s2) strcasecmp = #229;
float(string s1, string s2, float len) strncasecmp = #230;
// constants -----------------------------------------------------------------|
const vector VEC_ORIGIN = '0 0 0';
@ -485,22 +496,6 @@ enum {
WORLD_BASE,
};
enum {
PRO_NONE,
// alphabetically sorted, based on pronoun.is and what i've seen used
PRO_FAE,
PRO_HE,
PRO_IT,
PRO_SHE,
PRO_THEY,
PRO_XEY,
PRO_ZE_H,
PRO_ZE_Z,
PRO_MAX,
};
enum {
DOOR_START_OPEN = 1,
DOOR_DONT_LINK = 4,
@ -574,6 +569,23 @@ enum {
SIGIL_4 = 8,
};
// super co-op additions
enum {
PRO_NONE,
// alphabetically sorted, based on pronoun.is and what i've seen used
PRO_FAE,
PRO_HE,
PRO_IT,
PRO_SHE,
PRO_THEY,
PRO_XEY,
PRO_ZE_H,
PRO_ZE_Z,
PRO_MAX,
};
enum {
SF_CHEATS = 1,
SF_LIVES_BEG = 1,
@ -581,6 +593,14 @@ enum {
SF_LIVES_MSK = 14,
SF_DIST_AMMO = 16,
};
enum {
SPECTATING_NOT,
SPECTATING_DEAD,
SPECTATING_SPECTATING,
SPECTATING_INTERMISSION,
SPECTATING_FINALE,
};
#pragma noref 0
// globals -------------------------------------------------------------------|
@ -635,6 +655,7 @@ float player_respawned;
float all_players_are_dead;
float ext_con_set;
float ext_strings;
// fields --------------------------------------------------------------------|
@ -753,6 +774,7 @@ float ext_con_set;
// super co-op additions
.float pronoun;
.float lives;
.float spectating;
// functions -----------------------------------------------------------------|
@ -921,4 +943,6 @@ void() func_train_find;
void(vector p) boss_missile;
void() ImpulseCommands;
// EOF

View File

@ -116,15 +116,16 @@ void() finale_1 = {
pl = find(world, classname, "player");
while(pl != world) {
pl.view_ofs = VEC_ORIGIN;
pl.angles = other.v_angle = pos.mangle;
pl.fixangle = TRUE; // turn this way immediately
pl.map = self.map;
pl.nextthink = time + 0.5;
pl.view_ofs = VEC_ORIGIN;
pl.angles = other.v_angle = pos.mangle;
pl.fixangle = TRUE; // turn this way immediately
pl.map = self.map;
pl.nextthink = time + 0.5;
pl.takedamage = DAMAGE_NO;
pl.solid = SOLID_NOT;
pl.movetype = MOVETYPE_NONE;
pl.solid = SOLID_NOT;
pl.movetype = MOVETYPE_NONE;
pl.modelindex = 0;
pl.spectating = SPECTATING_FINALE;
setorigin(pl, pos.origin);
pl = find(pl, classname, "player");
}

View File

@ -1051,12 +1051,6 @@ Called every frame so impulse events can be handled as well as possible
============
*/
void() W_WeaponFrame = {
if(time < self.attack_finished) {
return;
}
ImpulseCommands();
// check for attack
if(self.button0) {
SuperDamageSound();

View File

@ -18,9 +18,8 @@ void() worldspawn = {
InitBodyQue();
if(cvar("pr_checkextension")) {
if(checkextension("DP_CON_SET")) {
ext_con_set = TRUE;
}
ext_con_set = checkextension("DP_CON_SET");
ext_strings = checkextension("FTE_STRINGS");
}
// custom map attributes

2
todo
View File

@ -14,7 +14,6 @@ all done
useful features:
configurable enemy stats
impulse command for spectating
indicators for where other players are
users can cancel map ends ("<name> initiated travel to <mapname>")
@ -34,6 +33,7 @@ corpse pickups have keys
custom pronouns
distributed ammo
enforcers are broken
impulse command for spectating
lives counting
no friendly fire
restart map after 10 seconds when everyone is dead