mirror of
https://github.com/nzp-team/quakec.git
synced 2025-02-16 00:51:57 +00:00
Merge branch 'main' into hud_gameover_nodraw
This commit is contained in:
commit
b862913ea5
23 changed files with 574 additions and 71 deletions
|
@ -20,6 +20,7 @@ defs/standard.qc
|
|||
#endif
|
||||
shared_defs.qc
|
||||
defs/custom.qc
|
||||
map_rotation.qc
|
||||
utilities/sound_helper.qc
|
||||
weapon_stats.qc
|
||||
utilities/math.qc
|
||||
|
@ -28,6 +29,8 @@ hash_table.qc
|
|||
dummies.qc
|
||||
rounds.qc
|
||||
main.qc
|
||||
plugins/plugin_mapvote.qc
|
||||
plugins/plugin_core.qc
|
||||
utilities/weapon_utilities.qc
|
||||
utilities/game_restart.qc
|
||||
utilities/command_parser.qc
|
||||
|
|
|
@ -73,6 +73,7 @@ void() Chase_Update =
|
|||
// take contents of the view leaf
|
||||
viewcontents = pointcontents(cl_vieworigin);
|
||||
|
||||
float attempts = 0;
|
||||
for (best = 0; best < NUM_TESTS; best++) {
|
||||
vector chase_newdest;
|
||||
|
||||
|
@ -85,8 +86,9 @@ void() Chase_Update =
|
|||
{
|
||||
// go back to the previous best as this one is bad
|
||||
// unless the first one was also bad, (viewleaf contents != viewleaf contents!!!)
|
||||
if (best > 0) {
|
||||
if (best > 0 && attempts < 5) {
|
||||
best--;
|
||||
attempts++;
|
||||
} else {
|
||||
best = NUM_TESTS;
|
||||
break;
|
||||
|
@ -139,6 +141,7 @@ void() Chase_Update =
|
|||
if (nummatches == 6) break;
|
||||
}
|
||||
|
||||
|
||||
// find the spot the player is looking at
|
||||
dest[0] = cl_vieworigin[0] + 4096 * v_forward[0];
|
||||
dest[1] = cl_vieworigin[1] + 4096 * v_forward[1];
|
||||
|
|
|
@ -98,6 +98,7 @@ string Chat_GetHexCodeForPlayer(float player_id)
|
|||
case 2: return "^x07E";
|
||||
case 3: return "^xCA0";
|
||||
case 4: return "^x5D0";
|
||||
case 255: return "^xF76";
|
||||
}
|
||||
return "^xFFF";
|
||||
}
|
||||
|
@ -112,7 +113,14 @@ void Chat_Register(int sender, int player_id, string message)
|
|||
// Append name+color to the message
|
||||
string player_hex = Chat_GetHexCodeForPlayer(player_id);
|
||||
string player_name = getplayerkeyvalue(sender, "name");
|
||||
message = strcat(player_hex, player_name, "^xCCC: ", message);
|
||||
|
||||
// Not a server message.
|
||||
if (player_id != 255) {
|
||||
message = strcat(player_hex, player_name, "^xCCC: ", message);
|
||||
} else {
|
||||
message = strcat(player_hex, message);
|
||||
}
|
||||
|
||||
|
||||
if (g_chatlines < (CHAT_LINES - 1)) {
|
||||
g_chatbuffer[g_chatlines + 1] = message;
|
||||
|
|
|
@ -1483,9 +1483,14 @@ void() HUD_Powerups =
|
|||
|
||||
void() HUD_Broadcast =
|
||||
{
|
||||
// broadcast_num refers to the playernum of the client. This
|
||||
// does NOT correspond to player key indexes, so we need to
|
||||
// grab the player with the matching playernum and use infokeys.
|
||||
entity player_target = findfloat(world, playernum, broadcast_num);
|
||||
string broadcast_name = infokey(player_target, INFOKEY_P_NAME);
|
||||
|
||||
string broadcast_msg = "";
|
||||
string broadcast_name = getplayerkeyvalue(broadcast_num - 1, "name");
|
||||
|
||||
|
||||
switch(broadcast_type) {
|
||||
case CSQC_BROADCAST_PLAYERDOWNED:
|
||||
broadcast_msg = strcat(broadcast_name, " needs to be revived");
|
||||
|
|
|
@ -48,18 +48,6 @@ void() ToggleMenu =
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// SetGamepadBindings()
|
||||
// Since our menu architecture currently sucks,
|
||||
// we don't yet support a good binds menu that
|
||||
// let's you get good gamepad control.. so
|
||||
// force these binds (sorry!)
|
||||
//
|
||||
void() SetGamepadBindings =
|
||||
{
|
||||
localcmd("unbindall; bind ~ \"toggleconsole\"; bind ` \"toggleconsole\"; bind ESCAPE \"togglemenu\"; joyadvaxisr 4; joyadvaxisu 2; joyadvaxisx 3; joyadvaxisy -1; bind JOY1 \"+button5\"; bindlevel JOY2 30 \"null\"; bind JOY3 \"+button4\"; bind GP_LSHOULDER \"impulse 33\"; bind AUX1 \"+moveup\"; bind AUX2 \"+button3\"; bindlevel AUX3 30 \"null\"; bindlevel AUX4 30 \"null\"; bind AUX5 \"togglemenu\"; bind AUX10 \"+attack\"; bind GP_Y \"+button4\"; bindlevel GP_A 30 \"impulse 10\"; bindlevel GP_B 30 \"impulse 30\"; bindlevel GP_X 30 \"+button5\"; bindlevel GP_LTHUMB 30 \"impulse 23\"; bindlevel GP_RTHUMB 30 \"+button6\"; bind GP_LTRIGGER \"+button8\"; bindlevel GP_DPAD_DOWN 30 \"+button7\"; bindlevel GP_RSHOULDER 30 \"+button3\"; bindlevel GP_DPAD_RIGHT 30 \"impulse 33\"; bindlevel GP_DPAD_UP 30 \"+button7\"; bind GP_VIEW \"showscores\"; joysidesensitivity 0.8; joyyawsensitivity 0.6; joypitchsensitivity 0.55\n");
|
||||
}
|
||||
|
||||
float(float isnew) SetZombieSkinning =
|
||||
{
|
||||
self.drawmask = MASK_ENGINE;
|
||||
|
@ -904,9 +892,15 @@ noref float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent
|
|||
float last_input_storage = last_input_was_gamepad;
|
||||
|
||||
if (last_input_deviceid > 0)
|
||||
{
|
||||
last_input_was_gamepad = TRUE;
|
||||
setlocaluserinfo(0, "using_gamepad", "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
last_input_was_gamepad = FALSE;
|
||||
setlocaluserinfo(0, "using_gamepad", "0");
|
||||
}
|
||||
|
||||
if (current_menu != MENU_NONE)
|
||||
{
|
||||
|
|
|
@ -623,6 +623,21 @@ static string(string s) str2ascii =
|
|||
return s;
|
||||
};
|
||||
|
||||
//
|
||||
// Menu_CleanUpServerAddress(server_address)
|
||||
// Strips stuff from the CNAME like /udp/ etc.
|
||||
// if it causes conflicts when trying to connect.
|
||||
//
|
||||
string Menu_CleanUpServerAddress(string server_address) =
|
||||
{
|
||||
// Strip /udp/
|
||||
if (substring(server_address, 0, 5) == "/udp/") {
|
||||
return substring(server_address, 5, strlen(server_address) - 5);
|
||||
}
|
||||
|
||||
return server_address;
|
||||
};
|
||||
|
||||
//
|
||||
// Menu_ServerList(id, pos, size, scrollofs, num_servers)
|
||||
// Draw the master server list
|
||||
|
@ -663,7 +678,9 @@ void(string id, vector pos, vector size, __inout vector scrollofs, float num_ser
|
|||
Menu_PlaySound(MENU_SND_ENTER);
|
||||
m_toggle(false);
|
||||
map_name_override = gethostcachestring(gethostcacheindexforkey("map"), index);
|
||||
localcmd("connect ", gethostcachestring(gethostcacheindexforkey("cname"), index), "\n");
|
||||
|
||||
string server_address = Menu_CleanUpServerAddress(gethostcachestring(gethostcacheindexforkey("cname"), index));
|
||||
localcmd("connect ", server_address, "\n");
|
||||
}
|
||||
#endif // MENU
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ string(string prev_id) Menu_Pause_GetNextButton =
|
|||
}
|
||||
}
|
||||
|
||||
if (player_count != 0 && ret == "pm_reloa")
|
||||
if (player_count != 1 && ret == "pm_reloa")
|
||||
ret = "pm_opts";
|
||||
|
||||
if (ret == "")
|
||||
|
@ -47,7 +47,7 @@ string(string next_id) Menu_Pause_GetPreviousButton =
|
|||
}
|
||||
}
|
||||
|
||||
if (player_count != 0 && ret == "pm_reloa")
|
||||
if (player_count != 1 && ret == "pm_reloa")
|
||||
ret = "pm_resum";
|
||||
|
||||
if (ret == "")
|
||||
|
@ -86,7 +86,7 @@ void() Menu_Pause =
|
|||
{
|
||||
Menu_Button(1, "pm_resum", "RESUME CARNAGE", "Return to Game.") ? ToggleMenu() : 0;
|
||||
|
||||
if (player_count == 0)
|
||||
if (player_count == 1)
|
||||
Menu_Button(2, "pm_reloa", "RESTART LEVEL", "Tough luck? Give things another go.") ? Menu_Pause_EnterSubMenu(1) : 0;
|
||||
else
|
||||
Menu_GreyButton(2, "RESTART LEVEL");
|
||||
|
@ -122,4 +122,4 @@ void() Menu_Pause =
|
|||
}
|
||||
|
||||
sui_pop_frame();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -134,7 +134,7 @@ void() Menu_Video_UpdateShowFPS =
|
|||
Menu_PlaySound(MENU_SND_ENTER);
|
||||
current_showfps++;
|
||||
if (current_showfps > 2) current_showfps = 0;
|
||||
localcmd(sprintf("show_fps %d", current_showfps));
|
||||
localcmd(sprintf("show_fps %d\n", current_showfps));
|
||||
};
|
||||
|
||||
void() Menu_Video_UpdateVsync =
|
||||
|
|
|
@ -513,14 +513,26 @@ void (float achievement_id, optional entity who) GiveAchievement =
|
|||
|
||||
#ifdef FTE
|
||||
|
||||
void CSQC_SendChatMessage(float player_id, string message) {
|
||||
void CSQC_SendChatMessage(float player_id, string message) =
|
||||
{
|
||||
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
|
||||
WriteByte(MSG_MULTICAST, EVENT_CHATMESSAGE);
|
||||
WriteByte(MSG_MULTICAST, num_for_edict(self) - 1);
|
||||
WriteByte(MSG_MULTICAST, player_id);
|
||||
WriteString(MSG_MULTICAST, message);
|
||||
multicast('0 0 0', MULTICAST_ALL);
|
||||
}
|
||||
};
|
||||
|
||||
void CSQC_SendChatMessageToPlayer(float player_id, string message, entity who) =
|
||||
{
|
||||
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
|
||||
WriteByte(MSG_MULTICAST, EVENT_CHATMESSAGE);
|
||||
WriteByte(MSG_MULTICAST, num_for_edict(self) - 1);
|
||||
WriteByte(MSG_MULTICAST, player_id);
|
||||
WriteString(MSG_MULTICAST, message);
|
||||
msg_entity = who;
|
||||
multicast('0 0 0', MULTICAST_ONE);
|
||||
};
|
||||
|
||||
/*
|
||||
=================
|
||||
|
|
|
@ -40,19 +40,18 @@ void(entity client) LastStand_Begin;
|
|||
void() Barrel_Hit;
|
||||
void() teddy_react;
|
||||
|
||||
void() EndGame_Restart =
|
||||
{
|
||||
localcmd("restart");
|
||||
}
|
||||
|
||||
// Fade to black function, creates another think for restart
|
||||
void() EndGame_FadePrompt =
|
||||
{
|
||||
nzp_screenflash(self, SCREENFLASH_COLOR_BLACK, 6, SCREENFLASH_FADE_IN);
|
||||
entity players = find(world, classname, "player");
|
||||
while(players != world) {
|
||||
nzp_screenflash(players, SCREENFLASH_COLOR_BLACK, 5, SCREENFLASH_FADE_IN);
|
||||
players = find(players, classname, "player");
|
||||
}
|
||||
|
||||
#ifdef FTE
|
||||
|
||||
self.think = EndGame_Restart;
|
||||
self.think = MapRotation_Decide;
|
||||
|
||||
#else
|
||||
|
||||
|
@ -66,22 +65,16 @@ void() EndGame_FadePrompt =
|
|||
//Actual endgame function, all zombies die, music plays
|
||||
void() EndGame =
|
||||
{
|
||||
local entity oldself;
|
||||
local entity who;
|
||||
|
||||
self.health = 0;
|
||||
self.velocity = '0 0 0';
|
||||
|
||||
oldself = self;
|
||||
|
||||
who = find(world,classname,"ai_zombie");
|
||||
entity old_self;
|
||||
entity who = find(world,classname,"ai_zombie");
|
||||
while(who != world)
|
||||
{
|
||||
if(who.health)
|
||||
{
|
||||
old_self = self;
|
||||
self = who;
|
||||
self.th_die();
|
||||
self = oldself;
|
||||
self = old_self;
|
||||
}
|
||||
|
||||
who = find(who,classname,"ai_zombie");
|
||||
|
@ -135,17 +128,29 @@ float() PollPlayersAlive =
|
|||
// Endgamesetup -- think function for setting up the death of everyone
|
||||
void() EndGameSetup =
|
||||
{
|
||||
self.health = 10;
|
||||
self.think = EndGame;
|
||||
self.nextthink = time + 4;
|
||||
self.weapon = 0;
|
||||
self.currentammo = 0;
|
||||
self.currentmag = 0;
|
||||
self.weaponmodel = "";
|
||||
self.weapon2model = "";
|
||||
self.animend = SUB_Null;
|
||||
self.perks = 0;
|
||||
self.movetype = MOVETYPE_TOSS;
|
||||
entity gameover_watcher = spawn();
|
||||
gameover_watcher.think = EndGame;
|
||||
gameover_watcher.nextthink = time + 4;
|
||||
gameover_watcher.classname = "gameover_watcher";
|
||||
|
||||
entity players = find(world, classname, "player");
|
||||
while(players != world) {
|
||||
players.health = 0;
|
||||
players.weapon = 0;
|
||||
players.currentammo = 0;
|
||||
players.currentmag = 0;
|
||||
players.weaponmodel = "";
|
||||
players.weapon2model = "";
|
||||
players.animend = SUB_Null;
|
||||
players.perks = 0;
|
||||
players.movetype = MOVETYPE_TOSS;
|
||||
players.velocity = '0 0 0';
|
||||
|
||||
Player_RemoveScore(players, players.points);
|
||||
Player_AddScore(players, players.score, false);
|
||||
|
||||
players = find(players, classname, "player");
|
||||
}
|
||||
|
||||
if (!game_over) {
|
||||
Rounds_PlayTransition("sounds/music/end.wav");
|
||||
|
@ -158,7 +163,7 @@ void() EndGameSetup =
|
|||
{
|
||||
Weapon_RemoveWeapon(i);
|
||||
}
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
float() push_away_zombies;
|
||||
|
|
|
@ -43,7 +43,15 @@
|
|||
|
||||
float cheats_have_been_activated;
|
||||
|
||||
// Quake assumes these are defined.
|
||||
// cypress - NOTE
|
||||
// on/off are inverted compared to goldsrc for
|
||||
// compatibility with existing nzp content.
|
||||
#define TRIGGERSTATE_ON 0
|
||||
#define TRIGGERSTATE_OFF 1
|
||||
#define TRIGGERSTATE_TOGGLE 2
|
||||
|
||||
.float triggerstate;
|
||||
|
||||
string string_null;
|
||||
.string killtarget;
|
||||
entity activator;
|
||||
|
|
|
@ -273,6 +273,7 @@ void() misc_model =
|
|||
self.model = "models/missing_model.mdl";
|
||||
}
|
||||
|
||||
self.oldmodel = self.model;
|
||||
|
||||
//
|
||||
// Set default stats.
|
||||
|
@ -384,7 +385,10 @@ void() place_model =
|
|||
#define SPAWNFLAG_COUNTER_RESETONFIRE 2
|
||||
void() game_counter_increment =
|
||||
{
|
||||
self.frags++;
|
||||
if (global_triggerstate == TRIGGERSTATE_OFF)
|
||||
self.frags--;
|
||||
else
|
||||
self.frags++;
|
||||
|
||||
if (self.frags == self.health) {
|
||||
SUB_UseTargets();
|
||||
|
|
|
@ -54,6 +54,8 @@ void() DelayThink =
|
|||
remove(self);
|
||||
};
|
||||
|
||||
float global_triggerstate;
|
||||
|
||||
void() SUB_UseTargets =
|
||||
{
|
||||
local entity t, stemp, otemp, act;
|
||||
|
@ -146,6 +148,7 @@ void() SUB_UseTargets =
|
|||
t.enemy = activator;
|
||||
t.message = self.message;
|
||||
t.killtarget = self.killtarget;
|
||||
t.triggerstate = self.triggerstate;
|
||||
t.target = self.target;
|
||||
t.target2 = self.target2;
|
||||
t.target3 = self.target3;
|
||||
|
@ -189,8 +192,12 @@ void() SUB_UseTargets =
|
|||
t.classname = "spawn_dog";
|
||||
if (self.use != SUB_Null)
|
||||
{
|
||||
global_triggerstate = self.triggerstate;
|
||||
|
||||
if (self.use)
|
||||
self.use();
|
||||
|
||||
global_triggerstate = -1;
|
||||
}
|
||||
self = stemp;
|
||||
other = otemp;
|
||||
|
|
|
@ -215,12 +215,6 @@ void() precaches =
|
|||
precache_model ("models/weapons/grenade/g_grenade.mdl");
|
||||
precache_extra (W_BIATCH);
|
||||
|
||||
// perk machine defaults
|
||||
//precache_model (PERK_QUICKREVIVE_DEFAULT_MODEL);
|
||||
//precache_model (PERK_JUGGERNOG_DEFAULT_MODEL);
|
||||
//precache_model (PERK_SPEEDCOLA_DEFAULT_MODEL);
|
||||
//precache_model (PERK_)
|
||||
|
||||
#ifdef FTE
|
||||
|
||||
// FTE only has co-op, so don't precache morphine elsewhere.
|
||||
|
@ -371,13 +365,17 @@ void() worldspawn =
|
|||
clientstat(STAT_INSTA, EV_FLOAT, insta_icon);
|
||||
clientstat(STAT_X2, EV_FLOAT, x2_icon);
|
||||
clientstat(STAT_SPECTATING, EV_FLOAT, is_spectator);
|
||||
clientstat(STAT_PLAYERNUM, EV_FLOAT, playernum); // literally useless but will be kept in case
|
||||
clientstat(STAT_PLAYERNUM, EV_FLOAT, playernum);
|
||||
clientstat(STAT_PLAYERSTANCE, EV_FLOAT, stance);
|
||||
clientstat(STAT_FACINGENEMY, EV_FLOAT, facingenemy);
|
||||
clientstat(STAT_VIEWZOOM, EV_FLOAT, viewzoom);
|
||||
clientstat(STAT_MAXHEALTH, EV_FLOAT, max_health);
|
||||
clientstat(STAT_PERKS, EV_FLOAT, perks);
|
||||
|
||||
autocvar(sv_enablechatplugins, 0);
|
||||
autocvar(sv_maprotationbasename, "map_rotation");
|
||||
autocvar(sv_maprotationmode, 0);
|
||||
|
||||
#endif // FTE
|
||||
|
||||
mappath = strcat("maps/", mapname);
|
||||
|
|
140
source/server/map_rotation.qc
Normal file
140
source/server/map_rotation.qc
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
server/map_rotation.qc
|
||||
|
||||
Map Rotation Logic for Dedicated Servers.
|
||||
|
||||
Copyright (C) 2021-2024 NZ:P Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
#ifdef FTE
|
||||
|
||||
#define MAPROTATION_MODE_DONTROTATE 0
|
||||
#define MAPROTATION_MODE_FIXEDROTATION 1
|
||||
#define MAPROTATION_MODE_RANDOM 2
|
||||
|
||||
//
|
||||
// MapRotation_RestartMap()
|
||||
// Loads the current map once again.
|
||||
//
|
||||
void() MapRotation_RestartMap =
|
||||
{
|
||||
localcmd(sprintf("changelevel %s\n", mapname));
|
||||
};
|
||||
|
||||
//
|
||||
// MapRotation_GetRandomMap()
|
||||
// Loads a random map in the server's
|
||||
// map directory.
|
||||
//
|
||||
void() MapRotation_GetRandomMap =
|
||||
{
|
||||
searchhandle maps;
|
||||
string map_path;
|
||||
string map_name;
|
||||
float map_count;
|
||||
float map_index;
|
||||
|
||||
// Grab a random map file
|
||||
maps = search_begin("maps/*.bsp", 0, 0);
|
||||
map_count = search_getsize(maps);
|
||||
map_index = rint(random() * map_count);
|
||||
|
||||
map_path = search_getfilename(maps, map_index);
|
||||
map_name = substring(map_path, 5, strlen(map_path)); // maps/
|
||||
map_name = substring(map_name, 0, strlen(map_name) - 4); // .bsp
|
||||
|
||||
search_end(maps);
|
||||
|
||||
localcmd(sprintf("changelevel %s\n", map_name));
|
||||
};
|
||||
|
||||
//
|
||||
// MapRotation_SelectNextMap()
|
||||
// Loads the next map in the map rotation
|
||||
// text file.
|
||||
//
|
||||
void() MapRotation_SelectNextMap =
|
||||
{
|
||||
float rotation_file = fopen(sprintf("%s.txt", cvar_string("sv_maprotationbasename")), FILE_READ);
|
||||
|
||||
if (rotation_file == -1) {
|
||||
MapRotation_GetRandomMap();
|
||||
}
|
||||
|
||||
float load_loop = true;
|
||||
string map_to_load = "";
|
||||
string first_map = fgets(rotation_file);
|
||||
string current_map = first_map;
|
||||
|
||||
while(load_loop) {
|
||||
// End of file, use the first map in the file.
|
||||
if not (current_map) {
|
||||
load_loop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Current line is for our map, so load the next
|
||||
// and then break out.
|
||||
if (mapname == current_map) {
|
||||
map_to_load = fgets(rotation_file);
|
||||
load_loop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
current_map = fgets(rotation_file);
|
||||
}
|
||||
|
||||
// If map_to_load is blank, use the first map in the rotation list.
|
||||
if (map_to_load == "") {
|
||||
map_to_load = first_map;
|
||||
}
|
||||
|
||||
fclose(rotation_file);
|
||||
|
||||
localcmd(sprintf("changelevel %s\n", strtrim(map_to_load)));
|
||||
};
|
||||
|
||||
//
|
||||
// MapRotation_Decide()
|
||||
// Called at game's end, picks an appropriate
|
||||
// map rotation mode.
|
||||
//
|
||||
void() MapRotation_Decide =
|
||||
{
|
||||
float map_rotation_mode = cvar("sv_maprotationmode");
|
||||
|
||||
switch(map_rotation_mode) {
|
||||
case MAPROTATION_MODE_DONTROTATE:
|
||||
MapRotation_RestartMap();
|
||||
break;
|
||||
case MAPROTATION_MODE_FIXEDROTATION:
|
||||
MapRotation_SelectNextMap();
|
||||
break;
|
||||
case MAPROTATION_MODE_RANDOM:
|
||||
MapRotation_GetRandomMap();
|
||||
break;
|
||||
default:
|
||||
MapRotation_RestartMap();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FTE
|
|
@ -204,7 +204,12 @@ void(entity client) LastStand_KillPlayer =
|
|||
self = client;
|
||||
|
||||
DisableReviveIcon(self.playernum);
|
||||
startspectate();
|
||||
|
||||
// If we are in the middle of the Game Over
|
||||
// sequence, we should not allow a bleed
|
||||
// out to override it.
|
||||
if (!game_over)
|
||||
startspectate();
|
||||
|
||||
self = old_self;
|
||||
};
|
||||
|
|
|
@ -592,6 +592,9 @@ void() PlayerPostThink =
|
|||
|
||||
// Network everything
|
||||
self.SendFlags = 1;
|
||||
|
||||
// Used to tell which gun to fire for akimbo weapons
|
||||
self.is_using_gamepad = stof(infokey(self, "using_gamepad"));
|
||||
|
||||
// Obtain menu state from CSQC via infokeys.
|
||||
self.is_in_menu = stof(infokey(self, "in_menu"));
|
||||
|
@ -1048,6 +1051,12 @@ void() PutClientInServer =
|
|||
GameRestart_ResetPerkaColas();
|
||||
};
|
||||
|
||||
void() RestartDedicatedServer =
|
||||
{
|
||||
bprint(PRINT_HIGH, "[INFO]: Issuing server restart as player is connecting to an empty server.\n");
|
||||
localcmd("restart\n");
|
||||
}
|
||||
|
||||
//called when client disconnects from the server
|
||||
void() ClientDisconnect =
|
||||
{
|
||||
|
@ -1079,6 +1088,18 @@ void() ClientDisconnect =
|
|||
self.is_in_menu = 0;
|
||||
|
||||
#endif // FTE
|
||||
|
||||
// We can encounter a player count of zero if we are in a dedicated
|
||||
// server. If this is the case we should restart the current level
|
||||
// to prevent issues with a player joining mid-match stuck in spectator
|
||||
// mode, or spawning in at a high round not able to progress.
|
||||
if (player_count == 0) {
|
||||
// We need to use a temp entity for this to add a delay, else this
|
||||
// will interfere with SP games.
|
||||
entity tempe = spawn();
|
||||
tempe.think = RestartDedicatedServer;
|
||||
tempe.nextthink = time + 0.25;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
47
source/server/plugins/plugin_core.qc
Normal file
47
source/server/plugins/plugin_core.qc
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
server/plugins/plugin_core.qc
|
||||
|
||||
In-game chat plugin core.
|
||||
|
||||
Copyright (C) 2021-2024 NZ:P Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
#ifdef FTE
|
||||
|
||||
//
|
||||
// Plugin command table
|
||||
// command_name : Command string entered into developer console.
|
||||
// command_function : QuakeC function called when command is executed.
|
||||
// Returns 0 for success, 1 for failure.
|
||||
//
|
||||
var struct {
|
||||
string command_name;
|
||||
float(string params) command_function;
|
||||
float requires_arguments;
|
||||
string command_description;
|
||||
} plugin_commands[] = {
|
||||
{"mapvote", ChatPlugin_MapVote, true, "Usage: mapvote <bsp_name>\nAttempts to initiate a Map Vote. Fails if one is already in progress.\n"},
|
||||
{"mapvote_y", ChatPlugin_MapVoteYes, false, "Usage: mapvote_y\n"},
|
||||
{"mapvote_n", ChatPlugin_MapVoteNo, false, "Usage: mapvote_n\n"},
|
||||
|
||||
};
|
||||
|
||||
#endif // FTE
|
186
source/server/plugins/plugin_mapvote.qc
Normal file
186
source/server/plugins/plugin_mapvote.qc
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
server/plugins/plugin_core.qc
|
||||
|
||||
Map Vote plugin for Dedicated Servers.
|
||||
|
||||
Copyright (C) 2021-2024 NZ:P Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
|
||||
Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA
|
||||
|
||||
*/
|
||||
#ifdef FTE
|
||||
|
||||
#define PLUGIN_MAPVOTE_VOTETIMER 60
|
||||
|
||||
.float plugin_mapvote_voted;
|
||||
float plugin_mapvote_in_progress;
|
||||
string plugin_mapvote_mapchoice;
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVoteChange()
|
||||
// Changes map after successful vote.
|
||||
//
|
||||
void() ChatPlugin_MapVoteChange =
|
||||
{
|
||||
localcmd(sprintf("changelevel %s\n", plugin_mapvote_mapchoice));
|
||||
};
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVotePollResults
|
||||
// Called at end of voting. Changes maps if vote
|
||||
// passed.
|
||||
//
|
||||
void() ChatPlugin_MapVotePollResults =
|
||||
{
|
||||
float total_yes_votes = serverkeyfloat("mapvotes_y");
|
||||
float total_no_votes = serverkeyfloat("mapvotes_n");
|
||||
|
||||
if (total_yes_votes > total_no_votes) {
|
||||
CSQC_SendChatMessage(255, sprintf("[MapVote] Vote to switch map to '%s' has PASSED.", plugin_mapvote_mapchoice));
|
||||
CSQC_SendChatMessage(255, "[MapVote] Changing maps in 15 seconds.");
|
||||
self.think = ChatPlugin_MapVoteChange;
|
||||
self.nextthink = time + 15;
|
||||
} else {
|
||||
CSQC_SendChatMessage(255, sprintf("[MapVote] Vote to switch map to '%s' has FAILED.", plugin_mapvote_mapchoice));
|
||||
plugin_mapvote_in_progress = false;
|
||||
remove(self);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVoteClear()
|
||||
// Clears any previous Map Vote infokeys.
|
||||
//
|
||||
void() ChatPlugin_MapVoteClear =
|
||||
{
|
||||
forceinfokey(world, "mapvotes_y", "0");
|
||||
forceinfokey(world, "mapvotes_n", "0");
|
||||
|
||||
entity players = find(world, classname, "player");
|
||||
while (players != world) {
|
||||
players.plugin_mapvote_voted = false;
|
||||
|
||||
players = find(players, classname, "player");
|
||||
}
|
||||
|
||||
entity spectators = find(world, classname, "spectator");
|
||||
while (spectators != world) {
|
||||
spectators.plugin_mapvote_voted = false;
|
||||
|
||||
spectators = find(spectators, classname, "player");
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVoteYes()
|
||||
// "mapvote_y" command, votes Yes on Map Vote.
|
||||
//
|
||||
float(string params) ChatPlugin_MapVoteYes =
|
||||
{
|
||||
// Fail if no map vote is active.
|
||||
if (plugin_mapvote_in_progress == false) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot vote because no Map Vote is active.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fail if we have already voted.
|
||||
if (self.plugin_mapvote_voted == true) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot vote because you have already voted.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
forceinfokey(world, "mapvotes_y", ftos(serverkeyfloat("mapvotes_y") + 1));
|
||||
self.plugin_mapvote_voted = true;
|
||||
CSQC_SendChatMessage(255, sprintf("[MapVote] %s voted YES to change map to '%s'", self.netname, plugin_mapvote_mapchoice));
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVoteNo()
|
||||
// "mapvote_n" command, votes No on Map Vote.
|
||||
//
|
||||
float(string params) ChatPlugin_MapVoteNo =
|
||||
{
|
||||
// Fail if no map vote is active.
|
||||
if (plugin_mapvote_in_progress == false) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot vote because no Map Vote is active.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fail if we have already voted.
|
||||
if (self.plugin_mapvote_voted == true) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot vote because you have already voted.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
forceinfokey(world, "mapvotes_n", ftos(serverkeyfloat("mapvotes_n") + 1));
|
||||
self.plugin_mapvote_voted = true;
|
||||
CSQC_SendChatMessage(255, sprintf("[MapVote] %s voted NO to change map to '%s'", self.netname, plugin_mapvote_mapchoice));
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
//
|
||||
// ChatPlugin_MapVote(params)
|
||||
// "mapvote" command, initiates Map Vote.
|
||||
//
|
||||
float(string params) ChatPlugin_MapVote =
|
||||
{
|
||||
// Fail if a map vote is already in progress.
|
||||
if (plugin_mapvote_in_progress) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot initiate a Map Vote while one is already in progress.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fail if the requested map is the one currently active.
|
||||
if (mapname == params) {
|
||||
CSQC_SendChatMessageToPlayer(255, "[MapVote] Cannot vote for a map that the server is already running.", self);
|
||||
return 1;
|
||||
}
|
||||
|
||||
plugin_mapvote_mapchoice = params;
|
||||
|
||||
// Fail if the requested map does not exist on disk.
|
||||
float bsp = fopen(sprintf("maps/%s.bsp", plugin_mapvote_mapchoice), FILE_READ);
|
||||
if (bsp == -1) {
|
||||
CSQC_SendChatMessageToPlayer(255, sprintf("[MapVote] Could not find map '%s' on server.", plugin_mapvote_mapchoice), self);
|
||||
return 1;
|
||||
}
|
||||
fclose(bsp);
|
||||
|
||||
// Reset any old votes.
|
||||
ChatPlugin_MapVoteClear();
|
||||
|
||||
plugin_mapvote_in_progress = true;
|
||||
|
||||
CSQC_SendChatMessage(255, sprintf("[MapVote] Begining vote for map '%s'", plugin_mapvote_mapchoice));
|
||||
CSQC_SendChatMessage(255, "Vote with 'mapvote_y' or 'mapvote_n' to switch maps.");
|
||||
CSQC_SendChatMessage(255, sprintf("Voting will end in %d seconds.", PLUGIN_MAPVOTE_VOTETIMER));
|
||||
|
||||
// Spawn a temporary entity to handle polling results.
|
||||
entity tempe = spawn();
|
||||
tempe.classname = "plugin_mapvote_poller";
|
||||
tempe.think = ChatPlugin_MapVotePollResults;
|
||||
tempe.nextthink = time + PLUGIN_MAPVOTE_VOTETIMER;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
#endif // FTE
|
|
@ -258,11 +258,13 @@ void() NewRound =
|
|||
} else {
|
||||
roundtype = 1;
|
||||
|
||||
if (world.fog) {
|
||||
localcmd(strcat("fog ", world.fog));
|
||||
} else {
|
||||
localcmd("fog 0 0 0 0 0\n");
|
||||
}
|
||||
string fog_value;
|
||||
if (world.fog)
|
||||
fog_value = strcat(world.fog, "\n");
|
||||
else
|
||||
fog_value = "0 0 0 0 0\n";
|
||||
|
||||
localcmd(strcat("fog ", fog_value));
|
||||
}
|
||||
|
||||
Remaining_Zombies = Total_Zombies = getZombieTotal();
|
||||
|
|
|
@ -196,10 +196,27 @@ float(string params) Command_say =
|
|||
{
|
||||
// Grab parameters.
|
||||
tokenize(params);
|
||||
string value = argv(0);
|
||||
string first_word = argv(0);
|
||||
|
||||
#ifdef FTE
|
||||
|
||||
if (cvar("sv_enablechatplugins")) {
|
||||
// Support for custom chat commands.
|
||||
for (float i = 0; i < plugin_commands.length; i++) {
|
||||
// Command names match
|
||||
if (first_word == plugin_commands[i].command_name) {
|
||||
// Return description if no params are given
|
||||
if (argv(1) == "" && plugin_commands[i].requires_arguments == true) {
|
||||
CSQC_SendChatMessageToPlayer(255, plugin_commands[i].command_description, self);
|
||||
} else {
|
||||
plugin_commands[i].command_function(argv(1));
|
||||
}
|
||||
|
||||
return COMMAND_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CSQC_SendChatMessage(self.playernum, params);
|
||||
|
||||
#endif // FTE
|
||||
|
|
|
@ -1912,9 +1912,20 @@ void() WeaponCore_FireButtonPressed =
|
|||
// to which click of the mouse you've provided, which is different behavior
|
||||
// to single-hand weapons, where left click fires its right-side. (Hooray
|
||||
// for ternaries!).
|
||||
// For gamepads, we don't want this behavior, because the gun that
|
||||
// fires will be the wrong side
|
||||
#ifdef FTE
|
||||
|
||||
(IsDualWeapon(self.weapon)) ? W_Fire(S_LEFT) : W_Fire(S_RIGHT);
|
||||
if (self.is_using_gamepad)
|
||||
{
|
||||
// Gamepads always fire the right side
|
||||
W_Fire(S_RIGHT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, fire the side of the mouse click for akimbo weapons
|
||||
(IsDualWeapon(self.weapon)) ? W_Fire(S_LEFT) : W_Fire(S_RIGHT);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
@ -1959,8 +1970,17 @@ void() WeaponCore_AimButtonPressed =
|
|||
// fire our secondary as opposed to Aiming down the Sight.
|
||||
if (IsDualWeapon(self.weapon)) {
|
||||
#ifdef FTE
|
||||
// On KBM this is the right click, so it should fire the right weapon.
|
||||
// On gamepads, this is the left trigger, so it should fire the left weapon.
|
||||
|
||||
W_Fire(S_RIGHT);
|
||||
if (self.is_using_gamepad)
|
||||
{
|
||||
W_Fire(S_LEFT);
|
||||
}
|
||||
else
|
||||
{
|
||||
W_Fire(S_RIGHT);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
|
|
@ -316,6 +316,7 @@ float game_over;
|
|||
|
||||
#ifdef FTE
|
||||
.float is_in_menu;
|
||||
.float is_using_gamepad;
|
||||
#endif // FTE
|
||||
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue