diff --git a/3zb2/pak10.pak b/3zb2/pak10.pak index fb278f6..bac11fc 100644 Binary files a/3zb2/pak10.pak and b/3zb2/pak10.pak differ diff --git a/3zb2/zig.cfg b/3zb2/zig.cfg index bf639a0..eb4b7c3 100644 --- a/3zb2/zig.cfg +++ b/3zb2/zig.cfg @@ -6,11 +6,13 @@ exec addbot.cfg set dmflags 16388 set maxclients 8 set autospawn 3 +set autobot 0 set zigmode 1 set zigspawn 1 set zigkiller 1 set zigrapple 0 +set zigintro 0 set respawn_protection 1 set ctf 0 set aimfix 1 diff --git a/CONFIG.txt b/CONFIG.txt index f56c00d..faa6687 100644 --- a/CONFIG.txt +++ b/CONFIG.txt @@ -48,11 +48,13 @@ Console commands vwep Visible weapon on/off (default on = 1, off = 0) maplist Set the maplist section name (default = "default") autospawn Autospawn (default 0, x = number of bots) +autobot Manage bots based on real clients (default 0, x = number of clients) chedit Chaining mode on/off (default off = 0, on = 1) zigmode Capture and Hold (ZigFlag) (default off = 0, on = 1) zigspawn ZigFlag returns home after 60s (default on = 1, off = 0) zigkiller ZigFlag holder kill gives bonus frag (default on = 1, off = 0) -zgrapple Add CTF grapple to none CTF game s(default off = 0, on = 1) +zigrapple Add CTF grapple to none CTF games (default off = 0, on = 1) +zigintro Add spectator mode on game start (default off = 0, on = 1) botlist Set the botlist section name (default = "default") ctf CTF mode on/off (default off = 0, on = 1) aimfix Enable more accurate aiming (default on = 1, off = 0) diff --git a/README.md b/README.md index 3322034..d46e5f0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a custom port of the 3rd Zigock Bot II to Quake II - Yamagi Quake II is recommended. \ All warnings (up to GCC9) and unused variables have been addressed in the original source. \ The code also has handpicked backport fixes, enhancements and features applied from various \ -sources: `tastyspleen`, `yquake2`, `OpenTDM`, `OpenFFA` and custom. +sources: `tastyspleen`, `yquake2`, `OpenTDM`, `OpenFFA` and many custom. This was modified for my own use and driven by nostalgia for the Quake II servers of the 90's. \ There are many heavily modified versions of the Quake II engine, this mod tries to keep the look and feel of \ @@ -17,17 +17,19 @@ On global linux installs, **e.g.** `/usr/share/games`, you may need to specify ` Bot chaining routes are supplied, further routes can be (re)created via the mod command `chedit` (See `CONFIG.txt`) -### ZigMode ZigFlag (Capture and Hold) +### ZigMode ZigFlag (Capture and Hold) - https://zigflag.net The premise is simple: **Get the flag and keep it** - *plays on standard Deathmatch maps*. The original `zigmode` was released belated, buggy and only half implemented, I attempted to make this feature a little \ -more refined, just for fun. I was trying to keep the look and feel of the original deathmatch, but with a few bells and whistles. +more refined, just for fun. I was trying to keep the look and feel of the original deathmatch, but with a few bells and \ +whistles. However `zigflag` turned into a fairly customised game. * Simple HUD enhancements. +* Automatic bot control. * Autospawn bots at level start. * Visual/Audio notifications to Flagholder. -* Flagholder dogtag displayed on scoreboard. +* Customised dogtags displayed on scoreboard. * Optional Flag respawn feature. * Optional Flagholder frag bonus. * Optional Flag sucks health from subdued holder. @@ -41,6 +43,8 @@ more refined, just for fun. I was trying to keep the look and feel of the origin ..and many bugfixes was the final outcome of playing around with the code. +A ZigFlag server can sometimes be found running at `quake2://quake.zigflag.net:27910` + The mod also supports skin and model teams with appropriate bonuses and penalties on Flag possession and `FRIENDLY_FIRE`. The changes subtly alter the game dynamics and improve on the original zigmode game element, IMHO. \ @@ -61,6 +65,7 @@ set zigmode 1 set zigspawn 1 set zigkiller 1 set zigrapple 0 +set zigintro 0 set ctf 0 set aimfix 1 set combathud 1 @@ -71,6 +76,7 @@ set playerid 1 set weaponswap 1 set botlist default set autospawn 3 +set autobot 0 set vwep 1 set maxclients 16 set respawn_protection 1 @@ -157,6 +163,9 @@ sudo cp release/game.so /usr/share/games/quake2/3zb2 To start `$` bots automatically, append: +set autospawn $ + +set autobot $ + +`autobot` will automatically **remove/add** `autospawn` number of bots dependant on `autobot` value of real clients connected ### Spawn bots at the farthest point @@ -230,6 +239,10 @@ Option to add grapple to the fray - CTF `pak0.pak` required. zigrapple 1 +Add spectator mode to game start (server mode) + + zigintro 1 + `Capture and Hold (ZigFlag)` mode for Deathmatch/Team games: zigmode 1 diff --git a/src/bot/func.c b/src/bot/func.c index 22b8d6c..75f33e7 100644 --- a/src/bot/func.c +++ b/src/bot/func.c @@ -884,7 +884,7 @@ void Bot_LevelChange() SpawnWaitingBots = k; - if (autospawn->value > 0) { + if (autospawn->value > 0 && !autobot->value) { int delta = autospawn->value - SpawnWaitingBots; while (delta-- > 0) { SpawnBotReserving(); @@ -1147,3 +1147,101 @@ void Cmd_AirStrike(edict_t *ent) gi.linkentity (viper); } +/* +=================================== + +Return the number of real clients + No BOTS + +=================================== +*/ +int RealClients () +{ + int i; + int realclients = 0; + edict_t *ent; + + for (i = 0; i < maxclients->value; i++) + { + ent = g_edicts + 1 + i; + + if (!ent->inuse) + continue; + + if (ent->client && !(ent->svflags & SVF_MONSTER)) + { + if(zigintro->value) { + if(ent->client->pers.joined) + realclients++; + } + else + realclients++; + } + } + return realclients; +} + +/* +=================================== + +Return the number of BOT clients + +=================================== +*/ +int BotClients () +{ + int i; + int botclients = 0; + edict_t *ent; + + for (i = 0; i < maxclients->value; i++) + { + ent = g_edicts + 1 + i; + + if (!ent->inuse) + continue; + + if (ent->client && (ent->svflags & SVF_MONSTER)) + botclients++; + } + return botclients; +} + +/* +=================================== + +Control Bots in 'autobot' + +=================================== +*/ +void AutoBot () +{ + int clients,bots,i; + int delay = 300; + int buffer = 10 * autospawn->value; + int remaining = timelimit->value * 60 - level.time; + float startup = 15.0f; + + clients = RealClients(); + bots = BotClients(); + + if(level.time < startup) + delay = 100; + + if(clients > autobot->value && bots > 0 && level.time > startup + && remaining > 10) { + level.autobotframe = level.framenum; + for (i=0 ; i < bots ; i++) + RemoveBot(); + } + + if(clients <= autobot->value && bots == 0 && remaining > buffer + && (level.framenum - level.autobotframe) > delay && !level.intermissiontime) { + for (i=0 ; i < autospawn->value ; i++) { + int delta = autospawn->value - SpawnWaitingBots; + while (delta-- > 0) { + SpawnBotReserving(); + } + } + } +} diff --git a/src/g_cmds.c b/src/g_cmds.c index dd534d4..dd5bfb1 100644 --- a/src/g_cmds.c +++ b/src/g_cmds.c @@ -573,6 +573,12 @@ void Cmd_InvUse_f (edict_t *ent) { gitem_t *it; + if (ent->client->resp.spectator && !ent->client->pers.joined) { + ent->client->pers.joined = true; + spectator_respawn(ent); + return; + } + //ZOID if (ent->client->menu) { PMenu_Select(ent); diff --git a/src/g_main.c b/src/g_main.c index e5b32f7..aff0693 100644 --- a/src/g_main.c +++ b/src/g_main.c @@ -66,10 +66,12 @@ cvar_t *vwep; cvar_t *maplist; cvar_t *botlist; cvar_t *autospawn; +cvar_t *autobot; cvar_t *zigmode; cvar_t *zigspawn; cvar_t *zigkiller; cvar_t *zigrapple; +cvar_t *zigintro; cvar_t *spawnbotfar; cvar_t *respawn_protection; int flagbounce; @@ -397,7 +399,7 @@ CheckDMRules */ void CheckDMRules (void) { - int i; + int i; gclient_t *cl; if (level.intermissiontime) @@ -754,6 +756,10 @@ void G_RunFrame (void) } + // autobot control + if(autobot->value && autospawn->value && level.framenum & 8) + AutoBot (); + // see if it is time to end a deathmatch CheckDMRules (); diff --git a/src/g_misc.c b/src/g_misc.c index 93e0074..c566020 100644 --- a/src/g_misc.c +++ b/src/g_misc.c @@ -43,9 +43,9 @@ void VelocityForHead (int damage, vec3_t v) v[0] = damage * 10.0 * crandom(); v[1] = damage * 10.0 * crandom(); - v[2] = 400.0 * crandom(); + v[2] = 100.0 * random(); - VectorScale (v, 0.7, v); + VectorScale (v, 0.35, v); } void VelocityForDamage (int damage, vec3_t v) diff --git a/src/g_save.c b/src/g_save.c index 7a70d2f..685c313 100644 --- a/src/g_save.c +++ b/src/g_save.c @@ -166,12 +166,16 @@ void InitGame (void) maplist = gi.cvar ("maplist", "default", CVAR_SERVERINFO | CVAR_LATCH); //autospawn autospawn = gi.cvar ("autospawn", "0", CVAR_SERVERINFO | CVAR_LATCH); + //autobot + autobot = gi.cvar ("autobot", "0", CVAR_SERVERINFO | CVAR_LATCH); //chain edit flag chedit = gi.cvar ("chedit", "0", CVAR_LATCH); //vwep support vwep = gi.cvar ("vwep", "1", CVAR_LATCH); //game mode zigmode = gi.cvar ("zigmode", "0", CVAR_SERVERINFO| CVAR_LATCH); + //game setup + zigintro = gi.cvar ("zigintro", "0", CVAR_LATCH); //ZOID //This game.dll only supports deathmatch if (!deathmatch->value) { diff --git a/src/g_spawn.c b/src/g_spawn.c index f9e2026..9ae5b9e 100644 --- a/src/g_spawn.c +++ b/src/g_spawn.c @@ -305,6 +305,27 @@ spawn_t spawns[] = { {NULL, NULL} }; +/* +=============== +Set the Observe mode string +=============== +*/ +void CSObserve() +{ + char observe[32]; + char tab[4]; + char end[16]; + char Highlight[MAX_STRING_CHARS]; + + sprintf(observe, "["); + sprintf(tab, "ENT"); + sprintf(end, "] to Join"); + HighlightStr(Highlight, tab, MAX_STRING_CHARS); + strcat(observe, Highlight); + strcat(observe, end); + gi.configstring(CS_OBSERVE, observe); +} + /* =============== ED_CallSpawn @@ -1052,6 +1073,7 @@ void SpawnEntities (char *mapname, char *entities, char *spawnpoint) CTFSetupNavSpawn(); //ナビの設置 if(!chedit->value) G_FindItemLink(); //アイテムのリンク(通常時のみ) + CSObserve(); G_SpawnRouteLink(); if(zigmode->value == 1) zigflag_spawn = 1; @@ -1293,10 +1315,10 @@ char *zig_statusbar = // Chase Cam "if 16 " "xv 0 " - "yb -48 " - "xl 260 " - "string \"Chasing:\" " - "xv 170 " + "yb -80 " + "xl 230 " + "string \"Observing:\" " + "xv 160 " "stat_string 16 " "endif " @@ -1322,7 +1344,7 @@ char *zig_statusbar = // countdown "if 30" "xr -50 " - "yb -60 " + "yb -65 " "xv 105 " "stat_string 30 " "endif " @@ -1418,6 +1440,7 @@ void SP_worldspawn (edict_t *ent) gi.configstring (CS_STATUSBAR, zig_statusbar); gi.imageindex("i_zig"); gi.imageindex("zigtag"); + gi.imageindex("spectag"); } else { gi.configstring (CS_STATUSBAR, dm_statusbar); diff --git a/src/header/bot.h b/src/header/bot.h index e16a815..3b95a8c 100644 --- a/src/header/bot.h +++ b/src/header/bot.h @@ -68,6 +68,7 @@ void Get_WaterState(edict_t *ent); void Bot_Think (edict_t *self); void PutBotInServer (edict_t *ent); void SpawnBotReserving2(int *red,int *blue); +void AutoBot(); //Combat AI void Combat_Level0(edict_t *ent,int foundedenemy,int enewep,float aim,float distance,int skill); diff --git a/src/header/local.h b/src/header/local.h index 0c711f7..6044d7c 100644 --- a/src/header/local.h +++ b/src/header/local.h @@ -28,8 +28,11 @@ // Timer #define CS_TIME (CS_GENERAL + 1) +// Observer notice +#define CS_OBSERVE (CS_GENERAL + 2) + // Player ID -#define CS_PLAYERNAMES (CS_GENERAL + 2) +#define CS_PLAYERNAMES (CS_GENERAL + 3) // Immune on respawn (n * FRAMETIME) sec #define SPAWNPROTECT 10 @@ -350,6 +353,7 @@ typedef struct typedef struct { int framenum; + int autobotframe; float time; char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) @@ -621,10 +625,12 @@ extern cvar_t *vwep; extern cvar_t *maplist; extern cvar_t *botlist; extern cvar_t *autospawn; +extern cvar_t *autobot; extern cvar_t *zigmode; extern cvar_t *zigspawn; extern cvar_t *zigkiller; extern cvar_t *zigrapple; +extern cvar_t *zigintro; extern cvar_t *respawn_protection; extern cvar_t *spawnbotfar; extern cvar_t *killerflag; @@ -722,7 +728,6 @@ void G_UseTargets (edict_t *ent, edict_t *activator); void G_SetMovedir (vec3_t angles, vec3_t movedir); void CPRepeat (char input , int count ); - void G_InitEdict (edict_t *e); edict_t *G_Spawn (void); void G_FreeEdict (edict_t *e); @@ -850,6 +855,7 @@ edict_t *PlayerTrail_LastSpot (void); // g_client.c // void respawn (edict_t *ent); +void spectator_respawn (edict_t *ent); void BeginIntermission (edict_t *targ); void PutClientInServer (edict_t *ent); void InitClientPersistant (gclient_t *client); @@ -978,6 +984,7 @@ typedef struct int game_helpchanged; int helpchanged; + qboolean joined; // client has joined the game qboolean spectator; // client is a spectator } client_persistant_t; @@ -1157,6 +1164,7 @@ struct gclient_s qboolean update_chase; //ZOID zgcl_t zc; //zigock client info + qboolean joined; }; @@ -1262,7 +1270,7 @@ struct edict_s int max_health; int gib_health; int deadflag; - qboolean show_hostile; + qboolean show_hostile; float powerarmor_time; diff --git a/src/player/client.c b/src/player/client.c index cd14b20..1b1b256 100644 --- a/src/player/client.c +++ b/src/player/client.c @@ -734,6 +734,8 @@ void InitClientPersistant (gclient_t *client) if(ctf->value || zigrapple->value) client->pers.inventory[ITEM_INDEX(item)] = 1; //ponpoko //ZOID + if(client->joined) + client->pers.joined = true; client->pers.health = 100; client->pers.max_health = 100; @@ -1261,6 +1263,23 @@ void respawn (edict_t *self) gi.AddCommandString ("menu_loadgame\n"); } +/* + * called on zigintro + */ +void check_spectator_limit(edict_t *ent) +{ + int i, numspec; + + for (i = 1, numspec = 0; i <= maxclients->value; i++) + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + gi.cprintf(ent, PRINT_HIGH, "Spectator limit is full, dropping into game.\n"); + ent->client->pers.spectator = false; + } +} + /* * only called when pers.spectator changes * note that resp.spectator should be the opposite of pers.spectator here @@ -1335,7 +1354,7 @@ void spectator_respawn (edict_t *ent) ent->client->respawn_time = level.time; - if (ent->client->pers.spectator) + if (ent->client->pers.spectator) gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); else gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); @@ -1381,6 +1400,9 @@ void PutClientInServer (edict_t *ent) index = ent-g_edicts-1; client = ent->client; + if(zigintro->value && client->pers.joined) + client->joined = client->pers.joined; + // deathmatch wipes most client data every spawn if (deathmatch->value) { @@ -1414,6 +1436,11 @@ void PutClientInServer (edict_t *ent) memset (&resp, 0, sizeof(resp)); } + if(zigintro->value && !client->joined) { + client->pers.spectator = true; + check_spectator_limit(ent); + } + // clear everything but the persistant data saved = client->pers; memcpy (&zgcl,&client->zc,sizeof(zgcl_t)); @@ -1739,6 +1766,14 @@ void ClientUserinfoChanged (edict_t *ent, char *userinfo) else ent->client->pers.spectator = false; + if(ent->client->pers.joined) + { + if(*s && strcmp(s, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + } + // set skin s = Info_ValueForKey (userinfo, "skin"); @@ -2582,6 +2617,10 @@ void ClientBeginServerFrame (edict_t *ent) if (deathmatch->value && client->pers.spectator != client->resp.spectator && (level.time - client->respawn_time) >= 5) { + + if(zigintro->value && !client->pers.joined) + return; + spectator_respawn(ent); return; } diff --git a/src/player/hud.c b/src/player/hud.c index f49ecc9..957c38e 100644 --- a/src/player/hud.c +++ b/src/player/hud.c @@ -151,10 +151,12 @@ void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) int sorted[MAX_CLIENTS]; int sortedscores[MAX_CLIENTS]; int score, total, rtotal; + int inuse = 1; int x, y; gclient_t *cl; edict_t *cl_ent; char *tag, *mark; + static qboolean done = false; // protect bprintf() against SZ_Getspace error int broadcast = 16; @@ -174,6 +176,8 @@ void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) cl_ent = g_edicts + 1 + i; if (!cl_ent->inuse) continue; + if (cl_ent->client->pers.connected && !ENT_IS_BOT(cl_ent)) + inuse = i + 1; score = game.clients[i].resp.score; for (j=0 ; jvalue && zigspawn->value && flagbounce > 0) { @@ -248,6 +252,9 @@ void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) else tag = NULL; + if (zigintro->value && !ENT_IS_BOT(cl_ent) && !cl_ent->client->pers.joined) + tag = "spectag"; + if(zigmode->value) if(killer != NULL && cl_ent == killer) tag = "zigtag"; @@ -263,7 +270,7 @@ void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) stringlength += j; } - if(level.intermissiontime && ent == &g_edicts[1] && rtotal <= broadcast && i < topresult) + if(level.intermissiontime && !done && ent == &g_edicts[inuse] && rtotal <= broadcast && i < topresult) { if(tag && strcmp(tag, "zigtag") == 0) mark = "F"; @@ -290,8 +297,13 @@ void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) gi.WriteByte (svc_layout); gi.WriteString (string); - if(level.intermissiontime && ent == &g_edicts[1] && rtotal <= broadcast) + if(level.intermissiontime && !done && ent == &g_edicts[inuse] && rtotal <= broadcast) CPRepeat('-', 54); + + if(level.intermissiontime) + done = true; + else + done = false; } @@ -592,7 +604,7 @@ void G_WriteTime(int remaining) int min = remaining / 60; int i; - sprintf(message, "[ Time:"); + sprintf(message, "[ Time: "); sprintf(end, " ]"); if(remaining < 0) @@ -781,7 +793,7 @@ void G_SetStats (edict_t *ent) // rank and time // if(zigmode->value && combathud->value && (level.framenum&8) - && !ent->client->pers.spectator && !level.intermissiontime) + && !level.intermissiontime) { ent->client->ps.stats[STAT_RANK] = ent->client->pers.rank; @@ -859,6 +871,12 @@ void G_SetSpectatorStats (edict_t *ent) cl->ps.stats[STAT_SPECTATOR] = 1; cl->ps.stats[STAT_VIEWID] = 0; + cl->ps.stats[STAT_RANK] = 0; + + if(combathud->value) + cl->ps.stats[STAT_TIME] = CS_TIME; + else + cl->ps.stats[STAT_TIME] = 0; // layouts are independant in spectator cl->ps.stats[STAT_LAYOUTS] = 0; @@ -871,7 +889,7 @@ void G_SetSpectatorStats (edict_t *ent) cl->ps.stats[STAT_CHASE] = CS_PLAYERNAMES + (cl->chase_target - g_edicts) - 1; else - cl->ps.stats[STAT_CHASE] = 0; + cl->ps.stats[STAT_CHASE] = CS_OBSERVE; } /* diff --git a/src/player/weapon.c b/src/player/weapon.c index 4f7fe60..0e60873 100644 --- a/src/player/weapon.c +++ b/src/player/weapon.c @@ -608,7 +608,12 @@ void Weapon_Generic2 (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) { ent->client->weaponstate = WEAPON_DROPPING; - ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + + if(weaponswap->value) + ent->client->ps.gunframe = FRAME_DEACTIVATE_LAST; + else + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + // ### Hentai ### BEGIN if((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) { diff --git a/windows/gamex86.dll b/windows/gamex86.dll index 39eb1ba..678e992 100644 Binary files a/windows/gamex86.dll and b/windows/gamex86.dll differ diff --git a/windows/gamex86_64.dll b/windows/gamex86_64.dll index 101474d..2e6f930 100644 Binary files a/windows/gamex86_64.dll and b/windows/gamex86_64.dll differ diff --git a/windows/path.diff b/windows/path.diff index 143a9a7..1656bed 100644 --- a/windows/path.diff +++ b/windows/path.diff @@ -44,10 +44,10 @@ index 7da1da0..b7583d9 100644 fpout = fopen(name,"rb"); if(fpout == NULL) diff --git a/src/g_main.c b/src/g_main.c -index e5b32f7..d1bea09 100644 +index 48f558a..edb5a21 100644 --- a/src/g_main.c +++ b/src/g_main.c -@@ -225,8 +225,8 @@ void Get_NextMap() +@@ -227,8 +227,8 @@ void Get_NextMap() if(!maplist->string) return; @@ -78,10 +78,10 @@ index c694cd5..7955ffc 100644 fpout = fopen(name,"wb"); if(fpout == NULL) gi.cprintf(NULL,PRINT_HIGH,"Can't open %s\n",name); diff --git a/src/header/local.h b/src/header/local.h -index 0c711f7..8a70923 100644 +index f976a2a..b86ccd8 100644 --- a/src/header/local.h +++ b/src/header/local.h -@@ -566,10 +566,10 @@ extern int meansOfDeath; +@@ -570,10 +570,10 @@ extern int meansOfDeath; extern edict_t *g_edicts;