From e309f6038e89d64de67b3fde2e1f41b916f83592 Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Sat, 18 Mar 2017 23:32:05 +0000 Subject: [PATCH] Single player game mode --- qcsrc/common/mapinfo.qh | 18 +++ qcsrc/server/g_world.qc | 1 + qcsrc/server/mutators/mutator/_mod.inc | 1 + qcsrc/server/mutators/mutator/_mod.qh | 1 + .../mutators/mutator/gamemode_singleplayer.qc | 110 ++++++++++++++++++ .../mutators/mutator/gamemode_singleplayer.qh | 35 ++++++ 6 files changed, 166 insertions(+) create mode 100644 qcsrc/server/mutators/mutator/gamemode_singleplayer.qc create mode 100644 qcsrc/server/mutators/mutator/gamemode_singleplayer.qh diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh index a3e000d33..2958903b8 100644 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@ -477,6 +477,24 @@ CLASS(Invasion, Gametype) ENDCLASS(Invasion) REGISTER_GAMETYPE(INVASION, NEW(Invasion)); +CLASS(SinglePlayer, Gametype) + INIT(SinglePlayer) + { + this.gametype_init(this, _("SinglePlayer"),"sp","g_singleplayer",true,"","",_("Explore the map and defeat the enemies")); + } + METHOD(SinglePlayer, m_isTwoBaseMode, bool()) + { + return true; + } + METHOD(SinglePlayer, m_generate_mapinfo, void(Gametype this, string v)) + { + if(v == "info_player_singleplayer") + MapInfo_Map_supportedGametypes |= this.m_flags; + } +ENDCLASS(SinglePlayer) +REGISTER_GAMETYPE(SINGLE_PLAYER, NEW(SinglePlayer)); +#define g_singleplayer IS_GAMETYPE(SINGLE_PLAYER) + const int MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps const int MAPINFO_FEATURE_VEHICLES = 2; const int MAPINFO_FEATURE_TURRETS = 4; diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 71c731a30..2bcdd573c 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -279,6 +279,7 @@ void cvar_changes_init() BADCVAR("g_race_laps_limit"); BADCVAR("g_race_qualifying_timelimit"); BADCVAR("g_race_qualifying_timelimit_override"); + BADCVAR("g_singleplayer"); BADCVAR("g_snafu"); BADCVAR("g_tdm"); BADCVAR("g_tdm_teams"); diff --git a/qcsrc/server/mutators/mutator/_mod.inc b/qcsrc/server/mutators/mutator/_mod.inc index 6835f5d56..92c180d44 100644 --- a/qcsrc/server/mutators/mutator/_mod.inc +++ b/qcsrc/server/mutators/mutator/_mod.inc @@ -11,4 +11,5 @@ #include #include #include +#include #include diff --git a/qcsrc/server/mutators/mutator/_mod.qh b/qcsrc/server/mutators/mutator/_mod.qh index aef0b332a..b3ca9c698 100644 --- a/qcsrc/server/mutators/mutator/_mod.qh +++ b/qcsrc/server/mutators/mutator/_mod.qh @@ -11,4 +11,5 @@ #include #include #include +#include #include diff --git a/qcsrc/server/mutators/mutator/gamemode_singleplayer.qc b/qcsrc/server/mutators/mutator/gamemode_singleplayer.qc new file mode 100644 index 000000000..e2dd8ab5b --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_singleplayer.qc @@ -0,0 +1,110 @@ +#include "gamemode_singleplayer.qh" + +/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32) +Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map. +Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. +Keys: +"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... +"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ +spawnfunc(sp_team) +{ + if(!g_singleplayer || !this.cnt) { delete(this); return; } + + this.classname = "sp_team"; + this.team = this.cnt + 1; +} + +// code from here on is just to support maps that don't have team entities +void sp_SpawnTeam (string teamname, int teamcolor) +{ + entity this = new_pure(sp_team); + this.netname = teamname; + this.cnt = teamcolor - 1; + this.team = teamcolor; + this.spawnfunc_checked = true; + //spawnfunc_sp_team(this); +} + + +// spawnfuncs +spawnfunc(info_player_singleplayer) +{ + if (!g_singleplayer) { delete(this); return; } + + this.team = NUM_TEAM_1; + spawnfunc_info_player_deathmatch(this); +} + +spawnfunc(info_player_singleplayer_enemy) +{ + if (!g_singleplayer) { delete(this); return; } + + this.team = NUM_TEAM_2; + spawnfunc_info_player_deathmatch(this); +} + +void sp_DelayedInit(entity this) +{ + // if no teams are found, spawn defaults + if(find(NULL, classname, "sp_team") == NULL) + { + LOG_TRACE("No \"sp_team\" entities found on this map, creating them anyway."); + + sp_SpawnTeam("Player", NUM_TEAM_1); + sp_SpawnTeam("Enemy", NUM_TEAM_2); + } +} + +MUTATOR_HOOKFUNCTION(sp, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + LOG_TRACEF("\n==== here ====\n"); + M_ARGV(1, string) = "sp_team"; + entity ent = M_ARGV(2, entity); + if ( IS_BOT_CLIENT(ent) ) + { + ent.team_forced = NUM_TEAM_2; + } + else if( IS_PLAYER(ent) ) + { + ent.team_forced = NUM_TEAM_1; + } + LOG_TRACEF("\n==== After processing ====\nent: %s\nteam: %d\n\n", + etos(ent), ent.team_forced); + return true; +} + +MUTATOR_HOOKFUNCTION(sp, Scores_CountFragsRemaining) +{ + return false; +} + + +.bool can_drop_weapon; +.string weapon_name; + +MUTATOR_HOOKFUNCTION(sp, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + entity spawn_spot = M_ARGV(1, entity); + if ( IS_BOT_CLIENT(player) ) + { + player.can_drop_weapon = spawn_spot.can_drop_weapon; + player.items |= IT_UNLIMITED_WEAPON_AMMO; + if ( spawn_spot.health ) + player.health = spawn_spot.health; + player.armorvalue = spawn_spot.armorvalue; + player.weapons = WepSet_FromWeapon(Weapons_fromstr(spawn_spot.weapon_name)); + if ( spawn_spot.netname ) + player.netname = spawn_spot.netname; + } + else + { + player.can_drop_weapon = true; + } +} + +MUTATOR_HOOKFUNCTION(sp, ForbidDropCurrentWeapon) +{ + entity player = M_ARGV(0, entity); + return !player.can_drop_weapon; +} diff --git a/qcsrc/server/mutators/mutator/gamemode_singleplayer.qh b/qcsrc/server/mutators/mutator/gamemode_singleplayer.qh new file mode 100644 index 000000000..039aceb13 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_singleplayer.qh @@ -0,0 +1,35 @@ +#pragma once + +#include "../gamemode.qh" + +void sp_DelayedInit(entity this); + +REGISTER_MUTATOR(sp, false) +{ + MUTATOR_ONADD + { + if (time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + InitializeEntity(NULL, sp_DelayedInit, INITPRIO_GAMETYPE); + + ActivateTeamplay(); + SetLimits(-1, -1, autocvar_timelimit_override, -1); + have_team_spawns = -1; // request team spawns + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back tdm_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} + -- 2.39.2